private static Shape fromSpatial4JShape(com.spatial4j.core.shape.Shape shape) { if (shape instanceof com.spatial4j.core.shape.Point) { com.spatial4j.core.shape.Point point = (com.spatial4j.core.shape.Point) shape; return point(point.getX(), point.getY()); } if (shape instanceof com.spatial4j.core.shape.Circle) { com.spatial4j.core.shape.Circle circle = (com.spatial4j.core.shape.Circle) shape; return circle( point(circle.getCenter().getX(), circle.getCenter().getY()), circle.getRadius()); } if (shape instanceof com.spatial4j.core.shape.Rectangle) { com.spatial4j.core.shape.Rectangle rectangle = (com.spatial4j.core.shape.Rectangle) shape; return rectangle( rectangle.getMinX(), rectangle.getMaxX(), rectangle.getMinY(), rectangle.getMaxY()); } if (shape instanceof com.spatial4j.core.shape.impl.BufferedLineString) { com.spatial4j.core.shape.impl.BufferedLineString spatialLineString = (com.spatial4j.core.shape.impl.BufferedLineString) shape; List<com.spatial4j.core.shape.Point> spatialPoints = spatialLineString.getPoints(); Point[] points = new Point[spatialPoints.size()]; for (int i = 0; i < points.length; i++) points[i] = point(spatialPoints.get(i).getX(), spatialPoints.get(i).getY()); return lineString(points); } if (shape instanceof com.spatial4j.core.shape.jts.JtsGeometry) return fromJtsGeometry((JtsGeometry) shape); throw new IllegalArgumentException("Unsupported shape type: " + shape.getClass().getName()); }
private Query getQuery(Function inner, Context context) { RefLiteralPair innerPair = new RefLiteralPair(inner); if (!innerPair.isValid()) { return null; } GeoPointFieldMapper mapper = getGeoPointFieldMapper( innerPair.reference().info().ident().columnIdent().fqn(), context.mapperService); Shape shape = (Shape) innerPair.input().value(); Geometry geometry = JtsSpatialContext.GEO.getGeometryFrom(shape); IndexGeoPointFieldData fieldData = context.fieldDataService.getForField(mapper); Filter filter; if (geometry.isRectangle()) { Rectangle boundingBox = shape.getBoundingBox(); filter = new InMemoryGeoBoundingBoxFilter( new GeoPoint(boundingBox.getMaxY(), boundingBox.getMinX()), new GeoPoint(boundingBox.getMinY(), boundingBox.getMaxX()), fieldData); } else { Coordinate[] coordinates = geometry.getCoordinates(); GeoPoint[] points = new GeoPoint[coordinates.length]; for (int i = 0; i < coordinates.length; i++) { Coordinate coordinate = coordinates[i]; points[i] = new GeoPoint(coordinate.y, coordinate.x); } filter = new GeoPolygonFilter(fieldData, points); } return new FilteredQuery( Queries.newMatchAllQuery(), context.indexCache.filter().cache(filter)); }
private SpatialRelation relateRectangleCircleWrapsPole(Rectangle r, SpatialContext ctx) { // This method handles the case where the circle wraps ONE pole, but not both. For both, // there is the inverseCircle case handled before now. The only exception is for the case where // the circle covers the entire globe, and we'll check that first. if (radiusDEG == 180) // whole globe return SpatialRelation.CONTAINS; // Check if r is within the pole wrap region: double yTop = getCenter().getY() + radiusDEG; if (yTop > 90) { double yTopOverlap = yTop - 90; assert yTopOverlap <= 90; if (r.getMinY() >= 90 - yTopOverlap) return SpatialRelation.CONTAINS; } else { double yBot = point.getY() - radiusDEG; if (yBot < -90) { double yBotOverlap = -90 - yBot; assert yBotOverlap <= 90; if (r.getMaxY() <= -90 + yBotOverlap) return SpatialRelation.CONTAINS; } else { // This point is probably not reachable ?? assert yTop == 90 || yBot == -90; // we simply touch a pole // continue } } // If there are no corners to check intersection because r wraps completely... if (r.getWidth() == 360) return SpatialRelation.INTERSECTS; // Check corners: int cornersIntersect = numCornersIntersect(r); // (It might be possible to reduce contains() calls within nCI() to exactly two, but this // intersection // code is complicated enough as it is.) double frontX = getCenter().getX(); if (cornersIntersect == 4) { // all double backX = frontX <= 0 ? frontX + 180 : frontX - 180; if (r.relateXRange(backX, backX).intersects()) return SpatialRelation.INTERSECTS; else return SpatialRelation.CONTAINS; } else if (cornersIntersect == 0) { // none if (r.relateXRange(frontX, frontX).intersects()) return SpatialRelation.INTERSECTS; else return SpatialRelation.DISJOINT; } else // partial return SpatialRelation.INTERSECTS; }
protected Rectangle randomRectangle() { final Rectangle WB = ctx.getWorldBounds(); int rW = (int) randomGaussianMeanMax(10, WB.getWidth()); double xMin = randomIntBetween((int) WB.getMinX(), (int) WB.getMaxX() - rW); double xMax = xMin + rW; int yH = (int) randomGaussianMeanMax(Math.min(rW, WB.getHeight()), WB.getHeight()); double yMin = randomIntBetween((int) WB.getMinY(), (int) WB.getMaxY() - yH); double yMax = yMin + yH; return ctx.makeRectangle(xMin, xMax, yMin, yMax); }
/** * Called after bounding box is intersected. * * @param bboxSect INTERSECTS or CONTAINS from enclosingBox's intersection * @return DISJOINT, CONTAINS, or INTERSECTS (not WITHIN) */ @Override protected SpatialRelation relateRectanglePhase2(Rectangle r, SpatialRelation bboxSect) { // Rectangle wraps around the world longitudinally creating a solid band; there are no corners // to test intersection if (r.getWidth() == 360) { return SpatialRelation.INTERSECTS; } if (inverseCircle != null) { return inverseCircle.relate(r).inverse(); } // if a pole is wrapped, we have a separate algorithm if (enclosingBox.getWidth() == 360) { return relateRectangleCircleWrapsPole(r, ctx); } // This is an optimization path for when there are no dateline or pole issues. if (!enclosingBox.getCrossesDateLine() && !r.getCrossesDateLine()) { return super.relateRectanglePhase2(r, bboxSect); } // do quick check to see if all corners are within this circle for CONTAINS int cornersIntersect = numCornersIntersect(r); if (cornersIntersect == 4) { // ensure r's x axis is within c's. If it doesn't, r sneaks around the globe to touch the // other side (intersect). SpatialRelation xIntersect = r.relateXRange(enclosingBox.getMinX(), enclosingBox.getMaxX()); if (xIntersect == SpatialRelation.WITHIN) return SpatialRelation.CONTAINS; return SpatialRelation.INTERSECTS; } // INTERSECT or DISJOINT ? if (cornersIntersect > 0) return SpatialRelation.INTERSECTS; // Now we check if one of the axis of the circle intersect with r. If so we have // intersection. /* x axis intersects */ if (r.relateYRange(getYAxis(), getYAxis()).intersects() // at y vertical && r.relateXRange(enclosingBox.getMinX(), enclosingBox.getMaxX()).intersects()) return SpatialRelation.INTERSECTS; /* y axis intersects */ if (r.relateXRange(getXAxis(), getXAxis()).intersects()) { // at x horizontal double yTop = getCenter().getY() + radiusDEG; assert yTop <= 90; double yBot = getCenter().getY() - radiusDEG; assert yBot >= -90; if (r.relateYRange(yBot, yTop).intersects()) // back bottom return SpatialRelation.INTERSECTS; } return SpatialRelation.DISJOINT; }
@Test public void testCellTraverse() { trie = new GeohashPrefixTree(ctx, 4); Cell prevC = null; Cell c = trie.getWorldCell(); assertEquals(0, c.getLevel()); assertEquals(ctx.getWorldBounds(), c.getShape()); while (c.getLevel() < trie.getMaxLevels()) { prevC = c; List<Cell> subCells = new ArrayList<>(); CellIterator subCellsIter = c.getNextLevelCells(null); while (subCellsIter.hasNext()) { subCells.add(subCellsIter.next()); } c = subCells.get(random().nextInt(subCells.size() - 1)); assertEquals(prevC.getLevel() + 1, c.getLevel()); Rectangle prevNShape = (Rectangle) prevC.getShape(); Shape s = c.getShape(); Rectangle sbox = s.getBoundingBox(); assertTrue(prevNShape.getWidth() > sbox.getWidth()); assertTrue(prevNShape.getHeight() > sbox.getHeight()); } }
@Override public Rectangle calcBoxByDistFromPt( Point from, double distDEG, SpatialContext ctx, Rectangle reuse) { double minX = from.getX() - distDEG; double maxX = from.getX() + distDEG; double minY = from.getY() - distDEG; double maxY = from.getY() + distDEG; if (reuse == null) { return ctx.makeRectangle(minX, maxX, minY, maxY); } else { reuse.reset(minX, maxX, minY, maxY); return reuse; } }
/** Returns either 0 for none, 1 for some, or 4 for all. */ private int numCornersIntersect(Rectangle r) { // We play some logic games to avoid calling contains() which can be expensive. boolean bool; // if true then all corners intersect, if false then no corners intersect // for partial, we exit early with 1 and ignore bool. bool = (contains(r.getMinX(), r.getMinY())); if (contains(r.getMinX(), r.getMaxY())) { if (!bool) return 1; // partial } else { if (bool) return 1; // partial } if (contains(r.getMaxX(), r.getMinY())) { if (!bool) return 1; // partial } else { if (bool) return 1; // partial } if (contains(r.getMaxX(), r.getMaxY())) { if (!bool) return 1; // partial } else { if (bool) return 1; // partial } return bool ? 4 : 0; }
protected Point randomPoint() { final Rectangle WB = ctx.getWorldBounds(); return ctx.makePoint( randomIntBetween((int) WB.getMinX(), (int) WB.getMaxX()), randomIntBetween((int) WB.getMinY(), (int) WB.getMaxY())); }
/** * Spatial4J shapes have no knowledge of directed edges. For this reason, a bounding box that * wraps the dateline can have a min longitude that is mathematically > than the Rectangles' * minX value. This is an issue for geometric collections (e.g., MultiPolygon and ShapeCollection) * Until geometry logic can be cleaned up in Spatial4J, ES provides the following expansion * algorithm for GeometryCollections */ private Rectangle expandBBox(Rectangle bbox, Rectangle expand) { if (bbox.equals(expand) || bbox.equals(SpatialContext.GEO.getWorldBounds())) { return bbox; } double minX = bbox.getMinX(); double eMinX = expand.getMinX(); double maxX = bbox.getMaxX(); double eMaxX = expand.getMaxX(); double minY = bbox.getMinY(); double eMinY = expand.getMinY(); double maxY = bbox.getMaxY(); double eMaxY = expand.getMaxY(); bbox.reset( Math.min(Math.min(minX, maxX), Math.min(eMinX, eMaxX)), Math.max(Math.max(minX, maxX), Math.max(eMinX, eMaxX)), Math.min(Math.min(minY, maxY), Math.min(eMinY, eMaxY)), Math.max(Math.max(minY, maxY), Math.max(eMinY, eMaxY))); return bbox; }
@Override public double area(Rectangle rect) { return rect.getArea(null); }
@Test public void geohashRecursiveRandom() { final String fieldName = "geohashRecursiveRandom"; // has length 12 // 1. Iterate test with the cluster at some worldly point of interest Point[] clusterCenters = new Point[] {new PointImpl(0, 0), new PointImpl(0, 90), new PointImpl(0, -90)}; for (Point clusterCenter : clusterCenters) { // 2. Iterate on size of cluster (a really small one and a large one) String hashCenter = GeohashUtils.encodeLatLon(clusterCenter.getY(), clusterCenter.getX(), PRECISION); // calculate the number of degrees in the smallest grid box size (use for both lat & lon) String smallBox = hashCenter.substring(0, hashCenter.length() - 1); // chop off leaf precision Rectangle clusterDims = GeohashUtils.decodeBoundary(smallBox, ctx); double smallDegrees = Math.max( clusterDims.getMaxX() - clusterDims.getMinX(), clusterDims.getMaxY() - clusterDims.getMinY()); assert smallDegrees < 1; double largeDegrees = 20d; // good large size; don't use >=45 for this test code to work double[] sideDegrees = {largeDegrees, smallDegrees}; for (double sideDegree : sideDegrees) { // 3. Index random points in this cluster box clearIndex(); List<Point> points = new ArrayList<Point>(); for (int i = 0; i < 20; i++) { double x = random.nextDouble() * sideDegree - sideDegree / 2 + clusterCenter.getX(); double y = random.nextDouble() * sideDegree - sideDegree / 2 + clusterCenter.getY(); final Point pt = normPointXY(x, y); points.add(pt); assertU(adoc("id", "" + i, fieldName, pt.getY() + "," + pt.getX())); } assertU(commit()); // 3. Use 4 query centers. Each is radially out from each corner of cluster box by twice // distance to box edge. for (double qcXoff : new double[] {sideDegree, -sideDegree}) { // query-center X offset from cluster center for (double qcYoff : new double[] {sideDegree, -sideDegree}) { // query-center Y offset from cluster center Point queryCenter = normPointXY(qcXoff + clusterCenter.getX(), qcYoff + clusterCenter.getY()); double[] distRange = calcDistRange(queryCenter, clusterCenter, sideDegree); // 4.1 query a small box getting nothing final String queryCenterStr = queryCenter.getY() + "," + queryCenter.getX(); checkHits(fieldName, queryCenterStr, distRange[0] * 0.99, 0); // 4.2 Query a large box enclosing the cluster, getting everything checkHits(fieldName, queryCenterStr, distRange[1] * 1.01, points.size()); // 4.3 Query a medium box getting some (calculate the correct solution and verify) double queryDist = distRange[0] + (distRange[1] - distRange[0]) / 2; // average // Find matching points. Put into int[] of doc ids which is the same thing as the index // into points list. int[] ids = new int[points.size()]; int ids_sz = 0; for (int i = 0; i < points.size(); i++) { Point point = points.get(i); if (calcDist(queryCenter, point) <= queryDist) ids[ids_sz++] = i; } ids = Arrays.copyOf(ids, ids_sz); // assert ids_sz > 0 (can't because randomness keeps us from being able to) checkHits(fieldName, queryCenterStr, queryDist, ids.length, ids); } } } // for sideDegree } // for clusterCenter } // randomTest()