Skip to content

Commit 265a214

Browse files
authored
geo_point runtime field implementation (elastic#63164)
Run time field that emits geo points from lat/lon values.
1 parent 5f4f8b2 commit 265a214

21 files changed

+1255
-128
lines changed

server/src/main/java/org/elasticsearch/common/geo/GeoShapeUtils.java

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,24 @@
1818
*/
1919
package org.elasticsearch.common.geo;
2020

21+
import org.apache.lucene.geo.LatLonGeometry;
2122
import org.elasticsearch.geometry.Circle;
23+
import org.elasticsearch.geometry.Geometry;
24+
import org.elasticsearch.geometry.GeometryCollection;
25+
import org.elasticsearch.geometry.GeometryVisitor;
2226
import org.elasticsearch.geometry.Line;
27+
import org.elasticsearch.geometry.LinearRing;
28+
import org.elasticsearch.geometry.MultiLine;
29+
import org.elasticsearch.geometry.MultiPoint;
30+
import org.elasticsearch.geometry.MultiPolygon;
2331
import org.elasticsearch.geometry.Point;
2432
import org.elasticsearch.geometry.Polygon;
2533
import org.elasticsearch.geometry.Rectangle;
34+
import org.elasticsearch.index.query.QueryShardContext;
35+
import org.elasticsearch.index.query.QueryShardException;
36+
37+
import java.util.ArrayList;
38+
import java.util.List;
2639

2740

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

76+
public static LatLonGeometry[] toLuceneGeometry(
77+
String name,
78+
QueryShardContext context,
79+
Geometry geometry,
80+
List<Class<? extends Geometry>> unsupportedGeometries
81+
) {
82+
final List<LatLonGeometry> geometries = new ArrayList<>();
83+
geometry.visit(new GeometryVisitor<>() {
84+
@Override
85+
public Void visit(Circle circle) {
86+
checkSupported(circle);
87+
if (circle.isEmpty() == false) {
88+
geometries.add(GeoShapeUtils.toLuceneCircle(circle));
89+
}
90+
return null;
91+
}
92+
93+
@Override
94+
public Void visit(GeometryCollection<?> collection) {
95+
checkSupported(collection);
96+
if (collection.isEmpty() == false) {
97+
for (Geometry shape : collection) {
98+
shape.visit(this);
99+
}
100+
}
101+
return null;
102+
}
103+
104+
@Override
105+
public Void visit(org.elasticsearch.geometry.Line line) {
106+
checkSupported(line);
107+
if (line.isEmpty() == false) {
108+
geometries.add(GeoShapeUtils.toLuceneLine(line));
109+
}
110+
return null;
111+
}
112+
113+
@Override
114+
public Void visit(LinearRing ring) {
115+
throw new QueryShardException(context, "Field [" + name + "] found and unsupported shape LinearRing");
116+
}
117+
118+
@Override
119+
public Void visit(MultiLine multiLine) {
120+
checkSupported(multiLine);
121+
if (multiLine.isEmpty() == false) {
122+
for (Line line : multiLine) {
123+
visit(line);
124+
}
125+
}
126+
return null;
127+
}
128+
129+
@Override
130+
public Void visit(MultiPoint multiPoint) {
131+
checkSupported(multiPoint);
132+
if (multiPoint.isEmpty() == false) {
133+
for (Point point : multiPoint) {
134+
visit(point);
135+
}
136+
}
137+
return null;
138+
}
139+
140+
@Override
141+
public Void visit(MultiPolygon multiPolygon) {
142+
checkSupported(multiPolygon);
143+
if (multiPolygon.isEmpty() == false) {
144+
for (Polygon polygon : multiPolygon) {
145+
visit(polygon);
146+
}
147+
}
148+
return null;
149+
}
150+
151+
@Override
152+
public Void visit(Point point) {
153+
checkSupported(point);
154+
if (point.isEmpty() == false) {
155+
geometries.add(toLucenePoint(point));
156+
}
157+
return null;
158+
159+
}
160+
161+
@Override
162+
public Void visit(org.elasticsearch.geometry.Polygon polygon) {
163+
checkSupported(polygon);
164+
if (polygon.isEmpty() == false) {
165+
List<org.elasticsearch.geometry.Polygon> collector = new ArrayList<>();
166+
GeoPolygonDecomposer.decomposePolygon(polygon, true, collector);
167+
collector.forEach((p) -> geometries.add(toLucenePolygon(p)));
168+
}
169+
return null;
170+
}
171+
172+
@Override
173+
public Void visit(Rectangle r) {
174+
checkSupported(r);
175+
if (r.isEmpty() == false) {
176+
geometries.add(toLuceneRectangle(r));
177+
}
178+
return null;
179+
}
180+
181+
private void checkSupported(Geometry geometry) {
182+
if (unsupportedGeometries.contains(geometry.getClass())) {
183+
throw new QueryShardException(context, "Field [" + name + "] found and unsupported shape [" + geometry.type() + "]");
184+
}
185+
}
186+
});
187+
return geometries.toArray(new LatLonGeometry[geometries.size()]);
188+
}
189+
63190
private GeoShapeUtils() {
64191
}
65192

server/src/main/java/org/elasticsearch/index/query/VectorGeoShapeQueryProcessor.java

Lines changed: 15 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,25 @@
2424
import org.apache.lucene.search.MatchNoDocsQuery;
2525
import org.apache.lucene.search.Query;
2626
import org.elasticsearch.Version;
27-
import org.elasticsearch.common.geo.GeoLineDecomposer;
28-
import org.elasticsearch.common.geo.GeoPolygonDecomposer;
2927
import org.elasticsearch.common.geo.GeoShapeUtils;
3028
import org.elasticsearch.common.geo.ShapeRelation;
31-
import org.elasticsearch.geometry.Circle;
3229
import org.elasticsearch.geometry.Geometry;
33-
import org.elasticsearch.geometry.GeometryCollection;
34-
import org.elasticsearch.geometry.GeometryVisitor;
3530
import org.elasticsearch.geometry.Line;
36-
import org.elasticsearch.geometry.LinearRing;
3731
import org.elasticsearch.geometry.MultiLine;
38-
import org.elasticsearch.geometry.MultiPoint;
39-
import org.elasticsearch.geometry.MultiPolygon;
40-
import org.elasticsearch.geometry.Point;
41-
import org.elasticsearch.geometry.Polygon;
42-
import org.elasticsearch.geometry.Rectangle;
4332

4433
import java.util.ArrayList;
34+
import java.util.Collections;
4535
import java.util.List;
4636

4737

4838
public class VectorGeoShapeQueryProcessor {
4939

40+
private static final List<Class<? extends Geometry>> WITHIN_UNSUPPORTED_GEOMETRIES = new ArrayList<>();
41+
static {
42+
WITHIN_UNSUPPORTED_GEOMETRIES.add(Line.class);
43+
WITHIN_UNSUPPORTED_GEOMETRIES.add(MultiLine.class);
44+
}
45+
5046
public Query geoShapeQuery(Geometry shape, String fieldName, ShapeRelation relation, QueryShardContext context) {
5147
// CONTAINS queries are not supported by VECTOR strategy for indices created before version 7.5.0 (Lucene 8.3.0)
5248
if (relation == ShapeRelation.CONTAINS && context.indexVersionCreated().before(Version.V_7_5_0)) {
@@ -58,125 +54,16 @@ public Query geoShapeQuery(Geometry shape, String fieldName, ShapeRelation relat
5854
}
5955

6056
private Query getVectorQueryFromShape(Geometry queryShape, String fieldName, ShapeRelation relation, QueryShardContext context) {
61-
final LuceneGeometryCollector visitor = new LuceneGeometryCollector(fieldName, context);
62-
queryShape.visit(visitor);
63-
final List<LatLonGeometry> geometries = visitor.geometries();
64-
if (geometries.size() == 0) {
65-
return new MatchNoDocsQuery();
66-
}
67-
return LatLonShape.newGeometryQuery(fieldName, relation.getLuceneRelation(),
68-
geometries.toArray(new LatLonGeometry[geometries.size()]));
69-
}
70-
71-
private static class LuceneGeometryCollector implements GeometryVisitor<Void, RuntimeException> {
72-
private final List<LatLonGeometry> geometries = new ArrayList<>();
73-
private final String name;
74-
private final QueryShardContext context;
75-
76-
private LuceneGeometryCollector(String name, QueryShardContext context) {
77-
this.name = name;
78-
this.context = context;
79-
}
80-
81-
List<LatLonGeometry> geometries() {
82-
return geometries;
83-
}
84-
85-
@Override
86-
public Void visit(Circle circle) {
87-
if (circle.isEmpty() == false) {
88-
geometries.add(GeoShapeUtils.toLuceneCircle(circle));
89-
}
90-
return null;
91-
}
92-
93-
@Override
94-
public Void visit(GeometryCollection<?> collection) {
95-
for (Geometry shape : collection) {
96-
shape.visit(this);
97-
}
98-
return null;
99-
}
100-
101-
@Override
102-
public Void visit(org.elasticsearch.geometry.Line line) {
103-
if (line.isEmpty() == false) {
104-
List<org.elasticsearch.geometry.Line> collector = new ArrayList<>();
105-
GeoLineDecomposer.decomposeLine(line, collector);
106-
collectLines(collector);
107-
}
108-
return null;
109-
}
110-
111-
@Override
112-
public Void visit(LinearRing ring) {
113-
throw new QueryShardException(context, "Field [" + name + "] found and unsupported shape LinearRing");
57+
final LatLonGeometry[] luceneGeometries;
58+
if (relation == ShapeRelation.WITHIN) {
59+
luceneGeometries = GeoShapeUtils.toLuceneGeometry(fieldName, context, queryShape, WITHIN_UNSUPPORTED_GEOMETRIES);
60+
} else {
61+
luceneGeometries = GeoShapeUtils.toLuceneGeometry(fieldName, context, queryShape, Collections.emptyList());
11462
}
115-
116-
@Override
117-
public Void visit(MultiLine multiLine) {
118-
List<org.elasticsearch.geometry.Line> collector = new ArrayList<>();
119-
GeoLineDecomposer.decomposeMultiLine(multiLine, collector);
120-
collectLines(collector);
121-
return null;
122-
}
123-
124-
@Override
125-
public Void visit(MultiPoint multiPoint) {
126-
for (Point point : multiPoint) {
127-
visit(point);
128-
}
129-
return null;
130-
}
131-
132-
@Override
133-
public Void visit(MultiPolygon multiPolygon) {
134-
if (multiPolygon.isEmpty() == false) {
135-
List<org.elasticsearch.geometry.Polygon> collector = new ArrayList<>();
136-
GeoPolygonDecomposer.decomposeMultiPolygon(multiPolygon, true, collector);
137-
collectPolygons(collector);
138-
}
139-
return null;
140-
}
141-
142-
@Override
143-
public Void visit(Point point) {
144-
if (point.isEmpty() == false) {
145-
geometries.add(GeoShapeUtils.toLucenePoint(point));
146-
}
147-
return null;
148-
149-
}
150-
151-
@Override
152-
public Void visit(org.elasticsearch.geometry.Polygon polygon) {
153-
if (polygon.isEmpty() == false) {
154-
List<org.elasticsearch.geometry.Polygon> collector = new ArrayList<>();
155-
GeoPolygonDecomposer.decomposePolygon(polygon, true, collector);
156-
collectPolygons(collector);
157-
}
158-
return null;
159-
}
160-
161-
@Override
162-
public Void visit(Rectangle r) {
163-
if (r.isEmpty() == false) {
164-
geometries.add(GeoShapeUtils.toLuceneRectangle(r));
165-
}
166-
return null;
167-
}
168-
169-
private void collectLines(List<org.elasticsearch.geometry.Line> geometryLines) {
170-
for (Line line: geometryLines) {
171-
geometries.add(GeoShapeUtils.toLuceneLine(line));
172-
}
173-
}
174-
175-
private void collectPolygons(List<org.elasticsearch.geometry.Polygon> geometryPolygons) {
176-
for (Polygon polygon : geometryPolygons) {
177-
geometries.add(GeoShapeUtils.toLucenePolygon(polygon));
178-
}
63+
if (luceneGeometries.length == 0) {
64+
return new MatchNoDocsQuery();
17965
}
66+
return LatLonShape.newGeometryQuery(fieldName, relation.getLuceneRelation(), luceneGeometries);
18067
}
18168
}
18269

x-pack/plugin/runtime-fields/src/main/java/org/elasticsearch/xpack/runtimefields/RuntimeFields.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.elasticsearch.xpack.runtimefields.mapper.BooleanFieldScript;
1515
import org.elasticsearch.xpack.runtimefields.mapper.DateFieldScript;
1616
import org.elasticsearch.xpack.runtimefields.mapper.DoubleFieldScript;
17+
import org.elasticsearch.xpack.runtimefields.mapper.GeoPointFieldScript;
1718
import org.elasticsearch.xpack.runtimefields.mapper.IpFieldScript;
1819
import org.elasticsearch.xpack.runtimefields.mapper.LongFieldScript;
1920
import org.elasticsearch.xpack.runtimefields.mapper.RuntimeFieldMapper;
@@ -36,6 +37,7 @@ public List<ScriptContext<?>> getContexts() {
3637
BooleanFieldScript.CONTEXT,
3738
DateFieldScript.CONTEXT,
3839
DoubleFieldScript.CONTEXT,
40+
GeoPointFieldScript.CONTEXT,
3941
IpFieldScript.CONTEXT,
4042
LongFieldScript.CONTEXT,
4143
StringFieldScript.CONTEXT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.runtimefields.fielddata;
8+
9+
import org.apache.lucene.geo.GeoEncodingUtils;
10+
import org.elasticsearch.common.geo.GeoPoint;
11+
import org.elasticsearch.index.fielddata.MultiGeoPointValues;
12+
import org.elasticsearch.xpack.runtimefields.mapper.GeoPointFieldScript;
13+
14+
import java.util.Arrays;
15+
16+
public final class GeoPointScriptDocValues extends MultiGeoPointValues {
17+
private final GeoPointFieldScript script;
18+
private final GeoPoint point;
19+
private int cursor;
20+
21+
GeoPointScriptDocValues(GeoPointFieldScript script) {
22+
this.script = script;
23+
this.point = new GeoPoint();
24+
}
25+
26+
@Override
27+
public boolean advanceExact(int docId) {
28+
script.runForDoc(docId);
29+
if (script.count() == 0) {
30+
return false;
31+
}
32+
Arrays.sort(script.values(), 0, script.count());
33+
cursor = 0;
34+
return true;
35+
}
36+
37+
@Override
38+
public int docValueCount() {
39+
return script.count();
40+
}
41+
42+
@Override
43+
public GeoPoint nextValue() {
44+
final long value = script.values()[cursor++];
45+
final int lat = (int) (value >>> 32);
46+
final int lon = (int) (value & 0xFFFFFFFF);
47+
return point.reset(GeoEncodingUtils.decodeLatitude(lat), GeoEncodingUtils.decodeLongitude(lon));
48+
}
49+
}

0 commit comments

Comments
 (0)