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

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

Go to download

The main module contains the GeoTools public interfaces that are used by other GeoTools modules (and GeoTools applications). Where possible we make use industry standard terms as provided by OGC and ISO standards. The formal GeoTools public api consists of gt-metadata, jts and the gt-main module. The main module contains the default implementations that are available provided to other GeoTools modules using our factory system. Factories are obtained from an appropriate FactoryFinder, giving applications a chance configure the factory used using the Factory Hints facilities. FilterFactory ff = CommonFactoryFinder.getFilterFactory(); Expression expr = ff.add( expression1, expression2 ); If you find yourself using implementation specific classes chances are you doing it wrong: Expression expr = new AddImpl( expression1, expressiom2 );

The newest version!
/*
 *    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 - 2024 Weber Informatics LLC | Privacy Policy