Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LUCENE-8746: Make EdgeTree (aka ComponentTree) support different type of components #627

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
*/
package org.apache.lucene.document;

import org.apache.lucene.geo.Component;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Polygon2D;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.PointValues;
import org.apache.lucene.search.BooleanClause;
Expand Down Expand Up @@ -251,7 +253,8 @@ public static Query newDistanceQuery(String field, double latitude, double longi
* @see Polygon
*/
public static Query newPolygonQuery(String field, Polygon... polygons) {
return new LatLonPointInPolygonQuery(field, polygons);
Component component = Polygon2D.create(polygons);
return new LatLonPointInComponentQuery(field, component);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@

import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;

import org.apache.lucene.geo.Component;
import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.geo.Polygon;
import org.apache.lucene.geo.Polygon2D;
import org.apache.lucene.geo.Rectangle;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.LeafReader;
Expand All @@ -45,34 +45,25 @@
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLatitude;
import static org.apache.lucene.geo.GeoEncodingUtils.encodeLongitude;

/** Finds all previously indexed points that fall within the specified polygons.
/** Finds all previously indexed points that fall within the specified {@link Component}.
*
* <p>The field must be indexed with using {@link org.apache.lucene.document.LatLonPoint} added per document.
* <p>The field must be indexed with using {@link LatLonPoint} added per document.
*
* @lucene.experimental */

final class LatLonPointInPolygonQuery extends Query {
final class LatLonPointInComponentQuery extends Query {
final String field;
final Polygon[] polygons;
final Component component;

LatLonPointInPolygonQuery(String field, Polygon[] polygons) {
LatLonPointInComponentQuery(String field, Component component) {
if (field == null) {
throw new IllegalArgumentException("field must not be null");
}
if (polygons == null) {
throw new IllegalArgumentException("polygons must not be null");
}
if (polygons.length == 0) {
throw new IllegalArgumentException("polygons must not be empty");
}
for (int i = 0; i < polygons.length; i++) {
if (polygons[i] == null) {
throw new IllegalArgumentException("polygon[" + i + "] must not be null");
}
if (component == null) {
throw new IllegalArgumentException("component must not be null");
}
this.field = field;
this.polygons = polygons.clone();
// TODO: we could also compute the maximal inner bounding box, to make relations faster to compute?
this.component = component;
}

@Override
Expand All @@ -87,10 +78,10 @@ public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float bo

// I don't use RandomAccessWeight here: it's no good to approximate with "match all docs"; this is an inverted structure and should be
// used in the first pass:
// bounding box over all polygons, this can speed up tree intersection/cheaply improve approximation for complex multi-polygons

// bounding box of the component, this can speed up tree intersection/cheaply improve approximation for complex components
// these are pre-encoded with LatLonPoint's encoding
final Rectangle box = Rectangle.fromPolygon(polygons);
final Rectangle box = component.getBoundingBox();
final byte minLat[] = new byte[Integer.BYTES];
final byte maxLat[] = new byte[Integer.BYTES];
final byte minLon[] = new byte[Integer.BYTES];
Expand All @@ -100,8 +91,7 @@ public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float bo
NumericUtils.intToSortableBytes(encodeLongitude(box.minLon), minLon, 0);
NumericUtils.intToSortableBytes(encodeLongitude(box.maxLon), maxLon, 0);

final Polygon2D tree = Polygon2D.create(polygons);
final GeoEncodingUtils.PolygonPredicate polygonPredicate = GeoEncodingUtils.createPolygonPredicate(polygons, tree);
final GeoEncodingUtils.ComponentPredicate componentPredicate = GeoEncodingUtils.createComponentPredicate(component);

return new ConstantScoreWeight(this, boost) {

Expand All @@ -123,7 +113,7 @@ public Scorer scorer(LeafReaderContext context) throws IOException {
// matching docids
DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);

values.intersect(
values.intersect(
new IntersectVisitor() {

DocIdSetBuilder.BulkAdder adder;
Expand All @@ -140,7 +130,7 @@ public void visit(int docID) {

@Override
public void visit(int docID, byte[] packedValue) {
if (polygonPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0),
if (componentPredicate.test(NumericUtils.sortableBytesToInt(packedValue, 0),
NumericUtils.sortableBytesToInt(packedValue, Integer.BYTES))) {
adder.add(docID);
}
Expand All @@ -155,13 +145,13 @@ public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
// outside of global bounding box range
return Relation.CELL_OUTSIDE_QUERY;
}

double cellMinLat = decodeLatitude(minPackedValue, 0);
double cellMinLon = decodeLongitude(minPackedValue, Integer.BYTES);
double cellMaxLat = decodeLatitude(maxPackedValue, 0);
double cellMaxLon = decodeLongitude(maxPackedValue, Integer.BYTES);

return tree.relate(cellMinLat, cellMaxLat, cellMinLon, cellMaxLon);
return component.relate(cellMinLat, cellMaxLat, cellMinLon, cellMaxLon);
}
});

Expand All @@ -180,17 +170,12 @@ public String getField() {
return field;
}

/** Returns a copy of the internal polygon array */
public Polygon[] getPolygons() {
return polygons.clone();
}

@Override
public int hashCode() {
final int prime = 31;
int result = classHash();
result = prime * result + field.hashCode();
result = prime * result + Arrays.hashCode(polygons);
result = prime * result + component.hashCode();
return result;
}

Expand All @@ -200,9 +185,9 @@ public boolean equals(Object other) {
equalsTo(getClass().cast(other));
}

private boolean equalsTo(LatLonPointInPolygonQuery other) {
private boolean equalsTo(LatLonPointInComponentQuery other) {
return field.equals(other.field) &&
Arrays.equals(polygons, other.polygons);
Objects.equals(component, other.component);
}

@Override
Expand All @@ -215,7 +200,7 @@ public String toString(String field) {
sb.append(this.field);
sb.append(':');
}
sb.append(Arrays.toString(polygons));
sb.append(component.toString());
return sb.toString();
}
}
65 changes: 65 additions & 0 deletions lucene/core/src/java/org/apache/lucene/geo/Component.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.lucene.geo;

import org.apache.lucene.index.PointValues;

import static org.apache.lucene.geo.GeoUtils.orient;

/**
* Geometry object that supports spatial relationships with bounding boxes,
* triangles and points.
*
* @lucene.internal
*/
public interface Component {
/** relates this component with a point **/
boolean contains(double lat, double lon);

/** relates this component with a bounding box **/
PointValues.Relation relate(double minY, double maxY, double minX, double maxX);

/** relates this component with a triangle **/
PointValues.Relation relateTriangle(double ax, double ay, double bx, double by, double cx, double cy);

/** bounding box for this component **/
Rectangle getBoundingBox();

//This should be moved when LatLonShape is moved from sandbox!
/**
* Compute whether the given x, y point is in a triangle; uses the winding order method */
static boolean pointInTriangle (double x, double y, double ax, double ay, double bx, double by, double cx, double cy) {
double minX = StrictMath.min(ax, StrictMath.min(bx, cx));
double minY = StrictMath.min(ay, StrictMath.min(by, cy));
double maxX = StrictMath.max(ax, StrictMath.max(bx, cx));
double maxY = StrictMath.max(ay, StrictMath.max(by, cy));
//check the bounding box because if the triangle is degenerated, e.g points and lines, we need to filter out
//coplanar points that are not part of the triangle.
if (x >= minX && x <= maxX && y >= minY && y <= maxY ) {
int a = orient(x, y, ax, ay, bx, by);
int b = orient(x, y, bx, by, cx, cy);
if (a == 0 || b == 0 || a < 0 == b < 0) {
int c = orient(x, y, cx, cy, ax, ay);
return c == 0 || (c < 0 == (b < 0 || a < 0));
}
return false;
} else {
return false;
}
}
}
Loading