Skip to content

Commit

Permalink
[GEO] Add optional left/right parameter to GeoJSON
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nknize committed Dec 22, 2014
1 parent b04720c commit e1af710
Show file tree
Hide file tree
Showing 9 changed files with 394 additions and 38 deletions.
32 changes: 31 additions & 1 deletion docs/reference/mapping/types/geo-shape-type.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ You can query documents using this type using
or <<query-dsl-geo-shape-query,geo_shape
Query>>.

[[geo-shape-mapping-options]]
[float]
==== Mapping Options

Expand Down Expand Up @@ -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
precise it should be. Defaults to 0.025 (2.5%) with 0.5 as the maximum
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]
Expand Down Expand Up @@ -246,7 +258,7 @@ defines the following vertex ordering:

For polygons that do not cross the dateline, vertex order will not matter in
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.

The following provides an example of an ambiguous polygon. Elasticsearch will apply
Expand All @@ -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]
===== http://www.geojson.org/geojson-spec.html#id5[MultiPoint]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
// List of linear rings defining the holes of the polygon
protected final ArrayList<BaseLineStringBuilder<?>> holes = new ArrayList<>();

public BasePolygonBuilder(Orientation orientation) {
super(orientation);
}

@SuppressWarnings("unchecked")
private E thisRef() {
return (E)this;
Expand Down Expand Up @@ -125,9 +129,9 @@ public Coordinate[][][] coordinates() {

Edge[] edges = new Edge[numEdges];
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++) {
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];
offset += length;
}
Expand Down Expand Up @@ -453,11 +457,14 @@ private static void connect(Edge in, Edge out) {
}
}

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) {
// 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)
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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ public class EnvelopeBuilder extends ShapeBuilder {
protected Coordinate topLeft;
protected Coordinate bottomRight;

public EnvelopeBuilder() {
this(Orientation.RIGHT);
}

public EnvelopeBuilder(Orientation orientation) {
super(orientation);
}

public EnvelopeBuilder topLeft(Coordinate topLeft) {
this.topLeft = topLeft;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ public class GeometryCollectionBuilder extends ShapeBuilder {

protected final ArrayList<ShapeBuilder> shapes = new ArrayList<>();

public GeometryCollectionBuilder() {
this(Orientation.RIGHT);
}

public GeometryCollectionBuilder(Orientation orientation) {
super(orientation);
}

public GeometryCollectionBuilder shape(ShapeBuilder shape) {
this.shapes.add(shape);
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,25 @@ public class MultiPolygonBuilder extends ShapeBuilder {

protected final ArrayList<BasePolygonBuilder<?>> polygons = new ArrayList<>();

public MultiPolygonBuilder() {
this(Orientation.RIGHT);
}

public MultiPolygonBuilder(Orientation orientation) {
super(orientation);
}

public MultiPolygonBuilder polygon(BasePolygonBuilder<?> polygon) {
this.polygons.add(polygon);
return this;
}

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);
return polygon;
}
Expand Down Expand Up @@ -92,8 +104,8 @@ public static class InternalPolygonBuilder extends BasePolygonBuilder<InternalPo

private final MultiPolygonBuilder collection;

private InternalPolygonBuilder(MultiPolygonBuilder collection) {
super();
private InternalPolygonBuilder(MultiPolygonBuilder collection, Orientation orientation) {
super(orientation);
this.collection = collection;
this.shell = new Ring<>(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@
public class PolygonBuilder extends BasePolygonBuilder<PolygonBuilder> {

public PolygonBuilder() {
this(new ArrayList<Coordinate>());
this(new ArrayList<Coordinate>(), Orientation.RIGHT);
}

protected PolygonBuilder(ArrayList<Coordinate> points) {
super();
public PolygonBuilder(Orientation orientation) {
this(new ArrayList<Coordinate>(), orientation);
}

protected PolygonBuilder(ArrayList<Coordinate> points, Orientation orientation) {
super(orientation);
this.shell = new Ring<>(this, points);
}

Expand Down

0 comments on commit e1af710

Please sign in to comment.