mirror of https://github.com/apache/lucene.git
LUCENE-7380: add Polygon.fromGeoJSON
This commit is contained in:
parent
2e0b2f5e37
commit
343f374b53
|
@ -54,6 +54,10 @@ New Features
|
|||
* LUCENE-7355: Added Analyzer#normalize(), which only applies normalization to
|
||||
an input string. (Adrien Grand)
|
||||
|
||||
* LUCENE-7380: Add Polygon.fromGeoJSON for more easily creating
|
||||
Polygon instances from a standard GeoJSON string (Robert Muir, Mike
|
||||
McCandless)
|
||||
|
||||
Bug Fixes
|
||||
|
||||
* LUCENE-6662: Fixed potential resource leaks. (Rishabh Patel via Adrien Grand)
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
*/
|
||||
package org.apache.lucene.geo;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Represents a closed polygon on the earth's surface.
|
||||
* Represents a closed polygon on the earth's surface. You can either construct the Polygon directly yourself with {@code double[]}
|
||||
* coordinates, or use {@link Polygon#fromGeoJSON} if you have a polygon already encoded as a
|
||||
* <a href="http://geojson.org/geojson-spec.html">GeoJSON</a> string.
|
||||
* <p>
|
||||
* NOTES:
|
||||
* <ol>
|
||||
|
@ -159,4 +162,12 @@ public final class Polygon {
|
|||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Parses a standard GeoJSON polygon string. The type of the incoming GeoJSON object must be a Polygon or MultiPolygon, optionally
|
||||
* embedded under a "type: Feature". A Polygon will return as a length 1 array, while a MultiPolygon will be 1 or more in length.
|
||||
*
|
||||
* <p>See <a href="http://geojson.org/geojson-spec.html">the GeoJSON specification</a>. */
|
||||
public static Polygon[] fromGeoJSON(String geojson) throws ParseException {
|
||||
return new SimpleGeoJSONPolygonParser(geojson).parse();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,440 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF 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.apache.lucene.geo;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
We accept either a whole type: Feature, like this:
|
||||
|
||||
{ "type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
|
||||
[100.0, 1.0], [100.0, 0.0] ]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"prop0": "value0",
|
||||
"prop1": {"this": "that"}
|
||||
}
|
||||
}
|
||||
|
||||
Or the inner object with type: Multi/Polygon.
|
||||
|
||||
Or a type: FeatureCollection, if it has only one Feature which is a Polygon or MultiPolyon.
|
||||
|
||||
type: MultiPolygon (union of polygons) is also accepted.
|
||||
*/
|
||||
|
||||
/** Does minimal parsing of a GeoJSON object, to extract either Polygon or MultiPolygon, either directly as a the top-level type, or if
|
||||
* the top-level type is Feature, as the geometry of that feature. */
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
class SimpleGeoJSONPolygonParser {
|
||||
final String input;
|
||||
private int upto;
|
||||
private String polyType;
|
||||
private List<Object> coordinates;
|
||||
|
||||
public SimpleGeoJSONPolygonParser(String input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
public Polygon[] parse() throws ParseException {
|
||||
// parse entire object
|
||||
parseObject("");
|
||||
|
||||
// make sure there's nothing left:
|
||||
readEnd();
|
||||
|
||||
// The order of JSON object keys (type, geometry, coordinates in our case) can be arbitrary, so we wait until we are done parsing to
|
||||
// put the pieces together here:
|
||||
|
||||
if (coordinates == null) {
|
||||
throw newParseException("did not see any polygon coordinates");
|
||||
}
|
||||
|
||||
if (polyType == null) {
|
||||
throw newParseException("did not see type: Polygon or MultiPolygon");
|
||||
}
|
||||
|
||||
if (polyType.equals("Polygon")) {
|
||||
return new Polygon[] {parsePolygon(coordinates)};
|
||||
} else {
|
||||
List<Polygon> polygons = new ArrayList<>();
|
||||
for(int i=0;i<coordinates.size();i++) {
|
||||
Object o = coordinates.get(i);
|
||||
if (o instanceof List == false) {
|
||||
throw newParseException("elements of coordinates array should be an array, but got: " + o.getClass());
|
||||
}
|
||||
polygons.add(parsePolygon((List<Object>) o));
|
||||
}
|
||||
|
||||
return polygons.toArray(new Polygon[polygons.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
/** path is the "address" by keys of where we are, e.g. geometry.coordinates */
|
||||
private void parseObject(String path) throws ParseException {
|
||||
scan('{');
|
||||
boolean first = true;
|
||||
while (true) {
|
||||
char ch = peek();
|
||||
if (ch == '}') {
|
||||
break;
|
||||
} else if (first == false) {
|
||||
if (ch == ',') {
|
||||
// ok
|
||||
upto++;
|
||||
ch = peek();
|
||||
if (ch == '}') {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
throw newParseException("expected , but got " + ch);
|
||||
}
|
||||
}
|
||||
|
||||
first = false;
|
||||
|
||||
int uptoStart = upto;
|
||||
String key = parseString();
|
||||
|
||||
if (path.equals("crs.properties") && key.equals("href")) {
|
||||
upto = uptoStart;
|
||||
throw newParseException("cannot handle linked crs");
|
||||
}
|
||||
|
||||
scan(':');
|
||||
|
||||
Object o;
|
||||
|
||||
ch = peek();
|
||||
|
||||
uptoStart = upto;
|
||||
|
||||
if (ch == '[') {
|
||||
String newPath;
|
||||
if (path.length() == 0) {
|
||||
newPath = key;
|
||||
} else {
|
||||
newPath = path + "." + key;
|
||||
}
|
||||
o = parseArray(newPath);
|
||||
} else if (ch == '{') {
|
||||
String newPath;
|
||||
if (path.length() == 0) {
|
||||
newPath = key;
|
||||
} else {
|
||||
newPath = path + "." + key;
|
||||
}
|
||||
parseObject(newPath);
|
||||
o = null;
|
||||
} else if (ch == '"') {
|
||||
o = parseString();
|
||||
} else if (ch == 't') {
|
||||
scan("true");
|
||||
o = Boolean.TRUE;
|
||||
} else if (ch == 'f') {
|
||||
scan("false");
|
||||
o = Boolean.FALSE;
|
||||
} else if (ch == 'n') {
|
||||
scan("null");
|
||||
o = null;
|
||||
} else if (ch == '-' || ch == '.' || (ch >= '0' && ch <= '9')) {
|
||||
o = parseNumber();
|
||||
} else if (ch == '}') {
|
||||
break;
|
||||
} else {
|
||||
throw newParseException("expected array, object, string or literal value, but got: " + ch);
|
||||
}
|
||||
|
||||
if (path.equals("crs.properties") && key.equals("name")) {
|
||||
if (o instanceof String == false) {
|
||||
upto = uptoStart;
|
||||
throw newParseException("crs.properties.name should be a string, but saw: " + o);
|
||||
}
|
||||
String crs = (String) o;
|
||||
if (crs.startsWith("urn:ogc:def:crs:OGC") == false || crs.endsWith(":CRS84") == false) {
|
||||
upto = uptoStart;
|
||||
throw newParseException("crs must be CRS84 from OGC, but saw: " + o);
|
||||
}
|
||||
}
|
||||
|
||||
if (key.equals("type") && path.startsWith("crs") == false) {
|
||||
if (o instanceof String == false) {
|
||||
upto = uptoStart;
|
||||
throw newParseException("type should be a string, but got: " + o);
|
||||
}
|
||||
String type = (String) o;
|
||||
if (type.equals("Polygon") && isValidGeometryPath(path)) {
|
||||
polyType = "Polygon";
|
||||
} else if (type.equals("MultiPolygon") && isValidGeometryPath(path)) {
|
||||
polyType = "MultiPolygon";
|
||||
} else if ((type.equals("FeatureCollection") || type.equals("Feature")) && (path.equals("features.[]") || path.equals(""))) {
|
||||
// OK, we recurse
|
||||
} else {
|
||||
upto = uptoStart;
|
||||
throw newParseException("can only handle type FeatureCollection (if it has a single polygon geometry), Feature, Polygon or MutiPolygon, but got " + type);
|
||||
}
|
||||
} else if (key.equals("coordinates") && isValidGeometryPath(path)) {
|
||||
if (o instanceof List == false) {
|
||||
upto = uptoStart;
|
||||
throw newParseException("coordinates should be an array, but got: " + o.getClass());
|
||||
}
|
||||
if (coordinates != null) {
|
||||
upto = uptoStart;
|
||||
throw newParseException("only one Polygon or MultiPolygon is supported");
|
||||
}
|
||||
coordinates = (List<Object>) o;
|
||||
}
|
||||
}
|
||||
|
||||
scan('}');
|
||||
}
|
||||
|
||||
/** Returns true if the object path is a valid location to see a Multi/Polygon geometry */
|
||||
private boolean isValidGeometryPath(String path) {
|
||||
return path.equals("") || path.equals("geometry") || path.equals("features.[].geometry");
|
||||
}
|
||||
|
||||
private Polygon parsePolygon(List<Object> coordinates) throws ParseException {
|
||||
List<Polygon> holes = new ArrayList<>();
|
||||
Object o = coordinates.get(0);
|
||||
if (o instanceof List == false) {
|
||||
throw newParseException("first element of polygon array must be an array [[lat, lon], [lat, lon] ...] but got: " + o);
|
||||
}
|
||||
double[][] polyPoints = parsePoints((List<Object>) o);
|
||||
for(int i=1;i<coordinates.size();i++) {
|
||||
o = coordinates.get(i);
|
||||
if (o instanceof List == false) {
|
||||
throw newParseException("elements of coordinates array must be an array [[lat, lon], [lat, lon] ...] but got: " + o);
|
||||
}
|
||||
double[][] holePoints = parsePoints((List<Object>) o);
|
||||
holes.add(new Polygon(holePoints[0], holePoints[1]));
|
||||
}
|
||||
return new Polygon(polyPoints[0], polyPoints[1], holes.toArray(new Polygon[holes.size()]));
|
||||
}
|
||||
|
||||
/** Parses [[lat, lon], [lat, lon] ...] into 2d double array */
|
||||
private double[][] parsePoints(List<Object> o) throws ParseException {
|
||||
double[] lats = new double[o.size()];
|
||||
double[] lons = new double[o.size()];
|
||||
for(int i=0;i<o.size();i++) {
|
||||
Object point = o.get(i);
|
||||
if (point instanceof List == false) {
|
||||
throw newParseException("elements of coordinates array must [lat, lon] array, but got: " + point);
|
||||
}
|
||||
List<Object> pointList = (List<Object>) point;
|
||||
if (pointList.size() != 2) {
|
||||
throw newParseException("elements of coordinates array must [lat, lon] array, but got wrong element count: " + pointList);
|
||||
}
|
||||
if (pointList.get(0) instanceof Double == false) {
|
||||
throw newParseException("elements of coordinates array must [lat, lon] array, but first element is not a Double: " + pointList.get(0));
|
||||
}
|
||||
if (pointList.get(1) instanceof Double == false) {
|
||||
throw newParseException("elements of coordinates array must [lat, lon] array, but second element is not a Double: " + pointList.get(1));
|
||||
}
|
||||
|
||||
// lon, lat ordering in GeoJSON!
|
||||
lons[i] = ((Double) pointList.get(0)).doubleValue();
|
||||
lats[i] = ((Double) pointList.get(1)).doubleValue();
|
||||
}
|
||||
|
||||
return new double[][] {lats, lons};
|
||||
}
|
||||
|
||||
private List<Object> parseArray(String path) throws ParseException {
|
||||
List<Object> result = new ArrayList<>();
|
||||
scan('[');
|
||||
while (upto < input.length()) {
|
||||
char ch = peek();
|
||||
if (ch == ']') {
|
||||
scan(']');
|
||||
return result;
|
||||
}
|
||||
|
||||
if (result.size() > 0) {
|
||||
if (ch != ',') {
|
||||
throw newParseException("expected ',' separating list items, but got '" + ch + "'");
|
||||
}
|
||||
|
||||
// skip the ,
|
||||
upto++;
|
||||
|
||||
if (upto == input.length()) {
|
||||
throw newParseException("hit EOF while parsing array");
|
||||
}
|
||||
ch = peek();
|
||||
}
|
||||
|
||||
Object o;
|
||||
if (ch == '[') {
|
||||
o = parseArray(path + ".[]");
|
||||
} else if (ch == '{') {
|
||||
// This is only used when parsing the "features" in type: FeatureCollection
|
||||
parseObject(path + ".[]");
|
||||
o = null;
|
||||
} else if (ch == '-' || ch == '.' || (ch >= '0' && ch <= '9')) {
|
||||
o = parseNumber();
|
||||
} else {
|
||||
throw newParseException("expected another array or number while parsing array, not '" + ch + "'");
|
||||
}
|
||||
|
||||
result.add(o);
|
||||
}
|
||||
|
||||
throw newParseException("hit EOF while reading array");
|
||||
}
|
||||
|
||||
private Number parseNumber() throws ParseException {
|
||||
StringBuilder b = new StringBuilder();
|
||||
int uptoStart = upto;
|
||||
while (upto < input.length()) {
|
||||
char ch = input.charAt(upto);
|
||||
if (ch == '-' || ch == '.' || (ch >= '0' && ch <= '9') || ch == 'e' || ch == 'E') {
|
||||
upto++;
|
||||
b.append(ch);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// we only handle doubles
|
||||
try {
|
||||
return Double.parseDouble(b.toString());
|
||||
} catch (NumberFormatException nfe) {
|
||||
upto = uptoStart;
|
||||
throw newParseException("could not parse number as double");
|
||||
}
|
||||
}
|
||||
|
||||
private String parseString() throws ParseException {
|
||||
scan('"');
|
||||
StringBuilder b = new StringBuilder();
|
||||
while (upto < input.length()) {
|
||||
char ch = input.charAt(upto);
|
||||
if (ch == '"') {
|
||||
upto++;
|
||||
return b.toString();
|
||||
}
|
||||
if (ch == '\\') {
|
||||
// an escaped character
|
||||
upto++;
|
||||
if (upto == input.length()) {
|
||||
throw newParseException("hit EOF inside string literal");
|
||||
}
|
||||
ch = input.charAt(upto);
|
||||
if (ch == 'u') {
|
||||
// 4 hex digit unicode BMP escape
|
||||
upto++;
|
||||
if (upto + 4 > input.length()) {
|
||||
throw newParseException("hit EOF inside string literal");
|
||||
}
|
||||
b.append(Integer.parseInt(input.substring(upto, upto+4), 16));
|
||||
} else if (ch == '\\') {
|
||||
b.append('\\');
|
||||
upto++;
|
||||
} else {
|
||||
// TODO: allow \n, \t, etc.???
|
||||
throw newParseException("unsupported string escape character \\" + ch);
|
||||
}
|
||||
} else {
|
||||
b.append(ch);
|
||||
upto++;
|
||||
}
|
||||
}
|
||||
|
||||
throw newParseException("hit EOF inside string literal");
|
||||
}
|
||||
|
||||
private char peek() throws ParseException {
|
||||
while (upto < input.length()) {
|
||||
char ch = input.charAt(upto);
|
||||
if (isJSONWhitespace(ch)) {
|
||||
upto++;
|
||||
continue;
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
throw newParseException("unexpected EOF");
|
||||
}
|
||||
|
||||
/** Scans across whitespace and consumes the expected character, or throws {@code ParseException} if the character is wrong */
|
||||
private void scan(char expected) throws ParseException {
|
||||
while (upto < input.length()) {
|
||||
char ch = input.charAt(upto);
|
||||
if (isJSONWhitespace(ch)) {
|
||||
upto++;
|
||||
continue;
|
||||
}
|
||||
if (ch != expected) {
|
||||
throw newParseException("expected '" + expected + "' but got '" + ch + "'");
|
||||
}
|
||||
upto++;
|
||||
return;
|
||||
}
|
||||
throw newParseException("expected '" + expected + "' but got EOF");
|
||||
}
|
||||
|
||||
private void readEnd() throws ParseException {
|
||||
while (upto < input.length()) {
|
||||
char ch = input.charAt(upto);
|
||||
if (isJSONWhitespace(ch) == false) {
|
||||
throw newParseException("unexpected character '" + ch + "' after end of GeoJSON object");
|
||||
}
|
||||
upto++;
|
||||
}
|
||||
}
|
||||
|
||||
/** Scans the expected string, or throws {@code ParseException} */
|
||||
private void scan(String expected) throws ParseException {
|
||||
if (upto + expected.length() > input.length()) {
|
||||
throw newParseException("expected \"" + expected + "\" but hit EOF");
|
||||
}
|
||||
String subString = input.substring(upto, upto+expected.length());
|
||||
if (subString.equals(expected) == false) {
|
||||
throw newParseException("expected \"" + expected + "\" but got \"" + subString + "\"");
|
||||
}
|
||||
upto += expected.length();
|
||||
}
|
||||
|
||||
private static boolean isJSONWhitespace(char ch) {
|
||||
// JSON doesn't accept allow unicode whitespace?
|
||||
return ch == 0x20 || // space
|
||||
ch == 0x09 || // tab
|
||||
ch == 0x0a || // line feed
|
||||
ch == 0x0d; // newline
|
||||
}
|
||||
|
||||
/** When calling this, upto should be at the position of the incorrect character! */
|
||||
private ParseException newParseException(String details) throws ParseException {
|
||||
String fragment;
|
||||
int end = Math.min(input.length(), upto+1);
|
||||
if (upto < 50) {
|
||||
fragment = input.substring(0, end);
|
||||
} else {
|
||||
fragment = "..." + input.substring(upto-50, end);
|
||||
}
|
||||
return new ParseException(details + " at character offset " + upto + "; fragment leading to this:\n" + fragment, upto);
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.apache.lucene.geo;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
public class TestPolygon extends LuceneTestCase {
|
||||
|
@ -59,4 +61,243 @@ public class TestPolygon extends LuceneTestCase {
|
|||
});
|
||||
assertTrue(expected.getMessage(), expected.getMessage().contains("it must close itself"));
|
||||
}
|
||||
|
||||
public void testGeoJSONPolygon() throws Exception {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("{\n");
|
||||
b.append(" \"type\": \"Polygon\",\n");
|
||||
b.append(" \"coordinates\": [\n");
|
||||
b.append(" [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],\n");
|
||||
b.append(" [100.0, 1.0], [100.0, 0.0] ]\n");
|
||||
b.append(" ]\n");
|
||||
b.append("}\n");
|
||||
|
||||
Polygon[] polygons = Polygon.fromGeoJSON(b.toString());
|
||||
assertEquals(1, polygons.length);
|
||||
assertEquals(new Polygon(new double[] {0.0, 0.0, 1.0, 1.0, 0.0},
|
||||
new double[] {100.0, 101.0, 101.0, 100.0, 100.0}), polygons[0]);
|
||||
}
|
||||
|
||||
public void testGeoJSONPolygonWithHole() throws Exception {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("{\n");
|
||||
b.append(" \"type\": \"Polygon\",\n");
|
||||
b.append(" \"coordinates\": [\n");
|
||||
b.append(" [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],\n");
|
||||
b.append(" [100.0, 1.0], [100.0, 0.0] ],\n");
|
||||
b.append(" [ [100.5, 0.5], [100.5, 0.75], [100.75, 0.75], [100.75, 0.5], [100.5, 0.5]]\n");
|
||||
b.append(" ]\n");
|
||||
b.append("}\n");
|
||||
|
||||
Polygon hole = new Polygon(new double[] {0.5, 0.75, 0.75, 0.5, 0.5},
|
||||
new double[] {100.5, 100.5, 100.75, 100.75, 100.5});
|
||||
Polygon expected = new Polygon(new double[] {0.0, 0.0, 1.0, 1.0, 0.0},
|
||||
new double[] {100.0, 101.0, 101.0, 100.0, 100.0}, hole);
|
||||
Polygon[] polygons = Polygon.fromGeoJSON(b.toString());
|
||||
|
||||
assertEquals(1, polygons.length);
|
||||
assertEquals(expected, polygons[0]);
|
||||
}
|
||||
|
||||
// a MultiPolygon returns multiple Polygons
|
||||
public void testGeoJSONMultiPolygon() throws Exception {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("{\n");
|
||||
b.append(" \"type\": \"MultiPolygon\",\n");
|
||||
b.append(" \"coordinates\": [\n");
|
||||
b.append(" [\n");
|
||||
b.append(" [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],\n");
|
||||
b.append(" [100.0, 1.0], [100.0, 0.0] ]\n");
|
||||
b.append(" ],\n");
|
||||
b.append(" [\n");
|
||||
b.append(" [ [10.0, 2.0], [11.0, 2.0], [11.0, 3.0],\n");
|
||||
b.append(" [10.0, 3.0], [10.0, 2.0] ]\n");
|
||||
b.append(" ]\n");
|
||||
b.append(" ],\n");
|
||||
b.append("}\n");
|
||||
|
||||
Polygon[] polygons = Polygon.fromGeoJSON(b.toString());
|
||||
assertEquals(2, polygons.length);
|
||||
assertEquals(new Polygon(new double[] {0.0, 0.0, 1.0, 1.0, 0.0},
|
||||
new double[] {100.0, 101.0, 101.0, 100.0, 100.0}), polygons[0]);
|
||||
assertEquals(new Polygon(new double[] {2.0, 2.0, 3.0, 3.0, 2.0},
|
||||
new double[] {10.0, 11.0, 11.0, 10.0, 10.0}), polygons[1]);
|
||||
}
|
||||
|
||||
// make sure type can appear last (JSON allows arbitrary key/value order for objects)
|
||||
public void testGeoJSONTypeComesLast() throws Exception {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("{\n");
|
||||
b.append(" \"coordinates\": [\n");
|
||||
b.append(" [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],\n");
|
||||
b.append(" [100.0, 1.0], [100.0, 0.0] ]\n");
|
||||
b.append(" ],\n");
|
||||
b.append(" \"type\": \"Polygon\",\n");
|
||||
b.append("}\n");
|
||||
|
||||
Polygon[] polygons = Polygon.fromGeoJSON(b.toString());
|
||||
assertEquals(1, polygons.length);
|
||||
assertEquals(new Polygon(new double[] {0.0, 0.0, 1.0, 1.0, 0.0},
|
||||
new double[] {100.0, 101.0, 101.0, 100.0, 100.0}), polygons[0]);
|
||||
}
|
||||
|
||||
// make sure Polygon inside a type: Feature also works
|
||||
public void testGeoJSONPolygonFeature() throws Exception {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("{ \"type\": \"Feature\",\n");
|
||||
b.append(" \"geometry\": {\n");
|
||||
b.append(" \"type\": \"Polygon\",\n");
|
||||
b.append(" \"coordinates\": [\n");
|
||||
b.append(" [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],\n");
|
||||
b.append(" [100.0, 1.0], [100.0, 0.0] ]\n");
|
||||
b.append(" ]\n");
|
||||
b.append(" },\n");
|
||||
b.append(" \"properties\": {\n");
|
||||
b.append(" \"prop0\": \"value0\",\n");
|
||||
b.append(" \"prop1\": {\"this\": \"that\"}\n");
|
||||
b.append(" }\n");
|
||||
b.append("}\n");
|
||||
|
||||
Polygon[] polygons = Polygon.fromGeoJSON(b.toString());
|
||||
assertEquals(1, polygons.length);
|
||||
assertEquals(new Polygon(new double[] {0.0, 0.0, 1.0, 1.0, 0.0},
|
||||
new double[] {100.0, 101.0, 101.0, 100.0, 100.0}), polygons[0]);
|
||||
}
|
||||
|
||||
// make sure MultiPolygon inside a type: Feature also works
|
||||
public void testGeoJSONMultiPolygonFeature() throws Exception {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("{ \"type\": \"Feature\",\n");
|
||||
b.append(" \"geometry\": {\n");
|
||||
b.append(" \"type\": \"MultiPolygon\",\n");
|
||||
b.append(" \"coordinates\": [\n");
|
||||
b.append(" [\n");
|
||||
b.append(" [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],\n");
|
||||
b.append(" [100.0, 1.0], [100.0, 0.0] ]\n");
|
||||
b.append(" ],\n");
|
||||
b.append(" [\n");
|
||||
b.append(" [ [10.0, 2.0], [11.0, 2.0], [11.0, 3.0],\n");
|
||||
b.append(" [10.0, 3.0], [10.0, 2.0] ]\n");
|
||||
b.append(" ]\n");
|
||||
b.append(" ]\n");
|
||||
b.append(" },\n");
|
||||
b.append(" \"properties\": {\n");
|
||||
b.append(" \"prop0\": \"value0\",\n");
|
||||
b.append(" \"prop1\": {\"this\": \"that\"}\n");
|
||||
b.append(" }\n");
|
||||
b.append("}\n");
|
||||
|
||||
Polygon[] polygons = Polygon.fromGeoJSON(b.toString());
|
||||
assertEquals(2, polygons.length);
|
||||
assertEquals(new Polygon(new double[] {0.0, 0.0, 1.0, 1.0, 0.0},
|
||||
new double[] {100.0, 101.0, 101.0, 100.0, 100.0}), polygons[0]);
|
||||
assertEquals(new Polygon(new double[] {2.0, 2.0, 3.0, 3.0, 2.0},
|
||||
new double[] {10.0, 11.0, 11.0, 10.0, 10.0}), polygons[1]);
|
||||
}
|
||||
|
||||
// FeatureCollection with one geometry is allowed:
|
||||
public void testGeoJSONFeatureCollectionWithSinglePolygon() throws Exception {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("{ \"type\": \"FeatureCollection\",\n");
|
||||
b.append(" \"features\": [\n");
|
||||
b.append(" { \"type\": \"Feature\",\n");
|
||||
b.append(" \"geometry\": {\n");
|
||||
b.append(" \"type\": \"Polygon\",\n");
|
||||
b.append(" \"coordinates\": [\n");
|
||||
b.append(" [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],\n");
|
||||
b.append(" [100.0, 1.0], [100.0, 0.0] ]\n");
|
||||
b.append(" ]\n");
|
||||
b.append(" },\n");
|
||||
b.append(" \"properties\": {\n");
|
||||
b.append(" \"prop0\": \"value0\",\n");
|
||||
b.append(" \"prop1\": {\"this\": \"that\"}\n");
|
||||
b.append(" }\n");
|
||||
b.append(" }\n");
|
||||
b.append(" ]\n");
|
||||
b.append("} \n");
|
||||
|
||||
Polygon expected = new Polygon(new double[] {0.0, 0.0, 1.0, 1.0, 0.0},
|
||||
new double[] {100.0, 101.0, 101.0, 100.0, 100.0});
|
||||
Polygon[] actual = Polygon.fromGeoJSON(b.toString());
|
||||
assertEquals(1, actual.length);
|
||||
assertEquals(expected, actual[0]);
|
||||
}
|
||||
|
||||
// stuff after the object is not allowed
|
||||
public void testIllegalGeoJSONExtraCrapAtEnd() throws Exception {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("{\n");
|
||||
b.append(" \"type\": \"Polygon\",\n");
|
||||
b.append(" \"coordinates\": [\n");
|
||||
b.append(" [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],\n");
|
||||
b.append(" [100.0, 1.0], [100.0, 0.0] ]\n");
|
||||
b.append(" ]\n");
|
||||
b.append("}\n");
|
||||
b.append("foo\n");
|
||||
|
||||
Exception e = expectThrows(ParseException.class, () -> Polygon.fromGeoJSON(b.toString()));
|
||||
assertTrue(e.getMessage().contains("unexpected character 'f' after end of GeoJSON object"));
|
||||
}
|
||||
|
||||
public void testIllegalGeoJSONLinkedCRS() throws Exception {
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("{\n");
|
||||
b.append(" \"type\": \"Polygon\",\n");
|
||||
b.append(" \"coordinates\": [\n");
|
||||
b.append(" [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],\n");
|
||||
b.append(" [100.0, 1.0], [100.0, 0.0] ]\n");
|
||||
b.append(" ],\n");
|
||||
b.append(" \"crs\": {\n");
|
||||
b.append(" \"type\": \"link\",\n");
|
||||
b.append(" \"properties\": {\n");
|
||||
b.append(" \"href\": \"http://example.com/crs/42\",\n");
|
||||
b.append(" \"type\": \"proj4\"\n");
|
||||
b.append(" }\n");
|
||||
b.append(" } \n");
|
||||
b.append("}\n");
|
||||
Exception e = expectThrows(ParseException.class, () -> Polygon.fromGeoJSON(b.toString()));
|
||||
assertTrue(e.getMessage().contains("cannot handle linked crs"));
|
||||
}
|
||||
|
||||
// FeatureCollection with more than one geometry is not supported:
|
||||
public void testIllegalGeoJSONMultipleFeatures() throws Exception {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("{ \"type\": \"FeatureCollection\",\n");
|
||||
b.append(" \"features\": [\n");
|
||||
b.append(" { \"type\": \"Feature\",\n");
|
||||
b.append(" \"geometry\": {\"type\": \"Point\", \"coordinates\": [102.0, 0.5]},\n");
|
||||
b.append(" \"properties\": {\"prop0\": \"value0\"}\n");
|
||||
b.append(" },\n");
|
||||
b.append(" { \"type\": \"Feature\",\n");
|
||||
b.append(" \"geometry\": {\n");
|
||||
b.append(" \"type\": \"LineString\",\n");
|
||||
b.append(" \"coordinates\": [\n");
|
||||
b.append(" [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]\n");
|
||||
b.append(" ]\n");
|
||||
b.append(" },\n");
|
||||
b.append(" \"properties\": {\n");
|
||||
b.append(" \"prop0\": \"value0\",\n");
|
||||
b.append(" \"prop1\": 0.0\n");
|
||||
b.append(" }\n");
|
||||
b.append(" },\n");
|
||||
b.append(" { \"type\": \"Feature\",\n");
|
||||
b.append(" \"geometry\": {\n");
|
||||
b.append(" \"type\": \"Polygon\",\n");
|
||||
b.append(" \"coordinates\": [\n");
|
||||
b.append(" [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],\n");
|
||||
b.append(" [100.0, 1.0], [100.0, 0.0] ]\n");
|
||||
b.append(" ]\n");
|
||||
b.append(" },\n");
|
||||
b.append(" \"properties\": {\n");
|
||||
b.append(" \"prop0\": \"value0\",\n");
|
||||
b.append(" \"prop1\": {\"this\": \"that\"}\n");
|
||||
b.append(" }\n");
|
||||
b.append(" }\n");
|
||||
b.append(" ]\n");
|
||||
b.append("} \n");
|
||||
|
||||
Exception e = expectThrows(ParseException.class, () -> Polygon.fromGeoJSON(b.toString()));
|
||||
assertTrue(e.getMessage().contains("can only handle type FeatureCollection (if it has a single polygon geometry), Feature, Polygon or MutiPolygon, but got Point"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue