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,83 @@
/*
* 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;
import java.util.Map;

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 Map<?, ?> toObject(Geometry geometry) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe return Object here as well for consistency. I don't think the fact that it returns Map is significant here.

Copy link
Contributor Author

@jtibshirani jtibshirani Jul 22, 2020

Choose a reason for hiding this comment

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

I sometimes like using covariance like this to clarify that a subclass always returns a specific type. I don't feel strongly about it though, happy to change it. We can always change it back later if we find it helpful for unit testing, etc. to have the exact type.

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 toObject(ParsedFormat geometry);
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if somebody can come up with a better name here :) maybe to toXContentObject() or toXConentValue() or something like this. I feel like toObject is misleadingly generic 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 previously had a method called toXContentMap that @talevy and @nik9000 found confusing, because we often refer to xContent and maps as distinct representations. Perhaps a name like toXContentAsObject could work?

Copy link
Contributor

Choose a reason for hiding this comment

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

haha, oi. since there are javadocs explaining it now, I drop my naming argument. I'm good with whatever sounds good to you!

Copy link
Contributor

Choose a reason for hiding this comment

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

I cannot say I love toXContentAsObject but I like it the best comparing to all other versions.

}
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 toObject(Geometry geometry) {
return wellKnownTextParser.toWKT(geometry);
}
}
Loading