Statistics
| Revision:

svn-gvsig-desktop / tags / J2ME_compat_v1_2_Build_1209 / libraries / libFMap / src / com / iver / cit / gvsig / fmap / drivers / WKTParser.java @ 19509

History | View | Annotate | Download (20.6 KB)

1
package com.iver.cit.gvsig.fmap.drivers;
2

    
3
import java.io.IOException;
4
import java.io.Reader;
5
import java.io.StreamTokenizer;
6
import java.io.StringReader;
7
import java.util.ArrayList;
8

    
9
import com.iver.cit.gvsig.fmap.core.FGeometry;
10
import com.iver.cit.gvsig.fmap.core.FMultiPoint2D;
11
import com.iver.cit.gvsig.fmap.core.FPoint2D;
12
import com.iver.cit.gvsig.fmap.core.FPolygon2D;
13
import com.iver.cit.gvsig.fmap.core.FPolyline2D;
14
import com.iver.cit.gvsig.fmap.core.GeneralPathX;
15
import com.iver.cit.gvsig.fmap.core.IGeometry;
16
import com.iver.cit.gvsig.fmap.core.ShapeFactory;
17
import com.vividsolutions.jts.geom.Coordinate;
18
import com.vividsolutions.jts.io.ParseException;
19

    
20
/**
21
 *  Converts a Well-Known Text string to a <code>Geometry</code>.
22
 * <p>
23
 *  The <code>WKTReader</code> allows
24
 *  extracting <code>Geometry</code> objects from either input streams or
25
 *  internal strings. This allows it to function as a parser to read <code>Geometry</code>
26
 *  objects from text blocks embedded in other data formats (e.g. XML). <P>
27
 * <p>
28
 * The Well-known
29
 *  Text format is defined in the <A HREF="http://www.opengis.org/techno/specs.htm">
30
 *  OpenGIS Simple Features Specification for SQL</A> . <P>
31
 * <p>
32
 *  <B>Note: </B> There is an inconsistency in the SFS. The WKT grammar states
33
 *  that <code>MultiPoints</code> are represented by <code>MULTIPOINT ( ( x y), (x y) )</code>
34
 *  , but the examples show <code>MultiPoint</code>s as <code>MULTIPOINT ( x y, x y )</code>
35
 *  . Other implementations follow the latter syntax, so JTS will adopt it as
36
 *  well.
37
 *
38
 *  A <code>WKTReader</code> is parameterized by a <code>GeometryFactory</code>
39
 *  , to allow it to create <code>Geometry</code> objects of the appropriate
40
 *  implementation. In particular, the <code>GeometryFactory</code> will
41
 *  determine the <code>PrecisionModel</code> and <code>SRID</code> that is
42
 *  used. <P>
43
 *
44
 *  The <code>WKTReader</code> will convert the input numbers to the precise
45
 *  internal representation.
46
 *
47
 *  Reads non-standard "LINEARRING" tags.
48
 *
49
 *@version 1.5
50
 */
51
public class WKTParser {
52

    
53
  /**
54
   * Creates a WKTReader that creates objects using a basic GeometryFactory.
55
   */
56
  public WKTParser() {
57
  }
58

    
59
  
60

    
61
        /**
62
     * Converts a Well-known Text representation to a <code>Geometry</code>.
63
     * 
64
     * @param wellKnownText
65
     *            one or more <Geometry Tagged Text>strings (see the OpenGIS
66
     *            Simple Features Specification) separated by whitespace
67
     * @return a <code>Geometry</code> specified by <code>wellKnownText</code>
68
     * @throws ParseException
69
     *             if a parsing problem occurs
70
         */
71
  public IGeometry read(String wellKnownText) throws ParseException {
72
    StringReader reader = new StringReader(wellKnownText);
73
    try {
74
      return read(reader);
75
    }
76
    finally {
77
      reader.close();
78
    }
79
  }
80

    
81
  /**
82
   *  Converts a Well-known Text representation to a <code>Geometry</code>.
83
   *
84
   *@param  reader           a Reader which will return a <Geometry Tagged Text>
85
   *      string (see the OpenGIS Simple Features Specification)
86
   *@return                  a <code>Geometry</code> read from <code>reader</code>
87
   *@throws  ParseException  if a parsing problem occurs
88
   */
89
  public IGeometry read(Reader reader) throws ParseException {
90
    StreamTokenizer tokenizer = new StreamTokenizer(reader);
91
    try {
92
      return readGeometryTaggedText(tokenizer);
93
    }
94
    catch (IOException e) {
95
      throw new ParseException(e.toString());
96
    }
97
  }
98

    
99
  /**
100
   *  Returns the next array of <code>Coordinate</code>s in the stream.
101
   *
102
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
103
   *      format. The next element returned by the stream should be "(" (the
104
   *      beginning of "(x1 y1, x2 y2, ..., xn yn)") or "EMPTY".
105
   *@return                  the next array of <code>Coordinate</code>s in the
106
   *      stream, or an empty array if "EMPTY" is the next element returned by
107
   *      the stream.
108
   *@throws  IOException     if an I/O error occurs
109
   *@throws  ParseException  if an unexpected token was encountered
110
   */
111
  private Coordinate[] getCoordinates(StreamTokenizer tokenizer)
112
      throws IOException, ParseException
113
  {
114
    String nextToken = getNextEmptyOrOpener(tokenizer);
115
    if (nextToken.equals("EMPTY")) {
116
      return new Coordinate[]{};
117
    }
118
    ArrayList coordinates = new ArrayList();
119
    coordinates.add(getPreciseCoordinate(tokenizer));
120
    nextToken = getNextCloserOrComma(tokenizer);
121
    while (nextToken.equals(",")) {
122
      coordinates.add(getPreciseCoordinate(tokenizer));
123
      nextToken = getNextCloserOrComma(tokenizer);
124
    }
125
    Coordinate[] array = new Coordinate[coordinates.size()];
126
    return (Coordinate[]) coordinates.toArray(array);
127
  }
128

    
129
  private Coordinate getPreciseCoordinate(StreamTokenizer tokenizer)
130
      throws IOException, ParseException
131
  {
132
    Coordinate coord = new Coordinate();
133
    coord.x = getNextNumber(tokenizer);
134
    coord.y = getNextNumber(tokenizer);
135
    if (isNumberNext(tokenizer)) {
136
        coord.z = getNextNumber(tokenizer);
137
    }
138
    return coord;
139
  }
140
  private boolean isNumberNext(StreamTokenizer tokenizer) throws IOException {
141
      try {
142
          return tokenizer.nextToken() == StreamTokenizer.TT_NUMBER;
143
      }
144
      finally {
145
          tokenizer.pushBack();
146
      }
147
  }
148
  /**
149
   *  Returns the next number in the stream.
150
   *
151
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
152
   *      format. The next token must be a number.
153
   *@return                  the next number in the stream
154
   *@throws  ParseException  if the next token is not a number
155
   *@throws  IOException     if an I/O error occurs
156
   */
157
  private double getNextNumber(StreamTokenizer tokenizer) throws IOException,
158
      ParseException {
159
    int type = tokenizer.nextToken();
160
    switch (type) {
161
      case StreamTokenizer.TT_EOF:
162
        throw new ParseException("Expected number but encountered end of stream");
163
      case StreamTokenizer.TT_EOL:
164
        throw new ParseException("Expected number but encountered end of line");
165
      case StreamTokenizer.TT_NUMBER:
166
        return tokenizer.nval;
167
      case StreamTokenizer.TT_WORD:
168
        throw new ParseException("Expected number but encountered word: " +
169
            tokenizer.sval);
170
      case '(':
171
        throw new ParseException("Expected number but encountered '('");
172
      case ')':
173
        throw new ParseException("Expected number but encountered ')'");
174
      case ',':
175
        throw new ParseException("Expected number but encountered ','");
176
    }
177
    return 0;
178
  }
179

    
180
  /**
181
   *  Returns the next "EMPTY" or "(" in the stream as uppercase text.
182
   *
183
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
184
   *      format. The next token must be "EMPTY" or "(".
185
   *@return                  the next "EMPTY" or "(" in the stream as uppercase
186
   *      text.
187
   *@throws  ParseException  if the next token is not "EMPTY" or "("
188
   *@throws  IOException     if an I/O error occurs
189
   */
190
  private String getNextEmptyOrOpener(StreamTokenizer tokenizer) throws IOException, ParseException {
191
    String nextWord = getNextWord(tokenizer);
192
    if (nextWord.equals("EMPTY") || nextWord.equals("(")) {
193
      return nextWord;
194
    }
195
    throw new ParseException("Expected 'EMPTY' or '(' but encountered '" +
196
        nextWord + "'");
197
  }
198

    
199
  /**
200
   *  Returns the next ")" or "," in the stream.
201
   *
202
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
203
   *      format. The next token must be ")" or ",".
204
   *@return                  the next ")" or "," in the stream
205
   *@throws  ParseException  if the next token is not ")" or ","
206
   *@throws  IOException     if an I/O error occurs
207
   */
208
  private String getNextCloserOrComma(StreamTokenizer tokenizer) throws IOException, ParseException {
209
    String nextWord = getNextWord(tokenizer);
210
    if (nextWord.equals(",") || nextWord.equals(")")) {
211
      return nextWord;
212
    }
213
    throw new ParseException("Expected ')' or ',' but encountered '" + nextWord
214
         + "'");
215
  }
216

    
217
  /**
218
   *  Returns the next ")" in the stream.
219
   *
220
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
221
   *      format. The next token must be ")".
222
   *@return                  the next ")" in the stream
223
   *@throws  ParseException  if the next token is not ")"
224
   *@throws  IOException     if an I/O error occurs
225
   */
226
  private String getNextCloser(StreamTokenizer tokenizer) throws IOException, ParseException {
227
    String nextWord = getNextWord(tokenizer);
228
    if (nextWord.equals(")")) {
229
      return nextWord;
230
    }
231
    throw new ParseException("Expected ')' but encountered '" + nextWord + "'");
232
  }
233

    
234
  /**
235
   *  Returns the next word in the stream as uppercase text.
236
   *
237
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
238
   *      format. The next token must be a word.
239
   *@return                  the next word in the stream as uppercase text
240
   *@throws  ParseException  if the next token is not a word
241
   *@throws  IOException     if an I/O error occurs
242
   */
243
  private String getNextWord(StreamTokenizer tokenizer) throws IOException, ParseException {
244
    int type = tokenizer.nextToken();
245
    switch (type) {
246
      case StreamTokenizer.TT_EOF:
247
        throw new ParseException("Expected word but encountered end of stream");
248
      case StreamTokenizer.TT_EOL:
249
        throw new ParseException("Expected word but encountered end of line");
250
      case StreamTokenizer.TT_NUMBER:
251
        throw new ParseException("Expected word but encountered number: " +
252
            tokenizer.nval);
253
      case StreamTokenizer.TT_WORD:
254
        return tokenizer.sval.toUpperCase();
255
      case '(':
256
        return "(";
257
      case ')':
258
        return ")";
259
      case ',':
260
        return ",";
261
    }
262
    // //assert.shouldNeverReachHere("Encountered unexpected StreamTokenizer type: " + type);
263
    return null;
264
  }
265

    
266
  /**
267
   *  Creates a <code>Geometry</code> using the next token in the stream.
268
   *
269
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
270
   *      format. The next tokens must form a &lt;Geometry Tagged Text&gt;.
271
   *@return                  a <code>Geometry</code> specified by the next token
272
   *      in the stream
273
   *@throws  ParseException  if the coordinates used to create a <code>Polygon</code>
274
   *      shell and holes do not form closed linestrings, or if an unexpected
275
   *      token was encountered
276
   *@throws  IOException     if an I/O error occurs
277
   */
278
  private IGeometry readGeometryTaggedText(StreamTokenizer tokenizer) throws IOException, ParseException {
279
    String type = getNextWord(tokenizer);
280
    if (type.equals("POINT")) {
281
      return readPointText(tokenizer);
282
    }
283
    else if (type.equals("LINESTRING")) {
284
      return readLineStringText(tokenizer);
285
    }
286
    else if (type.equals("LINEARRING")) {
287
      return readLinearRingText(tokenizer);
288
    }
289
    else if (type.equals("POLYGON")) {
290
      return readPolygonText(tokenizer);
291
    }
292
    else if (type.equals("MULTIPOINT")) {
293
      return readMultiPointText(tokenizer);
294
    }
295
    else if (type.equals("MULTILINESTRING")) {
296
      return readMultiLineStringText(tokenizer);
297
    }
298
    else if (type.equals("MULTIPOLYGON")) {
299
      return readMultiPolygonText(tokenizer);
300
    }
301
    /* else if (type.equals("GEOMETRYCOLLECTION")) {
302
      return readGeometryCollectionText(tokenizer);
303
    } */
304
    System.err.println("Unknown type: " + type);
305
    throw new ParseException("Unknown type: " + type);
306
  }
307

    
308
  /**
309
   *  Creates a <code>Point</code> using the next token in the stream.
310
   *
311
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
312
   *      format. The next tokens must form a &lt;Point Text&gt;.
313
   *@return                  a <code>Point</code> specified by the next token in
314
   *      the stream
315
   *@throws  IOException     if an I/O error occurs
316
   *@throws  ParseException  if an unexpected token was encountered
317
   */
318
  private FGeometry readPointText(StreamTokenizer tokenizer) throws IOException, ParseException {
319
    String nextToken = getNextEmptyOrOpener(tokenizer);
320
    if (nextToken.equals("EMPTY")) {
321
      return null;
322
    }
323
    Coordinate c = getPreciseCoordinate(tokenizer);
324
    FPoint2D point = new FPoint2D(c.x, c.y );
325
    getNextCloser(tokenizer);
326
    
327
    return ShapeFactory.createGeometry(point);
328
  }
329

    
330
  /**
331
   *  Creates a <code>LineString</code> using the next token in the stream.
332
   *
333
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
334
   *      format. The next tokens must form a &lt;LineString Text&gt;.
335
   *@return                  a <code>LineString</code> specified by the next
336
   *      token in the stream
337
   *@throws  IOException     if an I/O error occurs
338
   *@throws  ParseException  if an unexpected token was encountered
339
   */
340
  private FGeometry readLineStringText(StreamTokenizer tokenizer) throws IOException, ParseException {
341
      Coordinate[] arrayC = getCoordinates(tokenizer);
342
      GeneralPathX gp = new GeneralPathX();
343
      gp.moveTo(arrayC[0].x,arrayC[0].y);
344
      for (int i=1;i < arrayC.length; i++)
345
      {
346
          gp.lineTo(arrayC[i].x, arrayC[i].y);
347
      }
348
    return ShapeFactory.createGeometry(new FPolyline2D(gp));
349
  }
350

    
351
  /**
352
   *  Creates a <code>LinearRing</code> using the next token in the stream.
353
   *
354
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
355
   *      format. The next tokens must form a &lt;LineString Text&gt;.
356
   *@return                  a <code>LinearRing</code> specified by the next
357
   *      token in the stream
358
   *@throws  IOException     if an I/O error occurs
359
   *@throws  ParseException  if the coordinates used to create the <code>LinearRing</code>
360
   *      do not form a closed linestring, or if an unexpected token was
361
   *      encountered
362
   */
363
  private FGeometry readLinearRingText(StreamTokenizer tokenizer)
364
    throws IOException, ParseException
365
  {
366
      Coordinate[] arrayC = getCoordinates(tokenizer);
367
      GeneralPathX gp = new GeneralPathX();
368
      gp.moveTo(arrayC[0].x, arrayC[0].y);
369
      for (int i=1;i < arrayC.length; i++)
370
      {
371
          gp.lineTo(arrayC[i].x, arrayC[i].y);
372
      }
373
      return ShapeFactory.createGeometry(new FPolygon2D(gp));
374

    
375
  }
376

    
377
  /**
378
   *  Creates a <code>MultiPoint</code> using the next token in the stream.
379
   *
380
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
381
   *      format. The next tokens must form a &lt;MultiPoint Text&gt;.
382
   *@return                  a <code>MultiPoint</code> specified by the next
383
   *      token in the stream
384
   *@throws  IOException     if an I/O error occurs
385
   *@throws  ParseException  if an unexpected token was encountered
386
   */
387
  private IGeometry readMultiPointText(StreamTokenizer tokenizer) throws IOException, ParseException {
388
    Coordinate[] coords = getCoordinates(tokenizer);
389
    double[] x = new double[coords.length];
390
    double[] y = new double[coords.length];
391
    for (int i=0; i < coords.length; i++)
392
    {
393
        x[i] = coords[i].x;
394
        y[i] = coords[i].y;
395
    }
396
    FMultiPoint2D multi = new FMultiPoint2D(x, y);
397
    return multi;
398
  }
399

    
400

    
401
  /**
402
   *  Creates a <code>Polygon</code> using the next token in the stream.
403
   *
404
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
405
   *      format. The next tokens must form a &lt;Polygon Text&gt;.
406
   *@return                  a <code>Polygon</code> specified by the next token
407
   *      in the stream
408
   *@throws  ParseException  if the coordinates used to create the <code>Polygon</code>
409
   *      shell and holes do not form closed linestrings, or if an unexpected
410
   *      token was encountered.
411
   *@throws  IOException     if an I/O error occurs
412
   */
413
  private FGeometry readPolygonText(StreamTokenizer tokenizer) throws IOException, ParseException {
414
    String nextToken = getNextEmptyOrOpener(tokenizer);
415
    if (nextToken.equals("EMPTY")) {
416
        return null;
417
    }
418
    ArrayList holes = new ArrayList();
419
    FGeometry shell = readLinearRingText(tokenizer);
420
    nextToken = getNextCloserOrComma(tokenizer);
421
    while (nextToken.equals(",")) {
422
      FGeometry hole = readLinearRingText(tokenizer);
423
      holes.add(hole);
424
      nextToken = getNextCloserOrComma(tokenizer);
425
    }
426
    // LinearRing[] array = new LinearRing[holes.size()];
427
    return shell; //geometryFactory.createPolygon(shell, (LinearRing[]) holes.toArray(array));
428
  }
429

    
430
  /**
431
   *  Creates a <code>MultiLineString</code> using the next token in the stream.
432
   *
433
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
434
   *      format. The next tokens must form a &lt;MultiLineString Text&gt;.
435
   *@return                  a <code>MultiLineString</code> specified by the
436
   *      next token in the stream
437
   *@throws  IOException     if an I/O error occurs
438
   *@throws  ParseException  if an unexpected token was encountered
439
   */
440
  private FGeometry readMultiLineStringText(StreamTokenizer tokenizer) throws IOException, ParseException {
441
      // TODO: HACER ESTO BIEN, CON UN GENERAL PATH
442
    String nextToken = getNextEmptyOrOpener(tokenizer);
443
    if (nextToken.equals("EMPTY")) {
444
      return null;
445
    }
446
    ArrayList lineStrings = new ArrayList();
447
    FGeometry lineString = readLineStringText(tokenizer);
448
    lineStrings.add(lineString);
449
    nextToken = getNextCloserOrComma(tokenizer);
450
    while (nextToken.equals(",")) {
451
      lineString = readLineStringText(tokenizer);
452
      lineStrings.add(lineString);
453
      nextToken = getNextCloserOrComma(tokenizer);
454
    } 
455
    // LineString[] array = new LineString[lineStrings.size()];
456
    return lineString; // geometryFactory.createMultiLineString((LineString[]) lineStrings.toArray(array));
457
  }
458

    
459
  /**
460
   *  Creates a <code>MultiPolygon</code> using the next token in the stream.
461
   *
462
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
463
   *      format. The next tokens must form a &lt;MultiPolygon Text&gt;.
464
   *@return                  a <code>MultiPolygon</code> specified by the next
465
   *      token in the stream, or if if the coordinates used to create the
466
   *      <code>Polygon</code> shells and holes do not form closed linestrings.
467
   *@throws  IOException     if an I/O error occurs
468
   *@throws  ParseException  if an unexpected token was encountered
469
   */
470
  // TODO:
471
  private IGeometry readMultiPolygonText(StreamTokenizer tokenizer) throws IOException, ParseException {
472
    String nextToken = getNextEmptyOrOpener(tokenizer);
473
    if (nextToken.equals("EMPTY")) {
474
      return null;
475
    }
476
    ArrayList polygons = new ArrayList();
477
    FGeometry polygon = readPolygonText(tokenizer);
478
    /* polygons.add(polygon);
479
    nextToken = getNextCloserOrComma(tokenizer);
480
    while (nextToken.equals(",")) {
481
      polygon = readPolygonText(tokenizer);
482
      polygons.add(polygon);
483
      nextToken = getNextCloserOrComma(tokenizer);
484
    } */
485
    // Polygon[] array = new Polygon[polygons.size()];
486
    return polygon; //geometryFactory.createMultiPolygon((Polygon[]) polygons.toArray(array));
487
  } 
488

    
489
  /**
490
   *  Creates a <code>GeometryCollection</code> using the next token in the
491
   *  stream.
492
   *
493
   *@param  tokenizer        tokenizer over a stream of text in Well-known Text
494
   *      format. The next tokens must form a &lt;GeometryCollection Text&gt;.
495
   *@return                  a <code>GeometryCollection</code> specified by the
496
   *      next token in the stream
497
   *@throws  ParseException  if the coordinates used to create a <code>Polygon</code>
498
   *      shell and holes do not form closed linestrings, or if an unexpected
499
   *      token was encountered
500
   *@throws  IOException     if an I/O error occurs
501
   */
502
  // TODO:
503
  /* private GeometryCollection readGeometryCollectionText(StreamTokenizer tokenizer) throws IOException, ParseException {
504
    String nextToken = getNextEmptyOrOpener(tokenizer);
505
    if (nextToken.equals("EMPTY")) {
506
      return geometryFactory.createGeometryCollection(new Geometry[]{});
507
    }
508
    ArrayList geometries = new ArrayList();
509
    Geometry geometry = readGeometryTaggedText(tokenizer);
510
    geometries.add(geometry);
511
    nextToken = getNextCloserOrComma(tokenizer);
512
    while (nextToken.equals(",")) {
513
      geometry = readGeometryTaggedText(tokenizer);
514
      geometries.add(geometry);
515
      nextToken = getNextCloserOrComma(tokenizer);
516
    }
517
    Geometry[] array = new Geometry[geometries.size()];
518
    return geometryFactory.createGeometryCollection((Geometry[]) geometries.toArray(array));
519
  } */
520
}
521