/* * $Id: GeometryParser.java,v 1.1 2007-02-27 12:45:29 eugen Exp $ * * Copyright (c) 2003 Brockmann Consult GmbH. All right reserved. * http://www.brockmann-consult.de */ package com.bc.util.geom; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.io.IOException; import java.io.StreamTokenizer; import java.io.StringReader; import java.text.ParseException; public class GeometryParser { private StreamTokenizer tokenizer; public GeometryParser() { } /** * Creates geometry from the given well-known-text (WKT) representation. For details about the WKT, refer to the * OpenGIS SimpleFeatures for SQL Specification document. * * @param wkt the geometry well-known-text representation * * @return the geometry * * @throws ParseException if a parse fail occurs */ public Geometry parseWKT(String wkt) throws ParseException { if (wkt == null) { throw new IllegalArgumentException("wkt is null"); } initTokenizer(wkt); Geometry geometry = null; try { geometry = parseGeometryTaggedText(); } catch (IOException e) { throw new IllegalStateException(e.getMessage()); } finally { disposeTokenizer(); } return geometry; } private Geometry parseGeometryTaggedText() throws IOException, ParseException { Geometry geometry = parsePointTaggedText(); if (geometry == null) { geometry = parseLineStringTaggedText(); } if (geometry == null) { geometry = parsePolygonTaggedText(); } if (geometry == null) { geometry = parseMultiPointTaggedText(); } if (geometry == null) { geometry = parseMultiLineStringTaggedText(); } if (geometry == null) { geometry = parseMultiPolygonTaggedText(); } if (geometry == null) { geometry = parseGeometryCollectionTaggedText(); } if (geometry == null) { fail("geometry type name expected"); } return geometry; } private PointGeometry parsePointTaggedText() throws IOException, ParseException { int tt = tokenizer.nextToken(); if (tt == StreamTokenizer.TT_WORD && tokenizer.sval.equalsIgnoreCase(Geometry.POINT)) { return parsePointBody(); } tokenizer.pushBack(); return null; } private Geometry parseLineStringTaggedText() throws IOException, ParseException { int tt = tokenizer.nextToken(); if (tt == StreamTokenizer.TT_WORD && tokenizer.sval.equalsIgnoreCase(Geometry.LINESTRING)) { return parseLineStringBody(); } tokenizer.pushBack(); return null; } private Geometry parsePolygonTaggedText() throws IOException, ParseException { int tt = tokenizer.nextToken(); if (tt == StreamTokenizer.TT_WORD && tokenizer.sval.equalsIgnoreCase(Geometry.POLYGON)) { return parsePolygonBody(); } tokenizer.pushBack(); return null; } private Geometry parseMultiPointTaggedText() throws IOException, ParseException { int tt = tokenizer.nextToken(); if (tt == StreamTokenizer.TT_WORD && tokenizer.sval.equalsIgnoreCase(Geometry.MULTIPOINT)) { return parseMultiPointBody(); } tokenizer.pushBack(); return null; } private Geometry parseMultiLineStringTaggedText() throws IOException, ParseException { int tt = tokenizer.nextToken(); if (tt == StreamTokenizer.TT_WORD && tokenizer.sval.equalsIgnoreCase(Geometry.MULTILINESTRING)) { return parseMultiLineStringBody(); } tokenizer.pushBack(); return null; } private Geometry parseMultiPolygonTaggedText() throws IOException, ParseException { int tt = tokenizer.nextToken(); if (tt == StreamTokenizer.TT_WORD && tokenizer.sval.equalsIgnoreCase(Geometry.MULTIPOLYGON)) { return parseMultiPolygonBody(); } tokenizer.pushBack(); return null; } private Geometry parseGeometryCollectionTaggedText() throws IOException, ParseException { int tt = tokenizer.nextToken(); if (tt == StreamTokenizer.TT_WORD && tokenizer.sval.equalsIgnoreCase(Geometry.GEOMETRYCOLLECTION)) { return parseGeometryCollectionBody(); } tokenizer.pushBack(); return null; } private PointGeometry parsePointBody() throws IOException, ParseException { final Point2D point = new Point2D.Double(); int tt = tokenizer.nextToken(); if (tt != StreamTokenizer.TT_EOF) { parseListStart(tt); parsePoint(point); tt = tokenizer.nextToken(); if (tt != ')') { tokenizer.pushBack(); fail("')' expected"); } } return new PointGeometry(point.getX(), point.getY()); } private LineStringGeometry parseLineStringBody() throws IOException, ParseException { return parseLineStringBody(false); } private LineStringGeometry parseLineStringBody(boolean autoClose) throws IOException, ParseException { final GeneralPath gp = new GeneralPath(); int tt = tokenizer.nextToken(); if (tt != StreamTokenizer.TT_EOF) { parseListStart(tt); Point2D.Double point = new Point2D.Double(); int numElems = 0; float x0 = 0, y0 = 0; float xi = 0, yi = 0; boolean mustLineTo = false; do { if (mustLineTo) { gp.lineTo(xi, yi); } parsePoint(point); if (numElems == 0) { x0 = (float) point.x; y0 = (float) point.y; gp.moveTo(x0, y0); } else { xi = (float) point.x; yi = (float) point.y; mustLineTo = true; } numElems++; } while (!parseListEnd()); if (autoClose) { // lineTo(xi, yi) only if it is not first point, // because path.closePath() does the job for us if (mustLineTo && (x0 != xi || y0 != yi)) { gp.lineTo(xi, yi); } gp.closePath(); } else if (mustLineTo) { gp.lineTo(xi, yi); } } return new LineStringGeometry(gp); } private PolygonGeometry parsePolygonBody() throws IOException, ParseException { GeneralPath path = new GeneralPath(); ShapeGeometry elem = null; int numElems = 0; int tt = tokenizer.nextToken(); if (tt != StreamTokenizer.TT_EOF) { parseListStart(tt); do { elem = parseLineStringBody(true); path.append(elem.getAsShape(), false); numElems++; } while (!parseListEnd()); } return new PolygonGeometry(numElems == 1 ? elem.getAsShape() : path); } private MultiPointGeometry parseMultiPointBody() throws IOException, ParseException { MultiPointGeometry mp = new MultiPointGeometry(); PointGeometry p; int tt = tokenizer.nextToken(); if (tt != StreamTokenizer.TT_EOF) { parseListStart(tt); do { p = parsePointBody(); mp.addPoint(p); } while (!parseListEnd()); } return mp; } private MultiLineStringGeometry parseMultiLineStringBody() throws IOException, ParseException { MultiLineStringGeometry ml = new MultiLineStringGeometry(); LineStringGeometry l = null; int tt = tokenizer.nextToken(); if (tt != StreamTokenizer.TT_EOF) { parseListStart(tt); do { l = parseLineStringBody(); ml.addLineString(l); } while (!parseListEnd()); } return ml; } private MultiPolygonGeometry parseMultiPolygonBody() throws IOException, ParseException { MultiPolygonGeometry mp = new MultiPolygonGeometry(); PolygonGeometry p = null; int tt = tokenizer.nextToken(); if (tt != StreamTokenizer.TT_EOF) { parseListStart(tt); do { p = parsePolygonBody(); mp.addPolygon(p); } while (!parseListEnd()); } return mp; } private GeometryCollection parseGeometryCollectionBody() throws IOException, ParseException { GeometryCollection gc = new GeometryCollection(); Geometry g; int tt = tokenizer.nextToken(); if (tt != StreamTokenizer.TT_EOF) { parseListStart(tt); do { g = parseGeometryTaggedText(); gc.addGeometry(g); } while (parseListEnd()); } return gc; } private void parsePoint(Point2D point) throws IOException, ParseException { final double x = parseDouble(tokenizer, "x-value"); final double y = parseDouble(tokenizer, "y-value"); point.setLocation(x, y); } /** * Private helper method to enable parsing of floating point numbers in "e" notation. This contains the most basic * implementation that seems to be suitable for this kind of operation but will fail on certain unexpected values, * e.g. 1e3b4 where 1 will be the mantissa and 3b4 will be the exponent (which would fail...) * * @param tokenizer * @param valueName * * @return * * @throws ParseException * @throws IOException */ private double parseDouble(StreamTokenizer tokenizer, String valueName) throws ParseException, IOException { int tt = tokenizer.nextToken(); if (tt != StreamTokenizer.TT_NUMBER) { tokenizer.pushBack(); fail(valueName + " expected"); } double result = tokenizer.nval; tt = tokenizer.nextToken(); if (tt == StreamTokenizer.TT_WORD && (tokenizer.sval.startsWith("e") || tokenizer.sval.startsWith("E"))) { result = new Double(result + tokenizer.sval).doubleValue(); } else { tokenizer.pushBack(); } return result; } private void parseListStart(int tt) throws ParseException { if (tt != '(') { tokenizer.pushBack(); fail("'(' expected"); } } private boolean parseListEnd() throws IOException, ParseException { int tt = tokenizer.nextToken(); if (tt == ')') { return true; } else if (tt != ',') { tokenizer.pushBack(); fail("',' or ')' expected"); } return false; } private void initTokenizer(String wkt) { StringReader r = new StringReader(wkt); tokenizer = new StreamTokenizer(r); tokenizer.resetSyntax(); tokenizer.parseNumbers(); tokenizer.whitespaceChars(0, 32); tokenizer.eolIsSignificant(false); tokenizer.wordChars('a', 'z'); tokenizer.wordChars('A', 'Z'); tokenizer.wordChars('_', '_'); tokenizer.quoteChar('"'); } private void disposeTokenizer() { tokenizer = null; } private void fail(String message) throws ParseException { throw new ParseException(message, tokenizer.lineno()); } }