diff --git a/buildSrc/src/main/resources/forbidden/es-server-signatures.txt b/buildSrc/src/main/resources/forbidden/es-server-signatures.txt index d0757038c59..01c7d189073 100644 --- a/buildSrc/src/main/resources/forbidden/es-server-signatures.txt +++ b/buildSrc/src/main/resources/forbidden/es-server-signatures.txt @@ -147,3 +147,15 @@ org.apache.logging.log4j.Logger#error(java.lang.Object) org.apache.logging.log4j.Logger#error(java.lang.Object, java.lang.Throwable) org.apache.logging.log4j.Logger#fatal(java.lang.Object) org.apache.logging.log4j.Logger#fatal(java.lang.Object, java.lang.Throwable) + +# Remove once Lucene 7.7 is integrated +@defaultMessage Use org.apache.lucene.document.XLatLonShape classes instead +org.apache.lucene.document.LatLonShape +org.apache.lucene.document.LatLonShapeBoundingBoxQuery +org.apache.lucene.document.LatLonShapeLineQuery +org.apache.lucene.document.LatLonShapePolygonQuery +org.apache.lucene.document.LatLonShapeQuery + +org.apache.lucene.geo.Rectangle2D @ use @org.apache.lucene.geo.XRectangle2D instead + +org.apache.lucene.geo.Tessellator @ use @org.apache.lucene.geo.XTessellator instead \ No newline at end of file diff --git a/server/src/main/java/org/apache/lucene/document/XLatLonShape.java b/server/src/main/java/org/apache/lucene/document/XLatLonShape.java new file mode 100644 index 00000000000..4fd92f4508c --- /dev/null +++ b/server/src/main/java/org/apache/lucene/document/XLatLonShape.java @@ -0,0 +1,373 @@ +/* + * 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 org.apache.lucene.geo.GeoUtils; +import org.apache.lucene.geo.Line; +import org.apache.lucene.geo.Polygon; +import org.apache.lucene.geo.XTessellator; +import org.apache.lucene.geo.XTessellator.Triangle; +import org.apache.lucene.index.PointValues; +import org.apache.lucene.search.Query; +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.NumericUtils; + +import java.util.ArrayList; +import java.util.List; + +import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude; +import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude; + +/** + * An indexed shape utility class. + *
+ * {@link Polygon}'s are decomposed into a triangular mesh using the {@link XTessellator} utility class + * Each {@link Triangle} is encoded and indexed as a multi-value field. + *
+ * Finding all shapes that intersect a range (e.g., bounding box) at search time is efficient. + *
+ * This class defines static factory methods for common operations: + *
The field must be indexed using + * {@link XLatLonShape#createIndexableFields} added per document. + * + * @lucene.experimental + **/ +final class XLatLonShapeBoundingBoxQuery extends XLatLonShapeQuery { + final XRectangle2D rectangle2D; + + XLatLonShapeBoundingBoxQuery(String field, XLatLonShape.QueryRelation queryRelation, + double minLat, double maxLat, double minLon, double maxLon) { + super(field, queryRelation); + Rectangle rectangle = new Rectangle(minLat, maxLat, minLon, maxLon); + this.rectangle2D = XRectangle2D.create(rectangle); + } + + @Override + protected Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle, + int maxXOffset, int maxYOffset, byte[] maxTriangle) { + return rectangle2D.relateRangeBBox(minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle); + } + + /** returns true if the query matches the encoded triangle */ + @Override + protected boolean queryMatches(byte[] t, int[] scratchTriangle) { + // decode indexed triangle + XLatLonShape.decodeTriangle(t, scratchTriangle); + + int aY = scratchTriangle[0]; + int aX = scratchTriangle[1]; + int bY = scratchTriangle[2]; + int bX = scratchTriangle[3]; + int cY = scratchTriangle[4]; + int cX = scratchTriangle[5]; + + if (queryRelation == XLatLonShape.QueryRelation.WITHIN) { + return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY); + } + return rectangle2D.intersectsTriangle(aX, aY, bX, bY, cX, cY); + } + + @Override + public boolean equals(Object o) { + return sameClassAs(o) && equalsTo(getClass().cast(o)); + } + + @Override + protected boolean equalsTo(Object o) { + return super.equalsTo(o) && rectangle2D.equals(((XLatLonShapeBoundingBoxQuery)o).rectangle2D); + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 31 * hash + rectangle2D.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(rectangle2D.toString()); + return sb.toString(); + } +} diff --git a/server/src/main/java/org/apache/lucene/document/XLatLonShapeLineQuery.java b/server/src/main/java/org/apache/lucene/document/XLatLonShapeLineQuery.java new file mode 100644 index 00000000000..90905e0d83f --- /dev/null +++ b/server/src/main/java/org/apache/lucene/document/XLatLonShapeLineQuery.java @@ -0,0 +1,134 @@ +/* + * 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 org.apache.lucene.document.XLatLonShape.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; + +import java.util.Arrays; + +/** + * Finds all previously indexed shapes that intersect the specified arbitrary {@code Line}. + *
+ * Note: + *
+ * todo: + *
The field must be indexed using + * {@link XLatLonShape#createIndexableFields} added per document. + * + * @lucene.experimental + **/ +final class XLatLonShapeLineQuery extends XLatLonShapeQuery { + final Line[] lines; + private final Line2D line2D; + + XLatLonShapeLineQuery(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) { + XLatLonShape.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 == XLatLonShape.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 + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + protected boolean equalsTo(Object o) { + return super.equalsTo(o) && Arrays.equals(lines, ((XLatLonShapeLineQuery)o).lines); + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 31 * hash + Arrays.hashCode(lines); + return hash; + } +} diff --git a/server/src/main/java/org/apache/lucene/document/XLatLonShapePolygonQuery.java b/server/src/main/java/org/apache/lucene/document/XLatLonShapePolygonQuery.java new file mode 100644 index 00000000000..5e97828aae2 --- /dev/null +++ b/server/src/main/java/org/apache/lucene/document/XLatLonShapePolygonQuery.java @@ -0,0 +1,123 @@ +/* + * 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 org.apache.lucene.document.XLatLonShape.QueryRelation; +import org.apache.lucene.geo.GeoEncodingUtils; +import org.apache.lucene.geo.Polygon; +import org.apache.lucene.geo.Polygon2D; +import org.apache.lucene.index.PointValues.Relation; +import org.apache.lucene.util.NumericUtils; + +import java.util.Arrays; + +/** + * Finds all previously indexed shapes that intersect the specified arbitrary. + * + *
The field must be indexed using + * {@link XLatLonShape#createIndexableFields} added per document. + * + * @lucene.experimental + **/ +final class XLatLonShapePolygonQuery extends XLatLonShapeQuery { + final Polygon[] polygons; + private final Polygon2D poly2D; + + /** + * Creates a query that matches all indexed shapes to the provided polygons + */ + XLatLonShapePolygonQuery(String field, QueryRelation queryRelation, Polygon... polygons) { + 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].minLon > polygons[i].maxLon) { + throw new IllegalArgumentException("LatLonShapePolygonQuery does not currently support querying across dateline."); + } + } + this.polygons = polygons.clone(); + this.poly2D = Polygon2D.create(polygons); + } + + @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 poly2D.relate(minLat, maxLat, minLon, maxLon); + } + + @Override + protected boolean queryMatches(byte[] t, int[] scratchTriangle) { + XLatLonShape.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 == QueryRelation.WITHIN) { + return poly2D.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; + } + + @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("Polygon(" + polygons[0].toGeoJSON() + ")"); + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + protected boolean equalsTo(Object o) { + return super.equalsTo(o) && Arrays.equals(polygons, ((XLatLonShapePolygonQuery)o).polygons); + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 31 * hash + Arrays.hashCode(polygons); + return hash; + } +} diff --git a/server/src/main/java/org/apache/lucene/document/XLatLonShapeQuery.java b/server/src/main/java/org/apache/lucene/document/XLatLonShapeQuery.java new file mode 100644 index 00000000000..7aded1337e4 --- /dev/null +++ b/server/src/main/java/org/apache/lucene/document/XLatLonShapeQuery.java @@ -0,0 +1,364 @@ +/* + * 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 org.apache.lucene.document.XLatLonShape.QueryRelation; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.LeafReader; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.PointValues; +import org.apache.lucene.index.PointValues.IntersectVisitor; +import org.apache.lucene.index.PointValues.Relation; +import org.apache.lucene.search.ConstantScoreScorer; +import org.apache.lucene.search.ConstantScoreWeight; +import org.apache.lucene.search.DocIdSetIterator; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreMode; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.search.ScorerSupplier; +import org.apache.lucene.search.Weight; +import org.apache.lucene.util.BitSetIterator; +import org.apache.lucene.util.DocIdSetBuilder; +import org.apache.lucene.util.FixedBitSet; + +import java.io.IOException; +import java.util.Objects; + +/** + * Base LatLonShape Query class providing common query logic for + * {@link XLatLonShapeBoundingBoxQuery} and {@link XLatLonShapePolygonQuery} + * + * Note: this class implements the majority of the INTERSECTS, WITHIN, DISJOINT relation logic + * + * @lucene.experimental + **/ +abstract class XLatLonShapeQuery extends Query { + /** field name */ + final String field; + /** query relation + * disjoint: {@code CELL_OUTSIDE_QUERY} + * intersects: {@code CELL_CROSSES_QUERY}, + * within: {@code CELL_WITHIN_QUERY} */ + final XLatLonShape.QueryRelation queryRelation; + + protected XLatLonShapeQuery(String field, final QueryRelation queryType) { + if (field == null) { + throw new IllegalArgumentException("field must not be null"); + } + this.field = field; + this.queryRelation = queryType; + } + + /** + * relates an internal node (bounding box of a range of triangles) to the target query + * Note: logic is specific to query type + * see {@link XLatLonShapeBoundingBoxQuery#relateRangeToQuery} and {@link XLatLonShapePolygonQuery#relateRangeToQuery} + */ + protected abstract Relation relateRangeBBoxToQuery(int minXOffset, int minYOffset, byte[] minTriangle, + int maxXOffset, int maxYOffset, byte[] maxTriangle); + + /** returns true if the provided triangle matches the query */ + protected abstract boolean queryMatches(byte[] triangle, int[] scratchTriangle); + + /** relates a range of triangles (internal node) to the query */ + protected Relation relateRangeToQuery(byte[] minTriangle, byte[] maxTriangle) { + // compute bounding box of internal node + Relation r = relateRangeBBoxToQuery(XLatLonShape.BYTES, 0, minTriangle, 3 * XLatLonShape.BYTES, + 2 * XLatLonShape.BYTES, maxTriangle); + if (queryRelation == QueryRelation.DISJOINT) { + return transposeRelation(r); + } + return r; + } + + @Override + public final Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException { + + return new ConstantScoreWeight(this, boost) { + + /** create a visitor that adds documents that match the query using a sparse bitset. (Used by INTERSECT) */ + protected IntersectVisitor getSparseIntersectVisitor(DocIdSetBuilder result) { + return new IntersectVisitor() { + final int[] scratchTriangle = new int[6]; + DocIdSetBuilder.BulkAdder adder; + + @Override + public void grow(int count) { + adder = result.grow(count); + } + + @Override + public void visit(int docID) throws IOException { + adder.add(docID); + } + + @Override + public void visit(int docID, byte[] t) throws IOException { + if (queryMatches(t, scratchTriangle)) { + adder.add(docID); + } + } + + @Override + public Relation compare(byte[] minTriangle, byte[] maxTriangle) { + return relateRangeToQuery(minTriangle, maxTriangle); + } + }; + } + + /** create a visitor that adds documents that match the query using a dense bitset. (Used by WITHIN, DISJOINT) */ + protected IntersectVisitor getDenseIntersectVisitor(FixedBitSet intersect, FixedBitSet disjoint) { + return new IntersectVisitor() { + final int[] scratchTriangle = new int[6]; + @Override + public void visit(int docID) throws IOException { + if (queryRelation == QueryRelation.DISJOINT) { + // if DISJOINT query set the doc in the disjoint bitset + disjoint.set(docID); + } else { + // for INTERSECT, and WITHIN queries we set the intersect bitset + intersect.set(docID); + } + } + + @Override + public void visit(int docID, byte[] t) throws IOException { + if (queryMatches(t, scratchTriangle)) { + intersect.set(docID); + } else { + disjoint.set(docID); + } + } + + @Override + public Relation compare(byte[] minTriangle, byte[] maxTriangle) { + return relateRangeToQuery(minTriangle, maxTriangle); + } + }; + } + + /** get a scorer supplier for INTERSECT queries */ + protected ScorerSupplier getIntersectScorerSupplier(LeafReader reader, PointValues values, Weight weight, + ScoreMode scoreMode) throws IOException { + DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field); + IntersectVisitor visitor = getSparseIntersectVisitor(result); + return new RelationScorerSupplier(values, visitor) { + @Override + public Scorer get(long leadCost) throws IOException { + return getIntersectsScorer(XLatLonShapeQuery.this, reader, weight, result, score(), scoreMode); + } + }; + } + + /** get a scorer supplier for all other queries (DISJOINT, WITHIN) */ + protected ScorerSupplier getScorerSupplier(LeafReader reader, PointValues values, Weight weight, + ScoreMode scoreMode) throws IOException { + if (queryRelation == QueryRelation.INTERSECTS) { + return getIntersectScorerSupplier(reader, values, weight, scoreMode); + } + + FixedBitSet intersect = new FixedBitSet(reader.maxDoc()); + FixedBitSet disjoint = new FixedBitSet(reader.maxDoc()); + IntersectVisitor visitor = getDenseIntersectVisitor(intersect, disjoint); + return new RelationScorerSupplier(values, visitor) { + @Override + public Scorer get(long leadCost) throws IOException { + return getScorer(XLatLonShapeQuery.this, weight, intersect, disjoint, score(), scoreMode); + } + }; + } + + @Override + public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException { + LeafReader reader = context.reader(); + PointValues values = reader.getPointValues(field); + if (values == null) { + // No docs in this segment had any points fields + return null; + } + FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(field); + if (fieldInfo == null) { + // No docs in this segment indexed this field at all + return null; + } + + boolean allDocsMatch = true; + if (values.getDocCount() != reader.maxDoc() || + relateRangeToQuery(values.getMinPackedValue(), values.getMaxPackedValue()) != Relation.CELL_INSIDE_QUERY) { + allDocsMatch = false; + } + + final Weight weight = this; + if (allDocsMatch) { + return new ScorerSupplier() { + @Override + public Scorer get(long leadCost) throws IOException { + return new ConstantScoreScorer(weight, score(), scoreMode, DocIdSetIterator.all(reader.maxDoc())); + } + + @Override + public long cost() { + return reader.maxDoc(); + } + }; + } else { + return getScorerSupplier(reader, values, weight, scoreMode); + } + } + + @Override + public Scorer scorer(LeafReaderContext context) throws IOException { + ScorerSupplier scorerSupplier = scorerSupplier(context); + if (scorerSupplier == null) { + return null; + } + return scorerSupplier.get(Long.MAX_VALUE); + } + + @Override + public boolean isCacheable(LeafReaderContext ctx) { + return true; + } + }; + } + + /** returns the field name */ + public String getField() { + return field; + } + + /** returns the query relation */ + public QueryRelation getQueryRelation() { + return queryRelation; + } + + @Override + public int hashCode() { + int hash = classHash(); + hash = 31 * hash + field.hashCode(); + hash = 31 * hash + queryRelation.hashCode(); + return hash; + } + + @Override + public boolean equals(Object o) { + return sameClassAs(o) && equalsTo(o); + } + + protected boolean equalsTo(Object o) { + return Objects.equals(field, ((XLatLonShapeQuery)o).field) && this.queryRelation == ((XLatLonShapeQuery)o).queryRelation; + } + + /** transpose the relation; INSIDE becomes OUTSIDE, OUTSIDE becomes INSIDE, CROSSES remains unchanged */ + private static Relation transposeRelation(Relation r) { + if (r == Relation.CELL_INSIDE_QUERY) { + return Relation.CELL_OUTSIDE_QUERY; + } else if (r == Relation.CELL_OUTSIDE_QUERY) { + return Relation.CELL_INSIDE_QUERY; + } + return Relation.CELL_CROSSES_QUERY; + } + + /** utility class for implementing constant score logic specific to INTERSECT, WITHIN, and DISJOINT */ + private abstract static class RelationScorerSupplier extends ScorerSupplier { + PointValues values; + IntersectVisitor visitor; + long cost = -1; + + RelationScorerSupplier(PointValues values, IntersectVisitor visitor) { + this.values = values; + this.visitor = visitor; + } + + /** create a visitor that clears documents that do NOT match the polygon query; used with INTERSECTS */ + private IntersectVisitor getInverseIntersectVisitor(XLatLonShapeQuery query, FixedBitSet result, int[] cost) { + return new IntersectVisitor() { + int[] scratchTriangle = new int[6]; + @Override + public void visit(int docID) { + result.clear(docID); + cost[0]--; + } + + @Override + public void visit(int docID, byte[] packedTriangle) { + if (query.queryMatches(packedTriangle, scratchTriangle) == false) { + result.clear(docID); + cost[0]--; + } + } + + @Override + public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) { + return transposeRelation(query.relateRangeToQuery(minPackedValue, maxPackedValue)); + } + }; + } + + /** returns a Scorer for INTERSECT queries that uses a sparse bitset */ + protected Scorer getIntersectsScorer(XLatLonShapeQuery query, LeafReader reader, Weight weight, + DocIdSetBuilder docIdSetBuilder, final float boost, + ScoreMode scoreMode) throws IOException { + if (values.getDocCount() == reader.maxDoc() + && values.getDocCount() == values.size() + && cost() > reader.maxDoc() / 2) { + // If all docs have exactly one value and the cost is greater + // than half the leaf size then maybe we can make things faster + // by computing the set of documents that do NOT match the query + final FixedBitSet result = new FixedBitSet(reader.maxDoc()); + result.set(0, reader.maxDoc()); + int[] cost = new int[]{reader.maxDoc()}; + values.intersect(getInverseIntersectVisitor(query, result, cost)); + final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]); + return new ConstantScoreScorer(weight, boost, scoreMode, iterator); + } + + values.intersect(visitor); + DocIdSetIterator iterator = docIdSetBuilder.build().iterator(); + return new ConstantScoreScorer(weight, boost, scoreMode, iterator); + } + + /** returns a Scorer for all other (non INTERSECT) queries */ + protected Scorer getScorer(XLatLonShapeQuery query, Weight weight, + FixedBitSet intersect, FixedBitSet disjoint, final float boost, + ScoreMode scoreMode) throws IOException { + values.intersect(visitor); + DocIdSetIterator iterator; + if (query.queryRelation == QueryRelation.DISJOINT) { + disjoint.andNot(intersect); + iterator = new BitSetIterator(disjoint, cost()); + } else if (query.queryRelation == QueryRelation.WITHIN) { + intersect.andNot(disjoint); + iterator = new BitSetIterator(intersect, cost()); + } else { + iterator = new BitSetIterator(intersect, cost()); + } + return new ConstantScoreScorer(weight, boost, scoreMode, iterator); + } + + @Override + public long cost() { + if (cost == -1) { + // Computing the cost may be expensive, so only do it if necessary + cost = values.estimatePointCount(visitor); + assert cost >= 0; + } + return cost; + } + } +} diff --git a/server/src/main/java/org/apache/lucene/geo/XRectangle2D.java b/server/src/main/java/org/apache/lucene/geo/XRectangle2D.java new file mode 100644 index 00000000000..af06d0a0e39 --- /dev/null +++ b/server/src/main/java/org/apache/lucene/geo/XRectangle2D.java @@ -0,0 +1,317 @@ +/* + * 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.document.XLatLonShape; +import org.apache.lucene.index.PointValues; +import org.apache.lucene.util.FutureArrays; +import org.apache.lucene.util.NumericUtils; + +import java.util.Arrays; + +import static org.apache.lucene.document.XLatLonShape.BYTES; +import static org.apache.lucene.geo.GeoEncodingUtils.MAX_LON_ENCODED; +import static org.apache.lucene.geo.GeoEncodingUtils.MIN_LON_ENCODED; +import static org.apache.lucene.geo.GeoEncodingUtils.decodeLatitude; +import static org.apache.lucene.geo.GeoEncodingUtils.decodeLongitude; +import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude; +import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitudeCeil; +import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude; +import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitudeCeil; +import static org.apache.lucene.geo.GeoUtils.orient; + +/** + * 2D rectangle implementation containing spatial logic. + * + * @lucene.internal + */ +public class XRectangle2D { + final byte[] bbox; + final byte[] west; + final int minX; + final int maxX; + final int minY; + final int maxY; + + private XRectangle2D(double minLat, double maxLat, double minLon, double maxLon) { + this.bbox = new byte[4 * BYTES]; + int minXenc = encodeLongitudeCeil(minLon); + int maxXenc = encodeLongitude(maxLon); + int minYenc = encodeLatitudeCeil(minLat); + int maxYenc = encodeLatitude(maxLat); + if (minYenc > maxYenc) { + minYenc = maxYenc; + } + this.minY = minYenc; + this.maxY = maxYenc; + + if (minLon > maxLon == true) { + // crossing dateline is split into east/west boxes + this.west = new byte[4 * BYTES]; + this.minX = minXenc; + this.maxX = maxXenc; + encode(MIN_LON_ENCODED, this.maxX, this.minY, this.maxY, this.west); + encode(this.minX, MAX_LON_ENCODED, this.minY, this.maxY, this.bbox); + } else { + // encodeLongitudeCeil may cause minX to be > maxX iff + // the delta between the longitude < the encoding resolution + if (minXenc > maxXenc) { + minXenc = maxXenc; + } + this.west = null; + this.minX = minXenc; + this.maxX = maxXenc; + encode(this.minX, this.maxX, this.minY, this.maxY, bbox); + } + } + + /** Builds a XRectangle2D from rectangle */ + public static XRectangle2D create(Rectangle rectangle) { + return new XRectangle2D(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon); + } + + public boolean crossesDateline() { + return minX > maxX; + } + + /** Checks if the rectangle contains the provided point **/ + public boolean queryContainsPoint(int x, int y) { + if (this.crossesDateline() == true) { + return bboxContainsPoint(x, y, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY) + || bboxContainsPoint(x, y, this.minX, MAX_LON_ENCODED, this.minY, this.maxY); + } + return bboxContainsPoint(x, y, this.minX, this.maxX, this.minY, this.maxY); + } + + /** compare this to a provided rangle bounding box **/ + public PointValues.Relation relateRangeBBox(int minXOffset, int minYOffset, byte[] minTriangle, + int maxXOffset, int maxYOffset, byte[] maxTriangle) { + PointValues.Relation eastRelation = compareBBoxToRangeBBox(this.bbox, minXOffset, minYOffset, minTriangle, + maxXOffset, maxYOffset, maxTriangle); + if (this.crossesDateline() && eastRelation == PointValues.Relation.CELL_OUTSIDE_QUERY) { + return compareBBoxToRangeBBox(this.west, minXOffset, minYOffset, minTriangle, maxXOffset, maxYOffset, maxTriangle); + } + return eastRelation; + } + + /** Checks if the rectangle intersects the provided triangle **/ + public boolean intersectsTriangle(int aX, int aY, int bX, int bY, int cX, int cY) { + // 1. query contains any triangle points + if (queryContainsPoint(aX, aY) || queryContainsPoint(bX, bY) || queryContainsPoint(cX, cY)) { + return true; + } + + // compute bounding box of triangle + int tMinX = StrictMath.min(StrictMath.min(aX, bX), cX); + int tMaxX = StrictMath.max(StrictMath.max(aX, bX), cX); + int tMinY = StrictMath.min(StrictMath.min(aY, bY), cY); + int tMaxY = StrictMath.max(StrictMath.max(aY, bY), cY); + + // 2. check bounding boxes are disjoint + if (this.crossesDateline() == true) { + if (boxesAreDisjoint(tMinX, tMaxX, tMinY, tMaxY, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY) + && boxesAreDisjoint(tMinX, tMaxX, tMinY, tMaxY, this.minX, MAX_LON_ENCODED, this.minY, this.maxY)) { + return false; + } + } else if (tMaxX < minX || tMinX > maxX || tMinY > maxY || tMaxY < minY) { + return false; + } + + // 3. check triangle contains any query points + if (XTessellator.pointInTriangle(minX, minY, aX, aY, bX, bY, cX, cY)) { + return true; + } else if (XTessellator.pointInTriangle(maxX, minY, aX, aY, bX, bY, cX, cY)) { + return true; + } else if (XTessellator.pointInTriangle(maxX, maxY, aX, aY, bX, bY, cX, cY)) { + return true; + } else if (XTessellator.pointInTriangle(minX, maxY, aX, aY, bX, bY, cX, cY)) { + return true; + } + + // 4. last ditch effort: check crossings + if (queryIntersects(aX, aY, bX, bY, cX, cY)) { + return true; + } + return false; + } + + /** Checks if the rectangle contains the provided triangle **/ + public boolean containsTriangle(int ax, int ay, int bx, int by, int cx, int cy) { + if (this.crossesDateline() == true) { + return bboxContainsTriangle(ax, ay, bx, by, cx, cy, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY) + || bboxContainsTriangle(ax, ay, bx, by, cx, cy, this.minX, MAX_LON_ENCODED, this.minY, this.maxY); + } + return bboxContainsTriangle(ax, ay, bx, by, cx, cy, minX, maxX, minY, maxY); + } + + /** static utility method to compare a bbox with a range of triangles (just the bbox of the triangle collection) */ + private static PointValues.Relation compareBBoxToRangeBBox(final byte[] bbox, + int minXOffset, int minYOffset, byte[] minTriangle, + int maxXOffset, int maxYOffset, byte[] maxTriangle) { + // check bounding box (DISJOINT) + if (FutureArrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) > 0 || + FutureArrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, BYTES, 2 * BYTES) < 0 || + FutureArrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) > 0 || + FutureArrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 0, BYTES) < 0) { + return PointValues.Relation.CELL_OUTSIDE_QUERY; + } + + if (FutureArrays.compareUnsigned(minTriangle, minXOffset, minXOffset + BYTES, bbox, BYTES, 2 * BYTES) >= 0 && + FutureArrays.compareUnsigned(maxTriangle, maxXOffset, maxXOffset + BYTES, bbox, 3 * BYTES, 4 * BYTES) <= 0 && + FutureArrays.compareUnsigned(minTriangle, minYOffset, minYOffset + BYTES, bbox, 0, BYTES) >= 0 && + FutureArrays.compareUnsigned(maxTriangle, maxYOffset, maxYOffset + BYTES, bbox, 2 * BYTES, 3 * BYTES) <= 0) { + return PointValues.Relation.CELL_INSIDE_QUERY; + } + return PointValues.Relation.CELL_CROSSES_QUERY; + } + + /** + * encodes a bounding box into the provided byte array + */ + private static void encode(final int minX, final int maxX, final int minY, final int maxY, byte[] b) { + if (b == null) { + b = new byte[4 * XLatLonShape.BYTES]; + } + NumericUtils.intToSortableBytes(minY, b, 0); + NumericUtils.intToSortableBytes(minX, b, BYTES); + NumericUtils.intToSortableBytes(maxY, b, 2 * BYTES); + NumericUtils.intToSortableBytes(maxX, b, 3 * BYTES); + } + + /** returns true if the query intersects the provided triangle (in encoded space) */ + private boolean queryIntersects(int ax, int ay, int bx, int by, int cx, int cy) { + // check each edge of the triangle against the query + if (edgeIntersectsQuery(ax, ay, bx, by) || + edgeIntersectsQuery(bx, by, cx, cy) || + edgeIntersectsQuery(cx, cy, ax, ay)) { + return true; + } + return false; + } + + /** returns true if the edge (defined by (ax, ay) (bx, by)) intersects the query */ + private boolean edgeIntersectsQuery(int ax, int ay, int bx, int by) { + if (this.crossesDateline() == true) { + return edgeIntersectsBox(ax, ay, bx, by, MIN_LON_ENCODED, this.maxX, this.minY, this.maxY) + || edgeIntersectsBox(ax, ay, bx, by, this.minX, MAX_LON_ENCODED, this.minY, this.maxY); + } + return edgeIntersectsBox(ax, ay, bx, by, this.minX, this.maxX, this.minY, this.maxY); + } + + /** static utility method to check if a bounding box contains a point */ + private static boolean bboxContainsPoint(int x, int y, int minX, int maxX, int minY, int maxY) { + return (x < minX || x > maxX || y < minY || y > maxY) == false; + } + + /** static utility method to check if a bounding box contains a triangle */ + private static boolean bboxContainsTriangle(int ax, int ay, int bx, int by, int cx, int cy, + int minX, int maxX, int minY, int maxY) { + return bboxContainsPoint(ax, ay, minX, maxX, minY, maxY) + && bboxContainsPoint(bx, by, minX, maxX, minY, maxY) + && bboxContainsPoint(cx, cy, minX, maxX, minY, maxY); + } + + /** returns true if the edge (defined by (ax, ay) (bx, by)) intersects the query */ + private static boolean edgeIntersectsBox(int ax, int ay, int bx, int by, + int minX, int maxX, int minY, int maxY) { + // shortcut: if edge is a point (occurs w/ Line shapes); simply check bbox w/ point + if (ax == bx && ay == by) { + return Rectangle.containsPoint(ay, ax, minY, maxY, minX, maxX); + } + + // shortcut: check if either of the end points fall inside the box + if (bboxContainsPoint(ax, ay, minX, maxX, minY, maxY) + || bboxContainsPoint(bx, by, minX, maxX, minY, maxY)) { + return true; + } + + // shortcut: check bboxes of edges are disjoint + if (boxesAreDisjoint(Math.min(ax, bx), Math.max(ax, bx), Math.min(ay, by), Math.max(ay, by), + minX, maxX, minY, maxY)) { + return false; + } + + // shortcut: edge is a point + if (ax == bx && ay == by) { + return false; + } + + // top + if (orient(ax, ay, bx, by, minX, maxY) * orient(ax, ay, bx, by, maxX, maxY) <= 0 && + orient(minX, maxY, maxX, maxY, ax, ay) * orient(minX, maxY, maxX, maxY, bx, by) <= 0) { + return true; + } + + // right + if (orient(ax, ay, bx, by, maxX, maxY) * orient(ax, ay, bx, by, maxX, minY) <= 0 && + orient(maxX, maxY, maxX, minY, ax, ay) * orient(maxX, maxY, maxX, minY, bx, by) <= 0) { + return true; + } + + // bottom + if (orient(ax, ay, bx, by, maxX, minY) * orient(ax, ay, bx, by, minX, minY) <= 0 && + orient(maxX, minY, minX, minY, ax, ay) * orient(maxX, minY, minX, minY, bx, by) <= 0) { + return true; + } + + // left + if (orient(ax, ay, bx, by, minX, minY) * orient(ax, ay, bx, by, minX, maxY) <= 0 && + orient(minX, minY, minX, maxY, ax, ay) * orient(minX, minY, minX, maxY, bx, by) <= 0) { + return true; + } + return false; + } + + /** utility method to check if two boxes are disjoint */ + private static boolean boxesAreDisjoint(final int aMinX, final int aMaxX, final int aMinY, final int aMaxY, + final int bMinX, final int bMaxX, final int bMinY, final int bMaxY) { + return (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY); + } + + @Override + public boolean equals(Object o) { + return Arrays.equals(bbox, ((XRectangle2D)o).bbox) + && Arrays.equals(west, ((XRectangle2D)o).west); + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 31 * hash + Arrays.hashCode(bbox); + hash = 31 * hash + Arrays.hashCode(west); + return hash; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("Rectangle(lat="); + sb.append(decodeLatitude(minY)); + sb.append(" TO "); + sb.append(decodeLatitude(maxY)); + sb.append(" lon="); + sb.append(decodeLongitude(minX)); + sb.append(" TO "); + sb.append(decodeLongitude(maxX)); + if (maxX < minX) { + sb.append(" [crosses dateline!]"); + } + sb.append(")"); + return sb.toString(); + } +} diff --git a/server/src/main/java/org/apache/lucene/geo/XTessellator.java b/server/src/main/java/org/apache/lucene/geo/XTessellator.java new file mode 100644 index 00000000000..416b501202b --- /dev/null +++ b/server/src/main/java/org/apache/lucene/geo/XTessellator.java @@ -0,0 +1,889 @@ +/* + * 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.ArrayList; +import java.util.List; + +import org.apache.lucene.geo.GeoUtils.WindingOrder; +import org.apache.lucene.util.BitUtil; + +import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude; +import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude; +import static org.apache.lucene.geo.GeoUtils.orient; + +/** + * Computes a triangular mesh tessellation for a given polygon. + *
+ * This is inspired by mapbox's earcut algorithm (https://github.com/mapbox/earcut) + * which is a modification to FIST (https://www.cosy.sbg.ac.at/~held/projects/triang/triang.html) + * written by Martin Held, and ear clipping (https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf) + * written by David Eberly. + *
+ * Notes: + *
+ * The code is a modified version of the javascript implementation provided by MapBox + * under the following license: + *
+ * ISC License + *
+ * Copyright (c) 2016, Mapbox + *
+ * Permission to use, copy, modify, and/or distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright notice + * and this permission notice appear in all copies. + *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH'
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ *
+ * @lucene.experimental
+ */
+public final class XTessellator {
+ // this is a dumb heuristic to control whether we cut over to sorted morton values
+ private static final int VERTEX_THRESHOLD = 80;
+
+ /** state of the tessellated split - avoids recursion */
+ private enum State {
+ INIT, CURE, SPLIT
+ }
+
+ // No Instance:
+ private XTessellator() {}
+
+ /** Produces an array of vertices representing the triangulated result set of the Points array */
+ public static List
* Currently Shapes can only be indexed and can only be queried using
* {@link org.elasticsearch.index.query.GeoShapeQueryBuilder}, consequently
@@ -97,7 +97,7 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
return (GeoShapeFieldType) super.fieldType();
}
- /** parsing logic for {@link LatLonShape} indexing */
+ /** parsing logic for {@link XLatLonShape} indexing */
@Override
public void parse(ParseContext context) throws IOException {
try {
@@ -122,35 +122,35 @@ public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
private void indexShape(ParseContext context, Object luceneShape) {
if (luceneShape instanceof GeoPoint) {
GeoPoint pt = (GeoPoint) luceneShape;
- indexFields(context, LatLonShape.createIndexableFields(name(), pt.lat(), pt.lon()));
+ indexFields(context, XLatLonShape.createIndexableFields(name(), pt.lat(), pt.lon()));
} else if (luceneShape instanceof double[]) {
double[] pt = (double[]) luceneShape;
- indexFields(context, LatLonShape.createIndexableFields(name(), pt[1], pt[0]));
+ indexFields(context, XLatLonShape.createIndexableFields(name(), pt[1], pt[0]));
} else if (luceneShape instanceof Line) {
- indexFields(context, LatLonShape.createIndexableFields(name(), (Line)luceneShape));
+ indexFields(context, XLatLonShape.createIndexableFields(name(), (Line)luceneShape));
} else if (luceneShape instanceof Polygon) {
- indexFields(context, LatLonShape.createIndexableFields(name(), (Polygon) luceneShape));
+ indexFields(context, XLatLonShape.createIndexableFields(name(), (Polygon) luceneShape));
} else if (luceneShape instanceof double[][]) {
double[][] pts = (double[][])luceneShape;
for (int i = 0; i < pts.length; ++i) {
- indexFields(context, LatLonShape.createIndexableFields(name(), pts[i][1], pts[i][0]));
+ indexFields(context, XLatLonShape.createIndexableFields(name(), pts[i][1], pts[i][0]));
}
} else if (luceneShape instanceof Line[]) {
Line[] lines = (Line[]) luceneShape;
for (int i = 0; i < lines.length; ++i) {
- indexFields(context, LatLonShape.createIndexableFields(name(), lines[i]));
+ indexFields(context, XLatLonShape.createIndexableFields(name(), lines[i]));
}
} else if (luceneShape instanceof Polygon[]) {
Polygon[] polys = (Polygon[]) luceneShape;
for (int i = 0; i < polys.length; ++i) {
- indexFields(context, LatLonShape.createIndexableFields(name(), polys[i]));
+ indexFields(context, XLatLonShape.createIndexableFields(name(), polys[i]));
}
} else if (luceneShape instanceof Rectangle) {
// index rectangle as a polygon
Rectangle r = (Rectangle) luceneShape;
Polygon p = new Polygon(new double[]{r.minLat, r.minLat, r.maxLat, r.maxLat, r.minLat},
new double[]{r.minLon, r.maxLon, r.maxLon, r.minLon, r.minLon});
- indexFields(context, LatLonShape.createIndexableFields(name(), p));
+ indexFields(context, XLatLonShape.createIndexableFields(name(), p));
} else if (luceneShape instanceof Object[]) {
// recurse to index geometry collection
for (Object o : (Object[])luceneShape) {
diff --git a/server/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java
index 6ee0f3f10dd..96bd77725bd 100644
--- a/server/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java
+++ b/server/src/main/java/org/elasticsearch/index/query/GeoShapeQueryBuilder.java
@@ -19,7 +19,7 @@
package org.elasticsearch.index.query;
-import org.apache.lucene.document.LatLonShape;
+import org.apache.lucene.document.XLatLonShape;
import org.apache.lucene.geo.Line;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Rectangle;
@@ -429,16 +429,16 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder