Browse Source

Adding unit test for self intersecting polygons. Relevant to #7751 even/odd discussion

Updating documentation to describe polygon ambiguity and vertex ordering.
Nicholas Knize 11 years ago
parent
commit
ac0e37449e

+ 31 - 1
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
 For all types, both the inner `type` and `coordinates` fields are
 required.
 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
 order is longitude, latitude (X, Y)* within coordinate arrays. This
 differs from many Geospatial APIs (e.g., Google Maps) that generally
 differs from many Geospatial APIs (e.g., Google Maps) that generally
 use the colloquial latitude, longitude (Y, X).
 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]
 [float]
 ===== http://www.geojson.org/geojson-spec.html#id5[MultiPoint]
 ===== http://www.geojson.org/geojson-spec.html#id5[MultiPoint]
 
 

+ 23 - 0
src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java

@@ -19,6 +19,7 @@
 
 
 package org.elasticsearch.common.geo;
 package org.elasticsearch.common.geo;
 
 
+import com.spatial4j.core.exception.InvalidShapeException;
 import com.spatial4j.core.shape.Circle;
 import com.spatial4j.core.shape.Circle;
 import com.spatial4j.core.shape.Rectangle;
 import com.spatial4j.core.shape.Rectangle;
 import com.spatial4j.core.shape.Shape;
 import com.spatial4j.core.shape.Shape;
@@ -430,6 +431,28 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
         assertGeometryEquals(jtsGeom(expected), polygonGeoJson);
         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
     @Test
     public void testParse_multiPoint() throws IOException {
     public void testParse_multiPoint() throws IOException {
         String multiPointGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "MultiPoint")
         String multiPointGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "MultiPoint")

+ 1 - 1
src/test/java/org/elasticsearch/test/hamcrest/ElasticsearchGeoAssertions.java

@@ -251,7 +251,7 @@ public class ElasticsearchGeoAssertions {
 
 
     public static void assertValidException(XContentParser parser, Class expectedException) {
     public static void assertValidException(XContentParser parser, Class expectedException) {
         try {
         try {
-            ShapeBuilder.parse(parser);
+            ShapeBuilder.parse(parser).build();
             Assert.fail("process completed successfully when " + expectedException.getName() + " expected");
             Assert.fail("process completed successfully when " + expectedException.getName() + " expected");
         } catch (Exception e) {
         } catch (Exception e) {
             assert(e.getClass().equals(expectedException)):
             assert(e.getClass().equals(expectedException)):