Geo: Adds a set of no dependency geo classes for JDBC driver (#36477)

Adds a set of geo classes to represent geo data in the JDBC driver and 
to be used as an intermediate format to pass geo shapes for indexing 
and query generation in #35320.

Relates to #35767 and #35320
This commit is contained in:
Igor Motov 2019-01-15 10:52:46 -05:00 committed by GitHub
parent 0b396a0c5e
commit 6f91f06d86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 2435 additions and 1 deletions

49
libs/geo/build.gradle Normal file
View File

@ -0,0 +1,49 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
apply plugin: 'elasticsearch.build'
apply plugin: 'nebula.maven-base-publish'
apply plugin: 'nebula.maven-scm'
dependencies {
if (isEclipse == false || project.path == ":libs:geo-tests") {
testCompile("org.elasticsearch.test:framework:${version}") {
exclude group: 'org.elasticsearch', module: 'elasticsearch-geo'
}
}
}
forbiddenApisMain {
// geo does not depend on server
// TODO: Need to decide how we want to handle for forbidden signatures with the changes to core
replaceSignatureFiles 'jdk-signatures'
}
if (isEclipse) {
// in eclipse the project is under a fake root, we need to change around the source sets
sourceSets {
if (project.path == ":libs:geo") {
main.java.srcDirs = ['java']
main.resources.srcDirs = ['resources']
} else {
test.java.srcDirs = ['java']
test.resources.srcDirs = ['resources']
}
}
}

View File

@ -0,0 +1,104 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
/**
* Circle geometry (not part of WKT standard, but used in elasticsearch)
*/
public class Circle implements Geometry {
public static final Circle EMPTY = new Circle();
private final double lat;
private final double lon;
private final double radiusMeters;
private Circle() {
lat = 0;
lon = 0;
radiusMeters = -1;
}
public Circle(final double lat, final double lon, final double radiusMeters) {
this.lat = lat;
this.lon = lon;
this.radiusMeters = radiusMeters;
if (radiusMeters < 0 ) {
throw new IllegalArgumentException("Circle radius [" + radiusMeters + "] cannot be negative");
}
GeometryUtils.checkLatitude(lat);
GeometryUtils.checkLongitude(lon);
}
@Override
public ShapeType type() {
return ShapeType.CIRCLE;
}
public double getLat() {
return lat;
}
public double getLon() {
return lon;
}
public double getRadiusMeters() {
return radiusMeters;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Circle circle = (Circle) o;
if (Double.compare(circle.lat, lat) != 0) return false;
if (Double.compare(circle.lon, lon) != 0) return false;
return (Double.compare(circle.radiusMeters, radiusMeters) == 0);
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(lat);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(lon);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(radiusMeters);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public <T> T visit(GeometryVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
public boolean isEmpty() {
return radiusMeters < 0;
}
@Override
public String toString() {
return "lat=" + lat + ", lon=" + lon + ", radius=" + radiusMeters;
}
}

View File

@ -0,0 +1,32 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
/**
* Base class for all Geometry objects supported by elasticsearch
*/
public interface Geometry {
ShapeType type();
<T> T visit(GeometryVisitor<T> visitor);
boolean isEmpty();
}

View File

@ -0,0 +1,85 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
/**
* Collection of arbitrary geometry classes
*/
public class GeometryCollection<G extends Geometry> implements Geometry, Iterable<G> {
public static final GeometryCollection<Geometry> EMPTY = new GeometryCollection<>();
private final List<G> shapes;
public GeometryCollection() {
shapes = Collections.emptyList();
}
public GeometryCollection(List<G> shapes) {
if (shapes == null || shapes.isEmpty()) {
throw new IllegalArgumentException("the list of shapes cannot be null or empty");
}
this.shapes = shapes;
}
@Override
public ShapeType type() {
return ShapeType.GEOMETRYCOLLECTION;
}
@Override
public <T> T visit(GeometryVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
public boolean isEmpty() {
return shapes.isEmpty();
}
public int size() {
return shapes.size();
}
public G get(int i) {
return shapes.get(i);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GeometryCollection<?> that = (GeometryCollection<?>) o;
return Objects.equals(shapes, that.shapes);
}
@Override
public int hashCode() {
return Objects.hash(shapes);
}
@Override
public Iterator<G> iterator() {
return shapes.iterator();
}
}

View File

@ -0,0 +1,70 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
/**
* Geometry-related utility methods
*/
final class GeometryUtils {
/**
* Minimum longitude value.
*/
static final double MIN_LON_INCL = -180.0D;
/**
* Maximum longitude value.
*/
static final double MAX_LON_INCL = 180.0D;
/**
* Minimum latitude value.
*/
static final double MIN_LAT_INCL = -90.0D;
/**
* Maximum latitude value.
*/
static final double MAX_LAT_INCL = 90.0D;
// No instance:
private GeometryUtils() {
}
/**
* validates latitude value is within standard +/-90 coordinate bounds
*/
static void checkLatitude(double latitude) {
if (Double.isNaN(latitude) || latitude < MIN_LAT_INCL || latitude > MAX_LAT_INCL) {
throw new IllegalArgumentException(
"invalid latitude " + latitude + "; must be between " + MIN_LAT_INCL + " and " + MAX_LAT_INCL);
}
}
/**
* validates longitude value is within standard +/-180 coordinate bounds
*/
static void checkLongitude(double longitude) {
if (Double.isNaN(longitude) || longitude < MIN_LON_INCL || longitude > MAX_LON_INCL) {
throw new IllegalArgumentException(
"invalid longitude " + longitude + "; must be between " + MIN_LON_INCL + " and " + MAX_LON_INCL);
}
}
}

View File

@ -0,0 +1,69 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
/**
* Support class for creating Geometry Visitors.
* <p>
* This is an implementation of the Visitor pattern. The basic idea is to simplify adding new operations on Geometries, without
* constantly modifying and adding new functionality to the Geometry hierarchy and keeping it as lightweight as possible.
* <p>
* It is a more object-oriented alternative to structures like this:
* <pre>
* if (obj instanceof This) {
* doThis((This) obj);
* } elseif (obj instanceof That) {
* doThat((That) obj);
* ...
* } else {
* throw new IllegalArgumentException("Unknown object " + obj);
* }
* </pre>
* <p>
* The Visitor Pattern replaces this structure with Interface inheritance making it easier to identify all places that are using this
* structure, and making a shape a compile-time failure instead of runtime.
* <p>
* See {@link org.elasticsearch.geo.utils.WellKnownText#toWKT(Geometry, StringBuilder)} for an example of how this interface is used.
*
* @see <a href="https://en.wikipedia.org/wiki/Visitor_pattern">Visitor Pattern</a>
*/
public interface GeometryVisitor<T> {
T visit(Circle circle);
T visit(GeometryCollection<?> collection);
T visit(Line line);
T visit(LinearRing ring);
T visit(MultiLine multiLine);
T visit(MultiPoint multiPoint);
T visit(MultiPolygon multiPolygon);
T visit(Point point);
T visit(Polygon polygon);
T visit(Rectangle rectangle);
}

View File

@ -0,0 +1,106 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import java.util.Arrays;
/**
* Represents a Line on the earth's surface in lat/lon decimal degrees.
*/
public class Line implements Geometry {
public static final Line EMPTY = new Line();
private final double[] lats;
private final double[] lons;
protected Line() {
lats = new double[0];
lons = new double[0];
}
public Line(double[] lats, double[] lons) {
this.lats = lats;
this.lons = lons;
if (lats == null) {
throw new IllegalArgumentException("lats must not be null");
}
if (lons == null) {
throw new IllegalArgumentException("lons must not be null");
}
if (lats.length != lons.length) {
throw new IllegalArgumentException("lats and lons must be equal length");
}
if (lats.length < 2) {
throw new IllegalArgumentException("at least two points in the line is required");
}
for (int i = 0; i < lats.length; i++) {
GeometryUtils.checkLatitude(lats[i]);
GeometryUtils.checkLongitude(lons[i]);
}
}
public int length() {
return lats.length;
}
public double getLat(int i) {
return lats[i];
}
public double getLon(int i) {
return lons[i];
}
@Override
public ShapeType type() {
return ShapeType.LINESTRING;
}
@Override
public <T> T visit(GeometryVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
public boolean isEmpty() {
return lats.length == 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Line line = (Line) o;
return Arrays.equals(lats, line.lats) &&
Arrays.equals(lons, line.lons);
}
@Override
public int hashCode() {
int result = Arrays.hashCode(lats);
result = 31 * result + Arrays.hashCode(lons);
return result;
}
@Override
public String toString() {
return "lats=" + Arrays.toString(lats) +
", lons=" + Arrays.toString(lons);
}
}

View File

@ -0,0 +1,53 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
/**
* Represents a closed line on the earth's surface in lat/lon decimal degrees.
* <p>
* Cannot be serialized by WKT directly but used as a part of polygon
*/
public class LinearRing extends Line {
public static final LinearRing EMPTY = new LinearRing();
private LinearRing() {
}
public LinearRing(double[] lats, double[] lons) {
super(lats, lons);
if (lats.length < 2) {
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]) {
throw new IllegalArgumentException("first and last points of the linear ring must be the same (it must close itself): lats[0]="
+ lats[0] + " lats[" + (lats.length - 1) + "]=" + lats[lats.length - 1]);
}
}
@Override
public ShapeType type() {
return ShapeType.LINEARRING;
}
@Override
public <T> T visit(GeometryVisitor<T> visitor) {
return visitor.visit(this);
}
}

View File

@ -0,0 +1,46 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import java.util.List;
/**
* Represents a MultiLine geometry object on the earth's surface.
*/
public class MultiLine extends GeometryCollection<Line> {
public static final MultiLine EMPTY = new MultiLine();
private MultiLine() {
}
public MultiLine(List<Line> lines) {
super(lines);
}
@Override
public ShapeType type() {
return ShapeType.MULTILINESTRING;
}
@Override
public <T> T visit(GeometryVisitor<T> visitor) {
return visitor.visit(this);
}
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import java.util.List;
/**
* Represents a MultiPoint object on the earth's surface in decimal degrees.
*/
public class MultiPoint extends GeometryCollection<Point> {
public static final MultiPoint EMPTY = new MultiPoint();
private MultiPoint() {
}
public MultiPoint(List<Point> points) {
super(points);
}
@Override
public ShapeType type() {
return ShapeType.MULTIPOINT;
}
@Override
public <T> T visit(GeometryVisitor<T> visitor) {
return visitor.visit(this);
}
}

View File

@ -0,0 +1,46 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import java.util.List;
/**
* Collection of polygons
*/
public class MultiPolygon extends GeometryCollection<Polygon> {
public static final MultiPolygon EMPTY = new MultiPolygon();
private MultiPolygon() {
}
public MultiPolygon(List<Polygon> polygons) {
super(polygons);
}
@Override
public ShapeType type() {
return ShapeType.MULTIPOLYGON;
}
@Override
public <T> T visit(GeometryVisitor<T> visitor) {
return visitor.visit(this);
}
}

View File

@ -0,0 +1,95 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
/**
* Represents a Point on the earth's surface in decimal degrees.
*/
public class Point implements Geometry {
public static final Point EMPTY = new Point();
private final double lat;
private final double lon;
private final boolean empty;
private Point() {
lat = 0;
lon = 0;
empty = true;
}
public Point(double lat, double lon) {
GeometryUtils.checkLatitude(lat);
GeometryUtils.checkLongitude(lon);
this.lat = lat;
this.lon = lon;
this.empty = false;
}
@Override
public ShapeType type() {
return ShapeType.POINT;
}
public double lat() {
return lat;
}
public double lon() {
return lon;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
if (point.empty != empty) return false;
if (Double.compare(point.lat, lat) != 0) return false;
return Double.compare(point.lon, lon) == 0;
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(lat);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(lon);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public <T> T visit(GeometryVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
public boolean isEmpty() {
return empty;
}
@Override
public String toString() {
return "lat=" + lat + ", lon=" + lon;
}
}

View File

@ -0,0 +1,122 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Represents a closed polygon on the earth's surface with optional holes
*/
public final class Polygon implements Geometry {
public static final Polygon EMPTY = new Polygon();
private final LinearRing polygon;
private final List<LinearRing> holes;
private Polygon() {
polygon = LinearRing.EMPTY;
holes = Collections.emptyList();
}
/**
* Creates a new Polygon from the supplied latitude/longitude array, and optionally any holes.
*/
public Polygon(LinearRing polygon, List<LinearRing> holes) {
this.polygon = polygon;
this.holes = holes;
if (holes == null) {
throw new IllegalArgumentException("holes must not be null");
}
checkRing(polygon);
for (LinearRing hole : holes) {
checkRing(hole);
}
}
/**
* Creates a new Polygon from the supplied latitude/longitude array, and optionally any holes.
*/
public Polygon(LinearRing polygon) {
this(polygon, Collections.emptyList());
}
@Override
public ShapeType type() {
return ShapeType.POLYGON;
}
private void checkRing(LinearRing ring) {
if (ring.length() < 4) {
throw new IllegalArgumentException("at least 4 polygon points required");
}
}
public int getNumberOfHoles() {
return holes.size();
}
public LinearRing getPolygon() {
return polygon;
}
public LinearRing getHole(int i) {
if (i >= holes.size()) {
throw new IllegalArgumentException("Index " + i + " is outside the bounds of the " + holes.size() + " polygon holes");
}
return holes.get(i);
}
@Override
public <T> T visit(GeometryVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
public boolean isEmpty() {
return polygon.isEmpty();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("polygon=").append(polygon);
if (holes.size() > 0) {
sb.append(", holes=");
sb.append(holes);
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Polygon polygon1 = (Polygon) o;
return Objects.equals(polygon, polygon1.polygon) &&
Objects.equals(holes, polygon1.holes);
}
@Override
public int hashCode() {
return Objects.hash(polygon, holes);
}
}

View File

@ -0,0 +1,168 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
/**
* Represents a lat/lon rectangle in decimal degrees.
*/
public class Rectangle implements Geometry {
public static final Rectangle EMPTY = new Rectangle();
/**
* maximum longitude value (in degrees)
*/
private final double minLat;
/**
* minimum longitude value (in degrees)
*/
private final double minLon;
/**
* maximum latitude value (in degrees)
*/
private final double maxLat;
/**
* minimum latitude value (in degrees)
*/
private final double maxLon;
private final boolean empty;
private Rectangle() {
minLat = 0;
minLon = 0;
maxLat = 0;
maxLon = 0;
empty = true;
}
/**
* Constructs a bounding box by first validating the provided latitude and longitude coordinates
*/
public Rectangle(double minLat, double maxLat, double minLon, double maxLon) {
GeometryUtils.checkLatitude(minLat);
GeometryUtils.checkLatitude(maxLat);
GeometryUtils.checkLongitude(minLon);
GeometryUtils.checkLongitude(maxLon);
this.minLon = minLon;
this.maxLon = maxLon;
this.minLat = minLat;
this.maxLat = maxLat;
empty = false;
if (maxLat < minLat) {
throw new IllegalArgumentException("max lat cannot be less than min lat");
}
}
public double getWidth() {
if (crossesDateline()) {
return GeometryUtils.MAX_LON_INCL - minLon + maxLon - GeometryUtils.MIN_LON_INCL;
}
return maxLon - minLon;
}
public double getHeight() {
return maxLat - minLat;
}
public double getMinLat() {
return minLat;
}
public double getMinLon() {
return minLon;
}
public double getMaxLat() {
return maxLat;
}
public double getMaxLon() {
return maxLon;
}
@Override
public ShapeType type() {
return ShapeType.ENVELOPE;
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append("Rectangle(lat=");
b.append(minLat);
b.append(" TO ");
b.append(maxLat);
b.append(" lon=");
b.append(minLon);
b.append(" TO ");
b.append(maxLon);
if (maxLon < minLon) {
b.append(" [crosses dateline!]");
}
b.append(")");
return b.toString();
}
/**
* Returns true if this bounding box crosses the dateline
*/
public boolean crossesDateline() {
return maxLon < minLon;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Rectangle rectangle = (Rectangle) o;
if (Double.compare(rectangle.minLat, minLat) != 0) return false;
if (Double.compare(rectangle.minLon, minLon) != 0) return false;
if (Double.compare(rectangle.maxLat, maxLat) != 0) return false;
return Double.compare(rectangle.maxLon, maxLon) == 0;
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(minLat);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(minLon);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(maxLat);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(maxLon);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public <T> T visit(GeometryVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
public boolean isEmpty() {
return empty;
}
}

View File

@ -0,0 +1,36 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
/**
* Shape types supported by elasticsearch
*/
public enum ShapeType {
POINT,
MULTIPOINT,
LINESTRING,
MULTILINESTRING,
POLYGON,
MULTIPOLYGON,
GEOMETRYCOLLECTION,
LINEARRING, // not serialized by itself in WKT or WKB
ENVELOPE, // not part of the actual WKB spec
CIRCLE; // not part of the actual WKB spec
}

View File

@ -0,0 +1,24 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Common Geo classes
*/
package org.elasticsearch.geo;

View File

@ -0,0 +1,560 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.utils;
import org.elasticsearch.geo.geometry.Circle;
import org.elasticsearch.geo.geometry.Geometry;
import org.elasticsearch.geo.geometry.GeometryCollection;
import org.elasticsearch.geo.geometry.GeometryVisitor;
import org.elasticsearch.geo.geometry.Line;
import org.elasticsearch.geo.geometry.LinearRing;
import org.elasticsearch.geo.geometry.MultiLine;
import org.elasticsearch.geo.geometry.MultiPoint;
import org.elasticsearch.geo.geometry.MultiPolygon;
import org.elasticsearch.geo.geometry.Point;
import org.elasticsearch.geo.geometry.Polygon;
import org.elasticsearch.geo.geometry.Rectangle;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/**
* Utility class for converting to and from WKT
*/
public class WellKnownText {
public static final String EMPTY = "EMPTY";
public static final String SPACE = " ";
public static final String LPAREN = "(";
public static final String RPAREN = ")";
public static final String COMMA = ",";
public static final String NAN = "NaN";
private static final String NUMBER = "<NUMBER>";
private static final String EOF = "END-OF-STREAM";
private static final String EOL = "END-OF-LINE";
public static String toWKT(Geometry geometry) {
StringBuilder builder = new StringBuilder();
toWKT(geometry, builder);
return builder.toString();
}
public static void toWKT(Geometry geometry, StringBuilder sb) {
sb.append(getWKTName(geometry));
sb.append(SPACE);
if (geometry.isEmpty()) {
sb.append(EMPTY);
} else {
geometry.visit(new GeometryVisitor<Void>() {
@Override
public Void visit(Circle circle) {
sb.append(LPAREN);
visitPoint(circle.getLon(), circle.getLat());
sb.append(SPACE);
sb.append(circle.getRadiusMeters());
sb.append(RPAREN);
return null;
}
@Override
public Void visit(GeometryCollection<?> collection) {
if (collection.size() == 0) {
sb.append(EMPTY);
} else {
sb.append(LPAREN);
toWKT(collection.get(0), sb);
for (int i = 1; i < collection.size(); ++i) {
sb.append(COMMA);
toWKT(collection.get(i), sb);
}
sb.append(RPAREN);
}
return null;
}
@Override
public Void visit(Line line) {
sb.append(LPAREN);
visitPoint(line.getLon(0), line.getLat(0));
for (int i = 1; i < line.length(); ++i) {
sb.append(COMMA);
sb.append(SPACE);
visitPoint(line.getLon(i), line.getLat(i));
}
sb.append(RPAREN);
return null;
}
@Override
public Void visit(LinearRing ring) {
throw new IllegalArgumentException("Linear ring is not supported by WKT");
}
@Override
public Void visit(MultiLine multiLine) {
visitCollection(multiLine);
return null;
}
@Override
public Void visit(MultiPoint multiPoint) {
// walk through coordinates:
sb.append(LPAREN);
visitPoint(multiPoint.get(0).lon(), multiPoint.get(0).lat());
for (int i = 1; i < multiPoint.size(); ++i) {
sb.append(COMMA);
sb.append(SPACE);
Point point = multiPoint.get(i);
visitPoint(point.lon(), point.lat());
}
sb.append(RPAREN);
return null;
}
@Override
public Void visit(MultiPolygon multiPolygon) {
visitCollection(multiPolygon);
return null;
}
@Override
public Void visit(Point point) {
if (point.isEmpty()) {
sb.append(EMPTY);
} else {
sb.append(LPAREN);
visitPoint(point.lon(), point.lat());
sb.append(RPAREN);
}
return null;
}
private void visitPoint(double lon, double lat) {
sb.append(lon).append(SPACE).append(lat);
}
private void visitCollection(GeometryCollection<?> collection) {
if (collection.size() == 0) {
sb.append(EMPTY);
} else {
sb.append(LPAREN);
collection.get(0).visit(this);
for (int i = 1; i < collection.size(); ++i) {
sb.append(COMMA);
collection.get(i).visit(this);
}
sb.append(RPAREN);
}
}
@Override
public Void visit(Polygon polygon) {
sb.append(LPAREN);
visit((Line) polygon.getPolygon());
int numberOfHoles = polygon.getNumberOfHoles();
for (int i = 0; i < numberOfHoles; ++i) {
sb.append(", ");
visit((Line) polygon.getHole(i));
}
sb.append(RPAREN);
return null;
}
@Override
public Void visit(Rectangle rectangle) {
sb.append(LPAREN);
// minX, maxX, maxY, minY
sb.append(rectangle.getMinLon());
sb.append(COMMA);
sb.append(SPACE);
sb.append(rectangle.getMaxLon());
sb.append(COMMA);
sb.append(SPACE);
sb.append(rectangle.getMaxLat());
sb.append(COMMA);
sb.append(SPACE);
sb.append(rectangle.getMinLat());
sb.append(RPAREN);
return null;
}
});
}
}
public static Geometry fromWKT(String wkt) throws IOException, ParseException {
StringReader reader = new StringReader(wkt);
try {
// setup the tokenizer; configured to read words w/o numbers
StreamTokenizer tokenizer = new StreamTokenizer(reader);
tokenizer.resetSyntax();
tokenizer.wordChars('a', 'z');
tokenizer.wordChars('A', 'Z');
tokenizer.wordChars(128 + 32, 255);
tokenizer.wordChars('0', '9');
tokenizer.wordChars('-', '-');
tokenizer.wordChars('+', '+');
tokenizer.wordChars('.', '.');
tokenizer.whitespaceChars(' ', ' ');
tokenizer.whitespaceChars('\t', '\t');
tokenizer.whitespaceChars('\r', '\r');
tokenizer.whitespaceChars('\n', '\n');
tokenizer.commentChar('#');
return parseGeometry(tokenizer);
} finally {
reader.close();
}
}
/**
* parse geometry from the stream tokenizer
*/
private static Geometry parseGeometry(StreamTokenizer stream) throws IOException, ParseException {
final String type = nextWord(stream).toLowerCase(Locale.ROOT);
switch (type) {
case "point":
return parsePoint(stream);
case "multipoint":
return parseMultiPoint(stream);
case "linestring":
return parseLine(stream);
case "multilinestring":
return parseMultiLine(stream);
case "polygon":
return parsePolygon(stream);
case "multipolygon":
return parseMultiPolygon(stream);
case "bbox":
return parseBBox(stream);
case "geometrycollection":
return parseGeometryCollection(stream);
case "circle": // Not part of the standard, but we need it for internal serialization
return parseCircle(stream);
}
throw new IllegalArgumentException("Unknown geometry type: " + type);
}
private static GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream) throws IOException, ParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return GeometryCollection.EMPTY;
}
List<Geometry> shapes = new ArrayList<>();
shapes.add(parseGeometry(stream));
while (nextCloserOrComma(stream).equals(COMMA)) {
shapes.add(parseGeometry(stream));
}
return new GeometryCollection<>(shapes);
}
private static Point parsePoint(StreamTokenizer stream) throws IOException, ParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return Point.EMPTY;
}
double lon = nextNumber(stream);
double lat = nextNumber(stream);
Point pt = new Point(lat, lon);
if (isNumberNext(stream) == true) {
nextNumber(stream);
}
nextCloser(stream);
return pt;
}
private static void parseCoordinates(StreamTokenizer stream, ArrayList<Double> lats, ArrayList<Double> lons)
throws IOException, ParseException {
parseCoordinate(stream, lats, lons);
while (nextCloserOrComma(stream).equals(COMMA)) {
parseCoordinate(stream, lats, lons);
}
}
private static void parseCoordinate(StreamTokenizer stream, ArrayList<Double> lats, ArrayList<Double> lons)
throws IOException, ParseException {
lons.add(nextNumber(stream));
lats.add(nextNumber(stream));
if (isNumberNext(stream)) {
nextNumber(stream);
}
}
private static MultiPoint parseMultiPoint(StreamTokenizer stream) throws IOException, ParseException {
String token = nextEmptyOrOpen(stream);
if (token.equals(EMPTY)) {
return MultiPoint.EMPTY;
}
ArrayList<Double> lats = new ArrayList<>();
ArrayList<Double> lons = new ArrayList<>();
ArrayList<Point> points = new ArrayList<>();
parseCoordinates(stream, lats, lons);
for (int i = 0; i < lats.size(); i++) {
points.add(new Point(lats.get(i), lons.get(i)));
}
return new MultiPoint(Collections.unmodifiableList(points));
}
private static Line parseLine(StreamTokenizer stream) throws IOException, ParseException {
String token = nextEmptyOrOpen(stream);
if (token.equals(EMPTY)) {
return Line.EMPTY;
}
ArrayList<Double> lats = new ArrayList<>();
ArrayList<Double> lons = new ArrayList<>();
parseCoordinates(stream, lats, lons);
return new Line(lats.stream().mapToDouble(i -> i).toArray(), lons.stream().mapToDouble(i -> i).toArray());
}
private static MultiLine parseMultiLine(StreamTokenizer stream) throws IOException, ParseException {
String token = nextEmptyOrOpen(stream);
if (token.equals(EMPTY)) {
return MultiLine.EMPTY;
}
ArrayList<Line> lines = new ArrayList<>();
lines.add(parseLine(stream));
while (nextCloserOrComma(stream).equals(COMMA)) {
lines.add(parseLine(stream));
}
return new MultiLine(Collections.unmodifiableList(lines));
}
private static LinearRing parsePolygonHole(StreamTokenizer stream) throws IOException, ParseException {
nextOpener(stream);
ArrayList<Double> lats = new ArrayList<>();
ArrayList<Double> lons = new ArrayList<>();
parseCoordinates(stream, lats, lons);
return new LinearRing(lats.stream().mapToDouble(i -> i).toArray(), lons.stream().mapToDouble(i -> i).toArray());
}
private static Polygon parsePolygon(StreamTokenizer stream) throws IOException, ParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return Polygon.EMPTY;
}
nextOpener(stream);
ArrayList<Double> lats = new ArrayList<>();
ArrayList<Double> lons = new ArrayList<>();
parseCoordinates(stream, lats, lons);
ArrayList<LinearRing> holes = new ArrayList<>();
while (nextCloserOrComma(stream).equals(COMMA)) {
holes.add(parsePolygonHole(stream));
}
if (holes.isEmpty()) {
return new Polygon(new LinearRing(lats.stream().mapToDouble(i -> i).toArray(), lons.stream().mapToDouble(i -> i).toArray()));
} else {
return new Polygon(
new LinearRing(lats.stream().mapToDouble(i -> i).toArray(), lons.stream().mapToDouble(i -> i).toArray()),
Collections.unmodifiableList(holes));
}
}
private static MultiPolygon parseMultiPolygon(StreamTokenizer stream) throws IOException, ParseException {
String token = nextEmptyOrOpen(stream);
if (token.equals(EMPTY)) {
return MultiPolygon.EMPTY;
}
ArrayList<Polygon> polygons = new ArrayList<>();
polygons.add(parsePolygon(stream));
while (nextCloserOrComma(stream).equals(COMMA)) {
polygons.add(parsePolygon(stream));
}
return new MultiPolygon(Collections.unmodifiableList(polygons));
}
private static Rectangle parseBBox(StreamTokenizer stream) throws IOException, ParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return Rectangle.EMPTY;
}
double minLon = nextNumber(stream);
nextComma(stream);
double maxLon = nextNumber(stream);
nextComma(stream);
double maxLat = nextNumber(stream);
nextComma(stream);
double minLat = nextNumber(stream);
nextCloser(stream);
return new Rectangle(minLat, maxLat, minLon, maxLon);
}
private static Circle parseCircle(StreamTokenizer stream) throws IOException, ParseException {
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
return Circle.EMPTY;
}
double lon = nextNumber(stream);
double lat = nextNumber(stream);
double radius = nextNumber(stream);
Circle circle = new Circle(lat, lon, radius);
if (isNumberNext(stream) == true) {
nextNumber(stream);
}
nextCloser(stream);
return circle;
}
/**
* next word in the stream
*/
private static String nextWord(StreamTokenizer stream) throws ParseException, IOException {
switch (stream.nextToken()) {
case StreamTokenizer.TT_WORD:
final String word = stream.sval;
return word.equalsIgnoreCase(EMPTY) ? EMPTY : word;
case '(':
return LPAREN;
case ')':
return RPAREN;
case ',':
return COMMA;
}
throw new ParseException("expected word but found: " + tokenString(stream), stream.lineno());
}
private static double nextNumber(StreamTokenizer stream) throws IOException, ParseException {
if (stream.nextToken() == StreamTokenizer.TT_WORD) {
if (stream.sval.equalsIgnoreCase(NAN)) {
return Double.NaN;
} else {
try {
return Double.parseDouble(stream.sval);
} catch (NumberFormatException e) {
throw new ParseException("invalid number found: " + stream.sval, stream.lineno());
}
}
}
throw new ParseException("expected number but found: " + tokenString(stream), stream.lineno());
}
private static String tokenString(StreamTokenizer stream) {
switch (stream.ttype) {
case StreamTokenizer.TT_WORD:
return stream.sval;
case StreamTokenizer.TT_EOF:
return EOF;
case StreamTokenizer.TT_EOL:
return EOL;
case StreamTokenizer.TT_NUMBER:
return NUMBER;
}
return "'" + (char) stream.ttype + "'";
}
private static boolean isNumberNext(StreamTokenizer stream) throws IOException {
final int type = stream.nextToken();
stream.pushBack();
return type == StreamTokenizer.TT_WORD;
}
private static String nextEmptyOrOpen(StreamTokenizer stream) throws IOException, ParseException {
final String next = nextWord(stream);
if (next.equals(EMPTY) || next.equals(LPAREN)) {
return next;
}
throw new ParseException("expected " + EMPTY + " or " + LPAREN
+ " but found: " + tokenString(stream), stream.lineno());
}
private static String nextCloser(StreamTokenizer stream) throws IOException, ParseException {
if (nextWord(stream).equals(RPAREN)) {
return RPAREN;
}
throw new ParseException("expected " + RPAREN + " but found: " + tokenString(stream), stream.lineno());
}
private static String nextComma(StreamTokenizer stream) throws IOException, ParseException {
if (nextWord(stream).equals(COMMA) == true) {
return COMMA;
}
throw new ParseException("expected " + COMMA + " but found: " + tokenString(stream), stream.lineno());
}
private static String nextOpener(StreamTokenizer stream) throws IOException, ParseException {
if (nextWord(stream).equals(LPAREN)) {
return LPAREN;
}
throw new ParseException("expected " + LPAREN + " but found: " + tokenString(stream), stream.lineno());
}
private static String nextCloserOrComma(StreamTokenizer stream) throws IOException, ParseException {
String token = nextWord(stream);
if (token.equals(COMMA) || token.equals(RPAREN)) {
return token;
}
throw new ParseException("expected " + COMMA + " or " + RPAREN
+ " but found: " + tokenString(stream), stream.lineno());
}
public static String getWKTName(Geometry geometry) {
return geometry.visit(new GeometryVisitor<String>() {
@Override
public String visit(Circle circle) {
return "circle";
}
@Override
public String visit(GeometryCollection<?> collection) {
return "geometrycollection";
}
@Override
public String visit(Line line) {
return "linestring";
}
@Override
public String visit(LinearRing ring) {
throw new UnsupportedOperationException("line ring cannot be serialized using WKT");
}
@Override
public String visit(MultiLine multiLine) {
return "multilinestring";
}
@Override
public String visit(MultiPoint multiPoint) {
return "multipoint";
}
@Override
public String visit(MultiPolygon multiPolygon) {
return "multipolygon";
}
@Override
public String visit(Point point) {
return "point";
}
@Override
public String visit(Polygon polygon) {
return "polygon";
}
@Override
public String visit(Rectangle rectangle) {
return "bbox";
}
});
}
}

View File

@ -0,0 +1,203 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.geo.utils.WellKnownText;
import org.elasticsearch.test.AbstractWireTestCase;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
abstract class BaseGeometryTestCase<T extends Geometry> extends AbstractWireTestCase<T> {
@Override
protected Writeable.Reader<T> instanceReader() {
throw new IllegalStateException("shouldn't be called in this test");
}
@SuppressWarnings("unchecked")
@Override
protected T copyInstance(T instance, Version version) throws IOException {
String text = WellKnownText.toWKT(instance);
try {
return (T) WellKnownText.fromWKT(text);
} catch (ParseException e) {
throw new ElasticsearchException(e);
}
}
public void testVisitor() {
testVisitor(createTestInstance());
}
public static void testVisitor(Geometry geom) {
AtomicBoolean called = new AtomicBoolean(false);
Object result = geom.visit(new GeometryVisitor<Object>() {
private Object verify(Geometry geometry, String expectedClass) {
assertFalse("Visitor should be called only once", called.getAndSet(true));
assertSame(geom, geometry);
assertEquals(geometry.getClass().getName(), "org.elasticsearch.geo.geometry." + expectedClass);
return "result";
}
@Override
public Object visit(Circle circle) {
return verify(circle, "Circle");
}
@Override
public Object visit(GeometryCollection<?> collection) {
return verify(collection, "GeometryCollection"); }
@Override
public Object visit(Line line) {
return verify(line, "Line");
}
@Override
public Object visit(LinearRing ring) {
return verify(ring, "LinearRing");
}
@Override
public Object visit(MultiLine multiLine) {
return verify(multiLine, "MultiLine");
}
@Override
public Object visit(MultiPoint multiPoint) {
return verify(multiPoint, "MultiPoint");
}
@Override
public Object visit(MultiPolygon multiPolygon) {
return verify(multiPolygon, "MultiPolygon");
}
@Override
public Object visit(Point point) {
return verify(point, "Point");
}
@Override
public Object visit(Polygon polygon) {
return verify(polygon, "Polygon");
}
@Override
public Object visit(Rectangle rectangle) {
return verify(rectangle, "Rectangle");
}
});
assertTrue("visitor wasn't called", called.get());
assertEquals("result", result);
}
public static double randomLat() {
return randomDoubleBetween(-90, 90, true);
}
public static double randomLon() {
return randomDoubleBetween(-180, 180, true);
}
public static Circle randomCircle() {
return new Circle(randomDoubleBetween(-90, 90, true), randomDoubleBetween(-180, 180, true), randomDoubleBetween(0, 100, false));
}
public static Line randomLine() {
int size = randomIntBetween(2, 10);
double[] lats = new double[size];
double[] lons = new double[size];
for (int i = 0; i < size; i++) {
lats[i] = randomLat();
lons[i] = randomLon();
}
return new Line(lats, lons);
}
public static Point randomPoint() {
return new Point(randomLat(), randomLon());
}
public static LinearRing randomLinearRing() {
int size = randomIntBetween(3, 10);
double[] lats = new double[size + 1];
double[] lons = new double[size + 1];
for (int i = 0; i < size; i++) {
lats[i] = randomLat();
lons[i] = randomLon();
}
lats[size] = lats[0];
lons[size] = lons[0];
return new LinearRing(lats, lons);
}
public static Polygon randomPolygon() {
int size = randomIntBetween(0, 10);
List<LinearRing> holes = new ArrayList<>();
for (int i = 0; i < size; i++) {
holes.add(randomLinearRing());
}
if (holes.size() > 0) {
return new Polygon(randomLinearRing(), holes);
} else {
return new Polygon(randomLinearRing());
}
}
public static Rectangle randomRectangle() {
double lat1 = randomLat();
double lat2 = randomLat();
double minLon = randomLon();
double maxLon = randomLon();
return new Rectangle(Math.min(lat1, lat2), Math.max(lat1, lat2), minLon, maxLon);
}
public static GeometryCollection<Geometry> randomGeometryCollection() {
return randomGeometryCollection(0);
}
private static GeometryCollection<Geometry> randomGeometryCollection(int level) {
int size = randomIntBetween(1, 10);
List<Geometry> shapes = new ArrayList<>();
for (int i = 0; i < size; i++) {
@SuppressWarnings("unchecked") Supplier<Geometry> geometry = randomFrom(
BaseGeometryTestCase::randomCircle,
BaseGeometryTestCase::randomLine,
BaseGeometryTestCase::randomPoint,
BaseGeometryTestCase::randomPolygon,
BaseGeometryTestCase::randomRectangle,
level < 3 ? () -> randomGeometryCollection(level + 1) : BaseGeometryTestCase::randomPoint // don't build too deep
);
shapes.add(geometry.get());
}
return new GeometryCollection<>(shapes);
}
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import org.elasticsearch.geo.utils.WellKnownText;
import java.io.IOException;
import java.text.ParseException;
public class CircleTests extends BaseGeometryTestCase<Circle> {
@Override
protected Circle createTestInstance() {
return new Circle(randomDoubleBetween(-90, 90, true), randomDoubleBetween(-180, 180, true), randomDoubleBetween(0, 100, false));
}
public void testBasicSerialization() throws IOException, ParseException {
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("circle EMPTY", WellKnownText.toWKT(Circle.EMPTY));
assertEquals(Circle.EMPTY, WellKnownText.fromWKT("circle EMPTY)"));
}
public void testInitValidation() {
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> new Circle(10, 20, -1));
assertEquals("Circle radius [-1.0] cannot be negative", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new Circle(100, 20, 1));
assertEquals("invalid latitude 100.0; must be between -90.0 and 90.0", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new Circle(10, 200, 1));
assertEquals("invalid longitude 200.0; must be between -180.0 and 180.0", ex.getMessage());
}
}

View File

@ -0,0 +1,54 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import org.elasticsearch.geo.utils.WellKnownText;
import java.io.IOException;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collections;
public class GeometryCollectionTests extends BaseGeometryTestCase<GeometryCollection<Geometry>> {
@Override
protected GeometryCollection<Geometry> createTestInstance() {
return randomGeometryCollection();
}
public void testBasicSerialization() throws IOException, ParseException {
assertEquals("geometrycollection (point (20.0 10.0),point EMPTY)",
WellKnownText.toWKT(new GeometryCollection<Geometry>(Arrays.asList(new Point(10, 20), Point.EMPTY))));
assertEquals(new GeometryCollection<Geometry>(Arrays.asList(new Point(10, 20), Point.EMPTY)),
WellKnownText.fromWKT("geometrycollection (point (20.0 10.0),point EMPTY)"));
assertEquals("geometrycollection EMPTY", WellKnownText.toWKT(GeometryCollection.EMPTY));
assertEquals(GeometryCollection.EMPTY, WellKnownText.fromWKT("geometrycollection EMPTY)"));
}
@SuppressWarnings("ConstantConditions")
public void testInitValidation() {
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> new GeometryCollection<>(Collections.emptyList()));
assertEquals("the list of shapes cannot be null or empty", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new GeometryCollection<>(null));
assertEquals("the list of shapes cannot be null or empty", ex.getMessage());
}
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import org.elasticsearch.geo.utils.WellKnownText;
import java.io.IOException;
import java.text.ParseException;
public class LineTests extends BaseGeometryTestCase<Line> {
@Override
protected Line createTestInstance() {
return randomLine();
}
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(new Line(new double[]{1, 2}, new double[]{3, 4}), WellKnownText.fromWKT("linestring (3 1, 4 2)"));
assertEquals("linestring EMPTY", WellKnownText.toWKT(Line.EMPTY));
assertEquals(Line.EMPTY, WellKnownText.fromWKT("linestring EMPTY)"));
}
public void testInitValidation() {
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> new Line(new double[]{1}, new double[]{3}));
assertEquals("at least two points in the line is required", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new Line(new double[]{1, 2, 3, 1}, new double[]{3, 4, 500, 3}));
assertEquals("invalid longitude 500.0; must be between -180.0 and 180.0", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new Line(new double[]{1, 100, 3, 1}, new double[]{3, 4, 5, 3}));
assertEquals("invalid latitude 100.0; must be between -90.0 and 90.0", ex.getMessage());
}
}

View File

@ -0,0 +1,52 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import org.elasticsearch.geo.utils.WellKnownText;
import org.elasticsearch.test.ESTestCase;
public class LinearRingTests extends ESTestCase {
public void testBasicSerialization() {
UnsupportedOperationException ex = expectThrows(UnsupportedOperationException.class,
() -> WellKnownText.toWKT(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3})));
assertEquals("line ring cannot be serialized using WKT", ex.getMessage());
}
public void testInitValidation() {
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class,
() -> 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",
ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new LinearRing(new double[]{1}, new double[]{3}));
assertEquals("at least two points in the line is required", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 500, 3}));
assertEquals("invalid longitude 500.0; must be between -180.0 and 180.0", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new LinearRing(new double[]{1, 100, 3, 1}, new double[]{3, 4, 5, 3}));
assertEquals("invalid latitude 100.0; must be between -90.0 and 90.0", ex.getMessage());
}
public void testVisitor() {
BaseGeometryTestCase.testVisitor(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}));
}
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import org.elasticsearch.geo.utils.WellKnownText;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MultiLineTests extends BaseGeometryTestCase<MultiLine> {
@Override
protected MultiLine createTestInstance() {
int size = randomIntBetween(1, 10);
List<Line> arr = new ArrayList<Line>();
for (int i = 0; i < size; i++) {
arr.add(randomLine());
}
return new MultiLine(arr);
}
public void testBasicSerialization() throws IOException, ParseException {
assertEquals("multilinestring ((3.0 1.0, 4.0 2.0))", WellKnownText.toWKT(
new MultiLine(Collections.singletonList(new Line(new double[]{1, 2}, new double[]{3, 4})))));
assertEquals(new MultiLine(Collections.singletonList(new Line(new double[]{1, 2}, new double[]{3, 4}))),
WellKnownText.fromWKT("multilinestring ((3 1, 4 2))"));
assertEquals("multilinestring EMPTY", WellKnownText.toWKT(MultiLine.EMPTY));
assertEquals(MultiLine.EMPTY, WellKnownText.fromWKT("multilinestring EMPTY)"));
}
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import org.elasticsearch.geo.utils.WellKnownText;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MultiPointTests extends BaseGeometryTestCase<MultiPoint> {
@Override
protected MultiPoint createTestInstance() {
int size = randomIntBetween(1, 10);
List<Point> arr = new ArrayList<>();
for (int i = 0; i < size; i++) {
arr.add(randomPoint());
}
return new MultiPoint(arr);
}
public void testBasicSerialization() throws IOException, ParseException {
assertEquals("multipoint (2.0 1.0)", WellKnownText.toWKT(
new MultiPoint(Collections.singletonList(new Point(1, 2)))));
assertEquals(new MultiPoint(Collections.singletonList(new Point(1 ,2))),
WellKnownText.fromWKT("multipoint (2 1)"));
assertEquals("multipoint EMPTY", WellKnownText.toWKT(MultiPoint.EMPTY));
assertEquals(MultiPoint.EMPTY, WellKnownText.fromWKT("multipoint EMPTY)"));
}
}

View File

@ -0,0 +1,53 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import org.elasticsearch.geo.utils.WellKnownText;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MultiPolygonTests extends BaseGeometryTestCase<MultiPolygon> {
@Override
protected MultiPolygon createTestInstance() {
int size = randomIntBetween(1, 10);
List<Polygon> arr = new ArrayList<>();
for (int i = 0; i < size; i++) {
arr.add(randomPolygon());
}
return new MultiPolygon(arr);
}
public void testBasicSerialization() throws IOException, ParseException {
assertEquals("multipolygon (((3.0 1.0, 4.0 2.0, 5.0 3.0, 3.0 1.0)))",
WellKnownText.toWKT(new MultiPolygon(Collections.singletonList(
new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3}))))));
assertEquals(new MultiPolygon(Collections.singletonList(
new Polygon(new LinearRing(new double[]{1, 2, 3, 1}, new double[]{3, 4, 5, 3})))),
WellKnownText.fromWKT("multipolygon (((3.0 1.0, 4.0 2.0, 5.0 3.0, 3.0 1.0)))"));
assertEquals("multipolygon EMPTY", WellKnownText.toWKT(MultiPolygon.EMPTY));
assertEquals(MultiPolygon.EMPTY, WellKnownText.fromWKT("multipolygon EMPTY)"));
}
}

View File

@ -0,0 +1,48 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import org.elasticsearch.geo.utils.WellKnownText;
import java.io.IOException;
import java.text.ParseException;
public class PointTests extends BaseGeometryTestCase<Point> {
@Override
protected Point createTestInstance() {
return randomPoint();
}
public void testBasicSerialization() throws IOException, ParseException {
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("point EMPTY", WellKnownText.toWKT(Point.EMPTY));
assertEquals(Point.EMPTY, WellKnownText.fromWKT("point EMPTY)"));
}
public void testInitValidation() {
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> new Point(100, 10));
assertEquals("invalid latitude 100.0; must be between -90.0 and 90.0", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new Point(10, 500));
assertEquals("invalid longitude 500.0; must be between -180.0 and 180.0", ex.getMessage());
}
}

View File

@ -0,0 +1,52 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import org.elasticsearch.geo.utils.WellKnownText;
import java.io.IOException;
import java.text.ParseException;
public class PolygonTests extends BaseGeometryTestCase<Polygon> {
@Override
protected Polygon createTestInstance() {
return randomPolygon();
}
public void testBasicSerialization() throws IOException, ParseException {
assertEquals("polygon ((3.0 1.0, 4.0 2.0, 5.0 3.0, 3.0 1.0))",
WellKnownText.toWKT(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))"));
assertEquals("polygon EMPTY", WellKnownText.toWKT(Polygon.EMPTY));
assertEquals(Polygon.EMPTY, WellKnownText.fromWKT("polygon EMPTY)"));
}
public void testInitValidation() {
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class,
() -> new Polygon(new LinearRing(new double[]{1, 2, 1}, new double[]{3, 4, 3})));
assertEquals("at least 4 polygon points required", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class,
() -> 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());
}
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.geo.geometry;
import org.elasticsearch.geo.utils.WellKnownText;
import java.io.IOException;
import java.text.ParseException;
public class RectangleTests extends BaseGeometryTestCase<Rectangle> {
@Override
protected Rectangle createTestInstance() {
return randomRectangle();
}
public void testBasicSerialization() throws IOException, ParseException {
assertEquals("bbox (10.0, 20.0, 40.0, 30.0)", WellKnownText.toWKT(new Rectangle(30, 40, 10, 20)));
assertEquals(new Rectangle(30, 40, 10, 20), WellKnownText.fromWKT("bbox (10.0, 20.0, 40.0, 30.0)"));
assertEquals("bbox EMPTY", WellKnownText.toWKT(Rectangle.EMPTY));
assertEquals(Rectangle.EMPTY, WellKnownText.fromWKT("bbox EMPTY)"));
}
public void testInitValidation() {
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () -> new Rectangle(100, 1, 2, 3));
assertEquals("invalid latitude 100.0; must be between -90.0 and 90.0", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new Rectangle(1, 2, 200, 3));
assertEquals("invalid longitude 200.0; must be between -180.0 and 180.0", ex.getMessage());
ex = expectThrows(IllegalArgumentException.class, () -> new Rectangle(2, 1, 2, 3));
assertEquals("max lat cannot be less than min lat", ex.getMessage());
}
}

View File

@ -95,6 +95,7 @@ if (isEclipse) {
projects << 'libs:x-content-tests'
projects << 'libs:secure-sm-tests'
projects << 'libs:grok-tests'
projects << 'libs:geo-tests'
}
include projects.toArray(new String[0])
@ -130,7 +131,10 @@ if (isEclipse) {
project(":libs:grok").buildFileName = 'eclipse-build.gradle'
project(":libs:grok-tests").projectDir = new File(rootProject.projectDir, 'libs/grok/src/test')
project(":libs:grok-tests").buildFileName = 'eclipse-build.gradle'
}
project(":libs:geo").projectDir = new File(rootProject.projectDir, 'libs/geo/src/main')
project(":libs:geo").buildFileName = 'eclipse-build.gradle'
project(":libs:geo-tests").projectDir = new File(rootProject.projectDir, 'libs/geo/src/test')
project(":libs:geo-tests").buildFileName = 'eclipse-build.gradle'}
// look for extra plugins for elasticsearch
File extraProjects = new File(rootProject.projectDir.parentFile, "${dirName}-extra")
@ -141,3 +145,4 @@ if (extraProjects.exists()) {
}
project(":libs:cli").name = 'elasticsearch-cli'
project(":libs:geo").name = 'elasticsearch-geo'