Skip to content

Commit 60e08fd

Browse files
authored
Add support for GeoShape to the scripting fields API (#81617)
This change adds infrastructure for GeoShape making it accessible via the new scripting fields API. This does not add any methods outside of get at this point in time since it needs additional thought/discussion on what makes sense similar to GeoPoints. Note that because GeoShape does not support XContent this is just a skeleton that currently supports getScriptDocValues.
1 parent c6786dc commit 60e08fd

File tree

10 files changed

+212
-67
lines changed

10 files changed

+212
-67
lines changed

docs/changelog/81617.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 81617
2+
summary: Add support for `GeoShape` to the scripting fields API
3+
area: Infra/Scripting
4+
type: enhancement
5+
issues: []

x-pack/plugin/spatial/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ esplugin {
55
name 'spatial'
66
description 'A plugin for Basic Spatial features'
77
classname 'org.elasticsearch.xpack.spatial.SpatialPlugin'
8-
extendedPlugins = ['x-pack-core', 'legacy-geo']
8+
extendedPlugins = ['x-pack-core', 'legacy-geo', 'lang-painless']
99
}
1010

1111
dependencies {
1212
compileOnly project(path: ':modules:legacy-geo')
13+
compileOnly project(':modules:lang-painless:spi')
1314
compileOnly project(path: xpackModule('core'))
1415
testImplementation(testArtifact(project(xpackModule('core'))))
1516
testImplementation project(path: xpackModule('vector-tile'))
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.spatial;
9+
10+
import org.elasticsearch.painless.spi.PainlessExtension;
11+
import org.elasticsearch.painless.spi.Whitelist;
12+
import org.elasticsearch.painless.spi.WhitelistLoader;
13+
import org.elasticsearch.script.ScriptContext;
14+
import org.elasticsearch.script.ScriptModule;
15+
16+
import java.util.HashMap;
17+
import java.util.List;
18+
import java.util.Map;
19+
20+
public class SpatialPainlessExtension implements PainlessExtension {
21+
22+
private static final List<Whitelist> WHITELISTS = List.of(
23+
WhitelistLoader.loadFromResourceFiles(
24+
SpatialPainlessExtension.class,
25+
"org.elasticsearch.xpack.spatial.index.fielddata.txt",
26+
"org.elasticsearch.xpack.spatial.index.fielddata.plain.txt",
27+
"org.elasticsearch.xpack.spatial.index.mapper.txt"
28+
)
29+
);
30+
31+
@Override
32+
public Map<ScriptContext<?>, List<Whitelist>> getContextWhitelists() {
33+
Map<ScriptContext<?>, List<Whitelist>> contextWhitelistMap = new HashMap<>();
34+
35+
for (ScriptContext<?> context : ScriptModule.CORE_CONTEXTS.values()) {
36+
contextWhitelistMap.put(context, WHITELISTS);
37+
}
38+
39+
return contextWhitelistMap;
40+
}
41+
}

x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/AbstractAtomicGeoShapeShapeFieldData.java

Lines changed: 11 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111
import org.elasticsearch.common.geo.GeoBoundingBox;
1212
import org.elasticsearch.common.geo.GeoPoint;
1313
import org.elasticsearch.index.fielddata.ScriptDocValues;
14+
import org.elasticsearch.index.fielddata.ScriptDocValues.GeometrySupplier;
1415
import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
1516
import org.elasticsearch.script.field.DocValuesField;
1617
import org.elasticsearch.script.field.ToScriptField;
1718
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
1819
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues.GeoShapeValue;
1920
import org.elasticsearch.xpack.spatial.index.fielddata.LeafGeoShapeFieldData;
2021

21-
import java.io.IOException;
2222
import java.util.Collection;
2323
import java.util.Collections;
2424

@@ -66,71 +66,23 @@ public GeoShapeValues getGeoShapeValues() {
6666
};
6767
}
6868

69-
public static final class GeoShapeSupplier implements ScriptDocValues.GeometrySupplier<GeoShapeValue> {
70-
71-
private final GeoShapeValues in;
72-
private final GeoPoint centroid = new GeoPoint();
73-
private final GeoBoundingBox boundingBox = new GeoBoundingBox(new GeoPoint(), new GeoPoint());
74-
private GeoShapeValues.GeoShapeValue value;
75-
76-
public GeoShapeSupplier(GeoShapeValues in) {
77-
this.in = in;
78-
}
79-
80-
@Override
81-
public void setNextDocId(int docId) throws IOException {
82-
if (in.advanceExact(docId)) {
83-
value = in.value();
84-
centroid.reset(value.lat(), value.lon());
85-
boundingBox.topLeft().reset(value.boundingBox().maxY(), value.boundingBox().minX());
86-
boundingBox.bottomRight().reset(value.boundingBox().minY(), value.boundingBox().maxX());
87-
} else {
88-
value = null;
89-
}
90-
}
91-
92-
@Override
93-
public GeoShapeValue getInternal(int index) {
94-
throw new UnsupportedOperationException();
95-
}
96-
97-
public GeoShapeValue getInternal() {
98-
return value;
99-
}
100-
101-
@Override
102-
public int size() {
103-
return value == null ? 0 : 1;
104-
}
105-
106-
@Override
107-
public GeoPoint getInternalCentroid() {
108-
return centroid;
109-
}
110-
111-
@Override
112-
public GeoBoundingBox getInternalBoundingBox() {
113-
return boundingBox;
114-
}
115-
}
116-
11769
public static final class GeoShapeScriptValues extends ScriptDocValues.Geometry<GeoShapeValue> {
11870

119-
private final GeoShapeSupplier gsSupplier;
71+
private final GeometrySupplier<GeoShapeValue> gsSupplier;
12072

121-
public GeoShapeScriptValues(GeoShapeSupplier supplier) {
73+
public GeoShapeScriptValues(GeometrySupplier<GeoShapeValue> supplier) {
12274
super(supplier);
12375
this.gsSupplier = supplier;
12476
}
12577

12678
@Override
12779
public int getDimensionalType() {
128-
return gsSupplier.getInternal() == null ? -1 : gsSupplier.getInternal().dimensionalShapeType().ordinal();
80+
return gsSupplier.getInternal(0) == null ? -1 : gsSupplier.getInternal(0).dimensionalShapeType().ordinal();
12981
}
13082

13183
@Override
13284
public GeoPoint getCentroid() {
133-
return gsSupplier.getInternal() == null ? null : gsSupplier.getInternalCentroid();
85+
return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalCentroid();
13486
}
13587

13688
@Override
@@ -145,12 +97,16 @@ public double getMercatorHeight() {
14597

14698
@Override
14799
public GeoBoundingBox getBoundingBox() {
148-
return gsSupplier.getInternal() == null ? null : gsSupplier.getInternalBoundingBox();
100+
return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalBoundingBox();
149101
}
150102

151103
@Override
152104
public GeoShapeValues.GeoShapeValue get(int index) {
153-
return gsSupplier.getInternal();
105+
return gsSupplier.getInternal(0);
106+
}
107+
108+
public GeoShapeValues.GeoShapeValue getValue() {
109+
return gsSupplier.getInternal(0);
154110
}
155111

156112
@Override

x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java

Lines changed: 118 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
import org.apache.lucene.search.Query;
1616
import org.elasticsearch.Version;
1717
import org.elasticsearch.common.Explicit;
18+
import org.elasticsearch.common.geo.GeoBoundingBox;
1819
import org.elasticsearch.common.geo.GeoFormatterFactory;
20+
import org.elasticsearch.common.geo.GeoPoint;
1921
import org.elasticsearch.common.geo.GeoShapeUtils;
2022
import org.elasticsearch.common.geo.GeometryParser;
2123
import org.elasticsearch.common.geo.Orientation;
@@ -24,6 +26,7 @@
2426
import org.elasticsearch.common.logging.DeprecationLogger;
2527
import org.elasticsearch.geometry.Geometry;
2628
import org.elasticsearch.index.fielddata.IndexFieldData;
29+
import org.elasticsearch.index.fielddata.ScriptDocValues;
2730
import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper;
2831
import org.elasticsearch.index.mapper.DocumentParserContext;
2932
import org.elasticsearch.index.mapper.FieldMapper;
@@ -39,16 +42,19 @@
3942
import org.elasticsearch.index.query.QueryShardException;
4043
import org.elasticsearch.index.query.SearchExecutionContext;
4144
import org.elasticsearch.legacygeo.mapper.LegacyGeoShapeFieldMapper;
42-
import org.elasticsearch.script.field.DelegateDocValuesField;
45+
import org.elasticsearch.script.field.DocValuesField;
4346
import org.elasticsearch.search.lookup.SearchLookup;
47+
import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues;
4448
import org.elasticsearch.xpack.spatial.index.fielddata.plain.AbstractAtomicGeoShapeShapeFieldData;
4549
import org.elasticsearch.xpack.spatial.index.fielddata.plain.AbstractLatLonShapeIndexFieldData;
4650
import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType;
4751

4852
import java.io.IOException;
4953
import java.util.Arrays;
54+
import java.util.Iterator;
5055
import java.util.List;
5156
import java.util.Map;
57+
import java.util.NoSuchElementException;
5258
import java.util.Set;
5359
import java.util.function.Function;
5460
import java.util.function.Supplier;
@@ -175,16 +181,7 @@ public GeoShapeWithDocValuesFieldType(
175181

176182
public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, Supplier<SearchLookup> searchLookup) {
177183
failIfNoDocValues();
178-
return new AbstractLatLonShapeIndexFieldData.Builder(
179-
name(),
180-
GeoShapeValuesSourceType.instance(),
181-
(dv, n) -> new DelegateDocValuesField(
182-
new AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues(
183-
new AbstractAtomicGeoShapeShapeFieldData.GeoShapeSupplier(dv)
184-
),
185-
n
186-
)
187-
);
184+
return new AbstractLatLonShapeIndexFieldData.Builder(name(), GeoShapeValuesSourceType.instance(), GeoShapeDocValuesField::new);
188185
}
189186

190187
@Override
@@ -344,4 +341,114 @@ protected void checkIncomingMergeType(FieldMapper mergeWith) {
344341
}
345342
super.checkIncomingMergeType(mergeWith);
346343
}
344+
345+
public static class GeoShapeDocValuesField
346+
implements
347+
DocValuesField<GeoShapeValues.GeoShapeValue>,
348+
ScriptDocValues.GeometrySupplier<GeoShapeValues.GeoShapeValue> {
349+
350+
private final GeoShapeValues in;
351+
protected final String name;
352+
353+
private GeoShapeValues.GeoShapeValue value;
354+
355+
// maintain bwc by making bounding box and centroid available to GeoShapeValues (ScriptDocValues)
356+
private final GeoPoint centroid = new GeoPoint();
357+
private final GeoBoundingBox boundingBox = new GeoBoundingBox(new GeoPoint(), new GeoPoint());
358+
private AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues geoShapeScriptValues;
359+
360+
public GeoShapeDocValuesField(GeoShapeValues in, String name) {
361+
this.in = in;
362+
this.name = name;
363+
}
364+
365+
@Override
366+
public void setNextDocId(int docId) throws IOException {
367+
if (in.advanceExact(docId)) {
368+
value = in.value();
369+
centroid.reset(value.lat(), value.lon());
370+
boundingBox.topLeft().reset(value.boundingBox().maxY(), value.boundingBox().minX());
371+
boundingBox.bottomRight().reset(value.boundingBox().minY(), value.boundingBox().maxX());
372+
} else {
373+
value = null;
374+
}
375+
}
376+
377+
@Override
378+
public ScriptDocValues<GeoShapeValues.GeoShapeValue> getScriptDocValues() {
379+
if (geoShapeScriptValues == null) {
380+
geoShapeScriptValues = new AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues(this);
381+
}
382+
383+
return geoShapeScriptValues;
384+
}
385+
386+
@Override
387+
public GeoShapeValues.GeoShapeValue getInternal(int index) {
388+
if (index != 0) {
389+
throw new UnsupportedOperationException();
390+
}
391+
392+
return value;
393+
}
394+
395+
// maintain bwc by making centroid available to GeoShapeValues (ScriptDocValues)
396+
@Override
397+
public GeoPoint getInternalCentroid() {
398+
return centroid;
399+
}
400+
401+
// maintain bwc by making centroid available to GeoShapeValues (ScriptDocValues)
402+
@Override
403+
public GeoBoundingBox getInternalBoundingBox() {
404+
return boundingBox;
405+
}
406+
407+
@Override
408+
public String getName() {
409+
return name;
410+
}
411+
412+
@Override
413+
public boolean isEmpty() {
414+
return value == null;
415+
}
416+
417+
@Override
418+
public int size() {
419+
return value == null ? 0 : 1;
420+
}
421+
422+
public GeoShapeValues.GeoShapeValue get(GeoShapeValues.GeoShapeValue defaultValue) {
423+
return get(0, defaultValue);
424+
}
425+
426+
public GeoShapeValues.GeoShapeValue get(int index, GeoShapeValues.GeoShapeValue defaultValue) {
427+
if (isEmpty() || index != 0) {
428+
return defaultValue;
429+
}
430+
431+
return value;
432+
}
433+
434+
@Override
435+
public Iterator<GeoShapeValues.GeoShapeValue> iterator() {
436+
return new Iterator<GeoShapeValues.GeoShapeValue>() {
437+
private int index = 0;
438+
439+
@Override
440+
public boolean hasNext() {
441+
return index < size();
442+
}
443+
444+
@Override
445+
public GeoShapeValues.GeoShapeValue next() {
446+
if (hasNext() == false) {
447+
throw new NoSuchElementException();
448+
}
449+
return value;
450+
}
451+
};
452+
}
453+
}
347454
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.elasticsearch.xpack.spatial.SpatialPainlessExtension
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class org.elasticsearch.xpack.spatial.index.fielddata.plain.AbstractAtomicGeoShapeShapeFieldData$GeoShapeScriptValues {
2+
GeoShapeValues.GeoShapeValue get(int)
3+
GeoShapeValues.GeoShapeValue getValue()
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues$GeoShapeValue {
2+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class org.elasticsearch.xpack.spatial.index.mapper.GeoShapeWithDocValuesFieldMapper$GeoShapeDocValuesField {
2+
GeoShapeValues.GeoShapeValue get(GeoShapeValues.GeoShapeValue)
3+
GeoShapeValues.GeoShapeValue get(int, GeoShapeValues.GeoShapeValue)
4+
}

x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/spatial/70_script_doc_values.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,30 @@ setup:
9292

9393
- match: { error.root_cause.0.reason: "cannot write xcontent for geo_shape doc value" }
9494

95+
- do:
96+
catch: /illegal_argument_exception/
97+
search:
98+
rest_total_hits_as_int: true
99+
body:
100+
script_fields:
101+
type:
102+
script:
103+
source: "field('geo_shape').get(null)"
104+
105+
- match: { error.root_cause.0.reason: "cannot write xcontent for geo_shape doc value" }
106+
107+
- do:
108+
catch: /illegal_argument_exception/
109+
search:
110+
rest_total_hits_as_int: true
111+
body:
112+
script_fields:
113+
type:
114+
script:
115+
source: "/* avoid yaml stash */ $('geo_shape', null)"
116+
117+
- match: { error.root_cause.0.reason: "cannot write xcontent for geo_shape doc value" }
118+
95119
---
96120
"diagonal length":
97121
- do:

0 commit comments

Comments
 (0)