diff --git a/src/main/java/org/elasticsearch/common/geo/GeoPoint.java b/src/main/java/org/elasticsearch/common/geo/GeoPoint.java index 0e1d6961de5..3a8ccf45d98 100644 --- a/src/main/java/org/elasticsearch/common/geo/GeoPoint.java +++ b/src/main/java/org/elasticsearch/common/geo/GeoPoint.java @@ -20,13 +20,12 @@ package org.elasticsearch.common.geo; +import com.vividsolutions.jts.geom.Coordinate; + /** * */ -public final class GeoPoint { - - private double lat; - private double lon; +public final class GeoPoint extends Coordinate { public GeoPoint() { } @@ -41,32 +40,36 @@ public final class GeoPoint { this.resetFromString(value); } + public GeoPoint(GeoPoint other) { + super(other); + } + public GeoPoint(double lat, double lon) { - this.lat = lat; - this.lon = lon; + this.y = lat; + this.x = lon; } public GeoPoint reset(double lat, double lon) { - this.lat = lat; - this.lon = lon; + this.y = lat; + this.x = lon; return this; } public GeoPoint resetLat(double lat) { - this.lat = lat; + this.y = lat; return this; } public GeoPoint resetLon(double lon) { - this.lon = lon; + this.x = lon; return this; } public GeoPoint resetFromString(String value) { int comma = value.indexOf(','); if (comma != -1) { - lat = Double.parseDouble(value.substring(0, comma).trim()); - lon = Double.parseDouble(value.substring(comma + 1).trim()); + this.y = Double.parseDouble(value.substring(0, comma).trim()); + this.x = Double.parseDouble(value.substring(comma + 1).trim()); } else { resetFromGeoHash(value); } @@ -79,38 +82,40 @@ public final class GeoPoint { } public final double lat() { - return this.lat; + return this.y; } public final double getLat() { - return this.lat; + return this.y; } public final double lon() { - return this.lon; + return this.x; } public final double getLon() { - return this.lon; + return this.x; } public final String geohash() { - return GeoHashUtils.encode(lat, lon); + return GeoHashUtils.encode(y, x); } public final String getGeohash() { - return GeoHashUtils.encode(lat, lon); + return GeoHashUtils.encode(y, x); } @Override public boolean equals(Object o) { if (this == o) return true; - 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; + 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; return true; } @@ -119,15 +124,15 @@ public final class GeoPoint { public int hashCode() { int result; long temp; - temp = lat != +0.0d ? Double.doubleToLongBits(lat) : 0L; + temp = y != +0.0d ? Double.doubleToLongBits(y) : 0L; result = (int) (temp ^ (temp >>> 32)); - temp = lon != +0.0d ? Double.doubleToLongBits(lon) : 0L; + temp = x != +0.0d ? Double.doubleToLongBits(x) : 0L; result = 31 * result + (int) (temp ^ (temp >>> 32)); return result; } public String toString() { - return "[" + lat + ", " + lon + "]"; + return "[" + y + ", " + x + "]"; } public static GeoPoint parseFromLatLon(String latLon) { diff --git a/src/main/java/org/elasticsearch/common/geo/GeoUtils.java b/src/main/java/org/elasticsearch/common/geo/GeoUtils.java index c4bc51d7bfb..5128ab0fc1a 100644 --- a/src/main/java/org/elasticsearch/common/geo/GeoUtils.java +++ b/src/main/java/org/elasticsearch/common/geo/GeoUtils.java @@ -19,6 +19,7 @@ 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; @@ -37,7 +38,9 @@ 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) @@ -422,6 +425,113 @@ 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 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() { } } diff --git a/src/main/java/org/elasticsearch/common/geo/builders/BaseLineStringBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/BaseLineStringBuilder.java index bf5beb9beb9..5580822c9bf 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/BaseLineStringBuilder.java +++ b/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 com.spatial4j.core.shape.ShapeCollection; +import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.geo.GeoUtils; 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> extends PointCollection { protected BaseLineStringBuilder() { - this(new ArrayList()); + this(new ArrayList()); } - protected BaseLineStringBuilder(ArrayList points) { + protected BaseLineStringBuilder(ArrayList points) { super(points); } @@ -49,7 +49,7 @@ public abstract class BaseLineStringBuilder> @Override public Shape build() { - Coordinate[] coordinates = points.toArray(new Coordinate[points.size()]); + GeoPoint[] coordinates = points.toArray(new GeoPoint[points.size()]); Geometry geometry; if(wrapdateline) { ArrayList strings = decompose(FACTORY, coordinates, new ArrayList()); @@ -67,9 +67,9 @@ public abstract class BaseLineStringBuilder> return jtsGeometry(geometry); } - protected static ArrayList decompose(GeometryFactory factory, Coordinate[] coordinates, ArrayList strings) { - for(Coordinate[] part : decompose(+DATELINE, coordinates)) { - for(Coordinate[] line : decompose(-DATELINE, part)) { + protected static ArrayList decompose(GeometryFactory factory, GeoPoint[] coordinates, ArrayList strings) { + for(GeoPoint[] part : decompose(+DATELINE, coordinates)) { + for(GeoPoint[] line : decompose(-DATELINE, part)) { strings.add(factory.createLineString(line)); } } @@ -83,16 +83,16 @@ public abstract class BaseLineStringBuilder> * @param coordinates coordinates forming the linestring * @return array of linestrings given as coordinate arrays */ - protected static Coordinate[][] decompose(double dateline, Coordinate[] coordinates) { + protected static GeoPoint[][] decompose(double dateline, GeoPoint[] coordinates) { int offset = 0; - ArrayList parts = new ArrayList<>(); + ArrayList 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)) { - Coordinate[] part; + GeoPoint[] 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> if(offset == 0) { parts.add(shift(shift, coordinates)); } else if(offset < coordinates.length-1) { - Coordinate[] part = Arrays.copyOfRange(coordinates, offset, coordinates.length); + GeoPoint[] part = Arrays.copyOfRange(coordinates, offset, coordinates.length); parts.add(shift(shift, part)); } - return parts.toArray(new Coordinate[parts.size()][]); + return parts.toArray(new GeoPoint[parts.size()][]); } - private static Coordinate[] shift(double shift, Coordinate...coordinates) { + private static GeoPoint[] shift(double shift, GeoPoint...coordinates) { if(shift != 0) { for (int j = 0; j < coordinates.length; j++) { - coordinates[j] = new Coordinate(coordinates[j].x - 2 * shift, coordinates[j].y); + coordinates[j] = new GeoPoint(coordinates[j].y, coordinates[j].x - 2 * shift); } } return coordinates; diff --git a/src/main/java/org/elasticsearch/common/geo/builders/BasePolygonBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/BasePolygonBuilder.java index 2981145b90a..c53467a862e 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/BasePolygonBuilder.java +++ b/src/main/java/org/elasticsearch/common/geo/builders/BasePolygonBuilder.java @@ -20,8 +20,14 @@ package org.elasticsearch.common.geo.builders; import com.spatial4j.core.shape.Shape; -import com.vividsolutions.jts.geom.*; +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 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; @@ -67,7 +73,7 @@ public abstract class BasePolygonBuilder> extend * @param coordinate coordinate of the new point * @return this */ - public E point(Coordinate coordinate) { + public E point(GeoPoint coordinate) { shell.point(coordinate); return thisRef(); } @@ -77,7 +83,7 @@ public abstract class BasePolygonBuilder> extend * @param coordinates coordinates of the new points to add * @return this */ - public E points(Coordinate...coordinates) { + public E points(GeoPoint...coordinates) { shell.points(coordinates); return thisRef(); } @@ -121,7 +127,7 @@ public abstract class BasePolygonBuilder> extend * * @return coordinates of the polygon */ - public Coordinate[][][] coordinates() { + public GeoPoint[][][] 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; @@ -170,7 +176,7 @@ public abstract class BasePolygonBuilder> extend public Geometry buildGeometry(GeometryFactory factory, boolean fixDateline) { if(fixDateline) { - Coordinate[][][] polygons = coordinates(); + GeoPoint[][][] polygons = coordinates(); return polygons.length == 1 ? polygon(factory, polygons[0]) : multipolygon(factory, polygons); @@ -193,8 +199,8 @@ public abstract class BasePolygonBuilder> extend return factory.createPolygon(shell, holes); } - protected static LinearRing linearRing(GeometryFactory factory, ArrayList coordinates) { - return factory.createLinearRing(coordinates.toArray(new Coordinate[coordinates.size()])); + protected static LinearRing linearRing(GeometryFactory factory, ArrayList coordinates) { + return factory.createLinearRing(coordinates.toArray(new GeoPoint[coordinates.size()])); } @Override @@ -202,7 +208,7 @@ public abstract class BasePolygonBuilder> extend return TYPE; } - protected static Polygon polygon(GeometryFactory factory, Coordinate[][] polygon) { + protected static Polygon polygon(GeometryFactory factory, GeoPoint[][] polygon) { LinearRing shell = factory.createLinearRing(polygon[0]); LinearRing[] holes; @@ -227,7 +233,7 @@ public abstract class BasePolygonBuilder> extend * @param polygons definition of polygons * @return a new Multipolygon */ - protected static MultiPolygon multipolygon(GeometryFactory factory, Coordinate[][][] polygons) { + protected static MultiPolygon multipolygon(GeometryFactory factory, GeoPoint[][][] polygons) { Polygon[] polygonSet = new Polygon[polygons.length]; for (int i = 0; i < polygonSet.length; i++) { polygonSet[i] = polygon(factory, polygons[i]); @@ -283,18 +289,18 @@ public abstract class BasePolygonBuilder> extend * @param coordinates Array of coordinates to write the result to * @return the coordinates parameter */ - private static Coordinate[] coordinates(Edge component, Coordinate[] coordinates) { + private static GeoPoint[] coordinates(Edge component, GeoPoint[] coordinates) { for (int i = 0; i < coordinates.length; i++) { coordinates[i] = (component = component.next).coordinate; } return coordinates; } - private static Coordinate[][][] buildCoordinates(ArrayList> components) { - Coordinate[][][] result = new Coordinate[components.size()][][]; + private static GeoPoint[][][] buildCoordinates(ArrayList> components) { + GeoPoint[][][] result = new GeoPoint[components.size()][][]; for (int i = 0; i < result.length; i++) { - ArrayList component = components.get(i); - result[i] = component.toArray(new Coordinate[component.size()][]); + ArrayList component = components.get(i); + result[i] = component.toArray(new GeoPoint[component.size()][]); } if(debugEnabled()) { @@ -309,30 +315,30 @@ public abstract class BasePolygonBuilder> extend return result; } - private static final Coordinate[][] EMPTY = new Coordinate[0][]; + private static final GeoPoint[][] EMPTY = new GeoPoint[0][]; - private static Coordinate[][] holes(Edge[] holes, int numHoles) { + private static GeoPoint[][] holes(Edge[] holes, int numHoles) { if (numHoles == 0) { return EMPTY; } - final Coordinate[][] points = new Coordinate[numHoles][]; + final GeoPoint[][] points = new GeoPoint[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 Coordinate[length+1]); + points[i] = coordinates(holes[i], new GeoPoint[length+1]); } return points; } - private static Edge[] edges(Edge[] edges, int numHoles, ArrayList> components) { + private static Edge[] edges(Edge[] edges, int numHoles, ArrayList> components) { ArrayList 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 component = new ArrayList<>(); - component.add(coordinates(edges[i], new Coordinate[length+1])); + ArrayList component = new ArrayList<>(); + component.add(coordinates(edges[i], new GeoPoint[length+1])); components.add(component); } } @@ -340,13 +346,14 @@ public abstract class BasePolygonBuilder> extend return mainEdges.toArray(new Edge[mainEdges.size()]); } - private static Coordinate[][][] compose(Edge[] edges, Edge[] holes, int numHoles) { - final ArrayList> components = new ArrayList<>(); + private static GeoPoint[][][] compose(Edge[] edges, Edge[] holes, int numHoles) { + final ArrayList> components = new ArrayList<>(); assign(holes, holes(holes, numHoles), numHoles, edges(edges, numHoles, components), components); return buildCoordinates(components); } - private static void assign(Edge[] holes, Coordinate[][] points, int numHoles, Edge[] edges, ArrayList> components) { + private static void assign(Edge[] holes, GeoPoint[][] points, int numHoles, Edge[] edges, ArrayList> + 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 @@ -461,14 +468,13 @@ public abstract class BasePolygonBuilder> extend } private static int createEdges(int component, Orientation orientation, BaseLineStringBuilder shell, - BaseLineStringBuilder hole, - Edge[] edges, int offset) { + BaseLineStringBuilder hole, Edge[] edges, int edgeOffset) { // 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) - 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); + GeoPoint[] points = (hole != null) ? hole.coordinates(false) : shell.coordinates(false); + Edge.ring(component, direction, orientation == Orientation.LEFT, shell, points, edges, edgeOffset, points.length-1); return points.length-1; } @@ -477,17 +483,17 @@ public abstract class BasePolygonBuilder> extend private final P parent; protected Ring(P parent) { - this(parent, new ArrayList()); + this(parent, new ArrayList()); } - protected Ring(P parent, ArrayList points) { + protected Ring(P parent, ArrayList points) { super(points); this.parent = parent; } public P close() { - Coordinate start = points.get(0); - Coordinate end = points.get(points.size()-1); + GeoPoint start = points.get(0); + GeoPoint end = points.get(points.size()-1); if(start.x != end.x || start.y != end.y) { points.add(start); } diff --git a/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java index 27d7318c218..387443f6522 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/CircleBuilder.java +++ b/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 com.vividsolutions.jts.geom.Coordinate; +import org.elasticsearch.common.geo.GeoPoint; 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 Coordinate center; + private GeoPoint 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(Coordinate center) { + public CircleBuilder center(GeoPoint 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 Coordinate(lon, lat)); + return center(new GeoPoint(lat, lon)); } /** diff --git a/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java index a296b3406ef..5e8a45f3b62 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/EnvelopeBuilder.java +++ b/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 com.vividsolutions.jts.geom.Coordinate; +import org.elasticsearch.common.geo.GeoPoint; 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 Coordinate topLeft; - protected Coordinate bottomRight; + protected GeoPoint topLeft; + protected GeoPoint bottomRight; public EnvelopeBuilder() { this(Orientation.RIGHT); @@ -40,7 +40,7 @@ public class EnvelopeBuilder extends ShapeBuilder { super(orientation); } - public EnvelopeBuilder topLeft(Coordinate topLeft) { + public EnvelopeBuilder topLeft(GeoPoint topLeft) { this.topLeft = topLeft; return this; } @@ -49,7 +49,7 @@ public class EnvelopeBuilder extends ShapeBuilder { return topLeft(coordinate(longitude, latitude)); } - public EnvelopeBuilder bottomRight(Coordinate bottomRight) { + public EnvelopeBuilder bottomRight(GeoPoint bottomRight) { this.bottomRight = bottomRight; return this; } diff --git a/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java index dcef02d7a68..0f8a466ab63 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java +++ b/src/main/java/org/elasticsearch/common/geo/builders/MultiLineStringBuilder.java @@ -19,11 +19,10 @@ 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; @@ -48,8 +47,8 @@ public class MultiLineStringBuilder extends ShapeBuilder { return this; } - public Coordinate[][] coordinates() { - Coordinate[][] result = new Coordinate[lines.size()][]; + public GeoPoint[][] coordinates() { + GeoPoint[][] result = new GeoPoint[lines.size()][]; for (int i = 0; i < result.length; i++) { result[i] = lines.get(i).coordinates(false); } @@ -113,7 +112,7 @@ public class MultiLineStringBuilder extends ShapeBuilder { return collection; } - public Coordinate[] coordinates() { + public GeoPoint[] coordinates() { return super.coordinates(false); } diff --git a/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java index 83a19672a61..dd67d9cbbde 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/MultiPointBuilder.java +++ b/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 com.vividsolutions.jts.geom.Coordinate; +import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.xcontent.XContentBuilder; import java.io.IOException; @@ -48,7 +48,7 @@ public class MultiPointBuilder extends PointCollection { //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 shapes = new ArrayList<>(points.size()); - for (Coordinate coord : points) { + for (GeoPoint coord : points) { shapes.add(SPATIAL_CONTEXT.makePoint(coord.x, coord.y)); } return new ShapeCollection<>(shapes, SPATIAL_CONTEXT); diff --git a/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java index a47f0132d75..d38479ffbc9 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/MultiPolygonBuilder.java +++ b/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(Coordinate[][] part : polygon.coordinates()) { + for(GeoPoint[][] part : polygon.coordinates()) { shapes.add(jtsGeometry(PolygonBuilder.polygon(FACTORY, part))); } } diff --git a/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java index 53c67387e91..9ed24e8aac0 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/PointBuilder.java +++ b/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 Coordinate coordinate; + private GeoPoint coordinate; - public PointBuilder coordinate(Coordinate coordinate) { + public PointBuilder coordinate(GeoPoint coordinate) { this.coordinate = coordinate; return this; } diff --git a/src/main/java/org/elasticsearch/common/geo/builders/PointCollection.java b/src/main/java/org/elasticsearch/common/geo/builders/PointCollection.java index 8174efacfc6..7174bc2146c 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/PointCollection.java +++ b/src/main/java/org/elasticsearch/common/geo/builders/PointCollection.java @@ -24,23 +24,22 @@ 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> extends ShapeBuilder { - protected final ArrayList points; + protected final ArrayList points; protected boolean translated = false; protected PointCollection() { - this(new ArrayList()); + this(new ArrayList()); } - protected PointCollection(ArrayList points) { + protected PointCollection(ArrayList points) { this.points = points; } @@ -64,7 +63,7 @@ public abstract class PointCollection> extends Shap * @param coordinate coordinate of the point * @return this */ - public E point(Coordinate coordinate) { + public E point(GeoPoint coordinate) { this.points.add(coordinate); return thisRef(); } @@ -72,20 +71,20 @@ public abstract class PointCollection> extends Shap /** * Add a array of points to the collection * - * @param coordinates array of {@link Coordinate}s to add + * @param coordinates array of {@link GeoPoint}s to add * @return this */ - public E points(Coordinate...coordinates) { + public E points(GeoPoint...coordinates) { return this.points(Arrays.asList(coordinates)); } /** * Add a collection of points to the collection * - * @param coordinates array of {@link Coordinate}s to add + * @param coordinates array of {@link GeoPoint}s to add * @return this */ - public E points(Collection coordinates) { + public E points(Collection coordinates) { this.points.addAll(coordinates); return thisRef(); } @@ -96,8 +95,8 @@ public abstract class PointCollection> extends Shap * @param closed if set to true the first point of the array is repeated as last element * @return Array of coordinates */ - protected Coordinate[] coordinates(boolean closed) { - Coordinate[] result = points.toArray(new Coordinate[points.size() + (closed?1:0)]); + protected GeoPoint[] coordinates(boolean closed) { + GeoPoint[] result = points.toArray(new GeoPoint[points.size() + (closed?1:0)]); if(closed) { result[result.length-1] = result[0]; } @@ -114,12 +113,12 @@ public abstract class PointCollection> extends Shap */ protected XContentBuilder coordinatesToXcontent(XContentBuilder builder, boolean closed) throws IOException { builder.startArray(); - for(Coordinate point : points) { + for(GeoPoint point : points) { toXContent(builder, point); } if(closed) { - Coordinate start = points.get(0); - Coordinate end = points.get(points.size()-1); + GeoPoint start = points.get(0); + GeoPoint end = points.get(points.size()-1); if(start.x != end.x || start.y != end.y) { toXContent(builder, points.get(0)); } diff --git a/src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java index d7c7fa6abd3..e59176ddec7 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/PolygonBuilder.java +++ b/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 com.vividsolutions.jts.geom.Coordinate; +import org.elasticsearch.common.geo.GeoPoint; public class PolygonBuilder extends BasePolygonBuilder { public PolygonBuilder() { - this(new ArrayList(), Orientation.RIGHT); + this(new ArrayList(), Orientation.RIGHT); } public PolygonBuilder(Orientation orientation) { - this(new ArrayList(), orientation); + this(new ArrayList(), orientation); } - protected PolygonBuilder(ArrayList points, Orientation orientation) { + protected PolygonBuilder(ArrayList points, Orientation orientation) { super(orientation); this.shell = new Ring<>(this, points); } diff --git a/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java b/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java index 11545855077..ddabde50dbb 100644 --- a/src/main/java/org/elasticsearch/common/geo/builders/ShapeBuilder.java +++ b/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 = 180; + public static final double DATELINE = GeoUtils.DATELINE; // 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 Coordinate coordinate(double longitude, double latitude) { - return new Coordinate(longitude, latitude); + protected static GeoPoint coordinate(double longitude, double latitude) { + return new GeoPoint(latitude, longitude); } 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 Coordinate(longitude, latitude)); + return newPoint(new GeoPoint(latitude, longitude)); } /** - * Create a new {@link PointBuilder} from a {@link Coordinate} + * Create a new {@link PointBuilder} from a {@link GeoPoint} * @param coordinate coordinate defining the position of the point * @return a new {@link PointBuilder} */ - public static PointBuilder newPoint(Coordinate coordinate) { + public static PointBuilder newPoint(GeoPoint 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 Coordinate(lon, lat)); + return new CoordinateNode(new GeoPoint(lat, lon)); } 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, Coordinate coordinate) throws IOException { + protected static XContentBuilder toXContent(XContentBuilder builder, GeoPoint 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 Coordinate shift(Coordinate coordinate, double dateline) { + protected static GeoPoint shift(GeoPoint coordinate, double dateline) { if (dateline == 0) { return coordinate; } else { - return new Coordinate(-2 * dateline + coordinate.x, coordinate.y); + return new GeoPoint(coordinate.y, -2 * dateline + coordinate.x); } } @@ -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(Coordinate p1, Coordinate p2, double dateline) { + protected static final double intersection(GeoPoint p1, GeoPoint 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++) { - Coordinate p1 = edges[i].coordinate; - Coordinate p2 = edges[i].next.coordinate; + GeoPoint p1 = edges[i].coordinate; + GeoPoint 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. *

- * Can either be a leaf node consisting of a Coordinate, or a parent with + * Can either be a leaf node consisting of a GeoPoint, or a parent with * children */ protected static class CoordinateNode implements ToXContent { - protected final Coordinate coordinate; + protected final GeoPoint coordinate; protected final List children; /** * Creates a new leaf CoordinateNode * * @param coordinate - * Coordinate for the Node + * GeoPoint for the Node */ - protected CoordinateNode(Coordinate coordinate) { + protected CoordinateNode(GeoPoint 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 Coordinate}. It contains + * This helper class implements a linked list for {@link GeoPoint}. It contains * fields for a dateline intersection and component id */ protected static final class Edge { - Coordinate coordinate; // coordinate of the start point + GeoPoint coordinate; // coordinate of the start point Edge next; // next segment - Coordinate intersect; // potential intersection with dateline + GeoPoint intersect; // potential intersection with dateline int component = -1; // id of the component this edge belongs to - public static final Coordinate MAX_COORDINATE = new Coordinate(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + public static final GeoPoint MAX_COORDINATE = new GeoPoint(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); - protected Edge(Coordinate coordinate, Edge next, Coordinate intersection) { + protected Edge(GeoPoint coordinate, Edge next, GeoPoint intersection) { this.coordinate = coordinate; this.next = next; this.intersect = intersection; @@ -453,11 +453,11 @@ public abstract class ShapeBuilder implements ToXContent { } } - protected Edge(Coordinate coordinate, Edge next) { + protected Edge(GeoPoint coordinate, Edge next) { this(coordinate, next, Edge.MAX_COORDINATE); } - private static final int top(Coordinate[] points, int offset, int length) { + private static final int top(GeoPoint[] 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,29 +471,6 @@ 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 * @@ -503,8 +480,6 @@ 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 @@ -513,27 +488,29 @@ public abstract class ShapeBuilder implements ToXContent { * number of points to use * @return the edges creates */ - private static Edge[] concat(int component, boolean direction, Coordinate[] points, final int pointOffset, Edge[] edges, final int edgeOffset, - int length) { + private static Edge[] concat(int component, boolean direction, GeoPoint[] points, Edge[] edges, final int edgeOffset, + int length) { assert edges.length >= length+edgeOffset; - assert points.length >= length+pointOffset; - edges[edgeOffset] = new Edge(points[pointOffset], null); - for (int i = 1; i < length; i++) { + 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) { if (direction) { - edges[edgeOffset + i] = new Edge(points[pointOffset + i], edges[edgeOffset + i - 1]); - edges[edgeOffset + i].component = component; + edges[i] = new Edge(points[p], edges[i - 1]); + edges[i].component = component; } else { - edges[edgeOffset + i - 1].next = edges[edgeOffset + i] = new Edge(points[pointOffset + i], null); - edges[edgeOffset + i - 1].component = component; + edges[i - 1].next = edges[i] = new Edge(points[p], null); + edges[i - 1].component = component; } } if (direction) { - edges[edgeOffset].next = edges[edgeOffset + length - 1]; + edges[edgeOffset].next = edges[edgeEnd - 1]; edges[edgeOffset].component = component; } else { - edges[edgeOffset + length - 1].next = edges[edgeOffset]; - edges[edgeOffset + length - 1].component = component; + edges[edgeEnd - 1].next = edges[edgeOffset]; + edges[edgeEnd - 1].component = component; } return edges; @@ -544,60 +521,25 @@ 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, - Coordinate[] points, int offset, Edge[] edges, int toffset, int length) { + GeoPoint[] points, Edge[] edges, int edgeOffset, int length) { // calculate the direction of the points: - // 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; + boolean orientation = GeoUtils.computePolyOrientation(points, length); + boolean corrected = GeoUtils.correctPolyAmbiguity(points, handedness, orientation, component, length, + shell.translated); - // 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 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 + // correct the orientation post translation (ccw for shell, cw for holes) + if (corrected && (component == 0 || (component != 0 && handedness == orientation))) { if (component == 0) { - 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; + shell.translated = corrected; } + orientation = !orientation; } + return concat(component, direction ^ orientation, points, edges, edgeOffset, length); } /** @@ -605,13 +547,13 @@ public abstract class ShapeBuilder implements ToXContent { * * @param position * position of the intersection [0..1] - * @return the {@link Coordinate} of the intersection + * @return the {@link GeoPoint} of the intersection */ - protected Coordinate intersection(double position) { + protected GeoPoint intersection(double position) { return intersect = position(coordinate, next.coordinate, position); } - public static Coordinate position(Coordinate p1, Coordinate p2, double position) { + public static GeoPoint position(GeoPoint p1, GeoPoint p2, double position) { if (position == 0) { return p1; } else if (position == 1) { @@ -619,7 +561,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 Coordinate(x, y); + return new GeoPoint(y, x); } } @@ -793,12 +735,12 @@ public abstract class ShapeBuilder implements ToXContent { "geo_shape ('envelope') when expecting an array of 2 coordinates"); } // verify coordinate bounds, correct if necessary - Coordinate uL = coordinates.children.get(0).coordinate; - Coordinate lR = coordinates.children.get(1).coordinate; + GeoPoint uL = coordinates.children.get(0).coordinate; + GeoPoint lR = coordinates.children.get(1).coordinate; if (((lR.x < uL.x) || (uL.y < lR.y))) { - 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)); + 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)); } return newEnvelope(orientation).topLeft(uL).bottomRight(lR); } diff --git a/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java b/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java index 8e48726f4a0..cb19b8202be 100644 --- a/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java +++ b/src/main/java/org/elasticsearch/index/mapper/geo/GeoShapeFieldMapper.java @@ -294,6 +294,10 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper { 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 diff --git a/src/main/java/org/elasticsearch/index/search/geo/GeoPolygonFilter.java b/src/main/java/org/elasticsearch/index/search/geo/GeoPolygonFilter.java index d1da022acc7..0fda9ad2388 100644 --- a/src/main/java/org/elasticsearch/index/search/geo/GeoPolygonFilter.java +++ b/src/main/java/org/elasticsearch/index/search/geo/GeoPolygonFilter.java @@ -29,6 +29,7 @@ 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; @@ -93,15 +94,27 @@ 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++) { - 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) { + 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) { inPoly = !inPoly; } } + pp0 = pp1; } return inPoly; } diff --git a/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java b/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java index 0b71570403f..488903e698e 100644 --- a/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java +++ b/src/test/java/org/elasticsearch/common/geo/GeoJSONShapeParserTests.java @@ -26,7 +26,13 @@ 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.*; +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 org.elasticsearch.ElasticsearchIllegalArgumentException; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.geo.builders.ShapeBuilder; @@ -57,7 +63,7 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { .startArray("coordinates").value(100.0).value(0.0).endArray() .endObject().string(); - Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0)); + Point expected = GEOMETRY_FACTORY.createPoint(new GeoPoint(0.0, 100.0)); assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson); } @@ -69,12 +75,12 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { .endArray() .endObject().string(); - List lineCoordinates = new ArrayList<>(); - lineCoordinates.add(new Coordinate(100, 0)); - lineCoordinates.add(new Coordinate(101, 1)); + List lineCoordinates = new ArrayList<>(); + lineCoordinates.add(new GeoPoint(0, 100)); + lineCoordinates.add(new GeoPoint(1, 101)); LineString expected = GEOMETRY_FACTORY.createLineString( - lineCoordinates.toArray(new Coordinate[lineCoordinates.size()])); + lineCoordinates.toArray(new GeoPoint[lineCoordinates.size()])); assertGeometryEquals(jtsGeom(expected), lineGeoJson); } @@ -93,13 +99,13 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { .endObject().string(); MultiLineString expected = GEOMETRY_FACTORY.createMultiLineString(new LineString[]{ - GEOMETRY_FACTORY.createLineString(new Coordinate[]{ - new Coordinate(100, 0), - new Coordinate(101, 1), + GEOMETRY_FACTORY.createLineString(new GeoPoint[]{ + new GeoPoint(0, 100), + new GeoPoint(1, 101), }), - GEOMETRY_FACTORY.createLineString(new Coordinate[]{ - new Coordinate(102, 2), - new Coordinate(103, 3), + GEOMETRY_FACTORY.createLineString(new GeoPoint[]{ + new GeoPoint(2, 102), + new GeoPoint(3, 103), }), }); assertGeometryEquals(jtsGeom(expected), multilinesGeoJson); @@ -173,14 +179,14 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { .endArray() .endObject().string(); - List 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 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)); - LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); + LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new GeoPoint[shellCoordinates.size()])); Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null); assertGeometryEquals(jtsGeom(expected), polygonGeoJson); } @@ -567,25 +573,25 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { .endArray() .endObject().string(); - List 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 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 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)); + List 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)); LinearRing shell = GEOMETRY_FACTORY.createLinearRing( - shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); + shellCoordinates.toArray(new GeoPoint[shellCoordinates.size()])); LinearRing[] holes = new LinearRing[1]; holes[0] = GEOMETRY_FACTORY.createLinearRing( - holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); + holeCoordinates.toArray(new GeoPoint[holeCoordinates.size()])); Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, holes); assertGeometryEquals(jtsGeom(expected), polygonGeoJson); } @@ -657,34 +663,34 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { .endArray() .endObject().string(); - List 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 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 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)); + List 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)); - LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); + LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new GeoPoint[shellCoordinates.size()])); LinearRing[] holes = new LinearRing[1]; - holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); + holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new GeoPoint[holeCoordinates.size()])); Polygon withHoles = GEOMETRY_FACTORY.createPolygon(shell, holes); shellCoordinates = new ArrayList<>(); - 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)); + 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)); - shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); + shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new GeoPoint[shellCoordinates.size()])); Polygon withoutHoles = GEOMETRY_FACTORY.createPolygon(shell, null); Shape expected = shapeCollection(withoutHoles, withHoles); @@ -716,22 +722,22 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { .endObject().string(); shellCoordinates = new ArrayList<>(); - 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)); + 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)); holeCoordinates = new ArrayList<>(); - 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)); + 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)); - shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()])); + shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new GeoPoint[shellCoordinates.size()])); holes = new LinearRing[1]; - holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()])); + holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new GeoPoint[holeCoordinates.size()])); withHoles = GEOMETRY_FACTORY.createPolygon(shell, holes); assertGeometryEquals(jtsGeom(withHoles), multiPolygonGeoJson); @@ -757,12 +763,12 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { .string(); Shape[] expected = new Shape[2]; - LineString expectedLineString = GEOMETRY_FACTORY.createLineString(new Coordinate[]{ - new Coordinate(100, 0), - new Coordinate(101, 1), + LineString expectedLineString = GEOMETRY_FACTORY.createLineString(new GeoPoint[]{ + new GeoPoint(0, 100), + new GeoPoint(1, 101), }); expected[0] = jtsGeom(expectedLineString); - Point expectedPoint = GEOMETRY_FACTORY.createPoint(new Coordinate(102.0, 2.0)); + Point expectedPoint = GEOMETRY_FACTORY.createPoint(new GeoPoint(2.0, 102.0)); expected[1] = new JtsPoint(expectedPoint, SPATIAL_CONTEXT); //equals returns true only if geometries are in the same order @@ -785,7 +791,7 @@ public class GeoJSONShapeParserTests extends ElasticsearchTestCase { .startObject("lala").field("type", "NotAPoint").endObject() .endObject().string(); - Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0)); + Point expected = GEOMETRY_FACTORY.createPoint(new GeoPoint(0.0, 100.0)); assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson); } diff --git a/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java b/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java index cb9c53846f2..900db8072e2 100644 --- a/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java +++ b/src/test/java/org/elasticsearch/common/geo/ShapeBuilderTests.java @@ -24,7 +24,6 @@ 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; @@ -64,38 +63,39 @@ public class ShapeBuilderTests extends ElasticsearchTestCase { .point(-45, 30).toPolygon(); LineString exterior = polygon.getExteriorRing(); - 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)); + 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)); } @Test public void testNewPolygon_coordinate() { Polygon polygon = ShapeBuilder.newPolygon() - .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(); + .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(); LineString exterior = polygon.getExteriorRing(); - 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)); + 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)); } @Test public void testNewPolygon_coordinates() { Polygon polygon = ShapeBuilder.newPolygon() - .points(new Coordinate(-45, 30), new Coordinate(45, 30), new Coordinate(45, -30), new Coordinate(-45, -30), new Coordinate(-45, 30)).toPolygon(); + .points(new GeoPoint(30, -45), new GeoPoint(30, 45), new GeoPoint(-30, 45), new GeoPoint(-30, -45), + new GeoPoint(30, -45)).toPolygon(); LineString exterior = polygon.getExteriorRing(); - 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)); + 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)); } @Test