Skip to content
1 change: 1 addition & 0 deletions docs/reference/mapping/types.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ string:: <<text,`text`>>, <<keyword,`keyword`>> and <<wildcard,`wildcard
<<nested>>:: `nested` for arrays of JSON objects

[float]
[[spatial_datatypes]]
=== Spatial data types

<<geo-point>>:: `geo_point` for lat/lon points
Expand Down
10 changes: 7 additions & 3 deletions docs/reference/search/search-fields.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,13 @@ POST twitter/_search

<1> Both full field names and wildcard patterns are accepted.
<2> Using object notation, you can pass a `format` parameter to apply a custom
format for the field's values. This is currently supported for
<<date,`date` fields>> and <<date_nanos, `date_nanos` fields>>, which
accept a <<mapping-date-format,date format>>.
format for the field's values. The date fields
<<date,`date`>> and <<date_nanos, `date_nanos`>> accept a
<<mapping-date-format,date format>>. <<spatial_datatypes, Spatial fields>>
accept either `geojson` for http://www.geojson.org[GeoJSON] (the default)
or `wkt` for
https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry[Well Known Text].
Other field types do not support the `format` parameter.

The values are returned as a flat list in the `fields` section in each hit:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,26 @@ setup:
field: location

- match: {hits.total: 1}

---
"Test retrieve geo_shape field":
- do:
search:
index: test
body:
fields: [location]
_source: false

- match: { hits.hits.0.fields.location.0.type: "Point" }
- match: { hits.hits.0.fields.location.0.coordinates: [1.0, 1.0] }

- do:
search:
index: test
body:
fields:
- field: location
format: wkt
_source: false

- match: { hits.hits.0.fields.location.0: "POINT (1.0 1.0)" }
Original file line number Diff line number Diff line change
Expand Up @@ -609,5 +609,4 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
return builder;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.common.geo;

import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.geometry.Geometry;

import java.io.IOException;
import java.io.UncheckedIOException;

public class GeoJsonGeometryFormat implements GeometryFormat<Geometry> {
public static final String NAME = "geojson";

private final GeoJson geoJsonParser;

public GeoJsonGeometryFormat(GeoJson geoJsonParser) {
this.geoJsonParser = geoJsonParser;
}

@Override
public String name() {
return NAME;
}

@Override
public Geometry fromXContent(XContentParser parser) throws IOException {
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
return null;
}
return geoJsonParser.fromXContent(parser);
}

@Override
public XContentBuilder toXContent(Geometry geometry, XContentBuilder builder, ToXContent.Params params) throws IOException {
if (geometry != null) {
return GeoJson.toXContent(geometry, builder, params);
} else {
return builder.nullValue();
}
}

@Override
public Object toXContentAsObject(Geometry geometry) {
try {
XContentBuilder builder = XContentFactory.jsonBuilder();
GeoJson.toXContent(geometry, builder, ToXContent.EMPTY_PARAMS);
StreamInput input = BytesReference.bytes(builder).streamInput();

try (XContentParser parser = XContentType.JSON.xContent()
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, input)) {
return parser.map();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
*/
public interface GeometryFormat<ParsedFormat> {

/**
* The name of the format, for example 'wkt'.
*/
String name();

/**
* Parser JSON representation of a geometry
*/
Expand All @@ -41,4 +46,10 @@ public interface GeometryFormat<ParsedFormat> {
*/
XContentBuilder toXContent(ParsedFormat geometry, XContentBuilder builder, ToXContent.Params params) throws IOException;

/**
* Serializes the geometry into a standard Java object.
*
* For example, the GeoJson format returns the geometry as a map, while WKT returns a string.
*/
Object toXContentAsObject(ParsedFormat geometry);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,13 @@
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.support.MapXContentParser;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.GeometryCollection;
import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.utils.StandardValidator;
import org.elasticsearch.geometry.utils.GeometryValidator;
import org.elasticsearch.geometry.utils.StandardValidator;
import org.elasticsearch.geometry.utils.WellKnownText;

import java.io.IOException;
Expand Down Expand Up @@ -66,59 +64,31 @@ public Geometry parse(XContentParser parser) throws IOException, ParseException
/**
* Returns a geometry format object that can parse and then serialize the object back to the same format.
*/
public GeometryFormat<Geometry> geometryFormat(XContentParser parser) {
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
return new GeometryFormat<Geometry>() {
@Override
public Geometry fromXContent(XContentParser parser) throws IOException {
return null;
}

@Override
public XContentBuilder toXContent(Geometry geometry, XContentBuilder builder, ToXContent.Params params) throws IOException {
if (geometry != null) {
// We don't know the format of the original geometry - so going with default
return GeoJson.toXContent(geometry, builder, params);
} else {
return builder.nullValue();
}
}
};
} else if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
return new GeometryFormat<Geometry>() {
@Override
public Geometry fromXContent(XContentParser parser) throws IOException {
return geoJsonParser.fromXContent(parser);
}
public GeometryFormat<Geometry> geometryFormat(String format) {
if (format.equals(GeoJsonGeometryFormat.NAME)) {
return new GeoJsonGeometryFormat(geoJsonParser);
} else if (format.equals(WKTGeometryFormat.NAME)) {
return new WKTGeometryFormat(wellKnownTextParser);
} else {
throw new IllegalArgumentException("Unrecognized geometry format [" + format + "].");
}
}

@Override
public XContentBuilder toXContent(Geometry geometry, XContentBuilder builder, ToXContent.Params params) throws IOException {
if (geometry != null) {
return GeoJson.toXContent(geometry, builder, params);
} else {
return builder.nullValue();
}
}
};
/**
* Returns a geometry format object that can parse and then serialize the object back to the same format.
* This method automatically recognizes the format by examining the provided {@link XContentParser}.
*/
public GeometryFormat<Geometry> geometryFormat(XContentParser parser) {
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
return new GeoJsonGeometryFormat(geoJsonParser);
} else if (parser.currentToken() == XContentParser.Token.VALUE_STRING) {
return new GeometryFormat<Geometry>() {
@Override
public Geometry fromXContent(XContentParser parser) throws IOException, ParseException {
return wellKnownTextParser.fromWKT(parser.text());
}

@Override
public XContentBuilder toXContent(Geometry geometry, XContentBuilder builder, ToXContent.Params params) throws IOException {
if (geometry != null) {
return builder.value(wellKnownTextParser.toWKT(geometry));
} else {
return builder.nullValue();
}
}
};

return new WKTGeometryFormat(wellKnownTextParser);
} else if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
// We don't know the format of the original geometry - so going with default
return new GeoJsonGeometryFormat(geoJsonParser);
} else {
throw new ElasticsearchParseException("shape must be an object consisting of type and coordinates");
}
throw new ElasticsearchParseException("shape must be an object consisting of type and coordinates");
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.common.geo;

import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.geometry.Geometry;
import org.elasticsearch.geometry.utils.WellKnownText;

import java.io.IOException;
import java.text.ParseException;

public class WKTGeometryFormat implements GeometryFormat<Geometry> {
public static final String NAME = "wkt";

private final WellKnownText wellKnownTextParser;

public WKTGeometryFormat(WellKnownText wellKnownTextParser) {
this.wellKnownTextParser = wellKnownTextParser;
}

@Override
public String name() {
return NAME;
}

@Override
public Geometry fromXContent(XContentParser parser) throws IOException, ParseException {
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
return null;
}
return wellKnownTextParser.fromWKT(parser.text());
}

@Override
public XContentBuilder toXContent(Geometry geometry, XContentBuilder builder, ToXContent.Params params) throws IOException {
if (geometry != null) {
return builder.value(wellKnownTextParser.toWKT(geometry));
} else {
return builder.nullValue();
}
}

@Override
public String toXContentAsObject(Geometry geometry) {
return wellKnownTextParser.toWKT(geometry);
}
}
Loading