package com.revolsys.record.io.format.wkt; import java.io.IOException; import java.io.PushbackReader; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Lineal; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Polygon; import com.revolsys.geometry.model.Polygonal; import com.revolsys.geometry.model.Punctual; import com.revolsys.geometry.model.impl.LineStringDoubleBuilder; import com.revolsys.io.FileUtil; import com.revolsys.util.Exceptions; import com.revolsys.util.Property; public class WktParser { public static boolean hasChar(final PushbackReader reader, final char expected) throws IOException { skipWhitespace(reader); final int character = reader.read(); if (character < 0) { return false; } else if (character == expected) { return true; } else { reader.unread(character); return false; } } public static boolean hasText(final PushbackReader reader, final String expected) throws IOException { final int length = expected.length(); final char[] characters = new char[length]; final int characterCount = reader.read(characters); if (characterCount == length) { if (expected.equals(new String(characters))) { return true; } } else if (characterCount < 0) { return false; } reader.unread(characters, 0, characterCount); return false; } public static double parseDouble(final PushbackReader reader) throws IOException { int digitCount = 0; long number = 0; boolean negative = false; double decimalDivisor = -1; for (int character = reader.read(); character != -1; character = reader.read()) { if (character == '#') { if (number == 1 && decimalDivisor == 1) { final int character2 = reader.read(); if (character2 == 'Q') { if (hasText(reader, "NAN")) { return Double.NaN; } } else if (character2 == 'I') { if (hasText(reader, "NF")) { if (negative) { return Double.NEGATIVE_INFINITY; } else { return Double.POSITIVE_INFINITY; } } else if (hasText(reader, "ND")) { return Double.NaN; } } } reader.unread(character); throw new IllegalArgumentException( "Invalid WKT geometry. Expecting #QNAN oe #INF or #IND not " + FileUtil.getString(reader, 50)); } else if (character == 'N' || character == 'n') { if (digitCount == 0) { final int character2 = reader.read(); if (character2 == 'a' || character2 == 'A') { final int character3 = reader.read(); if (character3 == 'N' || character == 'n') { return Double.NaN; } reader.unread(character3); } reader.unread(character2); } reader.unread(character); throw new IllegalArgumentException( "Invalid WKT geometry. Expecting NaN not " + FileUtil.getString(reader, 50)); } else if (character == 'I') { if (hasText(reader, "nfinity")) { if (negative) { return Double.NEGATIVE_INFINITY; } else { return Double.POSITIVE_INFINITY; } } reader.unread(character); throw new IllegalArgumentException( "Invalid WKT geometry. Expecting Infinity not " + FileUtil.getString(reader, 50)); } else if (character == '.') { if (decimalDivisor == -1) { decimalDivisor = 1; } else { break; } } else if (character == '-') { if (digitCount == 0 && !negative) { negative = true; } else { reader.unread(character); break; } } else if (character >= '0' && character <= '9') { digitCount++; if (digitCount < 19) { number = number * 10 + character - '0'; if (decimalDivisor != -1) { decimalDivisor *= 10; } } } else { reader.unread(character); break; } } if (digitCount == 0) { throw new IllegalArgumentException("Invalid WKT geometry. No number found"); } else { double doubleNumber; if (decimalDivisor > 1) { doubleNumber = number / decimalDivisor; } else { doubleNumber = number; } if (negative) { return -doubleNumber; } else { return doubleNumber; } } } public static Integer parseInteger(final PushbackReader reader) throws IOException { int digitCount = 0; int number = 0; boolean negative = false; for (int character = reader.read(); character != -1; character = reader.read()) { if (character == '-') { if (digitCount == 0 && !negative) { negative = true; } else { reader.unread(character); break; } } else if (character >= '0' && character <= '9') { number = number * 10 + character - '0'; digitCount++; } else { reader.unread(character); break; } } if (digitCount == 0) { return null; } else if (negative) { return -number; } else { return number; } } public static void skipWhitespace(final PushbackReader reader) throws IOException { for (int character = reader.read(); character != -1; character = reader.read()) { if (!Character.isWhitespace(character)) { reader.unread(character); return; } } } private final GeometryFactory geometryFactory; public WktParser() { this(GeometryFactory.DEFAULT_3D); } public WktParser(final GeometryFactory geometryFactory) { this.geometryFactory = geometryFactory; } private int getAxisCount(final PushbackReader reader) throws IOException { skipWhitespace(reader); final int character = reader.read(); switch (character) { case '(': case 'E': reader.unread(character); return 2; case 'M': return 4; case 'X': final int yChar = reader.read(); if (yChar == 'Y') { final int zChar = reader.read(); if (zChar == 'Z') { final int zNext = reader.read(); if (zNext == 'M') { return 4; } else { reader.unread(zNext); return 3; } } else { reader.unread(zChar); } return 2; } reader.unread(yChar); return 2; case 'Z': final int zNext = reader.read(); if (zNext == 'M') { return 4; } else { reader.unread(zNext); return 3; } default: reader.unread(character); throw new IllegalArgumentException( "Invalid WKT geometry. Expecting Z, M, ZM, (, or EMPTY not: " + FileUtil.getString(reader, 50)); } } private boolean isEmpty(final PushbackReader reader) throws IOException { skipWhitespace(reader); if (hasText(reader, "EMPTY")) { skipWhitespace(reader); return true; } else { return false; } } private LineStringDoubleBuilder parseCoordinatesLineString(final GeometryFactory geometryFactory, final PushbackReader reader, final int axisCount) throws IOException { final LineStringDoubleBuilder line = geometryFactory.newLineStringBuilder(); skipWhitespace(reader); int character = reader.read(); if (character == '(') { int axisIndex = 0; int vertexIndex = 0; while (true) { final double number; try { number = parseDouble(reader); } catch (final IllegalArgumentException e) { character = reader.read(); if (character == ')') { return line; } else { reader.unread(character); throw new IllegalArgumentException( "Invalid WKT geometry. Expecting end of coordinates ')' not " + FileUtil.getString(reader, 50)); } } character = reader.read(); if (character == ',' || character == ')') { if (character == ',') { skipWhitespace(reader); } if (axisIndex < axisCount) { line.setCoordinate(vertexIndex, axisIndex, number); axisIndex = 0; vertexIndex++; } if (character == ')') { return line; } } else if (character == ' ' || Character.isWhitespace(character)) { skipWhitespace(reader); if (axisIndex < axisCount) { line.setCoordinate(vertexIndex, axisIndex, number); axisIndex++; } } else { throw new IllegalArgumentException( "Invalid WKT geometry. Expecting a space between coordinates not: " + FileUtil.getString(reader, 50)); } } } else { throw new IllegalArgumentException( "Expecting start of coordinates '(' not: " + FileUtil.getString(reader, 50)); } } @SuppressWarnings("unchecked") private <T extends Geometry> T parseGeometry(GeometryFactory geometryFactory, final boolean useAxisCountFromGeometryFactory, final PushbackReader reader) { try { final int axisCount = geometryFactory.getAxisCount(); final double scaleX = geometryFactory.getScaleX(); final double scaleY = geometryFactory.getScaleY(); final double scaleZ = geometryFactory.getScaleZ(); int character = (char)reader.read(); while (character != -1 && Character.isWhitespace(character)) { character = reader.read(); } if (character == 'S') { if (hasText(reader, "RID=")) { final Integer srid = parseInteger(reader); if (srid == null) { throw new IllegalArgumentException( "Invalid WKT geometry. Missing srid number after 'SRID=': " + FileUtil.getString(reader, 50)); } else if (srid != this.geometryFactory.getCoordinateSystemId()) { geometryFactory = GeometryFactory.floating(srid, axisCount); } if (!hasChar(reader, ';')) { throw new IllegalArgumentException("Invalid WKT geometry. Missing ; after 'SRID=" + srid + "': " + FileUtil.getString(reader, 50)); } } else { throw new IllegalArgumentException( "Invalid WKT geometry: S" + FileUtil.getString(reader, 50)); } character = reader.read(); while (character != -1 && Character.isWhitespace(character)) { character = reader.read(); } } Geometry geometry = null; switch (character) { case 'G': if (hasText(reader, "EOMETRYCOLLECTION")) { geometry = parseGeometryCollection(geometryFactory, useAxisCountFromGeometryFactory, reader); } else { throw new IllegalArgumentException( "Invalid WKT geometry type: G" + FileUtil.getString(reader, 50)); } break; case 'L': if (hasText(reader, "INESTRING")) { geometry = parseLineString(geometryFactory, useAxisCountFromGeometryFactory, reader); } else if (hasText(reader, "INEARRING")) { geometry = parseLinearRing(geometryFactory, useAxisCountFromGeometryFactory, reader); } else { throw new IllegalArgumentException( "Invalid WKT geometry type: L" + FileUtil.getString(reader, 50)); } break; case 'M': if (hasText(reader, "ULTI")) { if (hasText(reader, "POINT")) { geometry = parseMultiPoint(geometryFactory, useAxisCountFromGeometryFactory, reader); } else if (hasText(reader, "LINESTRING")) { geometry = parseMultiLineString(geometryFactory, useAxisCountFromGeometryFactory, reader); } else if (hasText(reader, "POLYGON")) { geometry = parseMultiPolygon(geometryFactory, useAxisCountFromGeometryFactory, reader); } else { throw new IllegalArgumentException( "Invalid WKT geometry type: MULTI" + FileUtil.getString(reader, 50)); } } else { throw new IllegalArgumentException( "Invalid WKT geometry type: M" + FileUtil.getString(reader, 50)); } break; case 'P': if (hasText(reader, "OINT")) { geometry = parsePoint(geometryFactory, useAxisCountFromGeometryFactory, reader); } else if (hasText(reader, "OLYGON")) { geometry = parsePolygon(geometryFactory, useAxisCountFromGeometryFactory, reader); } else { throw new IllegalArgumentException( "Invalid WKT geometry type: P" + FileUtil.getString(reader, 50)); } break; default: } if (geometry == null) { throw new IllegalArgumentException( "Invalid WKT geometry type: " + FileUtil.getString(reader, 50)); } if (this.geometryFactory.getCoordinateSystemId() == 0) { final int srid = geometry.getCoordinateSystemId(); if (useAxisCountFromGeometryFactory) { geometryFactory = GeometryFactory.fixed(srid, axisCount, scaleX, scaleY, scaleZ); return (T)geometryFactory.geometry(geometry); } else { return (T)geometry; } } else if (geometryFactory == this.geometryFactory) { return (T)geometry; } else { return (T)this.geometryFactory.geometry(geometry); } } catch (final IOException e) { throw Exceptions.wrap("Error reading WKT:" + FileUtil.getString(reader, 50), e); } } public <T extends Geometry> T parseGeometry(final String value) { return parseGeometry(value, true); } @SuppressWarnings("unchecked") public <T extends Geometry> T parseGeometry(final String value, final boolean useAxisCountFromGeometryFactory) { if (Property.hasValue(value)) { final PushbackReader reader = new PushbackReader(new StringReader(value), 20); final GeometryFactory geometryFactory = this.geometryFactory; return (T)parseGeometry(geometryFactory, useAxisCountFromGeometryFactory, reader); } else { return null; } } private Geometry parseGeometryCollection(GeometryFactory geometryFactory, final boolean useAxisCountFromGeometryFactory, final PushbackReader reader) throws IOException { final int axisCount = getAxisCount(reader); if (!useAxisCountFromGeometryFactory) { if (axisCount != geometryFactory.getAxisCount()) { final int srid = geometryFactory.getCoordinateSystemId(); final double scaleX = geometryFactory.getScaleX(); final double scaleY = geometryFactory.getScaleY(); final double scaleZ = geometryFactory.getScaleZ(); geometryFactory = GeometryFactory.fixed(srid, axisCount, scaleX, scaleY, scaleZ); } } if (isEmpty(reader)) { return geometryFactory.geometryCollection(); } else { final List<Geometry> geometries = new ArrayList<>(); skipWhitespace(reader); int character = reader.read(); switch (character) { case '(': do { final Geometry geometry = parseGeometry(geometryFactory, useAxisCountFromGeometryFactory, reader); geometries.add(geometry); skipWhitespace(reader); character = reader.read(); } while (character == ','); if (character == ')') { } else { throw new IllegalArgumentException("Expecting ) not" + FileUtil.getString(reader, 50)); } break; case ')': character = reader.read(); if (character == ')' || character == ',') { skipWhitespace(reader); } else { throw new IllegalArgumentException( "Expecting ' or ) not" + FileUtil.getString(reader, 50)); } break; default: throw new IllegalArgumentException("Expecting ( not" + FileUtil.getString(reader, 50)); } return geometryFactory.geometry(geometries); } } private Geometry parseLinearRing(GeometryFactory geometryFactory, final boolean useAxisCountFromGeometryFactory, final PushbackReader reader) throws IOException { final int axisCount = getAxisCount(reader); if (!useAxisCountFromGeometryFactory) { if (axisCount != geometryFactory.getAxisCount()) { final int srid = geometryFactory.getCoordinateSystemId(); final double scaleX = geometryFactory.getScaleX(); final double scaleY = geometryFactory.getScaleY(); final double scaleZ = geometryFactory.getScaleZ(); geometryFactory = GeometryFactory.fixed(srid, axisCount, scaleX, scaleY, scaleZ); } } if (isEmpty(reader)) { return geometryFactory.linearRing(); } else { final LineString points = parseCoordinatesLineString(geometryFactory, reader, axisCount); if (points.getVertexCount() == 1) { return geometryFactory.point(points); } else { return points.newLinearRing(); } } } private Geometry parseLineString(GeometryFactory geometryFactory, final boolean useAxisCountFromGeometryFactory, final PushbackReader reader) throws IOException { final int axisCount = getAxisCount(reader); if (!useAxisCountFromGeometryFactory) { if (axisCount != geometryFactory.getAxisCount()) { final int srid = geometryFactory.getCoordinateSystemId(); final double scaleX = geometryFactory.getScaleX(); final double scaleY = geometryFactory.getScaleY(); final double scaleZ = geometryFactory.getScaleZ(); geometryFactory = GeometryFactory.fixed(srid, axisCount, scaleX, scaleY, scaleZ); } } if (isEmpty(reader)) { return geometryFactory.lineString(); } else { final LineStringDoubleBuilder points = parseCoordinatesLineString(geometryFactory, reader, axisCount); if (points.getVertexCount() == 1) { return geometryFactory.point(points); } else { return points.newLineString(); } } } private Lineal parseMultiLineString(GeometryFactory geometryFactory, final boolean useAxisCountFromGeometryFactory, final PushbackReader reader) throws IOException { final int axisCount = getAxisCount(reader); if (!useAxisCountFromGeometryFactory) { if (axisCount != geometryFactory.getAxisCount()) { final int srid = geometryFactory.getCoordinateSystemId(); final double scaleX = geometryFactory.getScaleX(); final double scaleY = geometryFactory.getScaleY(); final double scaleZ = geometryFactory.getScaleZ(); geometryFactory = GeometryFactory.fixed(srid, axisCount, scaleX, scaleY, scaleZ); } } if (isEmpty(reader)) { return geometryFactory.lineString(); } else { final List<LineString> lines = parseParts(geometryFactory, reader, axisCount); return geometryFactory.lineal(lines); } } private Punctual parseMultiPoint(GeometryFactory geometryFactory, final boolean useAxisCountFromGeometryFactory, final PushbackReader reader) throws IOException { final int axisCount = getAxisCount(reader); if (!useAxisCountFromGeometryFactory) { if (axisCount != geometryFactory.getAxisCount()) { final int srid = geometryFactory.getCoordinateSystemId(); final double scaleX = geometryFactory.getScaleX(); final double scaleY = geometryFactory.getScaleY(); final double scaleZ = geometryFactory.getScaleZ(); geometryFactory = GeometryFactory.fixed(srid, axisCount, scaleX, scaleY, scaleZ); } } if (isEmpty(reader)) { return geometryFactory.point(); } else { final List<Point> pointsList = parsePointParts(geometryFactory, reader, axisCount); return geometryFactory.punctual(pointsList); } } private Polygonal parseMultiPolygon(GeometryFactory geometryFactory, final boolean useAxisCountFromGeometryFactory, final PushbackReader reader) throws IOException { final int axisCount = getAxisCount(reader); if (!useAxisCountFromGeometryFactory) { if (axisCount != geometryFactory.getAxisCount()) { final int srid = geometryFactory.getCoordinateSystemId(); final double scaleX = geometryFactory.getScaleX(); final double scaleY = geometryFactory.getScaleY(); final double scaleZ = geometryFactory.getScaleZ(); geometryFactory = GeometryFactory.fixed(srid, axisCount, scaleX, scaleY, scaleZ); } } if (isEmpty(reader)) { return geometryFactory.polygon(); } else { final List<List<LineString>> polygons = parsePartsList(geometryFactory, reader, axisCount); return geometryFactory.polygonal(polygons); } } private List<LineString> parseParts(final GeometryFactory geometryFactory, final PushbackReader reader, final int axisCount) throws IOException { skipWhitespace(reader); final List<LineString> parts = new ArrayList<>(); int character = reader.read(); switch (character) { case '(': do { final LineStringDoubleBuilder lineBuilder = parseCoordinatesLineString(geometryFactory, reader, axisCount); parts.add(lineBuilder.newLineString()); character = reader.read(); } while (character == ','); if (character != ')') { throw new IllegalArgumentException("Expecting ) not" + FileUtil.getString(reader, 50)); } break; case ')': character = reader.read(); if (character == ')' || character == ',') { } else { throw new IllegalArgumentException( "Expecting ' or ) not" + FileUtil.getString(reader, 50)); } break; default: throw new IllegalArgumentException("Expecting ( not" + FileUtil.getString(reader, 50)); } return parts; } private List<List<LineString>> parsePartsList(final GeometryFactory geometryFactory, final PushbackReader reader, final int axisCount) throws IOException { final List<List<LineString>> partsList = new ArrayList<>(); skipWhitespace(reader); int character = reader.read(); switch (character) { case '(': do { final List<LineString> parts = parseParts(geometryFactory, reader, axisCount); partsList.add(parts); skipWhitespace(reader); character = reader.read(); } while (character == ','); if (character == ')') { } else { reader.unread(character); throw new IllegalArgumentException("Expecting ) not " + FileUtil.getString(reader, 50)); } break; case ')': skipWhitespace(reader); character = reader.read(); if (character == ')' || character == ',') { skipWhitespace(reader); } else { reader.unread(character); throw new IllegalArgumentException( "Expecting ' or ) not " + FileUtil.getString(reader, 50)); } break; default: reader.unread(character); throw new IllegalArgumentException("Expecting ( not " + FileUtil.getString(reader, 50)); } return partsList; } private Point parsePoint(GeometryFactory geometryFactory, final boolean useAxisCountFromGeometryFactory, final PushbackReader reader) throws IOException { final int axisCount = getAxisCount(reader); if (!useAxisCountFromGeometryFactory) { if (axisCount != geometryFactory.getAxisCount()) { final int srid = geometryFactory.getCoordinateSystemId(); final double scaleX = geometryFactory.getScaleX(); final double scaleY = geometryFactory.getScaleY(); final double scaleZ = geometryFactory.getScaleZ(); geometryFactory = GeometryFactory.fixed(srid, axisCount, scaleX, scaleY, scaleZ); } } if (isEmpty(reader)) { return geometryFactory.point(); } else { final LineString points = parseCoordinatesLineString(geometryFactory, reader, axisCount); if (points.getVertexCount() > 1) { throw new IllegalArgumentException("Points may only have 1 vertex"); } return geometryFactory.point(points); } } private List<Point> parsePointParts(final GeometryFactory geometryFactory, final PushbackReader reader, final int axisCount) throws IOException { skipWhitespace(reader); final List<Point> parts = new ArrayList<>(); int character = reader.read(); switch (character) { case '(': do { final LineStringDoubleBuilder lineBuilder = parseCoordinatesLineString(geometryFactory, reader, axisCount); parts.add(geometryFactory.point(lineBuilder)); skipWhitespace(reader); character = reader.read(); } while (character == ','); if (character != ')') { throw new IllegalArgumentException("Expecting ) not" + FileUtil.getString(reader, 50)); } break; case ')': character = reader.read(); if (character == ')' || character == ',') { } else { throw new IllegalArgumentException( "Expecting ' or ) not" + FileUtil.getString(reader, 50)); } break; default: throw new IllegalArgumentException("Expecting ( not" + FileUtil.getString(reader, 50)); } return parts; } private Polygon parsePolygon(GeometryFactory geometryFactory, final boolean useAxisCountFromGeometryFactory, final PushbackReader reader) throws IOException { final int axisCount = getAxisCount(reader); if (!useAxisCountFromGeometryFactory) { if (axisCount != geometryFactory.getAxisCount()) { final int srid = geometryFactory.getCoordinateSystemId(); final double scaleX = geometryFactory.getScaleX(); final double scaleY = geometryFactory.getScaleY(); final double scaleZ = geometryFactory.getScaleZ(); geometryFactory = GeometryFactory.fixed(srid, axisCount, scaleX, scaleY, scaleZ); } } final List<LineString> parts; if (isEmpty(reader)) { parts = new ArrayList<>(); } else { parts = parseParts(geometryFactory, reader, axisCount); } return geometryFactory.polygon(parts); } }