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

geo_point runtime field implementation #63164

Merged
merged 15 commits into from
Oct 21, 2020
127 changes: 127 additions & 0 deletions server/src/main/java/org/elasticsearch/common/geo/GeoShapeUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,24 @@
*/
package org.elasticsearch.common.geo;

import org.apache.lucene.geo.LatLonGeometry;
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.index.query.QueryShardContext;
import org.elasticsearch.index.query.QueryShardException;

import java.util.ArrayList;
import java.util.List;


/**
Expand Down Expand Up @@ -60,6 +73,120 @@ public static org.apache.lucene.geo.Circle toLuceneCircle(Circle circle) {
return new org.apache.lucene.geo.Circle(circle.getLat(), circle.getLon(), circle.getRadiusMeters());
}

public static LatLonGeometry[] toLuceneGeometry(
String name,
QueryShardContext context,
Geometry geometry,
List<Class<? extends Geometry>> unsupportedGeometries
) {
final List<LatLonGeometry> geometries = new ArrayList<>();
geometry.visit(new GeometryVisitor<>() {
@Override
public Void visit(Circle circle) {
checkSupported(circle);
if (circle.isEmpty() == false) {
geometries.add(GeoShapeUtils.toLuceneCircle(circle));
}
return null;
}

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

@Override
public Void visit(org.elasticsearch.geometry.Line line) {
checkSupported(line);
if (line.isEmpty() == false) {
geometries.add(GeoShapeUtils.toLuceneLine(line));
}
return null;
}

@Override
public Void visit(LinearRing ring) {
throw new QueryShardException(context, "Field [" + name + "] found and unsupported shape LinearRing");
}

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

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

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

@Override
public Void visit(Point point) {
checkSupported(point);
if (point.isEmpty() == false) {
geometries.add(toLucenePoint(point));
}
return null;

}

@Override
public Void visit(org.elasticsearch.geometry.Polygon polygon) {
checkSupported(polygon);
if (polygon.isEmpty() == false) {
List<org.elasticsearch.geometry.Polygon> collector = new ArrayList<>();
GeoPolygonDecomposer.decomposePolygon(polygon, true, collector);
collector.forEach((p) -> geometries.add(toLucenePolygon(p)));
}
return null;
}

@Override
public Void visit(Rectangle r) {
checkSupported(r);
if (r.isEmpty() == false) {
geometries.add(toLuceneRectangle(r));
}
return null;
}

private void checkSupported(Geometry geometry) {
if (unsupportedGeometries.contains(geometry.getClass())) {
throw new QueryShardException(context, "Field [" + name + "] found and unsupported shape [" + geometry.type() + "]");
}
}
});
return geometries.toArray(new LatLonGeometry[geometries.size()]);
}

private GeoShapeUtils() {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,25 @@
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.elasticsearch.Version;
import org.elasticsearch.common.geo.GeoLineDecomposer;
import org.elasticsearch.common.geo.GeoPolygonDecomposer;
import org.elasticsearch.common.geo.GeoShapeUtils;
import org.elasticsearch.common.geo.ShapeRelation;
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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class VectorGeoShapeQueryProcessor {

private static final List<Class<? extends Geometry>> WITHIN_UNSUPPORTED_GEOMETRIES = new ArrayList<>();
static {
WITHIN_UNSUPPORTED_GEOMETRIES.add(Line.class);
WITHIN_UNSUPPORTED_GEOMETRIES.add(MultiLine.class);
}

public Query geoShapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
// CONTAINS queries are not supported by VECTOR strategy for indices created before version 7.5.0 (Lucene 8.3.0)
if (relation == ShapeRelation.CONTAINS && context.indexVersionCreated().before(Version.V_7_5_0)) {
Expand All @@ -58,125 +54,16 @@ public Query geoShapeQuery(Geometry shape, String fieldName, ShapeRelation relat
}

private Query getVectorQueryFromShape(Geometry queryShape, String fieldName, ShapeRelation relation, QueryShardContext context) {
final LuceneGeometryCollector visitor = new LuceneGeometryCollector(fieldName, context);
queryShape.visit(visitor);
final List<LatLonGeometry> geometries = visitor.geometries();
if (geometries.size() == 0) {
return new MatchNoDocsQuery();
}
return LatLonShape.newGeometryQuery(fieldName, relation.getLuceneRelation(),
geometries.toArray(new LatLonGeometry[geometries.size()]));
}

private static class LuceneGeometryCollector implements GeometryVisitor<Void, RuntimeException> {
private final List<LatLonGeometry> geometries = new ArrayList<>();
private final String name;
private final QueryShardContext context;

private LuceneGeometryCollector(String name, QueryShardContext context) {
this.name = name;
this.context = context;
}

List<LatLonGeometry> geometries() {
return geometries;
}

@Override
public Void visit(Circle circle) {
if (circle.isEmpty() == false) {
geometries.add(GeoShapeUtils.toLuceneCircle(circle));
}
return null;
}

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

@Override
public Void visit(org.elasticsearch.geometry.Line line) {
if (line.isEmpty() == false) {
List<org.elasticsearch.geometry.Line> collector = new ArrayList<>();
GeoLineDecomposer.decomposeLine(line, collector);
collectLines(collector);
}
return null;
}

@Override
public Void visit(LinearRing ring) {
throw new QueryShardException(context, "Field [" + name + "] found and unsupported shape LinearRing");
final LatLonGeometry[] luceneGeometries;
if (relation == ShapeRelation.WITHIN) {
luceneGeometries = GeoShapeUtils.toLuceneGeometry(fieldName, context, queryShape, WITHIN_UNSUPPORTED_GEOMETRIES);
} else {
luceneGeometries = GeoShapeUtils.toLuceneGeometry(fieldName, context, queryShape, Collections.emptyList());
}

@Override
public Void visit(MultiLine multiLine) {
List<org.elasticsearch.geometry.Line> collector = new ArrayList<>();
GeoLineDecomposer.decomposeMultiLine(multiLine, collector);
collectLines(collector);
return null;
}

@Override
public Void visit(MultiPoint multiPoint) {
for (Point point : multiPoint) {
visit(point);
}
return null;
}

@Override
public Void visit(MultiPolygon multiPolygon) {
if (multiPolygon.isEmpty() == false) {
List<org.elasticsearch.geometry.Polygon> collector = new ArrayList<>();
GeoPolygonDecomposer.decomposeMultiPolygon(multiPolygon, true, collector);
collectPolygons(collector);
}
return null;
}

@Override
public Void visit(Point point) {
if (point.isEmpty() == false) {
geometries.add(GeoShapeUtils.toLucenePoint(point));
}
return null;

}

@Override
public Void visit(org.elasticsearch.geometry.Polygon polygon) {
if (polygon.isEmpty() == false) {
List<org.elasticsearch.geometry.Polygon> collector = new ArrayList<>();
GeoPolygonDecomposer.decomposePolygon(polygon, true, collector);
collectPolygons(collector);
}
return null;
}

@Override
public Void visit(Rectangle r) {
if (r.isEmpty() == false) {
geometries.add(GeoShapeUtils.toLuceneRectangle(r));
}
return null;
}

private void collectLines(List<org.elasticsearch.geometry.Line> geometryLines) {
for (Line line: geometryLines) {
geometries.add(GeoShapeUtils.toLuceneLine(line));
}
}

private void collectPolygons(List<org.elasticsearch.geometry.Polygon> geometryPolygons) {
for (Polygon polygon : geometryPolygons) {
geometries.add(GeoShapeUtils.toLucenePolygon(polygon));
}
if (luceneGeometries.length == 0) {
return new MatchNoDocsQuery();
}
return LatLonShape.newGeometryQuery(fieldName, relation.getLuceneRelation(), luceneGeometries);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.elasticsearch.xpack.runtimefields.mapper.BooleanFieldScript;
import org.elasticsearch.xpack.runtimefields.mapper.DateFieldScript;
import org.elasticsearch.xpack.runtimefields.mapper.DoubleFieldScript;
import org.elasticsearch.xpack.runtimefields.mapper.GeoPointFieldScript;
import org.elasticsearch.xpack.runtimefields.mapper.IpFieldScript;
import org.elasticsearch.xpack.runtimefields.mapper.LongFieldScript;
import org.elasticsearch.xpack.runtimefields.mapper.RuntimeFieldMapper;
Expand All @@ -36,6 +37,7 @@ public List<ScriptContext<?>> getContexts() {
BooleanFieldScript.CONTEXT,
DateFieldScript.CONTEXT,
DoubleFieldScript.CONTEXT,
GeoPointFieldScript.CONTEXT,
IpFieldScript.CONTEXT,
LongFieldScript.CONTEXT,
StringFieldScript.CONTEXT
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.runtimefields.fielddata;

import org.apache.lucene.geo.GeoEncodingUtils;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.index.fielddata.MultiGeoPointValues;
import org.elasticsearch.xpack.runtimefields.mapper.GeoPointFieldScript;

import java.util.Arrays;

public final class GeoPointScriptDocValues extends MultiGeoPointValues {
private final GeoPointFieldScript script;
private final GeoPoint point;
private int cursor;

GeoPointScriptDocValues(GeoPointFieldScript script) {
this.script = script;
this.point = new GeoPoint();
}

@Override
public boolean advanceExact(int docId) {
script.runForDoc(docId);
if (script.count() == 0) {
return false;
}
Arrays.sort(script.values(), 0, script.count());
cursor = 0;
return true;
}

@Override
public int docValueCount() {
return script.count();
}

@Override
public GeoPoint nextValue() {
final long value = script.values()[cursor++];
final int lat = (int) (value >>> 32);
final int lon = (int) (value & 0xFFFFFFFF);
return point.reset(GeoEncodingUtils.decodeLatitude(lat), GeoEncodingUtils.decodeLongitude(lon));
}
}
Loading