Java Code Examples for org.locationtech.spatial4j.context.SpatialContext

The following examples show how to use org.locationtech.spatial4j.context.SpatialContext. These examples are extracted from open source projects. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source Project: crate   Source File: WithinFunction.java    License: Apache License 2.0 6 votes vote down vote up
@SuppressWarnings("unchecked")
private static Shape parseLeftShape(Object left) {
    Shape shape;
    if (left instanceof Point) {
        Point point = (Point) left;
        shape = SpatialContext.GEO.getShapeFactory().pointXY(point.getX(), point.getY());
    } else if (left instanceof Double[]) {
        Double[] values = (Double[]) left;
        shape = SpatialContext.GEO.getShapeFactory().pointXY(values[0], values[1]);
    } else if (left instanceof String) {
        shape = GeoJSONUtils.wkt2Shape((String) left);
    } else {
        shape = GeoJSONUtils.map2Shape((Map<String, Object>) left);
    }
    return shape;
}
 
Example 2
@Override
public Value evaluate(ValueFactory valueFactory, Value... args) throws ValueExprEvaluationException {
	if (args.length != 1) {
		throw new ValueExprEvaluationException(getURI() + " requires exactly 1 argument, got " + args.length);
	}

	SpatialContext geoContext = SpatialSupport.getSpatialContext();
	Shape geom = FunctionArguments.getShape(this, args[0], geoContext);

	String wkt;
	try {
		wkt = SpatialSupport.getWktWriter().toWkt(operation(geom));
	} catch (IOException | RuntimeException e) {
		throw new ValueExprEvaluationException(e);
	}

	return valueFactory.createLiteral(wkt, GEO.WKT_LITERAL);
}
 
Example 3
@Override
public Value evaluate(ValueFactory valueFactory, Value... args) throws ValueExprEvaluationException {
	if (args.length != 2) {
		throw new ValueExprEvaluationException(getURI() + " requires exactly 2 arguments, got " + args.length);
	}

	SpatialContext geoContext = SpatialSupport.getSpatialContext();
	Shape geom1 = FunctionArguments.getShape(this, args[0], geoContext);
	Shape geom2 = FunctionArguments.getShape(this, args[1], geoContext);

	String wkt;
	try {
		Shape result = operation(geom1, geom2);
		wkt = SpatialSupport.getWktWriter().toWkt(result);
	} catch (IOException | RuntimeException e) {
		throw new ValueExprEvaluationException(e);
	}
	return valueFactory.createLiteral(wkt, GEO.WKT_LITERAL);
}
 
Example 4
Source Project: rdf4j   Source File: Buffer.java    License: BSD 3-Clause "New" or "Revised" License 6 votes vote down vote up
@Override
public Value evaluate(ValueFactory valueFactory, Value... args) throws ValueExprEvaluationException {
	if (args.length != 3) {
		throw new ValueExprEvaluationException(getURI() + " requires exactly 3 arguments, got " + args.length);
	}

	SpatialContext geoContext = SpatialSupport.getSpatialContext();
	Shape geom = FunctionArguments.getShape(this, args[0], geoContext);
	double radiusUom = FunctionArguments.getDouble(this, args[1]);
	IRI units = FunctionArguments.getUnits(this, args[2]);
	double radiusDegs = FunctionArguments.convertToDegrees(radiusUom, units);

	Shape buffered = SpatialSupport.getSpatialAlgebra().buffer(geom, radiusDegs);

	String wkt;
	try {
		wkt = SpatialSupport.getWktWriter().toWkt(buffered);
	} catch (IOException ioe) {
		throw new ValueExprEvaluationException(ioe);
	}
	return valueFactory.createLiteral(wkt, GEO.WKT_LITERAL);
}
 
Example 5
Source Project: lucene-solr   Source File: SpatialDocMaker.java    License: Apache License 2.0 6 votes vote down vote up
/**
 * Builds a SpatialStrategy from configuration options.
 */
protected SpatialStrategy makeSpatialStrategy(final Config config) {
  //A Map view of Config that prefixes keys with "spatial."
  Map<String, String> configMap = new AbstractMap<String, String>() {
    @Override
    public Set<Entry<String, String>> entrySet() {
      throw new UnsupportedOperationException();
    }

    @Override
    public String get(Object key) {
      return config.get("spatial." + key, null);
    }
  };

  SpatialContext ctx = SpatialContextFactory.makeSpatialContext(configMap, null);

  return makeSpatialStrategy(config, configMap, ctx);
}
 
Example 6
Source Project: vertexium   Source File: ElasticsearchSearchQueryBase.java    License: Apache License 2.0 6 votes vote down vote up
private ShapeBuilder getCircleBuilder(GeoCircle geoCircle) {
    // NOTE: as of ES7, storing circles is no longer supported so we need approximate the circle with a polygon
    double radius = geoCircle.getRadius();
    double maxSideLengthKm = getSearchIndex().getConfig().getGeocircleToPolygonSideLength();
    maxSideLengthKm = Math.min(radius, maxSideLengthKm);

    // calculate how many points we need to use given the length of a polygon side
    int numberOfPoints = (int) Math.ceil(Math.PI / Math.asin((maxSideLengthKm / (2 * radius))));
    numberOfPoints = Math.min(numberOfPoints, getSearchIndex().getConfig().getGeocircleToPolygonMaxNumSides());

    // Given the number of sides, loop through slices of 360 degrees and calculate the lat/lon at that radius and heading
    SpatialContext spatialContext = SpatialContext.GEO;
    DistanceCalculator distanceCalculator = spatialContext.getDistCalc();
    Point centerPoint = spatialContext.getShapeFactory().pointXY(DistanceUtils.normLonDEG(geoCircle.getLongitude()), DistanceUtils.normLatDEG(geoCircle.getLatitude()));
    ArrayList<GeoPoint> points = new ArrayList<>();
    for (float angle = 360; angle > 0; angle -= 360.0 / numberOfPoints) {
        Point point = distanceCalculator.pointOnBearing(centerPoint, geoCircle.getRadius() * KM_TO_DEG, angle, spatialContext, null);
        points.add(new GeoPoint(point.getY(), point.getX()));
    }

    // Polygons must start/end at the same point, so add the first point onto the end
    points.add(points.get(0));

    return getPolygonBuilder(new GeoPolygon(points, geoCircle.getDescription()));
}
 
Example 7
Source Project: lucene-solr   Source File: SpatialPrefixTreeFactory.java    License: Apache License 2.0 6 votes vote down vote up
/**
 * The factory is looked up via "prefixTree" in args, expecting "geohash" or "quad".
 * If it's neither of these, then "geohash" is chosen for a geo context, otherwise "quad" is chosen.
 * The "version" arg, if present, is parsed with {@link Version} and the prefix tree might be sensitive to it.
 */
public static SpatialPrefixTree makeSPT(Map<String,String> args, ClassLoader classLoader, SpatialContext ctx) {
  //TODO refactor to use Java SPI like how Lucene already does for codecs/postingsFormats, etc
  SpatialPrefixTreeFactory instance;
  String cname = args.get(PREFIX_TREE);
  if (cname == null)
    cname = ctx.isGeo() ? "geohash" : "quad";
  if ("geohash".equalsIgnoreCase(cname))
    instance = new GeohashPrefixTree.Factory();
  else if ("quad".equalsIgnoreCase(cname))
    instance = new QuadPrefixTree.Factory();
  else if ("packedQuad".equalsIgnoreCase(cname))
    instance = new PackedQuadPrefixTree.Factory();
  else if ("s2".equalsIgnoreCase(cname))
    instance = new S2PrefixTree.Factory();
  else {
    try {
      Class<?> c = classLoader.loadClass(cname);
      instance = (SpatialPrefixTreeFactory) c.getConstructor().newInstance();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  instance.init(args, ctx);
  return instance.newSPT();
}
 
Example 8
Source Project: lucene-solr   Source File: S2PrefixTreeTest.java    License: Apache License 2.0 6 votes vote down vote up
@Test
@Repeat(iterations = 10)
public void testPrecision() {
  int arity = random().nextInt(3) +1;
  SpatialContext context = new Geo3dSpatialContextFactory().newSpatialContext();
  S2PrefixTree tree = new S2PrefixTree(context, S2PrefixTree.getMaxLevels(arity), arity);
  double precision = random().nextDouble();
  int level = tree.getLevelForDistance(precision);
  Point point = context.getShapeFactory().pointXY(0, 0);
  CellIterator iterator = tree.getTreeCellIterator(point, level);
  S2PrefixTreeCell cell = null;
  while (iterator.hasNext()) {
    cell = (S2PrefixTreeCell)iterator.next();
  }
  assertTrue(cell.getLevel() == level);
  double precisionCell = S2Projections.MAX_WIDTH.getValue(cell.cellId.level());
  assertTrue(precision > precisionCell);
}
 
Example 9
Source Project: lucene-solr   Source File: SpatialArgs.java    License: Apache License 2.0 6 votes vote down vote up
/**
 * Computes the distance given a shape and the {@code distErrPct}.  The
 * algorithm is the fraction of the distance from the center of the query
 * shape to its closest bounding box corner.
 *
 * @param shape Mandatory.
 * @param distErrPct 0 to 0.5
 * @param ctx Mandatory
 * @return A distance (in degrees).
 */
public static double calcDistanceFromErrPct(Shape shape, double distErrPct, SpatialContext ctx) {
  if (distErrPct < 0 || distErrPct > 0.5) {
    throw new IllegalArgumentException("distErrPct " + distErrPct + " must be between [0 to 0.5]");
  }
  if (distErrPct == 0 || shape instanceof Point) {
    return 0;
  }
  Rectangle bbox = shape.getBoundingBox();
  //Compute the distance from the center to a corner.  Because the distance
  // to a bottom corner vs a top corner can vary in a geospatial scenario,
  // take the closest one (greater precision).
  Point ctr = bbox.getCenter();
  double y = (ctr.getY() >= 0 ? bbox.getMaxY() : bbox.getMinY());
  double diagonalDist = ctx.getDistCalc().distance(ctr, bbox.getMaxX(), y);
  return diagonalDist * distErrPct;
}
 
Example 10
Source Project: lucene-solr   Source File: Geo3dDistanceCalculator.java    License: Apache License 2.0 6 votes vote down vote up
@Override
public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) {
  Geo3dPointShape geoFrom = (Geo3dPointShape) from;
  GeoPoint point = (GeoPoint) geoFrom.shape;
  double dist = DistanceUtils.DEGREES_TO_RADIANS * distDEG;
  double bearing = DistanceUtils.DEGREES_TO_RADIANS * bearingDEG;
  GeoPoint newPoint = planetModel.surfacePointOnBearing(point, dist, bearing);
  double newLat = newPoint.getLatitude() * DistanceUtils.RADIANS_TO_DEGREES;
  double newLon = newPoint.getLongitude() * DistanceUtils.RADIANS_TO_DEGREES;
  if (reuse != null) {
    reuse.reset(newLon, newLat);
    return reuse;
  }
  else {
    return ctx.getShapeFactory().pointXY(newLon, newLat);
  }
}
 
Example 11
Source Project: lucene-solr   Source File: SpatialArgsTest.java    License: Apache License 2.0 6 votes vote down vote up
@Test
public void calcDistanceFromErrPct() {
  final SpatialContext ctx = usually() ? SpatialContext.GEO : new Geo3dSpatialContextFactory().newSpatialContext();
  final double DEP = 0.5;//distErrPct

  //the result is the diagonal distance from the center to the closest corner,
  // times distErrPct

  Shape superwide = ctx.makeRectangle(-180, 180, 0, 0);
  //0 distErrPct means 0 distance always
  assertEquals(0, SpatialArgs.calcDistanceFromErrPct(superwide, 0, ctx), 0);
  assertEquals(180 * DEP, SpatialArgs.calcDistanceFromErrPct(superwide, DEP, ctx), 0);

  Shape supertall = ctx.makeRectangle(0, 0, -90, 90);
  assertEquals(90 * DEP, SpatialArgs.calcDistanceFromErrPct(supertall, DEP, ctx), 0);

  Shape upperhalf = ctx.makeRectangle(-180, 180, 0, 90);
  assertEquals(45 * DEP, SpatialArgs.calcDistanceFromErrPct(upperhalf, DEP, ctx), 0.0001);

  Shape midCircle = ctx.makeCircle(0, 0, 45);
  assertEquals(60 * DEP, SpatialArgs.calcDistanceFromErrPct(midCircle, DEP, ctx), 0.0001);
}
 
Example 12
Source Project: lucene-solr   Source File: LatLonPointSpatialField.java    License: Apache License 2.0 5 votes vote down vote up
public LatLonPointSpatialStrategy(SpatialContext ctx, String fieldName, boolean indexed, boolean docValues) {
  super(ctx, fieldName);
  if (!ctx.isGeo()) {
    throw new IllegalArgumentException("ctx must be geo=true: " + ctx);
  }
  this.indexed = indexed;
  this.docValues = docValues;
}
 
Example 13
public void setupGeohashGrid(int maxLevels) {
  this.ctx = SpatialContext.GEO;
  //A fairly shallow grid, and default 2.5% distErrPct
  if (maxLevels == -1)
    maxLevels = randomIntBetween(1, 3);//max 16k cells (32^3)
  this.grid = new GeohashPrefixTree(ctx, maxLevels);
  this.strategy = newRPT();
}
 
Example 14
public ElasticsearchDocument(String id, String type, String index, String resourceId, String context,
		Function<? super String, ? extends SpatialContext> geoContextMapper) {
	this(id, type, index, Versions.MATCH_ANY, new HashMap<>(), geoContextMapper);
	fields.put(SearchFields.URI_FIELD_NAME, resourceId);
	if (context != null) {
		fields.put(SearchFields.CONTEXT_FIELD_NAME, context);
	}
}
 
Example 15
public ElasticsearchDocument(String id, String type, String index, long version, Map<String, Object> fields,
		Function<? super String, ? extends SpatialContext> geoContextMapper) {
	this.id = id;
	this.type = type;
	this.version = version;
	this.index = index;
	this.fields = fields;
	this.geoContextMapper = geoContextMapper;
}
 
Example 16
protected Function<? super String, ? extends SpatialContext> createSpatialContextMapper(
		Map<String, String> parameters) {
	// this should really be based on the schema
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	SpatialContext geoContext = SpatialContextFactory.makeSpatialContext(parameters, classLoader);
	return Functions.constant(geoContext);
}
 
Example 17
private boolean supportsShapes(String field) {
	SpatialContext geoContext = geoContextMapper.apply(field);
	try {
		geoContext.readShapeFromWkt("POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))");
		return true;
	} catch (ParseException e) {
		return false;
	}
}
 
Example 18
private void setupCtx2D(SpatialContext ctx) {
  if (!ctx.isGeo())
    ctx2D = ctx;
  //A non-geo version of ctx.
  SpatialContextFactory ctxFactory = new SpatialContextFactory();
  ctxFactory.geo = false;
  ctxFactory.worldBounds = ctx.getWorldBounds();
  ctx2D = ctxFactory.newSpatialContext();
}
 
Example 19
Source Project: rdf4j   Source File: SolrIndex.java    License: BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
protected Function<? super String, ? extends SpatialContext> createSpatialContextMapper(
		Map<String, String> parameters) {
	// this should really be based on the schema
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	SpatialContext geoContext = SpatialContextFactory.makeSpatialContext(parameters, classLoader);
	return Functions.constant(geoContext);
}
 
Example 20
Source Project: lucene-solr   Source File: HeatmapFacetCounterTest.java    License: Apache License 2.0 5 votes vote down vote up
@Before
public void setUp() throws Exception {
  super.setUp();
  cellsValidated = cellValidatedNonZero = 0;
  ctx = SpatialContext.GEO;
  shapeFactory = ctx.getShapeFactory();
  grid = new QuadPrefixTree(ctx, randomIntBetween(1, 8));
  strategy = new RecursivePrefixTreeStrategy(grid, getTestClass().getSimpleName());
  if (rarely()) {
    ((PrefixTreeStrategy) strategy).setPointsOnly(true);
  }
}
 
Example 21
Source Project: lucene-solr   Source File: GeoJSONResponseWriter.java    License: Apache License 2.0 5 votes vote down vote up
@Override
public void write(Writer writer, SolrQueryRequest req, SolrQueryResponse rsp) throws IOException {
  
  String geofield = req.getParams().get(FIELD, null);
  if(geofield==null || geofield.length()==0) {
    throw new SolrException(ErrorCode.BAD_REQUEST, "GeoJSON.  Missing parameter: '"+FIELD+"'");
  }
  
  SchemaField sf = req.getSchema().getFieldOrNull(geofield);
  if(sf==null) {
    throw new SolrException(ErrorCode.BAD_REQUEST, "GeoJSON.  Unknown field: '"+FIELD+"'="+geofield);
  }
  
  SupportedFormats formats = null;
  if(sf.getType() instanceof AbstractSpatialFieldType) {
    SpatialContext ctx = ((AbstractSpatialFieldType)sf.getType()).getSpatialContext();
    formats = ctx.getFormats();
  }

  JSONWriter w = new GeoJSONWriter(writer, req, rsp, 
      geofield,
      formats); 
  
  try {
    w.writeResponse();
  } finally {
    w.close();
  }
}
 
Example 22
/**
 * Get the geo shape
 *
 * @param func    function
 * @param v       value
 * @param context
 * @return shape
 * @throws ValueExprEvaluationException
 */
public static Shape getShape(Function func, Value v, SpatialContext context) throws ValueExprEvaluationException {
	Literal wktLiteral = getLiteral(func, v, GEO.WKT_LITERAL);
	try {
		ShapeReader reader = context.getFormats().getWktReader();
		return reader.read(wktLiteral.getLabel());
	} catch (IOException | InvalidShapeException | ParseException e) {
		throw new ValueExprEvaluationException("Invalid argument for " + func.getURI() + ": " + wktLiteral, e);
	}
}
 
Example 23
Source Project: lucene-solr   Source File: CompositeStrategyTest.java    License: Apache License 2.0 5 votes vote down vote up
private void setupGeohashGrid(int maxLevels) {
  this.ctx = SpatialContext.GEO;
  //A fairly shallow grid
  if (maxLevels == -1)
    maxLevels = randomIntBetween(1, 3);//max 16k cells (32^3)
  this.grid = new GeohashPrefixTree(ctx, maxLevels);
  this.rptStrategy = newRPT();
}
 
Example 24
Source Project: lucene-solr   Source File: SpatialUtils.java    License: Apache License 2.0 5 votes vote down vote up
/** Calls {@link #parsePoint(String, org.locationtech.spatial4j.context.SpatialContext)} and wraps
 * the exception with {@link org.apache.solr.common.SolrException} with a helpful message. */
public static Point parsePointSolrException(String externalVal, SpatialContext ctx) throws SolrException {
  try {
    return parsePoint(externalVal, ctx);
  } catch (InvalidShapeException e) {
    String message = e.getMessage();
    if (!message.contains(externalVal))
      message = "Can't parse point '" + externalVal + "' because: " + message;
    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, message, e);
  }
}
 
Example 25
Source Project: lucene-solr   Source File: SpatialDocMaker.java    License: Apache License 2.0 5 votes vote down vote up
protected SpatialStrategy makeSpatialStrategy(final Config config, Map<String, String> configMap,
                                              SpatialContext ctx) {
  //TODO once strategies have factories, we could use them here.
  final String strategyName = config.get("spatial.strategy", "rpt");
  switch (strategyName) {
    case "rpt": return makeRPTStrategy(SPATIAL_FIELD, config, configMap, ctx);
    case "composite": return makeCompositeStrategy(config, configMap, ctx);
    //TODO add more as-needed
    default: throw new IllegalStateException("Unknown spatial.strategy: " + strategyName);
  }
}
 
Example 26
Source Project: lucene-solr   Source File: SpatialDocMaker.java    License: Apache License 2.0 5 votes vote down vote up
protected SpatialStrategy makeCompositeStrategy(Config config, Map<String, String> configMap, SpatialContext ctx) {
  final CompositeSpatialStrategy strategy = new CompositeSpatialStrategy(
      SPATIAL_FIELD, makeRPTStrategy(SPATIAL_FIELD + "_rpt", config, configMap, ctx),
      makeSerializedDVStrategy(SPATIAL_FIELD + "_sdv", config, configMap, ctx)
  );
  strategy.setOptimizePredicates(config.get("query.spatial.composite.optimizePredicates", true));
  return strategy;
}
 
Example 27
Source Project: lucene-solr   Source File: GeohashPrefixTree.java    License: Apache License 2.0 5 votes vote down vote up
public GeohashPrefixTree(SpatialContext ctx, int maxLevels) {
  super(ctx, maxLevels);
  Rectangle bounds = ctx.getWorldBounds();
  if (bounds.getMinX() != -180)
    throw new IllegalArgumentException("Geohash only supports lat-lon world bounds. Got "+bounds);
  int MAXP = getMaxLevelsPossible();
  if (maxLevels <= 0 || maxLevels > MAXP)
    throw new IllegalArgumentException("maxLevels must be [1-"+MAXP+"] but got "+ maxLevels);
}
 
Example 28
@Test
public void testShapePair() {
  ctx = SpatialContext.GEO;
  setupCtx2D(ctx);

  Shape leftShape = new ShapePair(ctx.makeRectangle(-74, -56, -8, 1), ctx.makeRectangle(-180, 134, -90, 90), true);
  Shape queryShape = ctx.makeRectangle(-180, 180, -90, 90);
  assertEquals(SpatialRelation.WITHIN, leftShape.relate(queryShape));
}
 
Example 29
Source Project: lucene-solr   Source File: S2PrefixTree.java    License: Apache License 2.0 5 votes vote down vote up
/**
 * Creates a S2 spatial tree with provided arity.
 *
 * @param ctx The provided spatial context. The shape factor of the spatial context
 *           must implement {@link S2ShapeFactory}
 * @param maxLevels The provided maximum level for this tree.
 * @param arity The arity of the tree.
 */
public S2PrefixTree(SpatialContext ctx, int maxLevels, int arity) {
    super(ctx, maxLevels);
    if (!(ctx.getShapeFactory() instanceof S2ShapeFactory)) {
        throw new IllegalArgumentException("Spatial context does not support S2 spatial index.");
    }
    this.s2ShapeFactory = (S2ShapeFactory) ctx.getShapeFactory();
    if (arity <1 || arity > 3) {
        throw new IllegalArgumentException("Invalid value for S2 tree arity. Possible values are 1, 2 or 3. Provided value is " + arity  + ".");
    }
    this.arity = arity;
}
 
Example 30
Source Project: lucene-solr   Source File: TestSolr4Spatial2.java    License: Apache License 2.0 5 votes vote down vote up
@Test @Repeat(iterations = 10)
public void testLLPDecodeIsStableAndPrecise() throws Exception {
  // test that LatLonPointSpatialField decode of docValue will round-trip (re-index then re-decode) to the same value
  @SuppressWarnings({"resource", "IOResourceOpenedButNotSafelyClosed"})
  SolrClient client = new EmbeddedSolrServer(h.getCore());// do NOT close it; it will close Solr

  final String fld = "llp_1_dv_dvasst";
  String ptOrig = GeoTestUtil.nextLatitude() + "," + GeoTestUtil.nextLongitude();
  assertU(adoc("id", "0", fld, ptOrig));
  assertU(commit());
  // retrieve it (probably less precision)
  String ptDecoded1 = (String) client.query(params("q", "id:0")).getResults().get(0).get(fld);
  // now write it back
  assertU(adoc("id", "0", fld, ptDecoded1));
  assertU(commit());
  // retrieve it; assert that it's the same as written
  String ptDecoded2 = (String) client.query(params("q", "id:0")).getResults().get(0).get(fld);
  assertEquals("orig:" + ptOrig, ptDecoded1, ptDecoded2);

  // test that the representation is pretty accurate
  final Point ptOrigObj = SpatialUtils.parsePoint(ptOrig, SpatialContext.GEO);
  final Point ptDecodedObj = SpatialUtils.parsePoint(ptDecoded1, SpatialContext.GEO);
  double deltaCentimeters = SpatialContext.GEO.calcDistance(ptOrigObj, ptDecodedObj) * DistanceUtils.DEG_TO_KM * 1000.0 * 100.0;
  //See javadocs of LatLonDocValuesField for these constants
  final Point absErrorPt = SpatialContext.GEO.getShapeFactory().pointXY(8.381903171539307E-8, 4.190951585769653E-8);
  double deltaCentimetersMax
      = SpatialContext.GEO.calcDistance(absErrorPt, 0,0) * DistanceUtils.DEG_TO_KM * 1000.0 * 100.0;
  assertEquals(1.0420371840922256, deltaCentimetersMax, 0.0);// just so that we see it in black & white in the test

  //max found by trial & error.  If we used 8 decimal places then we could get down to 1.04cm accuracy but then we
  // lose the ability to round-trip -- 40 would become 39.99999997  (ugh).
  assertTrue("deltaCm too high: " + deltaCentimeters, deltaCentimeters < 1.41);
  // Pt(x=105.29894270124083,y=-0.4371673760042398) to  Pt(x=105.2989428,y=-0.4371673) is 1.38568
}