Skip to content

Commit

Permalink
geo_point runtime field implementation (elastic#63164)
Browse files Browse the repository at this point in the history
Run time field that emits geo points from lat/lon values.
  • Loading branch information
iverase authored and pugnascotia committed Oct 21, 2020
1 parent 70f758e commit 1376afd
Show file tree
Hide file tree
Showing 21 changed files with 1,255 additions and 128 deletions.
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

0 comments on commit 1376afd

Please sign in to comment.