GEO: Add support for z values to libs/geo classes (#38921)

Adds support for z-values to all Geometry objects in the
libs/geo library.
This commit is contained in:
Igor Motov 2019-03-13 15:35:18 -04:00
parent 8a314a36db
commit 4a42e408c5
21 changed files with 382 additions and 81 deletions

View File

@ -20,24 +20,32 @@
package org.elasticsearch.geo.geometry; package org.elasticsearch.geo.geometry;
/** /**
* Circle geometry (not part of WKT standard, but used in elasticsearch) * Circle geometry (not part of WKT standard, but used in elasticsearch) defined by lat/lon coordinates of the center in degrees
* and optional altitude in meters.
*/ */
public class Circle implements Geometry { public class Circle implements Geometry {
public static final Circle EMPTY = new Circle(); public static final Circle EMPTY = new Circle();
private final double lat; private final double lat;
private final double lon; private final double lon;
private final double alt;
private final double radiusMeters; private final double radiusMeters;
private Circle() { private Circle() {
lat = 0; lat = 0;
lon = 0; lon = 0;
alt = Double.NaN;
radiusMeters = -1; radiusMeters = -1;
} }
public Circle(final double lat, final double lon, final double radiusMeters) { public Circle(final double lat, final double lon, final double radiusMeters) {
this(lat, lon, Double.NaN, radiusMeters);
}
public Circle(final double lat, final double lon, final double alt, final double radiusMeters) {
this.lat = lat; this.lat = lat;
this.lon = lon; this.lon = lon;
this.radiusMeters = radiusMeters; this.radiusMeters = radiusMeters;
this.alt = alt;
if (radiusMeters < 0 ) { if (radiusMeters < 0 ) {
throw new IllegalArgumentException("Circle radius [" + radiusMeters + "] cannot be negative"); throw new IllegalArgumentException("Circle radius [" + radiusMeters + "] cannot be negative");
} }
@ -62,6 +70,10 @@ public class Circle implements Geometry {
return radiusMeters; return radiusMeters;
} }
public double getAlt() {
return alt;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -70,7 +82,8 @@ public class Circle implements Geometry {
Circle circle = (Circle) o; Circle circle = (Circle) o;
if (Double.compare(circle.lat, lat) != 0) return false; if (Double.compare(circle.lat, lat) != 0) return false;
if (Double.compare(circle.lon, lon) != 0) return false; if (Double.compare(circle.lon, lon) != 0) return false;
return (Double.compare(circle.radiusMeters, radiusMeters) == 0); if (Double.compare(circle.radiusMeters, radiusMeters) != 0) return false;
return (Double.compare(circle.alt, alt) == 0);
} }
@Override @Override
@ -83,6 +96,8 @@ public class Circle implements Geometry {
result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(radiusMeters); temp = Double.doubleToLongBits(radiusMeters);
result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(alt);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result; return result;
} }
@ -98,7 +113,11 @@ public class Circle implements Geometry {
@Override @Override
public String toString() { public String toString() {
return "lat=" + lat + ", lon=" + lon + ", radius=" + radiusMeters; return "lat=" + lat + ", lon=" + lon + ", radius=" + radiusMeters + (Double.isNaN(alt) ? ", alt=" + alt : "");
} }
@Override
public boolean hasAlt() {
return Double.isNaN(alt) == false;
}
} }

View File

@ -29,4 +29,8 @@ public interface Geometry {
<T> T visit(GeometryVisitor<T> visitor); <T> T visit(GeometryVisitor<T> visitor);
boolean isEmpty(); boolean isEmpty();
default boolean hasAlt() {
return false;
}
} }

View File

@ -21,6 +21,7 @@ package org.elasticsearch.geo.geometry;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Objects; import java.util.Objects;
/** /**
@ -31,6 +32,8 @@ public class GeometryCollection<G extends Geometry> implements Geometry, Iterabl
private final List<G> shapes; private final List<G> shapes;
private boolean hasAlt;
public GeometryCollection() { public GeometryCollection() {
shapes = Collections.emptyList(); shapes = Collections.emptyList();
} }
@ -39,6 +42,12 @@ public class GeometryCollection<G extends Geometry> implements Geometry, Iterabl
if (shapes == null || shapes.isEmpty()) { if (shapes == null || shapes.isEmpty()) {
throw new IllegalArgumentException("the list of shapes cannot be null or empty"); throw new IllegalArgumentException("the list of shapes cannot be null or empty");
} }
hasAlt = shapes.get(0).hasAlt();
for (G shape : shapes) {
if (shape.hasAlt() != hasAlt) {
throw new IllegalArgumentException("all elements of the collection should have the same number of dimension");
}
}
this.shapes = shapes; this.shapes = shapes;
} }
@ -82,4 +91,18 @@ public class GeometryCollection<G extends Geometry> implements Geometry, Iterabl
public Iterator<G> iterator() { public Iterator<G> iterator() {
return shapes.iterator(); return shapes.iterator();
} }
@Override
public boolean hasAlt() {
return hasAlt;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(type().name().toLowerCase(Locale.ROOT)).append("(shapes=");
sb.append(shapes);
sb.append(")");
return sb.toString();
}
} }

View File

@ -22,21 +22,28 @@ package org.elasticsearch.geo.geometry;
import java.util.Arrays; import java.util.Arrays;
/** /**
* Represents a Line on the earth's surface in lat/lon decimal degrees. * Represents a Line on the earth's surface in lat/lon decimal degrees and optional altitude in meters.
*/ */
public class Line implements Geometry { public class Line implements Geometry {
public static final Line EMPTY = new Line(); public static final Line EMPTY = new Line();
private final double[] lats; private final double[] lats;
private final double[] lons; private final double[] lons;
private final double[] alts;
protected Line() { protected Line() {
lats = new double[0]; lats = new double[0];
lons = new double[0]; lons = new double[0];
alts = null;
} }
public Line(double[] lats, double[] lons) { public Line(double[] lats, double[] lons) {
this(lats, lons, null);
}
public Line(double[] lats, double[] lons, double[] alts) {
this.lats = lats; this.lats = lats;
this.lons = lons; this.lons = lons;
this.alts = alts;
if (lats == null) { if (lats == null) {
throw new IllegalArgumentException("lats must not be null"); throw new IllegalArgumentException("lats must not be null");
} }
@ -49,6 +56,9 @@ public class Line implements Geometry {
if (lats.length < 2) { if (lats.length < 2) {
throw new IllegalArgumentException("at least two points in the line is required"); throw new IllegalArgumentException("at least two points in the line is required");
} }
if (alts != null && alts.length != lats.length) {
throw new IllegalArgumentException("alts and lats must be equal length");
}
for (int i = 0; i < lats.length; i++) { for (int i = 0; i < lats.length; i++) {
GeometryUtils.checkLatitude(lats[i]); GeometryUtils.checkLatitude(lats[i]);
GeometryUtils.checkLongitude(lons[i]); GeometryUtils.checkLongitude(lons[i]);
@ -67,6 +77,14 @@ public class Line implements Geometry {
return lons[i]; return lons[i];
} }
public double getAlt(int i) {
if (alts != null) {
return alts[i];
} else {
return Double.NaN;
}
}
public double[] getLats() { public double[] getLats() {
return lats.clone(); return lats.clone();
} }
@ -75,6 +93,10 @@ public class Line implements Geometry {
return lons.clone(); return lons.clone();
} }
public double[] getAlts() {
return alts == null ? null : alts.clone();
}
@Override @Override
public ShapeType type() { public ShapeType type() {
return ShapeType.LINESTRING; return ShapeType.LINESTRING;
@ -96,19 +118,26 @@ public class Line implements Geometry {
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
Line line = (Line) o; Line line = (Line) o;
return Arrays.equals(lats, line.lats) && return Arrays.equals(lats, line.lats) &&
Arrays.equals(lons, line.lons); Arrays.equals(lons, line.lons) && Arrays.equals(alts, line.alts);
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = Arrays.hashCode(lats); int result = Arrays.hashCode(lats);
result = 31 * result + Arrays.hashCode(lons); result = 31 * result + Arrays.hashCode(lons);
result = 31 * result + Arrays.hashCode(alts);
return result; return result;
} }
@Override
public boolean hasAlt() {
return alts != null;
}
@Override @Override
public String toString() { public String toString() {
return "lats=" + Arrays.toString(lats) + return "lats=" + Arrays.toString(lats) +
", lons=" + Arrays.toString(lons); ", lons=" + Arrays.toString(lons) +
(hasAlt() ? ", alts=" + Arrays.toString(alts) : "");
} }
} }

View File

@ -20,7 +20,7 @@
package org.elasticsearch.geo.geometry; package org.elasticsearch.geo.geometry;
/** /**
* Represents a closed line on the earth's surface in lat/lon decimal degrees. * Represents a closed line on the earth's surface in lat/lon decimal degrees and optional altitude in meters.
* <p> * <p>
* Cannot be serialized by WKT directly but used as a part of polygon * Cannot be serialized by WKT directly but used as a part of polygon
*/ */
@ -31,13 +31,20 @@ public class LinearRing extends Line {
} }
public LinearRing(double[] lats, double[] lons) { public LinearRing(double[] lats, double[] lons) {
super(lats, lons); this(lats, lons, null);
}
public LinearRing(double[] lats, double[] lons, double[] alts) {
super(lats, lons, alts);
if (lats.length < 2) { if (lats.length < 2) {
throw new IllegalArgumentException("linear ring cannot contain less than 2 points, found " + lats.length); throw new IllegalArgumentException("linear ring cannot contain less than 2 points, found " + lats.length);
} }
if (lats[0] != lats[lats.length - 1] || lons[0] != lons[lons.length - 1]) { int last = lats.length - 1;
throw new IllegalArgumentException("first and last points of the linear ring must be the same (it must close itself): lats[0]=" if (lats[0] != lats[last] || lons[0] != lons[last] || (alts != null && alts[0] != alts[last])) {
+ lats[0] + " lats[" + (lats.length - 1) + "]=" + lats[lats.length - 1]); throw new IllegalArgumentException("first and last points of the linear ring must be the same (it must close itself):" +
" lats[0]=" + lats[0] + " lats[" + last + "]=" + lats[last] +
" lons[0]=" + lons[0] + " lons[" + last + "]=" + lons[last] +
(alts == null ? "" : " alts[0]=" + alts[0] + " alts[" + last + "]=" + alts[last] ));
} }
} }

View File

@ -22,7 +22,7 @@ package org.elasticsearch.geo.geometry;
import java.util.List; import java.util.List;
/** /**
* Represents a MultiPoint object on the earth's surface in decimal degrees. * Represents a MultiPoint object on the earth's surface in decimal degrees and optional altitude in meters.
*/ */
public class MultiPoint extends GeometryCollection<Point> { public class MultiPoint extends GeometryCollection<Point> {
public static final MultiPoint EMPTY = new MultiPoint(); public static final MultiPoint EMPTY = new MultiPoint();

View File

@ -20,26 +20,33 @@
package org.elasticsearch.geo.geometry; package org.elasticsearch.geo.geometry;
/** /**
* Represents a Point on the earth's surface in decimal degrees. * Represents a Point on the earth's surface in decimal degrees and optional altitude in meters.
*/ */
public class Point implements Geometry { public class Point implements Geometry {
public static final Point EMPTY = new Point(); public static final Point EMPTY = new Point();
private final double lat; private final double lat;
private final double lon; private final double lon;
private final double alt;
private final boolean empty; private final boolean empty;
private Point() { private Point() {
lat = 0; lat = 0;
lon = 0; lon = 0;
alt = Double.NaN;
empty = true; empty = true;
} }
public Point(double lat, double lon) { public Point(double lat, double lon) {
this(lat, lon, Double.NaN);
}
public Point(double lat, double lon, double alt) {
GeometryUtils.checkLatitude(lat); GeometryUtils.checkLatitude(lat);
GeometryUtils.checkLongitude(lon); GeometryUtils.checkLongitude(lon);
this.lat = lat; this.lat = lat;
this.lon = lon; this.lon = lon;
this.alt = alt;
this.empty = false; this.empty = false;
} }
@ -56,6 +63,10 @@ public class Point implements Geometry {
return lon; return lon;
} }
public double getAlt() {
return alt;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -64,7 +75,8 @@ public class Point implements Geometry {
Point point = (Point) o; Point point = (Point) o;
if (point.empty != empty) return false; if (point.empty != empty) return false;
if (Double.compare(point.lat, lat) != 0) return false; if (Double.compare(point.lat, lat) != 0) return false;
return Double.compare(point.lon, lon) == 0; if (Double.compare(point.lon, lon) != 0) return false;
return Double.compare(point.alt, alt) == 0;
} }
@Override @Override
@ -75,6 +87,8 @@ public class Point implements Geometry {
result = (int) (temp ^ (temp >>> 32)); result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(lon); temp = Double.doubleToLongBits(lon);
result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(alt);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result; return result;
} }
@ -88,8 +102,13 @@ public class Point implements Geometry {
return empty; return empty;
} }
@Override
public boolean hasAlt() {
return Double.isNaN(alt) == false;
}
@Override @Override
public String toString() { public String toString() {
return "lat=" + lat + ", lon=" + lon; return "lat=" + lat + ", lon=" + lon + (hasAlt() ? ", alt=" + alt : "");
} }
} }

View File

@ -30,10 +30,12 @@ public final class Polygon implements Geometry {
public static final Polygon EMPTY = new Polygon(); public static final Polygon EMPTY = new Polygon();
private final LinearRing polygon; private final LinearRing polygon;
private final List<LinearRing> holes; private final List<LinearRing> holes;
private final boolean hasAlt;
private Polygon() { private Polygon() {
polygon = LinearRing.EMPTY; polygon = LinearRing.EMPTY;
holes = Collections.emptyList(); holes = Collections.emptyList();
hasAlt = false;
} }
/** /**
@ -45,10 +47,15 @@ public final class Polygon implements Geometry {
if (holes == null) { if (holes == null) {
throw new IllegalArgumentException("holes must not be null"); throw new IllegalArgumentException("holes must not be null");
} }
boolean hasAlt = polygon.hasAlt();
checkRing(polygon); checkRing(polygon);
for (LinearRing hole : holes) { for (LinearRing hole : holes) {
if (hole.hasAlt() != hasAlt) {
throw new IllegalArgumentException("holes must have the same number of dimensions as the polygon");
}
checkRing(hole); checkRing(hole);
} }
this.hasAlt = hasAlt;
} }
/** /**
@ -94,6 +101,10 @@ public final class Polygon implements Geometry {
return polygon.isEmpty(); return polygon.isEmpty();
} }
@Override
public boolean hasAlt() {
return hasAlt;
}
@Override @Override
public String toString() { public String toString() {

View File

@ -20,7 +20,7 @@
package org.elasticsearch.geo.geometry; package org.elasticsearch.geo.geometry;
/** /**
* Represents a lat/lon rectangle in decimal degrees. * Represents a lat/lon rectangle in decimal degrees and optional altitude in meters.
*/ */
public class Rectangle implements Geometry { public class Rectangle implements Geometry {
public static final Rectangle EMPTY = new Rectangle(); public static final Rectangle EMPTY = new Rectangle();
@ -32,6 +32,10 @@ public class Rectangle implements Geometry {
* minimum longitude value (in degrees) * minimum longitude value (in degrees)
*/ */
private final double minLon; private final double minLon;
/**
* maximum altitude value (in meters)
*/
private final double minAlt;
/** /**
* maximum latitude value (in degrees) * maximum latitude value (in degrees)
*/ */
@ -40,6 +44,10 @@ public class Rectangle implements Geometry {
* minimum latitude value (in degrees) * minimum latitude value (in degrees)
*/ */
private final double maxLon; private final double maxLon;
/**
* minimum altitude value (in meters)
*/
private final double maxAlt;
private final boolean empty; private final boolean empty;
@ -48,6 +56,8 @@ public class Rectangle implements Geometry {
minLon = 0; minLon = 0;
maxLat = 0; maxLat = 0;
maxLon = 0; maxLon = 0;
minAlt = Double.NaN;
maxAlt = Double.NaN;
empty = true; empty = true;
} }
@ -55,6 +65,12 @@ public class Rectangle implements Geometry {
* Constructs a bounding box by first validating the provided latitude and longitude coordinates * Constructs a bounding box by first validating the provided latitude and longitude coordinates
*/ */
public Rectangle(double minLat, double maxLat, double minLon, double maxLon) { public Rectangle(double minLat, double maxLat, double minLon, double maxLon) {
this(minLat, maxLat, minLon, maxLon, Double.NaN, Double.NaN);
}
/**
* Constructs a bounding box by first validating the provided latitude and longitude coordinates
*/
public Rectangle(double minLat, double maxLat, double minLon, double maxLon, double minAlt, double maxAlt) {
GeometryUtils.checkLatitude(minLat); GeometryUtils.checkLatitude(minLat);
GeometryUtils.checkLatitude(maxLat); GeometryUtils.checkLatitude(maxLat);
GeometryUtils.checkLongitude(minLon); GeometryUtils.checkLongitude(minLon);
@ -63,10 +79,15 @@ public class Rectangle implements Geometry {
this.maxLon = maxLon; this.maxLon = maxLon;
this.minLat = minLat; this.minLat = minLat;
this.maxLat = maxLat; this.maxLat = maxLat;
this.minAlt = minAlt;
this.maxAlt = maxAlt;
empty = false; empty = false;
if (maxLat < minLat) { if (maxLat < minLat) {
throw new IllegalArgumentException("max lat cannot be less than min lat"); throw new IllegalArgumentException("max lat cannot be less than min lat");
} }
if (Double.isNaN(minAlt) != Double.isNaN(maxAlt)) {
throw new IllegalArgumentException("only one altitude value is specified");
}
} }
public double getWidth() { public double getWidth() {
@ -88,6 +109,11 @@ public class Rectangle implements Geometry {
return minLon; return minLon;
} }
public double getMinAlt() {
return minAlt;
}
public double getMaxLat() { public double getMaxLat() {
return maxLat; return maxLat;
} }
@ -96,6 +122,10 @@ public class Rectangle implements Geometry {
return maxLon; return maxLon;
} }
public double getMaxAlt() {
return maxAlt;
}
@Override @Override
public ShapeType type() { public ShapeType type() {
return ShapeType.ENVELOPE; return ShapeType.ENVELOPE;
@ -115,6 +145,12 @@ public class Rectangle implements Geometry {
if (maxLon < minLon) { if (maxLon < minLon) {
b.append(" [crosses dateline!]"); b.append(" [crosses dateline!]");
} }
if (hasAlt()) {
b.append(" alt=");
b.append(minAlt);
b.append(" TO ");
b.append(maxAlt);
}
b.append(")"); b.append(")");
return b.toString(); return b.toString();
@ -137,7 +173,9 @@ public class Rectangle implements Geometry {
if (Double.compare(rectangle.minLat, minLat) != 0) return false; if (Double.compare(rectangle.minLat, minLat) != 0) return false;
if (Double.compare(rectangle.minLon, minLon) != 0) return false; if (Double.compare(rectangle.minLon, minLon) != 0) return false;
if (Double.compare(rectangle.maxLat, maxLat) != 0) return false; if (Double.compare(rectangle.maxLat, maxLat) != 0) return false;
return Double.compare(rectangle.maxLon, maxLon) == 0; if (Double.compare(rectangle.maxLon, maxLon) != 0) return false;
if (Double.compare(rectangle.minAlt, minAlt) != 0) return false;
return Double.compare(rectangle.maxAlt, maxAlt) == 0;
} }
@ -153,6 +191,10 @@ public class Rectangle implements Geometry {
result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(maxLon); temp = Double.doubleToLongBits(maxLon);
result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(minAlt);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(maxAlt);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result; return result;
} }
@ -165,4 +207,9 @@ public class Rectangle implements Geometry {
public boolean isEmpty() { public boolean isEmpty() {
return empty; return empty;
} }
@Override
public boolean hasAlt() {
return Double.isNaN(maxAlt) == false;
}
} }

View File

@ -72,9 +72,13 @@ public class WellKnownText {
@Override @Override
public Void visit(Circle circle) { public Void visit(Circle circle) {
sb.append(LPAREN); sb.append(LPAREN);
visitPoint(circle.getLon(), circle.getLat()); visitPoint(circle.getLon(), circle.getLat(), Double.NaN);
sb.append(SPACE); sb.append(SPACE);
sb.append(circle.getRadiusMeters()); sb.append(circle.getRadiusMeters());
if (circle.hasAlt()) {
sb.append(SPACE);
sb.append(circle.getAlt());
}
sb.append(RPAREN); sb.append(RPAREN);
return null; return null;
} }
@ -98,11 +102,11 @@ public class WellKnownText {
@Override @Override
public Void visit(Line line) { public Void visit(Line line) {
sb.append(LPAREN); sb.append(LPAREN);
visitPoint(line.getLon(0), line.getLat(0)); visitPoint(line.getLon(0), line.getLat(0), line.getAlt(0));
for (int i = 1; i < line.length(); ++i) { for (int i = 1; i < line.length(); ++i) {
sb.append(COMMA); sb.append(COMMA);
sb.append(SPACE); sb.append(SPACE);
visitPoint(line.getLon(i), line.getLat(i)); visitPoint(line.getLon(i), line.getLat(i), line.getAlt(i));
} }
sb.append(RPAREN); sb.append(RPAREN);
return null; return null;
@ -127,12 +131,12 @@ public class WellKnownText {
} }
// walk through coordinates: // walk through coordinates:
sb.append(LPAREN); sb.append(LPAREN);
visitPoint(multiPoint.get(0).getLon(), multiPoint.get(0).getLat()); visitPoint(multiPoint.get(0).getLon(), multiPoint.get(0).getLat(), multiPoint.get(0).getAlt());
for (int i = 1; i < multiPoint.size(); ++i) { for (int i = 1; i < multiPoint.size(); ++i) {
sb.append(COMMA); sb.append(COMMA);
sb.append(SPACE); sb.append(SPACE);
Point point = multiPoint.get(i); Point point = multiPoint.get(i);
visitPoint(point.getLon(), point.getLat()); visitPoint(point.getLon(), point.getLat(), point.getAlt());
} }
sb.append(RPAREN); sb.append(RPAREN);
return null; return null;
@ -150,14 +154,17 @@ public class WellKnownText {
sb.append(EMPTY); sb.append(EMPTY);
} else { } else {
sb.append(LPAREN); sb.append(LPAREN);
visitPoint(point.getLon(), point.getLat()); visitPoint(point.getLon(), point.getLat(), point.getAlt());
sb.append(RPAREN); sb.append(RPAREN);
} }
return null; return null;
} }
private void visitPoint(double lon, double lat) { private void visitPoint(double lon, double lat, double alt) {
sb.append(lon).append(SPACE).append(lat); sb.append(lon).append(SPACE).append(lat);
if (Double.isNaN(alt) == false) {
sb.append(SPACE).append(alt);
}
} }
private void visitCollection(GeometryCollection<?> collection) { private void visitCollection(GeometryCollection<?> collection) {
@ -191,6 +198,7 @@ public class WellKnownText {
public Void visit(Rectangle rectangle) { public Void visit(Rectangle rectangle) {
sb.append(LPAREN); sb.append(LPAREN);
// minX, maxX, maxY, minY // minX, maxX, maxY, minY
// TODO: Add 3D support
sb.append(rectangle.getMinLon()); sb.append(rectangle.getMinLon());
sb.append(COMMA); sb.append(COMMA);
sb.append(SPACE); sb.append(SPACE);
@ -278,28 +286,33 @@ public class WellKnownText {
} }
double lon = nextNumber(stream); double lon = nextNumber(stream);
double lat = nextNumber(stream); double lat = nextNumber(stream);
Point pt = new Point(lat, lon); Point pt;
if (isNumberNext(stream) == true) { if (isNumberNext(stream)) {
nextNumber(stream); pt = new Point(lat, lon, nextNumber(stream));
} else {
pt = new Point(lat, lon);
} }
nextCloser(stream); nextCloser(stream);
return pt; return pt;
} }
private static void parseCoordinates(StreamTokenizer stream, ArrayList<Double> lats, ArrayList<Double> lons) private static void parseCoordinates(StreamTokenizer stream, ArrayList<Double> lats, ArrayList<Double> lons, ArrayList<Double> alts)
throws IOException, ParseException { throws IOException, ParseException {
parseCoordinate(stream, lats, lons); parseCoordinate(stream, lats, lons, alts);
while (nextCloserOrComma(stream).equals(COMMA)) { while (nextCloserOrComma(stream).equals(COMMA)) {
parseCoordinate(stream, lats, lons); parseCoordinate(stream, lats, lons, alts);
} }
} }
private static void parseCoordinate(StreamTokenizer stream, ArrayList<Double> lats, ArrayList<Double> lons) private static void parseCoordinate(StreamTokenizer stream, ArrayList<Double> lats, ArrayList<Double> lons, ArrayList<Double> alts)
throws IOException, ParseException { throws IOException, ParseException {
lons.add(nextNumber(stream)); lons.add(nextNumber(stream));
lats.add(nextNumber(stream)); lats.add(nextNumber(stream));
if (isNumberNext(stream)) { if (isNumberNext(stream)) {
nextNumber(stream); alts.add(nextNumber(stream));
}
if (alts.isEmpty() == false && alts.size() != lons.size()) {
throw new ParseException("coordinate dimensions do not match: " + tokenString(stream), stream.lineno());
} }
} }
@ -310,10 +323,15 @@ public class WellKnownText {
} }
ArrayList<Double> lats = new ArrayList<>(); ArrayList<Double> lats = new ArrayList<>();
ArrayList<Double> lons = new ArrayList<>(); ArrayList<Double> lons = new ArrayList<>();
ArrayList<Double> alts = new ArrayList<>();
ArrayList<Point> points = new ArrayList<>(); ArrayList<Point> points = new ArrayList<>();
parseCoordinates(stream, lats, lons); parseCoordinates(stream, lats, lons, alts);
for (int i = 0; i < lats.size(); i++) { for (int i = 0; i < lats.size(); i++) {
points.add(new Point(lats.get(i), lons.get(i))); if (alts.isEmpty()) {
points.add(new Point(lats.get(i), lons.get(i)));
} else {
points.add(new Point(lats.get(i), lons.get(i), alts.get(i)));
}
} }
return new MultiPoint(Collections.unmodifiableList(points)); return new MultiPoint(Collections.unmodifiableList(points));
} }
@ -325,8 +343,13 @@ public class WellKnownText {
} }
ArrayList<Double> lats = new ArrayList<>(); ArrayList<Double> lats = new ArrayList<>();
ArrayList<Double> lons = new ArrayList<>(); ArrayList<Double> lons = new ArrayList<>();
parseCoordinates(stream, lats, lons); ArrayList<Double> alts = new ArrayList<>();
return new Line(lats.stream().mapToDouble(i -> i).toArray(), lons.stream().mapToDouble(i -> i).toArray()); parseCoordinates(stream, lats, lons, alts);
if (alts.isEmpty()) {
return new Line(toArray(lats), toArray(lons));
} else {
return new Line(toArray(lats), toArray(lons), toArray(alts));
}
} }
private static MultiLine parseMultiLine(StreamTokenizer stream) throws IOException, ParseException { private static MultiLine parseMultiLine(StreamTokenizer stream) throws IOException, ParseException {
@ -346,8 +369,13 @@ public class WellKnownText {
nextOpener(stream); nextOpener(stream);
ArrayList<Double> lats = new ArrayList<>(); ArrayList<Double> lats = new ArrayList<>();
ArrayList<Double> lons = new ArrayList<>(); ArrayList<Double> lons = new ArrayList<>();
parseCoordinates(stream, lats, lons); ArrayList<Double> alts = new ArrayList<>();
return new LinearRing(lats.stream().mapToDouble(i -> i).toArray(), lons.stream().mapToDouble(i -> i).toArray()); parseCoordinates(stream, lats, lons, alts);
if (alts.isEmpty()) {
return new LinearRing(toArray(lats), toArray(lons));
} else {
return new LinearRing(toArray(lats), toArray(lons), toArray(alts));
}
} }
private static Polygon parsePolygon(StreamTokenizer stream) throws IOException, ParseException { private static Polygon parsePolygon(StreamTokenizer stream) throws IOException, ParseException {
@ -357,17 +385,22 @@ public class WellKnownText {
nextOpener(stream); nextOpener(stream);
ArrayList<Double> lats = new ArrayList<>(); ArrayList<Double> lats = new ArrayList<>();
ArrayList<Double> lons = new ArrayList<>(); ArrayList<Double> lons = new ArrayList<>();
parseCoordinates(stream, lats, lons); ArrayList<Double> alts = new ArrayList<>();
parseCoordinates(stream, lats, lons, alts);
ArrayList<LinearRing> holes = new ArrayList<>(); ArrayList<LinearRing> holes = new ArrayList<>();
while (nextCloserOrComma(stream).equals(COMMA)) { while (nextCloserOrComma(stream).equals(COMMA)) {
holes.add(parsePolygonHole(stream)); holes.add(parsePolygonHole(stream));
} }
if (holes.isEmpty()) { LinearRing shell;
return new Polygon(new LinearRing(lats.stream().mapToDouble(i -> i).toArray(), lons.stream().mapToDouble(i -> i).toArray())); if (alts.isEmpty()) {
shell = new LinearRing(toArray(lats), toArray(lons));
} else { } else {
return new Polygon( shell = new LinearRing(toArray(lats), toArray(lons), toArray(alts));
new LinearRing(lats.stream().mapToDouble(i -> i).toArray(), lons.stream().mapToDouble(i -> i).toArray()), }
Collections.unmodifiableList(holes)); if (holes.isEmpty()) {
return new Polygon(shell);
} else {
return new Polygon(shell, Collections.unmodifiableList(holes));
} }
} }
@ -388,6 +421,7 @@ public class WellKnownText {
if (nextEmptyOrOpen(stream).equals(EMPTY)) { if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return Rectangle.EMPTY; return Rectangle.EMPTY;
} }
// TODO: Add 3D support
double minLon = nextNumber(stream); double minLon = nextNumber(stream);
nextComma(stream); nextComma(stream);
double maxLon = nextNumber(stream); double maxLon = nextNumber(stream);
@ -407,10 +441,11 @@ public class WellKnownText {
double lon = nextNumber(stream); double lon = nextNumber(stream);
double lat = nextNumber(stream); double lat = nextNumber(stream);
double radius = nextNumber(stream); double radius = nextNumber(stream);
Circle circle = new Circle(lat, lon, radius); double alt = Double.NaN;
if (isNumberNext(stream) == true) { if (isNumberNext(stream) == true) {
nextNumber(stream); alt = nextNumber(stream);
} }
Circle circle = new Circle(lat, lon, alt, radius);
nextCloser(stream); nextCloser(stream);
return circle; return circle;
} }
@ -561,4 +596,8 @@ public class WellKnownText {
}); });
} }
private static double[] toArray(ArrayList<Double> doubles) {
return doubles.stream().mapToDouble(i -> i).toArray();
}
} }

View File

@ -30,10 +30,20 @@ import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier; import java.util.function.Function;
abstract class BaseGeometryTestCase<T extends Geometry> extends AbstractWireTestCase<T> { abstract class BaseGeometryTestCase<T extends Geometry> extends AbstractWireTestCase<T> {
@Override
protected final T createTestInstance() {
boolean hasAlt = randomBoolean();
T obj = createTestInstance(hasAlt);
assertEquals(hasAlt, obj.hasAlt());
return obj;
}
protected abstract T createTestInstance(boolean hasAlt);
@Override @Override
protected Writeable.Reader<T> instanceReader() { protected Writeable.Reader<T> instanceReader() {
throw new IllegalStateException("shouldn't be called in this test"); throw new IllegalStateException("shouldn't be called in this test");
@ -127,48 +137,86 @@ abstract class BaseGeometryTestCase<T extends Geometry> extends AbstractWireTest
return randomDoubleBetween(-180, 180, true); return randomDoubleBetween(-180, 180, true);
} }
public static Circle randomCircle() { public static Circle randomCircle(boolean hasAlt) {
return new Circle(randomDoubleBetween(-90, 90, true), randomDoubleBetween(-180, 180, true), randomDoubleBetween(0, 100, false)); if (hasAlt) {
return new Circle(randomDoubleBetween(-90, 90, true), randomDoubleBetween(-180, 180, true), randomDouble(),
randomDoubleBetween(0, 100, false));
} else {
return new Circle(randomDoubleBetween(-90, 90, true), randomDoubleBetween(-180, 180, true), randomDoubleBetween(0, 100, false));
}
} }
public static Line randomLine() { public static Line randomLine() {
return randomLine(randomBoolean());
}
public static Line randomLine(boolean hasAlts) {
int size = randomIntBetween(2, 10); int size = randomIntBetween(2, 10);
double[] lats = new double[size]; double[] lats = new double[size];
double[] lons = new double[size]; double[] lons = new double[size];
double[] alts = hasAlts ? new double[size] : null;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
lats[i] = randomLat(); lats[i] = randomLat();
lons[i] = randomLon(); lons[i] = randomLon();
if (hasAlts) {
alts[i] = randomDouble();
}
}
if (hasAlts) {
return new Line(lats, lons, alts);
} }
return new Line(lats, lons); return new Line(lats, lons);
} }
public static Point randomPoint() { public static Point randomPoint() {
return new Point(randomLat(), randomLon()); return randomPoint(randomBoolean());
} }
public static LinearRing randomLinearRing() { public static Point randomPoint(boolean hasAlt) {
if (hasAlt) {
return new Point(randomLat(), randomLon(), randomDouble());
} else {
return new Point(randomLat(), randomLon());
}
}
public static LinearRing randomLinearRing(boolean hasAlt) {
int size = randomIntBetween(3, 10); int size = randomIntBetween(3, 10);
double[] lats = new double[size + 1]; double[] lats = new double[size + 1];
double[] lons = new double[size + 1]; double[] lons = new double[size + 1];
double[] alts;
if (hasAlt) {
alts = new double[size + 1];
} else {
alts = null;
}
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
lats[i] = randomLat(); lats[i] = randomLat();
lons[i] = randomLon(); lons[i] = randomLon();
if (hasAlt) {
alts[i] = randomDouble();
}
} }
lats[size] = lats[0]; lats[size] = lats[0];
lons[size] = lons[0]; lons[size] = lons[0];
return new LinearRing(lats, lons); if (hasAlt) {
alts[size] = alts[0];
return new LinearRing(lats, lons, alts);
} else {
return new LinearRing(lats, lons);
}
} }
public static Polygon randomPolygon() { public static Polygon randomPolygon(boolean hasAlt) {
int size = randomIntBetween(0, 10); int size = randomIntBetween(0, 10);
List<LinearRing> holes = new ArrayList<>(); List<LinearRing> holes = new ArrayList<>();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
holes.add(randomLinearRing()); holes.add(randomLinearRing(hasAlt));
} }
if (holes.size() > 0) { if (holes.size() > 0) {
return new Polygon(randomLinearRing(), holes); return new Polygon(randomLinearRing(hasAlt), holes);
} else { } else {
return new Polygon(randomLinearRing()); return new Polygon(randomLinearRing(hasAlt));
} }
} }
@ -180,23 +228,23 @@ abstract class BaseGeometryTestCase<T extends Geometry> extends AbstractWireTest
return new Rectangle(Math.min(lat1, lat2), Math.max(lat1, lat2), minLon, maxLon); return new Rectangle(Math.min(lat1, lat2), Math.max(lat1, lat2), minLon, maxLon);
} }
public static GeometryCollection<Geometry> randomGeometryCollection() { public static GeometryCollection<Geometry> randomGeometryCollection(boolean hasAlt) {
return randomGeometryCollection(0); return randomGeometryCollection(0, hasAlt);
} }
private static GeometryCollection<Geometry> randomGeometryCollection(int level) { private static GeometryCollection<Geometry> randomGeometryCollection(int level, boolean hasAlt) {
int size = randomIntBetween(1, 10); int size = randomIntBetween(1, 10);
List<Geometry> shapes = new ArrayList<>(); List<Geometry> shapes = new ArrayList<>();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
@SuppressWarnings("unchecked") Supplier<Geometry> geometry = randomFrom( @SuppressWarnings("unchecked") Function<Boolean, Geometry> geometry = randomFrom(
BaseGeometryTestCase::randomCircle, BaseGeometryTestCase::randomCircle,
BaseGeometryTestCase::randomLine, BaseGeometryTestCase::randomLine,
BaseGeometryTestCase::randomPoint, BaseGeometryTestCase::randomPoint,
BaseGeometryTestCase::randomPolygon, BaseGeometryTestCase::randomPolygon,
BaseGeometryTestCase::randomRectangle, hasAlt ? BaseGeometryTestCase::randomPoint : (b) -> randomRectangle(),
level < 3 ? () -> randomGeometryCollection(level + 1) : BaseGeometryTestCase::randomPoint // don't build too deep level < 3 ? (b) -> randomGeometryCollection(level + 1, b) : BaseGeometryTestCase::randomPoint // don't build too deep
); );
shapes.add(geometry.get()); shapes.add(geometry.apply(hasAlt));
} }
return new GeometryCollection<>(shapes); return new GeometryCollection<>(shapes);
} }

View File

@ -26,14 +26,22 @@ import java.text.ParseException;
public class CircleTests extends BaseGeometryTestCase<Circle> { public class CircleTests extends BaseGeometryTestCase<Circle> {
@Override @Override
protected Circle createTestInstance() { protected Circle createTestInstance(boolean hasAlt) {
return new Circle(randomDoubleBetween(-90, 90, true), randomDoubleBetween(-180, 180, true), randomDoubleBetween(0, 100, false)); if (hasAlt) {
return new Circle(randomDoubleBetween(-90, 90, true), randomDoubleBetween(-180, 180, true), randomDouble(),
randomDoubleBetween(0, 100, false));
} else {
return new Circle(randomDoubleBetween(-90, 90, true), randomDoubleBetween(-180, 180, true), randomDoubleBetween(0, 100, false));
}
} }
public void testBasicSerialization() throws IOException, ParseException { public void testBasicSerialization() throws IOException, ParseException {
assertEquals("circle (20.0 10.0 15.0)", WellKnownText.toWKT(new Circle(10, 20, 15))); assertEquals("circle (20.0 10.0 15.0)", WellKnownText.toWKT(new Circle(10, 20, 15)));
assertEquals(new Circle(10, 20, 15), WellKnownText.fromWKT("circle (20.0 10.0 15.0)")); assertEquals(new Circle(10, 20, 15), WellKnownText.fromWKT("circle (20.0 10.0 15.0)"));
assertEquals("circle (20.0 10.0 15.0 25.0)", WellKnownText.toWKT(new Circle(10, 20, 25, 15)));
assertEquals(new Circle(10, 20, 25, 15), WellKnownText.fromWKT("circle (20.0 10.0 15.0 25.0)"));
assertEquals("circle EMPTY", WellKnownText.toWKT(Circle.EMPTY)); assertEquals("circle EMPTY", WellKnownText.toWKT(Circle.EMPTY));
assertEquals(Circle.EMPTY, WellKnownText.fromWKT("circle EMPTY)")); assertEquals(Circle.EMPTY, WellKnownText.fromWKT("circle EMPTY)"));
} }

View File

@ -28,10 +28,12 @@ import java.util.Collections;
public class GeometryCollectionTests extends BaseGeometryTestCase<GeometryCollection<Geometry>> { public class GeometryCollectionTests extends BaseGeometryTestCase<GeometryCollection<Geometry>> {
@Override @Override
protected GeometryCollection<Geometry> createTestInstance() { protected GeometryCollection<Geometry> createTestInstance(boolean hasAlt) {
return randomGeometryCollection(); return randomGeometryCollection(hasAlt);
} }
public void testBasicSerialization() throws IOException, ParseException { public void testBasicSerialization() throws IOException, ParseException {
assertEquals("geometrycollection (point (20.0 10.0),point EMPTY)", assertEquals("geometrycollection (point (20.0 10.0),point EMPTY)",
WellKnownText.toWKT(new GeometryCollection<Geometry>(Arrays.asList(new Point(10, 20), Point.EMPTY)))); WellKnownText.toWKT(new GeometryCollection<Geometry>(Arrays.asList(new Point(10, 20), Point.EMPTY))));
@ -50,5 +52,9 @@ public class GeometryCollectionTests extends BaseGeometryTestCase<GeometryCollec
ex = expectThrows(IllegalArgumentException.class, () -> new GeometryCollection<>(null)); ex = expectThrows(IllegalArgumentException.class, () -> new GeometryCollection<>(null));
assertEquals("the list of shapes cannot be null or empty", ex.getMessage()); assertEquals("the list of shapes cannot be null or empty", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new GeometryCollection<>(
Arrays.asList(new Point(10, 20), new Point(10, 20, 30))));
assertEquals("all elements of the collection should have the same number of dimension", ex.getMessage());
} }
} }

View File

@ -26,14 +26,19 @@ import java.text.ParseException;
public class LineTests extends BaseGeometryTestCase<Line> { public class LineTests extends BaseGeometryTestCase<Line> {
@Override @Override
protected Line createTestInstance() { protected Line createTestInstance(boolean hasAlt) {
return randomLine(); return randomLine(hasAlt);
} }
public void testBasicSerialization() throws IOException, ParseException { public void testBasicSerialization() throws IOException, ParseException {
assertEquals("linestring (3.0 1.0, 4.0 2.0)", WellKnownText.toWKT(new Line(new double[]{1, 2}, new double[]{3, 4}))); assertEquals("linestring (3.0 1.0, 4.0 2.0)", WellKnownText.toWKT(new Line(new double[]{1, 2}, new double[]{3, 4})));
assertEquals(new Line(new double[]{1, 2}, new double[]{3, 4}), WellKnownText.fromWKT("linestring (3 1, 4 2)")); assertEquals(new Line(new double[]{1, 2}, new double[]{3, 4}), WellKnownText.fromWKT("linestring (3 1, 4 2)"));
assertEquals("linestring (3.0 1.0 5.0, 4.0 2.0 6.0)", WellKnownText.toWKT(new Line(new double[]{1, 2}, new double[]{3, 4},
new double[]{5, 6})));
assertEquals(new Line(new double[]{1, 2}, new double[]{3, 4}, new double[]{6, 5}),
WellKnownText.fromWKT("linestring (3 1 6, 4 2 5)"));
assertEquals("linestring EMPTY", WellKnownText.toWKT(Line.EMPTY)); assertEquals("linestring EMPTY", WellKnownText.toWKT(Line.EMPTY));
assertEquals(Line.EMPTY, WellKnownText.fromWKT("linestring EMPTY)")); assertEquals(Line.EMPTY, WellKnownText.fromWKT("linestring EMPTY)"));
} }

View File

@ -33,7 +33,14 @@ public class LinearRingTests extends ESTestCase {
public void testInitValidation() { public void testInitValidation() {
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, IllegalArgumentException ex = expectThrows(IllegalArgumentException.class,
() -> new LinearRing(new double[]{1, 2, 3}, new double[]{3, 4, 5})); () -> new LinearRing(new double[]{1, 2, 3}, new double[]{3, 4, 5}));
assertEquals("first and last points of the linear ring must be the same (it must close itself): lats[0]=1.0 lats[2]=3.0", assertEquals("first and last points of the linear ring must be the same (it must close itself): lats[0]=1.0 lats[2]=3.0 " +
"lons[0]=3.0 lons[2]=5.0",
ex.getMessage());
ex = expectThrows(IllegalArgumentException.class,
() -> new LinearRing(new double[]{1, 2, 1}, new double[]{3, 4, 3}, new double[]{1, 2, 3}));
assertEquals("first and last points of the linear ring must be the same (it must close itself): lats[0]=1.0 lats[2]=1.0 " +
"lons[0]=3.0 lons[2]=3.0 alts[0]=1.0 alts[2]=3.0",
ex.getMessage()); ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new LinearRing(new double[]{1}, new double[]{3})); ex = expectThrows(IllegalArgumentException.class, () -> new LinearRing(new double[]{1}, new double[]{3}));

View File

@ -30,11 +30,11 @@ import java.util.List;
public class MultiLineTests extends BaseGeometryTestCase<MultiLine> { public class MultiLineTests extends BaseGeometryTestCase<MultiLine> {
@Override @Override
protected MultiLine createTestInstance() { protected MultiLine createTestInstance(boolean hasAlt) {
int size = randomIntBetween(1, 10); int size = randomIntBetween(1, 10);
List<Line> arr = new ArrayList<Line>(); List<Line> arr = new ArrayList<Line>();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
arr.add(randomLine()); arr.add(randomLine(hasAlt));
} }
return new MultiLine(arr); return new MultiLine(arr);
} }

View File

@ -24,17 +24,18 @@ import org.elasticsearch.geo.utils.WellKnownText;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
public class MultiPointTests extends BaseGeometryTestCase<MultiPoint> { public class MultiPointTests extends BaseGeometryTestCase<MultiPoint> {
@Override @Override
protected MultiPoint createTestInstance() { protected MultiPoint createTestInstance(boolean hasAlt) {
int size = randomIntBetween(1, 10); int size = randomIntBetween(1, 10);
List<Point> arr = new ArrayList<>(); List<Point> arr = new ArrayList<>();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
arr.add(randomPoint()); arr.add(randomPoint(hasAlt));
} }
return new MultiPoint(arr); return new MultiPoint(arr);
} }
@ -45,6 +46,16 @@ public class MultiPointTests extends BaseGeometryTestCase<MultiPoint> {
assertEquals(new MultiPoint(Collections.singletonList(new Point(1 ,2))), assertEquals(new MultiPoint(Collections.singletonList(new Point(1 ,2))),
WellKnownText.fromWKT("multipoint (2 1)")); WellKnownText.fromWKT("multipoint (2 1)"));
assertEquals("multipoint (2.0 1.0, 3.0 4.0)",
WellKnownText.toWKT(new MultiPoint(Arrays.asList(new Point(1, 2), new Point(4, 3)))));
assertEquals(new MultiPoint(Arrays.asList(new Point(1, 2), new Point(4, 3))),
WellKnownText.fromWKT("multipoint (2 1, 3 4)"));
assertEquals("multipoint (2.0 1.0 10.0, 3.0 4.0 20.0)",
WellKnownText.toWKT(new MultiPoint(Arrays.asList(new Point(1, 2, 10), new Point(4, 3, 20)))));
assertEquals(new MultiPoint(Arrays.asList(new Point(1, 2, 10), new Point(4, 3, 20))),
WellKnownText.fromWKT("multipoint (2 1 10, 3 4 20)"));
assertEquals("multipoint EMPTY", WellKnownText.toWKT(MultiPoint.EMPTY)); assertEquals("multipoint EMPTY", WellKnownText.toWKT(MultiPoint.EMPTY));
assertEquals(MultiPoint.EMPTY, WellKnownText.fromWKT("multipoint EMPTY)")); assertEquals(MultiPoint.EMPTY, WellKnownText.fromWKT("multipoint EMPTY)"));
} }

View File

@ -30,11 +30,11 @@ import java.util.List;
public class MultiPolygonTests extends BaseGeometryTestCase<MultiPolygon> { public class MultiPolygonTests extends BaseGeometryTestCase<MultiPolygon> {
@Override @Override
protected MultiPolygon createTestInstance() { protected MultiPolygon createTestInstance(boolean hasAlt) {
int size = randomIntBetween(1, 10); int size = randomIntBetween(1, 10);
List<Polygon> arr = new ArrayList<>(); List<Polygon> arr = new ArrayList<>();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
arr.add(randomPolygon()); arr.add(randomPolygon(hasAlt));
} }
return new MultiPolygon(arr); return new MultiPolygon(arr);
} }

View File

@ -26,14 +26,17 @@ import java.text.ParseException;
public class PointTests extends BaseGeometryTestCase<Point> { public class PointTests extends BaseGeometryTestCase<Point> {
@Override @Override
protected Point createTestInstance() { protected Point createTestInstance(boolean hasAlt) {
return randomPoint(); return randomPoint(hasAlt);
} }
public void testBasicSerialization() throws IOException, ParseException { public void testBasicSerialization() throws IOException, ParseException {
assertEquals("point (20.0 10.0)", WellKnownText.toWKT(new Point(10, 20))); assertEquals("point (20.0 10.0)", WellKnownText.toWKT(new Point(10, 20)));
assertEquals(new Point(10, 20), WellKnownText.fromWKT("point (20.0 10.0)")); assertEquals(new Point(10, 20), WellKnownText.fromWKT("point (20.0 10.0)"));
assertEquals("point (20.0 10.0 100.0)", WellKnownText.toWKT(new Point(10, 20, 100)));
assertEquals(new Point(10, 20, 100), WellKnownText.fromWKT("point (20.0 10.0 100.0)"));
assertEquals("point EMPTY", WellKnownText.toWKT(Point.EMPTY)); assertEquals("point EMPTY", WellKnownText.toWKT(Point.EMPTY));
assertEquals(Point.EMPTY, WellKnownText.fromWKT("point EMPTY)")); assertEquals(Point.EMPTY, WellKnownText.fromWKT("point EMPTY)"));
} }

View File

@ -23,11 +23,12 @@ import org.elasticsearch.geo.utils.WellKnownText;
import java.io.IOException; import java.io.IOException;
import java.text.ParseException; import java.text.ParseException;
import java.util.Collections;
public class PolygonTests extends BaseGeometryTestCase<Polygon> { public class PolygonTests extends BaseGeometryTestCase<Polygon> {
@Override @Override
protected Polygon createTestInstance() { protected Polygon createTestInstance(boolean hasAlt) {
return randomPolygon(); return randomPolygon(hasAlt);
} }
public void testBasicSerialization() throws IOException, ParseException { public void testBasicSerialization() throws IOException, ParseException {
@ -36,6 +37,11 @@ public class PolygonTests extends BaseGeometryTestCase<Polygon> {
assertEquals(new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3})), assertEquals(new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3})),
WellKnownText.fromWKT("polygon ((3 1, 4 2, 5 3, 3 1))")); WellKnownText.fromWKT("polygon ((3 1, 4 2, 5 3, 3 1))"));
assertEquals("polygon ((3.0 1.0 5.0, 4.0 2.0 4.0, 5.0 3.0 3.0, 3.0 1.0 5.0))",
WellKnownText.toWKT(new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}, new double[]{5, 4, 3, 5}))));
assertEquals(new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}, new double[]{5, 4, 3, 5})),
WellKnownText.fromWKT("polygon ((3 1 5, 4 2 4, 5 3 3, 3 1 5))"));
assertEquals("polygon EMPTY", WellKnownText.toWKT(Polygon.EMPTY)); assertEquals("polygon EMPTY", WellKnownText.toWKT(Polygon.EMPTY));
assertEquals(Polygon.EMPTY, WellKnownText.fromWKT("polygon EMPTY)")); assertEquals(Polygon.EMPTY, WellKnownText.fromWKT("polygon EMPTY)"));
} }
@ -48,5 +54,10 @@ public class PolygonTests extends BaseGeometryTestCase<Polygon> {
ex = expectThrows(IllegalArgumentException.class, ex = expectThrows(IllegalArgumentException.class,
() -> new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}), null)); () -> new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}), null));
assertEquals("holes must not be null", ex.getMessage()); assertEquals("holes must not be null", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class,
() -> new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}, new double[]{5, 4, 3, 5}),
Collections.singletonList(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}))));
assertEquals("holes must have the same number of dimensions as the polygon", ex.getMessage());
} }
} }

View File

@ -26,7 +26,8 @@ import java.text.ParseException;
public class RectangleTests extends BaseGeometryTestCase<Rectangle> { public class RectangleTests extends BaseGeometryTestCase<Rectangle> {
@Override @Override
protected Rectangle createTestInstance() { protected Rectangle createTestInstance(boolean hasAlt) {
assumeFalse("3rd dimension is not supported yet", hasAlt);
return randomRectangle(); return randomRectangle();
} }
@ -47,5 +48,8 @@ public class RectangleTests extends BaseGeometryTestCase<Rectangle> {
ex = expectThrows(IllegalArgumentException.class, () -> new Rectangle(2, 1, 2, 3)); ex = expectThrows(IllegalArgumentException.class, () -> new Rectangle(2, 1, 2, 3));
assertEquals("max lat cannot be less than min lat", ex.getMessage()); assertEquals("max lat cannot be less than min lat", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new Rectangle(1, 2, 2, 3, 5, Double.NaN));
assertEquals("only one altitude value is specified", ex.getMessage());
} }
} }