Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions docs/reference/mapping/types/geo-point.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -123,20 +123,43 @@ The following parameters are accepted by `geo_point` fields:

If `true`, malformed geo-points are ignored. If `false` (default),
malformed geo-points throw an exception and reject the whole document.
A geo-point is considered malformed if its latitude is outside the range
A geo-point is considered malformed if its latitude is outside the range
-90 <= latitude <= 90, or if its longitude is outside the range -180 <= longitude <= 180.
Note that this cannot be set if the `script` parameter is used.

`ignore_z_value`::

If `true` (default) three dimension points will be accepted (stored in source)
but only latitude and longitude values will be indexed; the third dimension is
ignored. If `false`, geo-points containing any more than latitude and longitude
(two dimensions) values throw an exception and reject the whole document.
(two dimensions) values throw an exception and reject the whole document. Note
that this cannot be set if the `script` parameter is used.

<<null-value,`null_value`>>::

Accepts an geopoint value which is substituted for any explicit `null` values.
Defaults to `null`, which means the field is treated as missing.
Defaults to `null`, which means the field is treated as missing. Note that this
cannot be set if the `script` parameter is used.

`on_script_error`::

Defines what to do if the script defined by the `script` parameter
throws an error at indexing time. Accepts `fail` (default), which
will cause the entire document to be rejected, and `continue`, which
will register the field in the document's
<<mapping-ignored-field,`_ignored`>> metadata field and continue
indexing. This parameter can only be set if the `script` field is
also set.

`script`::

If this parameter is set, then the field will index values generated
by this script, rather than reading the values directly from the
source. If a value is set for this field on the input document, then
the document will be rejected with an error.
Scripts are in the same format as their
<<runtime-mapping-fields,runtime equivalent>>, and should emit points
as a pair of (lat, lon) double values.

==== Using geo-points in scripts

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
---
setup:
- do:
indices.create:
index: locations
body:
settings:
number_of_shards: 1
number_of_replicas: 0
mappings:
properties:
location_from_doc_value:
type: geo_point
script:
source: |
emit(doc["location"].lat, doc["location"].lon);
location_from_source:
type: geo_point
script:
source: |
emit(params._source.location.lat, params._source.location.lon);
timestamp:
type: date
location:
type: geo_point
- do:
bulk:
index: locations
refresh: true
body: |
{"index":{}}
{"timestamp": "1998-04-30T14:30:17-05:00", "location" : {"lat": 13.5, "lon" : 34.89}}
{"index":{}}
{"timestamp": "1998-04-30T14:30:53-05:00", "location" : {"lat": -7.9, "lon" : 120.78}}
{"index":{}}
{"timestamp": "1998-04-30T14:31:12-05:00", "location" : {"lat": 45.78, "lon" : -173.45}}
{"index":{}}
{"timestamp": "1998-04-30T14:31:19-05:00", "location" : {"lat": 32.45, "lon" : 45.6}}
{"index":{}}
{"timestamp": "1998-04-30T14:31:22-05:00", "location" : {"lat": -63.24, "lon" : 31.0}}
{"index":{}}
{"timestamp": "1998-04-30T14:31:27-05:00", "location" : {"lat": 0.0, "lon" : 0.0}}


---
"get mapping":
- do:
indices.get_mapping:
index: locations
- match: {locations.mappings.properties.location_from_source.type: geo_point }
- match:
locations.mappings.properties.location_from_source.script.source: |
emit(params._source.location.lat, params._source.location.lon);
- match: {locations.mappings.properties.location_from_source.script.lang: painless }

---
"fetch fields from source":
- do:
search:
index: locations
body:
sort: timestamp
fields: [location, location_from_doc_value, location_from_source]
- match: {hits.total.value: 6}
- match: {hits.hits.0.fields.location.0.type: "Point" }
- match: {hits.hits.0.fields.location.0.coordinates: [34.89, 13.5] }
# calculated from scripts adds annoying extra precision
- match: { hits.hits.0.fields.location_from_doc_value.0.type: "Point" }
- match: { hits.hits.0.fields.location_from_doc_value.0.coordinates: [ 34.889999935403466, 13.499999991618097 ] }
- match: { hits.hits.0.fields.location_from_source.0.type: "Point" }
- match: { hits.hits.0.fields.location_from_source.0.coordinates: [ 34.889999935403466, 13.499999991618097 ] }

---
"exists query":
- do:
search:
index: locations
body:
query:
exists:
field: location_from_source
- match: {hits.total.value: 6}

---
"geo bounding box query":
- do:
search:
index: locations
body:
query:
geo_bounding_box:
location_from_source:
top_left:
lat: 10
lon: -10
bottom_right:
lat: -10
lon: 10
- match: {hits.total.value: 1}

---
"geo shape query":
- do:
search:
index: locations
body:
query:
geo_shape:
location_from_source:
shape:
type: "envelope"
coordinates: [ [ -10, 10 ], [ 10, -10 ] ]
- match: {hits.total.value: 1}

---
"geo distance query":
- do:
search:
index: locations
body:
query:
geo_distance:
distance: "2000km"
location_from_source:
lat: 0
lon: 0
- match: {hits.total.value: 1}

---
"bounds agg":
- do:
search:
index: locations
body:
aggs:
bounds:
geo_bounds:
field: "location"
wrap_longitude: false
bounds_from_doc_value:
geo_bounds:
field: "location_from_doc_value"
wrap_longitude: false
bounds_from_source:
geo_bounds:
field: "location_from_source"
wrap_longitude: false
- match: {hits.total.value: 6}
- match: {aggregations.bounds.bounds.top_left.lat: 45.7799999602139 }
- match: {aggregations.bounds.bounds.top_left.lon: -173.4500000718981 }
- match: {aggregations.bounds.bounds.bottom_right.lat: -63.240000014193356 }
- match: {aggregations.bounds.bounds.bottom_right.lon: 120.77999993227422 }
- match: {aggregations.bounds_from_doc_value.bounds.top_left.lat: 45.7799999602139 }
- match: {aggregations.bounds_from_doc_value.bounds.top_left.lon: -173.4500000718981 }
- match: {aggregations.bounds_from_doc_value.bounds.bottom_right.lat: -63.240000014193356 }
- match: {aggregations.bounds_from_doc_value.bounds.bottom_right.lon: 120.77999993227422 }
- match: {aggregations.bounds_from_source.bounds.top_left.lat: 45.7799999602139 }
- match: {aggregations.bounds_from_source.bounds.top_left.lon: -173.4500000718981 }
- match: {aggregations.bounds_from_source.bounds.bottom_right.lat: -63.240000014193356 }
- match: {aggregations.bounds_from_source.bounds.bottom_right.lon: 120.77999993227422 }

---
"geo_distance sort":
- do:
search:
index: locations
body:
sort:
_geo_distance:
location_from_source:
lat: 0.0
lon: 0.0
- match: {hits.total.value: 6}
- match: {hits.hits.0._source.location.lat: 0.0 }
- match: {hits.hits.0._source.location.lon: 0.0 }
- match: {hits.hits.1._source.location.lat: 13.5 }
- match: {hits.hits.1._source.location.lon: 34.89 }
- match: {hits.hits.2._source.location.lat: 32.45 }
- match: {hits.hits.2._source.location.lon: 45.6 }
- match: {hits.hits.3._source.location.lat: -63.24 }
- match: {hits.hits.3._source.location.lon: 31.0 }

---
"distance_feature query":
- do:
search:
index: locations
body:
query:
bool:
should:
distance_feature:
field: "location"
pivot: "1000km"
origin: [0.0, 0.0]

- match: {hits.total.value: 6}
- match: {hits.hits.0._source.location.lat: 0.0 }
- match: {hits.hits.0._source.location.lon: 0.0 }
- match: {hits.hits.1._source.location.lat: 13.5 }
- match: {hits.hits.1._source.location.lon: 34.89 }
- match: {hits.hits.2._source.location.lat: 32.45 }
- match: {hits.hits.2._source.location.lon: 45.6 }
- match: {hits.hits.3._source.location.lat: -63.24 }
- match: {hits.hits.3._source.location.lon: 31.0 }

Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public final Query termQuery(Object value, SearchExecutionContext context) {
}

@Override
public final ValueFetcher valueFetcher(SearchExecutionContext context, String format) {
public ValueFetcher valueFetcher(SearchExecutionContext context, String format) {
String geoFormat = format != null ? format : GeoJsonGeometryFormat.NAME;

if (parsesArrayValue) {
Expand All @@ -116,13 +116,13 @@ protected Object parseSourceValue(Object value) {

private final Explicit<Boolean> ignoreMalformed;
private final Explicit<Boolean> ignoreZValue;
private final Parser<T> parser;
private final Parser<? extends T> parser;

protected AbstractGeometryFieldMapper(String simpleName, MappedFieldType mappedFieldType,
Map<String, NamedAnalyzer> indexAnalyzers,
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> ignoreZValue,
MultiFields multiFields, CopyTo copyTo,
Parser<T> parser) {
Parser<? extends T> parser) {
super(simpleName, mappedFieldType, indexAnalyzers, multiFields, copyTo, false, null);
this.ignoreMalformed = ignoreMalformed;
this.ignoreZValue = ignoreZValue;
Expand All @@ -132,10 +132,24 @@ protected AbstractGeometryFieldMapper(String simpleName, MappedFieldType mappedF
protected AbstractGeometryFieldMapper(String simpleName, MappedFieldType mappedFieldType,
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> ignoreZValue,
MultiFields multiFields, CopyTo copyTo,
Parser<T> parser) {
Parser<? extends T> parser) {
this(simpleName, mappedFieldType, Collections.emptyMap(), ignoreMalformed, ignoreZValue, multiFields, copyTo, parser);
}

protected AbstractGeometryFieldMapper(
String simpleName,
MappedFieldType mappedFieldType,
MultiFields multiFields,
CopyTo copyTo,
Parser<? extends T> parser,
String onScriptError
) {
super(simpleName, mappedFieldType, Collections.emptyMap(), multiFields, copyTo, true, onScriptError);
this.ignoreMalformed = new Explicit<>(false, true);
this.ignoreZValue = new Explicit<>(false, true);
this.parser = parser;
}

@Override
public AbstractGeometryFieldType fieldType() {
return (AbstractGeometryFieldType) mappedFieldType;
Expand All @@ -155,16 +169,19 @@ protected void parseCreateField(ParseContext context) throws IOException {

@Override
public final void parse(ParseContext context) throws IOException {
parser.parse(context.parser(), v -> index(context, v), e -> {
if (ignoreMalformed()) {
context.addIgnoredField(fieldType().name());
} else {
throw new MapperParsingException(
"Failed to parse field [" + fieldType().name() + "] of type [" + contentType() + "]",
e
);
}
});
if (hasScript) {
throw new MapperParsingException("failed to parse field [" + fieldType().name() + "] of type + " + contentType() + "]",
new IllegalArgumentException("Cannot index data directly into a field with a [script] parameter"));
Copy link
Member

Choose a reason for hiding this comment

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

grr I forgot about the mappers that override parse when I added this to the base class. Is it worth extracting the check to a common method so that it can be called from here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I want to rework parse/parseCreateField anyway, as it's very confusing that we have these two methods with the same signature that do almost exactly the same thing. Can we consider this a band-aid until we tidy up in a followup?

Copy link
Member

Choose a reason for hiding this comment

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

sure, anyways the test is unified and tests the error message, so we ensure that it is always the same one

}
parser.parse(context.parser(), v -> index(context, v), e -> {
if (ignoreMalformed()) {
context.addIgnoredField(fieldType().name());
} else {
throw new MapperParsingException(
"failed to parse field [" + fieldType().name() + "] of type [" + contentType() + "]", e
);
}
});
}

public boolean ignoreMalformed() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,18 @@ public static Parameter<ParsedPoint> nullValueParam(Function<FieldMapper, Parsed
protected AbstractPointGeometryFieldMapper(String simpleName, MappedFieldType mappedFieldType,
MultiFields multiFields, Explicit<Boolean> ignoreMalformed,
Explicit<Boolean> ignoreZValue, ParsedPoint nullValue, CopyTo copyTo,
Parser<T> parser) {
Parser<? extends T> parser) {
super(simpleName, mappedFieldType, ignoreMalformed, ignoreZValue, multiFields, copyTo, parser);
this.nullValue = nullValue;
}

protected AbstractPointGeometryFieldMapper(String simpleName, MappedFieldType mappedFieldType,
MultiFields multiFields, CopyTo copyTo,
Parser<? extends T> parser, String onScriptError) {
super(simpleName, mappedFieldType, multiFields, copyTo, parser, onScriptError);
this.nullValue = null;
}

@Override
public final boolean parsesArrayValue() {
return true;
Expand Down
Loading