From ac0e37449e8c57ef6e57b6fe64c6eca33104410e Mon Sep 17 00:00:00 2001 From: Nicholas Knize Date: Wed, 10 Dec 2014 15:32:30 -0600 Subject: [PATCH] Adding unit test for self intersecting polygons. Relevant to #7751 even/odd discussion Updating documentation to describe polygon ambiguity and vertex ordering. --- .../mapping/types/geo-shape-type.asciidoc | 32 ++++++++++++++++++- .../common/geo/GeoJSONShapeParserTests.java | 23 +++++++++++++ .../hamcrest/ElasticsearchGeoAssertions.java | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/docs/reference/mapping/types/geo-shape-type.asciidoc b/docs/reference/mapping/types/geo-shape-type.asciidoc index a1cad8fd98f..1b35ec2f067 100644 --- a/docs/reference/mapping/types/geo-shape-type.asciidoc +++ b/docs/reference/mapping/types/geo-shape-type.asciidoc @@ -157,7 +157,7 @@ units, which default to `METERS`. For all types, both the inner `type` and `coordinates` fields are required. -Note: In GeoJSON, and therefore Elasticsearch, the correct *coordinate +In GeoJSON, and therefore Elasticsearch, the correct *coordinate order is longitude, latitude (X, Y)* within coordinate arrays. This differs from many Geospatial APIs (e.g., Google Maps) that generally use the colloquial latitude, longitude (Y, X). @@ -235,6 +235,36 @@ arrays represent the interior shapes ("holes"): } -------------------------------------------------- +*IMPORTANT NOTE:* GeoJSON does not mandate a specific order for vertices thus ambiguous +polygons around the dateline and poles are possible. To alleviate ambiguity +the Open Geospatial Consortium (OGC) +http://www.opengeospatial.org/standards/sfa[Simple Feature Access] specification +defines the following vertex ordering: + +* Outer Ring - Counterclockwise +* Inner Ring(s) / Holes - Clockwise + +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 +may be created and unexpected query/filter results will be returned. + +The following provides an example of an ambiguous polygon. Elasticsearch will apply +OGC standards to eliminate ambiguity resulting in a polygon that crosses the dateline. + +[source,js] +-------------------------------------------------- +{ + "location" : { + "type" : "polygon", + "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] diff --git a/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java b/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java index 81370fcaaad..e41f8db61bf 100644 --- a/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java +++ b/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.common.geo; +import com.spatial4j.core.exception.InvalidShapeException; import com.spatial4j.core.shape.Circle; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; @@ -430,6 +431,28 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { assertGeometryEquals(jtsGeom(expected), polygonGeoJson); } + @Test + public void testParse_selfCrossingPolygon() throws IOException { + // test self crossing ccw poly not crossing dateline + String polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon") + .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(-177.0).value(15.0).endArray() + .startArray().value(172.0).value(0.0).endArray() + .startArray().value(176.0).value(15.0).endArray() + .endArray() + .endArray() + .endObject().string(); + + XContentParser parser = JsonXContent.jsonXContent.createParser(polygonGeoJson); + parser.nextToken(); + ElasticsearchGeoAssertions.assertValidException(parser, InvalidShapeException.class); + } + @Test public void testParse_multiPoint() throws IOException { String multiPointGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "MultiPoint") diff --git a/src/test/java/org/elasticsearch/test/hamcrest/ElasticsearchGeoAssertions.java b/src/test/java/org/elasticsearch/test/hamcrest/ElasticsearchGeoAssertions.java index 7ba04cb9d78..595e84f41b6 100644 --- a/src/test/java/org/elasticsearch/test/hamcrest/ElasticsearchGeoAssertions.java +++ b/src/test/java/org/elasticsearch/test/hamcrest/ElasticsearchGeoAssertions.java @@ -251,7 +251,7 @@ public class ElasticsearchGeoAssertions { public static void assertValidException(XContentParser parser, Class expectedException) { try { - ShapeBuilder.parse(parser); + ShapeBuilder.parse(parser).build(); Assert.fail("process completed successfully when " + expectedException.getName() + " expected"); } catch (Exception e) { assert(e.getClass().equals(expectedException)):