| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
package org.locationtech.jts.io; |
| 13 |
|
| 14 |
|
| 15 |
import java.io.IOException; |
| 16 |
import java.io.Reader; |
| 17 |
import java.io.StreamTokenizer; |
| 18 |
import java.io.StringReader; |
| 19 |
import java.util.ArrayList; |
| 20 |
import java.util.EnumSet; |
| 21 |
import java.util.Locale; |
| 22 |
|
| 23 |
import org.locationtech.jts.geom.Coordinate; |
| 24 |
import org.locationtech.jts.geom.CoordinateSequence; |
| 25 |
import org.locationtech.jts.geom.CoordinateSequenceFactory; |
| 26 |
|
| 27 |
import org.locationtech.jts.geom.Geometry; |
| 28 |
import org.locationtech.jts.geom.GeometryCollection; |
| 29 |
import org.locationtech.jts.geom.GeometryFactory; |
| 30 |
import org.locationtech.jts.geom.LineString; |
| 31 |
import org.locationtech.jts.geom.LinearRing; |
| 32 |
import org.locationtech.jts.geom.MultiLineString; |
| 33 |
import org.locationtech.jts.geom.MultiPoint; |
| 34 |
import org.locationtech.jts.geom.MultiPolygon; |
| 35 |
import org.locationtech.jts.geom.Point; |
| 36 |
import org.locationtech.jts.geom.Polygon; |
| 37 |
import org.locationtech.jts.geom.PrecisionModel; |
| 38 |
import org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory; |
| 39 |
import org.locationtech.jts.util.Assert; |
| 40 |
import org.locationtech.jts.util.AssertionFailedException; |
| 41 |
|
| 42 |
/** |
| 43 |
* Converts a geometry in Well-Known Text format to a {@link Geometry}. |
| 44 |
* <p> |
| 45 |
* <code>WKTReader</code> supports |
| 46 |
* extracting <code>Geometry</code> objects from either {@link Reader}s or |
| 47 |
* {@link String}s. This allows it to function as a parser to read <code>Geometry</code> |
| 48 |
* objects from text blocks embedded in other data formats (e.g. XML). <P> |
| 49 |
* <p> |
| 50 |
* A <code>WKTReader</code> is parameterized by a <code>GeometryFactory</code>, |
| 51 |
* to allow it to create <code>Geometry</code> objects of the appropriate |
| 52 |
* implementation. In particular, the <code>GeometryFactory</code> |
| 53 |
* determines the <code>PrecisionModel</code> and <code>SRID</code> that is |
| 54 |
* used. <P> |
| 55 |
* |
| 56 |
* The <code>WKTReader</code> converts all input numbers to the precise |
| 57 |
* internal representation. |
| 58 |
* |
| 59 |
* <h3>Notes:</h3> |
| 60 |
* <ul> |
| 61 |
* <li>Keywords are case-insensitive. |
| 62 |
* <li>The reader supports non-standard "LINEARRING" tags. |
| 63 |
* <li>The reader uses <tt>Double.parseDouble</tt> to perform the conversion of ASCII |
| 64 |
* numbers to floating point. This means it supports the Java |
| 65 |
* syntax for floating point literals (including scientific notation). |
| 66 |
* </ul> |
| 67 |
* |
| 68 |
* <h3>Syntax</h3> |
| 69 |
* The following syntax specification describes the version of Well-Known Text |
| 70 |
* supported by JTS. |
| 71 |
* (The specification uses a syntax language similar to that used in |
| 72 |
* the C and Java language specifications.) |
| 73 |
* <p> |
| 74 |
* As of version 1.15, JTS can read (but not write) WKT Strings including Z, M or ZM |
| 75 |
* in the name of the geometry type (ex. POINT Z, LINESTRINGZM). |
| 76 |
* Note that it only makes the reader more flexible, but JTS could already read |
| 77 |
* 3D coordinates from WKT String and still can't read 4D coordinates. |
| 78 |
* |
| 79 |
* <blockquote><pre> |
| 80 |
* <i>WKTGeometry:</i> one of<i> |
| 81 |
* |
| 82 |
* WKTPoint WKTLineString WKTLinearRing WKTPolygon |
| 83 |
* WKTMultiPoint WKTMultiLineString WKTMultiPolygon |
| 84 |
* WKTGeometryCollection</i> |
| 85 |
* |
| 86 |
* <i>WKTPoint:</i> <b>POINT</b><i>[Dimension]</i> <b>( </b><i>Coordinate</i> <b>)</b> |
| 87 |
* |
| 88 |
* <i>WKTLineString:</i> <b>LINESTRING</b><i>[Dimension]</i> <i>CoordinateSequence</i> |
| 89 |
* |
| 90 |
* <i>WKTLinearRing:</i> <b>LINEARRING</b><i>[Dimension]</i> <i>CoordinateSequence</i> |
| 91 |
* |
| 92 |
* <i>WKTPolygon:</i> <b>POLYGON</b><i>[Dimension]</i> <i>CoordinateSequenceList</i> |
| 93 |
* |
| 94 |
* <i>WKTMultiPoint:</i> <b>MULTIPOINT</b><i>[Dimension]</i> <i>CoordinateSingletonList</i> |
| 95 |
* |
| 96 |
* <i>WKTMultiLineString:</i> <b>MULTILINESTRING</b><i>[Dimension]</i> <i>CoordinateSequenceList</i> |
| 97 |
* |
| 98 |
* <i>WKTMultiPolygon:</i> |
| 99 |
* <b>MULTIPOLYGON</b><i>[Dimension]</i> <b>(</b> <i>CoordinateSequenceList {</i> , <i>CoordinateSequenceList }</i> <b>)</b> |
| 100 |
* |
| 101 |
* <i>WKTGeometryCollection: </i> |
| 102 |
* <b>GEOMETRYCOLLECTION</b><i>[Dimension]</i> <b> (</b> <i>WKTGeometry {</i> , <i>WKTGeometry }</i> <b>)</b> |
| 103 |
* |
| 104 |
* <i>CoordinateSingletonList:</i> |
| 105 |
* <b>(</b> <i>CoordinateSingleton {</i> <b>,</b> <i>CoordinateSingleton }</i> <b>)</b> |
| 106 |
* | <b>EMPTY</b> |
| 107 |
* |
| 108 |
* <i>CoordinateSingleton:</i> |
| 109 |
* <b>(</b> <i>Coordinate</i> <b>)</b> |
| 110 |
* | <b>EMPTY</b> |
| 111 |
* |
| 112 |
* <i>CoordinateSequenceList:</i> |
| 113 |
* <b>(</b> <i>CoordinateSequence {</i> <b>,</b> <i>CoordinateSequence }</i> <b>)</b> |
| 114 |
* | <b>EMPTY</b> |
| 115 |
* |
| 116 |
* <i>CoordinateSequence:</i> |
| 117 |
* <b>(</b> <i>Coordinate {</i> , <i>Coordinate }</i> <b>)</b> |
| 118 |
* | <b>EMPTY</b> |
| 119 |
* |
| 120 |
* <i>Coordinate: |
| 121 |
* Number Number Number<sub>opt</sub></i> |
| 122 |
* |
| 123 |
* <i>Number:</i> A Java-style floating-point number (including <tt>NaN</tt>, with arbitrary case) |
| 124 |
* |
| 125 |
* <i>Dimension:</i> |
| 126 |
* <b>Z</b>|<b> Z</b>|<b>M</b>|<b> M</b>|<b>ZM</b>|<b> ZM</b> |
| 127 |
* |
| 128 |
* </pre></blockquote> |
| 129 |
* |
| 130 |
* |
| 131 |
*@version 1.7 |
| 132 |
* @see WKTWriter |
| 133 |
*/ |
| 134 |
public class WKTReader |
| 135 |
{ |
| 136 |
private static final String COMMA = ","; |
| 137 |
private static final String L_PAREN = "("; |
| 138 |
private static final String R_PAREN = ")"; |
| 139 |
private static final String NAN_SYMBOL = "NaN"; |
| 140 |
|
| 141 |
private GeometryFactory geometryFactory; |
| 142 |
private CoordinateSequenceFactory csFactory; |
| 143 |
private static CoordinateSequenceFactory csFactoryXYZM = CoordinateArraySequenceFactory.instance(); |
| 144 |
private PrecisionModel precisionModel; |
| 145 |
|
| 146 |
/** |
| 147 |
* Flag indicating that the old notation of coordinates in JTS |
| 148 |
* is supported. |
| 149 |
*/ |
| 150 |
private static final boolean ALLOW_OLD_JTS_COORDINATE_SYNTAX = true; |
| 151 |
private boolean isAllowOldJtsCoordinateSyntax = ALLOW_OLD_JTS_COORDINATE_SYNTAX; |
| 152 |
|
| 153 |
/** |
| 154 |
* Flag indicating that the old notation of MultiPoint coordinates in JTS |
| 155 |
* is supported. |
| 156 |
*/ |
| 157 |
private static final boolean ALLOW_OLD_JTS_MULTIPOINT_SYNTAX = true; |
| 158 |
private boolean isAllowOldJtsMultipointSyntax = ALLOW_OLD_JTS_MULTIPOINT_SYNTAX; |
| 159 |
|
| 160 |
/** |
| 161 |
* Creates a reader that creates objects using the default {@link GeometryFactory}. |
| 162 |
*/ |
| 163 |
public WKTReader() { |
| 164 |
this(new GeometryFactory()); |
| 165 |
} |
| 166 |
|
| 167 |
/** |
| 168 |
* Creates a reader that creates objects using the given |
| 169 |
* {@link GeometryFactory}. |
| 170 |
* |
| 171 |
*@param geometryFactory the factory used to create <code>Geometry</code>s. |
| 172 |
*/ |
| 173 |
public WKTReader(GeometryFactory geometryFactory) { |
| 174 |
this.geometryFactory = geometryFactory; |
| 175 |
this.csFactory = geometryFactory.getCoordinateSequenceFactory(); |
| 176 |
this.precisionModel = geometryFactory.getPrecisionModel(); |
| 177 |
} |
| 178 |
|
| 179 |
/** |
| 180 |
* Sets a flag indicating, that coordinates may have 3 ordinate values even though no Z or M ordinate indicator |
| 181 |
* is present. The default value is {@link #ALLOW_OLD_JTS_COORDINATE_SYNTAX}. |
| 182 |
* |
| 183 |
* @param value a boolean value |
| 184 |
*/ |
| 185 |
public void setIsOldJtsCoordinateSyntaxAllowed(boolean value) { |
| 186 |
isAllowOldJtsCoordinateSyntax = value; |
| 187 |
} |
| 188 |
|
| 189 |
/** |
| 190 |
* Sets a flag indicating, that point coordinates in a MultiPoint geometry must not be enclosed in paren. |
| 191 |
* The default value is {@link #ALLOW_OLD_JTS_MULTIPOINT_SYNTAX} |
| 192 |
* @param value a boolean value |
| 193 |
*/ |
| 194 |
public void setIsOldJtsMultiPointSyntaxAllowed(boolean value) { |
| 195 |
isAllowOldJtsMultipointSyntax = value; |
| 196 |
} |
| 197 |
|
| 198 |
/** |
| 199 |
* Reads a Well-Known Text representation of a {@link Geometry} |
| 200 |
* from a {@link String}. |
| 201 |
* |
| 202 |
* @param wellKnownText |
| 203 |
* one or more <Geometry Tagged Text> strings (see the OpenGIS |
| 204 |
* Simple Features Specification) separated by whitespace |
| 205 |
* @return a <code>Geometry</code> specified by <code>wellKnownText</code> |
| 206 |
* @throws ParseException |
| 207 |
* if a parsing problem occurs |
| 208 |
*/ |
| 209 |
public Geometry read(String wellKnownText) throws ParseException { |
| 210 |
StringReader reader = new StringReader(wellKnownText); |
| 211 |
try { |
| 212 |
return read(reader); |
| 213 |
} |
| 214 |
finally { |
| 215 |
reader.close(); |
| 216 |
} |
| 217 |
} |
| 218 |
|
| 219 |
/** |
| 220 |
* Reads a Well-Known Text representation of a {@link Geometry} |
| 221 |
* from a {@link Reader}. |
| 222 |
* |
| 223 |
*@param reader a Reader which will return a <Geometry Tagged Text> |
| 224 |
* string (see the OpenGIS Simple Features Specification) |
| 225 |
*@return a <code>Geometry</code> read from <code>reader</code> |
| 226 |
*@throws ParseException if a parsing problem occurs |
| 227 |
*/ |
| 228 |
public Geometry read(Reader reader) throws ParseException { |
| 229 |
StreamTokenizer tokenizer = createTokenizer(reader); |
| 230 |
try { |
| 231 |
return readGeometryTaggedText(tokenizer); |
| 232 |
} |
| 233 |
catch (IOException e) { |
| 234 |
throw new ParseException(e.toString()); |
| 235 |
} |
| 236 |
} |
| 237 |
|
| 238 |
/** |
| 239 |
* Utility function to create the tokenizer |
| 240 |
* @param reader a reader |
| 241 |
* |
| 242 |
* @return a WKT Tokenizer. |
| 243 |
*/ |
| 244 |
private static StreamTokenizer createTokenizer(Reader reader) { |
| 245 |
StreamTokenizer tokenizer = new StreamTokenizer(reader); |
| 246 |
|
| 247 |
tokenizer.resetSyntax(); |
| 248 |
tokenizer.wordChars('a', 'z'); |
| 249 |
tokenizer.wordChars('A', 'Z'); |
| 250 |
tokenizer.wordChars(128 + 32, 255); |
| 251 |
tokenizer.wordChars('0', '9'); |
| 252 |
tokenizer.wordChars('-', '-'); |
| 253 |
tokenizer.wordChars('+', '+'); |
| 254 |
tokenizer.wordChars('.', '.'); |
| 255 |
tokenizer.whitespaceChars(0, ' '); |
| 256 |
tokenizer.commentChar('#'); |
| 257 |
|
| 258 |
return tokenizer; |
| 259 |
} |
| 260 |
|
| 261 |
/** |
| 262 |
* Reads a <code>Coordinate</Code> from a stream using the given {@link StreamTokenizer}. |
| 263 |
* <p> |
| 264 |
* All ordinate values are read, but -depending on the {@link CoordinateSequenceFactory} of the |
| 265 |
* underlying {@link GeometryFactory}- not necessarily all can be handled. Those are silently dropped. |
| 266 |
* </p> |
| 267 |
* @param tokenizer the tokenizer to use |
| 268 |
* @param ordinateFlags a bit-mask defining the ordinates to read. |
| 269 |
* @param tryParen a value indicating if a starting {@link #L_PAREN} should be probed. |
| 270 |
* @return a {@link CoordinateSequence} of length 1 containing the read ordinate values |
| 271 |
* |
| 272 |
*@throws IOException if an I/O error occurs |
| 273 |
*@throws ParseException if an unexpected token was encountered |
| 274 |
*/ |
| 275 |
private CoordinateSequence getCoordinate(StreamTokenizer tokenizer, EnumSet<Ordinate> ordinateFlags, boolean tryParen) |
| 276 |
throws IOException, ParseException |
| 277 |
{ |
| 278 |
|
| 279 |
boolean opened = false; |
| 280 |
if (tryParen && isOpenerNext(tokenizer) ) { |
| 281 |
tokenizer.nextToken(); |
| 282 |
opened = true; |
| 283 |
} |
| 284 |
|
| 285 |
|
| 286 |
int offsetM = ordinateFlags.contains(Ordinate.Z) ? 1 : 0; |
| 287 |
CoordinateSequence sequence = csFactory.create(1, toDimension(ordinateFlags), ordinateFlags.contains(Ordinate.M) ? 1 : 0); |
| 288 |
sequence.setOrdinate(0, CoordinateSequence.X, precisionModel.makePrecise(getNextNumber(tokenizer))); |
| 289 |
sequence.setOrdinate(0, CoordinateSequence.Y, precisionModel.makePrecise(getNextNumber(tokenizer))); |
| 290 |
|
| 291 |
|
| 292 |
if (ordinateFlags.contains(Ordinate.Z)) |
| 293 |
sequence.setOrdinate(0, CoordinateSequence.Z, getNextNumber(tokenizer)); |
| 294 |
if (ordinateFlags.contains(Ordinate.M)) |
| 295 |
sequence.setOrdinate(0, CoordinateSequence.Z + offsetM, getNextNumber(tokenizer)); |
| 296 |
|
| 297 |
if (ordinateFlags.size() == 2 && this.isAllowOldJtsCoordinateSyntax && isNumberNext(tokenizer)) { |
| 298 |
sequence.setOrdinate(0, CoordinateSequence.Z, getNextNumber(tokenizer)); |
| 299 |
} |
| 300 |
|
| 301 |
|
| 302 |
if (opened) { |
| 303 |
getNextCloser(tokenizer); |
| 304 |
} |
| 305 |
|
| 306 |
return sequence; |
| 307 |
} |
| 308 |
|
| 309 |
/** |
| 310 |
* Reads a <code>Coordinate</Code> from a stream using the given {@link StreamTokenizer}. |
| 311 |
* <p> |
| 312 |
* All ordinate values are read, but -depending on the {@link CoordinateSequenceFactory} of the |
| 313 |
* underlying {@link GeometryFactory}- not necessarily all can be handled. Those are silently dropped. |
| 314 |
* </p> |
| 315 |
* <p> |
| 316 |
* |
| 317 |
* </p> |
| 318 |
* @param tokenizer the tokenizer to use |
| 319 |
* @param ordinateFlags a bit-mask defining the ordinates to read. |
| 320 |
* @return a {@link CoordinateSequence} of length 1 containing the read ordinate values |
| 321 |
* |
| 322 |
*@throws IOException if an I/O error occurs |
| 323 |
*@throws ParseException if an unexpected token was encountered |
| 324 |
*/ |
| 325 |
private CoordinateSequence getCoordinateSequence(StreamTokenizer tokenizer, EnumSet<Ordinate> ordinateFlags) |
| 326 |
throws IOException, ParseException { |
| 327 |
if (getNextEmptyOrOpener(tokenizer).equals(WKTConstants.EMPTY)) |
| 328 |
return this.csFactory.create(0, toDimension(ordinateFlags), ordinateFlags.contains(Ordinate.M) ? 1 : 0); |
| 329 |
|
| 330 |
ArrayList coordinates = new ArrayList(); |
| 331 |
do { |
| 332 |
coordinates.add(getCoordinate(tokenizer, ordinateFlags, false)); |
| 333 |
} while (getNextCloserOrComma(tokenizer).equals(COMMA)); |
| 334 |
|
| 335 |
return mergeSequences(coordinates, ordinateFlags); } |
| 336 |
|
| 337 |
/** |
| 338 |
* Reads a <code>CoordinateSequence</Code> from a stream using the given {@link StreamTokenizer} |
| 339 |
* for an old-style JTS MultiPoint (Point coordinates not enclosed in parentheses). |
| 340 |
* <p> |
| 341 |
* All ordinate values are read, but -depending on the {@link CoordinateSequenceFactory} of the |
| 342 |
* underlying {@link GeometryFactory}- not necessarily all can be handled. Those are silently dropped. |
| 343 |
* </p> |
| 344 |
* @param tokenizer the tokenizer to use |
| 345 |
* @param ordinateFlags a bit-mask defining the ordinates to read. |
| 346 |
* @param tryParen a value indicating if a starting {@link #L_PAREN} should be probed for each coordinate. |
| 347 |
* @param isReadEmptyOrOpener indicates if an opening paren or EMPTY should be scanned for |
| 348 |
* @return a {@link CoordinateSequence} of length 1 containing the read ordinate values |
| 349 |
* |
| 350 |
* @throws IOException if an I/O error occurs |
| 351 |
* @throws ParseException if an unexpected token was encountered |
| 352 |
S */ |
| 353 |
private CoordinateSequence getCoordinateSequenceOldMultiPoint(StreamTokenizer tokenizer, EnumSet<Ordinate> ordinateFlags) |
| 354 |
throws IOException, ParseException { |
| 355 |
|
| 356 |
ArrayList coordinates = new ArrayList(); |
| 357 |
do { |
| 358 |
coordinates.add(getCoordinate(tokenizer, ordinateFlags, true)); |
| 359 |
} while (getNextCloserOrComma(tokenizer).equals(COMMA)); |
| 360 |
|
| 361 |
return mergeSequences(coordinates, ordinateFlags); |
| 362 |
} |
| 363 |
|
| 364 |
/** |
| 365 |
* Computes the required dimension based on the given ordinate values. |
| 366 |
* It is assumed that {@link Ordinate#X} and {@link Ordinate#Y} are included. |
| 367 |
* |
| 368 |
* @param ordinateFlags the ordinate bit-mask |
| 369 |
* @return the number of dimensions required to store ordinates for the given bit-mask. |
| 370 |
*/ |
| 371 |
private int toDimension(EnumSet<Ordinate> ordinateFlags) { |
| 372 |
int dimension = 2; |
| 373 |
if (ordinateFlags.contains(Ordinate.Z)) |
| 374 |
dimension++; |
| 375 |
if (ordinateFlags.contains(Ordinate.M)) |
| 376 |
dimension++; |
| 377 |
|
| 378 |
if (dimension == 2 && this.isAllowOldJtsCoordinateSyntax) |
| 379 |
dimension++; |
| 380 |
|
| 381 |
return dimension; |
| 382 |
} |
| 383 |
|
| 384 |
/** |
| 385 |
* Merges an array of one-coordinate-{@link CoordinateSequence}s into one {@link CoordinateSequence}. |
| 386 |
* |
| 387 |
* @param sequences an array of coordinate sequences. Each sequence contains <b>exactly one</b> coordinate. |
| 388 |
* @param ordinateFlags a bit-mask of required ordinates. |
| 389 |
* @return a coordinate sequence containing all coordinate |
| 390 |
*/ |
| 391 |
private CoordinateSequence mergeSequences(ArrayList sequences, EnumSet<Ordinate> ordinateFlags) { |
| 392 |
|
| 393 |
|
| 394 |
if (sequences == null || sequences.size() == 0) |
| 395 |
return csFactory.create(0, toDimension(ordinateFlags)); |
| 396 |
|
| 397 |
if (sequences.size() == 1) |
| 398 |
return (CoordinateSequence) sequences.get(0); |
| 399 |
|
| 400 |
EnumSet<Ordinate> mergeOrdinates; |
| 401 |
if (this.isAllowOldJtsCoordinateSyntax && ordinateFlags.size() == 2) { |
| 402 |
mergeOrdinates = ordinateFlags.clone(); |
| 403 |
for (int i = 0; i < sequences.size(); i++) { |
| 404 |
if (((CoordinateSequence)sequences.get(i)).hasZ()) { |
| 405 |
mergeOrdinates.add(Ordinate.Z); |
| 406 |
break; |
| 407 |
} |
| 408 |
} |
| 409 |
} |
| 410 |
else |
| 411 |
mergeOrdinates = ordinateFlags; |
| 412 |
|
| 413 |
|
| 414 |
CoordinateSequence sequence = this.csFactory.create(sequences.size(), toDimension(mergeOrdinates), |
| 415 |
mergeOrdinates.contains(Ordinate.M) ? 1 : 0); |
| 416 |
|
| 417 |
int offsetM = CoordinateSequence.Z + (mergeOrdinates.contains(Ordinate.Z) ? 1 : 0); |
| 418 |
for (int i = 0; i < sequences.size(); i++) { |
| 419 |
CoordinateSequence item = (CoordinateSequence)sequences.get(i); |
| 420 |
sequence.setOrdinate(i, CoordinateSequence.X, item.getOrdinate(0, CoordinateSequence.X)); |
| 421 |
sequence.setOrdinate(i, CoordinateSequence.Y, item.getOrdinate(0, CoordinateSequence.Y)); |
| 422 |
if (mergeOrdinates.contains(Ordinate.Z)) |
| 423 |
sequence.setOrdinate(i, CoordinateSequence.Z, item.getOrdinate(0, CoordinateSequence.Z)); |
| 424 |
if (mergeOrdinates.contains(Ordinate.M)) |
| 425 |
sequence.setOrdinate(i, offsetM, item.getOrdinate(0, offsetM)); |
| 426 |
} |
| 427 |
|
| 428 |
|
| 429 |
return sequence; |
| 430 |
} |
| 431 |
|
| 432 |
/** |
| 433 |
* Returns the next array of <code>Coordinate</code>s in the stream. |
| 434 |
* |
| 435 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 436 |
* format. The next element returned by the stream should be L_PAREN (the |
| 437 |
* beginning of "(x1 y1, x2 y2, ..., xn yn)") or EMPTY. |
| 438 |
*@return the next array of <code>Coordinate</code>s in the |
| 439 |
* stream, or an empty array if EMPTY is the next element returned by |
| 440 |
* the stream. |
| 441 |
*@throws IOException if an I/O error occurs |
| 442 |
*@throws ParseException if an unexpected token was encountered |
| 443 |
* |
| 444 |
*@deprecated in favor of functions returning {@link CoordinateSequence}s |
| 445 |
*/ |
| 446 |
private Coordinate[] getCoordinates(StreamTokenizer tokenizer) throws IOException, ParseException { |
| 447 |
String nextToken = getNextEmptyOrOpener(tokenizer); |
| 448 |
if (nextToken.equals(WKTConstants.EMPTY)) { |
| 449 |
return new Coordinate[] {}; |
| 450 |
} |
| 451 |
ArrayList coordinates = new ArrayList(); |
| 452 |
coordinates.add(getPreciseCoordinate(tokenizer)); |
| 453 |
nextToken = getNextCloserOrComma(tokenizer); |
| 454 |
while (nextToken.equals(COMMA)) { |
| 455 |
coordinates.add(getPreciseCoordinate(tokenizer)); |
| 456 |
nextToken = getNextCloserOrComma(tokenizer); |
| 457 |
} |
| 458 |
Coordinate[] array = new Coordinate[coordinates.size()]; |
| 459 |
return (Coordinate[]) coordinates.toArray(array); |
| 460 |
} |
| 461 |
|
| 462 |
/** |
| 463 |
* Returns the next array of <code>Coordinate</code>s in the stream. |
| 464 |
* |
| 465 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 466 |
* format. The next element returned by the stream should be a number. |
| 467 |
*@return the next array of <code>Coordinate</code>s in the |
| 468 |
* stream. |
| 469 |
*@throws IOException if an I/O error occurs |
| 470 |
*@throws ParseException if an unexpected token was encountered |
| 471 |
* |
| 472 |
*@deprecated in favor of functions returning {@link CoordinateSequence}s |
| 473 |
*/ |
| 474 |
private Coordinate[] getCoordinatesNoLeftParen(StreamTokenizer tokenizer) throws IOException, ParseException { |
| 475 |
String nextToken = null; |
| 476 |
ArrayList coordinates = new ArrayList(); |
| 477 |
coordinates.add(getPreciseCoordinate(tokenizer)); |
| 478 |
nextToken = getNextCloserOrComma(tokenizer); |
| 479 |
while (nextToken.equals(COMMA)) { |
| 480 |
coordinates.add(getPreciseCoordinate(tokenizer)); |
| 481 |
nextToken = getNextCloserOrComma(tokenizer); |
| 482 |
} |
| 483 |
Coordinate[] array = new Coordinate[coordinates.size()]; |
| 484 |
return (Coordinate[]) coordinates.toArray(array); |
| 485 |
} |
| 486 |
|
| 487 |
/** |
| 488 |
* Returns the next precise <code>Coordinate</code> in the stream. |
| 489 |
* |
| 490 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 491 |
* format. The next element returned by the stream should be a number. |
| 492 |
*@return the next array of <code>Coordinate</code>s in the |
| 493 |
* stream. |
| 494 |
*@throws IOException if an I/O error occurs |
| 495 |
*@throws ParseException if an unexpected token was encountered |
| 496 |
* |
| 497 |
*@deprecated in favor of functions returning {@link CoordinateSequence}s |
| 498 |
*/ |
| 499 |
private Coordinate getPreciseCoordinate(StreamTokenizer tokenizer) |
| 500 |
throws IOException, ParseException |
| 501 |
{ |
| 502 |
Coordinate coord = new Coordinate(); |
| 503 |
coord.x = getNextNumber(tokenizer); |
| 504 |
coord.y = getNextNumber(tokenizer); |
| 505 |
if (isNumberNext(tokenizer)) { |
| 506 |
coord.setZ(getNextNumber(tokenizer)); |
| 507 |
} |
| 508 |
if (isNumberNext(tokenizer)) { |
| 509 |
getNextNumber(tokenizer); |
| 510 |
} |
| 511 |
precisionModel.makePrecise(coord); |
| 512 |
return coord; |
| 513 |
} |
| 514 |
|
| 515 |
/** |
| 516 |
* Tests if the next token in the stream is a number |
| 517 |
* |
| 518 |
* @param tokenizer the tokenizer |
| 519 |
* @return {@code true} if the next token is a number, otherwise {@code false} |
| 520 |
* @throws IOException if an I/O error occurs |
| 521 |
*/ |
| 522 |
private static boolean isNumberNext(StreamTokenizer tokenizer) throws IOException { |
| 523 |
int type = tokenizer.nextToken(); |
| 524 |
tokenizer.pushBack(); |
| 525 |
return type == StreamTokenizer.TT_WORD; |
| 526 |
} |
| 527 |
|
| 528 |
/** |
| 529 |
* Tests if the next token in the stream is a left opener ({@link #L_PAREN}) |
| 530 |
* |
| 531 |
* @param tokenizer the tokenizer |
| 532 |
* @return {@code true} if the next token is a {@link #L_PAREN}, otherwise {@code false} |
| 533 |
* @throws IOException if an I/O error occurs |
| 534 |
*/ |
| 535 |
private static boolean isOpenerNext(StreamTokenizer tokenizer) throws IOException { |
| 536 |
int type = tokenizer.nextToken(); |
| 537 |
tokenizer.pushBack(); |
| 538 |
return type == '('; |
| 539 |
} |
| 540 |
|
| 541 |
/** |
| 542 |
* Parses the next number in the stream. |
| 543 |
* Numbers with exponents are handled. |
| 544 |
* <tt>NaN</tt> values are handled correctly, and |
| 545 |
* the case of the "NaN" symbol is not significant. |
| 546 |
* |
| 547 |
* @param tokenizer tokenizer over a stream of text in Well-known Text |
| 548 |
* @return the next number in the stream |
| 549 |
* @throws ParseException if the next token is not a valid number |
| 550 |
* @throws IOException if an I/O error occurs |
| 551 |
*/ |
| 552 |
private double getNextNumber(StreamTokenizer tokenizer) throws IOException, |
| 553 |
ParseException { |
| 554 |
int type = tokenizer.nextToken(); |
| 555 |
switch (type) { |
| 556 |
case StreamTokenizer.TT_WORD: |
| 557 |
{ |
| 558 |
if (tokenizer.sval.equalsIgnoreCase(NAN_SYMBOL)) { |
| 559 |
return Double.NaN; |
| 560 |
} |
| 561 |
else { |
| 562 |
try { |
| 563 |
return Double.parseDouble(tokenizer.sval); |
| 564 |
} |
| 565 |
catch (NumberFormatException ex) { |
| 566 |
throw parseErrorWithLine(tokenizer, "Invalid number: " + tokenizer.sval); |
| 567 |
} |
| 568 |
} |
| 569 |
} |
| 570 |
} |
| 571 |
throw parseErrorExpected(tokenizer, "number"); |
| 572 |
} |
| 573 |
|
| 574 |
/** |
| 575 |
* Returns the next EMPTY or L_PAREN in the stream as uppercase text. |
| 576 |
* |
| 577 |
*@return the next EMPTY or L_PAREN in the stream as uppercase |
| 578 |
* text. |
| 579 |
*@throws ParseException if the next token is not EMPTY or L_PAREN |
| 580 |
*@throws IOException if an I/O error occurs |
| 581 |
* @param tokenizer tokenizer over a stream of text in Well-known Text |
| 582 |
*/ |
| 583 |
private static String getNextEmptyOrOpener(StreamTokenizer tokenizer) throws IOException, ParseException { |
| 584 |
String nextWord = getNextWord(tokenizer); |
| 585 |
if (nextWord.equalsIgnoreCase(WKTConstants.Z)) { |
| 586 |
|
| 587 |
nextWord = getNextWord(tokenizer); |
| 588 |
} |
| 589 |
else if (nextWord.equalsIgnoreCase(WKTConstants.M)) { |
| 590 |
|
| 591 |
nextWord = getNextWord(tokenizer); |
| 592 |
} |
| 593 |
else if (nextWord.equalsIgnoreCase(WKTConstants.ZM)) { |
| 594 |
|
| 595 |
|
| 596 |
nextWord = getNextWord(tokenizer); |
| 597 |
} |
| 598 |
if (nextWord.equals(WKTConstants.EMPTY) || nextWord.equals(L_PAREN)) { |
| 599 |
return nextWord; |
| 600 |
} |
| 601 |
throw parseErrorExpected(tokenizer, WKTConstants.EMPTY + " or " + L_PAREN); |
| 602 |
} |
| 603 |
|
| 604 |
/** |
| 605 |
* Returns the next ordinate flag information in the stream as uppercase text. |
| 606 |
* This can be Z, M or ZM. |
| 607 |
* |
| 608 |
*@return the next EMPTY or L_PAREN in the stream as uppercase |
| 609 |
* text. |
| 610 |
*@throws ParseException if the next token is not EMPTY or L_PAREN |
| 611 |
*@throws IOException if an I/O error occurs |
| 612 |
* @param tokenizer tokenizer over a stream of text in Well-known Text |
| 613 |
*/ |
| 614 |
private static EnumSet<Ordinate> getNextOrdinateFlags(StreamTokenizer tokenizer) throws IOException, ParseException { |
| 615 |
|
| 616 |
EnumSet<Ordinate> result = EnumSet.of(Ordinate.X, Ordinate.Y); |
| 617 |
|
| 618 |
String nextWord = lookAheadWord(tokenizer).toUpperCase(Locale.ROOT); |
| 619 |
if (nextWord.equalsIgnoreCase(WKTConstants.Z)) { |
| 620 |
tokenizer.nextToken(); |
| 621 |
result.add(Ordinate.Z); |
| 622 |
} |
| 623 |
else if (nextWord.equalsIgnoreCase(WKTConstants.M)) { |
| 624 |
tokenizer.nextToken(); |
| 625 |
result.add(Ordinate.M); |
| 626 |
} |
| 627 |
else if (nextWord.equalsIgnoreCase(WKTConstants.ZM)) { |
| 628 |
tokenizer.nextToken(); |
| 629 |
result.add(Ordinate.Z); |
| 630 |
result.add(Ordinate.M); |
| 631 |
} |
| 632 |
return result; |
| 633 |
} |
| 634 |
|
| 635 |
/** |
| 636 |
* Returns the next word in the stream. |
| 637 |
* |
| 638 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 639 |
* format. The next token must be a word. |
| 640 |
*@return the next word in the stream as uppercase text |
| 641 |
*@throws ParseException if the next token is not a word |
| 642 |
*@throws IOException if an I/O error occurs |
| 643 |
*/ |
| 644 |
private static String lookAheadWord(StreamTokenizer tokenizer) throws IOException, ParseException { |
| 645 |
String nextWord = getNextWord(tokenizer); |
| 646 |
tokenizer.pushBack(); |
| 647 |
return nextWord; |
| 648 |
} |
| 649 |
|
| 650 |
/** |
| 651 |
* Returns the next {@link #R_PAREN} or {@link #COMMA} in the stream. |
| 652 |
* |
| 653 |
*@return the next R_PAREN or COMMA in the stream |
| 654 |
*@throws ParseException if the next token is not R_PAREN or COMMA |
| 655 |
*@throws IOException if an I/O error occurs |
| 656 |
* @param tokenizer tokenizer over a stream of text in Well-known Text |
| 657 |
*/ |
| 658 |
private static String getNextCloserOrComma(StreamTokenizer tokenizer) throws IOException, ParseException { |
| 659 |
String nextWord = getNextWord(tokenizer); |
| 660 |
if (nextWord.equals(COMMA) || nextWord.equals(R_PAREN)) { |
| 661 |
return nextWord; |
| 662 |
} |
| 663 |
throw parseErrorExpected(tokenizer, COMMA + " or " + R_PAREN); |
| 664 |
} |
| 665 |
|
| 666 |
/** |
| 667 |
* Returns the next {@link #R_PAREN} in the stream. |
| 668 |
* |
| 669 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 670 |
* format. The next token must be R_PAREN. |
| 671 |
*@return the next R_PAREN in the stream |
| 672 |
*@throws ParseException if the next token is not R_PAREN |
| 673 |
*@throws IOException if an I/O error occurs |
| 674 |
*/ |
| 675 |
private String getNextCloser(StreamTokenizer tokenizer) throws IOException, ParseException { |
| 676 |
String nextWord = getNextWord(tokenizer); |
| 677 |
if (nextWord.equals(R_PAREN)) { |
| 678 |
return nextWord; |
| 679 |
} |
| 680 |
throw parseErrorExpected(tokenizer, R_PAREN); |
| 681 |
} |
| 682 |
|
| 683 |
/** |
| 684 |
* Returns the next word in the stream. |
| 685 |
* |
| 686 |
*@return the next word in the stream as uppercase text |
| 687 |
*@throws ParseException if the next token is not a word |
| 688 |
*@throws IOException if an I/O error occurs |
| 689 |
* @param tokenizer tokenizer over a stream of text in Well-known Text |
| 690 |
*/ |
| 691 |
private static String getNextWord(StreamTokenizer tokenizer) throws IOException, ParseException { |
| 692 |
int type = tokenizer.nextToken(); |
| 693 |
switch (type) { |
| 694 |
case StreamTokenizer.TT_WORD: |
| 695 |
|
| 696 |
String word = tokenizer.sval; |
| 697 |
if (word.equalsIgnoreCase(WKTConstants.EMPTY)) |
| 698 |
return WKTConstants.EMPTY; |
| 699 |
return word; |
| 700 |
|
| 701 |
case '(': return L_PAREN; |
| 702 |
case ')': return R_PAREN; |
| 703 |
case ',': return COMMA; |
| 704 |
} |
| 705 |
throw parseErrorExpected(tokenizer, "word"); |
| 706 |
} |
| 707 |
|
| 708 |
/** |
| 709 |
* Creates a formatted ParseException reporting that the current token |
| 710 |
* was unexpected. |
| 711 |
* |
| 712 |
* @param expected a description of what was expected |
| 713 |
* @throws AssertionFailedException if an invalid token is encountered |
| 714 |
*/ |
| 715 |
private static ParseException parseErrorExpected(StreamTokenizer tokenizer, String expected) |
| 716 |
{ |
| 717 |
|
| 718 |
if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) |
| 719 |
Assert.shouldNeverReachHere("Unexpected NUMBER token"); |
| 720 |
if (tokenizer.ttype == StreamTokenizer.TT_EOL) |
| 721 |
Assert.shouldNeverReachHere("Unexpected EOL token"); |
| 722 |
|
| 723 |
String tokenStr = tokenString(tokenizer); |
| 724 |
return parseErrorWithLine(tokenizer, "Expected " + expected + " but found " + tokenStr); |
| 725 |
} |
| 726 |
|
| 727 |
/** |
| 728 |
* Creates a formatted ParseException reporting that the current token |
| 729 |
* was unexpected. |
| 730 |
* |
| 731 |
* @param msg a description of what was expected |
| 732 |
* @throws AssertionFailedException if an invalid token is encountered |
| 733 |
*/ |
| 734 |
private static ParseException parseErrorWithLine(StreamTokenizer tokenizer, String msg) |
| 735 |
{ |
| 736 |
return new ParseException(msg + " (line " + tokenizer.lineno() + ")"); |
| 737 |
} |
| 738 |
|
| 739 |
/** |
| 740 |
* Gets a description of the current token type |
| 741 |
* @param tokenizer the tokenizer |
| 742 |
* @return a description of the current token |
| 743 |
*/ |
| 744 |
private static String tokenString(StreamTokenizer tokenizer) |
| 745 |
{ |
| 746 |
switch (tokenizer.ttype) { |
| 747 |
case StreamTokenizer.TT_NUMBER: |
| 748 |
return "<NUMBER>"; |
| 749 |
case StreamTokenizer.TT_EOL: |
| 750 |
return "End-of-Line"; |
| 751 |
case StreamTokenizer.TT_EOF: return "End-of-Stream"; |
| 752 |
case StreamTokenizer.TT_WORD: return "'" + tokenizer.sval + "'"; |
| 753 |
} |
| 754 |
return "'" + (char) tokenizer.ttype + "'"; |
| 755 |
} |
| 756 |
|
| 757 |
/** |
| 758 |
* Creates a <code>Geometry</code> using the next token in the stream. |
| 759 |
* |
| 760 |
*@return a <code>Geometry</code> specified by the next token |
| 761 |
* in the stream |
| 762 |
*@throws ParseException if the coordinates used to create a <code>Polygon</code> |
| 763 |
* shell and holes do not form closed linestrings, or if an unexpected |
| 764 |
* token was encountered |
| 765 |
*@throws IOException if an I/O error occurs |
| 766 |
* @param tokenizer tokenizer over a stream of text in Well-known Text |
| 767 |
*/ |
| 768 |
private Geometry readGeometryTaggedText(StreamTokenizer tokenizer) throws IOException, ParseException { |
| 769 |
String type; |
| 770 |
|
| 771 |
EnumSet<Ordinate> ordinateFlags = EnumSet.of(Ordinate.X, Ordinate.Y); |
| 772 |
try { |
| 773 |
type = getNextWord(tokenizer).toUpperCase(Locale.ROOT); |
| 774 |
if (type.endsWith(WKTConstants.ZM)) { |
| 775 |
ordinateFlags.add(Ordinate.Z); |
| 776 |
ordinateFlags.add(Ordinate.M); |
| 777 |
} else if (type.endsWith(WKTConstants.Z)) { |
| 778 |
ordinateFlags.add(Ordinate.Z); |
| 779 |
} else if (type.endsWith(WKTConstants.M)) { |
| 780 |
ordinateFlags.add(Ordinate.M); |
| 781 |
} |
| 782 |
} catch (IOException e) { |
| 783 |
return null; |
| 784 |
} catch (ParseException e) { |
| 785 |
return null; |
| 786 |
} |
| 787 |
|
| 788 |
return readGeometryTaggedText(tokenizer, type, ordinateFlags); |
| 789 |
} |
| 790 |
|
| 791 |
private Geometry readGeometryTaggedText(StreamTokenizer tokenizer, String type, EnumSet<Ordinate> ordinateFlags) |
| 792 |
throws IOException, ParseException { |
| 793 |
|
| 794 |
if (ordinateFlags.size() == 2) { |
| 795 |
ordinateFlags = getNextOrdinateFlags(tokenizer); |
| 796 |
} |
| 797 |
|
| 798 |
|
| 799 |
|
| 800 |
|
| 801 |
|
| 802 |
|
| 803 |
try { |
| 804 |
csFactory.create(0, toDimension(ordinateFlags), ordinateFlags.contains(Ordinate.M) ? 1 : 0); |
| 805 |
} catch (Exception e) |
| 806 |
{ |
| 807 |
geometryFactory = new GeometryFactory(geometryFactory.getPrecisionModel(), |
| 808 |
geometryFactory.getSRID(), csFactoryXYZM); |
| 809 |
} |
| 810 |
|
| 811 |
if (type.startsWith(WKTConstants.POINT)) { |
| 812 |
return readPointText(tokenizer, ordinateFlags); |
| 813 |
} |
| 814 |
else if (type.startsWith(WKTConstants.LINESTRING)) { |
| 815 |
return readLineStringText(tokenizer, ordinateFlags); |
| 816 |
} |
| 817 |
else if (type.startsWith(WKTConstants.LINEARRING)) { |
| 818 |
return readLinearRingText(tokenizer, ordinateFlags); |
| 819 |
} |
| 820 |
else if (type.startsWith(WKTConstants.POLYGON)) { |
| 821 |
return readPolygonText(tokenizer, ordinateFlags); |
| 822 |
} |
| 823 |
else if (type.startsWith(WKTConstants.MULTIPOINT)) { |
| 824 |
return readMultiPointText(tokenizer, ordinateFlags); |
| 825 |
} |
| 826 |
else if (type.startsWith(WKTConstants.MULTILINESTRING)) { |
| 827 |
return readMultiLineStringText(tokenizer, ordinateFlags); |
| 828 |
} |
| 829 |
else if (type.startsWith(WKTConstants.MULTIPOLYGON)) { |
| 830 |
return readMultiPolygonText(tokenizer, ordinateFlags); |
| 831 |
} |
| 832 |
else if (type.startsWith(WKTConstants.GEOMETRYCOLLECTION)) { |
| 833 |
return readGeometryCollectionText(tokenizer, ordinateFlags); |
| 834 |
} |
| 835 |
throw parseErrorWithLine(tokenizer, "Unknown geometry type: " + type); |
| 836 |
} |
| 837 |
|
| 838 |
/** |
| 839 |
* Creates a <code>Point</code> using the next token in the stream. |
| 840 |
* |
| 841 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 842 |
* format. The next tokens must form a <Point Text>. |
| 843 |
*@return a <code>Point</code> specified by the next token in |
| 844 |
* the stream |
| 845 |
*@throws IOException if an I/O error occurs |
| 846 |
*@throws ParseException if an unexpected token was encountered |
| 847 |
*/ |
| 848 |
private Point readPointText(StreamTokenizer tokenizer, EnumSet<Ordinate> ordinateFlags) throws IOException, ParseException { |
| 849 |
Point point = geometryFactory.createPoint(getCoordinateSequence(tokenizer, ordinateFlags)); |
| 850 |
return point; |
| 851 |
} |
| 852 |
|
| 853 |
/** |
| 854 |
* Creates a <code>LineString</code> using the next token in the stream. |
| 855 |
* |
| 856 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 857 |
* format. The next tokens must form a <LineString Text>. |
| 858 |
*@return a <code>LineString</code> specified by the next |
| 859 |
* token in the stream |
| 860 |
*@throws IOException if an I/O error occurs |
| 861 |
*@throws ParseException if an unexpected token was encountered |
| 862 |
*/ |
| 863 |
private LineString readLineStringText(StreamTokenizer tokenizer, EnumSet<Ordinate> ordinateFlags) throws IOException, ParseException { |
| 864 |
return geometryFactory.createLineString(getCoordinateSequence(tokenizer, ordinateFlags)); |
| 865 |
} |
| 866 |
|
| 867 |
/** |
| 868 |
* Creates a <code>LinearRing</code> using the next token in the stream. |
| 869 |
* |
| 870 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 871 |
* format. The next tokens must form a <LineString Text>. |
| 872 |
*@return a <code>LinearRing</code> specified by the next |
| 873 |
* token in the stream |
| 874 |
*@throws IOException if an I/O error occurs |
| 875 |
*@throws ParseException if the coordinates used to create the <code>LinearRing</code> |
| 876 |
* do not form a closed linestring, or if an unexpected token was |
| 877 |
* encountered |
| 878 |
*/ |
| 879 |
private LinearRing readLinearRingText(StreamTokenizer tokenizer, EnumSet<Ordinate> ordinateFlags) |
| 880 |
throws IOException, ParseException |
| 881 |
{ |
| 882 |
return geometryFactory.createLinearRing(getCoordinateSequence(tokenizer, ordinateFlags)); |
| 883 |
} |
| 884 |
|
| 885 |
/** |
| 886 |
* Creates a <code>MultiPoint</code> using the next tokens in the stream. |
| 887 |
* |
| 888 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 889 |
* format. The next tokens must form a <MultiPoint Text>. |
| 890 |
*@return a <code>MultiPoint</code> specified by the next |
| 891 |
* token in the stream |
| 892 |
*@throws IOException if an I/O error occurs |
| 893 |
*@throws ParseException if an unexpected token was encountered |
| 894 |
*/ |
| 895 |
private MultiPoint readMultiPointText(StreamTokenizer tokenizer, EnumSet<Ordinate> ordinateFlags) throws IOException, ParseException |
| 896 |
{ |
| 897 |
String nextToken = getNextEmptyOrOpener(tokenizer); |
| 898 |
if (nextToken.equals(WKTConstants.EMPTY)) { |
| 899 |
return geometryFactory.createMultiPoint(new Point[0]); |
| 900 |
} |
| 901 |
|
| 902 |
|
| 903 |
|
| 904 |
if (isAllowOldJtsMultipointSyntax) { |
| 905 |
String nextWord = lookAheadWord(tokenizer); |
| 906 |
if (nextWord != L_PAREN) { |
| 907 |
return geometryFactory.createMultiPoint( |
| 908 |
getCoordinateSequenceOldMultiPoint(tokenizer, ordinateFlags)); |
| 909 |
} |
| 910 |
} |
| 911 |
|
| 912 |
ArrayList points = new ArrayList(); |
| 913 |
Point point = readPointText(tokenizer, ordinateFlags); |
| 914 |
points.add(point); |
| 915 |
nextToken = getNextCloserOrComma(tokenizer); |
| 916 |
while (nextToken.equals(COMMA)) { |
| 917 |
point = readPointText(tokenizer, ordinateFlags); |
| 918 |
points.add(point); |
| 919 |
nextToken = getNextCloserOrComma(tokenizer); |
| 920 |
} |
| 921 |
Point[] array = new Point[points.size()]; |
| 922 |
return geometryFactory.createMultiPoint((Point[]) points.toArray(array)); |
| 923 |
} |
| 924 |
|
| 925 |
|
| 926 |
/** |
| 927 |
* Creates a <code>Polygon</code> using the next token in the stream. |
| 928 |
* |
| 929 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 930 |
* format. The next tokens must form a <Polygon Text>. |
| 931 |
*@return a <code>Polygon</code> specified by the next token |
| 932 |
* in the stream |
| 933 |
*@throws ParseException if the coordinates used to create the <code>Polygon</code> |
| 934 |
* shell and holes do not form closed linestrings, or if an unexpected |
| 935 |
* token was encountered. |
| 936 |
*@throws IOException if an I/O error occurs |
| 937 |
*/ |
| 938 |
private Polygon readPolygonText(StreamTokenizer tokenizer, EnumSet<Ordinate> ordinateFlags) throws IOException, ParseException { |
| 939 |
String nextToken = getNextEmptyOrOpener(tokenizer); |
| 940 |
if (nextToken.equals(WKTConstants.EMPTY)) { |
| 941 |
return geometryFactory.createPolygon(); |
| 942 |
} |
| 943 |
ArrayList holes = new ArrayList(); |
| 944 |
LinearRing shell = readLinearRingText(tokenizer, ordinateFlags); |
| 945 |
nextToken = getNextCloserOrComma(tokenizer); |
| 946 |
while (nextToken.equals(COMMA)) { |
| 947 |
LinearRing hole = readLinearRingText(tokenizer, ordinateFlags); |
| 948 |
holes.add(hole); |
| 949 |
nextToken = getNextCloserOrComma(tokenizer); |
| 950 |
} |
| 951 |
LinearRing[] array = new LinearRing[holes.size()]; |
| 952 |
return geometryFactory.createPolygon(shell, (LinearRing[]) holes.toArray(array)); |
| 953 |
} |
| 954 |
|
| 955 |
/** |
| 956 |
* Creates a <code>MultiLineString</code> using the next token in the stream. |
| 957 |
* |
| 958 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 959 |
* format. The next tokens must form a <MultiLineString Text>. |
| 960 |
*@return a <code>MultiLineString</code> specified by the |
| 961 |
* next token in the stream |
| 962 |
*@throws IOException if an I/O error occurs |
| 963 |
*@throws ParseException if an unexpected token was encountered |
| 964 |
*/ |
| 965 |
private MultiLineString readMultiLineStringText(StreamTokenizer tokenizer, EnumSet<Ordinate> ordinateFlags) |
| 966 |
throws IOException, ParseException { |
| 967 |
String nextToken = getNextEmptyOrOpener(tokenizer); |
| 968 |
if (nextToken.equals(WKTConstants.EMPTY)) { |
| 969 |
return geometryFactory.createMultiLineString(); |
| 970 |
} |
| 971 |
|
| 972 |
ArrayList lineStrings = new ArrayList(); |
| 973 |
do { |
| 974 |
LineString lineString = readLineStringText(tokenizer, ordinateFlags); |
| 975 |
lineStrings.add(lineString); |
| 976 |
nextToken = getNextCloserOrComma(tokenizer); |
| 977 |
} while (nextToken.equals(COMMA)); |
| 978 |
|
| 979 |
LineString[] array = new LineString[lineStrings.size()]; |
| 980 |
return geometryFactory.createMultiLineString((LineString[]) lineStrings.toArray(array)); |
| 981 |
} |
| 982 |
|
| 983 |
/** |
| 984 |
* Creates a <code>MultiPolygon</code> using the next token in the stream. |
| 985 |
* |
| 986 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 987 |
* format. The next tokens must form a <MultiPolygon Text>. |
| 988 |
*@return a <code>MultiPolygon</code> specified by the next |
| 989 |
* token in the stream, or if if the coordinates used to create the |
| 990 |
* <code>Polygon</code> shells and holes do not form closed linestrings. |
| 991 |
*@throws IOException if an I/O error occurs |
| 992 |
*@throws ParseException if an unexpected token was encountered |
| 993 |
*/ |
| 994 |
private MultiPolygon readMultiPolygonText(StreamTokenizer tokenizer, EnumSet<Ordinate> ordinateFlags) throws IOException, ParseException { |
| 995 |
String nextToken = getNextEmptyOrOpener(tokenizer); |
| 996 |
if (nextToken.equals(WKTConstants.EMPTY)) { |
| 997 |
return geometryFactory.createMultiPolygon(); |
| 998 |
} |
| 999 |
ArrayList polygons = new ArrayList(); |
| 1000 |
do { |
| 1001 |
Polygon polygon = readPolygonText(tokenizer, ordinateFlags); |
| 1002 |
polygons.add(polygon); |
| 1003 |
nextToken = getNextCloserOrComma(tokenizer); |
| 1004 |
} while (nextToken.equals(COMMA)); |
| 1005 |
Polygon[] array = new Polygon[polygons.size()]; |
| 1006 |
return geometryFactory.createMultiPolygon((Polygon[]) polygons.toArray(array)); |
| 1007 |
} |
| 1008 |
|
| 1009 |
/** |
| 1010 |
* Creates a <code>GeometryCollection</code> using the next token in the |
| 1011 |
* stream. |
| 1012 |
* |
| 1013 |
*@param tokenizer tokenizer over a stream of text in Well-known Text |
| 1014 |
* format. The next tokens must form a <GeometryCollection Text>. |
| 1015 |
*@return a <code>GeometryCollection</code> specified by the |
| 1016 |
* next token in the stream |
| 1017 |
*@throws ParseException if the coordinates used to create a <code>Polygon</code> |
| 1018 |
* shell and holes do not form closed linestrings, or if an unexpected |
| 1019 |
* token was encountered |
| 1020 |
*@throws IOException if an I/O error occurs |
| 1021 |
*/ |
| 1022 |
private GeometryCollection readGeometryCollectionText(StreamTokenizer tokenizer, EnumSet<Ordinate> ordinateFlags) throws IOException, ParseException { |
| 1023 |
String nextToken = getNextEmptyOrOpener(tokenizer); |
| 1024 |
if (nextToken.equals(WKTConstants.EMPTY)) { |
| 1025 |
return geometryFactory.createGeometryCollection(); |
| 1026 |
} |
| 1027 |
ArrayList geometries = new ArrayList(); |
| 1028 |
do { |
| 1029 |
Geometry geometry = readGeometryTaggedText(tokenizer); |
| 1030 |
geometries.add(geometry); |
| 1031 |
nextToken = getNextCloserOrComma(tokenizer); |
| 1032 |
} while (nextToken.equals(COMMA)); |
| 1033 |
|
| 1034 |
Geometry[] array = new Geometry[geometries.size()]; |
| 1035 |
return geometryFactory.createGeometryCollection((Geometry[]) geometries.toArray(array)); |
| 1036 |
} |
| 1037 |
|
| 1038 |
} |
| 1039 |
|
| 1040 |
|