Browse Source

Revert "[GEO] Update GeoPolygonFilter to handle ambiguous polygons"

This reverts commit 06667c6aa898895acd624b8a71a6e00ff7ae32b8 which introduces an undesireable dependency on JTS.
Nicholas Knize 10 years ago
parent
commit
9622f78fe6

+ 27 - 32
src/main/java/org/elasticsearch/common/geo/GeoPoint.java

@@ -20,12 +20,13 @@
 package org.elasticsearch.common.geo;
 
 
-import com.vividsolutions.jts.geom.Coordinate;
-
 /**
  *
  */
-public final class GeoPoint extends Coordinate {
+public final class GeoPoint {
+
+    private double lat;
+    private double lon;
 
     public GeoPoint() {
     }
@@ -40,36 +41,32 @@ public final class GeoPoint extends Coordinate {
         this.resetFromString(value);
     }
 
-    public GeoPoint(GeoPoint other) {
-        super(other);
-    }
-
     public GeoPoint(double lat, double lon) {
-        this.y = lat;
-        this.x = lon;
+        this.lat = lat;
+        this.lon = lon;
     }
 
     public GeoPoint reset(double lat, double lon) {
-        this.y = lat;
-        this.x = lon;
+        this.lat = lat;
+        this.lon = lon;
         return this;
     }
 
     public GeoPoint resetLat(double lat) {
-        this.y = lat;
+        this.lat = lat;
         return this;
     }
 
     public GeoPoint resetLon(double lon) {
-        this.x = lon;
+        this.lon = lon;
         return this;
     }
 
     public GeoPoint resetFromString(String value) {
         int comma = value.indexOf(',');
         if (comma != -1) {
-            this.y = Double.parseDouble(value.substring(0, comma).trim());
-            this.x = Double.parseDouble(value.substring(comma + 1).trim());
+            lat = Double.parseDouble(value.substring(0, comma).trim());
+            lon = Double.parseDouble(value.substring(comma + 1).trim());
         } else {
             resetFromGeoHash(value);
         }
@@ -82,40 +79,38 @@ public final class GeoPoint extends Coordinate {
     }
 
     public final double lat() {
-        return this.y;
+        return this.lat;
     }
 
     public final double getLat() {
-        return this.y;
+        return this.lat;
     }
 
     public final double lon() {
-        return this.x;
+        return this.lon;
     }
 
     public final double getLon() {
-        return this.x;
+        return this.lon;
     }
 
     public final String geohash() {
-        return GeoHashUtils.encode(y, x);
+        return GeoHashUtils.encode(lat, lon);
     }
 
     public final String getGeohash() {
-        return GeoHashUtils.encode(y, x);
+        return GeoHashUtils.encode(lat, lon);
     }
 
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
-        if (o == null) return false;
-        if (o instanceof Coordinate) {
-            Coordinate c = (Coordinate)o;
-            return Double.compare(c.x, this.x) == 0
-                    && Double.compare(c.y, this.y) == 0
-                    && Double.compare(c.z, this.z) == 0;
-        }
-        if (getClass() != o.getClass()) return false;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        GeoPoint geoPoint = (GeoPoint) o;
+
+        if (Double.compare(geoPoint.lat, lat) != 0) return false;
+        if (Double.compare(geoPoint.lon, lon) != 0) return false;
 
         return true;
     }
@@ -124,15 +119,15 @@ public final class GeoPoint extends Coordinate {
     public int hashCode() {
         int result;
         long temp;
-        temp = y != +0.0d ? Double.doubleToLongBits(y) : 0L;
+        temp = lat != +0.0d ? Double.doubleToLongBits(lat) : 0L;
         result = (int) (temp ^ (temp >>> 32));
-        temp = x != +0.0d ? Double.doubleToLongBits(x) : 0L;
+        temp = lon != +0.0d ? Double.doubleToLongBits(lon) : 0L;
         result = 31 * result + (int) (temp ^ (temp >>> 32));
         return result;
     }
 
     public String toString() {
-        return "[" + y + ", " + x + "]";
+        return "[" + lat + ", " + lon + "]";
     }
 
     public static GeoPoint parseFromLatLon(String latLon) {

+ 1 - 111
src/main/java/org/elasticsearch/common/geo/GeoUtils.java

@@ -19,7 +19,6 @@
 
 package org.elasticsearch.common.geo;
 
-import org.apache.commons.lang3.tuple.Pair;
 import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
 import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
 import org.apache.lucene.util.SloppyMath;
@@ -38,9 +37,7 @@ public class GeoUtils {
     public static final String LATITUDE = GeoPointFieldMapper.Names.LAT;
     public static final String LONGITUDE = GeoPointFieldMapper.Names.LON;
     public static final String GEOHASH = GeoPointFieldMapper.Names.GEOHASH;
-
-    public static final double DATELINE = 180.0D;
-
+    
     /** Earth ellipsoid major axis defined by WGS 84 in meters */
     public static final double EARTH_SEMI_MAJOR_AXIS = 6378137.0;      // meters (WGS 84)
 
@@ -425,113 +422,6 @@ public class GeoUtils {
         }
     }
 
-    public static boolean correctPolyAmbiguity(GeoPoint[] points, boolean handedness) {
-        return correctPolyAmbiguity(points, handedness, computePolyOrientation(points), 0, points.length, false);
-    }
-
-    public static boolean correctPolyAmbiguity(GeoPoint[] points, boolean handedness, boolean orientation, int component, int length,
-                                               boolean shellCorrected) {
-        // OGC requires shell as ccw (Right-Handedness) and holes as cw (Left-Handedness)
-        // since GeoJSON doesn't specify (and doesn't need to) GEO core will assume OGC standards
-        // thus if orientation is computed as cw, the logic will translate points across dateline
-        // and convert to a right handed system
-
-        // compute the bounding box and calculate range
-        Pair<Pair, Pair> range = GeoUtils.computeBBox(points, length);
-        final double rng = (Double)range.getLeft().getRight() - (Double)range.getLeft().getLeft();
-        // translate the points if the following is true
-        //   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)
-        //   2.  the shell of the candidate hole has been translated (to preserve the coordinate system)
-        boolean incorrectOrientation = component == 0 && handedness != orientation;
-        boolean translated = ((incorrectOrientation && (rng > DATELINE && rng != 360.0)) || (shellCorrected && component != 0));
-        if (translated) {
-            for (GeoPoint c : points) {
-                if (c.x < 0.0) {
-                    c.x += 360.0;
-                }
-            }
-        }
-        return translated;
-    }
-
-    public static boolean computePolyOrientation(GeoPoint[] points) {
-        return computePolyOrientation(points, points.length);
-    }
-
-    public static boolean computePolyOrientation(GeoPoint[] points, int length) {
-        // calculate the direction of the points:
-        // find the point at the top of the set and check its
-        // neighbors orientation. So direction is equivalent
-        // to clockwise/counterclockwise
-        final int top = computePolyOrigin(points, length);
-        final int prev = ((top + length - 1) % length);
-        final int next = ((top + 1) % length);
-        return (points[prev].x > points[next].x);
-    }
-
-    private static final int computePolyOrigin(GeoPoint[] points, int length) {
-        int top = 0;
-        // we start at 1 here since top points to 0
-        for (int i = 1; i < length; i++) {
-            if (points[i].y < points[top].y) {
-                top = i;
-            } else if (points[i].y == points[top].y) {
-                if (points[i].x < points[top].x) {
-                    top = i;
-                }
-            }
-        }
-        return top;
-    }
-
-    public static final Pair computeBBox(GeoPoint[] points) {
-        return computeBBox(points, 0);
-    }
-
-    public static final Pair computeBBox(GeoPoint[] points, int length) {
-        double minX = points[0].x;
-        double maxX = points[0].x;
-        double minY = points[0].y;
-        double maxY = points[0].y;
-        // compute the bounding coordinates (@todo: cleanup brute force)
-        for (int i = 1; i < length; ++i) {
-            if (points[i].x < minX) {
-                minX = points[i].x;
-            }
-            if (points[i].x > maxX) {
-                maxX = points[i].x;
-            }
-            if (points[i].y < minY) {
-                minY = points[i].y;
-            }
-            if (points[i].y > maxY) {
-                maxY = points[i].y;
-            }
-        }
-        // return a pair of ranges on the X and Y axis, respectively
-        return Pair.of(Pair.of(minX, maxX), Pair.of(minY, maxY));
-    }
-
-    public static GeoPoint convertToGreatCircle(GeoPoint point) {
-        return convertToGreatCircle(point.y, point.x);
-    }
-
-    public static GeoPoint convertToGreatCircle(double lat, double lon) {
-        GeoPoint p = new GeoPoint(lat, lon);
-        // convert the point to standard lat/lon bounds
-        normalizePoint(p);
-
-        if (p.x < 0.0D) {
-            p.x += 360.0D;
-        }
-
-        if (p.y < 0.0D) {
-            p.y +=180.0D;
-        }
-        return p;
-    }
-
     private GeoUtils() {
     }
 }

+ 15 - 15
src/main/java/org/elasticsearch/common/geo/builders/BaseLineStringBuilder.java

@@ -23,11 +23,11 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 
-import org.elasticsearch.common.geo.GeoPoint;
-import org.elasticsearch.common.geo.GeoUtils;
+import com.spatial4j.core.shape.ShapeCollection;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 
 import com.spatial4j.core.shape.Shape;
+import com.vividsolutions.jts.geom.Coordinate;
 import com.vividsolutions.jts.geom.Geometry;
 import com.vividsolutions.jts.geom.GeometryFactory;
 import com.vividsolutions.jts.geom.LineString;
@@ -35,10 +35,10 @@ import com.vividsolutions.jts.geom.LineString;
 public abstract class BaseLineStringBuilder<E extends BaseLineStringBuilder<E>> extends PointCollection<E> {
 
     protected BaseLineStringBuilder() {
-        this(new ArrayList<GeoPoint>());
+        this(new ArrayList<Coordinate>());
     }
 
-    protected BaseLineStringBuilder(ArrayList<GeoPoint> points) {
+    protected BaseLineStringBuilder(ArrayList<Coordinate> points) {
         super(points);
     }
 
@@ -49,7 +49,7 @@ public abstract class BaseLineStringBuilder<E extends BaseLineStringBuilder<E>>
 
     @Override
     public Shape build() {
-        GeoPoint[] coordinates = points.toArray(new GeoPoint[points.size()]);
+        Coordinate[] coordinates = points.toArray(new Coordinate[points.size()]);
         Geometry geometry;
         if(wrapdateline) {
             ArrayList<LineString> strings = decompose(FACTORY, coordinates, new ArrayList<LineString>());
@@ -67,9 +67,9 @@ public abstract class BaseLineStringBuilder<E extends BaseLineStringBuilder<E>>
         return jtsGeometry(geometry);
     }
 
-    protected static ArrayList<LineString> decompose(GeometryFactory factory, GeoPoint[] coordinates, ArrayList<LineString> strings) {
-        for(GeoPoint[] part : decompose(+DATELINE, coordinates)) {
-            for(GeoPoint[] line : decompose(-DATELINE, part)) {
+    protected static ArrayList<LineString> decompose(GeometryFactory factory, Coordinate[] coordinates, ArrayList<LineString> strings) {
+        for(Coordinate[] part : decompose(+DATELINE, coordinates)) {
+            for(Coordinate[] line : decompose(-DATELINE, part)) {
                 strings.add(factory.createLineString(line));
             }
         }
@@ -83,16 +83,16 @@ public abstract class BaseLineStringBuilder<E extends BaseLineStringBuilder<E>>
      * @param coordinates coordinates forming the linestring
      * @return array of linestrings given as coordinate arrays 
      */
-    protected static GeoPoint[][] decompose(double dateline, GeoPoint[] coordinates) {
+    protected static Coordinate[][] decompose(double dateline, Coordinate[] coordinates) {
         int offset = 0;
-        ArrayList<GeoPoint[]> parts = new ArrayList<>();
+        ArrayList<Coordinate[]> parts = new ArrayList<>();
         
         double shift = coordinates[0].x > DATELINE ? DATELINE : (coordinates[0].x < -DATELINE ? -DATELINE : 0);
 
         for (int i = 1; i < coordinates.length; i++) {
             double t = intersection(coordinates[i-1], coordinates[i], dateline);
             if(!Double.isNaN(t)) {
-                GeoPoint[] part;
+                Coordinate[] part;
                 if(t<1) {
                     part = Arrays.copyOfRange(coordinates, offset, i+1);
                     part[part.length-1] = Edge.position(coordinates[i-1], coordinates[i], t);
@@ -111,16 +111,16 @@ public abstract class BaseLineStringBuilder<E extends BaseLineStringBuilder<E>>
         if(offset == 0) {
             parts.add(shift(shift, coordinates));
         } else if(offset < coordinates.length-1) {
-            GeoPoint[] part = Arrays.copyOfRange(coordinates, offset, coordinates.length);
+            Coordinate[] part = Arrays.copyOfRange(coordinates, offset, coordinates.length);
             parts.add(shift(shift, part));
         }
-        return parts.toArray(new GeoPoint[parts.size()][]);
+        return parts.toArray(new Coordinate[parts.size()][]);
     }
 
-    private static GeoPoint[] shift(double shift, GeoPoint...coordinates) {
+    private static Coordinate[] shift(double shift, Coordinate...coordinates) {
         if(shift != 0) {
             for (int j = 0; j < coordinates.length; j++) {
-                coordinates[j] = new GeoPoint(coordinates[j].y, coordinates[j].x - 2 * shift);
+                coordinates[j] = new Coordinate(coordinates[j].x - 2 * shift, coordinates[j].y);
             }
         }
         return coordinates;

+ 32 - 38
src/main/java/org/elasticsearch/common/geo/builders/BasePolygonBuilder.java

@@ -20,14 +20,8 @@
 package org.elasticsearch.common.geo.builders;
 
 import com.spatial4j.core.shape.Shape;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LinearRing;
-import com.vividsolutions.jts.geom.MultiPolygon;
-import com.vividsolutions.jts.geom.Polygon;
+import com.vividsolutions.jts.geom.*;
 import org.elasticsearch.ElasticsearchParseException;
-import org.elasticsearch.common.geo.GeoPoint;
-import org.elasticsearch.common.geo.GeoUtils;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 
 import java.io.IOException;
@@ -73,7 +67,7 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
      * @param coordinate coordinate of the new point
      * @return this
      */
-    public E point(GeoPoint coordinate) {
+    public E point(Coordinate coordinate) {
         shell.point(coordinate);
         return thisRef();
     }
@@ -83,7 +77,7 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
      * @param coordinates coordinates of the new points to add
      * @return this
      */
-    public E points(GeoPoint...coordinates) {
+    public E points(Coordinate...coordinates) {
         shell.points(coordinates);
         return thisRef();
     }
@@ -127,7 +121,7 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
      * 
      * @return coordinates of the polygon
      */
-    public GeoPoint[][][] coordinates() {
+    public Coordinate[][][] coordinates() {
         int numEdges = shell.points.size()-1; // Last point is repeated 
         for (int i = 0; i < holes.size(); i++) {
             numEdges += holes.get(i).points.size()-1;
@@ -176,7 +170,7 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
     
     public Geometry buildGeometry(GeometryFactory factory, boolean fixDateline) {
         if(fixDateline) {
-            GeoPoint[][][] polygons = coordinates();
+            Coordinate[][][] polygons = coordinates();
             return polygons.length == 1
                     ? polygon(factory, polygons[0])
                     : multipolygon(factory, polygons);
@@ -199,8 +193,8 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
         return factory.createPolygon(shell, holes);
     }
 
-    protected static LinearRing linearRing(GeometryFactory factory, ArrayList<GeoPoint> coordinates) {
-        return factory.createLinearRing(coordinates.toArray(new GeoPoint[coordinates.size()]));
+    protected static LinearRing linearRing(GeometryFactory factory, ArrayList<Coordinate> coordinates) {
+        return factory.createLinearRing(coordinates.toArray(new Coordinate[coordinates.size()]));
     }
 
     @Override
@@ -208,7 +202,7 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
         return TYPE;
     }
 
-    protected static Polygon polygon(GeometryFactory factory, GeoPoint[][] polygon) {
+    protected static Polygon polygon(GeometryFactory factory, Coordinate[][] polygon) {
         LinearRing shell = factory.createLinearRing(polygon[0]);
         LinearRing[] holes;
         
@@ -233,7 +227,7 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
      * @param polygons definition of polygons
      * @return a new Multipolygon
      */
-    protected static MultiPolygon multipolygon(GeometryFactory factory, GeoPoint[][][] polygons) {
+    protected static MultiPolygon multipolygon(GeometryFactory factory, Coordinate[][][] polygons) {
         Polygon[] polygonSet = new Polygon[polygons.length];
         for (int i = 0; i < polygonSet.length; i++) {
             polygonSet[i] = polygon(factory, polygons[i]);
@@ -289,18 +283,18 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
      * @param coordinates Array of coordinates to write the result to
      * @return the coordinates parameter
      */
-    private static GeoPoint[] coordinates(Edge component, GeoPoint[] coordinates) {
+    private static Coordinate[] coordinates(Edge component, Coordinate[] coordinates) {
         for (int i = 0; i < coordinates.length; i++) {
             coordinates[i] = (component = component.next).coordinate;
         }
         return coordinates;
     }
 
-    private static GeoPoint[][][] buildCoordinates(ArrayList<ArrayList<GeoPoint[]>> components) {
-        GeoPoint[][][] result = new GeoPoint[components.size()][][];
+    private static Coordinate[][][] buildCoordinates(ArrayList<ArrayList<Coordinate[]>> components) {
+        Coordinate[][][] result = new Coordinate[components.size()][][];
         for (int i = 0; i < result.length; i++) {
-            ArrayList<GeoPoint[]> component = components.get(i);
-            result[i] = component.toArray(new GeoPoint[component.size()][]);
+            ArrayList<Coordinate[]> component = components.get(i);
+            result[i] = component.toArray(new Coordinate[component.size()][]);
         }
 
         if(debugEnabled()) {
@@ -315,30 +309,30 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
         return result;
     } 
 
-    private static final GeoPoint[][] EMPTY = new GeoPoint[0][];
+    private static final Coordinate[][] EMPTY = new Coordinate[0][];
 
-    private static GeoPoint[][] holes(Edge[] holes, int numHoles) {
+    private static Coordinate[][] holes(Edge[] holes, int numHoles) {
         if (numHoles == 0) {
             return EMPTY;
         }
-        final GeoPoint[][] points = new GeoPoint[numHoles][];
+        final Coordinate[][] points = new Coordinate[numHoles][];
 
         for (int i = 0; i < numHoles; i++) {
             int length = component(holes[i], -(i+1), null); // mark as visited by inverting the sign
-            points[i] = coordinates(holes[i], new GeoPoint[length+1]);
+            points[i] = coordinates(holes[i], new Coordinate[length+1]);
         }
 
         return points;
     } 
 
-    private static Edge[] edges(Edge[] edges, int numHoles, ArrayList<ArrayList<GeoPoint[]>> components) {
+    private static Edge[] edges(Edge[] edges, int numHoles, ArrayList<ArrayList<Coordinate[]>> components) {
         ArrayList<Edge> mainEdges = new ArrayList<>(edges.length);
 
         for (int i = 0; i < edges.length; i++) {
             if (edges[i].component >= 0) {
                 int length = component(edges[i], -(components.size()+numHoles+1), mainEdges);
-                ArrayList<GeoPoint[]> component = new ArrayList<>();
-                component.add(coordinates(edges[i], new GeoPoint[length+1]));
+                ArrayList<Coordinate[]> component = new ArrayList<>();
+                component.add(coordinates(edges[i], new Coordinate[length+1]));
                 components.add(component);
             }
         }
@@ -346,14 +340,13 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
         return mainEdges.toArray(new Edge[mainEdges.size()]);
     }
 
-    private static GeoPoint[][][] compose(Edge[] edges, Edge[] holes, int numHoles) {
-        final ArrayList<ArrayList<GeoPoint[]>> components = new ArrayList<>();
+    private static Coordinate[][][] compose(Edge[] edges, Edge[] holes, int numHoles) {
+        final ArrayList<ArrayList<Coordinate[]>> components = new ArrayList<>();
         assign(holes, holes(holes, numHoles), numHoles, edges(edges, numHoles, components), components);
         return buildCoordinates(components);
     }
 
-    private static void assign(Edge[] holes, GeoPoint[][] points, int numHoles, Edge[] edges, ArrayList<ArrayList<GeoPoint[]>>
-            components) {
+    private static void assign(Edge[] holes, Coordinate[][] points, int numHoles, Edge[] edges, ArrayList<ArrayList<Coordinate[]>> components) {
         // Assign Hole to related components
         // To find the new component the hole belongs to all intersections of the
         // polygon edges with a vertical line are calculated. This vertical line
@@ -468,13 +461,14 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
     }
 
     private static int createEdges(int component, Orientation orientation, BaseLineStringBuilder<?> shell,
-                                   BaseLineStringBuilder<?> hole, Edge[] edges, int edgeOffset) {
+                                   BaseLineStringBuilder<?> hole,
+                                   Edge[] edges, int offset) {
         // inner rings (holes) have an opposite direction than the outer rings
         // XOR will invert the orientation for outer ring cases (Truth Table:, T/T = F, T/F = T, F/T = T, F/F = F)
         boolean direction = (component != 0 ^ orientation == Orientation.RIGHT);
         // set the points array accordingly (shell or hole)
-        GeoPoint[] points = (hole != null) ? hole.coordinates(false) : shell.coordinates(false);
-        Edge.ring(component, direction, orientation == Orientation.LEFT, shell, points, edges, edgeOffset, points.length-1);
+        Coordinate[] points = (hole != null) ? hole.coordinates(false) : shell.coordinates(false);
+        Edge.ring(component, direction, orientation == Orientation.LEFT, shell, points, 0, edges, offset, points.length-1);
         return points.length-1;
     }
 
@@ -483,17 +477,17 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
         private final P parent;
 
         protected Ring(P parent) {
-            this(parent, new ArrayList<GeoPoint>());
+            this(parent, new ArrayList<Coordinate>());
         }
 
-        protected Ring(P parent, ArrayList<GeoPoint> points) {
+        protected Ring(P parent, ArrayList<Coordinate> points) {
             super(points);
             this.parent = parent;
         }
 
         public P close() {
-            GeoPoint start = points.get(0);
-            GeoPoint end = points.get(points.size()-1);
+            Coordinate start = points.get(0);
+            Coordinate end = points.get(points.size()-1);
             if(start.x != end.x || start.y != end.y) {
                 points.add(start);
             }

+ 4 - 4
src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java

@@ -20,7 +20,7 @@
 package org.elasticsearch.common.geo.builders;
 
 import com.spatial4j.core.shape.Circle;
-import org.elasticsearch.common.geo.GeoPoint;
+import com.vividsolutions.jts.geom.Coordinate;
 import org.elasticsearch.common.unit.DistanceUnit;
 import org.elasticsearch.common.unit.DistanceUnit.Distance;
 import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -34,7 +34,7 @@ public class CircleBuilder extends ShapeBuilder {
 
     private DistanceUnit unit;
     private double radius;
-    private GeoPoint center;
+    private Coordinate center;
     
     /**
      * Set the center of the circle
@@ -42,7 +42,7 @@ public class CircleBuilder extends ShapeBuilder {
      * @param center coordinate of the circles center
      * @return this
      */
-    public CircleBuilder center(GeoPoint center) {
+    public CircleBuilder center(Coordinate center) {
         this.center = center;
         return this;
     }
@@ -54,7 +54,7 @@ public class CircleBuilder extends ShapeBuilder {
      * @return this
      */
     public CircleBuilder center(double lon, double lat) {
-        return center(new GeoPoint(lat, lon));
+        return center(new Coordinate(lon, lat));
     }
 
     /**

+ 5 - 5
src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java

@@ -20,7 +20,7 @@
 package org.elasticsearch.common.geo.builders;
 
 import com.spatial4j.core.shape.Rectangle;
-import org.elasticsearch.common.geo.GeoPoint;
+import com.vividsolutions.jts.geom.Coordinate;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 
 import java.io.IOException;
@@ -29,8 +29,8 @@ public class EnvelopeBuilder extends ShapeBuilder {
 
     public static final GeoShapeType TYPE = GeoShapeType.ENVELOPE; 
 
-    protected GeoPoint topLeft;
-    protected GeoPoint bottomRight;
+    protected Coordinate topLeft;
+    protected Coordinate bottomRight;
 
     public EnvelopeBuilder() {
         this(Orientation.RIGHT);
@@ -40,7 +40,7 @@ public class EnvelopeBuilder extends ShapeBuilder {
         super(orientation);
     }
 
-    public EnvelopeBuilder topLeft(GeoPoint topLeft) {
+    public EnvelopeBuilder topLeft(Coordinate topLeft) {
         this.topLeft = topLeft;
         return this;
     }
@@ -49,7 +49,7 @@ public class EnvelopeBuilder extends ShapeBuilder {
         return topLeft(coordinate(longitude, latitude));
     }
 
-    public EnvelopeBuilder bottomRight(GeoPoint bottomRight) {
+    public EnvelopeBuilder bottomRight(Coordinate bottomRight) {
         this.bottomRight = bottomRight;
         return this;
     }

+ 5 - 4
src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java

@@ -19,10 +19,11 @@
 
 package org.elasticsearch.common.geo.builders;
 
-import org.elasticsearch.common.geo.GeoPoint;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 
 import com.spatial4j.core.shape.Shape;
+import com.spatial4j.core.shape.jts.JtsGeometry;
+import com.vividsolutions.jts.geom.Coordinate;
 import com.vividsolutions.jts.geom.Geometry;
 import com.vividsolutions.jts.geom.LineString;
 
@@ -47,8 +48,8 @@ public class MultiLineStringBuilder extends ShapeBuilder {
         return this;
     }
 
-    public GeoPoint[][] coordinates() {
-        GeoPoint[][] result = new GeoPoint[lines.size()][];
+    public Coordinate[][] coordinates() {
+        Coordinate[][] result = new Coordinate[lines.size()][];
         for (int i = 0; i < result.length; i++) {
             result[i] = lines.get(i).coordinates(false);
         }
@@ -112,7 +113,7 @@ public class MultiLineStringBuilder extends ShapeBuilder {
             return collection;
         }
 
-        public GeoPoint[] coordinates() {
+        public Coordinate[] coordinates() {
             return super.coordinates(false);
         }
 

+ 2 - 2
src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java

@@ -22,7 +22,7 @@ package org.elasticsearch.common.geo.builders;
 import com.spatial4j.core.shape.Point;
 import com.spatial4j.core.shape.Shape;
 import com.spatial4j.core.shape.ShapeCollection;
-import org.elasticsearch.common.geo.GeoPoint;
+import com.vividsolutions.jts.geom.Coordinate;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 
 import java.io.IOException;
@@ -48,7 +48,7 @@ public class MultiPointBuilder extends PointCollection<MultiPointBuilder> {
         //Could wrap JtsGeometry but probably slower due to conversions to/from JTS in relate()
         //MultiPoint geometry = FACTORY.createMultiPoint(points.toArray(new Coordinate[points.size()]));
         List<Point> shapes = new ArrayList<>(points.size());
-        for (GeoPoint coord : points) {
+        for (Coordinate coord : points) {
             shapes.add(SPATIAL_CONTEXT.makePoint(coord.x, coord.y));
         }
         return new ShapeCollection<>(shapes, SPATIAL_CONTEXT);

+ 2 - 2
src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java

@@ -24,10 +24,10 @@ import java.util.ArrayList;
 import java.util.List;
 
 import com.spatial4j.core.shape.ShapeCollection;
-import org.elasticsearch.common.geo.GeoPoint;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 
 import com.spatial4j.core.shape.Shape;
+import com.vividsolutions.jts.geom.Coordinate;
 
 public class MultiPolygonBuilder extends ShapeBuilder {
 
@@ -84,7 +84,7 @@ public class MultiPolygonBuilder extends ShapeBuilder {
         
         if(wrapdateline) {
             for (BasePolygonBuilder<?> polygon : this.polygons) {
-                for(GeoPoint[][] part : polygon.coordinates()) {
+                for(Coordinate[][] part : polygon.coordinates()) {
                     shapes.add(jtsGeometry(PolygonBuilder.polygon(FACTORY, part)));
                 }
             }

+ 3 - 3
src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java

@@ -21,18 +21,18 @@ package org.elasticsearch.common.geo.builders;
 
 import java.io.IOException;
 
-import org.elasticsearch.common.geo.GeoPoint;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 
 import com.spatial4j.core.shape.Point;
+import com.vividsolutions.jts.geom.Coordinate;
 
 public class PointBuilder extends ShapeBuilder {
 
     public static final GeoShapeType TYPE = GeoShapeType.POINT;
 
-    private GeoPoint coordinate;
+    private Coordinate coordinate;
 
-    public PointBuilder coordinate(GeoPoint coordinate) {
+    public PointBuilder coordinate(Coordinate coordinate) {
         this.coordinate = coordinate;
         return this;
     }

+ 15 - 14
src/main/java/org/elasticsearch/common/geo/builders/PointCollection.java

@@ -24,22 +24,23 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 
-import org.elasticsearch.common.geo.GeoPoint;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 
+import com.vividsolutions.jts.geom.Coordinate;
+
 /**
  * The {@link PointCollection} is an abstract base implementation for all GeoShapes. It simply handles a set of points. 
  */
 public abstract class PointCollection<E extends PointCollection<E>> extends ShapeBuilder {
 
-    protected final ArrayList<GeoPoint> points;
+    protected final ArrayList<Coordinate> points;
     protected boolean translated = false;
 
     protected PointCollection() {
-        this(new ArrayList<GeoPoint>());
+        this(new ArrayList<Coordinate>());
     }
 
-    protected PointCollection(ArrayList<GeoPoint> points) {
+    protected PointCollection(ArrayList<Coordinate> points) {
         this.points = points;
     }
     
@@ -63,7 +64,7 @@ public abstract class PointCollection<E extends PointCollection<E>> extends Shap
      * @param coordinate coordinate of the point
      * @return this
      */
-    public E point(GeoPoint coordinate) {
+    public E point(Coordinate coordinate) {
         this.points.add(coordinate);
         return thisRef();
     }
@@ -71,20 +72,20 @@ public abstract class PointCollection<E extends PointCollection<E>> extends Shap
     /**
      * Add a array of points to the collection
      * 
-     * @param coordinates array of {@link GeoPoint}s to add
+     * @param coordinates array of {@link Coordinate}s to add
      * @return this
      */
-    public E points(GeoPoint...coordinates) {
+    public E points(Coordinate...coordinates) {
         return this.points(Arrays.asList(coordinates));
     }
 
     /**
      * Add a collection of points to the collection
      * 
-     * @param coordinates array of {@link GeoPoint}s to add
+     * @param coordinates array of {@link Coordinate}s to add
      * @return this
      */
-    public E points(Collection<? extends GeoPoint> coordinates) {
+    public E points(Collection<? extends Coordinate> coordinates) {
         this.points.addAll(coordinates);
         return thisRef();
     }
@@ -95,8 +96,8 @@ public abstract class PointCollection<E extends PointCollection<E>> extends Shap
      * @param closed if set to true the first point of the array is repeated as last element
      * @return Array of coordinates
      */
-    protected GeoPoint[] coordinates(boolean closed) {
-        GeoPoint[] result = points.toArray(new GeoPoint[points.size() + (closed?1:0)]);
+    protected Coordinate[] coordinates(boolean closed) {
+        Coordinate[] result = points.toArray(new Coordinate[points.size() + (closed?1:0)]);
         if(closed) {
             result[result.length-1] = result[0];
         }
@@ -113,12 +114,12 @@ public abstract class PointCollection<E extends PointCollection<E>> extends Shap
      */
     protected XContentBuilder coordinatesToXcontent(XContentBuilder builder, boolean closed) throws IOException {
         builder.startArray();
-        for(GeoPoint point : points) {
+        for(Coordinate point : points) {
             toXContent(builder, point);
         }
         if(closed) {
-            GeoPoint start = points.get(0);
-            GeoPoint end = points.get(points.size()-1);
+            Coordinate start = points.get(0);
+            Coordinate end = points.get(points.size()-1);
             if(start.x != end.x || start.y != end.y) {
                 toXContent(builder, points.get(0));
             }

+ 4 - 4
src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java

@@ -21,19 +21,19 @@ package org.elasticsearch.common.geo.builders;
 
 import java.util.ArrayList;
 
-import org.elasticsearch.common.geo.GeoPoint;
+import com.vividsolutions.jts.geom.Coordinate;
 
 public class PolygonBuilder extends BasePolygonBuilder<PolygonBuilder> {
 
     public PolygonBuilder() {
-        this(new ArrayList<GeoPoint>(), Orientation.RIGHT);
+        this(new ArrayList<Coordinate>(), Orientation.RIGHT);
     }
 
     public PolygonBuilder(Orientation orientation) {
-        this(new ArrayList<GeoPoint>(), orientation);
+        this(new ArrayList<Coordinate>(), orientation);
     }
 
-    protected PolygonBuilder(ArrayList<GeoPoint> points, Orientation orientation) {
+    protected PolygonBuilder(ArrayList<Coordinate> points, Orientation orientation) {
         super(orientation);
         this.shell = new Ring<>(this, points);
     }

+ 118 - 60
src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java

@@ -22,12 +22,12 @@ package org.elasticsearch.common.geo.builders;
 import com.spatial4j.core.context.jts.JtsSpatialContext;
 import com.spatial4j.core.shape.Shape;
 import com.spatial4j.core.shape.jts.JtsGeometry;
+import com.vividsolutions.jts.geom.Coordinate;
 import com.vividsolutions.jts.geom.Geometry;
 import com.vividsolutions.jts.geom.GeometryFactory;
+import org.apache.commons.lang3.tuple.Pair;
 import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.ElasticsearchParseException;
-import org.elasticsearch.common.geo.GeoPoint;
-import org.elasticsearch.common.geo.GeoUtils;
 import org.elasticsearch.common.logging.ESLogger;
 import org.elasticsearch.common.logging.ESLoggerFactory;
 import org.elasticsearch.common.unit.DistanceUnit.Distance;
@@ -57,7 +57,7 @@ public abstract class ShapeBuilder implements ToXContent {
         DEBUG = debug;
     }
 
-    public static final double DATELINE = GeoUtils.DATELINE;
+    public static final double DATELINE = 180;
     // TODO how might we use JtsSpatialContextFactory to configure the context (esp. for non-geo)?
     public static final JtsSpatialContext SPATIAL_CONTEXT = JtsSpatialContext.GEO;
     public static final GeometryFactory FACTORY = SPATIAL_CONTEXT.getGeometryFactory();
@@ -84,8 +84,8 @@ public abstract class ShapeBuilder implements ToXContent {
         this.orientation = orientation;
     }
 
-    protected static GeoPoint coordinate(double longitude, double latitude) {
-        return new GeoPoint(latitude, longitude);
+    protected static Coordinate coordinate(double longitude, double latitude) {
+        return new Coordinate(longitude, latitude);
     }
 
     protected JtsGeometry jtsGeometry(Geometry geom) {
@@ -106,15 +106,15 @@ public abstract class ShapeBuilder implements ToXContent {
      * @return a new {@link PointBuilder}
      */
     public static PointBuilder newPoint(double longitude, double latitude) {
-        return newPoint(new GeoPoint(latitude, longitude));
+        return newPoint(new Coordinate(longitude, latitude));
     }
 
     /**
-     * Create a new {@link PointBuilder} from a {@link GeoPoint}
+     * Create a new {@link PointBuilder} from a {@link Coordinate}
      * @param coordinate coordinate defining the position of the point
      * @return a new {@link PointBuilder}
      */
-    public static PointBuilder newPoint(GeoPoint coordinate) {
+    public static PointBuilder newPoint(Coordinate coordinate) {
         return new PointBuilder().coordinate(coordinate);
     }
 
@@ -250,7 +250,7 @@ public abstract class ShapeBuilder implements ToXContent {
             token = parser.nextToken();
             double lat = parser.doubleValue();
             token = parser.nextToken();
-            return new CoordinateNode(new GeoPoint(lat, lon));
+            return new CoordinateNode(new Coordinate(lon, lat));
         } else if (token == XContentParser.Token.VALUE_NULL) {
             throw new ElasticsearchIllegalArgumentException("coordinates cannot contain NULL values)");
         }
@@ -289,7 +289,7 @@ public abstract class ShapeBuilder implements ToXContent {
         return GeoShapeType.parse(parser, geoDocMapper);
     }
 
-    protected static XContentBuilder toXContent(XContentBuilder builder, GeoPoint coordinate) throws IOException {
+    protected static XContentBuilder toXContent(XContentBuilder builder, Coordinate coordinate) throws IOException {
         return builder.startArray().value(coordinate.x).value(coordinate.y).endArray();
     }
 
@@ -309,11 +309,11 @@ public abstract class ShapeBuilder implements ToXContent {
         }
     }
 
-    protected static GeoPoint shift(GeoPoint coordinate, double dateline) {
+    protected static Coordinate shift(Coordinate coordinate, double dateline) {
         if (dateline == 0) {
             return coordinate;
         } else {
-            return new GeoPoint(coordinate.y, -2 * dateline + coordinate.x);
+            return new Coordinate(-2 * dateline + coordinate.x, coordinate.y);
         }
     }
 
@@ -325,7 +325,7 @@ public abstract class ShapeBuilder implements ToXContent {
 
     /**
      * Calculate the intersection of a line segment and a vertical dateline.
-     *
+     * 
      * @param p1
      *            start-point of the line segment
      * @param p2
@@ -336,7 +336,7 @@ public abstract class ShapeBuilder implements ToXContent {
      *         segment intersects with the line segment. Otherwise this method
      *         returns {@link Double#NaN}
      */
-    protected static final double intersection(GeoPoint p1, GeoPoint p2, double dateline) {
+    protected static final double intersection(Coordinate p1, Coordinate p2, double dateline) {
         if (p1.x == p2.x && p1.x != dateline) {
             return Double.NaN;
         } else if (p1.x == p2.x && p1.x == dateline) {
@@ -366,8 +366,8 @@ public abstract class ShapeBuilder implements ToXContent {
         int numIntersections = 0;
         assert !Double.isNaN(dateline);
         for (int i = 0; i < edges.length; i++) {
-            GeoPoint p1 = edges[i].coordinate;
-            GeoPoint p2 = edges[i].next.coordinate;
+            Coordinate p1 = edges[i].coordinate;
+            Coordinate p2 = edges[i].next.coordinate;
             assert !Double.isNaN(p2.x) && !Double.isNaN(p1.x);  
             edges[i].intersect = Edge.MAX_COORDINATE;
 
@@ -384,21 +384,21 @@ public abstract class ShapeBuilder implements ToXContent {
     /**
      * Node used to represent a tree of coordinates.
      * <p/>
-     * Can either be a leaf node consisting of a GeoPoint, or a parent with
+     * Can either be a leaf node consisting of a Coordinate, or a parent with
      * children
      */
     protected static class CoordinateNode implements ToXContent {
 
-        protected final GeoPoint coordinate;
+        protected final Coordinate coordinate;
         protected final List<CoordinateNode> children;
 
         /**
          * Creates a new leaf CoordinateNode
          * 
          * @param coordinate
-         *            GeoPoint for the Node
+         *            Coordinate for the Node
          */
-        protected CoordinateNode(GeoPoint coordinate) {
+        protected CoordinateNode(Coordinate coordinate) {
             this.coordinate = coordinate;
             this.children = null;
         }
@@ -434,17 +434,17 @@ public abstract class ShapeBuilder implements ToXContent {
     }
 
     /**
-     * This helper class implements a linked list for {@link GeoPoint}. It contains
+     * This helper class implements a linked list for {@link Coordinate}. It contains
      * fields for a dateline intersection and component id 
      */
     protected static final class Edge {
-        GeoPoint coordinate; // coordinate of the start point
+        Coordinate coordinate; // coordinate of the start point
         Edge next; // next segment
-        GeoPoint intersect; // potential intersection with dateline
+        Coordinate intersect; // potential intersection with dateline
         int component = -1; // id of the component this edge belongs to
-        public static final GeoPoint MAX_COORDINATE = new GeoPoint(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+        public static final Coordinate MAX_COORDINATE = new Coordinate(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
 
-        protected Edge(GeoPoint coordinate, Edge next, GeoPoint intersection) {
+        protected Edge(Coordinate coordinate, Edge next, Coordinate intersection) {
             this.coordinate = coordinate;
             this.next = next;
             this.intersect = intersection;
@@ -453,11 +453,11 @@ public abstract class ShapeBuilder implements ToXContent {
             }
         }
 
-        protected Edge(GeoPoint coordinate, Edge next) {
+        protected Edge(Coordinate coordinate, Edge next) {
             this(coordinate, next, Edge.MAX_COORDINATE);
         }
 
-        private static final int top(GeoPoint[] points, int offset, int length) {
+        private static final int top(Coordinate[] points, int offset, int length) {
             int top = 0; // we start at 1 here since top points to 0
             for (int i = 1; i < length; i++) {
                 if (points[offset + i].y < points[offset + top].y) {
@@ -471,6 +471,29 @@ public abstract class ShapeBuilder implements ToXContent {
             return top;
         }
 
+        private static final Pair range(Coordinate[] points, int offset, int length) {
+            double minX = points[0].x;
+            double maxX = points[0].x;
+            double minY = points[0].y;
+            double maxY = points[0].y;
+            // compute the bounding coordinates (@todo: cleanup brute force)
+            for (int i = 1; i < length; ++i) {
+                if (points[offset + i].x < minX) {
+                    minX = points[offset + i].x;
+                }
+                if (points[offset + i].x > maxX) {
+                    maxX = points[offset + i].x;
+                }
+                if (points[offset + i].y < minY) {
+                    minY = points[offset + i].y;
+                }
+                if (points[offset + i].y > maxY) {
+                    maxY = points[offset + i].y;
+                }
+            }
+            return Pair.of(Pair.of(minX, maxX), Pair.of(minY, maxY));
+        }
+
         /**
          * Concatenate a set of points to a polygon
          * 
@@ -480,6 +503,8 @@ public abstract class ShapeBuilder implements ToXContent {
          *            direction of the ring
          * @param points
          *            list of points to concatenate
+         * @param pointOffset
+         *            index of the first point
          * @param edges
          *            Array of edges to write the result to
          * @param edgeOffset
@@ -488,29 +513,27 @@ public abstract class ShapeBuilder implements ToXContent {
          *            number of points to use
          * @return the edges creates
          */
-        private static Edge[] concat(int component, boolean direction, GeoPoint[] points, Edge[] edges, final int edgeOffset,
-                                     int length) {
+        private static Edge[] concat(int component, boolean direction, Coordinate[] points, final int pointOffset, Edge[] edges, final int edgeOffset,
+                int length) {
             assert edges.length >= length+edgeOffset;
-            assert points.length >= length;
-            edges[edgeOffset] = new Edge(points[0], null);
-            int edgeEnd = edgeOffset + length;
-
-            for (int i = edgeOffset+1, p = 1; i < edgeEnd; ++i, ++p) {
+            assert points.length >= length+pointOffset;
+            edges[edgeOffset] = new Edge(points[pointOffset], null);
+            for (int i = 1; i < length; i++) {
                 if (direction) {
-                    edges[i] = new Edge(points[p], edges[i - 1]);
-                    edges[i].component = component;
+                    edges[edgeOffset + i] = new Edge(points[pointOffset + i], edges[edgeOffset + i - 1]);
+                    edges[edgeOffset + i].component = component;
                 } else {
-                    edges[i - 1].next = edges[i] = new Edge(points[p], null);
-                    edges[i - 1].component = component;
+                    edges[edgeOffset + i - 1].next = edges[edgeOffset + i] = new Edge(points[pointOffset + i], null);
+                    edges[edgeOffset + i - 1].component = component;
                 }
             }
 
             if (direction) {
-                edges[edgeOffset].next = edges[edgeEnd - 1];
+                edges[edgeOffset].next = edges[edgeOffset + length - 1];
                 edges[edgeOffset].component = component;
             } else {
-                edges[edgeEnd - 1].next = edges[edgeOffset];
-                edges[edgeEnd - 1].component = component;
+                edges[edgeOffset + length - 1].next = edges[edgeOffset];
+                edges[edgeOffset + length - 1].component = component;
             }
 
             return edges;
@@ -521,25 +544,60 @@ public abstract class ShapeBuilder implements ToXContent {
          * 
          * @param points
          *            array of point
+         * @param offset
+         *            index of the first point
          * @param length
          *            number of points
          * @return Array of edges
          */
         protected static Edge[] ring(int component, boolean direction, boolean handedness, BaseLineStringBuilder<?> shell,
-                                     GeoPoint[] points, Edge[] edges, int edgeOffset, int length) {
+                                     Coordinate[] points, int offset, Edge[] edges, int toffset, int length) {
             // calculate the direction of the points:
-            boolean orientation = GeoUtils.computePolyOrientation(points, length);
-            boolean corrected = GeoUtils.correctPolyAmbiguity(points, handedness, orientation, component, length,
-                    shell.translated);
-
-            // correct the orientation post translation (ccw for shell, cw for holes)
-            if (corrected && (component == 0 || (component != 0 && handedness == orientation))) {
+            // find the point a the top of the set and check its
+            // neighbors orientation. So direction is equivalent
+            // to clockwise/counterclockwise
+            final int top = top(points, offset, length);
+            final int prev = (offset + ((top + length - 1) % length));
+            final int next = (offset + ((top + 1) % length));
+            boolean orientation = points[offset + prev].x > points[offset + next].x;
+
+            // OGC requires shell as ccw (Right-Handedness) and holes as cw (Left-Handedness) 
+            // since GeoJSON doesn't specify (and doesn't need to) GEO core will assume OGC standards
+            // thus if orientation is computed as cw, the logic will translate points across dateline
+            // and convert to a right handed system
+
+            // compute the bounding box and calculate range
+            Pair<Pair, Pair> range = range(points, offset, length);
+            final double rng = (Double)range.getLeft().getRight() - (Double)range.getLeft().getLeft();
+            // translate the points if the following is true
+            //   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)
+            //   2.  the shell of the candidate hole has been translated (to preserve the coordinate system)
+            boolean incorrectOrientation = component == 0 && handedness != orientation;
+            if ( (incorrectOrientation && (rng > DATELINE && rng != 2*DATELINE)) || (shell.translated && component != 0)) {
+                translate(points);
+                // flip the translation bit if the shell is being translated
                 if (component == 0) {
-                    shell.translated = corrected;
+                    shell.translated = true;
+                }
+                // correct the orientation post translation (ccw for shell, cw for holes)
+                if (component == 0 || (component != 0 && handedness == orientation)) {
+                    orientation = !orientation;
+                }
+            }
+            return concat(component, direction ^ orientation, points, offset, edges, toffset, length);
+        }
+
+        /**
+         * Transforms coordinates in the eastern hemisphere (-180:0) to a (180:360) range 
+         * @param points
+         */
+        protected static void translate(Coordinate[] points) {
+            for (Coordinate c : points) {
+                if (c.x < 0) {
+                    c.x += 2*DATELINE;
                 }
-                orientation = !orientation;
             }
-            return concat(component, direction ^ orientation, points, edges, edgeOffset, length);
         }
 
         /**
@@ -547,13 +605,13 @@ public abstract class ShapeBuilder implements ToXContent {
          * 
          * @param position
          *            position of the intersection [0..1]
-         * @return the {@link GeoPoint} of the intersection
+         * @return the {@link Coordinate} of the intersection
          */
-        protected GeoPoint intersection(double position) {
+        protected Coordinate intersection(double position) {
             return intersect = position(coordinate, next.coordinate, position);
         }
 
-        public static GeoPoint position(GeoPoint p1, GeoPoint p2, double position) {
+        public static Coordinate position(Coordinate p1, Coordinate p2, double position) {
             if (position == 0) {
                 return p1;
             } else if (position == 1) {
@@ -561,7 +619,7 @@ public abstract class ShapeBuilder implements ToXContent {
             } else {
                 final double x = p1.x + position * (p2.x - p1.x);
                 final double y = p1.y + position * (p2.y - p1.y);
-                return new GeoPoint(y, x);
+                return new Coordinate(x, y);
             }
         }
 
@@ -735,12 +793,12 @@ public abstract class ShapeBuilder implements ToXContent {
                         "geo_shape ('envelope') when expecting an array of 2 coordinates");
             }
             // verify coordinate bounds, correct if necessary
-            GeoPoint uL = coordinates.children.get(0).coordinate;
-            GeoPoint lR = coordinates.children.get(1).coordinate;
+            Coordinate uL = coordinates.children.get(0).coordinate;
+            Coordinate lR = coordinates.children.get(1).coordinate;
             if (((lR.x < uL.x) || (uL.y < lR.y))) {
-                GeoPoint uLtmp = uL;
-                uL = new GeoPoint(Math.max(uL.y, lR.y), Math.min(uL.x, lR.x));
-                lR = new GeoPoint(Math.min(uLtmp.y, lR.y), Math.max(uLtmp.x, lR.x));
+                Coordinate uLtmp = uL;
+                uL = new Coordinate(Math.min(uL.x, lR.x), Math.max(uL.y, lR.y));
+                lR = new Coordinate(Math.max(uLtmp.x, lR.x), Math.min(uLtmp.y, lR.y));
             }
             return newEnvelope(orientation).topLeft(uL).bottomRight(lR);
         }

+ 0 - 4
src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java

@@ -294,10 +294,6 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
         if (includeDefaults || defaultStrategy.getDistErrPct() != Defaults.DISTANCE_ERROR_PCT) {
             builder.field(Names.DISTANCE_ERROR_PCT, defaultStrategy.getDistErrPct());
         }
-
-        if (includeDefaults || shapeOrientation != Defaults.ORIENTATION ) {
-            builder.field(Names.ORIENTATION, shapeOrientation);
-        }
     }
 
     @Override

+ 5 - 18
src/main/java/org/elasticsearch/index/search/geo/GeoPolygonFilter.java

@@ -29,7 +29,6 @@ import org.apache.lucene.search.Filter;
 import org.apache.lucene.util.Bits;
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.geo.GeoPoint;
-import org.elasticsearch.common.geo.GeoUtils;
 import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
 import org.elasticsearch.index.fielddata.MultiGeoPointValues;
 
@@ -94,27 +93,15 @@ public class GeoPolygonFilter extends Filter {
 
         private static boolean pointInPolygon(GeoPoint[] points, double lat, double lon) {
             boolean inPoly = false;
-            // @TODO handedness will be an option provided by the parser
-            boolean corrected = GeoUtils.correctPolyAmbiguity(points, false);
-            GeoPoint p = (corrected) ?
-                    GeoUtils.convertToGreatCircle(lat, lon) :
-                    new GeoPoint(lat, lon);
-
-            GeoPoint pp0 = (corrected) ? GeoUtils.convertToGreatCircle(points[0]) : points[0] ;
-            GeoPoint pp1;
-            // simple even-odd PIP computation
-            //   1.  Determine if point is contained in the longitudinal range
-            //   2.  Determine whether point crosses the edge by computing the latitudinal delta
-            //       between the end-point of a parallel vector (originating at the point) and the
-            //       y-component of the edge sink
+
             for (int i = 1; i < points.length; i++) {
-                pp1 = points[i];
-                if (pp1.x < p.x && pp0.x >= p.x || pp0.x < p.x && pp1.x >= p.x) {
-                    if (pp1.y + (p.x - pp1.x) / (pp0.x - pp1.x) * (pp0.y - pp1.y) < p.y) {
+                if (points[i].lon() < lon && points[i-1].lon() >= lon
+                        || points[i-1].lon() < lon && points[i].lon() >= lon) {
+                    if (points[i].lat() + (lon - points[i].lon()) /
+                            (points[i-1].lon() - points[i].lon()) * (points[i-1].lat() - points[i].lat()) < lat) {
                         inPoly = !inPoly;
                     }
                 }
-                pp0 = pp1;
             }
             return inPoly;
         }

+ 70 - 76
src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java

@@ -26,13 +26,7 @@ import com.spatial4j.core.shape.Shape;
 import com.spatial4j.core.shape.ShapeCollection;
 import com.spatial4j.core.shape.jts.JtsGeometry;
 import com.spatial4j.core.shape.jts.JtsPoint;
-import com.vividsolutions.jts.geom.Geometry;
-import com.vividsolutions.jts.geom.GeometryFactory;
-import com.vividsolutions.jts.geom.LineString;
-import com.vividsolutions.jts.geom.LinearRing;
-import com.vividsolutions.jts.geom.MultiLineString;
-import com.vividsolutions.jts.geom.Point;
-import com.vividsolutions.jts.geom.Polygon;
+import com.vividsolutions.jts.geom.*;
 import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.ElasticsearchParseException;
 import org.elasticsearch.common.geo.builders.ShapeBuilder;
@@ -63,7 +57,7 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
                 .startArray("coordinates").value(100.0).value(0.0).endArray()
                 .endObject().string();
 
-        Point expected = GEOMETRY_FACTORY.createPoint(new GeoPoint(0.0, 100.0));
+        Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
         assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson);
     }
 
@@ -75,12 +69,12 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
                 .endArray()
                 .endObject().string();
 
-        List<GeoPoint> lineCoordinates = new ArrayList<>();
-        lineCoordinates.add(new GeoPoint(0, 100));
-        lineCoordinates.add(new GeoPoint(1, 101));
+        List<Coordinate> lineCoordinates = new ArrayList<>();
+        lineCoordinates.add(new Coordinate(100, 0));
+        lineCoordinates.add(new Coordinate(101, 1));
 
         LineString expected = GEOMETRY_FACTORY.createLineString(
-                lineCoordinates.toArray(new GeoPoint[lineCoordinates.size()]));
+                lineCoordinates.toArray(new Coordinate[lineCoordinates.size()]));
         assertGeometryEquals(jtsGeom(expected), lineGeoJson);
     }
 
@@ -99,13 +93,13 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
                 .endObject().string();
 
         MultiLineString expected = GEOMETRY_FACTORY.createMultiLineString(new LineString[]{
-                GEOMETRY_FACTORY.createLineString(new GeoPoint[]{
-                        new GeoPoint(0, 100),
-                        new GeoPoint(1, 101),
+                GEOMETRY_FACTORY.createLineString(new Coordinate[]{
+                        new Coordinate(100, 0),
+                        new Coordinate(101, 1),
                 }),
-                GEOMETRY_FACTORY.createLineString(new GeoPoint[]{
-                        new GeoPoint(2, 102),
-                        new GeoPoint(3, 103),
+                GEOMETRY_FACTORY.createLineString(new Coordinate[]{
+                        new Coordinate(102, 2),
+                        new Coordinate(103, 3),
                 }),
         });
         assertGeometryEquals(jtsGeom(expected), multilinesGeoJson);
@@ -179,14 +173,14 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
                 .endArray()
                 .endObject().string();
 
-        List<GeoPoint> shellCoordinates = new ArrayList<>();
-        shellCoordinates.add(new GeoPoint(0, 100));
-        shellCoordinates.add(new GeoPoint(0, 101));
-        shellCoordinates.add(new GeoPoint(1, 101));
-        shellCoordinates.add(new GeoPoint(1, 100));
-        shellCoordinates.add(new GeoPoint(0, 100));
+        List<Coordinate> shellCoordinates = new ArrayList<>();
+        shellCoordinates.add(new Coordinate(100, 0));
+        shellCoordinates.add(new Coordinate(101, 0));
+        shellCoordinates.add(new Coordinate(101, 1));
+        shellCoordinates.add(new Coordinate(100, 1));
+        shellCoordinates.add(new Coordinate(100, 0));
 
-        LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new GeoPoint[shellCoordinates.size()]));
+        LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
         Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null);
         assertGeometryEquals(jtsGeom(expected), polygonGeoJson);
     }
@@ -573,25 +567,25 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
                 .endArray()
                 .endObject().string();
 
-        List<GeoPoint> shellCoordinates = new ArrayList<>();
-        shellCoordinates.add(new GeoPoint(0, 100));
-        shellCoordinates.add(new GeoPoint(0, 101));
-        shellCoordinates.add(new GeoPoint(1, 101));
-        shellCoordinates.add(new GeoPoint(1, 100));
-        shellCoordinates.add(new GeoPoint(0, 100));
+        List<Coordinate> shellCoordinates = new ArrayList<>();
+        shellCoordinates.add(new Coordinate(100, 0));
+        shellCoordinates.add(new Coordinate(101, 0));
+        shellCoordinates.add(new Coordinate(101, 1));
+        shellCoordinates.add(new Coordinate(100, 1));
+        shellCoordinates.add(new Coordinate(100, 0));
 
-        List<GeoPoint> holeCoordinates = new ArrayList<>();
-        holeCoordinates.add(new GeoPoint(0.2, 100.2));
-        holeCoordinates.add(new GeoPoint(0.2, 100.8));
-        holeCoordinates.add(new GeoPoint(0.8, 100.8));
-        holeCoordinates.add(new GeoPoint(0.8, 100.2));
-        holeCoordinates.add(new GeoPoint(0.2, 100.2));
+        List<Coordinate> holeCoordinates = new ArrayList<>();
+        holeCoordinates.add(new Coordinate(100.2, 0.2));
+        holeCoordinates.add(new Coordinate(100.8, 0.2));
+        holeCoordinates.add(new Coordinate(100.8, 0.8));
+        holeCoordinates.add(new Coordinate(100.2, 0.8));
+        holeCoordinates.add(new Coordinate(100.2, 0.2));
 
         LinearRing shell = GEOMETRY_FACTORY.createLinearRing(
-                shellCoordinates.toArray(new GeoPoint[shellCoordinates.size()]));
+                shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
         LinearRing[] holes = new LinearRing[1];
         holes[0] = GEOMETRY_FACTORY.createLinearRing(
-                holeCoordinates.toArray(new GeoPoint[holeCoordinates.size()]));
+                holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
         Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, holes);
         assertGeometryEquals(jtsGeom(expected), polygonGeoJson);
     }
@@ -663,34 +657,34 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
                 .endArray()
                 .endObject().string();
 
-        List<GeoPoint> shellCoordinates = new ArrayList<>();
-        shellCoordinates.add(new GeoPoint(0, 100));
-        shellCoordinates.add(new GeoPoint(0, 101));
-        shellCoordinates.add(new GeoPoint(1, 101));
-        shellCoordinates.add(new GeoPoint(1, 100));
-        shellCoordinates.add(new GeoPoint(0, 100));
+        List<Coordinate> shellCoordinates = new ArrayList<>();
+        shellCoordinates.add(new Coordinate(100, 0));
+        shellCoordinates.add(new Coordinate(101, 0));
+        shellCoordinates.add(new Coordinate(101, 1));
+        shellCoordinates.add(new Coordinate(100, 1));
+        shellCoordinates.add(new Coordinate(100, 0));
 
-        List<GeoPoint> holeCoordinates = new ArrayList<>();
-        holeCoordinates.add(new GeoPoint(0.2, 100.2));
-        holeCoordinates.add(new GeoPoint(0.2, 100.8));
-        holeCoordinates.add(new GeoPoint(0.8, 100.8));
-        holeCoordinates.add(new GeoPoint(0.8, 100.2));
-        holeCoordinates.add(new GeoPoint(0.2, 100.2));
+        List<Coordinate> holeCoordinates = new ArrayList<>();
+        holeCoordinates.add(new Coordinate(100.2, 0.2));
+        holeCoordinates.add(new Coordinate(100.8, 0.2));
+        holeCoordinates.add(new Coordinate(100.8, 0.8));
+        holeCoordinates.add(new Coordinate(100.2, 0.8));
+        holeCoordinates.add(new Coordinate(100.2, 0.2));
 
-        LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new GeoPoint[shellCoordinates.size()]));
+        LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
         LinearRing[] holes = new LinearRing[1];
-        holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new GeoPoint[holeCoordinates.size()]));
+        holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
         Polygon withHoles = GEOMETRY_FACTORY.createPolygon(shell, holes);
 
         shellCoordinates = new ArrayList<>();
-        shellCoordinates.add(new GeoPoint(3, 102));
-        shellCoordinates.add(new GeoPoint(3, 103));
-        shellCoordinates.add(new GeoPoint(2, 103));
-        shellCoordinates.add(new GeoPoint(2, 102));
-        shellCoordinates.add(new GeoPoint(3, 102));
+        shellCoordinates.add(new Coordinate(102, 3));
+        shellCoordinates.add(new Coordinate(103, 3));
+        shellCoordinates.add(new Coordinate(103, 2));
+        shellCoordinates.add(new Coordinate(102, 2));
+        shellCoordinates.add(new Coordinate(102, 3));
 
 
-        shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new GeoPoint[shellCoordinates.size()]));
+        shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
         Polygon withoutHoles = GEOMETRY_FACTORY.createPolygon(shell, null);
 
         Shape expected = shapeCollection(withoutHoles, withHoles);
@@ -722,22 +716,22 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
                 .endObject().string();
 
         shellCoordinates = new ArrayList<>();
-        shellCoordinates.add(new GeoPoint(1, 100));
-        shellCoordinates.add(new GeoPoint(1, 101));
-        shellCoordinates.add(new GeoPoint(0, 101));
-        shellCoordinates.add(new GeoPoint(0, 100));
-        shellCoordinates.add(new GeoPoint(1, 100));
+        shellCoordinates.add(new Coordinate(100, 1));
+        shellCoordinates.add(new Coordinate(101, 1));
+        shellCoordinates.add(new Coordinate(101, 0));
+        shellCoordinates.add(new Coordinate(100, 0));
+        shellCoordinates.add(new Coordinate(100, 1));
 
         holeCoordinates = new ArrayList<>();
-        holeCoordinates.add(new GeoPoint(0.8, 100.2));
-        holeCoordinates.add(new GeoPoint(0.2, 100.2));
-        holeCoordinates.add(new GeoPoint(0.2, 100.8));
-        holeCoordinates.add(new GeoPoint(0.8, 100.8));
-        holeCoordinates.add(new GeoPoint(0.8, 100.2));
+        holeCoordinates.add(new Coordinate(100.2, 0.8));
+        holeCoordinates.add(new Coordinate(100.2, 0.2));
+        holeCoordinates.add(new Coordinate(100.8, 0.2));
+        holeCoordinates.add(new Coordinate(100.8, 0.8));
+        holeCoordinates.add(new Coordinate(100.2, 0.8));
 
-        shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new GeoPoint[shellCoordinates.size()]));
+        shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
         holes = new LinearRing[1];
-        holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new GeoPoint[holeCoordinates.size()]));
+        holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
         withHoles = GEOMETRY_FACTORY.createPolygon(shell, holes);
 
         assertGeometryEquals(jtsGeom(withHoles), multiPolygonGeoJson);
@@ -763,12 +757,12 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
                 .string();
 
         Shape[] expected = new Shape[2];
-        LineString expectedLineString = GEOMETRY_FACTORY.createLineString(new GeoPoint[]{
-                new GeoPoint(0, 100),
-                new GeoPoint(1, 101),
+        LineString expectedLineString = GEOMETRY_FACTORY.createLineString(new Coordinate[]{
+                new Coordinate(100, 0),
+                new Coordinate(101, 1),
         });
         expected[0] = jtsGeom(expectedLineString);
-        Point expectedPoint = GEOMETRY_FACTORY.createPoint(new GeoPoint(2.0, 102.0));
+        Point expectedPoint = GEOMETRY_FACTORY.createPoint(new Coordinate(102.0, 2.0));
         expected[1] = new JtsPoint(expectedPoint, SPATIAL_CONTEXT);
 
         //equals returns true only if geometries are in the same order
@@ -791,7 +785,7 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase {
                 .startObject("lala").field("type", "NotAPoint").endObject()
                 .endObject().string();
 
-        Point expected = GEOMETRY_FACTORY.createPoint(new GeoPoint(0.0, 100.0));
+        Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
         assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson);
     }
 

+ 19 - 19
src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java

@@ -24,6 +24,7 @@ import com.spatial4j.core.shape.Point;
 import com.spatial4j.core.shape.Rectangle;
 import com.spatial4j.core.shape.Shape;
 import com.spatial4j.core.shape.impl.PointImpl;
+import com.vividsolutions.jts.geom.Coordinate;
 import com.vividsolutions.jts.geom.LineString;
 import com.vividsolutions.jts.geom.Polygon;
 import org.elasticsearch.common.geo.builders.PolygonBuilder;
@@ -63,39 +64,38 @@ public class ShapeBuilderTests extends ElasticsearchTestCase {
                 .point(-45, 30).toPolygon();
 
         LineString exterior = polygon.getExteriorRing();
-        assertEquals(exterior.getCoordinateN(0), new GeoPoint(30, -45));
-        assertEquals(exterior.getCoordinateN(1), new GeoPoint(30, 45));
-        assertEquals(exterior.getCoordinateN(2), new GeoPoint(-30, 45));
-        assertEquals(exterior.getCoordinateN(3), new GeoPoint(-30, -45));
+        assertEquals(exterior.getCoordinateN(0), new Coordinate(-45, 30));
+        assertEquals(exterior.getCoordinateN(1), new Coordinate(45, 30));
+        assertEquals(exterior.getCoordinateN(2), new Coordinate(45, -30));
+        assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30));
     }
 
     @Test
     public void testNewPolygon_coordinate() {
         Polygon polygon = ShapeBuilder.newPolygon()
-                .point(new GeoPoint(30, -45))
-                .point(new GeoPoint(30, 45))
-                .point(new GeoPoint(-30, 45))
-                .point(new GeoPoint(-30, -45))
-                .point(new GeoPoint(30, -45)).toPolygon();
+                .point(new Coordinate(-45, 30))
+                .point(new Coordinate(45, 30))
+                .point(new Coordinate(45, -30))
+                .point(new Coordinate(-45, -30))
+                .point(new Coordinate(-45, 30)).toPolygon();
 
         LineString exterior = polygon.getExteriorRing();
-        assertEquals(exterior.getCoordinateN(0), new GeoPoint(30, -45));
-        assertEquals(exterior.getCoordinateN(1), new GeoPoint(30, 45));
-        assertEquals(exterior.getCoordinateN(2), new GeoPoint(-30, 45));
-        assertEquals(exterior.getCoordinateN(3), new GeoPoint(-30, -45));
+        assertEquals(exterior.getCoordinateN(0), new Coordinate(-45, 30));
+        assertEquals(exterior.getCoordinateN(1), new Coordinate(45, 30));
+        assertEquals(exterior.getCoordinateN(2), new Coordinate(45, -30));
+        assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30));
     }
 
     @Test
     public void testNewPolygon_coordinates() {
         Polygon polygon = ShapeBuilder.newPolygon()
-                .points(new GeoPoint(30, -45), new GeoPoint(30, 45), new GeoPoint(-30, 45), new GeoPoint(-30, -45),
-                        new GeoPoint(30, -45)).toPolygon();
+                .points(new Coordinate(-45, 30), new Coordinate(45, 30), new Coordinate(45, -30), new Coordinate(-45, -30), new Coordinate(-45, 30)).toPolygon();
 
         LineString exterior = polygon.getExteriorRing();
-        assertEquals(exterior.getCoordinateN(0), new GeoPoint(30, -45));
-        assertEquals(exterior.getCoordinateN(1), new GeoPoint(30, 45));
-        assertEquals(exterior.getCoordinateN(2), new GeoPoint(-30, 45));
-        assertEquals(exterior.getCoordinateN(3), new GeoPoint(-30, -45));
+        assertEquals(exterior.getCoordinateN(0), new Coordinate(-45, 30));
+        assertEquals(exterior.getCoordinateN(1), new Coordinate(45, 30));
+        assertEquals(exterior.getCoordinateN(2), new Coordinate(45, -30));
+        assertEquals(exterior.getCoordinateN(3), new Coordinate(-45, -30));
     }
     
     @Test