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
* 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
* 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 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
- *
- *
- *
- *