[GEO] Update GeoPolygonFilter to handle ambiguous polygons

PR #8672 addresses ambiguous polygons - those that either cross the dateline or span the map - by complying with the OGC standard right-hand rule. Since ```GeoPolygonFilter``` is self contained logic, the fix in #8672 did not address the issue for the ```GeoPolygonFilter```. This was identified in issue #5968

This fixes the ambiguous polygon issue in ```GeoPolygonFilter``` by moving the dateline crossing code from ```ShapeBuilder``` to ```GeoUtils``` and reusing the logic inside the ```pointInPolygon``` method.  Unit tests are added to ensure support for coordinates specified in either standard lat/lon or great-circle coordinate systems.

closes #5968
closes #9304
This commit is contained in:
Nicholas Knize 2015-01-14 22:06:42 -06:00
parent 9ac6d78308
commit 06667c6aa8
17 changed files with 409 additions and 325 deletions

View File

@ -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) {

View File

@ -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;
@ -38,6 +39,8 @@ public class GeoUtils {
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<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() {
}
}

View File

@ -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<E extends BaseLineStringBuilder<E>> extends PointCollection<E> {
protected BaseLineStringBuilder() {
this(new ArrayList<Coordinate>());
this(new ArrayList<GeoPoint>());
}
protected BaseLineStringBuilder(ArrayList<Coordinate> points) {
protected BaseLineStringBuilder(ArrayList<GeoPoint> points) {
super(points);
}
@ -49,7 +49,7 @@ public abstract class BaseLineStringBuilder<E extends BaseLineStringBuilder<E>>
@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<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, Coordinate[] coordinates, ArrayList<LineString> strings) {
for(Coordinate[] part : decompose(+DATELINE, coordinates)) {
for(Coordinate[] line : decompose(-DATELINE, part)) {
protected static ArrayList<LineString> decompose(GeometryFactory factory, GeoPoint[] coordinates, ArrayList<LineString> 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<E extends BaseLineStringBuilder<E>>
* @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<Coordinate[]> parts = new ArrayList<>();
ArrayList<GeoPoint[]> 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<E extends BaseLineStringBuilder<E>>
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;

View File

@ -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<E extends BasePolygonBuilder<E>> 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<E extends BasePolygonBuilder<E>> 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<E extends BasePolygonBuilder<E>> 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<E extends BasePolygonBuilder<E>> 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<E extends BasePolygonBuilder<E>> extend
return factory.createPolygon(shell, holes);
}
protected static LinearRing linearRing(GeometryFactory factory, ArrayList<Coordinate> coordinates) {
return factory.createLinearRing(coordinates.toArray(new Coordinate[coordinates.size()]));
protected static LinearRing linearRing(GeometryFactory factory, ArrayList<GeoPoint> coordinates) {
return factory.createLinearRing(coordinates.toArray(new GeoPoint[coordinates.size()]));
}
@Override
@ -202,7 +208,7 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> 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<E extends BasePolygonBuilder<E>> 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<E extends BasePolygonBuilder<E>> 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<ArrayList<Coordinate[]>> components) {
Coordinate[][][] result = new Coordinate[components.size()][][];
private static GeoPoint[][][] buildCoordinates(ArrayList<ArrayList<GeoPoint[]>> components) {
GeoPoint[][][] result = new GeoPoint[components.size()][][];
for (int i = 0; i < result.length; i++) {
ArrayList<Coordinate[]> component = components.get(i);
result[i] = component.toArray(new Coordinate[component.size()][]);
ArrayList<GeoPoint[]> component = components.get(i);
result[i] = component.toArray(new GeoPoint[component.size()][]);
}
if(debugEnabled()) {
@ -309,30 +315,30 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> 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<ArrayList<Coordinate[]>> components) {
private static Edge[] edges(Edge[] edges, int numHoles, ArrayList<ArrayList<GeoPoint[]>> 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<Coordinate[]> component = new ArrayList<>();
component.add(coordinates(edges[i], new Coordinate[length+1]));
ArrayList<GeoPoint[]> component = new ArrayList<>();
component.add(coordinates(edges[i], new GeoPoint[length+1]));
components.add(component);
}
}
@ -340,13 +346,14 @@ public abstract class BasePolygonBuilder<E extends BasePolygonBuilder<E>> extend
return mainEdges.toArray(new Edge[mainEdges.size()]);
}
private static Coordinate[][][] compose(Edge[] edges, Edge[] holes, int numHoles) {
final ArrayList<ArrayList<Coordinate[]>> components = new ArrayList<>();
private static GeoPoint[][][] compose(Edge[] edges, Edge[] holes, int numHoles) {
final ArrayList<ArrayList<GeoPoint[]>> 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<ArrayList<Coordinate[]>> components) {
private static void assign(Edge[] holes, GeoPoint[][] points, int numHoles, Edge[] edges, ArrayList<ArrayList<GeoPoint[]>>
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<E extends BasePolygonBuilder<E>> 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<E extends BasePolygonBuilder<E>> extend
private final P parent;
protected Ring(P parent) {
this(parent, new ArrayList<Coordinate>());
this(parent, new ArrayList<GeoPoint>());
}
protected Ring(P parent, ArrayList<Coordinate> points) {
protected Ring(P parent, ArrayList<GeoPoint> 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);
}

View File

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

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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<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 (Coordinate coord : points) {
for (GeoPoint coord : points) {
shapes.add(SPATIAL_CONTEXT.makePoint(coord.x, coord.y));
}
return new ShapeCollection<>(shapes, SPATIAL_CONTEXT);

View File

@ -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)));
}
}

View File

@ -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;
}

View File

@ -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<E extends PointCollection<E>> extends ShapeBuilder {
protected final ArrayList<Coordinate> points;
protected final ArrayList<GeoPoint> points;
protected boolean translated = false;
protected PointCollection() {
this(new ArrayList<Coordinate>());
this(new ArrayList<GeoPoint>());
}
protected PointCollection(ArrayList<Coordinate> points) {
protected PointCollection(ArrayList<GeoPoint> points) {
this.points = points;
}
@ -64,7 +63,7 @@ public abstract class PointCollection<E extends PointCollection<E>> 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<E extends PointCollection<E>> 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<? extends Coordinate> coordinates) {
public E points(Collection<? extends GeoPoint> coordinates) {
this.points.addAll(coordinates);
return thisRef();
}
@ -96,8 +95,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 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<E extends PointCollection<E>> 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));
}

View File

@ -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<PolygonBuilder> {
public PolygonBuilder() {
this(new ArrayList<Coordinate>(), Orientation.RIGHT);
this(new ArrayList<GeoPoint>(), Orientation.RIGHT);
}
public PolygonBuilder(Orientation orientation) {
this(new ArrayList<Coordinate>(), orientation);
this(new ArrayList<GeoPoint>(), orientation);
}
protected PolygonBuilder(ArrayList<Coordinate> points, Orientation orientation) {
protected PolygonBuilder(ArrayList<GeoPoint> points, Orientation orientation) {
super(orientation);
this.shell = new Ring<>(this, points);
}

View File

@ -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);
}
}
@ -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.
* <p/>
* 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<CoordinateNode> 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<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
// 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);
}

View File

@ -294,6 +294,10 @@ 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

View File

@ -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;
}

View File

@ -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<Coordinate> lineCoordinates = new ArrayList<>();
lineCoordinates.add(new Coordinate(100, 0));
lineCoordinates.add(new Coordinate(101, 1));
List<GeoPoint> 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<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> 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<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> 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> 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<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));
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<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> 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> 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<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));
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);
}

View File

@ -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