diff --git a/lucene/core/src/java/org/apache/lucene/.DS_Store b/lucene/core/src/java/org/apache/lucene/.DS_Store new file mode 100644 index 000000000000..1a0f40167148 Binary files /dev/null and b/lucene/core/src/java/org/apache/lucene/.DS_Store differ diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java index c3ecc8aed219..45f3f3fc9d77 100644 --- a/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java +++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java @@ -16,7 +16,9 @@ */ package org.apache.lucene.document; +import org.apache.lucene.geo.Component; import org.apache.lucene.geo.Polygon; +import org.apache.lucene.geo.Polygon2D; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.PointValues; import org.apache.lucene.search.BooleanClause; @@ -251,7 +253,8 @@ public static Query newDistanceQuery(String field, double latitude, double longi * @see Polygon */ public static Query newPolygonQuery(String field, Polygon... polygons) { - return new LatLonPointInPolygonQuery(field, polygons); + Component component = Polygon2D.create(polygons); + return new LatLonPointInComponentQuery(field, component); } /** diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPointInComponentQuery.java similarity index 79% rename from lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java rename to lucene/core/src/java/org/apache/lucene/document/LatLonPointInComponentQuery.java index 6907b1dbe096..9fe91aece1fa 100644 --- a/lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java +++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPointInComponentQuery.java @@ -18,10 +18,10 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Objects; +import org.apache.lucene.geo.Component; import org.apache.lucene.geo.GeoEncodingUtils; -import org.apache.lucene.geo.Polygon; -import org.apache.lucene.geo.Polygon2D; import org.apache.lucene.geo.Rectangle; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.LeafReader; @@ -45,34 +45,25 @@ import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude; import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude; -/** Finds all previously indexed points that fall within the specified polygons. +/** Finds all previously indexed points that fall within the specified {@link Component}. * - *

The field must be indexed with using {@link org.apache.lucene.document.LatLonPoint} added per document. + *

The field must be indexed with using {@link LatLonPoint} added per document. * * @lucene.experimental */ -final class LatLonPointInPolygonQuery extends Query { +final class LatLonPointInComponentQuery extends Query { final String field; - final Polygon[] polygons; + final Component component; - LatLonPointInPolygonQuery(String field, Polygon[] polygons) { + LatLonPointInComponentQuery(String field, Component component) { if (field == null) { throw new IllegalArgumentException("field must not be null"); } - if (polygons == null) { - throw new IllegalArgumentException("polygons must not be null"); - } - if (polygons.length == 0) { - throw new IllegalArgumentException("polygons must not be empty"); - } - for (int i = 0; i < polygons.length; i++) { - if (polygons[i] == null) { - throw new IllegalArgumentException("polygon[" + i + "] must not be null"); - } + if (component == null) { + throw new IllegalArgumentException("component must not be null"); } this.field = field; - this.polygons = polygons.clone(); - // TODO: we could also compute the maximal inner bounding box, to make relations faster to compute? + this.component = component; } @Override @@ -87,10 +78,10 @@ public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float bo // I don't use RandomAccessWeight here: it's no good to approximate with "match all docs"; this is an inverted structure and should be // used in the first pass: - - // bounding box over all polygons, this can speed up tree intersection/cheaply improve approximation for complex multi-polygons + + // bounding box of the component, this can speed up tree intersection/cheaply improve approximation for complex components // these are pre-encoded with LatLonPoint's encoding - final Rectangle box = Rectangle.fromPolygon(polygons); + final Rectangle box = component.getBoundingBox(); final byte minLat[] = new byte[Integer.BYTES]; final byte maxLat[] = new byte[Integer.BYTES]; final byte minLon[] = new byte[Integer.BYTES]; @@ -100,8 +91,7 @@ public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float bo NumericUtils.intToSortableBytes(encodeLongitude(box.minLon), minLon, 0); NumericUtils.intToSortableBytes(encodeLongitude(box.maxLon), maxLon, 0); - final Polygon2D tree = Polygon2D.create(polygons); - final GeoEncodingUtils.PolygonPredicate polygonPredicate = GeoEncodingUtils.createPolygonPredicate(polygons, tree); + final GeoEncodingUtils.ComponentPredicate componentPredicate = GeoEncodingUtils.createComponentPredicate(component); return new ConstantScoreWeight(this, boost) { @@ -123,7 +113,7 @@ public Scorer scorer(LeafReaderContext context) throws IOException { // matching docids DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field); - values.intersect( + values.intersect( new IntersectVisitor() { DocIdSetBuilder.BulkAdder adder; @@ -140,7 +130,7 @@ public void visit(int docID) { @Override public void visit(int docID, byte[] packedValue) { - if (polygonPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0), + if (componentPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0), NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES))) { adder.add(docID); } @@ -155,13 +145,13 @@ public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) { // outside of global bounding box range return Relation.CELL_OUTSIDE_QUERY; } - + double cellMinLat = decodeLatitude(minPackedValue, 0); double cellMinLon = decodeLongitude(minPackedValue, Integer.BYTES); double cellMaxLat = decodeLatitude(maxPackedValue, 0); double cellMaxLon = decodeLongitude(maxPackedValue, Integer.BYTES); - return tree.relate(cellMinLat, cellMaxLat, cellMinLon, cellMaxLon); + return component.relate(cellMinLat, cellMaxLat, cellMinLon, cellMaxLon); } }); @@ -180,17 +170,12 @@ public String getField() { return field; } - /** Returns a copy of the internal polygon array */ - public Polygon[] getPolygons() { - return polygons.clone(); - } - @Override public int hashCode() { final int prime = 31; int result = classHash(); result = prime * result + field.hashCode(); - result = prime * result + Arrays.hashCode(polygons); + result = prime * result + component.hashCode(); return result; } @@ -200,9 +185,9 @@ public boolean equals(Object other) { equalsTo(getClass().cast(other)); } - private boolean equalsTo(LatLonPointInPolygonQuery other) { + private boolean equalsTo(LatLonPointInComponentQuery other) { return field.equals(other.field) && - Arrays.equals(polygons, other.polygons); + Objects.equals(component, other.component); } @Override @@ -215,7 +200,7 @@ public String toString(String field) { sb.append(this.field); sb.append(':'); } - sb.append(Arrays.toString(polygons)); + sb.append(component.toString()); return sb.toString(); } } diff --git a/lucene/core/src/java/org/apache/lucene/geo/Component.java b/lucene/core/src/java/org/apache/lucene/geo/Component.java new file mode 100644 index 000000000000..9878082164b6 --- /dev/null +++ b/lucene/core/src/java/org/apache/lucene/geo/Component.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.lucene.geo; + +import org.apache.lucene.index.PointValues; + +import static org.apache.lucene.geo.GeoUtils.orient; + +/** + * Geometry object that supports spatial relationships with bounding boxes, + * triangles and points. + * + * @lucene.internal + */ +public interface Component { + /** relates this component with a point **/ + boolean contains(double lat, double lon); + + /** relates this component with a bounding box **/ + PointValues.Relation relate(double minY, double maxY, double minX, double maxX); + + /** relates this component with a triangle **/ + PointValues.Relation relateTriangle(double ax, double ay, double bx, double by, double cx, double cy); + + /** bounding box for this component **/ + Rectangle getBoundingBox(); + + //This should be moved when LatLonShape is moved from sandbox! + /** + * Compute whether the given x, y point is in a triangle; uses the winding order method */ + static boolean pointInTriangle (double x, double y, double ax, double ay, double bx, double by, double cx, double cy) { + double minX = StrictMath.min(ax, StrictMath.min(bx, cx)); + double minY = StrictMath.min(ay, StrictMath.min(by, cy)); + double maxX = StrictMath.max(ax, StrictMath.max(bx, cx)); + double maxY = StrictMath.max(ay, StrictMath.max(by, cy)); + //check the bounding box because if the triangle is degenerated, e.g points and lines, we need to filter out + //coplanar points that are not part of the triangle. + if (x >= minX && x <= maxX && y >= minY && y <= maxY ) { + int a = orient(x, y, ax, ay, bx, by); + int b = orient(x, y, bx, by, cx, cy); + if (a == 0 || b == 0 || a < 0 == b < 0) { + int c = orient(x, y, cx, cy, ax, ay); + return c == 0 || (c < 0 == (b < 0 || a < 0)); + } + return false; + } else { + return false; + } + } +} diff --git a/lucene/core/src/java/org/apache/lucene/geo/ComponentTree.java b/lucene/core/src/java/org/apache/lucene/geo/ComponentTree.java new file mode 100644 index 000000000000..58d64b430d40 --- /dev/null +++ b/lucene/core/src/java/org/apache/lucene/geo/ComponentTree.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.lucene.geo; + +import java.util.Comparator; + +import org.apache.lucene.index.PointValues.Relation; +import org.apache.lucene.util.ArrayUtil; + +/** + * 2D geometry collection implementation represented as a r-tree of {@link Component}. + * + * @lucene.internal + */ +public class ComponentTree implements Component { + /** root node of edge tree */ + protected final Component component; + /** box of this component and its children or null if there is no children */ + protected Rectangle box; + // child components, or null + protected ComponentTree left; + protected ComponentTree right; + + protected ComponentTree(Component component) { + this.component = component; + this.box = null; + } + + public boolean contains(double latitude, double longitude) { + if (box == null || Rectangle.disjoint(box, latitude, latitude, longitude, longitude) == false) { + if (Rectangle.disjoint(component.getBoundingBox(), latitude, latitude, longitude, longitude) == false) { + if (component.contains(latitude, longitude)) { + return true; + } + } + if (left != null) { + if (left.contains(latitude, longitude)) { + return true; + } + } + if (right != null) { + if (right.contains(latitude, longitude)) { + return true; + } + } + } + return false; + } + + public Relation relate(double minLat, double maxLat, double minLon, double maxLon) { + if (box == null || Rectangle.disjoint(box, minLat, maxLat, minLon, maxLon) == false) { + // if the rectangle fully encloses us, we cross. + if (Rectangle.within(component.getBoundingBox(), minLat, maxLat, minLon, maxLon)) { + return Relation.CELL_CROSSES_QUERY; + } + if (Rectangle.disjoint(component.getBoundingBox(), minLat, maxLat, minLon, maxLon) == false) { + Relation relation = component.relate(minLat, maxLat, minLon, maxLon); + if (relation != Relation.CELL_OUTSIDE_QUERY) { + return relation; + } + } + if (left != null) { + Relation relation = left.relate(minLat, maxLat, minLon, maxLon); + if (relation != Relation.CELL_OUTSIDE_QUERY) { + return relation; + } + } + if (right != null) { + Relation relation = right.relate(minLat, maxLat, minLon, maxLon); + if (relation != Relation.CELL_OUTSIDE_QUERY) { + return relation; + } + } + } + return Relation.CELL_OUTSIDE_QUERY; + } + + /** Returns relation to the provided triangle */ + public Relation relateTriangle(double ax, double ay, double bx, double by, double cx, double cy) { + // compute bounding box of triangle + double minLat = StrictMath.min(StrictMath.min(ay, by), cy); + double minLon = StrictMath.min(StrictMath.min(ax, bx), cx); + double maxLat = StrictMath.max(StrictMath.max(ay, by), cy); + double maxLon = StrictMath.max(StrictMath.max(ax, bx), cx); + return relateTriangle(minLat, maxLat, minLon, maxLon, ax, ay, bx, by, cx, cy); + } + + @Override + public Rectangle getBoundingBox() { + return box == null ? component.getBoundingBox() : box; + } + + private Relation relateTriangle(double minLat, double maxLat, double minLon, double maxLon, double ax, double ay, double bx, double by, double cx, double cy) { + if (box == null || Rectangle.disjoint(box, minLat, maxLat, minLon, maxLon) == false) { + if (Rectangle.disjoint(component.getBoundingBox(), minLat, maxLat, minLon, maxLon) == false) { + Relation relation = component.relateTriangle(ax, ay, bx, by, cx, cy); + if (relation != Relation.CELL_OUTSIDE_QUERY) { + return relation; + } + } + if (left != null) { + Relation relation = left.relateTriangle(minLat, maxLat, minLon, maxLon, ax, ay, bx, by, cx, cy); + if (relation != Relation.CELL_OUTSIDE_QUERY) { + return relation; + } + } + if (right != null) { + Relation relation = right.relateTriangle(minLat, maxLat, minLon, maxLon, ax, ay, bx, by, cx, cy); + if (relation != Relation.CELL_OUTSIDE_QUERY) { + return relation; + } + } + } + return Relation.CELL_OUTSIDE_QUERY; + } + + /** Creates tree from sorted components (with full bounding box for children) */ + protected static ComponentTree createTree(Component components[], int low, int high, boolean splitX) { + if (low > high) { + return null; + } + final int mid = (low + high) >>> 1; + if (low < high) { + Comparator comparator; + if (splitX) { + comparator = (left, right) -> { + int ret = Double.compare(left.getBoundingBox().minLon, right.getBoundingBox().minLon); + if (ret == 0) { + ret = Double.compare(left.getBoundingBox().maxLon, right.getBoundingBox().maxLon); + } + return ret; + }; + } else { + comparator = (left, right) -> { + int ret = Double.compare(left.getBoundingBox().minLat, right.getBoundingBox().minLat); + if (ret == 0) { + ret = Double.compare(left.getBoundingBox().maxLat, right.getBoundingBox().maxLat); + } + return ret; + }; + } + ArrayUtil.select(components, low, high + 1, mid, comparator); + } else { + return new ComponentTree(components[mid]); + } + // add midpoint + ComponentTree newNode = new ComponentTree(components[mid]); + // add children + newNode.left = createTree(components, low, mid - 1, !splitX); + newNode.right = createTree(components, mid + 1, high, !splitX); + if (newNode.left != null || newNode.right != null) { + // pull up bounding box values to this node + double minX = newNode.component.getBoundingBox().minLon; + double maxX = newNode.component.getBoundingBox().maxLon; + double minY = newNode.component.getBoundingBox().minLat; + double maxY = newNode.component.getBoundingBox().maxLat; + + if (newNode.left != null) { + maxX = Math.max(maxX, newNode.left.getBoundingBox().maxLon); + maxY = Math.max(maxY, newNode.left.getBoundingBox().maxLat); + minX = splitX == true ? newNode.left.getBoundingBox().minLon : Math.min(minX, newNode.left.getBoundingBox().minLon); + minY = splitX == false ? newNode.left.getBoundingBox().minLat : Math.min(minY, newNode.left.getBoundingBox().minLat); + } + if (newNode.right != null) { + maxX = Math.max(maxX, newNode.right.getBoundingBox().maxLon); + maxY = Math.max(maxY, newNode.right.getBoundingBox().maxLat); + minX = splitX == true ? minX : Math.min(minX, newNode.right.getBoundingBox().minLon); + minY = splitX == false ? minY : Math.min(minY, newNode.right.getBoundingBox().minLat); + } + newNode.box = new Rectangle(minY, maxY, minX, maxX); + } + assert newNode.left == null || (newNode.getBoundingBox().minLat <= newNode.left.getBoundingBox().minLat && + newNode.getBoundingBox().maxLat >= newNode.left.getBoundingBox().maxLat && + newNode.getBoundingBox(). minLon <= newNode.left.getBoundingBox().minLon && + newNode.getBoundingBox().maxLon >= newNode.left.getBoundingBox().maxLon); + assert newNode.right == null || (newNode.getBoundingBox().minLat <= newNode.right.getBoundingBox().minLat && + newNode.getBoundingBox().maxLat >= newNode.right.getBoundingBox().maxLat && + newNode.getBoundingBox(). minLon <= newNode.right.getBoundingBox().minLon && + newNode.getBoundingBox().maxLon >= newNode.right.getBoundingBox().maxLon); + return newNode; + } + + /** Builds a Component from multiple components in a tree structure */ + public static Component create(Component... components) { + if (components.length == 1) { + return components[0]; + } + return ComponentTree.createTree(components, 0, components.length - 1, true); + } +} diff --git a/lucene/core/src/java/org/apache/lucene/geo/EdgeTree.java b/lucene/core/src/java/org/apache/lucene/geo/EdgeTree.java index bc81dcd1f00e..d38cb853bf39 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/EdgeTree.java +++ b/lucene/core/src/java/org/apache/lucene/geo/EdgeTree.java @@ -17,10 +17,8 @@ package org.apache.lucene.geo; import java.util.Arrays; -import java.util.Comparator; +import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.lucene.index.PointValues.Relation; -import org.apache.lucene.util.ArrayUtil; import static org.apache.lucene.geo.GeoUtils.lineCrossesLine; import static org.apache.lucene.geo.GeoUtils.lineCrossesLineWithBoundary; @@ -30,281 +28,160 @@ * 2D line/polygon geometry implementation represented as a balanced interval tree of edges. *

* Construction takes {@code O(n log n)} time for sorting and tree construction. - * {@link #relate relate()} are {@code O(n)}, but for most + * Crosses methods are {@code O(n)}, but for most * practical lines and polygons are much faster than brute force. * @lucene.internal */ -public abstract class EdgeTree { - /** minimum latitude of this geometry's bounding box area */ - public final double minLat; - /** maximum latitude of this geometry's bounding box area */ - public final double maxLat; - /** minimum longitude of this geometry's bounding box area */ - public final double minLon; - /** maximum longitude of this geometry's bounding box area */ - public final double maxLon; - - // each component is a node in an augmented 2d kd-tree: we alternate splitting between latitude/longitude, - // and pull up max values for both dimensions to each parent node (regardless of split). - - /** maximum latitude of this component or any of its children */ - protected double maxY; - /** maximum longitude of this component or any of its children */ - protected double maxX; - /** which dimension was this node split on */ - // TODO: its implicit based on level, but boolean keeps code simple - protected boolean splitX; - - // child components, or null - protected EdgeTree left; - protected EdgeTree right; - - /** root node of edge tree */ - protected final Edge tree; - - protected EdgeTree(final double minLat, final double maxLat, final double minLon, final double maxLon, double[] lats, double[] lons) { - this.minLat = minLat; - this.maxLat = maxLat; - this.minLon = minLon; - this.maxLon = maxLon; - this.maxY = maxLat; - this.maxX = maxLon; - - // create interval tree of edges - this.tree = createTree(lats, lons); +class EdgeTree { + final double lat1, lat2; + final double lon1, lon2; + /** min of this edge */ + final double low; + /** max latitude of this edge or any children */ + double max; + + /** left child edge, or null */ + EdgeTree left; + /** right child edge, or null */ + EdgeTree right; + + protected EdgeTree(final double lat1, final double lon1, final double lat2, final double lon2, final double low, final double max) { + this.lat1 = lat1; + this.lon1 = lon1; + this.lat2 = lat2; + this.lon2 = lon2; + this.low = low; + this.max = max; } - /** Returns relation to the provided triangle */ - public Relation relateTriangle(double ax, double ay, double bx, double by, double cx, double cy) { - // compute bounding box of triangle - double triMinLat = StrictMath.min(StrictMath.min(ay, by), cy); - double triMinLon = StrictMath.min(StrictMath.min(ax, bx), cx); - if (triMinLat <= maxY && triMinLon <= maxX) { - Relation relation = internalComponentRelateTriangle(ax, ay, bx, by, cx, cy); - if (relation != Relation.CELL_OUTSIDE_QUERY) { - return relation; - } - if (left != null) { - relation = left.relateTriangle(ax, ay, bx, by, cx, cy); - if (relation != Relation.CELL_OUTSIDE_QUERY) { - return relation; - } - } - double triMaxLat = StrictMath.max(StrictMath.max(ay, by), cy); - double triMaxLon = StrictMath.max(StrictMath.max(ax, bx), cx); - if (right != null && ((splitX == false && triMaxLat >= this.minLat) || (splitX && triMaxLon >= this.minLon))) { - relation = right.relateTriangle(ax, ay, bx, by, cx, cy); - if (relation != Relation.CELL_OUTSIDE_QUERY) { - return relation; + /** + * Returns true if the point crosses this edge subtree an odd number of times + *

+ * See + * https://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html for more information. + */ + // ported to java from https://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html + // original code under the BSD license (https://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html#License%20to%20Use) + // + // Copyright (c) 1970-2003, Wm. Randolph Franklin + // + // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + // documentation files (the "Software"), to deal in the Software without restriction, including without limitation + // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and + // to permit persons to whom the Software is furnished to do so, subject to the following conditions: + // + // 1. Redistributions of source code must retain the above copyright + // notice, this list of conditions and the following disclaimers. + // 2. Redistributions in binary form must reproduce the above copyright + // notice in the documentation and/or other materials provided with + // the distribution. + // 3. The name of W. Randolph Franklin may not be used to endorse or + // promote products derived from this Software without specific + // prior written permission. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED + // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + // IN THE SOFTWARE. + public boolean contains(double latitude, double longitude, AtomicBoolean isOnEdge) { + // crossings algorithm is an odd-even algorithm, so we descend the tree xor'ing results along our path + boolean res = false; + if (isOnEdge.get() == false && latitude <= max) { + if (latitude == lat1 && latitude == lat2 || + (latitude <= lat1 && latitude >= lat2) != (latitude >= lat1 && latitude <= lat2)) { + if (GeoUtils.orient(lon1, lat1, lon2, lat2, longitude, latitude) == 0) { + // if its on the boundary return true + isOnEdge.set(true); + return true; + } else if (lat1 > latitude != lat2 > latitude) { + res = longitude < (lon2 - lon1) * (latitude - lat1) / (lat2 - lat1) + lon1; } } - } - return Relation.CELL_OUTSIDE_QUERY; - } - - /** Returns relation to the provided rectangle */ - public Relation relate(double minLat, double maxLat, double minLon, double maxLon) { - if (minLat <= maxY && minLon <= maxX) { - Relation relation = internalComponentRelate(minLat, maxLat, minLon, maxLon); - if (relation != Relation.CELL_OUTSIDE_QUERY) { - return relation; - } if (left != null) { - relation = left.relate(minLat, maxLat, minLon, maxLon); - if (relation != Relation.CELL_OUTSIDE_QUERY) { - return relation; - } + res ^= left.contains(latitude, longitude, isOnEdge); } - if (right != null && ((splitX == false && maxLat >= this.minLat) || (splitX && maxLon >= this.minLon))) { - relation = right.relate(minLat, maxLat, minLon, maxLon); - if (relation != Relation.CELL_OUTSIDE_QUERY) { - return relation; - } + + if (right != null && latitude >= low) { + res ^= right.contains(latitude, longitude, isOnEdge); } } - return Relation.CELL_OUTSIDE_QUERY; + return isOnEdge.get() || res; } - /** Returns relation to the provided rectangle for this component */ - protected abstract Relation componentRelate(double minLat, double maxLat, double minLon, double maxLon); - - /** Returns relation to the provided triangle for this component */ - protected abstract Relation componentRelateTriangle(double ax, double ay, double bx, double by, double cx, double cy); - - - private Relation internalComponentRelateTriangle(double ax, double ay, double bx, double by, double cx, double cy) { + /** Returns true if the triangle crosses any edge in this edge subtree */ + boolean crossesTriangle(double ax, double ay, double bx, double by, double cx, double cy) { // compute bounding box of triangle double minLat = StrictMath.min(StrictMath.min(ay, by), cy); double minLon = StrictMath.min(StrictMath.min(ax, bx), cx); double maxLat = StrictMath.max(StrictMath.max(ay, by), cy); double maxLon = StrictMath.max(StrictMath.max(ax, bx), cx); - if (maxLon < this.minLon || minLon > this.maxLon || maxLat < this.minLat || minLat > this.maxLat) { - return Relation.CELL_OUTSIDE_QUERY; - } else if (bx == cx && by == cy) { - return componentRelateTriangle(bx, by, ax, ay, cx, cy); - } - return componentRelateTriangle(ax, ay, bx, by, cx, cy); + return crossesTriangle(minLat, maxLat, minLon, maxLon, ax, ay, bx, by, cx, cy); } + private boolean crossesTriangle(double minLat, double maxLat, double minLon, double maxLon, double ax, double ay, double bx, double by, double cx, double cy) { + if (minLat <= max) { + double dy = lat1; + double ey = lat2; + double dx = lon1; + double ex = lon2; + + // optimization: see if the rectangle is outside of the "bounding box" of the polyline at all + // if not, don't waste our time trying more complicated stuff + boolean outside = (dy < minLat && ey < minLat) || + (dy > maxLat && ey > maxLat) || + (dx < minLon && ex < minLon) || + (dx > maxLon && ex > maxLon); + + if (outside == false) { + // does triangle's edges intersect polyline? + if (lineCrossesLine(ax, ay, bx, by, dx, dy, ex, ey) || + lineCrossesLine(bx, by, cx, cy, dx, dy, ex, ey) || + lineCrossesLine(cx, cy, ax, ay, dx, dy, ex, ey)) { + return true; + } + } - /** Returns relation to the provided rectangle for this component */ - protected Relation internalComponentRelate(double minLat, double maxLat, double minLon, double maxLon) { - // if the bounding boxes are disjoint then the shape does not cross - if (maxLon < this.minLon || minLon > this.maxLon || maxLat < this.minLat || minLat > this.maxLat) { - return Relation.CELL_OUTSIDE_QUERY; - } - // if the rectangle fully encloses us, we cross. - if (minLat <= this.minLat && maxLat >= this.maxLat && minLon <= this.minLon && maxLon >= this.maxLon) { - return Relation.CELL_CROSSES_QUERY; - } - return componentRelate(minLat, maxLat, minLon, maxLon); - } + if (left != null && left.crossesTriangle(minLat, maxLat, minLon, maxLon, ax, ay, bx, by, cx, cy)) { + return true; + } - /** Creates tree from sorted components (with range low and high inclusive) */ - protected static EdgeTree createTree(EdgeTree components[], int low, int high, boolean splitX) { - if (low > high) { - return null; - } - final int mid = (low + high) >>> 1; - if (low < high) { - Comparator comparator; - if (splitX) { - comparator = (left, right) -> { - int ret = Double.compare(left.minLon, right.minLon); - if (ret == 0) { - ret = Double.compare(left.maxX, right.maxX); - } - return ret; - }; - } else { - comparator = (left, right) -> { - int ret = Double.compare(left.minLat, right.minLat); - if (ret == 0) { - ret = Double.compare(left.maxY, right.maxY); - } - return ret; - }; + if (right != null && maxLat >= low && right.crossesTriangle(minLat, maxLat, minLon, maxLon, ax, ay, bx, by, cx, cy)) { + return true; } - ArrayUtil.select(components, low, high + 1, mid, comparator); } - // add midpoint - EdgeTree newNode = components[mid]; - newNode.splitX = splitX; - // add children - newNode.left = createTree(components, low, mid - 1, !splitX); - newNode.right = createTree(components, mid + 1, high, !splitX); - // pull up max values to this node - if (newNode.left != null) { - newNode.maxX = Math.max(newNode.maxX, newNode.left.maxX); - newNode.maxY = Math.max(newNode.maxY, newNode.left.maxY); - } - if (newNode.right != null) { - newNode.maxX = Math.max(newNode.maxX, newNode.right.maxX); - newNode.maxY = Math.max(newNode.maxY, newNode.right.maxY); - } - return newNode; + return false; } - /** - * Internal tree node: represents geometry edge from lat1,lon1 to lat2,lon2. - * The sort value is {@code low}, which is the minimum latitude of the edge. - * {@code max} stores the maximum latitude of this edge or any children. - */ - static class Edge { - // lat-lon pair (in original order) of the two vertices - final double lat1, lat2; - final double lon1, lon2; - //edge belongs to the dateline - final boolean dateline; - /** min of this edge */ - final double low; - /** max latitude of this edge or any children */ - double max; - - /** left child edge, or null */ - Edge left; - /** right child edge, or null */ - Edge right; - - Edge(double lat1, double lon1, double lat2, double lon2, double low, double max) { - this.lat1 = lat1; - this.lon1 = lon1; - this.lat2 = lat2; - this.lon2 = lon2; - this.low = low; - this.max = max; - dateline = (lon1 == GeoUtils.MIN_LON_INCL && lon2 == GeoUtils.MIN_LON_INCL) - || (lon1 == GeoUtils.MAX_LON_INCL && lon2 == GeoUtils.MAX_LON_INCL); - } - - /** Returns true if the triangle crosses any edge in this edge subtree */ - boolean crossesTriangle(double ax, double ay, double bx, double by, double cx, double cy) { - // compute min lat of triangle bounding box - double triMinLat = StrictMath.min(StrictMath.min(ay, by), cy); - if (triMinLat <= max) { - double dy = lat1; - double ey = lat2; - double dx = lon1; - double ex = lon2; - - // compute remaining bounding box of triangle - double triMinLon = StrictMath.min(StrictMath.min(ax, bx), cx); - double triMaxLat = StrictMath.max(StrictMath.max(ay, by), cy); - double triMaxLon = StrictMath.max(StrictMath.max(ax, bx), cx); - - // optimization: see if the rectangle is outside of the "bounding box" of the polyline at all - // if not, don't waste our time trying more complicated stuff - boolean outside = (dy < triMinLat && ey < triMinLat) || - (dy > triMaxLat && ey > triMaxLat) || - (dx < triMinLon && ex < triMinLon) || - (dx > triMaxLon && ex > triMaxLon); - - if (outside == false) { - if (lineCrossesLine(dx, dy, ex, ey, ax, ay, bx, by) || - lineCrossesLine(dx, dy, ex, ey, bx, by, cx, cy) || - lineCrossesLine(dx, dy, ex, ey, cx, cy, ax, ay)) { - return true; - } - } - - if (left != null && left.crossesTriangle(ax, ay, bx, by, cx, cy)) { - return true; - } - - if (right != null && triMaxLat >= low && right.crossesTriangle(ax, ay, bx, by, cx, cy)) { - return true; - } + /** Returns true if the box crosses any edge in this edge subtree */ + boolean crossesBox(double minLat, double maxLat, double minLon, double maxLon, boolean includeBoundary) { + // we just have to cross one edge to answer the question, so we descend the tree and return when we do. + if (minLat <= max) { + // we compute line intersections of every polygon edge with every box line. + // if we find one, return true. + // for each box line (AB): + // for each poly line (CD): + // intersects = orient(C,D,A) * orient(C,D,B) <= 0 && orient(A,B,C) * orient(A,B,D) <= 0 + double cy = lat1; + double dy = lat2; + double cx = lon1; + double dx = lon2; + + // optimization: see if either end of the line segment is contained by the rectangle + if (Rectangle.containsPoint(cy, cx, minLat, maxLat, minLon, maxLon) || + Rectangle.containsPoint(dy, dx, minLat, maxLat, minLon, maxLon)) { + return true; } - return false; - } - - /** Returns true if the box crosses any edge in this edge subtree */ - boolean crossesBox(double minLat, double maxLat, double minLon, double maxLon, boolean includeBoundary) { - // we just have to cross one edge to answer the question, so we descend the tree and return when we do. - if (minLat <= max) { - // we compute line intersections of every polygon edge with every box line. - // if we find one, return true. - // for each box line (AB): - // for each poly line (CD): - // intersects = orient(C,D,A) * orient(C,D,B) <= 0 && orient(A,B,C) * orient(A,B,D) <= 0 - double cy = lat1; - double dy = lat2; - double cx = lon1; - double dx = lon2; - - // optimization: see if either end of the line segment is contained by the rectangle - if (Rectangle.containsPoint(cy, cx, minLat, maxLat, minLon, maxLon) || - Rectangle.containsPoint(dy, dx, minLat, maxLat, minLon, maxLon)) { - return true; - } - // optimization: see if the rectangle is outside of the "bounding box" of the polyline at all - // if not, don't waste our time trying more complicated stuff - boolean outside = (cy < minLat && dy < minLat) || - (cy > maxLat && dy > maxLat) || - (cx < minLon && dx < minLon) || - (cx > maxLon && dx > maxLon); + // optimization: see if the rectangle is outside of the "bounding box" of the polyline at all + // if not, don't waste our time trying more complicated stuff + boolean outside = (cy < minLat && dy < minLat) || + (cy > maxLat && dy > maxLat) || + (cx < minLon && dx < minLon) || + (cx > maxLon && dx > maxLon); + if (outside == false) { + // does rectangle's edges intersect polyline? if (outside == false) { if (includeBoundary == true && lineCrossesLineWithBoundary(cx, cy, dx, dy, minLon, minLat, maxLon, minLat) || @@ -320,85 +197,83 @@ boolean crossesBox(double minLat, double maxLat, double minLon, double maxLon, b return true; } } + } - if (left != null && left.crossesBox(minLat, maxLat, minLon, maxLon, includeBoundary)) { - return true; - } + if (left != null && left.crossesBox(minLat, maxLat, minLon, maxLon, includeBoundary)) { + return true; + } - if (right != null && maxLat >= low && right.crossesBox(minLat, maxLat, minLon, maxLon, includeBoundary)) { - return true; - } + if (right != null && maxLat >= low && right.crossesBox(minLat, maxLat, minLon, maxLon, includeBoundary)) { + return true; } - return false; } + return false; + } - /** Returns true if the line crosses any edge in this edge subtree */ - boolean crossesLine(double a2x, double a2y, double b2x, double b2y) { - double minY = StrictMath.min(a2y, b2y); - double maxY = StrictMath.max(a2y, b2y); - if (minY <= max) { - double a1x = lon1; - double a1y = lat1; - double b1x = lon2; - double b1y = lat2; - - double minX = StrictMath.min(a2x, b2x); - double maxX = StrictMath.max(a2x, b2x); - - boolean outside = (a1y < minY && b1y < minY) || - (a1y > maxY && b1y > maxY) || - (a1x < minX && b1x < minX) || - (a1x > maxX && b1x > maxX); - if (outside == false && lineCrossesLineWithBoundary(a1x, a1y, b1x, b1y, a2x, a2y, b2x, b2y)) { - return true; - } + /** Returns true if the line crosses any edge in this edge subtree */ + boolean crossesLine(double a2x, double a2y, double b2x, double b2y) { + double minY = StrictMath.min(a2y, b2y); + double maxY = StrictMath.max(a2y, b2y); + if (minY <= max) { + double a1x = lon1; + double a1y = lat1; + double b1x = lon2; + double b1y = lat2; + + double minX = StrictMath.min(a2x, b2x); + double maxX = StrictMath.max(a2x, b2x); + + boolean outside = (a1y < minY && b1y < minY) || + (a1y > maxY && b1y > maxY) || + (a1x < minX && b1x < minX) || + (a1x > maxX && b1x > maxX); + if (outside == false && lineCrossesLineWithBoundary(a1x, a1y, b1x, b1y, a2x, a2y, b2x, b2y)) { + return true; + } - if (left != null && left.crossesLine(a2x, a2y, b2x, b2y)) { - return true; - } - if (right != null && maxY >= low && right.crossesLine(a2x, a2y, b2x, b2y)) { - return true; - } + if (left != null && left.crossesLine(a2x, a2y, b2x, b2y)) { + return true; + } + if (right != null && maxY >= low && right.crossesLine(a2x, a2y, b2x, b2y)) { + return true; } - return false; } + return false; } - //This should be moved when LatLonShape is moved from sandbox! - /** - * Compute whether the given x, y point is in a triangle; uses the winding order method */ - protected static boolean pointInTriangle (double x, double y, double ax, double ay, double bx, double by, double cx, double cy) { - double minX = StrictMath.min(ax, StrictMath.min(bx, cx)); - double minY = StrictMath.min(ay, StrictMath.min(by, cy)); - double maxX = StrictMath.max(ax, StrictMath.max(bx, cx)); - double maxY = StrictMath.max(ay, StrictMath.max(by, cy)); - //check the bounding box because if the triangle is degenerated, e.g points and lines, we need to filter out - //coplanar points that are not part of the triangle. - if (x >= minX && x <= maxX && y >= minY && y <= maxY ) { - int a = orient(x, y, ax, ay, bx, by); - int b = orient(x, y, bx, by, cx, cy); - if (a == 0 || b == 0 || a < 0 == b < 0) { - int c = orient(x, y, cx, cy, ax, ay); - return c == 0 || (c < 0 == (b < 0 || a < 0)); + /** returns true if the provided x, y point lies on any of the edges */ + boolean pointInEdge(double latitude, double longitude) { + if (latitude <= max) { + double minY = StrictMath.min(lat1, lat2); + double maxY = StrictMath.max(lat1, lat2); + double minX = StrictMath.min(lon1, lon2); + double maxX = StrictMath.max(lon1, lon2); + if (Rectangle.containsPoint(latitude, longitude, minY, maxY, minX, maxX) && + orient(lon1, lat1, lon2, lat2, longitude, latitude) == 0) { + return true; + } + if (left != null && left.pointInEdge(latitude, longitude)) { + return true; + } + if (right != null && maxY >= low && right.pointInEdge(latitude, longitude)) { + return true; } - return false; - } else { - return false; } + return false; } /** * Creates an edge interval tree from a set of geometry vertices. * @return root node of the tree. */ - private static Edge createTree(double[] lats, double[] lons) { - Edge edges[] = new Edge[lats.length - 1]; + public static EdgeTree createTree(double[] lats, double[] lons) { + EdgeTree edges[] = new EdgeTree[lats.length - 1]; for (int i = 1; i < lats.length; i++) { double lat1 = lats[i-1]; double lon1 = lons[i-1]; double lat2 = lats[i]; double lon2 = lons[i]; - edges[i - 1] = new Edge(lat1, lon1, lat2, lon2, Math.min(lat1, lat2), Math.max(lat1, lat2)); + edges[i - 1] = new EdgeTree(lat1, lon1, lat2, lon2, Math.min(lat1, lat2), Math.max(lat1, lat2)); } // sort the edges then build a balanced tree from them Arrays.sort(edges, (left, right) -> { @@ -412,13 +287,13 @@ private static Edge createTree(double[] lats, double[] lons) { } /** Creates tree from sorted edges (with range low and high inclusive) */ - private static Edge createTree(Edge edges[], int low, int high) { + private static EdgeTree createTree(EdgeTree edges[], int low, int high) { if (low > high) { return null; } // add midpoint int mid = (low + high) >>> 1; - Edge newNode = edges[mid]; + EdgeTree newNode = edges[mid]; // add children newNode.left = createTree(edges, low, mid - 1); newNode.right = createTree(edges, mid + 1, high); diff --git a/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java b/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java index 00b7252fe8d3..0b4334f49cb6 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java +++ b/lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java @@ -181,18 +181,18 @@ public static DistancePredicate createDistancePredicate(double lat, double lon, /** Create a predicate that checks whether points are within a polygon. * It works the same way as {@link #createDistancePredicate}. * @lucene.internal */ - public static PolygonPredicate createPolygonPredicate(Polygon[] polygons, Polygon2D tree) { - final Rectangle boundingBox = Rectangle.fromPolygon(polygons); - final Function boxToRelation = box -> tree.relate( + public static ComponentPredicate createComponentPredicate(Component component) { + final Rectangle boundingBox = component.getBoundingBox(); + final Function boxToRelation = box -> component.relate( box.minLat, box.maxLat, box.minLon, box.maxLon); final Grid subBoxes = createSubBoxes(boundingBox, boxToRelation); - return new PolygonPredicate( + return new ComponentPredicate( subBoxes.latShift, subBoxes.lonShift, subBoxes.latBase, subBoxes.lonBase, subBoxes.maxLatDelta, subBoxes.maxLonDelta, subBoxes.relations, - tree); + component); } private static Grid createSubBoxes(Rectangle boundingBox, Function boxToRelation) { @@ -342,18 +342,18 @@ public boolean test(int lat, int lon) { } /** A predicate that checks whether a given point is within a polygon. */ - public static class PolygonPredicate extends Grid { + public static class ComponentPredicate extends Grid { - private final Polygon2D tree; + private final Component component; - private PolygonPredicate( + private ComponentPredicate( int latShift, int lonShift, int latBase, int lonBase, int maxLatDelta, int maxLonDelta, byte[] relations, - Polygon2D tree) { + Component component) { super(latShift, lonShift, latBase, lonBase, maxLatDelta, maxLonDelta, relations); - this.tree = tree; + this.component = component; } /** Check whether the given point is within the considered polygon. @@ -375,7 +375,7 @@ public boolean test(int lat, int lon) { final int relation = relations[(lat2 - latBase) * maxLonDelta + (lon2 - lonBase)]; if (relation == Relation.CELL_CROSSES_QUERY.ordinal()) { - return tree.contains(decodeLatitude(lat), decodeLongitude(lon)); + return component.contains(decodeLatitude(lat), decodeLongitude(lon)); } else { return relation == Relation.CELL_INSIDE_QUERY.ordinal(); } diff --git a/lucene/core/src/java/org/apache/lucene/geo/Polygon2D.java b/lucene/core/src/java/org/apache/lucene/geo/Polygon2D.java index 277d02b7954b..8b751747bb43 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/Polygon2D.java +++ b/lucene/core/src/java/org/apache/lucene/geo/Polygon2D.java @@ -16,6 +16,7 @@ */ package org.apache.lucene.geo; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.lucene.index.PointValues.Relation; @@ -27,63 +28,43 @@ * http://www-ma2.upc.es/geoc/Schirra-pointPolygon.pdf. * @lucene.internal */ -// Both Polygon.contains() and Polygon.crossesSlowly() loop all edges, and first check that the edge is within a range. -// we just organize the edges to do the same computations on the same subset of edges more efficiently. -public final class Polygon2D extends EdgeTree { - // each component/hole is a node in an augmented 2d kd-tree: we alternate splitting between latitude/longitude, - // and pull up max values for both dimensions to each parent node (regardless of split). - /** tree of holes, or null */ - private final Polygon2D holes; +public final class Polygon2D implements Component { + private final Polygon polygon; + private final EdgeTree tree; + private final Rectangle box; + /** Holes component or null */ + private final Component holes; + /** keeps track if points lies on polygon boundary */ private final AtomicBoolean containsBoundary = new AtomicBoolean(false); - private Polygon2D(Polygon polygon, Polygon2D holes) { - super(polygon.minLat, polygon.maxLat, polygon.minLon, polygon.maxLon, polygon.getPolyLats(), polygon.getPolyLons()); + private Polygon2D(Polygon polygon, Component holes) { + this.polygon = polygon; this.holes = holes; + this.tree = EdgeTree.createTree(polygon.getPolyLats(), polygon.getPolyLons()); + this.box = new Rectangle(polygon.minLat, polygon.maxLat, polygon.minLon, polygon.maxLon); } /** - * Returns true if the point is contained within this polygon. *

* See * https://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html for more information. */ + @Override public boolean contains(double latitude, double longitude) { - if (latitude <= maxY && longitude <= maxX) { - if (componentContains(latitude, longitude)) { - return true; - } - if (left != null) { - if (((Polygon2D)left).contains(latitude, longitude)) { - return true; + if (Rectangle.containsPoint(latitude, longitude, box.minLat, box.maxLat, box.minLon, box.maxLon)) { + containsBoundary.set(false); + if (tree.contains(latitude, longitude, containsBoundary)) { + if (holes != null && holes.contains(latitude, longitude)) { + return false; } + return true; } - if (right != null && ((splitX == false && latitude >= minLat) || (splitX && longitude >= minLon))) { - if (((Polygon2D)right).contains(latitude, longitude)) { - return true; - } - } - } - return false; - } - - /** Returns true if the point is contained within this polygon component. */ - private boolean componentContains(double latitude, double longitude) { - // check bounding box - if (latitude < minLat || latitude > maxLat || longitude < minLon || longitude > maxLon) { - return false; - } - containsBoundary.set(false); - if (contains(tree, latitude, longitude, containsBoundary)) { - if (holes != null && holes.contains(latitude, longitude)) { - return false; - } - return true; } return false; } @Override - protected Relation componentRelate(double minLat, double maxLat, double minLon, double maxLon) { + public Relation relate(double minLat, double maxLat, double minLon, double maxLon) { // check any holes if (holes != null) { Relation holeRelation = holes.relate(minLat, maxLat, minLon, maxLon); @@ -101,10 +82,8 @@ protected Relation componentRelate(double minLat, double maxLat, double minLon, } return Relation.CELL_INSIDE_QUERY; } else if (numCorners == 0) { - if (minLat >= tree.lat1 && maxLat <= tree.lat1 && minLon >= tree.lon2 && maxLon <= tree.lon2) { - return Relation.CELL_CROSSES_QUERY; - } - if (tree.crossesBox(minLat, maxLat, minLon, maxLon, false)) { + if (Rectangle.containsPoint(tree.lat1, tree.lon1, minLat, maxLat, minLon, maxLon) || + tree.crossesBox(minLat, maxLat, minLon, maxLon, false)) { return Relation.CELL_CROSSES_QUERY; } return Relation.CELL_OUTSIDE_QUERY; @@ -113,8 +92,7 @@ protected Relation componentRelate(double minLat, double maxLat, double minLon, } @Override - protected Relation componentRelateTriangle(double ax, double ay, double bx, double by, double cx, double cy) { - // check any holes + public Relation relateTriangle(double ax, double ay, double bx, double by, double cx, double cy) { if (holes != null) { Relation holeRelation = holes.relateTriangle(ax, ay, bx, by, cx, cy); if (holeRelation == Relation.CELL_CROSSES_QUERY) { @@ -123,7 +101,7 @@ protected Relation componentRelateTriangle(double ax, double ay, double bx, doub return Relation.CELL_OUTSIDE_QUERY; } } - if (ax == bx && bx == cx && ay == by && by == cy) { + if (ax == bx && bx == cx && ay == by && by == cy) { // indexed "triangle" is a point: shortcut by checking contains return contains(ay, ax) ? Relation.CELL_INSIDE_QUERY : Relation.CELL_OUTSIDE_QUERY; } else if ((ax == cx && ay == cy) || (bx == cx && by == cy)) { @@ -138,10 +116,10 @@ protected Relation componentRelateTriangle(double ax, double ay, double bx, doub private Relation relateIndexedLineSegment(double a2x, double a2y, double b2x, double b2y) { // check endpoints of the line segment int numCorners = 0; - if (componentContains(a2y, a2x)) { + if (contains(a2y, a2x)) { ++numCorners; } - if (componentContains(b2y, b2x)) { + if (contains(b2y, b2x)) { ++numCorners; } @@ -169,10 +147,8 @@ private Relation relateIndexedTriangle(double ax, double ay, double bx, double b } return Relation.CELL_INSIDE_QUERY; } else if (numCorners == 0) { - if (pointInTriangle(tree.lon1, tree.lat1, ax, ay, bx, by, cx, cy) == true) { - return Relation.CELL_CROSSES_QUERY; - } - if (tree.crossesTriangle(ax, ay, bx, by, cx, cy)) { + if (Component.pointInTriangle(tree.lon1, tree.lat1, ax, ay, bx, by, cx, cy) || + tree.crossesTriangle(ax, ay, bx, by, cx, cy)) { return Relation.CELL_CROSSES_QUERY; } return Relation.CELL_OUTSIDE_QUERY; @@ -182,16 +158,16 @@ private Relation relateIndexedTriangle(double ax, double ay, double bx, double b private int numberOfTriangleCorners(double ax, double ay, double bx, double by, double cx, double cy) { int containsCount = 0; - if (componentContains(ay, ax)) { + if (contains(ay, ax)) { containsCount++; } - if (componentContains(by, bx)) { + if (contains(by, bx)) { containsCount++; } if (containsCount == 1) { return containsCount; } - if (componentContains(cy, cx)) { + if (contains(cy, cx)) { containsCount++; } return containsCount; @@ -200,95 +176,68 @@ private int numberOfTriangleCorners(double ax, double ay, double bx, double by, // returns 0, 4, or something in between private int numberOfCorners(double minLat, double maxLat, double minLon, double maxLon) { int containsCount = 0; - if (componentContains(minLat, minLon)) { + if (contains(minLat, minLon)) { containsCount++; } - if (componentContains(minLat, maxLon)) { + if (contains(minLat, maxLon)) { containsCount++; } if (containsCount == 1) { return containsCount; } - if (componentContains(maxLat, maxLon)) { + if (contains(maxLat, maxLon)) { containsCount++; } if (containsCount == 2) { return containsCount; } - if (componentContains(maxLat, minLon)) { + if (contains(maxLat, minLon)) { containsCount++; } return containsCount; } - /** Builds a Polygon2D from multipolygon */ - public static Polygon2D create(Polygon... polygons) { - Polygon2D components[] = new Polygon2D[polygons.length]; - for (int i = 0; i < components.length; i++) { - Polygon gon = polygons[i]; - Polygon gonHoles[] = gon.getHoles(); - Polygon2D holes = null; - if (gonHoles.length > 0) { - holes = create(gonHoles); - } - components[i] = new Polygon2D(gon, holes); - } - return (Polygon2D)createTree(components, 0, components.length - 1, false); + @Override + public Rectangle getBoundingBox() { + return box; } - /** - * Returns true if the point crosses this edge subtree an odd number of times - *

- * See - * https://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html for more information. - */ - // ported to java from https://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html - // original code under the BSD license (https://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html#License%20to%20Use) - // - // Copyright (c) 1970-2003, Wm. Randolph Franklin - // - // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated - // documentation files (the "Software"), to deal in the Software without restriction, including without limitation - // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and - // to permit persons to whom the Software is furnished to do so, subject to the following conditions: - // - // 1. Redistributions of source code must retain the above copyright - // notice, this list of conditions and the following disclaimers. - // 2. Redistributions in binary form must reproduce the above copyright - // notice in the documentation and/or other materials provided with - // the distribution. - // 3. The name of W. Randolph Franklin may not be used to endorse or - // promote products derived from this Software without specific - // prior written permission. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED - // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - // IN THE SOFTWARE. - private static boolean contains(Edge edge, double lat, double lon, AtomicBoolean isOnEdge) { - boolean res = false; - if (isOnEdge.get() == false && lat <= edge.max) { - if (lat == edge.lat1 && lat == edge.lat2 || - (lat <= edge.lat1 && lat >= edge.lat2) != (lat >= edge.lat1 && lat <= edge.lat2)) { - if ((lon == edge.lon1 && lon == edge.lon2) || - ((lon <= edge.lon1 && lon >= edge.lon2) != (lon >= edge.lon1 && lon <= edge.lon2) && - GeoUtils.orient(edge.lon1, edge.lat1, edge.lon2, edge.lat2, lon, lat) == 0)) { - // if its on the boundary return true - isOnEdge.set(true); - return true; - } else if (edge.lat1 > lat != edge.lat2 > lat) { - res = lon < (edge.lon2 - edge.lon1) * (lat - edge.lat1) / (edge.lat2 - edge.lat1) + edge.lon1; - } - } - if (edge.left != null) { - res ^= contains(edge.left, lat, lon, isOnEdge); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Polygon2D polygon2D = (Polygon2D) o; + return Objects.equals(polygon, polygon2D.polygon); + } - if (edge.right != null && lat >= edge.low) { - res ^= contains(edge.right, lat, lon, isOnEdge); - } + @Override + public int hashCode() { + return Objects.hash(polygon); + } + + @Override + public String toString() { + return "Polygon2D{" + + "polygon=" + polygon + + '}'; + } + + /** Builds a Component from polygon */ + private static Component createComponent(Polygon polygon) { + Polygon gonHoles[] = polygon.getHoles(); + Component holes = null; + if (gonHoles.length > 0) { + holes = create(gonHoles); + } + return new Polygon2D(polygon, holes); + } + + /** Builds a Component tree from multipolygon */ + public static Component create(Polygon... polygons) { + Component components[] = new Component[polygons.length]; + for (int i = 0; i < components.length; i++) { + components[i] = createComponent(polygons[i]); } - return isOnEdge.get() || res; + return ComponentTree.create(components); } } diff --git a/lucene/core/src/java/org/apache/lucene/geo/Rectangle.java b/lucene/core/src/java/org/apache/lucene/geo/Rectangle.java index 45d437df1bc6..6f069ed399f4 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/Rectangle.java +++ b/lucene/core/src/java/org/apache/lucene/geo/Rectangle.java @@ -94,6 +94,21 @@ public static boolean containsPoint(final double lat, final double lon, return lat >= minLat && lat <= maxLat && lon >= minLon && lon <= maxLon; } + /** returns true if the first rectangle is disjoint with the second rectangle (defined by minLat, maxLat, minLon, maxLon) */ + public static boolean disjoint(Rectangle rectangle, + final double minLat, final double maxLat, + final double minLon, final double maxLon) { + return (maxLon < rectangle.minLon || minLon > rectangle.maxLon || maxLat rectangle.maxLat); + } + + /** returns true if the first rectangle is within the second rectangle (defined by minLat, maxLat, minLon, maxLon) */ + public static boolean within(Rectangle rectangle, + final double minLat, final double maxLat, + final double minLon, final double maxLon) { + return minLat <= rectangle.minLat && maxLat >= rectangle.maxLat && + minLon <= rectangle.minLon && maxLon >= rectangle.maxLon; + } + /** Compute Bounding Box for a circle using WGS-84 parameters */ public static Rectangle fromPointDistance(final double centerLat, final double centerLon, final double radiusMeters) { checkLatitude(centerLat); diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestComponentTree.java b/lucene/core/src/test/org/apache/lucene/geo/TestComponentTree.java new file mode 100644 index 000000000000..06ed56a3b697 --- /dev/null +++ b/lucene/core/src/test/org/apache/lucene/geo/TestComponentTree.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.lucene.geo; + +import org.apache.lucene.index.PointValues; +import org.apache.lucene.util.LuceneTestCase; + +/** Test Component tree*/ +public class TestComponentTree extends LuceneTestCase { + + public void testMultiPolygon() { + int numComponents = random().nextInt(30) + 1; + Polygon[] polygon = new Polygon[numComponents]; + for (int i =0; i < numComponents; i++) { + polygon[i] = GeoTestUtil.nextPolygon(); + } + Component component = Polygon2D.create(polygon); + Component[] components = new Component[numComponents]; + for (int i =0; i < numComponents; i++) { + components[i] = Polygon2D.create(polygon[i]); + } + for (int i =0; i < 100; i++) { + double lat = GeoTestUtil.nextLatitude(); + double lon = GeoTestUtil.nextLongitude(); + assertEquals(component.contains(lat, lon), contains(components, lat, lon)); + } + // Note that if components overlap, we can either get CELL_CROSSES_QUERY or CELL_INSIDE_QUERY + // depending on the order. + for (int i =0; i < 100; i++) { + Rectangle rectangle = GeoTestUtil.nextBoxNotCrossingDateline(); + PointValues.Relation r1 = component.relate(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon); + PointValues.Relation r2 = relate(components, rectangle); + assertTrue(r1 == r2 || (r1 != PointValues.Relation.CELL_OUTSIDE_QUERY && r2 != PointValues.Relation.CELL_OUTSIDE_QUERY)); + } + for (int i =0; i < 100; i++) { + double ay = GeoTestUtil.nextLatitude(); + double ax = GeoTestUtil.nextLongitude(); + double by = GeoTestUtil.nextLatitude(); + double bx = GeoTestUtil.nextLongitude(); + double cy = GeoTestUtil.nextLatitude(); + double cx = GeoTestUtil.nextLongitude(); + PointValues.Relation r1 = component.relateTriangle(ax, ay, bx, by, cx, cy); + PointValues.Relation r2 = relateTriangle(components, ax, ay, bx, by, cx, cy); + assertTrue(r1 == r2 || (r1 != PointValues.Relation.CELL_OUTSIDE_QUERY && r2 != PointValues.Relation.CELL_OUTSIDE_QUERY)); + } + } + + private boolean contains(Component[] components, double lat, double lon) { + for (Component component : components) { + if (component.contains(lat, lon)) { + return true; + } + } + return false; + } + + private PointValues.Relation relate(Component[] components, Rectangle rectangle) { + boolean inside = false; + for (Component component : components) { + + PointValues.Relation relation = component.relate(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon); + if (relation == PointValues.Relation.CELL_CROSSES_QUERY) { + return relation; + } else if (relation == PointValues.Relation.CELL_INSIDE_QUERY) { + inside = true; + } + } + return (inside) ? PointValues.Relation.CELL_INSIDE_QUERY : PointValues.Relation.CELL_OUTSIDE_QUERY; + } + + private PointValues.Relation relateTriangle(Component[] components,double ax, double ay, double bx, double by, double cx, double cy) { + boolean inside = false; + for (Component component : components) { + PointValues.Relation relation = component.relateTriangle(ax, ay, bx, by, cx, cy); + if (relation == PointValues.Relation.CELL_CROSSES_QUERY) { + return relation; + } else if (relation == PointValues.Relation.CELL_INSIDE_QUERY) { + inside = true; + } + } + return (inside) ? PointValues.Relation.CELL_INSIDE_QUERY : PointValues.Relation.CELL_OUTSIDE_QUERY; + } +} diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestPolygon2D.java b/lucene/core/src/test/org/apache/lucene/geo/TestPolygon2D.java index c43517b0a3a1..3073736770fe 100644 --- a/lucene/core/src/test/org/apache/lucene/geo/TestPolygon2D.java +++ b/lucene/core/src/test/org/apache/lucene/geo/TestPolygon2D.java @@ -35,7 +35,7 @@ public void testMultiPolygon() { Polygon hole = new Polygon(new double[] { -10, -10, 10, 10, -10 }, new double[] { -10, 10, 10, -10, -10 }); Polygon outer = new Polygon(new double[] { -50, -50, 50, 50, -50 }, new double[] { -50, 50, 50, -50, -50 }, hole); Polygon island = new Polygon(new double[] { -5, -5, 5, 5, -5 }, new double[] { -5, 5, 5, -5, -5 } ); - Polygon2D polygon = Polygon2D.create(outer, island); + Component polygon = Polygon2D.create(outer, island); // contains(point) assertTrue(polygon.contains(-2, 2)); // on the island @@ -66,21 +66,21 @@ public void testPacMan() throws Exception { double yMax = 1;//5; // test cell crossing poly - Polygon2D polygon = Polygon2D.create(new Polygon(py, px)); + Component polygon = Polygon2D.create(new Polygon(py, px)); assertEquals(Relation.CELL_CROSSES_QUERY, polygon.relate(yMin, yMax, xMin, xMax)); } public void testBoundingBox() throws Exception { for (int i = 0; i < 100; i++) { - Polygon2D polygon = Polygon2D.create(nextPolygon()); + Component polygon = Polygon2D.create(nextPolygon()); for (int j = 0; j < 100; j++) { double latitude = nextLatitude(); double longitude = nextLongitude(); // if the point is within poly, then it should be in our bounding box if (polygon.contains(latitude, longitude)) { - assertTrue(latitude >= polygon.minLat && latitude <= polygon.maxLat); - assertTrue(longitude >= polygon.minLon && longitude <= polygon.maxLon); + assertTrue(latitude >= polygon.getBoundingBox().minLat && latitude <= polygon.getBoundingBox().maxLat); + assertTrue(longitude >= polygon.getBoundingBox().minLon && longitude <= polygon.getBoundingBox().maxLon); } } } @@ -90,7 +90,7 @@ public void testBoundingBox() throws Exception { public void testBoundingBoxEdgeCases() throws Exception { for (int i = 0; i < 100; i++) { Polygon polygon = nextPolygon(); - Polygon2D impl = Polygon2D.create(polygon); + Component impl = Polygon2D.create(polygon); for (int j = 0; j < 100; j++) { double point[] = GeoTestUtil.nextPointNear(polygon); @@ -110,7 +110,7 @@ public void testContainsRandom() throws Exception { int iters = atLeast(50); for (int i = 0; i < iters; i++) { Polygon polygon = nextPolygon(); - Polygon2D impl = Polygon2D.create(polygon); + Component impl = Polygon2D.create(polygon); for (int j = 0; j < 100; j++) { Rectangle rectangle = GeoTestUtil.nextBoxNear(polygon); @@ -147,7 +147,7 @@ public void testContainsRandom() throws Exception { public void testContainsEdgeCases() throws Exception { for (int i = 0; i < 1000; i++) { Polygon polygon = nextPolygon(); - Polygon2D impl = Polygon2D.create(polygon); + Component impl = Polygon2D.create(polygon); for (int j = 0; j < 10; j++) { Rectangle rectangle = GeoTestUtil.nextBoxNear(polygon); @@ -183,7 +183,7 @@ public void testIntersectRandom() { int iters = atLeast(10); for (int i = 0; i < iters; i++) { Polygon polygon = nextPolygon(); - Polygon2D impl = Polygon2D.create(polygon); + Component impl = Polygon2D.create(polygon); for (int j = 0; j < 100; j++) { Rectangle rectangle = GeoTestUtil.nextBoxNear(polygon); @@ -220,7 +220,7 @@ public void testIntersectRandom() { public void testIntersectEdgeCases() { for (int i = 0; i < 100; i++) { Polygon polygon = nextPolygon(); - Polygon2D impl = Polygon2D.create(polygon); + Component impl = Polygon2D.create(polygon); for (int j = 0; j < 10; j++) { Rectangle rectangle = GeoTestUtil.nextBoxNear(polygon); @@ -253,7 +253,7 @@ public void testIntersectEdgeCases() { /** Tests edge case behavior with respect to insideness */ public void testEdgeInsideness() { - Polygon2D poly = Polygon2D.create(new Polygon(new double[] { -2, -2, 2, 2, -2 }, new double[] { -2, 2, 2, -2, -2 })); + Component poly = Polygon2D.create(new Polygon(new double[] { -2, -2, 2, 2, -2 }, new double[] { -2, 2, 2, -2, -2 })); assertTrue(poly.contains(-2, -2)); // bottom left corner: true assertTrue(poly.contains(-2, 2)); // bottom right corner: true assertTrue(poly.contains(2, -2)); // top left corner: true @@ -281,7 +281,7 @@ public void testContainsAgainstOriginal() { while (polygon.getHoles().length > 0) { polygon = nextPolygon(); } - Polygon2D impl = Polygon2D.create(polygon); + Component impl = Polygon2D.create(polygon); // random lat/lons against polygon for (int j = 0; j < 1000; j++) { @@ -298,7 +298,7 @@ public void testContainsAgainstOriginal() { public void testRelateTriangle() { for (int i = 0; i < 100; ++i) { Polygon polygon = nextPolygon(); - Polygon2D impl = Polygon2D.create(polygon); + Component impl = Polygon2D.create(polygon); for (int j = 0; j < 100; j++) { double[] a = nextPointNear(polygon); @@ -315,7 +315,7 @@ public void testRelateTriangle() { public void testRelateTriangleContainsPolygon() { Polygon polygon = new Polygon(new double[]{0, 0, 1, 1, 0}, new double[]{0, 1, 1, 0, 0}); - Polygon2D impl = Polygon2D.create(polygon); + Component impl = Polygon2D.create(polygon); assertEquals(Relation.CELL_CROSSES_QUERY, impl.relateTriangle(-10 , -1, 2, -1, 10, 10)); } @@ -327,7 +327,7 @@ public void testRelateTriangleEdgeCases() { // random number of vertices int numVertices = RandomNumbers.randomIntBetween(random(), 100, 1000); Polygon polygon = createRegularPolygon(0, 0, randomRadius, numVertices); - Polygon2D impl = Polygon2D.create(polygon); + Component impl = Polygon2D.create(polygon); // create and test a simple tessellation for (int j = 1; j < numVertices; ++j) { @@ -342,7 +342,7 @@ public void testRelateTriangleEdgeCases() { public void testLineCrossingPolygonPoints() { Polygon p = new Polygon(new double[] {0, -1, 0, 1, 0}, new double[] {-1, 0, 1, 0, -1}); - Polygon2D polygon2D = Polygon2D.create(p); + Component polygon2D = Polygon2D.create(p); Relation rel = polygon2D.relateTriangle(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(-1.5)), GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(0)), GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(1.5)), @@ -354,7 +354,7 @@ public void testLineCrossingPolygonPoints() { public void testRandomLineCrossingPolygon() { Polygon p = GeoTestUtil.createRegularPolygon(0, 0, 1000, TestUtil.nextInt(random(), 100, 10000)); - Polygon2D polygon2D = Polygon2D.create(p); + Component polygon2D = Polygon2D.create(p); for (int i=0; i < 1000; i ++) { double longitude = GeoTestUtil.nextLongitude(); double latitude = GeoTestUtil.nextLatitude(); diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShape.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShape.java index ee41ffa51fce..4b4bded65ab8 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShape.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShape.java @@ -19,9 +19,13 @@ import java.util.ArrayList; import java.util.List; +import org.apache.lucene.geo.Component; +import org.apache.lucene.geo.ComponentTree; import org.apache.lucene.geo.GeoUtils; import org.apache.lucene.geo.Line; +import org.apache.lucene.geo.Line2D; import org.apache.lucene.geo.Polygon; +import org.apache.lucene.geo.Polygon2D; import org.apache.lucene.geo.Tessellator; import org.apache.lucene.geo.Tessellator.Triangle; import org.apache.lucene.index.PointValues; @@ -104,14 +108,42 @@ public static Query newBoxQuery(String field, QueryRelation queryRelation, doubl * note: does not support dateline crossing **/ public static Query newLineQuery(String field, QueryRelation queryRelation, Line... lines) { - return new LatLonShapeLineQuery(field, queryRelation, lines); + if (lines == null) { + throw new IllegalArgumentException("lines must not be null"); + } + if (lines.length == 0) { + throw new IllegalArgumentException("lines must not be empty"); + } + for (int i = 0; i < lines.length; ++i) { + if (lines[i] == null) { + throw new IllegalArgumentException("line[" + i + "] must not be null"); + } else if (lines[i].minLon > lines[i].maxLon) { + throw new IllegalArgumentException("LatLonShapeLineQuery does not currently support querying across dateline."); + } + } + Component component = Line2D.create(lines.clone()); + return new LatLonShapeComponentQuery(field, queryRelation, component); } /** create a query to find all polygons that intersect a provided polygon (or array of polygons) * note: does not support dateline crossing **/ public static Query newPolygonQuery(String field, QueryRelation queryRelation, Polygon... polygons) { - return new LatLonShapePolygonQuery(field, queryRelation, polygons); + if (polygons == null) { + throw new IllegalArgumentException("polygons must not be null"); + } + if (polygons.length == 0) { + throw new IllegalArgumentException("polygons must not be empty"); + } + for (int i = 0; i < polygons.length; i++) { + if (polygons[i] == null) { + throw new IllegalArgumentException("polygon[" + i + "] must not be null"); + } else if (polygons[i].minLon > polygons[i].maxLon) { + throw new IllegalArgumentException("LatLonShapePolygonQuery does not currently support querying across dateline."); + } + } + Component component = Polygon2D.create(polygons.clone()); + return new LatLonShapeComponentQuery(field, queryRelation, component); } /** polygons are decomposed into tessellated triangles using {@link org.apache.lucene.geo.Tessellator} diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapePolygonQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeComponentQuery.java similarity index 67% rename from lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapePolygonQuery.java rename to lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeComponentQuery.java index 94dde0d49482..ea3bf2a82801 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapePolygonQuery.java +++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeComponentQuery.java @@ -16,47 +16,35 @@ */ package org.apache.lucene.document; -import java.util.Arrays; +import java.util.Objects; import org.apache.lucene.document.LatLonShape.QueryRelation; +import org.apache.lucene.geo.Component; +import org.apache.lucene.geo.ComponentTree; import org.apache.lucene.geo.GeoEncodingUtils; -import org.apache.lucene.geo.Polygon; -import org.apache.lucene.geo.Polygon2D; import org.apache.lucene.index.PointValues.Relation; import org.apache.lucene.util.NumericUtils; /** - * Finds all previously indexed shapes that intersect the specified arbitrary. + * Finds all previously indexed shapes that intersect the specified arbitrary component tree. * *

The field must be indexed using - * {@link org.apache.lucene.document.LatLonShape#createIndexableFields} added per document. + * {@link LatLonShape#createIndexableFields} added per document. * * @lucene.experimental **/ -final class LatLonShapePolygonQuery extends LatLonShapeQuery { - final Polygon[] polygons; - final private Polygon2D poly2D; +final class LatLonShapeComponentQuery extends LatLonShapeQuery { + final private Component component; /** * Creates a query that matches all indexed shapes to the provided polygons */ - public LatLonShapePolygonQuery(String field, QueryRelation queryRelation, Polygon... polygons) { + public LatLonShapeComponentQuery(String field, QueryRelation queryRelation, Component component) { super(field, queryRelation); - if (polygons == null) { - throw new IllegalArgumentException("polygons must not be null"); + if (component == null) { + throw new IllegalArgumentException("componentTree must not be null"); } - if (polygons.length == 0) { - throw new IllegalArgumentException("polygons must not be empty"); - } - for (int i = 0; i < polygons.length; i++) { - if (polygons[i] == null) { - throw new IllegalArgumentException("polygon[" + i + "] must not be null"); - } else if (polygons[i].minLon > polygons[i].maxLon) { - throw new IllegalArgumentException("LatLonShapePolygonQuery does not currently support querying across dateline."); - } - } - this.polygons = polygons.clone(); - this.poly2D = Polygon2D.create(polygons); + this.component = component; } @Override @@ -69,7 +57,7 @@ protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] double maxLon = GeoEncodingUtils.decodeLongitude(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset)); // check internal node against query - return poly2D.relate(minLat, maxLat, minLon, maxLon); + return component.relate(minLat, maxLat, minLon, maxLon); } @Override @@ -84,10 +72,10 @@ protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation qu double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle[5]); if (queryRelation == QueryRelation.WITHIN) { - return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; + return component.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; } // INTERSECTS - return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY; + return component.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY; } @Override @@ -100,19 +88,19 @@ public String toString(String field) { sb.append(this.field); sb.append(':'); } - sb.append("Polygon(" + polygons[0].toGeoJSON() + ")"); + sb.append(component.toString()); return sb.toString(); } @Override protected boolean equalsTo(Object o) { - return super.equalsTo(o) && Arrays.equals(polygons, ((LatLonShapePolygonQuery)o).polygons); + return super.equalsTo(o) && Objects.equals(component, ((LatLonShapeComponentQuery)o).component); } @Override public int hashCode() { int hash = super.hashCode(); - hash = 31 * hash + Arrays.hashCode(polygons); + hash = 31 * hash + Objects.hashCode(component); return hash; } } \ No newline at end of file diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeLineQuery.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeLineQuery.java deleted file mode 100644 index e7933457c3ce..000000000000 --- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonShapeLineQuery.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.lucene.document; - -import java.util.Arrays; - -import org.apache.lucene.document.LatLonShape.QueryRelation; -import org.apache.lucene.geo.GeoEncodingUtils; -import org.apache.lucene.geo.Line; -import org.apache.lucene.geo.Line2D; -import org.apache.lucene.index.PointValues.Relation; -import org.apache.lucene.util.NumericUtils; - -/** - * Finds all previously indexed shapes that intersect the specified arbitrary {@code Line}. - *

- * Note: - *

- *

- * todo: - *

- *

The field must be indexed using - * {@link org.apache.lucene.document.LatLonShape#createIndexableFields} added per document. - * - * @lucene.experimental - **/ -final class LatLonShapeLineQuery extends LatLonShapeQuery { - final Line[] lines; - final private Line2D line2D; - - public LatLonShapeLineQuery(String field, QueryRelation queryRelation, Line... lines) { - super(field, queryRelation); - /** line queries do not support within relations, only intersects and disjoint */ - if (queryRelation == QueryRelation.WITHIN) { - throw new IllegalArgumentException("LatLonShapeLineQuery does not support " + QueryRelation.WITHIN + " queries"); - } - - if (lines == null) { - throw new IllegalArgumentException("lines must not be null"); - } - if (lines.length == 0) { - throw new IllegalArgumentException("lines must not be empty"); - } - for (int i = 0; i < lines.length; ++i) { - if (lines[i] == null) { - throw new IllegalArgumentException("line[" + i + "] must not be null"); - } else if (lines[i].minLon > lines[i].maxLon) { - throw new IllegalArgumentException("LatLonShapeLineQuery does not currently support querying across dateline."); - } - } - this.lines = lines.clone(); - this.line2D = Line2D.create(lines); - } - - @Override - protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle, - int maxXOffset, int maxYOffset, byte[] maxTriangle) { - double minLat = GeoEncodingUtils.decodeLatitude(NumericUtils.sortableBytesToInt(minTriangle, minYOffset)); - double minLon = GeoEncodingUtils.decodeLongitude(NumericUtils.sortableBytesToInt(minTriangle, minXOffset)); - double maxLat = GeoEncodingUtils.decodeLatitude(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset)); - double maxLon = GeoEncodingUtils.decodeLongitude(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset)); - - // check internal node against query - return line2D.relate(minLat, maxLat, minLon, maxLon); - } - - @Override - protected boolean queryMatches(byte[] t, int[] scratchTriangle, QueryRelation queryRelation) { - LatLonShape.decodeTriangle(t, scratchTriangle); - - double alat = GeoEncodingUtils.decodeLatitude(scratchTriangle[0]); - double alon = GeoEncodingUtils.decodeLongitude(scratchTriangle[1]); - double blat = GeoEncodingUtils.decodeLatitude(scratchTriangle[2]); - double blon = GeoEncodingUtils.decodeLongitude(scratchTriangle[3]); - double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle[4]); - double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle[5]); - - if (queryRelation == LatLonShape.QueryRelation.WITHIN) { - return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; - } - // INTERSECTS - return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY; - } - - @Override - public String toString(String field) { - final StringBuilder sb = new StringBuilder(); - sb.append(getClass().getSimpleName()); - sb.append(':'); - if (this.field.equals(field) == false) { - sb.append(" field="); - sb.append(this.field); - sb.append(':'); - } - sb.append("Line(" + lines[0].toGeoJSON() + ")"); - return sb.toString(); - } - - @Override - protected boolean equalsTo(Object o) { - return super.equalsTo(o) && Arrays.equals(lines, ((LatLonShapeLineQuery)o).lines); - } - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 31 * hash + Arrays.hashCode(lines); - return hash; - } -} diff --git a/lucene/sandbox/src/java/org/apache/lucene/geo/Line2D.java b/lucene/sandbox/src/java/org/apache/lucene/geo/Line2D.java index 9f413195b8f3..016201404899 100644 --- a/lucene/sandbox/src/java/org/apache/lucene/geo/Line2D.java +++ b/lucene/sandbox/src/java/org/apache/lucene/geo/Line2D.java @@ -16,9 +16,9 @@ */ package org.apache.lucene.geo; -import org.apache.lucene.index.PointValues.Relation; +import java.util.Objects; -import static org.apache.lucene.geo.GeoUtils.orient; +import org.apache.lucene.index.PointValues.Relation; /** * 2D line implementation represented as a balanced interval tree of edges. @@ -27,23 +27,30 @@ * {@link #relate relate()} are {@code O(n)}, but for most practical lines are much faster than brute force. * @lucene.internal */ -public final class Line2D extends EdgeTree { +public final class Line2D implements Component { + + private final Line line; + private final EdgeTree tree; + private final Rectangle box; private Line2D(Line line) { - super(line.minLat, line.maxLat, line.minLon, line.maxLon, line.getLats(), line.getLons()); + this.line = line; + tree = EdgeTree.createTree(line.getLats(), line.getLons()); + box = new Rectangle(line.minLat, line.maxLat, line.minLon, line.maxLon); } - /** create a Line2D edge tree from provided array of Linestrings */ - public static Line2D create(Line... lines) { - Line2D components[] = new Line2D[lines.length]; - for (int i = 0; i < components.length; ++i) { - components[i] = new Line2D(lines[i]); + @Override + public boolean contains(double latitude, double longitude) { + if (Rectangle.containsPoint(latitude, longitude, box.minLat, box.maxLat, box.minLon, box.maxLon)) { + if (tree.pointInEdge(latitude, longitude)) { + return true; + } } - return (Line2D)createTree(components, 0, components.length - 1, false); + return false; } @Override - protected Relation componentRelate(double minLat, double maxLat, double minLon, double maxLon) { + public Relation relate(double minLat, double maxLat, double minLon, double maxLon) { if (tree.crossesBox(minLat, maxLat, minLon, maxLon, true)) { return Relation.CELL_CROSSES_QUERY; } @@ -51,10 +58,10 @@ protected Relation componentRelate(double minLat, double maxLat, double minLon, } @Override - protected Relation componentRelateTriangle(double ax, double ay, double bx, double by, double cx, double cy) { + public Relation relateTriangle(double ax, double ay, double bx, double by, double cx, double cy) { if (ax == bx && bx == cx && ay == by && by == cy) { // indexed "triangle" is a point: check if point lies on any line segment - if (isPointOnLine(tree, ax, ay)) { + if (tree.pointInEdge(ay, ax)) { return Relation.CELL_INSIDE_QUERY; } } else if ((ax == cx && ay == cy) || (bx == cx && by == cy)) { @@ -63,7 +70,7 @@ protected Relation componentRelateTriangle(double ax, double ay, double bx, doub return Relation.CELL_CROSSES_QUERY; } return Relation.CELL_OUTSIDE_QUERY; - } else if (pointInTriangle(tree.lon1, tree.lat1, ax, ay, bx, by, cx, cy) == true || + } else if (Component.pointInTriangle(tree.lon1, tree.lat1, ax, ay, bx, by, cx, cy) == true || tree.crossesTriangle(ax, ay, bx, by, cx, cy)) { // indexed "triangle" is a triangle: return Relation.CELL_CROSSES_QUERY; @@ -71,24 +78,42 @@ protected Relation componentRelateTriangle(double ax, double ay, double bx, doub return Relation.CELL_OUTSIDE_QUERY; } - /** returns true if the provided x, y point lies on the line */ - private boolean isPointOnLine(Edge tree, double x, double y) { - if (y <= tree.max) { - double minY = StrictMath.min(tree.lat1, tree.lat2); - double maxY = StrictMath.max(tree.lat1, tree.lat2); - double minX = StrictMath.min(tree.lon1, tree.lon2); - double maxX = StrictMath.max(tree.lon1, tree.lon2); - if (Rectangle.containsPoint(y, x, minY, maxY, minX, maxX) && - orient(tree.lon1, tree.lat1, tree.lon2, tree.lat2, x, y) == 0) { - return true; - } - if (tree.left != null && isPointOnLine(tree.left, x, y)) { - return true; - } - if (tree.right != null && maxY >= tree.low && isPointOnLine(tree.right, x, y)) { - return true; - } + @Override + public Rectangle getBoundingBox() { + return box; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Line2D line2D = (Line2D) o; + return Objects.equals(line, line2D.line); + } + + @Override + public int hashCode() { + return Objects.hash(line); + } + + @Override + public String toString() { + return "Line2D{" + + "line=" + line + + '}'; + } + + /** Builds a Component from polygon */ + private static Component createComponent(Line line) { + return new Line2D(line); + } + + /** create a Line2D edge tree from provided array of Linestrings */ + public static Component create(Line... lines) { + Component[] components = new Component[lines.length]; + for (int i = 0; i < components.length; ++i) { + components[i] = createComponent(lines[i]); } - return false; + return ComponentTree.create(components); } } \ No newline at end of file diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java b/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java index d15248df8b28..d491d6d5551a 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/BaseLatLonShapeTestCase.java @@ -23,6 +23,8 @@ import com.carrotsearch.randomizedtesting.generators.RandomPicks; import org.apache.lucene.document.LatLonShape.QueryRelation; +import org.apache.lucene.geo.Component; +import org.apache.lucene.geo.ComponentTree; import org.apache.lucene.geo.GeoTestUtil; import org.apache.lucene.geo.Line; import org.apache.lucene.geo.Line2D; @@ -395,7 +397,7 @@ protected void verifyRandomLineQueries(IndexReader reader, Object... shapes) thr // line Line queryLine = randomQueryLine(shapes); - Line2D queryLine2D = Line2D.create(queryLine); + Component queryLine2D = Line2D.create(new Line[] {queryLine}); QueryRelation queryRelation = RandomPicks.randomFrom(random(), POINT_LINE_RELATIONS); Query query = newLineQuery(FIELD_NAME, queryRelation, queryLine); @@ -436,7 +438,7 @@ public void collect(int doc) throws IOException { } else if (shapes[id] == null) { expected = false; } else { - expected = getValidator(queryRelation).testLineQuery(queryLine2D, shapes[id]); + expected = getValidator(queryRelation).testComponentQuery(queryLine2D, shapes[id]); } if (hits.get(docID) != expected) { @@ -486,7 +488,7 @@ protected void verifyRandomPolygonQueries(IndexReader reader, Object... shapes) // Polygon Polygon queryPolygon = GeoTestUtil.nextPolygon(); - Polygon2D queryPoly2D = Polygon2D.create(queryPolygon); + Component queryPoly2D = Polygon2D.create(queryPolygon); QueryRelation queryRelation = RandomPicks.randomFrom(random(), QueryRelation.values()); Query query = newPolygonQuery(FIELD_NAME, queryRelation, queryPolygon); @@ -527,7 +529,7 @@ public void collect(int doc) throws IOException { } else if (shapes[id] == null) { expected = false; } else { - expected = getValidator(queryRelation).testPolygonQuery(queryPoly2D, shapes[id]); + expected = getValidator(queryRelation).testComponentQuery(queryPoly2D, shapes[id]); } if (hits.get(docID) != expected) { @@ -637,8 +639,7 @@ static ShapeType fromObject(Object shape) { protected static abstract class Validator { protected QueryRelation queryRelation = QueryRelation.INTERSECTS; public abstract boolean testBBoxQuery(double minLat, double maxLat, double minLon, double maxLon, Object shape); - public abstract boolean testLineQuery(Line2D line2d, Object shape); - public abstract boolean testPolygonQuery(Polygon2D poly2d, Object shape); + public abstract boolean testComponentQuery(Component component, Object shape); public void setRelation(QueryRelation relation) { this.queryRelation = relation; diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java index 7e6d9958ace5..6544fc743bad 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonLineShapeQueries.java @@ -19,11 +19,10 @@ import com.carrotsearch.randomizedtesting.generators.RandomNumbers; import org.apache.lucene.document.LatLonShape.QueryRelation; -import org.apache.lucene.geo.EdgeTree; +import org.apache.lucene.geo.Component; +import org.apache.lucene.geo.ComponentTree; import org.apache.lucene.geo.GeoTestUtil; import org.apache.lucene.geo.Line; -import org.apache.lucene.geo.Line2D; -import org.apache.lucene.geo.Polygon2D; import org.apache.lucene.geo.Rectangle; import org.apache.lucene.geo.Rectangle2D; import org.apache.lucene.index.PointValues.Relation; @@ -97,20 +96,11 @@ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double } @Override - public boolean testLineQuery(Line2D line2d, Object shape) { - return testLine(line2d, (Line) shape); - } - - @Override - public boolean testPolygonQuery(Polygon2D poly2d, Object shape) { - return testLine(poly2d, (Line) shape); - } - - private boolean testLine(EdgeTree queryPoly, Line line) { - + public boolean testComponentQuery(Component component, Object shape) { + Line line = (Line) shape; for (int i = 0, j = 1; j < line.numPoints(); ++i, ++j) { double[] qTriangle = quantizeTriangle(line.getLon(i), line.getLat(i), line.getLon(j), line.getLat(j), line.getLon(i), line.getLat(i)); - Relation r = queryPoly.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]); + Relation r = component.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]); if (queryRelation == QueryRelation.DISJOINT) { if (r != Relation.CELL_OUTSIDE_QUERY) return false; } else if (queryRelation == QueryRelation.WITHIN) { diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiLineShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiLineShapeQueries.java index 5c9c42ee330c..a4ae80eee85f 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiLineShapeQueries.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiLineShapeQueries.java @@ -20,9 +20,9 @@ import java.util.List; import org.apache.lucene.document.LatLonShape.QueryRelation; +import org.apache.lucene.geo.Component; +import org.apache.lucene.geo.ComponentTree; import org.apache.lucene.geo.Line; -import org.apache.lucene.geo.Line2D; -import org.apache.lucene.geo.Polygon2D; /** random bounding box and polygon query tests for random indexed arrays of {@link Line} types */ public class TestLatLonMultiLineShapeQueries extends BaseLatLonShapeTestCase { @@ -83,26 +83,10 @@ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double } @Override - public boolean testLineQuery(Line2D query, Object shape) { + public boolean testComponentQuery(Component component, Object shape) { Line[] lines = (Line[])shape; for (Line l : lines) { - boolean b = LINEVALIDATOR.testLineQuery(query, l); - if (b == true && queryRelation == QueryRelation.INTERSECTS) { - return true; - } else if (b == false && queryRelation == QueryRelation.DISJOINT) { - return false; - } else if (b == false && queryRelation == QueryRelation.WITHIN) { - return false; - } - } - return queryRelation != QueryRelation.INTERSECTS; - } - - @Override - public boolean testPolygonQuery(Polygon2D query, Object shape) { - Line[] lines = (Line[])shape; - for (Line l : lines) { - boolean b = LINEVALIDATOR.testPolygonQuery(query, l); + boolean b = LINEVALIDATOR.testComponentQuery(component, l); if (b == true && queryRelation == QueryRelation.INTERSECTS) { return true; } else if (b == false && queryRelation == QueryRelation.DISJOINT) { diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPointShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPointShapeQueries.java index 44d095d2d1b0..0c4f355baf62 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPointShapeQueries.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPointShapeQueries.java @@ -20,9 +20,9 @@ import java.util.List; import org.apache.lucene.document.LatLonShape.QueryRelation; +import org.apache.lucene.geo.Component; +import org.apache.lucene.geo.ComponentTree; import org.apache.lucene.geo.GeoTestUtil; -import org.apache.lucene.geo.Line2D; -import org.apache.lucene.geo.Polygon2D; /** random bounding box and polygon query tests for random indexed arrays of {@code latitude, longitude} points */ public class TestLatLonMultiPointShapeQueries extends BaseLatLonShapeTestCase { @@ -83,26 +83,10 @@ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double } @Override - public boolean testLineQuery(Line2D query, Object shape) { + public boolean testComponentQuery(Component component, Object shape) { Point[] points = (Point[]) shape; for (Point p : points) { - boolean b = POINTVALIDATOR.testLineQuery(query, p); - if (b == true && queryRelation == QueryRelation.INTERSECTS) { - return true; - } else if (b == false && queryRelation == QueryRelation.DISJOINT) { - return false; - } else if (b == false && queryRelation == QueryRelation.WITHIN) { - return false; - } - } - return queryRelation != QueryRelation.INTERSECTS; - } - - @Override - public boolean testPolygonQuery(Polygon2D query, Object shape) { - Point[] points = (Point[]) shape; - for (Point p : points) { - boolean b = POINTVALIDATOR.testPolygonQuery(query, p); + boolean b = POINTVALIDATOR.testComponentQuery(component, p); if (b == true && queryRelation == QueryRelation.INTERSECTS) { return true; } else if (b == false && queryRelation == QueryRelation.DISJOINT) { diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPolygonShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPolygonShapeQueries.java index 3729bbad08b5..530b1891b248 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPolygonShapeQueries.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonMultiPolygonShapeQueries.java @@ -20,9 +20,9 @@ import java.util.List; import org.apache.lucene.document.LatLonShape.QueryRelation; -import org.apache.lucene.geo.Line2D; +import org.apache.lucene.geo.Component; +import org.apache.lucene.geo.ComponentTree; import org.apache.lucene.geo.Polygon; -import org.apache.lucene.geo.Polygon2D; import org.apache.lucene.geo.Tessellator; /** random bounding box and polygon query tests for random indexed arrays of {@link Polygon} types */ @@ -95,26 +95,10 @@ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double } @Override - public boolean testLineQuery(Line2D query, Object shape) { + public boolean testComponentQuery(Component component, Object shape) { Polygon[] polygons = (Polygon[])shape; for (Polygon p : polygons) { - boolean b = POLYGONVALIDATOR.testLineQuery(query, p); - if (b == true && queryRelation == QueryRelation.INTERSECTS) { - return true; - } else if (b == false && queryRelation == QueryRelation.DISJOINT) { - return false; - } else if (b == false && queryRelation == QueryRelation.WITHIN) { - return false; - } - } - return queryRelation != QueryRelation.INTERSECTS; - } - - @Override - public boolean testPolygonQuery(Polygon2D query, Object shape) { - Polygon[] polygons = (Polygon[])shape; - for (Polygon p : polygons) { - boolean b = POLYGONVALIDATOR.testPolygonQuery(query, p); + boolean b = POLYGONVALIDATOR.testComponentQuery(component, p); if (b == true && queryRelation == QueryRelation.INTERSECTS) { return true; } else if (b == false && queryRelation == QueryRelation.DISJOINT) { diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java index d894aed7c9a7..5ac6e6282247 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPointShapeQueries.java @@ -18,11 +18,10 @@ import com.carrotsearch.randomizedtesting.generators.RandomNumbers; import org.apache.lucene.document.LatLonShape.QueryRelation; -import org.apache.lucene.geo.EdgeTree; +import org.apache.lucene.geo.Component; +import org.apache.lucene.geo.ComponentTree; import org.apache.lucene.geo.GeoTestUtil; import org.apache.lucene.geo.Line; -import org.apache.lucene.geo.Line2D; -import org.apache.lucene.geo.Polygon2D; import org.apache.lucene.index.PointValues.Relation; /** random bounding box and polygon query tests for random generated {@code latitude, longitude} points */ @@ -90,20 +89,12 @@ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double } @Override - public boolean testLineQuery(Line2D line2d, Object shape) { - return testPoint(line2d, (Point) shape); - } - - @Override - public boolean testPolygonQuery(Polygon2D poly2d, Object shape) { - return testPoint(poly2d, (Point) shape); - } - - private boolean testPoint(EdgeTree tree, Point p) { + public boolean testComponentQuery(Component component, Object shape) { + Point p = (Point) shape; double lat = quantizeLat(p.lat); double lon = quantizeLon(p.lon); // for consistency w/ the query we test the point as a triangle - Relation r = tree.relateTriangle(lon, lat, lon, lat, lon, lat); + Relation r = component.relateTriangle(lon, lat, lon, lat, lon, lat); if (queryRelation == QueryRelation.WITHIN) { return r == Relation.CELL_INSIDE_QUERY; } else if (queryRelation == QueryRelation.DISJOINT) { diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java index a3e060c9ae11..a56df0a6fadc 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPolygonShapeQueries.java @@ -19,10 +19,9 @@ import java.util.List; import org.apache.lucene.document.LatLonShape.QueryRelation; -import org.apache.lucene.geo.EdgeTree; -import org.apache.lucene.geo.Line2D; +import org.apache.lucene.geo.Component; +import org.apache.lucene.geo.ComponentTree; import org.apache.lucene.geo.Polygon; -import org.apache.lucene.geo.Polygon2D; import org.apache.lucene.geo.Rectangle; import org.apache.lucene.geo.Rectangle2D; import org.apache.lucene.geo.Tessellator; @@ -86,20 +85,12 @@ public boolean testBBoxQuery(double minLat, double maxLat, double minLon, double } @Override - public boolean testLineQuery(Line2D query, Object shape) { - return testPolygon(query, (Polygon) shape); - } - - @Override - public boolean testPolygonQuery(Polygon2D query, Object shape) { - return testPolygon(query, (Polygon) shape); - } - - private boolean testPolygon(EdgeTree tree, Polygon shape) { - List tessellation = Tessellator.tessellate(shape); + public boolean testComponentQuery(Component component, Object shape) { + Polygon polygon = (Polygon) shape; + List tessellation = Tessellator.tessellate(polygon); for (Tessellator.Triangle t : tessellation) { double[] qTriangle = quantizeTriangle(t.getLon(0), t.getLat(0), t.getLon(1), t.getLat(1), t.getLon(2), t.getLat(2)); - Relation r = tree.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]); + Relation r = component.relateTriangle(qTriangle[1], qTriangle[0], qTriangle[3], qTriangle[2], qTriangle[5], qTriangle[4]); if (queryRelation == QueryRelation.DISJOINT) { if (r != Relation.CELL_OUTSIDE_QUERY) return false; } else if (queryRelation == QueryRelation.WITHIN) { diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java index a325bc760e22..0ce92950cb7c 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShape.java @@ -18,6 +18,7 @@ import com.carrotsearch.randomizedtesting.generators.RandomNumbers; import org.apache.lucene.document.LatLonShape.QueryRelation; +import org.apache.lucene.geo.Component; import org.apache.lucene.geo.GeoTestUtil; import org.apache.lucene.geo.Line; import org.apache.lucene.geo.Line2D; @@ -341,7 +342,7 @@ public void testLUCENE8679() { double blon = -52.67048754768767; Polygon polygon = new Polygon(new double[] {-14.448264200949083, 0, 0, -14.448264200949083, -14.448264200949083}, new double[] {0.9999999403953552, 0.9999999403953552, 124.50086371762484, 124.50086371762484, 0.9999999403953552}); - Polygon2D polygon2D = Polygon2D.create(polygon); + Component polygon2D = Polygon2D.create(polygon); PointValues.Relation rel = polygon2D.relateTriangle( quantizeLon(alon), quantizeLat(blat), quantizeLon(blon), quantizeLat(blat), @@ -359,7 +360,7 @@ public void testLUCENE8679() { public void testTriangleTouchingEdges() { Polygon p = new Polygon(new double[] {0, 0, 1, 1, 0}, new double[] {0, 1, 1, 0, 0}); - Polygon2D polygon2D = Polygon2D.create(p); + Component polygon2D = Polygon2D.create(p); //3 shared points PointValues.Relation rel = polygon2D.relateTriangle( quantizeLon(0.5), quantizeLat(0), @@ -459,7 +460,7 @@ public void testLUCENE8736() throws Exception { public void testTriangleCrossingPolygonVertices() { Polygon p = new Polygon(new double[] {0, 0, -5, -10, -5, 0}, new double[] {-1, 1, 5, 0, -5, -1}); - Polygon2D polygon2D = Polygon2D.create(p); + Component polygon2D = Polygon2D.create(p); PointValues.Relation rel = polygon2D.relateTriangle( quantizeLon(-5), quantizeLat(0), quantizeLon(10), quantizeLat(0), @@ -469,7 +470,7 @@ public void testTriangleCrossingPolygonVertices() { public void testLineCrossingPolygonVertices() { Polygon p = new Polygon(new double[] {0, -1, 0, 1, 0}, new double[] {-1, 0, 1, 0, -1}); - Polygon2D polygon2D = Polygon2D.create(p); + Component polygon2D = Polygon2D.create(p); PointValues.Relation rel = polygon2D.relateTriangle( quantizeLon(-1.5), quantizeLat(0), quantizeLon(1.5), quantizeLat(0), @@ -479,11 +480,12 @@ public void testLineCrossingPolygonVertices() { public void testLineSharedLine() { Line l = new Line(new double[] {0, 0, 0, 0}, new double[] {-2, -1, 0, 1}); - Line2D l2d = Line2D.create(l); + Component l2d = Line2D.create(l); PointValues.Relation r = l2d.relateTriangle( quantizeLon(-5), quantizeLat(0), quantizeLon(5), quantizeLat(0), quantizeLon(-5), quantizeLat(0)); assertEquals(PointValues.Relation.CELL_CROSSES_QUERY, r); } + } diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShapeEncoding.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShapeEncoding.java index e4cd2bc5e537..bd629b444ed2 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShapeEncoding.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonShapeEncoding.java @@ -19,6 +19,8 @@ import java.util.Arrays; +import org.apache.lucene.geo.Component; +import org.apache.lucene.geo.ComponentTree; import org.apache.lucene.geo.GeoEncodingUtils; import org.apache.lucene.geo.GeoTestUtil; import org.apache.lucene.geo.GeoUtils; @@ -527,7 +529,7 @@ private void verifyEncoding(double alat, double alon, double blat, double blon, for (int i =0; i < 100; i ++) { Polygon polygon = GeoTestUtil.nextPolygon(); - Polygon2D polygon2D = Polygon2D.create(polygon); + Component polygon2D = Polygon2D.create(polygon); PointValues.Relation originalRelation = polygon2D.relateTriangle(originalQuantize[1], originalQuantize[0], originalQuantize[3], originalQuantize[2], originalQuantize[5], originalQuantize[4]); PointValues.Relation encodedRelation = polygon2D.relateTriangle(encodedQuantize[1], encodedQuantize[0], encodedQuantize[3], encodedQuantize[2], encodedQuantize[5], encodedQuantize[4]); assertTrue(originalRelation == encodedRelation); diff --git a/lucene/sandbox/src/test/org/apache/lucene/geo/TestLine2D.java b/lucene/sandbox/src/test/org/apache/lucene/geo/TestLine2D.java index da25cba59f45..78923cd22595 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/geo/TestLine2D.java +++ b/lucene/sandbox/src/test/org/apache/lucene/geo/TestLine2D.java @@ -25,43 +25,43 @@ public class TestLine2D extends LuceneTestCase { public void testTriangleDisjoint() { Line line = new Line(new double[] {0, 1, 2, 3}, new double[] {0, 0, 2, 2}); - Line2D line2D = Line2D.create(line); + Component line2D = Line2D.create(line); int ax = GeoEncodingUtils.encodeLongitude(4); int ay = GeoEncodingUtils.encodeLatitude(4); int bx = GeoEncodingUtils.encodeLongitude(5); int by = GeoEncodingUtils.encodeLatitude(5); int cx = GeoEncodingUtils.encodeLongitude(5); int cy = GeoEncodingUtils.encodeLatitude(4); - assertEquals(Relation.CELL_OUTSIDE_QUERY, line2D.componentRelateTriangle(ax, ay, bx, by , cx, cy));; + assertEquals(Relation.CELL_OUTSIDE_QUERY, line2D.relateTriangle(ax, ay, bx, by , cx, cy));; } public void testTriangleIntersects() { Line line = new Line(new double[] {0.5, 0, 1, 2, 3}, new double[] {0.5, 0, 0, 2, 2}); - Line2D line2D = Line2D.create(line); + Component line2D = Line2D.create(line); int ax = GeoEncodingUtils.encodeLongitude(0.0); int ay = GeoEncodingUtils.encodeLatitude(0.0); int bx = GeoEncodingUtils.encodeLongitude(1); int by = GeoEncodingUtils.encodeLatitude(0); int cx = GeoEncodingUtils.encodeLongitude(0); int cy = GeoEncodingUtils.encodeLatitude(1); - assertEquals(Relation.CELL_CROSSES_QUERY, line2D.componentRelateTriangle(ax, ay, bx, by , cx, cy)); + assertEquals(Relation.CELL_CROSSES_QUERY, line2D.relateTriangle(ax, ay, bx, by , cx, cy)); } public void testTriangleContains() { Line line = new Line(new double[] {0.5, 0, 1, 2, 3}, new double[] {0.5, 0, 0, 2, 2}); - Line2D line2D = Line2D.create(line); + Component line2D = Line2D.create(line); int ax = GeoEncodingUtils.encodeLongitude(-10); int ay = GeoEncodingUtils.encodeLatitude(-10); int bx = GeoEncodingUtils.encodeLongitude(4); int by = GeoEncodingUtils.encodeLatitude(-10); int cx = GeoEncodingUtils.encodeLongitude(4); int cy = GeoEncodingUtils.encodeLatitude(30); - assertEquals(Relation.CELL_CROSSES_QUERY, line2D.componentRelateTriangle(ax, ay, bx, by , cx, cy)); + assertEquals(Relation.CELL_CROSSES_QUERY, line2D.relateTriangle(ax, ay, bx, by , cx, cy)); } public void testRandomTriangles() { Line line = TestLatLonLineShapeQueries.nextLine(); - Line2D line2D = Line2D.create(line); + Component line2D = Line2D.create(line); for (int i =0; i < 100; i++) { double ax = GeoTestUtil.nextLongitude(); @@ -78,7 +78,7 @@ public void testRandomTriangles() { Relation r = line2D.relate(tMinY, tMaxY, tMinX, tMaxX); if (r == Relation.CELL_OUTSIDE_QUERY) { - assertEquals(Relation.CELL_OUTSIDE_QUERY, line2D.componentRelateTriangle(ax, ay, bx, by, cx, cy)); + assertEquals(Relation.CELL_OUTSIDE_QUERY, line2D.relateTriangle(ax, ay, bx, by, cx, cy)); } } }