/*
* 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.common.geo;
import com.spatial4j.core.exception.InvalidShapeException;
import com.spatial4j.core.shape.Circle;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.ShapeCollection;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.spatial4j.core.shape.jts.JtsPoint;
import com.vividsolutions.jts.geom.*;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.elasticsearch.common.geo.builders.ShapeBuilder.SPATIAL_CONTEXT;
/**
* Tests for {@code GeoJSONShapeParser}
*/
public class GeoJSONShapeParserTests extends ESTestCase {
private final static GeometryFactory GEOMETRY_FACTORY = SPATIAL_CONTEXT.getGeometryFactory();
public void testParse_simplePoint() throws IOException {
String pointGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Point")
.startArray("coordinates").value(100.0).value(0.0).endArray()
.endObject().string();
Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson);
}
public void testParse_lineString() throws IOException {
String lineGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "LineString")
.startArray("coordinates")
.startArray().value(100.0).value(0.0).endArray()
.startArray().value(101.0).value(1.0).endArray()
.endArray()
.endObject().string();
List<Coordinate> lineCoordinates = new ArrayList<>();
lineCoordinates.add(new Coordinate(100, 0));
lineCoordinates.add(new Coordinate(101, 1));
LineString expected = GEOMETRY_FACTORY.createLineString(
lineCoordinates.toArray(new Coordinate[lineCoordinates.size()]));
assertGeometryEquals(jtsGeom(expected), lineGeoJson);
}
public void testParse_multiLineString() throws IOException {
String multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "MultiLineString")
.startArray("coordinates")
.startArray()
.startArray().value(100.0).value(0.0).endArray()
.startArray().value(101.0).value(1.0).endArray()
.endArray()
.startArray()
.startArray().value(102.0).value(2.0).endArray()
.startArray().value(103.0).value(3.0).endArray()
.endArray()
.endArray()
.endObject().string();
MultiLineString expected = GEOMETRY_FACTORY.createMultiLineString(new LineString[]{
GEOMETRY_FACTORY.createLineString(new Coordinate[]{
new Coordinate(100, 0),
new Coordinate(101, 1),
}),
GEOMETRY_FACTORY.createLineString(new Coordinate[]{
new Coordinate(102, 2),
new Coordinate(103, 3),
}),
});
assertGeometryEquals(jtsGeom(expected), multilinesGeoJson);
}
public void testParse_circle() throws IOException {
String multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "circle")
.startArray("coordinates").value(100.0).value(0.0).endArray()
.field("radius", "100m")
.endObject().string();
Circle expected = SPATIAL_CONTEXT.makeCircle(100.0, 0.0, 360 * 100 / GeoUtils.EARTH_EQUATOR);
assertGeometryEquals(expected, multilinesGeoJson);
}
public void testParse_multiDimensionShapes() throws IOException {
// multi dimension point
String pointGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Point")
.startArray("coordinates").value(100.0).value(0.0).value(15.0).value(18.0).endArray()
.endObject().string();
Point expectedPt = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
assertGeometryEquals(new JtsPoint(expectedPt, SPATIAL_CONTEXT), pointGeoJson);
// multi dimension linestring
String lineGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "LineString")
.startArray("coordinates")
.startArray().value(100.0).value(0.0).value(15.0).endArray()
.startArray().value(101.0).value(1.0).value(18.0).value(19.0).endArray()
.endArray()
.endObject().string();
List<Coordinate> lineCoordinates = new ArrayList<>();
lineCoordinates.add(new Coordinate(100, 0));
lineCoordinates.add(new Coordinate(101, 1));
LineString expectedLS = GEOMETRY_FACTORY.createLineString(
lineCoordinates.toArray(new Coordinate[lineCoordinates.size()]));
assertGeometryEquals(jtsGeom(expectedLS), lineGeoJson);
}
public void testParse_envelope() throws IOException {
// test #1: envelope with expected coordinate order (TopLeft, BottomRight)
String multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "envelope")
.startArray("coordinates")
.startArray().value(-50).value(30).endArray()
.startArray().value(50).value(-30).endArray()
.endArray()
.endObject().string();
Rectangle expected = SPATIAL_CONTEXT.makeRectangle(-50, 50, -30, 30);
assertGeometryEquals(expected, multilinesGeoJson);
// test #2: envelope with agnostic coordinate order (TopRight, BottomLeft)
multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "envelope")
.startArray("coordinates")
.startArray().value(50).value(30).endArray()
.startArray().value(-50).value(-30).endArray()
.endArray()
.endObject().string();
expected = SPATIAL_CONTEXT.makeRectangle(-50, 50, -30, 30);
assertGeometryEquals(expected, multilinesGeoJson);
// test #3: "envelope" (actually a triangle) with invalid number of coordinates (TopRight, BottomLeft, BottomRight)
multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "envelope")
.startArray("coordinates")
.startArray().value(50).value(30).endArray()
.startArray().value(-50).value(-30).endArray()
.startArray().value(50).value(-39).endArray()
.endArray()
.endObject().string();
XContentParser parser = JsonXContent.jsonXContent.createParser(multilinesGeoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
// test #4: "envelope" with empty coordinates
multilinesGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "envelope")
.startArray("coordinates")
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(multilinesGeoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
}
public void testParse_polygonNoHoles() throws IOException {
String polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(100.0).value(1.0).endArray()
.startArray().value(101.0).value(1.0).endArray()
.startArray().value(101.0).value(0.0).endArray()
.startArray().value(100.0).value(0.0).endArray()
.startArray().value(100.0).value(1.0).endArray()
.endArray()
.endArray()
.endObject().string();
List<Coordinate> shellCoordinates = new ArrayList<>();
shellCoordinates.add(new Coordinate(100, 0));
shellCoordinates.add(new Coordinate(101, 0));
shellCoordinates.add(new Coordinate(101, 1));
shellCoordinates.add(new Coordinate(100, 1));
shellCoordinates.add(new Coordinate(100, 0));
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null);
assertGeometryEquals(jtsGeom(expected), polygonGeoJson);
}
public void testParse_invalidPoint() throws IOException {
// test case 1: create an invalid point object with multipoint data format
String invalidPoint1 = XContentFactory.jsonBuilder().startObject().field("type", "point")
.startArray("coordinates")
.startArray().value(-74.011).value(40.753).endArray()
.endArray()
.endObject().string();
XContentParser parser = JsonXContent.jsonXContent.createParser(invalidPoint1);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
// test case 2: create an invalid point object with an empty number of coordinates
String invalidPoint2 = XContentFactory.jsonBuilder().startObject().field("type", "point")
.startArray("coordinates")
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(invalidPoint2);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
}
public void testParse_invalidMultipoint() throws IOException {
// test case 1: create an invalid multipoint object with single coordinate
String invalidMultipoint1 = XContentFactory.jsonBuilder().startObject().field("type", "multipoint")
.startArray("coordinates").value(-74.011).value(40.753).endArray()
.endObject().string();
XContentParser parser = JsonXContent.jsonXContent.createParser(invalidMultipoint1);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
// test case 2: create an invalid multipoint object with null coordinate
String invalidMultipoint2 = XContentFactory.jsonBuilder().startObject().field("type", "multipoint")
.startArray("coordinates")
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(invalidMultipoint2);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
// test case 3: create a valid formatted multipoint object with invalid number (0) of coordinates
String invalidMultipoint3 = XContentFactory.jsonBuilder().startObject().field("type", "multipoint")
.startArray("coordinates")
.startArray().endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(invalidMultipoint3);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
}
public void testParse_invalidMultiPolygon() throws IOException {
// test invalid multipolygon (an "accidental" polygon with inner rings outside outer ring)
String multiPolygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "MultiPolygon")
.startArray("coordinates")
.startArray()//one poly (with two holes)
.startArray()
.startArray().value(102.0).value(2.0).endArray()
.startArray().value(103.0).value(2.0).endArray()
.startArray().value(103.0).value(3.0).endArray()
.startArray().value(102.0).value(3.0).endArray()
.startArray().value(102.0).value(2.0).endArray()
.endArray()
.startArray()// first hole
.startArray().value(100.0).value(0.0).endArray()
.startArray().value(101.0).value(0.0).endArray()
.startArray().value(101.0).value(1.0).endArray()
.startArray().value(100.0).value(1.0).endArray()
.startArray().value(100.0).value(0.0).endArray()
.endArray()
.startArray()//second hole
.startArray().value(100.2).value(0.8).endArray()
.startArray().value(100.2).value(0.2).endArray()
.startArray().value(100.8).value(0.2).endArray()
.startArray().value(100.8).value(0.8).endArray()
.startArray().value(100.2).value(0.8).endArray()
.endArray()
.endArray()
.endArray()
.endObject().string();
XContentParser parser = JsonXContent.jsonXContent.createParser(multiPolygonGeoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, InvalidShapeException.class);
}
public void testParse_OGCPolygonWithoutHoles() throws IOException {
// test 1: ccw poly not crossing dateline
String polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(-177.0).value(10.0).endArray()
.startArray().value(-177.0).value(-10.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.endArray()
.endArray()
.endObject().string();
XContentParser parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
Shape shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
// test 2: ccw poly crossing dateline
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(-177.0).value(10.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(-177.0).value(-10.0).endArray()
.startArray().value(-177.0).value(10.0).endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
// test 3: cw poly not crossing dateline
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(180.0).value(10.0).endArray()
.startArray().value(180.0).value(-10.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
// test 4: cw poly crossing dateline
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(184.0).value(15.0).endArray()
.startArray().value(184.0).value(0.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(174.0).value(-10.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
}
public void testParse_OGCPolygonWithHoles() throws IOException {
// test 1: ccw poly not crossing dateline
String polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(-177.0).value(10.0).endArray()
.startArray().value(-177.0).value(-10.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.endArray()
.startArray()
.startArray().value(-172.0).value(8.0).endArray()
.startArray().value(174.0).value(10.0).endArray()
.startArray().value(-172.0).value(-8.0).endArray()
.startArray().value(-172.0).value(8.0).endArray()
.endArray()
.endArray()
.endObject().string();
XContentParser parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
Shape shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
// test 2: ccw poly crossing dateline
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(-177.0).value(10.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(-177.0).value(-10.0).endArray()
.startArray().value(-177.0).value(10.0).endArray()
.endArray()
.startArray()
.startArray().value(178.0).value(8.0).endArray()
.startArray().value(-178.0).value(8.0).endArray()
.startArray().value(-180.0).value(-8.0).endArray()
.startArray().value(178.0).value(8.0).endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
// test 3: cw poly not crossing dateline
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(180.0).value(10.0).endArray()
.startArray().value(179.0).value(-10.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.endArray()
.startArray()
.startArray().value(177.0).value(8.0).endArray()
.startArray().value(179.0).value(10.0).endArray()
.startArray().value(179.0).value(-8.0).endArray()
.startArray().value(177.0).value(8.0).endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
// test 4: cw poly crossing dateline
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(183.0).value(10.0).endArray()
.startArray().value(183.0).value(-10.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(183.0).value(10.0).endArray()
.endArray()
.startArray()
.startArray().value(178.0).value(8.0).endArray()
.startArray().value(182.0).value(8.0).endArray()
.startArray().value(180.0).value(-8.0).endArray()
.startArray().value(178.0).value(8.0).endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
}
public void testParse_invalidPolygon() throws IOException {
/**
* The following 3 test cases ensure proper error handling of invalid polygons
* per the GeoJSON specification
*/
// test case 1: create an invalid polygon with only 2 points
String invalidPoly = XContentFactory.jsonBuilder().startObject().field("type", "polygon")
.startArray("coordinates")
.startArray()
.startArray().value(-74.011).value(40.753).endArray()
.startArray().value(-75.022).value(41.783).endArray()
.endArray()
.endArray()
.endObject().string();
XContentParser parser = JsonXContent.jsonXContent.createParser(invalidPoly);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
// test case 2: create an invalid polygon with only 1 point
invalidPoly = XContentFactory.jsonBuilder().startObject().field("type", "polygon")
.startArray("coordinates")
.startArray()
.startArray().value(-74.011).value(40.753).endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(invalidPoly);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
// test case 3: create an invalid polygon with 0 points
invalidPoly = XContentFactory.jsonBuilder().startObject().field("type", "polygon")
.startArray("coordinates")
.startArray()
.startArray().endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(invalidPoly);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
// test case 4: create an invalid polygon with null value points
invalidPoly = XContentFactory.jsonBuilder().startObject().field("type", "polygon")
.startArray("coordinates")
.startArray()
.startArray().nullValue().nullValue().endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(invalidPoly);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, IllegalArgumentException.class);
// test case 5: create an invalid polygon with 1 invalid LinearRing
invalidPoly = XContentFactory.jsonBuilder().startObject().field("type", "polygon")
.startArray("coordinates")
.nullValue().nullValue()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(invalidPoly);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, IllegalArgumentException.class);
// test case 6: create an invalid polygon with 0 LinearRings
invalidPoly = XContentFactory.jsonBuilder().startObject().field("type", "polygon")
.startArray("coordinates").endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(invalidPoly);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
// test case 7: create an invalid polygon with 0 LinearRings
invalidPoly = XContentFactory.jsonBuilder().startObject().field("type", "polygon")
.startArray("coordinates")
.startArray().value(-74.011).value(40.753).endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(invalidPoly);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, ElasticsearchParseException.class);
}
public void testParse_polygonWithHole() throws IOException {
String polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(100.0).value(1.0).endArray()
.startArray().value(101.0).value(1.0).endArray()
.startArray().value(101.0).value(0.0).endArray()
.startArray().value(100.0).value(0.0).endArray()
.startArray().value(100.0).value(1.0).endArray()
.endArray()
.startArray()
.startArray().value(100.2).value(0.8).endArray()
.startArray().value(100.2).value(0.2).endArray()
.startArray().value(100.8).value(0.2).endArray()
.startArray().value(100.8).value(0.8).endArray()
.startArray().value(100.2).value(0.8).endArray()
.endArray()
.endArray()
.endObject().string();
// add 3d point to test ISSUE #10501
List<Coordinate> shellCoordinates = new ArrayList<>();
shellCoordinates.add(new Coordinate(100, 0, 15.0));
shellCoordinates.add(new Coordinate(101, 0));
shellCoordinates.add(new Coordinate(101, 1));
shellCoordinates.add(new Coordinate(100, 1, 10.0));
shellCoordinates.add(new Coordinate(100, 0));
List<Coordinate> holeCoordinates = new ArrayList<>();
holeCoordinates.add(new Coordinate(100.2, 0.2));
holeCoordinates.add(new Coordinate(100.8, 0.2));
holeCoordinates.add(new Coordinate(100.8, 0.8));
holeCoordinates.add(new Coordinate(100.2, 0.8));
holeCoordinates.add(new Coordinate(100.2, 0.2));
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(
shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
LinearRing[] holes = new LinearRing[1];
holes[0] = GEOMETRY_FACTORY.createLinearRing(
holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, holes);
assertGeometryEquals(jtsGeom(expected), polygonGeoJson);
}
public void testParse_selfCrossingPolygon() throws IOException {
// test self crossing ccw poly not crossing dateline
String polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.startArray("coordinates")
.startArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(-177.0).value(10.0).endArray()
.startArray().value(-177.0).value(-10.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(-177.0).value(15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.endArray()
.endArray()
.endObject().string();
XContentParser parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertValidException(parser, InvalidShapeException.class);
}
public void testParse_multiPoint() throws IOException {
String multiPointGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "MultiPoint")
.startArray("coordinates")
.startArray().value(100.0).value(0.0).endArray()
.startArray().value(101.0).value(1.0).endArray()
.endArray()
.endObject().string();
ShapeCollection expected = shapeCollection(
SPATIAL_CONTEXT.makePoint(100, 0),
SPATIAL_CONTEXT.makePoint(101, 1.0));
assertGeometryEquals(expected, multiPointGeoJson);
}
public void testParse_multiPolygon() throws IOException {
// test #1: two polygons; one without hole, one with hole
String multiPolygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "MultiPolygon")
.startArray("coordinates")
.startArray()//first poly (without holes)
.startArray()
.startArray().value(102.0).value(2.0).endArray()
.startArray().value(103.0).value(2.0).endArray()
.startArray().value(103.0).value(3.0).endArray()
.startArray().value(102.0).value(3.0).endArray()
.startArray().value(102.0).value(2.0).endArray()
.endArray()
.endArray()
.startArray()//second poly (with hole)
.startArray()
.startArray().value(100.0).value(0.0).endArray()
.startArray().value(101.0).value(0.0).endArray()
.startArray().value(101.0).value(1.0).endArray()
.startArray().value(100.0).value(1.0).endArray()
.startArray().value(100.0).value(0.0).endArray()
.endArray()
.startArray()//hole
.startArray().value(100.2).value(0.8).endArray()
.startArray().value(100.2).value(0.2).endArray()
.startArray().value(100.8).value(0.2).endArray()
.startArray().value(100.8).value(0.8).endArray()
.startArray().value(100.2).value(0.8).endArray()
.endArray()
.endArray()
.endArray()
.endObject().string();
List<Coordinate> shellCoordinates = new ArrayList<>();
shellCoordinates.add(new Coordinate(100, 0));
shellCoordinates.add(new Coordinate(101, 0));
shellCoordinates.add(new Coordinate(101, 1));
shellCoordinates.add(new Coordinate(100, 1));
shellCoordinates.add(new Coordinate(100, 0));
List<Coordinate> holeCoordinates = new ArrayList<>();
holeCoordinates.add(new Coordinate(100.2, 0.2));
holeCoordinates.add(new Coordinate(100.8, 0.2));
holeCoordinates.add(new Coordinate(100.8, 0.8));
holeCoordinates.add(new Coordinate(100.2, 0.8));
holeCoordinates.add(new Coordinate(100.2, 0.2));
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
LinearRing[] holes = new LinearRing[1];
holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
Polygon withHoles = GEOMETRY_FACTORY.createPolygon(shell, holes);
shellCoordinates = new ArrayList<>();
shellCoordinates.add(new Coordinate(102, 3));
shellCoordinates.add(new Coordinate(103, 3));
shellCoordinates.add(new Coordinate(103, 2));
shellCoordinates.add(new Coordinate(102, 2));
shellCoordinates.add(new Coordinate(102, 3));
shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
Polygon withoutHoles = GEOMETRY_FACTORY.createPolygon(shell, null);
Shape expected = shapeCollection(withoutHoles, withHoles);
assertGeometryEquals(expected, multiPolygonGeoJson);
// test #2: multipolygon; one polygon with one hole
// this test converting the multipolygon from a ShapeCollection type
// to a simple polygon (jtsGeom)
multiPolygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "MultiPolygon")
.startArray("coordinates")
.startArray()
.startArray()
.startArray().value(100.0).value(1.0).endArray()
.startArray().value(101.0).value(1.0).endArray()
.startArray().value(101.0).value(0.0).endArray()
.startArray().value(100.0).value(0.0).endArray()
.startArray().value(100.0).value(1.0).endArray()
.endArray()
.startArray()// hole
.startArray().value(100.2).value(0.8).endArray()
.startArray().value(100.2).value(0.2).endArray()
.startArray().value(100.8).value(0.2).endArray()
.startArray().value(100.8).value(0.8).endArray()
.startArray().value(100.2).value(0.8).endArray()
.endArray()
.endArray()
.endArray()
.endObject().string();
shellCoordinates = new ArrayList<>();
shellCoordinates.add(new Coordinate(100, 1));
shellCoordinates.add(new Coordinate(101, 1));
shellCoordinates.add(new Coordinate(101, 0));
shellCoordinates.add(new Coordinate(100, 0));
shellCoordinates.add(new Coordinate(100, 1));
holeCoordinates = new ArrayList<>();
holeCoordinates.add(new Coordinate(100.2, 0.8));
holeCoordinates.add(new Coordinate(100.2, 0.2));
holeCoordinates.add(new Coordinate(100.8, 0.2));
holeCoordinates.add(new Coordinate(100.8, 0.8));
holeCoordinates.add(new Coordinate(100.2, 0.8));
shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
holes = new LinearRing[1];
holes[0] = GEOMETRY_FACTORY.createLinearRing(holeCoordinates.toArray(new Coordinate[holeCoordinates.size()]));
withHoles = GEOMETRY_FACTORY.createPolygon(shell, holes);
assertGeometryEquals(jtsGeom(withHoles), multiPolygonGeoJson);
}
public void testParse_geometryCollection() throws IOException {
String geometryCollectionGeoJson = XContentFactory.jsonBuilder().startObject()
.field("type", "GeometryCollection")
.startArray("geometries")
.startObject()
.field("type", "LineString")
.startArray("coordinates")
.startArray().value(100.0).value(0.0).endArray()
.startArray().value(101.0).value(1.0).endArray()
.endArray()
.endObject()
.startObject()
.field("type", "Point")
.startArray("coordinates").value(102.0).value(2.0).endArray()
.endObject()
.endArray()
.endObject()
.string();
Shape[] expected = new Shape[2];
LineString expectedLineString = GEOMETRY_FACTORY.createLineString(new Coordinate[]{
new Coordinate(100, 0),
new Coordinate(101, 1),
});
expected[0] = jtsGeom(expectedLineString);
Point expectedPoint = GEOMETRY_FACTORY.createPoint(new Coordinate(102.0, 2.0));
expected[1] = new JtsPoint(expectedPoint, SPATIAL_CONTEXT);
//equals returns true only if geometries are in the same order
assertGeometryEquals(shapeCollection(expected), geometryCollectionGeoJson);
}
public void testThatParserExtractsCorrectTypeAndCoordinatesFromArbitraryJson() throws IOException {
String pointGeoJson = XContentFactory.jsonBuilder().startObject()
.startObject("crs")
.field("type", "name")
.startObject("properties")
.field("name", "urn:ogc:def:crs:OGC:1.3:CRS84")
.endObject()
.endObject()
.field("bbox", "foobar")
.field("type", "point")
.field("bubu", "foobar")
.startArray("coordinates").value(100.0).value(0.0).endArray()
.startObject("nested").startArray("coordinates").value(200.0).value(0.0).endArray().endObject()
.startObject("lala").field("type", "NotAPoint").endObject()
.endObject().string();
Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson);
}
public void testParse_orientationOption() throws IOException {
// test 1: valid ccw (right handed system) poly not crossing dateline (with 'right' field)
String polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.field("orientation", "right")
.startArray("coordinates")
.startArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(-177.0).value(10.0).endArray()
.startArray().value(-177.0).value(-10.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.endArray()
.startArray()
.startArray().value(-172.0).value(8.0).endArray()
.startArray().value(174.0).value(10.0).endArray()
.startArray().value(-172.0).value(-8.0).endArray()
.startArray().value(-172.0).value(8.0).endArray()
.endArray()
.endArray()
.endObject().string();
XContentParser parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
Shape shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
// test 2: valid ccw (right handed system) poly not crossing dateline (with 'ccw' field)
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.field("orientation", "ccw")
.startArray("coordinates")
.startArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(-177.0).value(10.0).endArray()
.startArray().value(-177.0).value(-10.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.endArray()
.startArray()
.startArray().value(-172.0).value(8.0).endArray()
.startArray().value(174.0).value(10.0).endArray()
.startArray().value(-172.0).value(-8.0).endArray()
.startArray().value(-172.0).value(8.0).endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
// test 3: valid ccw (right handed system) poly not crossing dateline (with 'counterclockwise' field)
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.field("orientation", "counterclockwise")
.startArray("coordinates")
.startArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(-177.0).value(10.0).endArray()
.startArray().value(-177.0).value(-10.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.endArray()
.startArray()
.startArray().value(-172.0).value(8.0).endArray()
.startArray().value(174.0).value(10.0).endArray()
.startArray().value(-172.0).value(-8.0).endArray()
.startArray().value(-172.0).value(8.0).endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertPolygon(shape);
// test 4: valid cw (left handed system) poly crossing dateline (with 'left' field)
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.field("orientation", "left")
.startArray("coordinates")
.startArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(-177.0).value(10.0).endArray()
.startArray().value(-177.0).value(-10.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.endArray()
.startArray()
.startArray().value(-178.0).value(8.0).endArray()
.startArray().value(178.0).value(8.0).endArray()
.startArray().value(180.0).value(-8.0).endArray()
.startArray().value(-178.0).value(8.0).endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
// test 5: valid cw multipoly (left handed system) poly crossing dateline (with 'cw' field)
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.field("orientation", "cw")
.startArray("coordinates")
.startArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(-177.0).value(10.0).endArray()
.startArray().value(-177.0).value(-10.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.endArray()
.startArray()
.startArray().value(-178.0).value(8.0).endArray()
.startArray().value(178.0).value(8.0).endArray()
.startArray().value(180.0).value(-8.0).endArray()
.startArray().value(-178.0).value(8.0).endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
// test 6: valid cw multipoly (left handed system) poly crossing dateline (with 'clockwise' field)
polygonGeoJson = XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
.field("orientation", "clockwise")
.startArray("coordinates")
.startArray()
.startArray().value(176.0).value(15.0).endArray()
.startArray().value(-177.0).value(10.0).endArray()
.startArray().value(-177.0).value(-10.0).endArray()
.startArray().value(176.0).value(-15.0).endArray()
.startArray().value(172.0).value(0.0).endArray()
.startArray().value(176.0).value(15.0).endArray()
.endArray()
.startArray()
.startArray().value(-178.0).value(8.0).endArray()
.startArray().value(178.0).value(8.0).endArray()
.startArray().value(180.0).value(-8.0).endArray()
.startArray().value(-178.0).value(8.0).endArray()
.endArray()
.endArray()
.endObject().string();
parser = JsonXContent.jsonXContent.createParser(polygonGeoJson);
parser.nextToken();
shape = ShapeBuilder.parse(parser).build();
ElasticsearchGeoAssertions.assertMultiPolygon(shape);
}
private void assertGeometryEquals(Shape expected, String geoJson) throws IOException {
XContentParser parser = JsonXContent.jsonXContent.createParser(geoJson);
parser.nextToken();
ElasticsearchGeoAssertions.assertEquals(expected, ShapeBuilder.parse(parser).build());
}
private ShapeCollection<Shape> shapeCollection(Shape... shapes) {
return new ShapeCollection<>(Arrays.asList(shapes), SPATIAL_CONTEXT);
}
private ShapeCollection<Shape> shapeCollection(Geometry... geoms) {
List<Shape> shapes = new ArrayList<>(geoms.length);
for (Geometry geom : geoms) {
shapes.add(jtsGeom(geom));
}
return new ShapeCollection<>(shapes, SPATIAL_CONTEXT);
}
private JtsGeometry jtsGeom(Geometry geom) {
return new JtsGeometry(geom, SPATIAL_CONTEXT, false, false);
}
}