[GEO] Add optional left/right parameter to GeoJSON
This feature adds an optional orientation parameter to the GeoJSON document and geo_shape mapping enabling users to explicitly define how they want Elasticsearch to interpret vertex ordering. The default uses the right-hand rule (counterclockwise for outer ring, clockwise for inner ring) complying with OGC Simple Feature Access standards. The parameter can be explicitly specified for an entire index using the geo_shape mapping by adding "orientation":{"left"|"right"|"cw"|"ccw"|"clockwise"|"counterclockwise"} and/or overridden on each insert by adding the same parameter to the GeoJSON document. closes #8764
This commit is contained in:
parent
fb6c3b7c29
commit
77a7ef28b3
|
@ -11,6 +11,7 @@ You can query documents using this type using
|
||||||
or <<query-dsl-geo-shape-query,geo_shape
|
or <<query-dsl-geo-shape-query,geo_shape
|
||||||
Query>>.
|
Query>>.
|
||||||
|
|
||||||
|
[[geo-shape-mapping-options]]
|
||||||
[float]
|
[float]
|
||||||
==== Mapping Options
|
==== Mapping Options
|
||||||
|
|
||||||
|
@ -46,6 +47,17 @@ via the mapping API even if you use the precision parameter.
|
||||||
|`distance_error_pct` |Used as a hint to the PrefixTree about how
|
|`distance_error_pct` |Used as a hint to the PrefixTree about how
|
||||||
precise it should be. Defaults to 0.025 (2.5%) with 0.5 as the maximum
|
precise it should be. Defaults to 0.025 (2.5%) with 0.5 as the maximum
|
||||||
supported value.
|
supported value.
|
||||||
|
|
||||||
|
|`orientation` |Optionally define how to interpret vertex order for
|
||||||
|
polygons / multipolygons. This parameter defines one of two coordinate
|
||||||
|
system rules (Right-hand or Left-hand) each of which can be specified in three
|
||||||
|
different ways. 1. Right-hand rule (default): `right`, `ccw`, `counterclockwise`,
|
||||||
|
2. Left-hand rule: `left`, `cw`, `clockwise`. The default orientation
|
||||||
|
(`counterclockwise`) complies with the OGC standard which defines
|
||||||
|
outer ring vertices in counterclockwise order with inner ring(s) vertices (holes)
|
||||||
|
in clockwise order. Setting this parameter in the geo_shape mapping explicitly
|
||||||
|
sets vertex order for the coordinate list of a geo_shape field but can be
|
||||||
|
overridden in each individual GeoJSON document.
|
||||||
|=======================================================================
|
|=======================================================================
|
||||||
|
|
||||||
[float]
|
[float]
|
||||||
|
@ -246,7 +258,7 @@ defines the following vertex ordering:
|
||||||
|
|
||||||
For polygons that do not cross the dateline, vertex order will not matter in
|
For polygons that do not cross the dateline, vertex order will not matter in
|
||||||
Elasticsearch. For polygons that do cross the dateline, Elasticsearch requires
|
Elasticsearch. For polygons that do cross the dateline, Elasticsearch requires
|
||||||
vertex orderinging comply with the OGC specification. Otherwise, an unintended polygon
|
vertex ordering to comply with the OGC specification. Otherwise, an unintended polygon
|
||||||
may be created and unexpected query/filter results will be returned.
|
may be created and unexpected query/filter results will be returned.
|
||||||
|
|
||||||
The following provides an example of an ambiguous polygon. Elasticsearch will apply
|
The following provides an example of an ambiguous polygon. Elasticsearch will apply
|
||||||
|
@ -265,6 +277,24 @@ OGC standards to eliminate ambiguity resulting in a polygon that crosses the dat
|
||||||
}
|
}
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
|
||||||
|
An `orientation` parameter can be defined when setting the geo_shape mapping (see <<geo-shape-mapping-options>>). This will define vertex
|
||||||
|
order for the coordinate list on the mapped geo_shape field. It can also be overridden on each document. The following is an example for
|
||||||
|
overriding the orientation on a document:
|
||||||
|
|
||||||
|
[source,js]
|
||||||
|
--------------------------------------------------
|
||||||
|
{
|
||||||
|
"location" : {
|
||||||
|
"type" : "polygon",
|
||||||
|
"orientation" : "clockwise",
|
||||||
|
"coordinates" : [
|
||||||
|
[ [-177.0, 10.0], [176.0, 15.0], [172.0, 0.0], [176.0, -15.0], [-177.0, -10.0], [-177.0, 10.0] ],
|
||||||
|
[ [178.2, 8.2], [-178.8, 8.2], [-180.8, -8.8], [178.2, 8.8] ]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
[float]
|
[float]
|
||||||
===== http://www.geojson.org/geojson-spec.html#id5[MultiPoint]
|
===== http://www.geojson.org/geojson-spec.html#id5[MultiPoint]
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,10 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
|
||||||
// List of linear rings defining the holes of the polygon
|
// List of linear rings defining the holes of the polygon
|
||||||
protected final ArrayList<BaseLineStringBuilder<?>> holes = new ArrayList<>();
|
protected final ArrayList<BaseLineStringBuilder<?>> holes = new ArrayList<>();
|
||||||
|
|
||||||
|
public BasePolygonBuilder(Orientation orientation) {
|
||||||
|
super(orientation);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private E thisRef() {
|
private E thisRef() {
|
||||||
return (E)this;
|
return (E)this;
|
||||||
|
@ -125,9 +129,9 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
|
||||||
|
|
||||||
Edge[] edges = new Edge[numEdges];
|
Edge[] edges = new Edge[numEdges];
|
||||||
Edge[] holeComponents = new Edge[holes.size()];
|
Edge[] holeComponents = new Edge[holes.size()];
|
||||||
int offset = createEdges(0, false, shell, null, edges, 0);
|
int offset = createEdges(0, orientation.getValue(), shell, null, edges, 0);
|
||||||
for (int i = 0; i < holes.size(); i++) {
|
for (int i = 0; i < holes.size(); i++) {
|
||||||
int length = createEdges(i+1, true, shell, this.holes.get(i), edges, offset);
|
int length = createEdges(i+1, orientation.getValue(), shell, this.holes.get(i), edges, offset);
|
||||||
holeComponents[i] = edges[offset];
|
holeComponents[i] = edges[offset];
|
||||||
offset += length;
|
offset += length;
|
||||||
}
|
}
|
||||||
|
@ -453,11 +457,14 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int createEdges(int component, boolean direction, BaseLineStringBuilder<?> shell, BaseLineStringBuilder<?> hole,
|
private static int createEdges(int component, boolean orientation, BaseLineStringBuilder<?> shell,
|
||||||
|
BaseLineStringBuilder<?> hole,
|
||||||
Edge[] edges, int offset) {
|
Edge[] edges, int offset) {
|
||||||
|
// inner rings (holes) have an opposite direction than the outer rings
|
||||||
|
boolean direction = (component != 0) ? !orientation : orientation;
|
||||||
// set the points array accordingly (shell or hole)
|
// set the points array accordingly (shell or hole)
|
||||||
Coordinate[] points = (hole != null) ? hole.coordinates(false) : shell.coordinates(false);
|
Coordinate[] points = (hole != null) ? hole.coordinates(false) : shell.coordinates(false);
|
||||||
Edge.ring(component, direction, shell, points, 0, edges, offset, points.length-1);
|
Edge.ring(component, direction, orientation, shell, points, 0, edges, offset, points.length-1);
|
||||||
return points.length-1;
|
return points.length-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,14 @@ public class EnvelopeBuilder extends ShapeBuilder {
|
||||||
protected Coordinate topLeft;
|
protected Coordinate topLeft;
|
||||||
protected Coordinate bottomRight;
|
protected Coordinate bottomRight;
|
||||||
|
|
||||||
|
public EnvelopeBuilder() {
|
||||||
|
this(Orientation.RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnvelopeBuilder(Orientation orientation) {
|
||||||
|
super(orientation);
|
||||||
|
}
|
||||||
|
|
||||||
public EnvelopeBuilder topLeft(Coordinate topLeft) {
|
public EnvelopeBuilder topLeft(Coordinate topLeft) {
|
||||||
this.topLeft = topLeft;
|
this.topLeft = topLeft;
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -33,6 +33,14 @@ public class GeometryCollectionBuilder extends ShapeBuilder {
|
||||||
|
|
||||||
protected final ArrayList<ShapeBuilder> shapes = new ArrayList<>();
|
protected final ArrayList<ShapeBuilder> shapes = new ArrayList<>();
|
||||||
|
|
||||||
|
public GeometryCollectionBuilder() {
|
||||||
|
this(Orientation.RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeometryCollectionBuilder(Orientation orientation) {
|
||||||
|
super(orientation);
|
||||||
|
}
|
||||||
|
|
||||||
public GeometryCollectionBuilder shape(ShapeBuilder shape) {
|
public GeometryCollectionBuilder shape(ShapeBuilder shape) {
|
||||||
this.shapes.add(shape);
|
this.shapes.add(shape);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -35,13 +35,25 @@ public class MultiPolygonBuilder extends ShapeBuilder {
|
||||||
|
|
||||||
protected final ArrayList<BasePolygonBuilder<?>> polygons = new ArrayList<>();
|
protected final ArrayList<BasePolygonBuilder<?>> polygons = new ArrayList<>();
|
||||||
|
|
||||||
|
public MultiPolygonBuilder() {
|
||||||
|
this(Orientation.RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiPolygonBuilder(Orientation orientation) {
|
||||||
|
super(orientation);
|
||||||
|
}
|
||||||
|
|
||||||
public MultiPolygonBuilder polygon(BasePolygonBuilder<?> polygon) {
|
public MultiPolygonBuilder polygon(BasePolygonBuilder<?> polygon) {
|
||||||
this.polygons.add(polygon);
|
this.polygons.add(polygon);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InternalPolygonBuilder polygon() {
|
public InternalPolygonBuilder polygon() {
|
||||||
InternalPolygonBuilder polygon = new InternalPolygonBuilder(this);
|
return polygon(Orientation.RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InternalPolygonBuilder polygon(Orientation orientation) {
|
||||||
|
InternalPolygonBuilder polygon = new InternalPolygonBuilder(this, orientation);
|
||||||
this.polygon(polygon);
|
this.polygon(polygon);
|
||||||
return polygon;
|
return polygon;
|
||||||
}
|
}
|
||||||
|
@ -92,8 +104,8 @@ public class MultiPolygonBuilder extends ShapeBuilder {
|
||||||
|
|
||||||
private final MultiPolygonBuilder collection;
|
private final MultiPolygonBuilder collection;
|
||||||
|
|
||||||
private InternalPolygonBuilder(MultiPolygonBuilder collection) {
|
private InternalPolygonBuilder(MultiPolygonBuilder collection, Orientation orientation) {
|
||||||
super();
|
super(orientation);
|
||||||
this.collection = collection;
|
this.collection = collection;
|
||||||
this.shell = new Ring<>(this);
|
this.shell = new Ring<>(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,15 @@ import com.vividsolutions.jts.geom.Coordinate;
|
||||||
public class PolygonBuilder extends BasePolygonBuilder<PolygonBuilder> {
|
public class PolygonBuilder extends BasePolygonBuilder<PolygonBuilder> {
|
||||||
|
|
||||||
public PolygonBuilder() {
|
public PolygonBuilder() {
|
||||||
this(new ArrayList<Coordinate>());
|
this(new ArrayList<Coordinate>(), Orientation.RIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PolygonBuilder(ArrayList<Coordinate> points) {
|
public PolygonBuilder(Orientation orientation) {
|
||||||
super();
|
this(new ArrayList<Coordinate>(), orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PolygonBuilder(ArrayList<Coordinate> points, Orientation orientation) {
|
||||||
|
super(orientation);
|
||||||
this.shell = new Ring<>(this, points);
|
this.shell = new Ring<>(this, points);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.elasticsearch.common.xcontent.XContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||||
|
import org.elasticsearch.index.mapper.geo.GeoShapeFieldMapper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -73,10 +74,16 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
/** @see com.spatial4j.core.shape.jts.JtsGeometry#index() */
|
/** @see com.spatial4j.core.shape.jts.JtsGeometry#index() */
|
||||||
protected final boolean autoIndexJtsGeometry = true;//may want to turn off once SpatialStrategy impls do it.
|
protected final boolean autoIndexJtsGeometry = true;//may want to turn off once SpatialStrategy impls do it.
|
||||||
|
|
||||||
|
protected Orientation orientation = Orientation.RIGHT;
|
||||||
|
|
||||||
protected ShapeBuilder() {
|
protected ShapeBuilder() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ShapeBuilder(Orientation orientation) {
|
||||||
|
this.orientation = orientation;
|
||||||
|
}
|
||||||
|
|
||||||
protected static Coordinate coordinate(double longitude, double latitude) {
|
protected static Coordinate coordinate(double longitude, double latitude) {
|
||||||
return new Coordinate(longitude, latitude);
|
return new Coordinate(longitude, latitude);
|
||||||
}
|
}
|
||||||
|
@ -143,6 +150,14 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
return new PolygonBuilder();
|
return new PolygonBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Polygon
|
||||||
|
* @return a new {@link PointBuilder}
|
||||||
|
*/
|
||||||
|
public static PolygonBuilder newPolygon(Orientation orientation) {
|
||||||
|
return new PolygonBuilder(orientation);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Collection of polygons
|
* Create a new Collection of polygons
|
||||||
* @return a new {@link MultiPolygonBuilder}
|
* @return a new {@link MultiPolygonBuilder}
|
||||||
|
@ -151,6 +166,14 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
return new MultiPolygonBuilder();
|
return new MultiPolygonBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Collection of polygons
|
||||||
|
* @return a new {@link MultiPolygonBuilder}
|
||||||
|
*/
|
||||||
|
public static MultiPolygonBuilder newMultiPolygon(Orientation orientation) {
|
||||||
|
return new MultiPolygonBuilder(orientation);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new GeometryCollection
|
* Create a new GeometryCollection
|
||||||
* @return a new {@link GeometryCollectionBuilder}
|
* @return a new {@link GeometryCollectionBuilder}
|
||||||
|
@ -159,6 +182,14 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
return new GeometryCollectionBuilder();
|
return new GeometryCollectionBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new GeometryCollection
|
||||||
|
* @return a new {@link GeometryCollectionBuilder}
|
||||||
|
*/
|
||||||
|
public static GeometryCollectionBuilder newGeometryCollection(Orientation orientation) {
|
||||||
|
return new GeometryCollectionBuilder(orientation);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create a new Circle
|
* create a new Circle
|
||||||
* @return a new {@link CircleBuilder}
|
* @return a new {@link CircleBuilder}
|
||||||
|
@ -171,9 +202,13 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
* create a new rectangle
|
* create a new rectangle
|
||||||
* @return a new {@link EnvelopeBuilder}
|
* @return a new {@link EnvelopeBuilder}
|
||||||
*/
|
*/
|
||||||
public static EnvelopeBuilder newEnvelope() {
|
public static EnvelopeBuilder newEnvelope() { return new EnvelopeBuilder(); }
|
||||||
return new EnvelopeBuilder();
|
|
||||||
}
|
/**
|
||||||
|
* create a new rectangle
|
||||||
|
* @return a new {@link EnvelopeBuilder}
|
||||||
|
*/
|
||||||
|
public static EnvelopeBuilder newEnvelope(Orientation orientation) { return new EnvelopeBuilder(orientation); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@ -237,13 +272,43 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
* @throws IOException if the input could not be read
|
* @throws IOException if the input could not be read
|
||||||
*/
|
*/
|
||||||
public static ShapeBuilder parse(XContentParser parser) throws IOException {
|
public static ShapeBuilder parse(XContentParser parser) throws IOException {
|
||||||
return GeoShapeType.parse(parser);
|
return GeoShapeType.parse(parser, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ShapeBuilder} from {@link XContent}
|
||||||
|
* @param parser parser to read the GeoShape from
|
||||||
|
* @param geoDocMapper document field mapper reference required for spatial parameters relevant
|
||||||
|
* to the shape construction process (e.g., orientation)
|
||||||
|
* todo: refactor to place build specific parameters in the SpatialContext
|
||||||
|
* @return {@link ShapeBuilder} read from the parser or null
|
||||||
|
* if the parsers current token has been <code><null</code>
|
||||||
|
* @throws IOException if the input could not be read
|
||||||
|
*/
|
||||||
|
public static ShapeBuilder parse(XContentParser parser, GeoShapeFieldMapper geoDocMapper) throws IOException {
|
||||||
|
return GeoShapeType.parse(parser, geoDocMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static XContentBuilder toXContent(XContentBuilder builder, Coordinate coordinate) throws IOException {
|
protected static XContentBuilder toXContent(XContentBuilder builder, Coordinate coordinate) throws IOException {
|
||||||
return builder.startArray().value(coordinate.x).value(coordinate.y).endArray();
|
return builder.startArray().value(coordinate.x).value(coordinate.y).endArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Orientation orientationFromString(String orientation) {
|
||||||
|
orientation = orientation.toLowerCase(Locale.ROOT);
|
||||||
|
switch (orientation) {
|
||||||
|
case "right":
|
||||||
|
case "counterclockwise":
|
||||||
|
case "ccw":
|
||||||
|
return Orientation.RIGHT;
|
||||||
|
case "left":
|
||||||
|
case "clockwise":
|
||||||
|
case "cw":
|
||||||
|
return Orientation.LEFT;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown orientation [" + orientation + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected static Coordinate shift(Coordinate coordinate, double dateline) {
|
protected static Coordinate shift(Coordinate coordinate, double dateline) {
|
||||||
if (dateline == 0) {
|
if (dateline == 0) {
|
||||||
return coordinate;
|
return coordinate;
|
||||||
|
@ -485,8 +550,8 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
* number of points
|
* number of points
|
||||||
* @return Array of edges
|
* @return Array of edges
|
||||||
*/
|
*/
|
||||||
protected static Edge[] ring(int component, boolean direction, BaseLineStringBuilder<?> shell, Coordinate[] points, int offset,
|
protected static Edge[] ring(int component, boolean direction, boolean handedness, BaseLineStringBuilder<?> shell,
|
||||||
Edge[] edges, int toffset, int length) {
|
Coordinate[] points, int offset, Edge[] edges, int toffset, int length) {
|
||||||
// calculate the direction of the points:
|
// calculate the direction of the points:
|
||||||
// find the point a the top of the set and check its
|
// find the point a the top of the set and check its
|
||||||
// neighbors orientation. So direction is equivalent
|
// neighbors orientation. So direction is equivalent
|
||||||
|
@ -508,15 +573,15 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
// 1. shell orientation is cw and range is greater than a hemisphere (180 degrees) but not spanning 2 hemispheres
|
// 1. shell orientation is cw and range is greater than a hemisphere (180 degrees) but not spanning 2 hemispheres
|
||||||
// (translation would result in a collapsed poly)
|
// (translation would result in a collapsed poly)
|
||||||
// 2. the shell of the candidate hole has been translated (to preserve the coordinate system)
|
// 2. the shell of the candidate hole has been translated (to preserve the coordinate system)
|
||||||
if (((component == 0 && orientation) && (rng > DATELINE && rng != 2*DATELINE))
|
boolean incorrectOrientation = component == 0 && handedness != orientation;
|
||||||
|| (shell.translated && component != 0)) {
|
if ( (incorrectOrientation && (rng > DATELINE && rng != 2*DATELINE)) || (shell.translated && component != 0)) {
|
||||||
translate(points);
|
translate(points);
|
||||||
// flip the translation bit if the shell is being translated
|
// flip the translation bit if the shell is being translated
|
||||||
if (component == 0) {
|
if (component == 0) {
|
||||||
shell.translated = true;
|
shell.translated = true;
|
||||||
}
|
}
|
||||||
// correct the orientation post translation (ccw for shell, cw for holes)
|
// correct the orientation post translation (ccw for shell, cw for holes)
|
||||||
if (component == 0 || (component != 0 && !orientation)) {
|
if (component == 0 || (component != 0 && handedness == orientation)) {
|
||||||
orientation = !orientation;
|
orientation = !orientation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -574,9 +639,35 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static enum Orientation {
|
||||||
|
LEFT("left", true),
|
||||||
|
CLOCKWISE("clockwise", true),
|
||||||
|
CW("cw", true),
|
||||||
|
RIGHT("right", false),
|
||||||
|
COUNTERCLOCKWISE("counterclockwise", false),
|
||||||
|
CCW("ccw", false);
|
||||||
|
|
||||||
|
protected String name;
|
||||||
|
protected boolean orientation;
|
||||||
|
|
||||||
|
private Orientation(String name, boolean orientation) {
|
||||||
|
this.orientation = orientation;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Orientation forName(String name) {
|
||||||
|
return Orientation.valueOf(name.toUpperCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getValue() {
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static final String FIELD_TYPE = "type";
|
public static final String FIELD_TYPE = "type";
|
||||||
public static final String FIELD_COORDINATES = "coordinates";
|
public static final String FIELD_COORDINATES = "coordinates";
|
||||||
public static final String FIELD_GEOMETRIES = "geometries";
|
public static final String FIELD_GEOMETRIES = "geometries";
|
||||||
|
public static final String FIELD_ORIENTATION = "orientation";
|
||||||
|
|
||||||
protected static final boolean debugEnabled() {
|
protected static final boolean debugEnabled() {
|
||||||
return LOGGER.isDebugEnabled() || DEBUG;
|
return LOGGER.isDebugEnabled() || DEBUG;
|
||||||
|
@ -613,6 +704,18 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ShapeBuilder parse(XContentParser parser) throws IOException {
|
public static ShapeBuilder parse(XContentParser parser) throws IOException {
|
||||||
|
return parse(parser, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the geometry specified by the source document and return a ShapeBuilder instance used to
|
||||||
|
* build the actual geometry
|
||||||
|
* @param parser - parse utility object including source document
|
||||||
|
* @param shapeMapper - field mapper needed for index specific parameters
|
||||||
|
* @return ShapeBuilder - a builder instance used to create the geometry
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static ShapeBuilder parse(XContentParser parser, GeoShapeFieldMapper shapeMapper) throws IOException {
|
||||||
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
|
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
|
||||||
return null;
|
return null;
|
||||||
} else if (parser.currentToken() != XContentParser.Token.START_OBJECT) {
|
} else if (parser.currentToken() != XContentParser.Token.START_OBJECT) {
|
||||||
|
@ -623,6 +726,7 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
Distance radius = null;
|
Distance radius = null;
|
||||||
CoordinateNode node = null;
|
CoordinateNode node = null;
|
||||||
GeometryCollectionBuilder geometryCollections = null;
|
GeometryCollectionBuilder geometryCollections = null;
|
||||||
|
Orientation requestedOrientation = (shapeMapper == null) ? Orientation.RIGHT : shapeMapper.orientation();
|
||||||
|
|
||||||
XContentParser.Token token;
|
XContentParser.Token token;
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
|
@ -637,10 +741,13 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
node = parseCoordinates(parser);
|
node = parseCoordinates(parser);
|
||||||
} else if (FIELD_GEOMETRIES.equals(fieldName)) {
|
} else if (FIELD_GEOMETRIES.equals(fieldName)) {
|
||||||
parser.nextToken();
|
parser.nextToken();
|
||||||
geometryCollections = parseGeometries(parser);
|
geometryCollections = parseGeometries(parser, requestedOrientation);
|
||||||
} else if (CircleBuilder.FIELD_RADIUS.equals(fieldName)) {
|
} else if (CircleBuilder.FIELD_RADIUS.equals(fieldName)) {
|
||||||
parser.nextToken();
|
parser.nextToken();
|
||||||
radius = Distance.parseDistance(parser.text());
|
radius = Distance.parseDistance(parser.text());
|
||||||
|
} else if (FIELD_ORIENTATION.equals(fieldName)) {
|
||||||
|
parser.nextToken();
|
||||||
|
requestedOrientation = orientationFromString(parser.text());
|
||||||
} else {
|
} else {
|
||||||
parser.nextToken();
|
parser.nextToken();
|
||||||
parser.skipChildren();
|
parser.skipChildren();
|
||||||
|
@ -664,10 +771,10 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
case MULTIPOINT: return parseMultiPoint(node);
|
case MULTIPOINT: return parseMultiPoint(node);
|
||||||
case LINESTRING: return parseLineString(node);
|
case LINESTRING: return parseLineString(node);
|
||||||
case MULTILINESTRING: return parseMultiLine(node);
|
case MULTILINESTRING: return parseMultiLine(node);
|
||||||
case POLYGON: return parsePolygon(node);
|
case POLYGON: return parsePolygon(node, requestedOrientation);
|
||||||
case MULTIPOLYGON: return parseMultiPolygon(node);
|
case MULTIPOLYGON: return parseMultiPolygon(node, requestedOrientation);
|
||||||
case CIRCLE: return parseCircle(node, radius);
|
case CIRCLE: return parseCircle(node, radius);
|
||||||
case ENVELOPE: return parseEnvelope(node);
|
case ENVELOPE: return parseEnvelope(node, requestedOrientation);
|
||||||
case GEOMETRYCOLLECTION: return geometryCollections;
|
case GEOMETRYCOLLECTION: return geometryCollections;
|
||||||
default:
|
default:
|
||||||
throw new ElasticsearchParseException("Shape type [" + shapeType + "] not included");
|
throw new ElasticsearchParseException("Shape type [" + shapeType + "] not included");
|
||||||
|
@ -694,8 +801,9 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
return newCircleBuilder().center(coordinates.coordinate).radius(radius);
|
return newCircleBuilder().center(coordinates.coordinate).radius(radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static EnvelopeBuilder parseEnvelope(CoordinateNode coordinates) {
|
protected static EnvelopeBuilder parseEnvelope(CoordinateNode coordinates, Orientation orientation) {
|
||||||
return newEnvelope().topLeft(coordinates.children.get(0).coordinate).bottomRight(coordinates.children.get(1).coordinate);
|
return newEnvelope(orientation).
|
||||||
|
topLeft(coordinates.children.get(0).coordinate).bottomRight(coordinates.children.get(1).coordinate);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void validateMultiPointNode(CoordinateNode coordinates) {
|
protected static void validateMultiPointNode(CoordinateNode coordinates) {
|
||||||
|
@ -766,24 +874,24 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
return parseLineString(coordinates);
|
return parseLineString(coordinates);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static PolygonBuilder parsePolygon(CoordinateNode coordinates) {
|
protected static PolygonBuilder parsePolygon(CoordinateNode coordinates, Orientation orientation) {
|
||||||
if (coordinates.children == null || coordinates.children.isEmpty()) {
|
if (coordinates.children == null || coordinates.children.isEmpty()) {
|
||||||
throw new ElasticsearchParseException("Invalid LinearRing provided for type polygon. Linear ring must be an array of " +
|
throw new ElasticsearchParseException("Invalid LinearRing provided for type polygon. Linear ring must be an array of " +
|
||||||
"coordinates");
|
"coordinates");
|
||||||
}
|
}
|
||||||
|
|
||||||
LineStringBuilder shell = parseLinearRing(coordinates.children.get(0));
|
LineStringBuilder shell = parseLinearRing(coordinates.children.get(0));
|
||||||
PolygonBuilder polygon = new PolygonBuilder(shell.points);
|
PolygonBuilder polygon = new PolygonBuilder(shell.points, orientation);
|
||||||
for (int i = 1; i < coordinates.children.size(); i++) {
|
for (int i = 1; i < coordinates.children.size(); i++) {
|
||||||
polygon.hole(parseLinearRing(coordinates.children.get(i)));
|
polygon.hole(parseLinearRing(coordinates.children.get(i)));
|
||||||
}
|
}
|
||||||
return polygon;
|
return polygon;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static MultiPolygonBuilder parseMultiPolygon(CoordinateNode coordinates) {
|
protected static MultiPolygonBuilder parseMultiPolygon(CoordinateNode coordinates, Orientation orientation) {
|
||||||
MultiPolygonBuilder polygons = newMultiPolygon();
|
MultiPolygonBuilder polygons = newMultiPolygon(orientation);
|
||||||
for (CoordinateNode node : coordinates.children) {
|
for (CoordinateNode node : coordinates.children) {
|
||||||
polygons.polygon(parsePolygon(node));
|
polygons.polygon(parsePolygon(node, orientation));
|
||||||
}
|
}
|
||||||
return polygons;
|
return polygons;
|
||||||
}
|
}
|
||||||
|
@ -795,13 +903,13 @@ public abstract class ShapeBuilder implements ToXContent {
|
||||||
* @return Geometry[] geometries of the GeometryCollection
|
* @return Geometry[] geometries of the GeometryCollection
|
||||||
* @throws IOException Thrown if an error occurs while reading from the XContentParser
|
* @throws IOException Thrown if an error occurs while reading from the XContentParser
|
||||||
*/
|
*/
|
||||||
protected static GeometryCollectionBuilder parseGeometries(XContentParser parser) throws IOException {
|
protected static GeometryCollectionBuilder parseGeometries(XContentParser parser, Orientation orientation) throws IOException {
|
||||||
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
|
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
|
||||||
throw new ElasticsearchParseException("Geometries must be an array of geojson objects");
|
throw new ElasticsearchParseException("Geometries must be an array of geojson objects");
|
||||||
}
|
}
|
||||||
|
|
||||||
XContentParser.Token token = parser.nextToken();
|
XContentParser.Token token = parser.nextToken();
|
||||||
GeometryCollectionBuilder geometryCollection = newGeometryCollection();
|
GeometryCollectionBuilder geometryCollection = newGeometryCollection(orientation);
|
||||||
while (token != XContentParser.Token.END_ARRAY) {
|
while (token != XContentParser.Token.END_ARRAY) {
|
||||||
ShapeBuilder shapeBuilder = GeoShapeType.parse(parser);
|
ShapeBuilder shapeBuilder = GeoShapeType.parse(parser);
|
||||||
geometryCollection.shape(shapeBuilder);
|
geometryCollection.shape(shapeBuilder);
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.geo.GeoUtils;
|
import org.elasticsearch.common.geo.GeoUtils;
|
||||||
import org.elasticsearch.common.geo.SpatialStrategy;
|
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
|
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
|
||||||
import org.elasticsearch.common.unit.DistanceUnit;
|
import org.elasticsearch.common.unit.DistanceUnit;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.index.codec.docvaluesformat.DocValuesFormatProvider;
|
import org.elasticsearch.index.codec.docvaluesformat.DocValuesFormatProvider;
|
||||||
|
@ -79,6 +80,7 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
||||||
public static final String TREE_LEVELS = "tree_levels";
|
public static final String TREE_LEVELS = "tree_levels";
|
||||||
public static final String TREE_PRESISION = "precision";
|
public static final String TREE_PRESISION = "precision";
|
||||||
public static final String DISTANCE_ERROR_PCT = "distance_error_pct";
|
public static final String DISTANCE_ERROR_PCT = "distance_error_pct";
|
||||||
|
public static final String ORIENTATION = "orientation";
|
||||||
public static final String STRATEGY = "strategy";
|
public static final String STRATEGY = "strategy";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +90,7 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
||||||
public static final int GEOHASH_LEVELS = GeoUtils.geoHashLevelsForPrecision("50m");
|
public static final int GEOHASH_LEVELS = GeoUtils.geoHashLevelsForPrecision("50m");
|
||||||
public static final int QUADTREE_LEVELS = GeoUtils.quadTreeLevelsForPrecision("50m");
|
public static final int QUADTREE_LEVELS = GeoUtils.quadTreeLevelsForPrecision("50m");
|
||||||
public static final double DISTANCE_ERROR_PCT = 0.025d;
|
public static final double DISTANCE_ERROR_PCT = 0.025d;
|
||||||
|
public static final Orientation ORIENTATION = Orientation.RIGHT;
|
||||||
|
|
||||||
public static final FieldType FIELD_TYPE = new FieldType();
|
public static final FieldType FIELD_TYPE = new FieldType();
|
||||||
|
|
||||||
|
@ -99,7 +102,6 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
||||||
FIELD_TYPE.setOmitNorms(true);
|
FIELD_TYPE.setOmitNorms(true);
|
||||||
FIELD_TYPE.freeze();
|
FIELD_TYPE.freeze();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder extends AbstractFieldMapper.Builder<Builder, GeoShapeFieldMapper> {
|
public static class Builder extends AbstractFieldMapper.Builder<Builder, GeoShapeFieldMapper> {
|
||||||
|
@ -109,6 +111,7 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
||||||
private int treeLevels = 0;
|
private int treeLevels = 0;
|
||||||
private double precisionInMeters = -1;
|
private double precisionInMeters = -1;
|
||||||
private double distanceErrorPct = Defaults.DISTANCE_ERROR_PCT;
|
private double distanceErrorPct = Defaults.DISTANCE_ERROR_PCT;
|
||||||
|
private Orientation orientation = Defaults.ORIENTATION;
|
||||||
|
|
||||||
private SpatialPrefixTree prefixTree;
|
private SpatialPrefixTree prefixTree;
|
||||||
|
|
||||||
|
@ -141,6 +144,11 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder orientation(Orientation orientation) {
|
||||||
|
this.orientation = orientation;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GeoShapeFieldMapper build(BuilderContext context) {
|
public GeoShapeFieldMapper build(BuilderContext context) {
|
||||||
|
|
||||||
|
@ -153,7 +161,7 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
||||||
throw new ElasticsearchIllegalArgumentException("Unknown prefix tree type [" + tree + "]");
|
throw new ElasticsearchIllegalArgumentException("Unknown prefix tree type [" + tree + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GeoShapeFieldMapper(names, prefixTree, strategyName, distanceErrorPct, fieldType, postingsProvider,
|
return new GeoShapeFieldMapper(names, prefixTree, strategyName, distanceErrorPct, orientation, fieldType, postingsProvider,
|
||||||
docValuesProvider, multiFieldsBuilder.build(this, context), copyTo);
|
docValuesProvider, multiFieldsBuilder.build(this, context), copyTo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,6 +197,9 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
||||||
} else if (Names.DISTANCE_ERROR_PCT.equals(fieldName)) {
|
} else if (Names.DISTANCE_ERROR_PCT.equals(fieldName)) {
|
||||||
builder.distanceErrorPct(Double.parseDouble(fieldNode.toString()));
|
builder.distanceErrorPct(Double.parseDouble(fieldNode.toString()));
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
|
} else if (Names.ORIENTATION.equals(fieldName)) {
|
||||||
|
builder.orientation(ShapeBuilder.orientationFromString(fieldNode.toString()));
|
||||||
|
iterator.remove();
|
||||||
} else if (Names.STRATEGY.equals(fieldName)) {
|
} else if (Names.STRATEGY.equals(fieldName)) {
|
||||||
builder.strategy(fieldNode.toString());
|
builder.strategy(fieldNode.toString());
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
|
@ -201,16 +212,18 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
||||||
private final PrefixTreeStrategy defaultStrategy;
|
private final PrefixTreeStrategy defaultStrategy;
|
||||||
private final RecursivePrefixTreeStrategy recursiveStrategy;
|
private final RecursivePrefixTreeStrategy recursiveStrategy;
|
||||||
private final TermQueryPrefixTreeStrategy termStrategy;
|
private final TermQueryPrefixTreeStrategy termStrategy;
|
||||||
|
private Orientation shapeOrientation;
|
||||||
|
|
||||||
public GeoShapeFieldMapper(FieldMapper.Names names, SpatialPrefixTree tree, String defaultStrategyName, double distanceErrorPct,
|
public GeoShapeFieldMapper(FieldMapper.Names names, SpatialPrefixTree tree, String defaultStrategyName, double distanceErrorPct,
|
||||||
FieldType fieldType, PostingsFormatProvider postingsProvider, DocValuesFormatProvider docValuesProvider,
|
Orientation shapeOrientation, FieldType fieldType, PostingsFormatProvider postingsProvider,
|
||||||
MultiFields multiFields, CopyTo copyTo) {
|
DocValuesFormatProvider docValuesProvider, MultiFields multiFields, CopyTo copyTo) {
|
||||||
super(names, 1, fieldType, null, null, null, postingsProvider, docValuesProvider, null, null, null, null, multiFields, copyTo);
|
super(names, 1, fieldType, null, null, null, postingsProvider, docValuesProvider, null, null, null, null, multiFields, copyTo);
|
||||||
this.recursiveStrategy = new RecursivePrefixTreeStrategy(tree, names.indexName());
|
this.recursiveStrategy = new RecursivePrefixTreeStrategy(tree, names.indexName());
|
||||||
this.recursiveStrategy.setDistErrPct(distanceErrorPct);
|
this.recursiveStrategy.setDistErrPct(distanceErrorPct);
|
||||||
this.termStrategy = new TermQueryPrefixTreeStrategy(tree, names.indexName());
|
this.termStrategy = new TermQueryPrefixTreeStrategy(tree, names.indexName());
|
||||||
this.termStrategy.setDistErrPct(distanceErrorPct);
|
this.termStrategy.setDistErrPct(distanceErrorPct);
|
||||||
this.defaultStrategy = resolveStrategy(defaultStrategyName);
|
this.defaultStrategy = resolveStrategy(defaultStrategyName);
|
||||||
|
this.shapeOrientation = shapeOrientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -233,7 +246,7 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
||||||
try {
|
try {
|
||||||
Shape shape = context.parseExternalValue(Shape.class);
|
Shape shape = context.parseExternalValue(Shape.class);
|
||||||
if (shape == null) {
|
if (shape == null) {
|
||||||
ShapeBuilder shapeBuilder = ShapeBuilder.parse(context.parser());
|
ShapeBuilder shapeBuilder = ShapeBuilder.parse(context.parser(), this);
|
||||||
if (shapeBuilder == null) {
|
if (shapeBuilder == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -305,6 +318,8 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
||||||
return this.termStrategy;
|
return this.termStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Orientation orientation() { return this.shapeOrientation; }
|
||||||
|
|
||||||
public PrefixTreeStrategy resolveStrategy(String strategyName) {
|
public PrefixTreeStrategy resolveStrategy(String strategyName) {
|
||||||
if (SpatialStrategy.RECURSIVE.getStrategyName().equals(strategyName)) {
|
if (SpatialStrategy.RECURSIVE.getStrategyName().equals(strategyName)) {
|
||||||
return recursiveStrategy;
|
return recursiveStrategy;
|
||||||
|
|
|
@ -681,6 +681,171 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
|
||||||
assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson);
|
assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParse_orientationOption() throws IOException {
|
||||||
|
// test 1: valid ccw (right handed system) poly not crossing dateline (with 'right' field)
|
||||||
|
String polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
|
||||||
|
.field("orientation", "right")
|
||||||
|
.startArray("coordinates")
|
||||||
|
.startArray()
|
||||||
|
.startArray().value(176.0).value(15.0).endArray()
|
||||||
|
.startArray().value(-177.0).value(10.0).endArray()
|
||||||
|
.startArray().value(-177.0).value(-10.0).endArray()
|
||||||
|
.startArray().value(176.0).value(-15.0).endArray()
|
||||||
|
.startArray().value(172.0).value(0.0).endArray()
|
||||||
|
.startArray().value(176.0).value(15.0).endArray()
|
||||||
|
.endArray()
|
||||||
|
.startArray()
|
||||||
|
.startArray().value(-172.0).value(8.0).endArray()
|
||||||
|
.startArray().value(174.0).value(10.0).endArray()
|
||||||
|
.startArray().value(-172.0).value(-8.0).endArray()
|
||||||
|
.startArray().value(-172.0).value(8.0).endArray()
|
||||||
|
.endArray()
|
||||||
|
.endArray()
|
||||||
|
.endObject().string();
|
||||||
|
|
||||||
|
XContentParser parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
|
||||||
|
parser.nextToken();
|
||||||
|
Shape shape = ShapeBuilder.parse(parser).build();
|
||||||
|
|
||||||
|
ElasticsearchGeoAssertions.assertPolygon(shape);
|
||||||
|
|
||||||
|
// test 2: valid ccw (right handed system) poly not crossing dateline (with 'ccw' field)
|
||||||
|
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
|
||||||
|
.field("orientation", "ccw")
|
||||||
|
.startArray("coordinates")
|
||||||
|
.startArray()
|
||||||
|
.startArray().value(176.0).value(15.0).endArray()
|
||||||
|
.startArray().value(-177.0).value(10.0).endArray()
|
||||||
|
.startArray().value(-177.0).value(-10.0).endArray()
|
||||||
|
.startArray().value(176.0).value(-15.0).endArray()
|
||||||
|
.startArray().value(172.0).value(0.0).endArray()
|
||||||
|
.startArray().value(176.0).value(15.0).endArray()
|
||||||
|
.endArray()
|
||||||
|
.startArray()
|
||||||
|
.startArray().value(-172.0).value(8.0).endArray()
|
||||||
|
.startArray().value(174.0).value(10.0).endArray()
|
||||||
|
.startArray().value(-172.0).value(-8.0).endArray()
|
||||||
|
.startArray().value(-172.0).value(8.0).endArray()
|
||||||
|
.endArray()
|
||||||
|
.endArray()
|
||||||
|
.endObject().string();
|
||||||
|
|
||||||
|
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
|
||||||
|
parser.nextToken();
|
||||||
|
shape = ShapeBuilder.parse(parser).build();
|
||||||
|
|
||||||
|
ElasticsearchGeoAssertions.assertPolygon(shape);
|
||||||
|
|
||||||
|
// test 3: valid ccw (right handed system) poly not crossing dateline (with 'counterclockwise' field)
|
||||||
|
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
|
||||||
|
.field("orientation", "counterclockwise")
|
||||||
|
.startArray("coordinates")
|
||||||
|
.startArray()
|
||||||
|
.startArray().value(176.0).value(15.0).endArray()
|
||||||
|
.startArray().value(-177.0).value(10.0).endArray()
|
||||||
|
.startArray().value(-177.0).value(-10.0).endArray()
|
||||||
|
.startArray().value(176.0).value(-15.0).endArray()
|
||||||
|
.startArray().value(172.0).value(0.0).endArray()
|
||||||
|
.startArray().value(176.0).value(15.0).endArray()
|
||||||
|
.endArray()
|
||||||
|
.startArray()
|
||||||
|
.startArray().value(-172.0).value(8.0).endArray()
|
||||||
|
.startArray().value(174.0).value(10.0).endArray()
|
||||||
|
.startArray().value(-172.0).value(-8.0).endArray()
|
||||||
|
.startArray().value(-172.0).value(8.0).endArray()
|
||||||
|
.endArray()
|
||||||
|
.endArray()
|
||||||
|
.endObject().string();
|
||||||
|
|
||||||
|
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
|
||||||
|
parser.nextToken();
|
||||||
|
shape = ShapeBuilder.parse(parser).build();
|
||||||
|
|
||||||
|
ElasticsearchGeoAssertions.assertPolygon(shape);
|
||||||
|
|
||||||
|
// test 4: valid cw (left handed system) poly crossing dateline (with 'left' field)
|
||||||
|
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
|
||||||
|
.field("orientation", "left")
|
||||||
|
.startArray("coordinates")
|
||||||
|
.startArray()
|
||||||
|
.startArray().value(176.0).value(15.0).endArray()
|
||||||
|
.startArray().value(-177.0).value(10.0).endArray()
|
||||||
|
.startArray().value(-177.0).value(-10.0).endArray()
|
||||||
|
.startArray().value(176.0).value(-15.0).endArray()
|
||||||
|
.startArray().value(172.0).value(0.0).endArray()
|
||||||
|
.startArray().value(176.0).value(15.0).endArray()
|
||||||
|
.endArray()
|
||||||
|
.startArray()
|
||||||
|
.startArray().value(-178.0).value(8.0).endArray()
|
||||||
|
.startArray().value(178.0).value(8.0).endArray()
|
||||||
|
.startArray().value(180.0).value(-8.0).endArray()
|
||||||
|
.startArray().value(-178.0).value(8.0).endArray()
|
||||||
|
.endArray()
|
||||||
|
.endArray()
|
||||||
|
.endObject().string();
|
||||||
|
|
||||||
|
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
|
||||||
|
parser.nextToken();
|
||||||
|
shape = ShapeBuilder.parse(parser).build();
|
||||||
|
|
||||||
|
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
|
||||||
|
|
||||||
|
// test 5: valid cw multipoly (left handed system) poly crossing dateline (with 'cw' field)
|
||||||
|
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
|
||||||
|
.field("orientation", "cw")
|
||||||
|
.startArray("coordinates")
|
||||||
|
.startArray()
|
||||||
|
.startArray().value(176.0).value(15.0).endArray()
|
||||||
|
.startArray().value(-177.0).value(10.0).endArray()
|
||||||
|
.startArray().value(-177.0).value(-10.0).endArray()
|
||||||
|
.startArray().value(176.0).value(-15.0).endArray()
|
||||||
|
.startArray().value(172.0).value(0.0).endArray()
|
||||||
|
.startArray().value(176.0).value(15.0).endArray()
|
||||||
|
.endArray()
|
||||||
|
.startArray()
|
||||||
|
.startArray().value(-178.0).value(8.0).endArray()
|
||||||
|
.startArray().value(178.0).value(8.0).endArray()
|
||||||
|
.startArray().value(180.0).value(-8.0).endArray()
|
||||||
|
.startArray().value(-178.0).value(8.0).endArray()
|
||||||
|
.endArray()
|
||||||
|
.endArray()
|
||||||
|
.endObject().string();
|
||||||
|
|
||||||
|
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
|
||||||
|
parser.nextToken();
|
||||||
|
shape = ShapeBuilder.parse(parser).build();
|
||||||
|
|
||||||
|
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
|
||||||
|
|
||||||
|
// test 6: valid cw multipoly (left handed system) poly crossing dateline (with 'clockwise' field)
|
||||||
|
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
|
||||||
|
.field("orientation", "clockwise")
|
||||||
|
.startArray("coordinates")
|
||||||
|
.startArray()
|
||||||
|
.startArray().value(176.0).value(15.0).endArray()
|
||||||
|
.startArray().value(-177.0).value(10.0).endArray()
|
||||||
|
.startArray().value(-177.0).value(-10.0).endArray()
|
||||||
|
.startArray().value(176.0).value(-15.0).endArray()
|
||||||
|
.startArray().value(172.0).value(0.0).endArray()
|
||||||
|
.startArray().value(176.0).value(15.0).endArray()
|
||||||
|
.endArray()
|
||||||
|
.startArray()
|
||||||
|
.startArray().value(-178.0).value(8.0).endArray()
|
||||||
|
.startArray().value(178.0).value(8.0).endArray()
|
||||||
|
.startArray().value(180.0).value(-8.0).endArray()
|
||||||
|
.startArray().value(-178.0).value(8.0).endArray()
|
||||||
|
.endArray()
|
||||||
|
.endArray()
|
||||||
|
.endObject().string();
|
||||||
|
|
||||||
|
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
|
||||||
|
parser.nextToken();
|
||||||
|
shape = ShapeBuilder.parse(parser).build();
|
||||||
|
|
||||||
|
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertGeometryEquals(Shape expected, String geoJson) throws IOException {
|
private void assertGeometryEquals(Shape expected, String geoJson) throws IOException {
|
||||||
XContentParser parser = JsonXContent.jsonXContent.createParser(geoJson);
|
XContentParser parser = JsonXContent.jsonXContent.createParser(geoJson);
|
||||||
parser.nextToken();
|
parser.nextToken();
|
||||||
|
|
Loading…
Reference in New Issue