HHH-6511 - Geolatte WKT decoder replaces custom EWKT parser.

This commit is contained in:
Karel Maesen 2012-10-12 22:56:35 +02:00 committed by Steve Ebersole
parent 4b7c319cc0
commit b4b626c398
4 changed files with 26 additions and 916 deletions

View File

@ -27,8 +27,10 @@ import javax.persistence.Table;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.ParseException;
import org.geolatte.geom.codec.Wkt;
import org.geolatte.geom.codec.WktDecoder;
import org.geolatte.geom.jts.JTS;
import org.hibernate.spatial.testing.EWKTReader;
import org.hibernate.spatial.testing.TestDataElement;
/**
@ -71,10 +73,10 @@ public class GeomEntity {
}
public static GeomEntity createFrom(TestDataElement element) throws ParseException {
EWKTReader reader = new EWKTReader();
WktDecoder<org.geolatte.geom.Geometry> decoder = Wkt.newWktDecoder( Wkt.Dialect.POSTGIS_EWKT_1 );
Geometry geom = JTS.to( decoder.decode( element.wkt ) );
GeomEntity result = new GeomEntity();
result.setId( element.id );
Geometry geom = reader.read( element.wkt );
geom.setSRID( element.srid );
result.setGeom( geom );
result.setType( element.type );

View File

@ -38,8 +38,11 @@ import java.util.Properties;
import javax.sql.DataSource;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.ParseException;
import org.apache.commons.dbcp.BasicDataSource;
import org.geolatte.geom.codec.Wkt;
import org.geolatte.geom.codec.WktDecodeException;
import org.geolatte.geom.codec.WktDecoder;
import org.geolatte.geom.jts.JTS;
import org.hibernate.spatial.Log;
import org.hibernate.spatial.LogFactory;
@ -356,13 +359,13 @@ public class DataSourceUtils {
*/
public Map<Integer, Geometry> expectedGeoms(String type, TestData testData) {
Map<Integer, Geometry> result = new HashMap<Integer, Geometry>();
EWKTReader parser = new EWKTReader();
WktDecoder<org.geolatte.geom.Geometry> decoder = Wkt.newWktDecoder();
for ( TestDataElement testDataElement : testData ) {
if ( testDataElement.type.equalsIgnoreCase( type ) ) {
try {
result.put( testDataElement.id, parser.read( testDataElement.wkt ) );
result.put( testDataElement.id, JTS.to( decoder.decode( testDataElement.wkt ) ) );
}
catch ( ParseException e ) {
catch ( WktDecodeException e ) {
System.out
.println(
String.format(

View File

@ -1,907 +0,0 @@
/*
* This file is part of Hibernate Spatial, an extension to the
* hibernate ORM solution for spatial (geographic) data.
*
* Copyright © 2007-2012 Geovise BVBA
*
* 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
*/
package org.hibernate.spatial.testing;
import java.io.IOException;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.util.ArrayList;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.util.Assert;
import org.hibernate.spatial.jts.mgeom.MCoordinate;
import org.hibernate.spatial.jts.mgeom.MGeometryFactory;
import org.hibernate.spatial.jts.mgeom.MLineString;
/**
* Converts a geometry in EWKT to a JTS-Geometry.
* <p/>
* <code>EWKTReader</code> supports
* extracting <code>Geometry</code> objects from either {@link java.io.Reader}s or
* {@link String}s. This allows it to function as a parser to read <code>Geometry</code>
* objects from text blocks embedded in other data formats (e.g. XML). <P>
* <p/>
* A <code>WKTReader</code> is parameterized by a <code>GeometryFactory</code>,
* to allow it to create <code>Geometry</code> objects of the appropriate
* implementation. In particular, the <code>GeometryFactory</code>
* determines the <code>PrecisionModel</code> and <code>SRID</code> that is
* used. <P>
* <p/>
* The <code>WKTReader</code> converts all input numbers to the precise
* internal representation.
* <p/>
* <h3>Notes:</h3>
* <ul>
* <li>The reader supports non-standard "LINEARRING" tags.
* <li>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).
* </ul>
* <p/>
* <h3>Syntax</h3>
* 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.)
* <p/>
* <p/>
* <blockquote><pre>
* <i>WKTGeometry:</i> one of<i>
* <p/>
* WKTPoint WKTLineString WKTLinearRing WKTPolygon
* WKTMultiPoint WKTMultiLineString WKTMultiPolygon
* WKTGeometryCollection</i>
* <p/>
* <i>WKTPoint:</i> <b>POINT ( </b><i>Coordinate</i> <b>)</b>
* <p/>
* <i>WKTLineString:</i> <b>LINESTRING</b> <i>CoordinateSequence</i>
* <p/>
* <i>WKTLinearRing:</i> <b>LINEARRING</b> <i>CoordinateSequence</i>
* <p/>
* <i>WKTPolygon:</i> <b>POLYGON</b> <i>CoordinateSequenceList</i>
* <p/>
* <i>WKTMultiPoint:</i> <b>MULTIPOINT</b> <i>CoordinateSequence</i>
* <p/>
* <i>WKTMultiLineString:</i> <b>MULTILINESTRING</b> <i>CoordinateSequenceList</i>
* <p/>
* <i>WKTMultiPolygon:</i>
* <b>MULTIPOLYGON (</b> <i>CoordinateSequenceList {</i> , <i>CoordinateSequenceList }</i> <b>)</b>
* <p/>
* <i>WKTGeometryCollection: </i>
* <b>GEOMETRYCOLLECTION (</b> <i>WKTGeometry {</i> , <i>WKTGeometry }</i> <b>)</b>
* <p/>
* <i>CoordinateSequenceList:</i>
* <b>(</b> <i>CoordinateSequence {</i> <b>,</b> <i>CoordinateSequence }</i> <b>)</b>
* <p/>
* <i>CoordinateSequence:</i>
* <b>(</b> <i>Coordinate {</i> , <i>Coordinate }</i> <b>)</b>
* <p/>
* <i>Coordinate:
* Number Number Number<sub>opt</sub></i>
* <p/>
* <i>Number:</i> A Java-style floating-point number
* <p/>
* </pre></blockquote>
*
* @see WKTWriter
*/
public class EWKTReader {
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 EQUALS = "=";
private static final String SEMICOLON = ";";
private GeometryFactory geometryFactory;
private PrecisionModel precisionModel;
private StreamTokenizer tokenizer;
private int dimension = -1;
private Boolean hasM = null;
/**
* Creates a reader that creates objects using the default {@link GeometryFactory}.
*/
public EWKTReader() {
this( new MGeometryFactory() );
}
/**
* Creates a reader that creates objects using the given
* {@link GeometryFactory}.
*
* @param geometryFactory the factory used to create <code>Geometry</code>s.
*/
public EWKTReader(GeometryFactory geometryFactory) {
this.geometryFactory = geometryFactory;
precisionModel = geometryFactory.getPrecisionModel();
}
/**
* Reads a Well-Known Text representation of a {@link com.vividsolutions.jts.geom.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 <code>Geometry</code> specified by <code>wellKnownText</code>
*
* @throws com.vividsolutions.jts.io.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 java.io.Reader}.
*
* @param reader a Reader which will return a <Geometry Tagged Text>
* string (see the OpenGIS Simple Features Specification)
*
* @return a <code>Geometry</code> read from <code>reader</code>
*
* @throws ParseException if a parsing problem occurs
*/
public Geometry read(Reader reader) throws ParseException {
try {
synchronized ( this ) {
if ( this.tokenizer != null ) {
throw new RuntimeException( "EWKT-Reader is already in use." );
}
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( '#' );
this.hasM = null;
this.dimension = -1;
return readGeometryTaggedText();
}
catch ( IOException e ) {
throw new ParseException( e.toString() );
}
finally {
this.tokenizer = null;
}
}
/**
* Returns the next array of <code>Coordinate</code>s 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 <code>Coordinate</code>s 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 MCoordinate[] getCoordinates()
throws IOException, ParseException {
String nextToken = getNextEmptyOrOpener();
if ( nextToken.equals( EMPTY ) ) {
return new MCoordinate[] { };
}
ArrayList coordinates = new ArrayList();
coordinates.add( getPreciseCoordinate() );
nextToken = getNextCloserOrComma();
while ( nextToken.equals( COMMA ) ) {
coordinates.add( getPreciseCoordinate() );
nextToken = getNextCloserOrComma();
}
return (MCoordinate[]) coordinates.toArray( new MCoordinate[coordinates.size()] );
}
/**
* gets the next Coordinate and checks dimension
*
* @return
*
* @throws IOException
* @throws ParseException
*/
private Coordinate getPreciseCoordinate()
throws IOException, ParseException {
MCoordinate coord = new MCoordinate();
coord.x = getNextNumber();
coord.y = getNextNumber();
Double thirdOrdinateValue = null;
Double fourthOrdinateValue = null;
if ( this.dimension == 3 ) {
thirdOrdinateValue = getNextNumber();
}
else if ( this.dimension == 4 ) {
thirdOrdinateValue = getNextNumber();
fourthOrdinateValue = getNextNumber();
}
else if ( this.dimension < 0 ) {
if ( isNumberNext() ) {
thirdOrdinateValue = getNextNumber();
}
if ( isNumberNext() ) {
fourthOrdinateValue = getNextNumber();
}
if ( fourthOrdinateValue != null ) {
this.dimension = 4;
setHasM( true );
}
else if ( thirdOrdinateValue != null ) {
this.dimension = 3;
setHasM( Boolean.TRUE.equals( this.hasM ) );
}
else {
this.dimension = 2;
setHasM( false );
}
}
switch ( this.dimension ) {
case 2:
break;
case 3:
if ( this.hasM ) {
coord.m = thirdOrdinateValue;
}
else {
coord.z = thirdOrdinateValue;
}
break;
case 4:
if ( this.hasM ) {
coord.z = thirdOrdinateValue;
coord.m = fourthOrdinateValue;
}
else {
throw new ParseException( "Unsupported geometry dimension." );
}
break;
default:
throw new ParseException( "Unsupported geometry dimension." );
}
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.
*
* @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: {
try {
return Double.parseDouble( tokenizer.sval );
}
catch ( NumberFormatException ex ) {
throw new ParseException( "Invalid number: " + tokenizer.sval );
}
}
}
parseError( "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;
}
parseError( 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;
}
parseError( 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;
}
parseError( 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 int getSRID() throws IOException, ParseException {
if ( !getNextWord().equals( EQUALS ) ) {
parseError( EQUALS );
return 0;
}
int srid = Integer.parseInt( getNextWord() );
if ( !getNextWord().equals( SEMICOLON ) ) {
parseError( SEMICOLON );
return 0;
}
return srid;
}
/**
* 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;
case '=':
return EQUALS;
case ';':
return SEMICOLON;
}
parseError( "word" );
return null;
}
/**
* Throws a formatted ParseException for the current token.
*
* @param expected a description of what was expected
*
* @throws ParseException
* @throws com.vividsolutions.jts.util.AssertionFailedException if an invalid token is encountered
*/
private void parseError(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();
throw new ParseException( "Expected " + expected + " but found " + tokenStr );
}
/**
* 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 "<NUMBER>";
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 <code>Geometry</code> 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 &lt;Geometry Tagged Text&gt;.
*
* @return a <code>Geometry</code> specified by the next token
* in the stream
*
* @throws ParseException if the coordinates used to create a <code>Polygon</code>
* 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;
Geometry geom;
int srid = geometryFactory.getSRID();
try {
String firstWord = getNextWord();
if ( "SRID".equals( firstWord ) ) {
srid = getSRID();
type = getNextWord();
}
else {
type = firstWord;
}
}
catch ( IOException e ) {
return null;
}
catch ( ParseException e ) {
return null;
}
if ( type.equals( "POINT" ) ) {
geom = readPointText();
}
else if ( type.equals( "POINTM" ) ) {
setHasM( true );
geom = readPointText();
}
else if ( type.equalsIgnoreCase( "LINESTRING" ) ) {
geom = readLineStringText();
}
else if ( type.equalsIgnoreCase( "LINESTRINGM" ) ) {
setHasM( true );
geom = readLineStringText();
}
else if ( type.equalsIgnoreCase( "LINEARRING" ) ) {
geom = readLinearRingText();
}
else if ( type.equalsIgnoreCase( "LINEARRINGM" ) ) {
setHasM( true );
geom = readLinearRingText();
}
else if ( type.equalsIgnoreCase( "POLYGON" ) ) {
geom = readPolygonText();
}
else if ( type.equalsIgnoreCase( "POLYGONM" ) ) {
//setHasM(true);
//geom = readPolygonText();
throw new RuntimeException( "PolygonM is not supported." );
}
else if ( type.equalsIgnoreCase( "MULTIPOINT" ) ) {
geom = readMultiPointText();
}
else if ( type.equalsIgnoreCase( "MULTIPOINTM" ) ) {
setHasM( true );
geom = readMultiPointText();
}
else if ( type.equalsIgnoreCase( "MULTILINESTRING" ) ) {
geom = readMultiLineStringText();
}
else if ( type.equalsIgnoreCase( "MULTILINESTRINGM" ) ) {
setHasM( true );
geom = readMultiLineStringText();
}
else if ( type.equalsIgnoreCase( "MULTIPOLYGON" ) ) {
geom = readMultiPolygonText();
}
else if ( type.equalsIgnoreCase( "MULTIPOLYGONM" ) ) {
//setHasM(true);
//geom = readMultiPolygonText();
throw new RuntimeException( "MultiPolygonM is not supported." );
}
else if ( type.equalsIgnoreCase( "GEOMETRYCOLLECTION" ) ) {
geom = readGeometryCollectionText();
}
else if ( type.equalsIgnoreCase( "GEOMETRYCOLLECTIONM" ) ) {
setHasM( true );
geom = readGeometryCollectionText();
}
else {
throw new ParseException( "Unknown geometry type: " + type );
}
geom.setSRID( srid );
return geom;
}
/**
* m-values sicherstellen
*
* @throws ParseException
*/
private void setHasM(boolean hasM) throws ParseException {
if ( this.hasM == null ) {
this.hasM = hasM;
}
else if ( this.hasM != hasM ) {
throw new ParseException( "Inkonsistent use of m-values." );
}
}
/**
* Creates a <code>Point</code> 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 &lt;Point Text&gt;.
*
* @return a <code>Point</code> 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 <code>LineString</code> 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 &lt;LineString Text&gt;.
*
* @return a <code>LineString</code> 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 {
MCoordinate[] coords = getCoordinates();
if ( this.hasM != null && this.hasM ) {
return ( (MGeometryFactory) geometryFactory ).createMLineString( coords );
}
else {
return geometryFactory.createLineString( coords );
}
}
/**
* Creates a <code>LinearRing</code> 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 &lt;LineString Text&gt;.
*
* @return a <code>LinearRing</code> 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 <code>LinearRing</code>
* do not form a closed linestring, or if an unexpected token was
* encountered
*/
private LinearRing readLinearRingText()
throws IOException, ParseException {
MCoordinate[] coords = getCoordinates();
if ( this.hasM ) {
throw new RuntimeException( "LinearRingM not supported." );
}
else {
return geometryFactory.createLinearRing( coords );
}
}
/**
* Creates a <code>MultiPoint</code> 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 &lt;MultiPoint Text&gt;.
*
* @return a <code>MultiPoint</code> 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 {
MCoordinate[] coords = getCoordinates();
Point[] pts = toPoints( coords );
return geometryFactory.createMultiPoint( pts );
}
/**
* Creates an array of <code>Point</code>s having the given <code>Coordinate</code>
* s.
*
* @param coordinates the <code>Coordinate</code>s with which to create the
* <code>Point</code>s
*
* @return <code>Point</code>s created using this <code>WKTReader</code>
* s <code>GeometryFactory</code>
*/
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 <code>Polygon</code> using the next token in the stream.
*
* @param hasM
* @param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next tokens must form a &lt;Polygon Text&gt;.
*
* @return a <code>Polygon</code> specified by the next token
* in the stream
*
* @throws ParseException if the coordinates used to create the <code>Polygon</code>
* 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 {
// PolygonM is not supported
setHasM( false );
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 <code>MultiLineString</code> using the next token in the stream.
*
* @param hasM
* @param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next tokens must form a &lt;MultiLineString Text&gt;.
*
* @return a <code>MultiLineString</code> 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 {
ArrayList lineStrings = new ArrayList();
String nextToken = getNextEmptyOrOpener();
if ( nextToken.equals( EMPTY ) ) {
// No Coordinates for LineString
}
else {
LineString lineString = readLineStringText();
lineStrings.add( lineString );
nextToken = getNextCloserOrComma();
while ( nextToken.equals( COMMA ) ) {
lineString = readLineStringText();
lineStrings.add( lineString );
nextToken = getNextCloserOrComma();
}
}
if ( this.hasM != null && this.hasM == true ) {
MLineString[] mlines = (MLineString[]) lineStrings.toArray( new MLineString[lineStrings.size()] );
return ( (MGeometryFactory) geometryFactory ).createMultiMLineString( mlines );
}
else {
setHasM( false );
LineString[] lines = (LineString[]) lineStrings.toArray( new LineString[lineStrings.size()] );
return geometryFactory.createMultiLineString( lines );
}
}
/**
* Creates a <code>MultiPolygon</code> using the next token in the stream.
*
* @param hasM
* @param tokenizer tokenizer over a stream of text in Well-known Text
* format. The next tokens must form a &lt;MultiPolygon Text&gt;.
*
* @return a <code>MultiPolygon</code> specified by the next
* token in the stream, or if if the coordinates used to create the
* <code>Polygon</code> 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 {
// MultiPolygonM is not supported
setHasM( false );
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 <code>GeometryCollection</code> 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 &lt;GeometryCollection Text&gt;.
*
* @return a <code>GeometryCollection</code> specified by the
* next token in the stream
*
* @throws ParseException if the coordinates used to create a <code>Polygon</code>
* 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 ) );
}
}

View File

@ -40,7 +40,7 @@ public class GeometryEquality {
return geom2 == null;
}
if ( geom1.isEmpty() ) {
return geom2.isEmpty() && geom1.getSRID() == geom2.getSRID();
return geom2.isEmpty() && equalSRID( geom1, geom2 );
}
if ( geom1 instanceof GeometryCollection ) {
if ( !( geom2 instanceof GeometryCollection ) ) {
@ -62,6 +62,18 @@ public class GeometryEquality {
}
}
/**
* Two geometries are equal iff both have the same SRID, or both are unknown (i.e. a SRID of 0 or -1).
*
* @param geom1
* @param geom2
* @return
*/
private boolean equalSRID(Geometry geom1, Geometry geom2) {
return geom1.getSRID() == geom2.getSRID() ||
( geom1.getSRID() <1 && geom2.getSRID() < 1);
}
/**
* Test whether two geometries, not of type GeometryCollection are equal.
*
@ -72,7 +84,7 @@ public class GeometryEquality {
*/
protected boolean testSimpleGeometryEquality(Geometry geom1, Geometry geom2) {
//return geom1.equals(geom2);
return testTypeAndVertexEquality( geom1, geom2 ) && geom1.getSRID() == geom2.getSRID();
return testTypeAndVertexEquality( geom1, geom2 ) && equalSRID( geom1, geom2 );
}
protected boolean testTypeAndVertexEquality(Geometry geom1, Geometry geom2) {