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

Move functions that generate lucene geometries under a utility class #104928

Merged
merged 14 commits into from
Jan 31, 2024

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.apache.lucene.index.IndexableField;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.geo.GeometryNormalizer;
import org.elasticsearch.common.geo.LuceneGeometriesUtils;
import org.elasticsearch.common.geo.Orientation;
import org.elasticsearch.geometry.Circle;
import org.elasticsearch.geometry.Geometry;
Expand Down Expand Up @@ -94,7 +95,7 @@ public Void visit(GeometryCollection<?> collection) {

@Override
public Void visit(Line line) {
addFields(LatLonShape.createIndexableFields(name, toLuceneLine(line)));
addFields(LatLonShape.createIndexableFields(name, LuceneGeometriesUtils.toLatLonLine(line)));
return null;
}

Expand Down Expand Up @@ -135,7 +136,7 @@ public Void visit(Point point) {

@Override
public Void visit(Polygon polygon) {
addFields(LatLonShape.createIndexableFields(name, toLucenePolygon(polygon), true));
addFields(LatLonShape.createIndexableFields(name, LuceneGeometriesUtils.toLatLonPolygon(polygon), true));
return null;
}

Expand Down Expand Up @@ -199,22 +200,10 @@ private void addFields(IndexableField[] fields) {
}
}

private static org.apache.lucene.geo.Polygon toLucenePolygon(Polygon polygon) {
org.apache.lucene.geo.Polygon[] holes = new org.apache.lucene.geo.Polygon[polygon.getNumberOfHoles()];
for (int i = 0; i < holes.length; i++) {
holes[i] = new org.apache.lucene.geo.Polygon(polygon.getHole(i).getY(), polygon.getHole(i).getX());
}
return new org.apache.lucene.geo.Polygon(polygon.getPolygon().getY(), polygon.getPolygon().getX(), holes);
}

private static org.apache.lucene.geo.Polygon toLucenePolygon(Rectangle r) {
return new org.apache.lucene.geo.Polygon(
new double[] { r.getMinLat(), r.getMinLat(), r.getMaxLat(), r.getMaxLat(), r.getMinLat() },
new double[] { r.getMinLon(), r.getMaxLon(), r.getMaxLon(), r.getMinLon(), r.getMinLon() }
);
}

private static org.apache.lucene.geo.Line toLuceneLine(Line line) {
return new org.apache.lucene.geo.Line(line.getLats(), line.getLons());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,18 @@

package org.elasticsearch.index.mapper;

import org.apache.lucene.geo.GeoEncodingUtils;
import org.apache.lucene.geo.LatLonGeometry;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.geo.GeometryNormalizer;
import org.elasticsearch.common.geo.Orientation;
import org.elasticsearch.common.geo.LuceneGeometriesUtils;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.SpatialStrategy;
import org.elasticsearch.geometry.Circle;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.GeometryCollection;
import org.elasticsearch.geometry.GeometryVisitor;
import org.elasticsearch.geometry.Line;
import org.elasticsearch.geometry.LinearRing;
import org.elasticsearch.geometry.MultiLine;
import org.elasticsearch.geometry.MultiPoint;
import org.elasticsearch.geometry.MultiPolygon;
import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.Polygon;
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.geometry.ShapeType;
import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.index.query.SearchExecutionContext;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

/**
* Implemented by {@link org.elasticsearch.index.mapper.MappedFieldType} that support
Expand All @@ -43,10 +29,18 @@ public interface GeoShapeQueryable {

Query geoShapeQuery(SearchExecutionContext context, String fieldName, ShapeRelation relation, LatLonGeometry... luceneGeometries);

default Query geoShapeQuery(SearchExecutionContext context, String fieldName, ShapeRelation relation, Geometry shape) {
default Query geoShapeQuery(SearchExecutionContext context, String fieldName, ShapeRelation relation, Geometry geometry) {
final Consumer<ShapeType> checker = relation == ShapeRelation.WITHIN ? t -> {
if (t == ShapeType.LINESTRING) {
// Line geometries and WITHIN relation is not supported by Lucene. Throw an error here
// to have same behavior for runtime fields.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if not supported by lucene, I presume in ES|QL that simply means we cannot optimize this case by pushing down to lucene, but could still support it using source values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot support it because of quantization. We are changing the language not the indexing model.

throw new IllegalArgumentException("found an unsupported shape Line");
}
} : t -> {};
final LatLonGeometry[] luceneGeometries;
try {
luceneGeometries = toQuantizeLuceneGeometry(shape, relation);
// quantize the geometries to match the values on the index
luceneGeometries = LuceneGeometriesUtils.toLatLonGeometry(geometry, true, checker);
} catch (IllegalArgumentException e) {
throw new QueryShardException(context, "Exception creating query on Field [" + fieldName + "] " + e.getMessage(), e);
}
Expand All @@ -66,157 +60,4 @@ default Query geoShapeQuery(
) {
return geoShapeQuery(context, fieldName, relation, shape);
}

private static double quantizeLat(double lat) {
return GeoEncodingUtils.decodeLatitude(GeoEncodingUtils.encodeLatitude(lat));
}

private static double[] quantizeLats(double[] lats) {
return Arrays.stream(lats).map(GeoShapeQueryable::quantizeLat).toArray();
}

private static double quantizeLon(double lon) {
return GeoEncodingUtils.decodeLongitude(GeoEncodingUtils.encodeLongitude(lon));
}

private static double[] quantizeLons(double[] lons) {
return Arrays.stream(lons).map(GeoShapeQueryable::quantizeLon).toArray();
}

/**
* transforms an Elasticsearch {@link Geometry} into a lucene {@link LatLonGeometry} and quantize
* the latitude and longitude values to match the values on the index.
*/
static LatLonGeometry[] toQuantizeLuceneGeometry(Geometry geometry, ShapeRelation relation) {
if (geometry == null) {
return new LatLonGeometry[0];
}
if (GeometryNormalizer.needsNormalize(Orientation.CCW, geometry)) {
// make geometry lucene friendly
geometry = GeometryNormalizer.apply(Orientation.CCW, geometry);
}
if (geometry.isEmpty()) {
return new LatLonGeometry[0];
}
final List<LatLonGeometry> geometries = new ArrayList<>();
geometry.visit(new GeometryVisitor<>() {
@Override
public Void visit(Circle circle) {
if (circle.isEmpty() == false) {
geometries.add(
new org.apache.lucene.geo.Circle(
quantizeLat(circle.getLat()),
quantizeLon(circle.getLon()),
circle.getRadiusMeters()
)
);
}
return null;
}

@Override
public Void visit(GeometryCollection<?> collection) {
if (collection.isEmpty() == false) {
for (Geometry shape : collection) {
shape.visit(this);
}
}
return null;
}

@Override
public Void visit(org.elasticsearch.geometry.Line line) {
if (line.isEmpty() == false) {
if (relation == ShapeRelation.WITHIN) {
// Line geometries and WITHIN relation is not supported by Lucene. Throw an error here
// to have same behavior for runtime fields.
throw new IllegalArgumentException("found an unsupported shape Line");
}
geometries.add(new org.apache.lucene.geo.Line(quantizeLats(line.getLats()), quantizeLons(line.getLons())));
}
return null;
}

@Override
public Void visit(LinearRing ring) {
throw new IllegalArgumentException("Found an unsupported shape LinearRing");
}

@Override
public Void visit(MultiLine multiLine) {
if (multiLine.isEmpty() == false) {
for (Line line : multiLine) {
visit(line);
}
}
return null;
}

@Override
public Void visit(MultiPoint multiPoint) {
if (multiPoint.isEmpty() == false) {
for (Point point : multiPoint) {
visit(point);
}
}
return null;
}

@Override
public Void visit(MultiPolygon multiPolygon) {
if (multiPolygon.isEmpty() == false) {
for (Polygon polygon : multiPolygon) {
visit(polygon);
}
}
return null;
}

@Override
public Void visit(Point point) {
if (point.isEmpty() == false) {
geometries.add(new org.apache.lucene.geo.Point(quantizeLat(point.getLat()), quantizeLon(point.getLon())));
}
return null;

}

@Override
public Void visit(org.elasticsearch.geometry.Polygon polygon) {
if (polygon.isEmpty() == false) {
org.apache.lucene.geo.Polygon[] holes = new org.apache.lucene.geo.Polygon[polygon.getNumberOfHoles()];
for (int i = 0; i < holes.length; i++) {
holes[i] = new org.apache.lucene.geo.Polygon(
quantizeLats(polygon.getHole(i).getY()),
quantizeLons(polygon.getHole(i).getX())
);
}
geometries.add(
new org.apache.lucene.geo.Polygon(
quantizeLats(polygon.getPolygon().getY()),
quantizeLons(polygon.getPolygon().getX()),
holes
)
);
}
return null;
}

@Override
public Void visit(Rectangle r) {
if (r.isEmpty() == false) {
geometries.add(
new org.apache.lucene.geo.Rectangle(
quantizeLat(r.getMinLat()),
quantizeLat(r.getMaxLat()),
quantizeLon(r.getMinLon()),
quantizeLon(r.getMaxLon())
)
);
}
return null;
}
});
return geometries.toArray(new LatLonGeometry[0]);
}
}
Loading