com.vividsolutions.jts.io.WKTReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jts Show documentation
Show all versions of jts Show documentation
The JTS Topology Suite is an API for modelling and
manipulating 2-dimensional linear geometry. It provides
numerous geometric predicates and functions. JTS
conforms to the Simple Features Specification for
SQL published by the Open GIS Consortium.
/*
* The JTS Topology Suite is a collection of Java classes that
* implement the fundamental operations required to validate a given
* geo-spatial data set to a known topological specification.
*
* Copyright (C) 2001 Vivid Solutions
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.vividsolutions.jts.io;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.util.*;
import com.vividsolutions.jts.io.ParseException;
import java.io.IOException;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.util.ArrayList;
/**
* Converts a geometry in Well-Known Text format to a {@link Geometry}.
*
* WKTReader
supports
* extracting Geometry
objects from either {@link Reader}s or
* {@link String}s. This allows it to function as a parser to read Geometry
* objects from text blocks embedded in other data formats (e.g. XML).
*
* A WKTReader
is parameterized by a GeometryFactory
,
* to allow it to create Geometry
objects of the appropriate
* implementation. In particular, the GeometryFactory
* determines the PrecisionModel
and SRID
that is
* used.
*
* The WKTReader
converts all input numbers to the precise
* internal representation.
*
*
Notes:
*
* - Keywords are case-insensitive.
*
- The reader supports non-standard "LINEARRING" tags.
*
- The reader uses Double.parseDouble to perform the conversion of ASCII
* numbers to floating point. This means it supports the Java
* syntax for floating point literals (including scientific notation).
*
*
* Syntax
* The following syntax specification describes the version of Well-Known Text
* supported by JTS.
* (The specification uses a syntax language similar to that used in
* the C and Java language specifications.)
*
*
*
* WKTGeometry: one of
*
* WKTPoint WKTLineString WKTLinearRing WKTPolygon
* WKTMultiPoint WKTMultiLineString WKTMultiPolygon
* WKTGeometryCollection
*
* WKTPoint: POINT ( Coordinate )
*
* WKTLineString: LINESTRING CoordinateSequence
*
* WKTLinearRing: LINEARRING CoordinateSequence
*
* WKTPolygon: POLYGON CoordinateSequenceList
*
* WKTMultiPoint: MULTIPOINT CoordinateSingletonList
*
* WKTMultiLineString: MULTILINESTRING CoordinateSequenceList
*
* WKTMultiPolygon:
* MULTIPOLYGON ( CoordinateSequenceList { , CoordinateSequenceList } )
*
* WKTGeometryCollection:
* GEOMETRYCOLLECTION ( WKTGeometry { , WKTGeometry } )
*
* CoordinateSingletonList:
* ( CoordinateSingleton { , CoordinateSingleton } )
* | EMPTY
*
* CoordinateSingleton:
* ( Coordinate )
* | EMPTY
*
* CoordinateSequenceList:
* ( CoordinateSequence { , CoordinateSequence } )
* | EMPTY
*
* CoordinateSequence:
* ( Coordinate { , Coordinate } )
* | EMPTY
*
* Coordinate:
* Number Number Numberopt
*
* Number: A Java-style floating-point number (including NaN, with arbitrary case)
*
*
*
*
*@version 1.7
* @see WKTWriter
*/
public class WKTReader
{
private static final String EMPTY = "EMPTY";
private static final String COMMA = ",";
private static final String L_PAREN = "(";
private static final String R_PAREN = ")";
private static final String NAN_SYMBOL = "NaN";
private GeometryFactory geometryFactory;
private PrecisionModel precisionModel;
private StreamTokenizer tokenizer;
/**
* Creates a reader that creates objects using the default {@link GeometryFactory}.
*/
public WKTReader() {
this(new GeometryFactory());
}
/**
* Creates a reader that creates objects using the given
* {@link GeometryFactory}.
*
*@param geometryFactory the factory used to create Geometry
s.
*/
public WKTReader(GeometryFactory geometryFactory) {
this.geometryFactory = geometryFactory;
precisionModel = geometryFactory.getPrecisionModel();
}
/**
* Reads a Well-Known Text representation of a {@link Geometry}
* from a {@link String}.
*
* @param wellKnownText
* one or more strings (see the OpenGIS
* Simple Features Specification) separated by whitespace
* @return a Geometry
specified by wellKnownText
* @throws ParseException
* if a parsing problem occurs
*/
public Geometry read(String wellKnownText) throws ParseException {
StringReader reader = new StringReader(wellKnownText);
try {
return read(reader);
}
finally {
reader.close();
}
}
/**
* Reads a Well-Known Text representation of a {@link Geometry}
* from a {@link Reader}.
*
*@param reader a Reader which will return a
* string (see the OpenGIS Simple Features Specification)
*@return a Geometry
read from reader
*@throws ParseException if a parsing problem occurs
*/
public Geometry read(Reader reader) throws ParseException {
tokenizer = new StreamTokenizer(reader);
// set tokenizer to NOT parse numbers
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(0, ' ');
tokenizer.commentChar('#');
try {
return readGeometryTaggedText();
}
catch (IOException e) {
throw new ParseException(e.toString());
}
}
/**
* Returns the next array of Coordinate
s in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next element returned by the stream should be L_PAREN (the
* beginning of "(x1 y1, x2 y2, ..., xn yn)") or EMPTY.
*@return the next array of Coordinate
s in the
* stream, or an empty array if EMPTY is the next element returned by
* the stream.
*@throws IOException if an I/O error occurs
*@throws ParseException if an unexpected token was encountered
*/
private Coordinate[] getCoordinates() throws IOException, ParseException {
String nextToken = getNextEmptyOrOpener();
if (nextToken.equals(EMPTY)) {
return new Coordinate[] {};
}
ArrayList coordinates = new ArrayList();
coordinates.add(getPreciseCoordinate());
nextToken = getNextCloserOrComma();
while (nextToken.equals(COMMA)) {
coordinates.add(getPreciseCoordinate());
nextToken = getNextCloserOrComma();
}
Coordinate[] array = new Coordinate[coordinates.size()];
return (Coordinate[]) coordinates.toArray(array);
}
private Coordinate[] getCoordinatesNoLeftParen() throws IOException, ParseException {
String nextToken = null;
ArrayList coordinates = new ArrayList();
coordinates.add(getPreciseCoordinate());
nextToken = getNextCloserOrComma();
while (nextToken.equals(COMMA)) {
coordinates.add(getPreciseCoordinate());
nextToken = getNextCloserOrComma();
}
Coordinate[] array = new Coordinate[coordinates.size()];
return (Coordinate[]) coordinates.toArray(array);
}
private Coordinate getPreciseCoordinate()
throws IOException, ParseException
{
Coordinate coord = new Coordinate();
coord.x = getNextNumber();
coord.y = getNextNumber();
if (isNumberNext()) {
coord.z = getNextNumber();
}
precisionModel.makePrecise(coord);
return coord;
}
private boolean isNumberNext() throws IOException {
int type = tokenizer.nextToken();
tokenizer.pushBack();
return type == StreamTokenizer.TT_WORD;
}
/**
* Parses the next number in the stream.
* Numbers with exponents are handled.
* NaN values are handled correctly, and
* the case of the "NaN" symbol is not significant.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next token must be a number.
*@return the next number in the stream
*@throws ParseException if the next token is not a valid number
*@throws IOException if an I/O error occurs
*/
private double getNextNumber() throws IOException,
ParseException {
int type = tokenizer.nextToken();
switch (type) {
case StreamTokenizer.TT_WORD:
{
if (tokenizer.sval.equalsIgnoreCase(NAN_SYMBOL)) {
return Double.NaN;
}
else {
try {
return Double.parseDouble(tokenizer.sval);
}
catch (NumberFormatException ex) {
throw new ParseException("Invalid number: " + tokenizer.sval);
}
}
}
}
parseError("number");
return 0.0;
}
/**
* Returns the next EMPTY or L_PAREN in the stream as uppercase text.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next token must be EMPTY or L_PAREN.
*@return the next EMPTY or L_PAREN in the stream as uppercase
* text.
*@throws ParseException if the next token is not EMPTY or L_PAREN
*@throws IOException if an I/O error occurs
*/
private String getNextEmptyOrOpener() throws IOException, ParseException {
String nextWord = getNextWord();
if (nextWord.equals(EMPTY) || nextWord.equals(L_PAREN)) {
return nextWord;
}
parseError(EMPTY + " or " + L_PAREN);
return null;
}
/**
* Returns the next R_PAREN or COMMA in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next token must be R_PAREN or COMMA.
*@return the next R_PAREN or COMMA in the stream
*@throws ParseException if the next token is not R_PAREN or COMMA
*@throws IOException if an I/O error occurs
*/
private String getNextCloserOrComma() throws IOException, ParseException {
String nextWord = getNextWord();
if (nextWord.equals(COMMA) || nextWord.equals(R_PAREN)) {
return nextWord;
}
parseError(COMMA + " or " + R_PAREN);
return null;
}
/**
* Returns the next R_PAREN in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next token must be R_PAREN.
*@return the next R_PAREN in the stream
*@throws ParseException if the next token is not R_PAREN
*@throws IOException if an I/O error occurs
*/
private String getNextCloser() throws IOException, ParseException {
String nextWord = getNextWord();
if (nextWord.equals(R_PAREN)) {
return nextWord;
}
parseError(R_PAREN);
return null;
}
/**
* Returns the next word in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next token must be a word.
*@return the next word in the stream as uppercase text
*@throws ParseException if the next token is not a word
*@throws IOException if an I/O error occurs
*/
private String getNextWord() throws IOException, ParseException {
int type = tokenizer.nextToken();
switch (type) {
case StreamTokenizer.TT_WORD:
String word = tokenizer.sval;
if (word.equalsIgnoreCase(EMPTY))
return EMPTY;
return word;
case '(': return L_PAREN;
case ')': return R_PAREN;
case ',': return COMMA;
}
parseError("word");
return null;
}
/**
* Returns the next word in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next token must be a word.
*@return the next word in the stream as uppercase text
*@throws ParseException if the next token is not a word
*@throws IOException if an I/O error occurs
*/
private String lookaheadWord() throws IOException, ParseException {
String nextWord = getNextWord();
tokenizer.pushBack();
return nextWord;
}
/**
* Throws a formatted ParseException for the current token.
*
* @param expected a description of what was expected
* @throws ParseException
* @throws AssertionFailedException if an invalid token is encountered
*/
private void parseError(String expected)
throws ParseException
{
// throws Asserts for tokens that should never be seen
if (tokenizer.ttype == StreamTokenizer.TT_NUMBER)
Assert.shouldNeverReachHere("Unexpected NUMBER token");
if (tokenizer.ttype == StreamTokenizer.TT_EOL)
Assert.shouldNeverReachHere("Unexpected EOL token");
String tokenStr = tokenString();
throw new ParseException("Expected " + expected + " but found " + tokenStr);
}
/**
* Gets a description of the current token
*
* @return a description of the current token
*/
private String tokenString()
{
switch (tokenizer.ttype) {
case StreamTokenizer.TT_NUMBER:
return "";
case StreamTokenizer.TT_EOL:
return "End-of-Line";
case StreamTokenizer.TT_EOF: return "End-of-Stream";
case StreamTokenizer.TT_WORD: return "'" + tokenizer.sval + "'";
}
return "'" + (char) tokenizer.ttype + "'";
}
/**
* Creates a Geometry
using the next token in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next tokens must form a <Geometry Tagged Text>.
*@return a Geometry
specified by the next token
* in the stream
*@throws ParseException if the coordinates used to create a Polygon
* shell and holes do not form closed linestrings, or if an unexpected
* token was encountered
*@throws IOException if an I/O error occurs
*/
private Geometry readGeometryTaggedText() throws IOException, ParseException {
String type = null;
try{
type = getNextWord();
}catch(IOException e){
return null;
}catch(ParseException e){
return null;
}
if (type.equalsIgnoreCase("POINT")) {
return readPointText();
}
else if (type.equalsIgnoreCase("LINESTRING")) {
return readLineStringText();
}
else if (type.equalsIgnoreCase("LINEARRING")) {
return readLinearRingText();
}
else if (type.equalsIgnoreCase("POLYGON")) {
return readPolygonText();
}
else if (type.equalsIgnoreCase("MULTIPOINT")) {
return readMultiPointText();
}
else if (type.equalsIgnoreCase("MULTILINESTRING")) {
return readMultiLineStringText();
}
else if (type.equalsIgnoreCase("MULTIPOLYGON")) {
return readMultiPolygonText();
}
else if (type.equalsIgnoreCase("GEOMETRYCOLLECTION")) {
return readGeometryCollectionText();
}
throw new ParseException("Unknown geometry type: " + type);
}
/**
* Creates a Point
using the next token in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next tokens must form a <Point Text>.
*@return a Point
specified by the next token in
* the stream
*@throws IOException if an I/O error occurs
*@throws ParseException if an unexpected token was encountered
*/
private Point readPointText() throws IOException, ParseException {
String nextToken = getNextEmptyOrOpener();
if (nextToken.equals(EMPTY)) {
return geometryFactory.createPoint((Coordinate)null);
}
Point point = geometryFactory.createPoint(getPreciseCoordinate());
getNextCloser();
return point;
}
/**
* Creates a LineString
using the next token in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next tokens must form a <LineString Text>.
*@return a LineString
specified by the next
* token in the stream
*@throws IOException if an I/O error occurs
*@throws ParseException if an unexpected token was encountered
*/
private LineString readLineStringText() throws IOException, ParseException {
return geometryFactory.createLineString(getCoordinates());
}
/**
* Creates a LinearRing
using the next token in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next tokens must form a <LineString Text>.
*@return a LinearRing
specified by the next
* token in the stream
*@throws IOException if an I/O error occurs
*@throws ParseException if the coordinates used to create the LinearRing
* do not form a closed linestring, or if an unexpected token was
* encountered
*/
private LinearRing readLinearRingText()
throws IOException, ParseException
{
return geometryFactory.createLinearRing(getCoordinates());
}
/*
private MultiPoint OLDreadMultiPointText() throws IOException, ParseException {
return geometryFactory.createMultiPoint(toPoints(getCoordinates()));
}
*/
private static final boolean ALLOW_OLD_JTS_MULTIPOINT_SYNTAX = true;
/**
* Creates a MultiPoint
using the next tokens in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next tokens must form a <MultiPoint Text>.
*@return a MultiPoint
specified by the next
* token in the stream
*@throws IOException if an I/O error occurs
*@throws ParseException if an unexpected token was encountered
*/
private MultiPoint readMultiPointText() throws IOException, ParseException
{
String nextToken = getNextEmptyOrOpener();
if (nextToken.equals(EMPTY)) {
return geometryFactory.createMultiPoint(new Point[0]);
}
// check for old-style JTS syntax and parse it if present
// MD 2009-02-21 - this is only provided for backwards compatibility for a few versions
if (ALLOW_OLD_JTS_MULTIPOINT_SYNTAX) {
String nextWord = lookaheadWord();
if (nextWord != L_PAREN) {
return geometryFactory.createMultiPoint(toPoints(getCoordinatesNoLeftParen()));
}
}
ArrayList points = new ArrayList();
Point point = readPointText();
points.add(point);
nextToken = getNextCloserOrComma();
while (nextToken.equals(COMMA)) {
point = readPointText();
points.add(point);
nextToken = getNextCloserOrComma();
}
Point[] array = new Point[points.size()];
return geometryFactory.createMultiPoint((Point[]) points.toArray(array));
}
/**
* Creates an array of Point
s having the given Coordinate
* s.
*
*@param coordinates the Coordinate
s with which to create the
* Point
s
*@return Point
s created using this WKTReader
* s GeometryFactory
*/
private Point[] toPoints(Coordinate[] coordinates) {
ArrayList points = new ArrayList();
for (int i = 0; i < coordinates.length; i++) {
points.add(geometryFactory.createPoint(coordinates[i]));
}
return (Point[]) points.toArray(new Point[]{});
}
/**
* Creates a Polygon
using the next token in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next tokens must form a <Polygon Text>.
*@return a Polygon
specified by the next token
* in the stream
*@throws ParseException if the coordinates used to create the Polygon
* shell and holes do not form closed linestrings, or if an unexpected
* token was encountered.
*@throws IOException if an I/O error occurs
*/
private Polygon readPolygonText() throws IOException, ParseException {
String nextToken = getNextEmptyOrOpener();
if (nextToken.equals(EMPTY)) {
return geometryFactory.createPolygon(geometryFactory.createLinearRing(
new Coordinate[]{}), new LinearRing[]{});
}
ArrayList holes = new ArrayList();
LinearRing shell = readLinearRingText();
nextToken = getNextCloserOrComma();
while (nextToken.equals(COMMA)) {
LinearRing hole = readLinearRingText();
holes.add(hole);
nextToken = getNextCloserOrComma();
}
LinearRing[] array = new LinearRing[holes.size()];
return geometryFactory.createPolygon(shell, (LinearRing[]) holes.toArray(array));
}
/**
* Creates a MultiLineString
using the next token in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next tokens must form a <MultiLineString Text>.
*@return a MultiLineString
specified by the
* next token in the stream
*@throws IOException if an I/O error occurs
*@throws ParseException if an unexpected token was encountered
*/
private com.vividsolutions.jts.geom.MultiLineString readMultiLineStringText() throws IOException, ParseException {
String nextToken = getNextEmptyOrOpener();
if (nextToken.equals(EMPTY)) {
return geometryFactory.createMultiLineString(new LineString[]{});
}
ArrayList lineStrings = new ArrayList();
LineString lineString = readLineStringText();
lineStrings.add(lineString);
nextToken = getNextCloserOrComma();
while (nextToken.equals(COMMA)) {
lineString = readLineStringText();
lineStrings.add(lineString);
nextToken = getNextCloserOrComma();
}
LineString[] array = new LineString[lineStrings.size()];
return geometryFactory.createMultiLineString((LineString[]) lineStrings.toArray(array));
}
/**
* Creates a MultiPolygon
using the next token in the stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next tokens must form a <MultiPolygon Text>.
*@return a MultiPolygon
specified by the next
* token in the stream, or if if the coordinates used to create the
* Polygon
shells and holes do not form closed linestrings.
*@throws IOException if an I/O error occurs
*@throws ParseException if an unexpected token was encountered
*/
private MultiPolygon readMultiPolygonText() throws IOException, ParseException {
String nextToken = getNextEmptyOrOpener();
if (nextToken.equals(EMPTY)) {
return geometryFactory.createMultiPolygon(new Polygon[]{});
}
ArrayList polygons = new ArrayList();
Polygon polygon = readPolygonText();
polygons.add(polygon);
nextToken = getNextCloserOrComma();
while (nextToken.equals(COMMA)) {
polygon = readPolygonText();
polygons.add(polygon);
nextToken = getNextCloserOrComma();
}
Polygon[] array = new Polygon[polygons.size()];
return geometryFactory.createMultiPolygon((Polygon[]) polygons.toArray(array));
}
/**
* Creates a GeometryCollection
using the next token in the
* stream.
*
*@param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next tokens must form a <GeometryCollection Text>.
*@return a GeometryCollection
specified by the
* next token in the stream
*@throws ParseException if the coordinates used to create a Polygon
* shell and holes do not form closed linestrings, or if an unexpected
* token was encountered
*@throws IOException if an I/O error occurs
*/
private GeometryCollection readGeometryCollectionText() throws IOException, ParseException {
String nextToken = getNextEmptyOrOpener();
if (nextToken.equals(EMPTY)) {
return geometryFactory.createGeometryCollection(new Geometry[]{});
}
ArrayList geometries = new ArrayList();
Geometry geometry = readGeometryTaggedText();
geometries.add(geometry);
nextToken = getNextCloserOrComma();
while (nextToken.equals(COMMA)) {
geometry = readGeometryTaggedText();
geometries.add(geometry);
nextToken = getNextCloserOrComma();
}
Geometry[] array = new Geometry[geometries.size()];
return geometryFactory.createGeometryCollection((Geometry[]) geometries.toArray(array));
}
}