diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index e8c614e0e04..d359035d38f 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -61,6 +61,9 @@ Improvements * LUCENE-9123: Add new JapaneseTokenizer constructors with discardCompoundToken option that controls whether the tokenizer emits original (compound) tokens when the mode is not NORMAL. (Kazuaki Hiraga via Tomoko Uchida) +* LUCENE-9194: Simplify XYShapeXQuery API by adding a new abstract class called XYGeometry. Queries are + executed with input objects that extend such interface. (Ignacio Vera) + Optimizations --------------------- diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInPolygonQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInPolygonQuery.java index 525b77ece46..abf1c883884 100644 --- a/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInPolygonQuery.java +++ b/lucene/core/src/java/org/apache/lucene/document/LatLonDocValuesPointInPolygonQuery.java @@ -22,8 +22,8 @@ import java.util.Arrays; import org.apache.lucene.geo.Component2D; import org.apache.lucene.geo.GeoEncodingUtils; +import org.apache.lucene.geo.LatLonGeometry; import org.apache.lucene.geo.Polygon; -import org.apache.lucene.geo.Polygon2D; import org.apache.lucene.index.DocValues; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.SortedNumericDocValues; @@ -104,7 +104,7 @@ public class LatLonDocValuesPointInPolygonQuery extends Query { return new ConstantScoreWeight(this, boost) { - final Component2D tree = Polygon2D.create(polygons); + final Component2D tree = LatLonGeometry.create(polygons); final GeoEncodingUtils.PolygonPredicate polygonPredicate = GeoEncodingUtils.createComponentPredicate(tree); @Override diff --git a/lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java b/lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java index 82212aed3ad..b823ca6313b 100644 --- a/lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java +++ b/lucene/core/src/java/org/apache/lucene/document/LatLonPointInPolygonQuery.java @@ -21,8 +21,8 @@ import java.util.Arrays; import org.apache.lucene.geo.Component2D; import org.apache.lucene.geo.GeoEncodingUtils; +import org.apache.lucene.geo.LatLonGeometry; import org.apache.lucene.geo.Polygon; -import org.apache.lucene.geo.Polygon2D; import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; @@ -142,7 +142,7 @@ final class LatLonPointInPolygonQuery extends Query { @Override public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException { - final Component2D tree = Polygon2D.create(polygons); + final Component2D tree = LatLonGeometry.create(polygons); final GeoEncodingUtils.PolygonPredicate polygonPredicate = GeoEncodingUtils.createComponentPredicate(tree); // bounding box over all polygons, this can speed up tree intersection/cheaply improve approximation for complex multi-polygons final byte minLat[] = new byte[Integer.BYTES]; diff --git a/lucene/core/src/java/org/apache/lucene/document/XYShape.java b/lucene/core/src/java/org/apache/lucene/document/XYShape.java index 387d04853e5..88f9a5d5d7e 100644 --- a/lucene/core/src/java/org/apache/lucene/document/XYShape.java +++ b/lucene/core/src/java/org/apache/lucene/document/XYShape.java @@ -22,6 +22,9 @@ import java.util.List; import org.apache.lucene.document.ShapeField.QueryRelation; // javadoc import org.apache.lucene.document.ShapeField.Triangle; import org.apache.lucene.geo.Tessellator; +import org.apache.lucene.geo.XYGeometry; +import org.apache.lucene.geo.XYPoint; +import org.apache.lucene.geo.XYRectangle; import org.apache.lucene.index.PointValues; // javadoc import org.apache.lucene.geo.XYLine; import org.apache.lucene.geo.XYPolygon; @@ -34,7 +37,7 @@ import static org.apache.lucene.geo.XYEncodingUtils.encode; /** * A cartesian shape utility class for indexing and searching geometries whose vertices are unitless x, y values. *
- * This class defines six static factory methods for common indexing and search operations: + * This class defines seven static factory methods for common indexing and search operations: *
The field must be indexed using - * {@link org.apache.lucene.document.XYShape#createIndexableFields} added per document. - **/ -public class XYShapeBoundingBoxQuery extends ShapeQuery { - final Component2D rectangle2D; - final private XYRectangle rectangle; - - /** construct a Bounding Box Query over cartesian geometries from the given ranges */ - public XYShapeBoundingBoxQuery(String field, QueryRelation queryRelation, double minX, double maxX, double minY, double maxY) { - super(field, queryRelation); - this.rectangle = new XYRectangle(minX, maxX, minY, maxY); - this.rectangle2D = XYRectangle2D.create(this.rectangle); - } - - @Override - protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle, - int maxXOffset, int maxYOffset, byte[] maxTriangle) { - double minY = decode(NumericUtils.sortableBytesToInt(minTriangle, minYOffset)); - double minX = decode(NumericUtils.sortableBytesToInt(minTriangle, minXOffset)); - double maxY = decode(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset)); - double maxX = decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset)); - // check internal node against query - Relation rel = rectangle2D.relate(minX, maxX, minY, maxY); - // TODO: Check if this really helps - if (queryRelation == QueryRelation.INTERSECTS && rel == Relation.CELL_CROSSES_QUERY) { - // for intersects we can restrict the conditions by using the inner box - double innerMaxY = decode(NumericUtils.sortableBytesToInt(maxTriangle, minYOffset)); - if (rectangle2D.relate(minX, maxX, minY, innerMaxY) == Relation.CELL_INSIDE_QUERY) { - return Relation.CELL_INSIDE_QUERY; - } - double innerMaX = decode(NumericUtils.sortableBytesToInt(maxTriangle, minXOffset)); - if (rectangle2D.relate(minX, innerMaX, minY, maxY) == Relation.CELL_INSIDE_QUERY) { - return Relation.CELL_INSIDE_QUERY; - } - double innerMinY = decode(NumericUtils.sortableBytesToInt(minTriangle, maxYOffset)); - if (rectangle2D.relate(minX, maxX, innerMinY, maxY) == Relation.CELL_INSIDE_QUERY) { - return Relation.CELL_INSIDE_QUERY; - } - double innerMinX = decode(NumericUtils.sortableBytesToInt(minTriangle, maxXOffset)); - if (rectangle2D.relate(innerMinX, maxX, minY, maxY) == Relation.CELL_INSIDE_QUERY) { - return Relation.CELL_INSIDE_QUERY; - } - } - return rel; - } - - /** returns true if the query matches the encoded triangle */ - @Override - protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) { - // decode indexed triangle - ShapeField.decodeTriangle(t, scratchTriangle); - - double aY = decode(scratchTriangle.aY); - double aX = decode(scratchTriangle.aX); - double bY = decode(scratchTriangle.bY); - double bX = decode(scratchTriangle.bX); - double cY = decode(scratchTriangle.cY); - double cX = decode(scratchTriangle.cX); - - switch (queryRelation) { - case INTERSECTS: return rectangle2D.relateTriangle(aX, aY, bX, bY, cX, cY) != Relation.CELL_OUTSIDE_QUERY; - case WITHIN: return rectangle2D.contains(aX, aY) && rectangle2D.contains(bX, bY) && rectangle2D.contains(cX, cY); - case DISJOINT: return rectangle2D.relateTriangle(aX, aY, bX, bY, cX, cY) == Relation.CELL_OUTSIDE_QUERY; - default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]"); - } - } - - @Override - protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) { - ShapeField.decodeTriangle(t, scratchTriangle); - - double aY = decode(scratchTriangle.aY); - double aX = decode(scratchTriangle.aX); - double bY = decode(scratchTriangle.bY); - double bX = decode(scratchTriangle.bX); - double cY = decode(scratchTriangle.cY); - double cX = decode(scratchTriangle.cX); - - return rectangle2D.withinTriangle(aX, aY, scratchTriangle.ab, bX, bY, scratchTriangle.bc, cX, cY, scratchTriangle.ca); - } - - @Override - public boolean equals(Object o) { - return sameClassAs(o) && equalsTo(getClass().cast(o)); - } - - @Override - protected boolean equalsTo(Object o) { - return super.equalsTo(o) && rectangle.equals(((XYShapeBoundingBoxQuery)o).rectangle); - } - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 31 * hash + rectangle.hashCode(); - return hash; - } - - @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(rectangle.toString()); - return sb.toString(); - } -} \ No newline at end of file diff --git a/lucene/core/src/java/org/apache/lucene/document/XYShapeLineQuery.java b/lucene/core/src/java/org/apache/lucene/document/XYShapeLineQuery.java deleted file mode 100644 index 1a61bddfed4..00000000000 --- a/lucene/core/src/java/org/apache/lucene/document/XYShapeLineQuery.java +++ /dev/null @@ -1,146 +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.ShapeField.QueryRelation; -import org.apache.lucene.geo.Component2D; -import org.apache.lucene.geo.Line2D; -import org.apache.lucene.geo.XYLine; -import org.apache.lucene.index.PointValues.Relation; -import org.apache.lucene.util.NumericUtils; - -import static org.apache.lucene.geo.XYEncodingUtils.decode; - -/** - * Finds all previously indexed cartesian shapes that intersect the specified arbitrary {@code XYLine}. - *
- * Note: - *
- * todo: - *
The field must be indexed using - * {@link org.apache.lucene.document.XYShape#createIndexableFields} added per document. - **/ -final class XYShapeLineQuery extends ShapeQuery { - final XYLine[] lines; - final private Component2D line2D; - - /** construct a Line Query over cartesian geometries from the given line objects */ - public XYShapeLineQuery(String field, QueryRelation queryRelation, XYLine... lines) { - super(field, queryRelation); - /** line queries do not support within relations, only intersects and disjoint */ - if (queryRelation == QueryRelation.WITHIN) { - throw new IllegalArgumentException("XYShapeLineQuery 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].minX > lines[i].maxX) { - throw new IllegalArgumentException("XYShapeLineQuery: minX cannot be greater than maxX."); - } else if (lines[i].minY > lines[i].maxY) { - throw new IllegalArgumentException("XYShapeLineQuery: minY cannot be greater than maxY."); - } - } - 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 minY = decode(NumericUtils.sortableBytesToInt(minTriangle, minYOffset)); - double minX = decode(NumericUtils.sortableBytesToInt(minTriangle, minXOffset)); - double maxY = decode(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset)); - double maxX = decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset)); - - // check internal node against query - return line2D.relate(minX, maxX, minY, maxY); - } - - @Override - protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, QueryRelation queryRelation) { - ShapeField.decodeTriangle(t, scratchTriangle); - - double alat = decode(scratchTriangle.aY); - double alon = decode(scratchTriangle.aX); - double blat = decode(scratchTriangle.bY); - double blon = decode(scratchTriangle.bX); - double clat = decode(scratchTriangle.cY); - double clon = decode(scratchTriangle.cX); - - switch (queryRelation) { - case INTERSECTS: return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY; - case WITHIN: return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; - case DISJOINT: return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_OUTSIDE_QUERY; - default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]"); - } - } - - @Override - protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) { - ShapeField.decodeTriangle(t, scratchTriangle); - - double alat = decode(scratchTriangle.aY); - double alon = decode(scratchTriangle.aX); - double blat = decode(scratchTriangle.bY); - double blon = decode(scratchTriangle.bX); - double clat = decode(scratchTriangle.cY); - double clon = decode(scratchTriangle.cX); - - return line2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca); - } - - @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("XYLine(").append(lines[0].toGeoJSON()).append(")"); - return sb.toString(); - } - - @Override - protected boolean equalsTo(Object o) { - return super.equalsTo(o) && Arrays.equals(lines, ((XYShapeLineQuery)o).lines); - } - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 31 * hash + Arrays.hashCode(lines); - return hash; - } -} diff --git a/lucene/core/src/java/org/apache/lucene/document/XYShapePointQuery.java b/lucene/core/src/java/org/apache/lucene/document/XYShapePointQuery.java deleted file mode 100644 index dee78692b5e..00000000000 --- a/lucene/core/src/java/org/apache/lucene/document/XYShapePointQuery.java +++ /dev/null @@ -1,126 +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.geo.Component2D; -import org.apache.lucene.geo.Point2D; -import org.apache.lucene.geo.XYEncodingUtils; -import org.apache.lucene.index.PointValues.Relation; -import org.apache.lucene.util.NumericUtils; - -import static org.apache.lucene.geo.XYEncodingUtils.decode; - -/** - * Finds all previously indexed shapes that intersect the specified bounding box. - * - *
The field must be indexed using - * {@link XYShape#createIndexableFields} added per document. - **/ -final class XYShapePointQuery extends ShapeQuery { - final Component2D point2D; - final float[][] points; - - /** construct a Point or MultiPoint Query over cartesian geometries from the given point values */ - public XYShapePointQuery(String field, ShapeField.QueryRelation queryRelation, float[][] points) { - super(field, queryRelation); - this.points = points; - this.point2D = Point2D.create(points); - } - - @Override - protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle, - int maxXOffset, int maxYOffset, byte[] maxTriangle) { - double minY = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, minYOffset)); - double minX = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(minTriangle, minXOffset)); - double maxY = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxYOffset)); - double maxX = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset)); - - // check internal node against query - return point2D.relate(minX, maxX, minY, maxY); - } - - /** returns true if the query matches the encoded triangle */ - @Override - protected boolean queryMatches(byte[] t, ShapeField.DecodedTriangle scratchTriangle, ShapeField.QueryRelation queryRelation) { - ShapeField.decodeTriangle(t, scratchTriangle); - - double aY = decode(scratchTriangle.aY); - double aX = decode(scratchTriangle.aX); - double bY = decode(scratchTriangle.bY); - double bX = decode(scratchTriangle.bX); - double cY = decode(scratchTriangle.cY); - double cX = decode(scratchTriangle.cX); - - switch (queryRelation) { - case INTERSECTS: - return point2D.relateTriangle(aX, aY, bX, bY, cX, cY) != Relation.CELL_OUTSIDE_QUERY; - case WITHIN: - return point2D.relateTriangle(aX, aY, bX, bY, cX, cY) == Relation.CELL_INSIDE_QUERY; - case DISJOINT: - return point2D.relateTriangle(aX, aY, bX, bY, cX, cY) == Relation.CELL_OUTSIDE_QUERY; - default: - throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]"); - } - } - - @Override - protected Component2D.WithinRelation queryWithin(byte[] t, ShapeField.DecodedTriangle scratchTriangle) { - ShapeField.decodeTriangle(t, scratchTriangle); - - double aY = decode(scratchTriangle.aY); - double aX = decode(scratchTriangle.aX); - double bY = decode(scratchTriangle.bY); - double bX = decode(scratchTriangle.bX); - double cY = decode(scratchTriangle.cY); - double cX = decode(scratchTriangle.cX); - - return point2D.withinTriangle(aX, aY, scratchTriangle.ab, bX, bY, scratchTriangle.bc, cX, cY, scratchTriangle.ca); - } - - @Override - public boolean equals(Object o) { - return sameClassAs(o) && equalsTo(getClass().cast(o)); - } - - @Override - protected boolean equalsTo(Object o) { - return super.equalsTo(o) && Arrays.equals(points, ((XYShapePointQuery)o).points); - } - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 31 * hash + Arrays.hashCode(points); - return hash; - } - - @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("lat = " + points[0][0] + " , lon = " + points[0][1]); - return sb.toString(); - } -} diff --git a/lucene/core/src/java/org/apache/lucene/document/XYShapePolygonQuery.java b/lucene/core/src/java/org/apache/lucene/document/XYShapeQuery.java similarity index 62% rename from lucene/core/src/java/org/apache/lucene/document/XYShapePolygonQuery.java rename to lucene/core/src/java/org/apache/lucene/document/XYShapeQuery.java index 11639c04ac3..98be784c438 100644 --- a/lucene/core/src/java/org/apache/lucene/document/XYShapePolygonQuery.java +++ b/lucene/core/src/java/org/apache/lucene/document/XYShapeQuery.java @@ -21,45 +21,29 @@ import java.util.Arrays; import org.apache.lucene.document.ShapeField.QueryRelation; import org.apache.lucene.geo.Component2D; import org.apache.lucene.geo.XYEncodingUtils; -import org.apache.lucene.geo.XYPolygon; -import org.apache.lucene.geo.XYPolygon2D; +import org.apache.lucene.geo.XYGeometry; import org.apache.lucene.index.PointValues.Relation; import org.apache.lucene.util.NumericUtils; import static org.apache.lucene.geo.XYEncodingUtils.decode; /** - * Finds all previously indexed cartesian shapes that intersect the specified arbitrary cartesian {@link XYPolygon}. + * Finds all previously indexed cartesian shapes that comply the given {@link QueryRelation} with + * the specified array of {@link XYGeometry}. * - *
The field must be indexed using - * {@link org.apache.lucene.document.XYShape#createIndexableFields} added per document. + *
The field must be indexed using {@link XYShape#createIndexableFields} added per document. **/ -final class XYShapePolygonQuery extends ShapeQuery { - final XYPolygon[] polygons; - final private Component2D poly2D; +final class XYShapeQuery extends ShapeQuery { + final XYGeometry[] geometries; + final private Component2D component2D; /** * Creates a query that matches all indexed shapes to the provided polygons */ - public XYShapePolygonQuery(String field, QueryRelation queryRelation, XYPolygon... polygons) { + XYShapeQuery(String field, QueryRelation queryRelation, XYGeometry... geometries) { super(field, queryRelation); - 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].minX > polygons[i].maxX) { - throw new IllegalArgumentException("XYShapePolygonQuery: minX cannot be greater than maxX."); - } else if (polygons[i].minY > polygons[i].maxY) { - throw new IllegalArgumentException("XYShapePolygonQuery: minY cannot be greater than maxY."); - } - } - this.polygons = polygons.clone(); - this.poly2D = XYPolygon2D.create(polygons); + this.component2D = XYGeometry.create(geometries); + this.geometries = geometries.clone(); } @Override @@ -72,7 +56,7 @@ final class XYShapePolygonQuery extends ShapeQuery { double maxLon = XYEncodingUtils.decode(NumericUtils.sortableBytesToInt(maxTriangle, maxXOffset)); // check internal node against query - return poly2D.relate(minLon, maxLon, minLat, maxLat); + return component2D.relate(minLon, maxLon, minLat, maxLat); } @Override @@ -87,9 +71,9 @@ final class XYShapePolygonQuery extends ShapeQuery { double clon = decode(scratchTriangle.cX); switch (queryRelation) { - case INTERSECTS: return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY; - case WITHIN: return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; - case DISJOINT: return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_OUTSIDE_QUERY; + case INTERSECTS: return component2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY; + case WITHIN: return component2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; + case DISJOINT: return component2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_OUTSIDE_QUERY; default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]"); } } @@ -105,7 +89,7 @@ final class XYShapePolygonQuery extends ShapeQuery { double clat = decode(scratchTriangle.cY); double clon = decode(scratchTriangle.cX); - return poly2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca); + return component2D.withinTriangle(alon, alat, scratchTriangle.ab, blon, blat, scratchTriangle.bc, clon, clat, scratchTriangle.ca); } @Override @@ -118,19 +102,24 @@ final class XYShapePolygonQuery extends ShapeQuery { sb.append(this.field); sb.append(':'); } - sb.append("XYPolygon(").append(polygons[0].toGeoJSON()).append(")"); + sb.append("["); + for (int i = 0; i < geometries.length; i++) { + sb.append(geometries[i].toString()); + sb.append(','); + } + sb.append(']'); return sb.toString(); } @Override protected boolean equalsTo(Object o) { - return super.equalsTo(o) && Arrays.equals(polygons, ((XYShapePolygonQuery)o).polygons); + return super.equalsTo(o) && Arrays.equals(geometries, ((XYShapeQuery)o).geometries); } @Override public int hashCode() { int hash = super.hashCode(); - hash = 31 * hash + Arrays.hashCode(polygons); + hash = 31 * hash + Arrays.hashCode(geometries); return hash; } } \ No newline at end of file diff --git a/lucene/core/src/java/org/apache/lucene/geo/ComponentTree.java b/lucene/core/src/java/org/apache/lucene/geo/ComponentTree.java index 8a2b5f31473..c09bcdf2d80 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/ComponentTree.java +++ b/lucene/core/src/java/org/apache/lucene/geo/ComponentTree.java @@ -25,17 +25,15 @@ import org.apache.lucene.util.ArrayUtil; * 2D multi-component geometry implementation represented as an interval tree of components. *
* Construction takes {@code O(n log n)} time for sorting and tree construction. - * - * @lucene.internal */ final class ComponentTree implements Component2D { - /** minimum latitude of this geometry's bounding box area */ + /** minimum Y of this geometry's bounding box area */ private double minY; - /** maximum latitude of this geometry's bounding box area */ + /** maximum Y of this geometry's bounding box area */ private double maxY; - /** minimum longitude of this geometry's bounding box area */ + /** minimum X of this geometry's bounding box area */ private double minX; - /** maximum longitude of this geometry's bounding box area */ + /** maximum X of this geometry's bounding box area */ private double maxX; // child components, or null. Note internal nodes might mot have // a consistent bounding box. Internal nodes should not be accessed @@ -48,7 +46,7 @@ final class ComponentTree implements Component2D { /** root node of edge tree */ final private Component2D component; - protected ComponentTree(Component2D component, boolean splitX) { + private ComponentTree(Component2D component, boolean splitX) { this.minY = component.getMinY(); this.maxY = component.getMaxY(); this.minX = component.getMinX(); @@ -97,7 +95,6 @@ final class ComponentTree implements Component2D { return false; } - /** Returns relation to the provided triangle */ @Override public Relation relateTriangle(double minX, double maxX, double minY, double maxY, double ax, double ay, double bx, double by, double cx, double cy) { @@ -131,7 +128,6 @@ final class ComponentTree implements Component2D { return component.withinTriangle(minX, maxX, minY, maxY, aX, aY, ab, bX, bY, bc, cX, cY, ca); } - /** Returns relation to the provided rectangle */ @Override public Relation relate(double minX, double maxX, double minY, double maxY) { if (minY <= this.maxY && minX <= this.maxX) { @@ -156,7 +152,7 @@ final class ComponentTree implements Component2D { } /** Creates tree from provided components */ - public static Component2D create(Component2D[] components) { + static Component2D create(Component2D[] components) { if (components.length == 1) { return components[0]; } 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 f13e1a819d4..5e995063153 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/EdgeTree.java +++ b/lucene/core/src/java/org/apache/lucene/geo/EdgeTree.java @@ -23,45 +23,39 @@ import static org.apache.lucene.geo.GeoUtils.lineCrossesLineWithBoundary; import static org.apache.lucene.geo.GeoUtils.orient; /** - * 2D line/polygon geometry implementation represented as a balanced interval tree of edges. + * Internal tree node: represents geometry edge from [x1, y1] to [x2, y2]. + * The sort value is {@code low}, which is the minimum y of the edge. + * {@code max} stores the maximum y of this edge or any children. *
* Construction takes {@code O(n log n)} time for sorting and tree construction. - * {@link #relate relate()} are {@code O(n)}, but for most + * Methods are {@code O(n)}, but for most * practical lines and polygons are much faster than brute force. - * @lucene.internal */ -/** - * 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. - * - * @lucene.internal - */ -public class EdgeTree { - // lat-lon pair (in original order) of the two vertices - final double y1, y2; - final double x1, x2; - /** 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; - /** helper bytes to signal if a point is on an edge, it is within the edge tree or disjoint */ - final private static byte FALSE = 0x00; - final private static byte TRUE = 0x01; - final private static byte ON_EDGE = 0x02; +final class EdgeTree { + // X-Y pair (in original order) of the two vertices + final double y1, y2; + final double x1, x2; + /** min Y of this edge */ + final double low; + /** max Y of this edge or any children */ + double max; + /** left child edge, or null */ + EdgeTree left; + /** right child edge, or null */ + EdgeTree right; + /** helper bytes to signal if a point is on an edge, it is within the edge tree or disjoint */ + final private static byte FALSE = 0x00; + final private static byte TRUE = 0x01; + final private static byte ON_EDGE = 0x02; - EdgeTree(double x1, double y1, double x2, double y2, double low, double max) { - this.y1 = y1; - this.x1 = x1; - this.y2 = y2; - this.x2 = x2; - this.low = low; - this.max = max; - } + private EdgeTree(double x1, double y1, double x2, double y2, double low, double max) { + this.y1 = y1; + this.x1 = x1; + this.y2 = y2; + this.x2 = x2; + this.low = low; + this.max = max; + } /** * Returns true if the point is on an edge or crosses the edge subtree an odd number @@ -135,7 +129,7 @@ public class EdgeTree { } /** returns true if the provided x, y point lies on the line */ - protected boolean isPointOnLine(double x, double y) { + boolean isPointOnLine(double x, double y) { if (y <= max) { double a1x = x1; double a1y = y1; @@ -160,7 +154,7 @@ public class EdgeTree { /** Returns true if the triangle crosses any edge in this edge subtree */ - protected boolean crossesTriangle(double minX, double maxX, double minY, double maxY, + boolean crossesTriangle(double minX, double maxX, double minY, double maxY, double ax, double ay, double bx, double by, double cx, double cy, boolean includeBoundary) { if (minY <= max) { double dy = y1; @@ -204,7 +198,7 @@ public class EdgeTree { } /** Returns true if the box crosses any edge in this edge subtree */ - protected boolean crossesBox(double minX, double maxX, double minY, double maxY, boolean includeBoundary) { + boolean crossesBox(double minX, double maxX, double minY, double maxY, boolean includeBoundary) { // we just have to cross one edge to answer the question, so we descend the tree and return when we do. if (minY <= max) { // we compute line intersections of every polygon edge with every box line. @@ -261,7 +255,7 @@ public class EdgeTree { } /** Returns true if the line crosses any edge in this edge subtree */ - protected boolean crossesLine(double minX, double maxX, double minY, double maxY, double a2x, double a2y, double b2x, double b2y, boolean includeBoundary) { + boolean crossesLine(double minX, double maxX, double minY, double maxY, double a2x, double a2y, double b2x, double b2y, boolean includeBoundary) { if (minY <= max) { double a1x = x1; double a1y = y1; @@ -297,7 +291,7 @@ public class EdgeTree { * Creates an edge interval tree from a set of geometry vertices. * @return root node of the tree. */ - protected static EdgeTree createTree(double[] x, double[] y) { + static EdgeTree createTree(double[] x, double[] y) { EdgeTree edges[] = new EdgeTree[x.length - 1]; for (int i = 1; i < x.length; i++) { double x1 = x[i-1]; diff --git a/lucene/core/src/java/org/apache/lucene/geo/Line2D.java b/lucene/core/src/java/org/apache/lucene/geo/Line2D.java index ed247bf7b3b..5c6593ad76e 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/Line2D.java +++ b/lucene/core/src/java/org/apache/lucene/geo/Line2D.java @@ -23,17 +23,16 @@ import org.apache.lucene.index.PointValues.Relation; *
* Line {@code Line2D} Construction takes {@code O(n log n)} time for sorting and tree construction. * {@link #relate relate()} are {@code O(n)}, but for most practical lines are much faster than brute force. - * @lucene.internal */ -public final class Line2D implements Component2D { +final class Line2D implements Component2D { - /** minimum latitude of this geometry's bounding box area */ + /** minimum Y of this geometry's bounding box area */ final private double minY; - /** maximum latitude of this geometry's bounding box area */ + /** maximum Y of this geometry's bounding box area */ final private double maxY; - /** minimum longitude of this geometry's bounding box area */ + /** minimum X of this geometry's bounding box area */ final private double minX; - /** maximum longitude of this geometry's bounding box area */ + /** maximum X of this geometry's bounding box area */ final private double maxX; /** lines represented as a 2-d interval tree.*/ final private EdgeTree tree; @@ -184,21 +183,13 @@ public final class Line2D implements Component2D { return relation; } - /** create a Line2D edge tree from provided array of Linestrings */ - public static Component2D create(Line... lines) { - Component2D components[] = new Component2D[lines.length]; - for (int i = 0; i < components.length; ++i) { - components[i] = new Line2D(lines[i]); - } - return ComponentTree.create(components); + /** create a Line2D from the provided LatLon Linestring */ + static Component2D create(Line line) { + return new Line2D(line); } - /** create a Line2D edge tree from provided array of Linestrings */ - public static Component2D create(XYLine... lines) { - Line2D components[] = new Line2D[lines.length]; - for (int i = 0; i < components.length; ++i) { - components[i] = new Line2D(lines[i]); - } - return ComponentTree.create(components); + /** create a Line2D from the provided XY Linestring */ + static Component2D create(XYLine line) { + return new Line2D(line); } } \ No newline at end of file diff --git a/lucene/core/src/java/org/apache/lucene/geo/Point.java b/lucene/core/src/java/org/apache/lucene/geo/Point.java index 859bed73d9b..44845bd771c 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/Point.java +++ b/lucene/core/src/java/org/apache/lucene/geo/Point.java @@ -56,9 +56,7 @@ public final class Point extends LatLonGeometry { @Override protected Component2D toComponent2D() { - double qLat = GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(lat)); - double qLon = GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lon)); - return Point2D.create(new double[] {qLat, qLon}); + return Point2D.create(this); } @Override diff --git a/lucene/core/src/java/org/apache/lucene/geo/Point2D.java b/lucene/core/src/java/org/apache/lucene/geo/Point2D.java index d9865d4ce63..f3e79090c88 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/Point2D.java +++ b/lucene/core/src/java/org/apache/lucene/geo/Point2D.java @@ -21,15 +21,13 @@ import org.apache.lucene.index.PointValues; /** * 2D point implementation containing geo spatial logic. - * - * @lucene.internal */ -public class Point2D implements Component2D { +final class Point2D implements Component2D { final private double x; final private double y; - Point2D(double x, double y) { + private Point2D(double x, double y) { this.x = x; this.y = y; } @@ -88,22 +86,14 @@ public class Point2D implements Component2D { return WithinRelation.DISJOINT; } - /** create a Point2D component tree from provided array of LatLon points. */ - public static Component2D create(double[]... points) { - Point2D components[] = new Point2D[points.length]; - for (int i = 0; i < components.length; ++i) { - components[i] = new Point2D(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(points[i][1])) - , GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(points[i][0]))); - } - return ComponentTree.create(components); + /** create a Point2D component tree from a LatLon point */ + static Component2D create(Point point) { + return new Point2D(GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(point.getLon())), + GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(point.getLat()))); } - /** create a Point2D component tree from provided array of XY points. */ - public static Component2D create(float[]... xyPoints) { - Point2D components[] = new Point2D[xyPoints.length]; - for (int i = 0; i < components.length; ++i) { - components[i] = new Point2D(xyPoints[i][0], xyPoints[i][1]); - } - return ComponentTree.create(components); + /** create a Point2D component tree from a XY point */ + static Component2D create(XYPoint xyPoint) { + return new Point2D(xyPoint.getX(), xyPoint.getY()); } } 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 c36d6aad659..3e5fdb03a06 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/Polygon2D.java +++ b/lucene/core/src/java/org/apache/lucene/geo/Polygon2D.java @@ -23,24 +23,23 @@ import org.apache.lucene.index.PointValues.Relation; *
* Loosely based on the algorithm described in * http://www-ma2.upc.es/geoc/Schirra-pointPolygon.pdf. - * @lucene.internal */ -public class Polygon2D implements Component2D { - /** minimum latitude of this geometry's bounding box area */ +final class Polygon2D implements Component2D { + /** minimum Y of this geometry's bounding box area */ final private double minY; - /** maximum latitude of this geometry's bounding box area */ + /** maximum Y of this geometry's bounding box area */ final private double maxY; - /** minimum longitude of this geometry's bounding box area */ + /** minimum X of this geometry's bounding box area */ final private double minX; - /** maximum longitude of this geometry's bounding box area */ + /** maximum X of this geometry's bounding box area */ final private double maxX; /** tree of holes, or null */ final protected Component2D holes; /** Edges of the polygon represented as a 2-d interval tree.*/ final EdgeTree tree; - protected Polygon2D(final double minX, final double maxX, final double minY, final double maxY, double[] x, double[] y, Component2D holes) { + private Polygon2D(final double minX, final double maxX, final double minY, final double maxY, double[] x, double[] y, Component2D holes) { this.minY = minY; this.maxY = maxY; this.minX = minX; @@ -49,6 +48,10 @@ public class Polygon2D implements Component2D { this.tree = EdgeTree.createTree(x, y); } + private Polygon2D(XYPolygon polygon, Component2D holes) { + this(polygon.minX, polygon.maxX, polygon.minY, polygon.maxY, polygon.getPolyX(), polygon.getPolyY(), holes); + } + protected Polygon2D(Polygon polygon, Component2D holes) { this(polygon.minLon, polygon.maxLon, polygon.minLat, polygon.maxLat, polygon.getPolyLons(), polygon.getPolyLats(), holes); } @@ -312,18 +315,24 @@ public class Polygon2D implements Component2D { return containsCount; } - /** Builds a Polygon2D from multipolygon */ - public static Component2D create(Polygon... polygons) { - Component2D components[] = new Component2D[polygons.length]; - for (int i = 0; i < components.length; i++) { - Polygon gon = polygons[i]; - Polygon gonHoles[] = gon.getHoles(); - Component2D holes = null; - if (gonHoles.length > 0) { - holes = create(gonHoles); - } - components[i] = new Polygon2D(gon, holes); + /** Builds a Polygon2D from LatLon polygon */ + static Component2D create(Polygon polygon) { + Polygon gonHoles[] = polygon.getHoles(); + Component2D holes = null; + if (gonHoles.length > 0) { + holes = LatLonGeometry.create(gonHoles); } - return ComponentTree.create(components); + return new Polygon2D(polygon, holes); } + + /** Builds a Polygon2D from XY polygon */ + static Component2D create(XYPolygon polygon) { + XYPolygon gonHoles[] = polygon.getHoles(); + Component2D holes = null; + if (gonHoles.length > 0) { + holes = XYGeometry.create(gonHoles); + } + return new Polygon2D(polygon, holes); + } + } diff --git a/lucene/core/src/java/org/apache/lucene/geo/XYGeometry.java b/lucene/core/src/java/org/apache/lucene/geo/XYGeometry.java new file mode 100644 index 00000000000..a6bfca2dd25 --- /dev/null +++ b/lucene/core/src/java/org/apache/lucene/geo/XYGeometry.java @@ -0,0 +1,51 @@ +/* + * 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; + +/** + * Cartesian Geometry object. + **/ +public abstract class XYGeometry { + + /** get a Component2D from this object */ + protected abstract Component2D toComponent2D(); + + /** Creates a Component2D from the provided XYGeometries array */ + public static Component2D create(XYGeometry... xyGeometries) { + if (xyGeometries == null) { + throw new IllegalArgumentException("geometries must not be null"); + } + if (xyGeometries.length == 0) { + throw new IllegalArgumentException("geometries must not be empty"); + } + if (xyGeometries.length == 1) { + if (xyGeometries[0] == null) { + throw new IllegalArgumentException("geometries[0] must not be null"); + } + return xyGeometries[0].toComponent2D(); + } + Component2D[] components = new Component2D[xyGeometries.length]; + for (int i = 0; i < xyGeometries.length; i++) { + if (xyGeometries[i] == null) { + throw new IllegalArgumentException("geometries[" + i + "] must not be null"); + } + components[i] = xyGeometries[i].toComponent2D(); + } + return ComponentTree.create(components); + } +} diff --git a/lucene/core/src/java/org/apache/lucene/geo/XYLine.java b/lucene/core/src/java/org/apache/lucene/geo/XYLine.java index 02b957ad025..c3f59ac32aa 100644 --- a/lucene/core/src/java/org/apache/lucene/geo/XYLine.java +++ b/lucene/core/src/java/org/apache/lucene/geo/XYLine.java @@ -19,10 +19,10 @@ package org.apache.lucene.geo; import java.util.Arrays; /** - * Represents a line in cartesian space. You can construct the Line directly with {@code double[]}, {@code double[]} x, y arrays + * Represents a line in cartesian space. You can construct the Line directly with {@code float[]}, {@code float[]} x, y arrays * coordinates. */ -public class XYLine { +public class XYLine extends XYGeometry { /** array of x coordinates */ private final double[] x; /** array of y coordinates */ @@ -30,7 +30,7 @@ public class XYLine { /** minimum x of this line's bounding box */ public final double minX; - /** maximum x of this line's bounding box */ + /** maximum y of this line's bounding box */ public final double maxX; /** minimum y of this line's bounding box */ public final double minY; @@ -38,7 +38,7 @@ public class XYLine { public final double maxY; /** - * Creates a new Line from the supplied x/y array. + * Creates a new Line from the supplied X/Y array. */ public XYLine(float[] x, float[] y) { if (x == null) { @@ -103,6 +103,19 @@ public class XYLine { return y.clone(); } + @Override + protected Component2D toComponent2D() { + return Line2D.create(this); + } + + public String toGeoJSON() { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(Polygon.verticesToGeoJSON(x, y)); + sb.append("]"); + return sb.toString(); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -132,13 +145,4 @@ public class XYLine { sb.append(')'); return sb.toString(); } - - /** prints polygons as geojson */ - public String toGeoJSON() { - StringBuilder sb = new StringBuilder(); - sb.append("["); - sb.append(Polygon.verticesToGeoJSON(x, y)); - sb.append("]"); - return sb.toString(); - } } diff --git a/lucene/core/src/java/org/apache/lucene/geo/XYPoint.java b/lucene/core/src/java/org/apache/lucene/geo/XYPoint.java new file mode 100644 index 00000000000..9b6d0eeaa11 --- /dev/null +++ b/lucene/core/src/java/org/apache/lucene/geo/XYPoint.java @@ -0,0 +1,85 @@ +/* + * 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; + +/** + * Represents a point on the earth's surface. You can construct the point directly with {@code double} + * coordinates. + *
+ * NOTES: + *