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