All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vividsolutions.jts.io.WKTReader Maven / Gradle / Ivy

The newest version!
/*
 * 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 Geometrys. */ 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 Coordinates 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 Coordinates 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) { parseErrorWithLine("Invalid number: " + tokenizer.sval); } } } } parseErrorExpected("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; } parseErrorExpected(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; } parseErrorExpected(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; } parseErrorExpected(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; } parseErrorExpected("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 reporting that the current token * was unexpected. * * @param expected a description of what was expected * @throws ParseException * @throws AssertionFailedException if an invalid token is encountered */ private void parseErrorExpected(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(); parseErrorWithLine("Expected " + expected + " but found " + tokenStr); } private void parseErrorWithLine(String msg) throws ParseException { throw new ParseException(msg + " (line " + tokenizer.lineno() + ")"); } /** * 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(); } parseErrorWithLine("Unknown geometry type: " + type); // should never reach here return null; } /** * 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 Points having the given Coordinate * s. * *@param coordinates the Coordinates with which to create the * Points *@return Points 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)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy