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

org.geotools.geometry.iso.text.WKTParser Maven / Gradle / Ivy

/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
 *
 *    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;
 *    version 2.1 of the License.
 *
 *    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.
 */
package org.geotools.geometry.iso.text;

import java.io.IOException;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.geotools.geometry.GeometryBuilder;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.Geometry;
import org.opengis.geometry.PositionFactory;
import org.opengis.geometry.aggregate.AggregateFactory;
import org.opengis.geometry.aggregate.MultiPrimitive;
import org.opengis.geometry.coordinate.GeometryFactory;
import org.opengis.geometry.coordinate.LineString;
import org.opengis.geometry.primitive.Curve;
import org.opengis.geometry.primitive.Point;
import org.opengis.geometry.primitive.PrimitiveFactory;
import org.opengis.geometry.primitive.Ring;
import org.opengis.geometry.primitive.Surface;
import org.opengis.geometry.primitive.SurfaceBoundary;

/**
 * This class is used to parse well known text (WKT) which describes an ISO 19107 Geometry. The
 * grammar described comes from the ISO 19125-1 spec which describes feature geometry. It doesn't
 * seem to exactly mesh up with the geometry as described in 19107 so not all of the grammar is
 * supported.
 *
 * 

The types in the WKT format, and their mappings: * *

    *
  • POINT org.opengis.geometry.primitive.Point *
  • LINESTRING org.opengis.geometry.primitive.Curve *
  • POLYGON org.opengis.geometry.primitive.Surface *
  • MULTIPOINT org.opengis.geometry.coordinate.aggregate.MultiPoint Note that there is no * factory method for MultiPoint.
    * For now, to keep implementation-independance I'm returning it as a List *
  • MULTILINESTRING no matching type in the GeoAPI interfaces Could also be returned as list. *
    * Not handled for now *
  • MULTIPOLYGON no matching type in the GeoAPI interfaces Could also be returned as list.
    * Not handled for now *
* * > Please note that this parser is not thread safe; you can however reuse the parser. * * @author Jody Garnett * @author Joel Skelton * @since 2.5 * @version $Id$ */ public class WKTParser { private static final String EMPTY = "EMPTY"; private static final String COMMA = ","; private static final String L_PAREN = "("; private static final String R_PAREN = ")"; private GeometryFactory geometryFactory; private PrimitiveFactory primitiveFactory; @SuppressWarnings("PMD.UnusedPrivateField") private PositionFactory positionFactory; public WKTParser(GeometryBuilder builder) { this( builder.getGeometryFactory(), builder.getPrimitiveFactory(), builder.getPositionFactory(), builder.getAggregateFactory()); } /** * Constructor takes pre-created geometry and primitive factories that will be used to parse the * Well Known Text (WKT). The geometries created from the WKT will be created in the * CoordinateReferenceSystem * * @param geometryFactory A GeometryFactory created with a * CoordinateReferenceSystem and PrecisionModel * @param primitiveFactory A PrimitiveFactory created with the same crs and * precision as above * @param positionFactory A PositionFactory created with the same crs and precision * as above * @param aggregateFactory A AggregateFactory created with the same crs and * precision as above */ public WKTParser( GeometryFactory geometryFactory, PrimitiveFactory primitiveFactory, PositionFactory positionFactory, AggregateFactory aggregateFactory) { this.geometryFactory = geometryFactory; this.primitiveFactory = primitiveFactory; this.positionFactory = positionFactory; } /** * Provide a GeometryFactory for the parser. * *

Should be called prior to use. */ public void setFactory(GeometryFactory factory) { this.geometryFactory = factory; } /** * Provide a PrimitiveFactory for the parser. * *

Should be called prior to use. */ public void setFactory(PrimitiveFactory factory) { this.primitiveFactory = factory; } /** * Provide a PositionFactory for the parser. * *

Should be called prior to use. */ public void setFactory(PositionFactory factory) { this.positionFactory = factory; } /** * Takes a string containing well known text geometry description and wraps it in a Reader which * is then passed on to parseWKT for handling. * * @param text A string containing the well known text to be parsed. * @return Geometry indicated by text (as created with current factories) */ public Geometry parse(String text) throws ParseException { return read(new StringReader(text)); } /** * Reads a Well-Known Text representation of a 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 = new StreamTokenizer(reader); setUpTokenizer(tokenizer); try { return readGeometryTaggedText(tokenizer); } catch (IOException e) { throw new ParseException(e.toString(), tokenizer.lineno()); } } /** * Sets up a {@link StreamTokenizer} for use in parsing the geometry text. * * @param tokenizer A StreamTokenizer */ private void setUpTokenizer(StreamTokenizer tokenizer) { final int char128 = 128; final int skip32 = 32; final int char255 = 255; // set tokenizer to NOT parse numbers tokenizer.resetSyntax(); tokenizer.wordChars('a', 'z'); tokenizer.wordChars('A', 'Z'); tokenizer.wordChars(char128 + skip32, char255); tokenizer.wordChars('0', '9'); tokenizer.wordChars('-', '-'); tokenizer.wordChars('+', '+'); tokenizer.wordChars('.', '.'); tokenizer.whitespaceChars(0, ' '); tokenizer.commentChar('#'); } /** * 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 Object of the correct type for the next item 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(StreamTokenizer tokenizer) throws IOException, ParseException { String type = getNextWord(tokenizer); if (type.equals("POINT")) { return readPointText(tokenizer); } else if (type.equalsIgnoreCase("LINESTRING")) { return readLineStringText(tokenizer); } else if (type.equalsIgnoreCase("LINEARRING")) { return readLinearRingText(tokenizer); } else if (type.equalsIgnoreCase("POLYGON")) { return readPolygonText(tokenizer); } else if (type.equalsIgnoreCase("MULTIPOINT")) { return readMultiPointText(tokenizer); } else if (type.equalsIgnoreCase("MULTIPOLYGON")) { return readMultiPolygonText(tokenizer); } else if (type.equalsIgnoreCase("GEOMETRYCOLLECTION")) { return readGeometryCollectionText(tokenizer); } else if (type.equalsIgnoreCase("MULTILINESTRING")) { return readMultiLineStringText(tokenizer); } throw new ParseException("Unknown geometry type: " + type, tokenizer.lineno()); } /** * Returns a list of DirectPosition objects which it read from the StreamTokenizer * * @return a List\ */ private List getCoordinates(StreamTokenizer tokenizer) throws IOException, ParseException { String nextToken = getNextEmptyOrOpener(tokenizer); List coordinates = new ArrayList(); if (!nextToken.equals(EMPTY)) { coordinates.add(getPreciseCoordinate(tokenizer)); nextToken = getNextCloserOrComma(tokenizer); while (nextToken.equals(COMMA)) { coordinates.add(getPreciseCoordinate(tokenizer)); nextToken = getNextCloserOrComma(tokenizer); } } return coordinates; } /** * Parse a single coordinate from a StreamTokenizer * * @return a single DirectPosition */ private DirectPosition getPreciseCoordinate(StreamTokenizer tokenizer) throws IOException, ParseException { DirectPosition pos = positionFactory.createDirectPosition(); pos.setOrdinate(0, getNextNumber(tokenizer)); pos.setOrdinate(1, getNextNumber(tokenizer)); if (isNumberNext(tokenizer)) { pos.setOrdinate(1, getNextNumber(tokenizer)); } return pos; } private boolean isNumberNext(StreamTokenizer tokenizer) 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. * * @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(StreamTokenizer tokenizer) throws IOException, ParseException { int type = tokenizer.nextToken(); switch (type) { case StreamTokenizer.TT_WORD: { try { return Double.parseDouble(tokenizer.sval); } catch (NumberFormatException ex) { throw new ParseException( "Invalid number: " + tokenizer.sval, tokenizer.lineno()); } } default: } parseError("number", tokenizer); 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(StreamTokenizer tokenizer) throws IOException, ParseException { String nextWord = getNextWord(tokenizer); if (nextWord.equals(EMPTY) || nextWord.equals(L_PAREN)) { return nextWord; } parseError(EMPTY + " or " + L_PAREN, tokenizer); 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(StreamTokenizer tokenizer) throws IOException, ParseException { String nextWord = getNextWord(tokenizer); if (nextWord.equals(COMMA) || nextWord.equals(R_PAREN)) { return nextWord; } parseError(COMMA + " or " + R_PAREN, tokenizer); 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(StreamTokenizer tokenizer) throws IOException, ParseException { String nextWord = getNextWord(tokenizer); if (nextWord.equals(R_PAREN)) { return nextWord; } parseError(R_PAREN, tokenizer); 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(StreamTokenizer tokenizer) throws IOException, ParseException { int type = tokenizer.nextToken(); String value; switch (type) { case StreamTokenizer.TT_WORD: String word = tokenizer.sval; if (word.equalsIgnoreCase(EMPTY)) { value = EMPTY; } value = word; break; case '(': value = L_PAREN; break; case ')': value = R_PAREN; break; case ',': value = COMMA; break; default: parseError("word", tokenizer); value = null; break; } return value; } /** * Throws a formatted ParseException for the current token. * * @param expected a description of what was expected */ private void parseError(String expected, StreamTokenizer tokenizer) throws ParseException { String tokenStr = tokenString(tokenizer); throw new ParseException("Expected " + expected + " but found " + tokenStr, 0); } /** * Gets a description of the current token * * @return a description of the current token */ private 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 + "'"; default: } return "'" + (char) tokenizer.ttype + "'"; } /** * 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) throws IOException, ParseException { String nextToken = getNextEmptyOrOpener(tokenizer); if (nextToken.equals(EMPTY)) { return primitiveFactory.createPoint(new double[2]); } Point point = primitiveFactory.createPoint(getPreciseCoordinate(tokenizer)); getNextCloser(tokenizer); 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 Curve readLineStringText(StreamTokenizer tokenizer) throws IOException, ParseException { List coordList = getCoordinates(tokenizer); LineString lineString = geometryFactory.createLineString(coordList); List curveSegmentList = Collections.singletonList(lineString); Curve curve = primitiveFactory.createCurve(curveSegmentList); return curve; } /** * Creates a Curve 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 Curve 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 Curve do not form a * closed linestring, or if an unexpected token was encountered */ private Curve readLinearRingText(StreamTokenizer tokenizer) throws IOException, ParseException { List coordList = getCoordinates(tokenizer); LineString lineString = geometryFactory.createLineString(coordList); List curveSegmentList = Collections.singletonList(lineString); Curve curve = primitiveFactory.createCurve(curveSegmentList); return curve; // List curveList = Collections.singletonList(curve); // return primitiveFactory.createRing(curveList); } /** * Creates a SurfaceBoundary 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 Surface specified by the vertices 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 Surface readPolygonText(StreamTokenizer tokenizer) throws IOException, ParseException { String nextToken = getNextEmptyOrOpener(tokenizer); if (nextToken.equals(EMPTY)) { return null; } Curve curve = readLinearRingText(tokenizer); List curveList = Collections.singletonList(curve); Ring shell = primitiveFactory.createRing(curveList); // Ring shell = readLinearRingText(tokenizer); List holes = new ArrayList(); nextToken = getNextCloserOrComma(tokenizer); while (nextToken.equals(COMMA)) { Curve holecurve = readLinearRingText(tokenizer); List holeList = Collections.singletonList(holecurve); Ring hole = primitiveFactory.createRing(holeList); // Ring hole = readLinearRingText(tokenizer); holes.add(hole); nextToken = getNextCloserOrComma(tokenizer); } SurfaceBoundary sb = primitiveFactory.createSurfaceBoundary(shell, holes); return primitiveFactory.createSurface(sb); } /** * Creates a {@code MultiPrimitive} using the next token in the stream. * * @param tokenizer tokenizer on top of a stream of text in Well-known Text format. The next * tokens must form a <Polygon Text>. * @return a MultiPrimitive 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 MultiPrimitive readMultiPolygonText(StreamTokenizer tokenizer) throws IOException, ParseException { String nextToken = getNextEmptyOrOpener(tokenizer); if (nextToken.equals(EMPTY)) { return null; } MultiPrimitive multi = geometryFactory.createMultiPrimitive(); Surface surface = readPolygonText(tokenizer); // multi.getElements().add(surface); Set elements = multi.getElements(); elements.add(surface); nextToken = getNextCloserOrComma(tokenizer); while (nextToken.equals(COMMA)) { surface = readPolygonText(tokenizer); // multi.getElements().add(surface); elements.add(surface); nextToken = getNextCloserOrComma(tokenizer); } return multi; } /** * Creates a {@code MultiPrimitive} using the next token in the stream. * * @param tokenizer tokenizer on top of a stream of text in Well-known Text format. The next * tokens must form a <Point Text>. * @return a MultiPrimitive 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 MultiPrimitive readMultiPointText(StreamTokenizer tokenizer) throws IOException, ParseException { String nextToken = getNextEmptyOrOpener(tokenizer); if (nextToken.equals(EMPTY)) { return null; } MultiPrimitive multi = geometryFactory.createMultiPrimitive(); Point point = primitiveFactory.createPoint(getPreciseCoordinate(tokenizer)); // multi.getElements().add(point); Set elements = multi.getElements(); elements.add(point); nextToken = getNextCloserOrComma(tokenizer); while (nextToken.equals(COMMA)) { point = primitiveFactory.createPoint(getPreciseCoordinate(tokenizer)); // multi.getElements().add(point); elements.add(point); nextToken = getNextCloserOrComma(tokenizer); } return multi; } /** * Creates a {@code MultiPrimitive} out of a GEOMETRYCOLLECCTION specifier. * * @param tokenizer tokenizer on top of a stream of text in Well-known Text format. * @return a MultiPrimitive specified by the next tokens in the stream * @throws IOException if an I/O error occurs */ private MultiPrimitive readGeometryCollectionText(StreamTokenizer tokenizer) throws IOException, ParseException { String nextToken = getNextEmptyOrOpener(tokenizer); if (nextToken.equals(EMPTY)) { return null; } MultiPrimitive multi = geometryFactory.createMultiPrimitive(); Geometry geom = readGeometryTaggedText(tokenizer); // multi.getElements().add(geom); Set elements = multi.getElements(); elements.add(geom); nextToken = getNextCloserOrComma(tokenizer); while (nextToken.equals(COMMA)) { geom = readGeometryTaggedText(tokenizer); // multi.getElements().add(geom); elements.add(geom); nextToken = getNextCloserOrComma(tokenizer); } return multi; } /** * Creates a {@code MultiPrimitive} out of a MULTILINESTRING specifier * * @param tokenizer tokenizer on top of a stream of text in Well-known Text format. * @return a MultiPrimitive specified by the next tokens in the stream * @throws IOException if an I/O error occurs */ private MultiPrimitive readMultiLineStringText(StreamTokenizer tokenizer) throws IOException, ParseException { String nextToken = getNextEmptyOrOpener(tokenizer); if (nextToken.equals(EMPTY)) { return null; } MultiPrimitive multi = geometryFactory.createMultiPrimitive(); Curve curve = readLineStringText(tokenizer); // multi.getElements().add(curve); Set elements = multi.getElements(); elements.add(curve); nextToken = getNextCloserOrComma(tokenizer); while (nextToken.equals(COMMA)) { curve = readLineStringText(tokenizer); // multi.getElements().add(curve); elements.add(curve); nextToken = getNextCloserOrComma(tokenizer); } return multi; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy