Statistics
| Revision:

svn-gvsig-desktop / tags / Root_Fmap_GisPlanet / libraries / libFMap / src / com / iver / cit / gvsig / fmap / drivers / jdbc / postgis / WKTParser.java @ 1826

History | View | Annotate | Download (20.2 KB)

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

    
3
import com.iver.cit.gvsig.fmap.core.FGeometry;
4
import com.iver.cit.gvsig.fmap.core.FMultiPoint2D;
5
import com.iver.cit.gvsig.fmap.core.FPoint2D;
6
import com.iver.cit.gvsig.fmap.core.FPolygon2D;
7
import com.iver.cit.gvsig.fmap.core.FPolyline2D;
8
import com.iver.cit.gvsig.fmap.core.GeneralPathX;
9
import com.iver.cit.gvsig.fmap.core.IGeometry;
10
import com.iver.cit.gvsig.fmap.core.ShapeFactory;
11
import com.vividsolutions.jts.geom.Coordinate;
12
import com.vividsolutions.jts.io.ParseException;
13

    
14

    
15
import java.awt.geom.Point2D;
16
import java.io.IOException;
17
import java.io.Reader;
18
import java.io.StreamTokenizer;
19
import java.io.StringReader;
20
import java.util.ArrayList;
21

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

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

    
61
  
62

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

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

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

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

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

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

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

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

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

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

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

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

    
377
  }
378

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

    
402

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

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

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

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