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

com.hazelcast.shaded.org.locationtech.jts.io.WKTReader Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version
/*
 * Copyright (c) 2016 Vivid Solutions.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package com.hazelcast.shaded.org.locationtech.jts.io;


import java.io.IOException;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;

import com.hazelcast.shaded.org.locationtech.jts.geom.Coordinate;
import com.hazelcast.shaded.org.locationtech.jts.geom.CoordinateSequence;
import com.hazelcast.shaded.org.locationtech.jts.geom.CoordinateSequenceFactory;
import com.hazelcast.shaded.org.locationtech.jts.geom.CoordinateXY;
import com.hazelcast.shaded.org.locationtech.jts.geom.CoordinateXYM;
import com.hazelcast.shaded.org.locationtech.jts.geom.CoordinateXYZM;
import com.hazelcast.shaded.org.locationtech.jts.geom.Geometry;
import com.hazelcast.shaded.org.locationtech.jts.geom.GeometryCollection;
import com.hazelcast.shaded.org.locationtech.jts.geom.GeometryFactory;
import com.hazelcast.shaded.org.locationtech.jts.geom.LineString;
import com.hazelcast.shaded.org.locationtech.jts.geom.LinearRing;
import com.hazelcast.shaded.org.locationtech.jts.geom.MultiLineString;
import com.hazelcast.shaded.org.locationtech.jts.geom.MultiPoint;
import com.hazelcast.shaded.org.locationtech.jts.geom.MultiPolygon;
import com.hazelcast.shaded.org.locationtech.jts.geom.Point;
import com.hazelcast.shaded.org.locationtech.jts.geom.Polygon;
import com.hazelcast.shaded.org.locationtech.jts.geom.PrecisionModel;
import com.hazelcast.shaded.org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory;
import com.hazelcast.shaded.org.locationtech.jts.util.Assert;
import com.hazelcast.shaded.org.locationtech.jts.util.AssertionFailedException;

/**
 * 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. *

* As of version 1.15, JTS can read (but not write) WKT syntax * which specifies coordinate dimension Z, M or ZM as modifiers (e.g. POINT Z) * or in the name of the geometry type (e.g. LINESTRINGZM). * If the coordinate dimension is specified it will be set in the created geometry. * If the coordinate dimension is not specified, the default behaviour is to * create XYZ geometry (this is backwards compatible with older JTS versions). * This can be altered to create XY geometry by * calling {@link #setIsOldJtsCoordinateSyntaxAllowed(boolean)}. *

* A reader can be set to ensure the input is structurally valid * by calling {@link #setFixStructure(boolean)}. * This ensures that geometry can be constructed without errors due to missing coordinates. * The created geometry may still be topologically invalid. * *

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[Dimension] ( Coordinate )
 *
 * WKTLineString: LINESTRING[Dimension] CoordinateSequence
 *
 * WKTLinearRing: LINEARRING[Dimension] CoordinateSequence
 *
 * WKTPolygon: POLYGON[Dimension] CoordinateSequenceList
 *
 * WKTMultiPoint: MULTIPOINT[Dimension] CoordinateSingletonList
 *
 * WKTMultiLineString: MULTILINESTRING[Dimension] CoordinateSequenceList
 *
 * WKTMultiPolygon:
 *         MULTIPOLYGON[Dimension] ( CoordinateSequenceList { , CoordinateSequenceList } )
 *
 * WKTGeometryCollection: 
 *         GEOMETRYCOLLECTION[Dimension]  ( WKTGeometry { , WKTGeometry } )
 *
 * CoordinateSingletonList:
 *         ( CoordinateSingleton { , CoordinateSingleton } )
 *         | EMPTY
 *         
 * CoordinateSingleton:
 *         ( Coordinate )
 *         | EMPTY
 *
 * CoordinateSequenceList:
 *         ( CoordinateSequence { , CoordinateSequence } )
 *         | EMPTY
 *
 * CoordinateSequence:
 *         ( Coordinate { , Coordinate } )
 *         | EMPTY
 *
 * Coordinate:
 *         Number Number Numberopt Numberopt
 *
 * Number: A Java-style floating-point number (including NaN, with arbitrary case)
 *
 * Dimension:
 *         Z| Z|M| M|ZM| ZM
 *
 * 
* * *@version 1.7 * @see WKTWriter */ public class WKTReader { 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 CoordinateSequenceFactory csFactory; private static CoordinateSequenceFactory csFactoryXYZM = CoordinateArraySequenceFactory.instance(); private PrecisionModel precisionModel; /** * Flag indicating that the old notation of coordinates in JTS * is supported. */ private static final boolean ALLOW_OLD_JTS_COORDINATE_SYNTAX = true; private boolean isAllowOldJtsCoordinateSyntax = ALLOW_OLD_JTS_COORDINATE_SYNTAX; /** * Flag indicating that the old notation of MultiPoint coordinates in JTS * is supported. */ private static final boolean ALLOW_OLD_JTS_MULTIPOINT_SYNTAX = true; private boolean isAllowOldJtsMultipointSyntax = ALLOW_OLD_JTS_MULTIPOINT_SYNTAX; private boolean isFixStructure = false; /** * 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; this.csFactory = geometryFactory.getCoordinateSequenceFactory(); this.precisionModel = geometryFactory.getPrecisionModel(); } /** * Sets a flag indicating, that coordinates may have 3 ordinate values even though no Z or M ordinate indicator * is present. The default value is {@link #ALLOW_OLD_JTS_COORDINATE_SYNTAX}. * * @param value a boolean value */ public void setIsOldJtsCoordinateSyntaxAllowed(boolean value) { isAllowOldJtsCoordinateSyntax = value; } /** * Sets a flag indicating, that point coordinates in a MultiPoint geometry must not be enclosed in paren. * The default value is {@link #ALLOW_OLD_JTS_MULTIPOINT_SYNTAX} * @param value a boolean value */ public void setIsOldJtsMultiPointSyntaxAllowed(boolean value) { isAllowOldJtsMultipointSyntax = value; } /** * Sets a flag indicating that the structure of input geometry should be fixed * so that the geometry can be constructed without error. * This involves adding coordinates if the input coordinate sequence is shorter than required. * * @param isFixStructure true if the input structure should be fixed * * @see LinearRing#MINIMUM_VALID_SIZE */ public void setFixStructure(boolean isFixStructure) { this.isFixStructure = isFixStructure; } /** * Reads a Well-Known Text representation of a {@link Geometry} * from a {@link String}. * * @param wellKnownText * one or more <Geometry Tagged Text> 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 <Geometry Tagged Text> * 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 { StreamTokenizer tokenizer = createTokenizer(reader); try { return readGeometryTaggedText(tokenizer); } catch (IOException e) { throw new ParseException(e.toString()); } } /** * Utility function to create the tokenizer * @param reader a reader * * @return a WKT Tokenizer. */ private static StreamTokenizer createTokenizer(Reader reader) { StreamTokenizer 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('#'); return tokenizer; } /** * Reads a Coordinate from a stream using the given {@link StreamTokenizer}. *

* All ordinate values are read, but -depending on the {@link CoordinateSequenceFactory} of the * underlying {@link GeometryFactory}- not necessarily all can be handled. Those are silently dropped. *

* @param tokenizer the tokenizer to use * @param ordinateFlags a bit-mask defining the ordinates to read. * @param tryParen a value indicating if a starting {@link #L_PAREN} should be probed. * @return a {@link Coordinate} of appropriate dimension containing the read ordinate values * *@throws IOException if an I/O error occurs *@throws ParseException if an unexpected token was encountered */ private Coordinate getCoordinate(StreamTokenizer tokenizer, EnumSet ordinateFlags, boolean tryParen) throws IOException, ParseException { boolean opened = false; if (tryParen && isOpenerNext(tokenizer) ) { tokenizer.nextToken(); opened = true; } // create a sequence for one coordinate int offsetM = ordinateFlags.contains(Ordinate.Z) ? 1 : 0; Coordinate coord = createCoordinate(ordinateFlags); coord.setOrdinate(CoordinateSequence.X, precisionModel.makePrecise(getNextNumber(tokenizer))); coord.setOrdinate(CoordinateSequence.Y, precisionModel.makePrecise(getNextNumber(tokenizer))); // additionally read other vertices if (ordinateFlags.contains(Ordinate.Z)) coord.setOrdinate(CoordinateSequence.Z, getNextNumber(tokenizer)); if (ordinateFlags.contains(Ordinate.M)) coord.setOrdinate(CoordinateSequence.Z + offsetM, getNextNumber(tokenizer)); if (ordinateFlags.size() == 2 && this.isAllowOldJtsCoordinateSyntax && isNumberNext(tokenizer)) { coord.setOrdinate(CoordinateSequence.Z, getNextNumber(tokenizer)); } // read close token if it was opened here if (opened) { getNextCloser(tokenizer); } return coord; } private Coordinate createCoordinate(EnumSet ordinateFlags) { boolean hasZ = ordinateFlags.contains(Ordinate.Z); boolean hasM = ordinateFlags.contains(Ordinate.M); if (hasZ && hasM) return new CoordinateXYZM(); if (hasM) return new CoordinateXYM(); if (hasZ || this.isAllowOldJtsCoordinateSyntax) return new Coordinate(); return new CoordinateXY(); } /** * Reads a Coordinate from a stream using the given {@link StreamTokenizer}. *

* All ordinate values are read, but -depending on the {@link CoordinateSequenceFactory} of the * underlying {@link GeometryFactory}- not necessarily all can be handled. Those are silently dropped. *

*

* *

* @param tokenizer the tokenizer to use * @param ordinateFlags a bit-mask defining the ordinates to read. * @return a {@link CoordinateSequence} of length 1 containing the read ordinate values * *@throws IOException if an I/O error occurs *@throws ParseException if an unexpected token was encountered */ private CoordinateSequence getCoordinateSequence(StreamTokenizer tokenizer, EnumSet ordinateFlags, int minSize, boolean isRing) throws IOException, ParseException { if (getNextEmptyOrOpener(tokenizer).equals(WKTConstants.EMPTY)) return createCoordinateSequenceEmpty(ordinateFlags); List coordinates = new ArrayList(); do { coordinates.add(getCoordinate(tokenizer, ordinateFlags, false)); } while (getNextCloserOrComma(tokenizer).equals(COMMA)); if (isFixStructure) { fixStructure(coordinates, minSize, isRing); } Coordinate[] coordArray = coordinates.toArray(new Coordinate[0]); return csFactory.create(coordArray); } private static void fixStructure(List coords, int minSize, boolean isRing) { if (coords.size() == 0) return; if (isRing && ! isClosed(coords)) { coords.add(coords.get(0).copy()); } while (coords.size() < minSize) { coords.add(coords.get(coords.size() - 1).copy()); } } private static boolean isClosed(List coords) { if (coords.size() == 0) return true; if (coords.size() == 1 || ! coords.get(0).equals2D(coords.get(coords.size() - 1))) { return false; } return true; } private CoordinateSequence createCoordinateSequenceEmpty(EnumSet ordinateFlags) throws IOException, ParseException { return csFactory.create(0, toDimension(ordinateFlags), ordinateFlags.contains(Ordinate.M) ? 1 : 0); } /** * Reads a CoordinateSequence from a stream using the given {@link StreamTokenizer} * for an old-style JTS MultiPoint (Point coordinates not enclosed in parentheses). *

* All ordinate values are read, but -depending on the {@link CoordinateSequenceFactory} of the * underlying {@link GeometryFactory}- not necessarily all can be handled. Those are silently dropped. *

* @param tokenizer the tokenizer to use * @param ordinateFlags a bit-mask defining the ordinates to read. * @param tryParen a value indicating if a starting {@link #L_PAREN} should be probed for each coordinate. * @param isReadEmptyOrOpener indicates if an opening paren or EMPTY should be scanned for * @return a {@link CoordinateSequence} of length 1 containing the read ordinate values * * @throws IOException if an I/O error occurs * @throws ParseException if an unexpected token was encountered S */ private CoordinateSequence getCoordinateSequenceOldMultiPoint(StreamTokenizer tokenizer, EnumSet ordinateFlags) throws IOException, ParseException { List coordinates = new ArrayList(); do { coordinates.add(getCoordinate(tokenizer, ordinateFlags, true)); } while (getNextCloserOrComma(tokenizer).equals(COMMA)); Coordinate[] coordArray = coordinates.toArray(new Coordinate[0]); return csFactory.create(coordArray); } /** * Computes the required dimension based on the given ordinate values. * It is assumed that {@link Ordinate#X} and {@link Ordinate#Y} are included. * * @param ordinateFlags the ordinate bit-mask * @return the number of dimensions required to store ordinates for the given bit-mask. */ private int toDimension(EnumSet ordinateFlags) { int dimension = 2; if (ordinateFlags.contains(Ordinate.Z)) dimension++; if (ordinateFlags.contains(Ordinate.M)) dimension++; if (dimension == 2 && this.isAllowOldJtsCoordinateSyntax) dimension++; return dimension; } /** * Tests if the next token in the stream is a number * * @param tokenizer the tokenizer * @return {@code true} if the next token is a number, otherwise {@code false} * @throws IOException if an I/O error occurs */ private static boolean isNumberNext(StreamTokenizer tokenizer) throws IOException { int type = tokenizer.nextToken(); tokenizer.pushBack(); return type == StreamTokenizer.TT_WORD; } /** * Tests if the next token in the stream is a left opener ({@link #L_PAREN}) * * @param tokenizer the tokenizer * @return {@code true} if the next token is a {@link #L_PAREN}, otherwise {@code false} * @throws IOException if an I/O error occurs */ private static boolean isOpenerNext(StreamTokenizer tokenizer) throws IOException { int type = tokenizer.nextToken(); tokenizer.pushBack(); return type == '('; } /** * 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 * @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(StreamTokenizer tokenizer) 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 parseErrorWithLine(tokenizer, "Invalid number: " + tokenizer.sval); } } } } throw parseErrorExpected(tokenizer, "number"); } /** * Returns the next EMPTY or L_PAREN in the stream as uppercase text. * *@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 * @param tokenizer tokenizer over a stream of text in Well-known Text */ private static String getNextEmptyOrOpener(StreamTokenizer tokenizer) throws IOException, ParseException { String nextWord = getNextWord(tokenizer); if (nextWord.equalsIgnoreCase(WKTConstants.Z)) { //z = true; nextWord = getNextWord(tokenizer); } else if (nextWord.equalsIgnoreCase(WKTConstants.M)) { //m = true; nextWord = getNextWord(tokenizer); } else if (nextWord.equalsIgnoreCase(WKTConstants.ZM)) { //z = true; //m = true; nextWord = getNextWord(tokenizer); } if (nextWord.equals(WKTConstants.EMPTY) || nextWord.equals(L_PAREN)) { return nextWord; } throw parseErrorExpected(tokenizer, WKTConstants.EMPTY + " or " + L_PAREN); } /** * Returns the next ordinate flag information in the stream as uppercase text. * This can be Z, M or ZM. * *@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 * @param tokenizer tokenizer over a stream of text in Well-known Text */ private static EnumSet getNextOrdinateFlags(StreamTokenizer tokenizer) throws IOException, ParseException { EnumSet result = EnumSet.of(Ordinate.X, Ordinate.Y); String nextWord = lookAheadWord(tokenizer).toUpperCase(Locale.ROOT); if (nextWord.equalsIgnoreCase(WKTConstants.Z)) { tokenizer.nextToken(); result.add(Ordinate.Z); } else if (nextWord.equalsIgnoreCase(WKTConstants.M)) { tokenizer.nextToken(); result.add(Ordinate.M); } else if (nextWord.equalsIgnoreCase(WKTConstants.ZM)) { tokenizer.nextToken(); result.add(Ordinate.Z); result.add(Ordinate.M); } return result; } /** * 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 static String lookAheadWord(StreamTokenizer tokenizer) throws IOException, ParseException { String nextWord = getNextWord(tokenizer); tokenizer.pushBack(); return nextWord; } /** * Returns the next {@link #R_PAREN} or {@link #COMMA} in the stream. * *@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 * @param tokenizer tokenizer over a stream of text in Well-known Text */ private static String getNextCloserOrComma(StreamTokenizer tokenizer) throws IOException, ParseException { String nextWord = getNextWord(tokenizer); if (nextWord.equals(COMMA) || nextWord.equals(R_PAREN)) { return nextWord; } throw parseErrorExpected(tokenizer, COMMA + " or " + R_PAREN); } /** * Returns the next {@link #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(StreamTokenizer tokenizer) throws IOException, ParseException { String nextWord = getNextWord(tokenizer); if (nextWord.equals(R_PAREN)) { return nextWord; } throw parseErrorExpected(tokenizer, R_PAREN); } /** * Returns the next word in the stream. * *@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 * @param tokenizer tokenizer over a stream of text in Well-known Text */ private static String getNextWord(StreamTokenizer tokenizer) throws IOException, ParseException { int type = tokenizer.nextToken(); switch (type) { case StreamTokenizer.TT_WORD: String word = tokenizer.sval; if (word.equalsIgnoreCase(WKTConstants.EMPTY)) return WKTConstants.EMPTY; return word; case '(': return L_PAREN; case ')': return R_PAREN; case ',': return COMMA; } throw parseErrorExpected(tokenizer, "word"); } /** * Creates a formatted ParseException reporting that the current token * was unexpected. * * @param expected a description of what was expected * @throws AssertionFailedException if an invalid token is encountered */ private static ParseException parseErrorExpected(StreamTokenizer tokenizer, String expected) { // 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(tokenizer); return parseErrorWithLine(tokenizer, "Expected " + expected + " but found " + tokenStr); } /** * Creates a formatted ParseException reporting that the current token * was unexpected. * * @param msg a description of what was expected * @throws AssertionFailedException if an invalid token is encountered */ private static ParseException parseErrorWithLine(StreamTokenizer tokenizer, String msg) { return new ParseException(msg + " (line " + tokenizer.lineno() + ")"); } /** * Gets a description of the current token type * @param tokenizer the tokenizer * @return a description of the current token */ private static String tokenString(StreamTokenizer tokenizer) { 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. * *@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 * @param tokenizer tokenizer over a stream of text in Well-known Text */ private Geometry readGeometryTaggedText(StreamTokenizer tokenizer) throws IOException, ParseException { String type; EnumSet ordinateFlags = EnumSet.of(Ordinate.X, Ordinate.Y); type = getNextWord(tokenizer).toUpperCase(Locale.ROOT); if (type.endsWith(WKTConstants.ZM)) { ordinateFlags.add(Ordinate.Z); ordinateFlags.add(Ordinate.M); } else if (type.endsWith(WKTConstants.Z)) { ordinateFlags.add(Ordinate.Z); } else if (type.endsWith(WKTConstants.M)) { ordinateFlags.add(Ordinate.M); } return readGeometryTaggedText(tokenizer, type, ordinateFlags); } private Geometry readGeometryTaggedText(StreamTokenizer tokenizer, String type, EnumSet ordinateFlags) throws IOException, ParseException { if (ordinateFlags.size() == 2) { ordinateFlags = getNextOrdinateFlags(tokenizer); } // if we can create a sequence with the required dimension everything is ok, otherwise // we need to take a different coordinate sequence factory. // It would be good to not have to try/catch this but if the CoordinateSequenceFactory // exposed a value indicating which min/max dimension it can handle or even an // ordinate bit-flag. try { csFactory.create(0, toDimension(ordinateFlags), ordinateFlags.contains(Ordinate.M) ? 1 : 0); } catch (Exception e) { geometryFactory = new GeometryFactory(geometryFactory.getPrecisionModel(), geometryFactory.getSRID(), csFactoryXYZM); } if (isTypeName(tokenizer, type, WKTConstants.POINT)) { return readPointText(tokenizer, ordinateFlags); } else if (isTypeName(tokenizer, type, WKTConstants.LINESTRING)) { return readLineStringText(tokenizer, ordinateFlags); } else if (isTypeName(tokenizer, type, WKTConstants.LINEARRING)) { return readLinearRingText(tokenizer, ordinateFlags); } else if (isTypeName(tokenizer, type, WKTConstants.POLYGON)) { return readPolygonText(tokenizer, ordinateFlags); } else if (isTypeName(tokenizer, type, WKTConstants.MULTIPOINT)) { return readMultiPointText(tokenizer, ordinateFlags); } else if (isTypeName(tokenizer, type, WKTConstants.MULTILINESTRING)) { return readMultiLineStringText(tokenizer, ordinateFlags); } else if (isTypeName(tokenizer, type, WKTConstants.MULTIPOLYGON)) { return readMultiPolygonText(tokenizer, ordinateFlags); } else if (isTypeName(tokenizer, type, WKTConstants.GEOMETRYCOLLECTION)) { return readGeometryCollectionText(tokenizer, ordinateFlags); } throw parseErrorWithLine(tokenizer, "Unknown geometry type: " + type); } private boolean isTypeName(StreamTokenizer tokenizer, String type, String typeName) throws ParseException { if (! type.startsWith(typeName)) return false; String modifiers = type.substring(typeName.length()); boolean isValidMod = modifiers.length() <= 2 && (modifiers.length() == 0 ||modifiers.equals(WKTConstants.Z) || modifiers.equals(WKTConstants.M) || modifiers.equals(WKTConstants.ZM)); if (! isValidMod) { throw parseErrorWithLine(tokenizer, "Invalid dimension modifiers: " + type); } return true; } /** * 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(StreamTokenizer tokenizer, EnumSet ordinateFlags) throws IOException, ParseException { Point point = geometryFactory.createPoint(getCoordinateSequence(tokenizer, ordinateFlags, 1, false)); 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(StreamTokenizer tokenizer, EnumSet ordinateFlags) throws IOException, ParseException { return geometryFactory.createLineString(getCoordinateSequence(tokenizer, ordinateFlags, LineString.MINIMUM_VALID_SIZE, false)); } /** * 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(StreamTokenizer tokenizer, EnumSet ordinateFlags) throws IOException, ParseException { return geometryFactory.createLinearRing(getCoordinateSequence(tokenizer, ordinateFlags, LinearRing.MINIMUM_VALID_SIZE, 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(StreamTokenizer tokenizer, EnumSet ordinateFlags) throws IOException, ParseException { String nextToken = getNextEmptyOrOpener(tokenizer); if (nextToken.equals(WKTConstants.EMPTY)) { return geometryFactory.createMultiPoint(new Point[0]); } // check for old-style JTS syntax (no parentheses surrounding Point coordinates) and parse it if present // MD 2009-02-21 - this is only provided for backwards compatibility for a few versions if (isAllowOldJtsMultipointSyntax) { String nextWord = lookAheadWord(tokenizer); if (nextWord != L_PAREN && nextWord != WKTConstants.EMPTY) { return geometryFactory.createMultiPoint( getCoordinateSequenceOldMultiPoint(tokenizer, ordinateFlags)); } } List points = new ArrayList(); Point point = readPointText(tokenizer, ordinateFlags); points.add(point); nextToken = getNextCloserOrComma(tokenizer); while (nextToken.equals(COMMA)) { point = readPointText(tokenizer, ordinateFlags); points.add(point); nextToken = getNextCloserOrComma(tokenizer); } Point[] array = new Point[points.size()]; return geometryFactory.createMultiPoint((Point[]) points.toArray(array)); } /** * 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(StreamTokenizer tokenizer, EnumSet ordinateFlags) throws IOException, ParseException { String nextToken = getNextEmptyOrOpener(tokenizer); if (nextToken.equals(WKTConstants.EMPTY)) { return geometryFactory.createPolygon(createCoordinateSequenceEmpty(ordinateFlags)); } List holes = new ArrayList(); LinearRing shell = readLinearRingText(tokenizer, ordinateFlags); nextToken = getNextCloserOrComma(tokenizer); while (nextToken.equals(COMMA)) { LinearRing hole = readLinearRingText(tokenizer, ordinateFlags); holes.add(hole); nextToken = getNextCloserOrComma(tokenizer); } 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 MultiLineString readMultiLineStringText(StreamTokenizer tokenizer, EnumSet ordinateFlags) throws IOException, ParseException { String nextToken = getNextEmptyOrOpener(tokenizer); if (nextToken.equals(WKTConstants.EMPTY)) { return geometryFactory.createMultiLineString(); } List lineStrings = new ArrayList(); do { LineString lineString = readLineStringText(tokenizer, ordinateFlags); lineStrings.add(lineString); nextToken = getNextCloserOrComma(tokenizer); } while (nextToken.equals(COMMA)); 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(StreamTokenizer tokenizer, EnumSet ordinateFlags) throws IOException, ParseException { String nextToken = getNextEmptyOrOpener(tokenizer); if (nextToken.equals(WKTConstants.EMPTY)) { return geometryFactory.createMultiPolygon(); } List polygons = new ArrayList(); do { Polygon polygon = readPolygonText(tokenizer, ordinateFlags); polygons.add(polygon); nextToken = getNextCloserOrComma(tokenizer); } while (nextToken.equals(COMMA)); 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(StreamTokenizer tokenizer, EnumSet ordinateFlags) throws IOException, ParseException { String nextToken = getNextEmptyOrOpener(tokenizer); if (nextToken.equals(WKTConstants.EMPTY)) { return geometryFactory.createGeometryCollection(); } List geometries = new ArrayList(); do { Geometry geometry = readGeometryTaggedText(tokenizer); geometries.add(geometry); nextToken = getNextCloserOrComma(tokenizer); } while (nextToken.equals(COMMA)); Geometry[] array = new Geometry[geometries.size()]; return geometryFactory.createGeometryCollection((Geometry[]) geometries.toArray(array)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy