Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.compat.cdc / org.gvsig.fmap.geometry / org.gvsig.fmap.geometry.jts / src / main / java / org / gvsig / fmap / geom / jts / operation / fromwkt / EWKTParser.java @ 47432

History | View | Annotate | Download (41.9 KB)

1
/*
2
 * $Id$
3
 *
4
 * This file is an adapted version of the JTS WKTReader. It has
5
 * been extended by Martin Steinwender to deal with Measured coordinates.
6
 *
7
 * Original copyright notice:
8
 *
9
 * The JTS Topology Suite is a collection of Java classes that
10
 * implement the fundamental operations required to validate a given
11
 * geo-spatial data set to a known topological specification.
12
 *
13
 * Copyright (C) 2001 Vivid Solutions
14
 *
15
 * This library is free software; you can redistribute it and/or
16
 * modify it under the terms of the GNU Lesser General Public
17
 * License as published by the Free Software Foundation; either
18
 * version 2.1 of the License, or (at your option) any later version.
19
 *
20
 * This library is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23
 * Lesser General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Lesser General Public
26
 * License along with this library; if not, write to the Free Software
27
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
28
 *
29
 * For more information, contact:
30
 *
31
 *     Vivid Solutions
32
 *     Suite #1A
33
 *     2328 Government Street
34
 *     Victoria BC  V8T 5G5
35
 *     Canada
36
 *
37
 *     (250)385-6040
38
 *     www.vividsolutions.com
39
 */
40

    
41

    
42
package org.gvsig.fmap.geom.jts.operation.fromwkt;
43

    
44
import com.vividsolutions.jts.geom.Coordinate;
45
import com.vividsolutions.jts.geom.CoordinateSequence;
46
import com.vividsolutions.jts.geom.GeometryFactory;
47
import com.vividsolutions.jts.geom.PrecisionModel;
48
import com.vividsolutions.jts.io.ParseException;
49
import com.vividsolutions.jts.io.WKTWriter;
50
import com.vividsolutions.jts.util.Assert;
51
import java.io.IOException;
52
import java.io.Reader;
53
import java.io.StreamTokenizer;
54
import java.io.StringReader;
55
import java.util.ArrayList;
56
import org.apache.commons.lang3.StringUtils;
57
import org.gvsig.fmap.geom.Geometry;
58
import org.gvsig.fmap.geom.Geometry.SUBTYPES;
59
import org.gvsig.fmap.geom.Geometry.TYPES;
60
import org.gvsig.fmap.geom.GeometryLocator;
61
import org.gvsig.fmap.geom.GeometryManager;
62
import org.gvsig.fmap.geom.aggregate.MultiLine;
63
import org.gvsig.fmap.geom.aggregate.MultiPoint;
64
import org.gvsig.fmap.geom.aggregate.MultiPolygon;
65
import org.gvsig.fmap.geom.exception.CreateGeometryException;
66
import org.gvsig.fmap.geom.jts.mgeom.MCoordinate;
67
import org.gvsig.fmap.geom.jts.mgeom.MGeometryFactory;
68
import org.gvsig.fmap.geom.primitive.Line;
69
import org.gvsig.fmap.geom.primitive.Point;
70
import org.gvsig.fmap.geom.primitive.Polygon;
71
import org.gvsig.fmap.geom.primitive.Ring;
72
import org.gvsig.tools.locator.LocatorException;
73

    
74
/**
75
 * Converts a geometry in EWKT to a JTS-Geometry.
76
 * <p/>
77
 * <code>EWKTReader</code> supports
78
 * extracting <code>Geometry</code> objects from either {@link java.io.Reader}s or
79
 * {@link String}s. This allows it to function as a parser to read <code>Geometry</code>
80
 * objects from text blocks embedded in other data formats (e.g. XML). <P>
81
 * <p/>
82
 * A <code>WKTReader</code> is parameterized by a <code>GeometryFactory</code>,
83
 * to allow it to create <code>Geometry</code> objects of the appropriate
84
 * implementation. In particular, the <code>GeometryFactory</code>
85
 * determines the <code>PrecisionModel</code> and <code>SRID</code> that is
86
 * used. <P>
87
 * <p/>
88
 * The <code>WKTReader</code> converts all input numbers to the precise
89
 * internal representation.
90
 * <p/>
91
 * <h3>Notes:</h3>
92
 * <ul>
93
 * <li>The reader supports non-standard "LINEARRING" tags.
94
 * <li>The reader uses Double.parseDouble to perform the conversion of ASCII
95
 * numbers to floating point.  This means it supports the Java
96
 * syntax for floating point literals (including scientific notation).
97
 * </ul>
98
 * <p/>
99
 * <h3>Syntax</h3>
100
 * The following syntax specification describes the version of Well-Known Text
101
 * supported by JTS.
102
 * (The specification uses a syntax language similar to that used in
103
 * the C and Java language specifications.)
104
 * <p/>
105
 * <p/>
106
 * <blockquote><pre>
107
 * <i>WKTGeometry:</i> one of<i>
108
 * <p/>
109
 *       WKTPoint  WKTLineString  WKTLinearRing  WKTPolygon
110
 *       WKTMultiPoint  WKTMultiLineString  WKTMultiPolygon
111
 *       WKTGeometryCollection</i>
112
 * <p/>
113
 * <i>WKTPoint:</i> <b>POINT ( </b><i>Coordinate</i> <b>)</b>
114
 * <p/>
115
 * <i>WKTLineString:</i> <b>LINESTRING</b> <i>CoordinateSequence</i>
116
 * <p/>
117
 * <i>WKTLinearRing:</i> <b>LINEARRING</b> <i>CoordinateSequence</i>
118
 * <p/>
119
 * <i>WKTPolygon:</i> <b>POLYGON</b> <i>CoordinateSequenceList</i>
120
 * <p/>
121
 * <i>WKTMultiPoint:</i> <b>MULTIPOINT</b> <i>CoordinateSequence</i>
122
 * <p/>
123
 * <i>WKTMultiLineString:</i> <b>MULTILINESTRING</b> <i>CoordinateSequenceList</i>
124
 * <p/>
125
 * <i>WKTMultiPolygon:</i>
126
 *         <b>MULTIPOLYGON (</b> <i>CoordinateSequenceList {</i> , <i>CoordinateSequenceList }</i> <b>)</b>
127
 * <p/>
128
 * <i>WKTGeometryCollection: </i>
129
 *         <b>GEOMETRYCOLLECTION (</b> <i>WKTGeometry {</i> , <i>WKTGeometry }</i> <b>)</b>
130
 * <p/>
131
 * <i>CoordinateSequenceList:</i>
132
 *         <b>(</b> <i>CoordinateSequence {</i> <b>,</b> <i>CoordinateSequence }</i> <b>)</b>
133
 * <p/>
134
 * <i>CoordinateSequence:</i>
135
 *         <b>(</b> <i>Coordinate {</i> , <i>Coordinate }</i> <b>)</b>
136
 * <p/>
137
 * <i>Coordinate:
138
 *         Number Number Number<sub>opt</sub></i>
139
 * <p/>
140
 * <i>Number:</i> A Java-style floating-point number
141
 * <p/>
142
 * </pre></blockquote>
143
 *
144
 * @see WKTWriter
145
 */
146
public class EWKTParser {
147
    private static final String EMPTY = "EMPTY";
148
    private static final String COMMA = ",";
149
    private static final String L_PAREN = "(";
150
    private static final String R_PAREN = ")";
151
    private static final String EQUALS = "=";
152
    private static final String SEMICOLON = ";";
153

    
154
    private static final String Z = "Z";
155
    private static final String ZM = "ZM";
156
    private static final String M = "M";
157

    
158
    private GeometryFactory geometryFactory;
159
    private PrecisionModel precisionModel;
160
    private StreamTokenizer tokenizer;
161

    
162
    private int dimension = -1;
163
    private Boolean hasM = null;
164

    
165
    private GeometryManager manager = GeometryLocator.getGeometryManager();
166

    
167

    
168
    /**
169
     * Creates a reader that creates objects using the default {@link GeometryFactory}.
170
     */
171
    public EWKTParser() {
172
        this(new MGeometryFactory());
173
    }
174

    
175
    /**
176
     * Creates a reader that creates objects using the given
177
     * {@link GeometryFactory}.
178
     *
179
     * @param geometryFactory the factory used to create <code>Geometry</code>s.
180
     */
181
    public EWKTParser(GeometryFactory geometryFactory) {
182
        this.geometryFactory = geometryFactory;
183
        precisionModel = geometryFactory.getPrecisionModel();
184
    }
185

    
186
    /**
187
     * Reads a Well-Known Text representation of a {@link com.vividsolutions.jts.geom.Geometry}
188
     * from a {@link String}.
189
     *
190
     * @param wellKnownText one or more <Geometry Tagged Text>strings (see the OpenGIS
191
     *                      Simple Features Specification) separated by whitespace
192
     * @return a <code>Geometry</code> specified by <code>wellKnownText</code>
193
     * @throws com.vividsolutions.jts.io.ParseException
194
     *          if a parsing problem occurs
195
     */
196
    public Geometry read(String wellKnownText) throws ParseException {
197
        StringReader reader = new StringReader(wellKnownText);
198
        try {
199
            return read(reader);
200
        }
201
        finally {
202
            reader.close();
203
        }
204
    }
205

    
206
    /**
207
     * Reads a Well-Known Text representation of a {@link Geometry}
208
     * from a {@link java.io.Reader}.
209
     *
210
     * @param reader a Reader which will return a <Geometry Tagged Text>
211
     *               string (see the OpenGIS Simple Features Specification)
212
     * @return a <code>Geometry</code> read from <code>reader</code>
213
     * @throws ParseException if a parsing problem occurs
214
     */
215
    public Geometry read(Reader reader) throws ParseException {
216

    
217
        try {
218

    
219
            synchronized (this) {
220
                if (this.tokenizer != null) {
221
                    throw new RuntimeException("EWKT-Reader is already in use.");
222
                }
223
                tokenizer = new StreamTokenizer(reader);
224
            }
225

    
226
            // set tokenizer to NOT parse numbers
227
            tokenizer.resetSyntax();
228
            tokenizer.wordChars('a', 'z');
229
            tokenizer.wordChars('A', 'Z');
230
            tokenizer.wordChars(128 + 32, 255);
231
            tokenizer.wordChars('0', '9');
232
            tokenizer.wordChars('-', '-');
233
            tokenizer.wordChars('+', '+');
234
            tokenizer.wordChars('.', '.');
235
            tokenizer.whitespaceChars(0, ' ');
236
            tokenizer.commentChar('#');
237

    
238
            this.hasM = null;
239
            this.dimension = -1;
240

    
241
            return readGeometryTaggedText();
242

    
243
        } catch (IOException | CreateGeometryException e) {
244
            throw new ParseException(e.toString());
245
        } finally {
246
            this.tokenizer = null;
247
        }
248
    }
249

    
250
    /**
251
     * Returns the next array of <code>Coordinate</code>s in the stream.
252
     *
253
     * @param tokenizer tokenizer over a stream of text in Well-known Text
254
     *                  format. The next element returned by the stream should be L_PAREN (the
255
     *                  beginning of "(x1 y1, x2 y2, ..., xn yn)") or EMPTY.
256
     * @return the next array of <code>Coordinate</code>s in the
257
     *         stream, or an empty array if EMPTY is the next element returned by
258
     *         the stream.
259
     * @throws IOException    if an I/O error occurs
260
     * @throws ParseException if an unexpected token was encountered
261
     */
262
    private MCoordinate[] getCoordinates()
263
            throws IOException, ParseException {
264
        String nextToken = getNextEmptyOrOpenerOrDimension();
265
        if (nextToken.equals(EMPTY)) {
266
            return new MCoordinate[]{};
267
        }
268
        if (nextToken.equals(Z)) {
269
            this.dimension = 3;
270
            setHasM(false);
271
            nextToken = getNextEmptyOrOpener();
272
            if (nextToken.equals(EMPTY)) {
273
                return new MCoordinate[]{};
274
            }
275
        }
276
        if (nextToken.equals(M)) {
277
            this.dimension = 3;
278
            setHasM(true);
279
            nextToken = getNextEmptyOrOpener();
280
            if (nextToken.equals(EMPTY)) {
281
                return new MCoordinate[]{};
282
            }
283
        }
284
        if (nextToken.equals(ZM)) {
285
            this.dimension = 4;
286
            setHasM(true);
287
            nextToken = getNextEmptyOrOpener();
288
            if (nextToken.equals(EMPTY)) {
289
                return new MCoordinate[]{};
290
            }
291
        }
292

    
293
        ArrayList coordinates = new ArrayList();
294
        coordinates.add(getPreciseCoordinate());
295
        nextToken = getNextCloserOrComma();
296
        while (nextToken.equals(COMMA)) {
297
            coordinates.add(getPreciseCoordinate());
298
            nextToken = getNextCloserOrComma();
299
        }
300
        return (MCoordinate[]) coordinates.toArray(new MCoordinate[coordinates.size()]);
301
    }
302

    
303
    /**
304
     * gets the next Coordinate and checks dimension
305
     *
306
     * @return
307
     * @throws IOException
308
     * @throws ParseException
309
     */
310
    private Coordinate getPreciseCoordinate()
311
            throws IOException, ParseException {
312
        MCoordinate coord = new MCoordinate();
313
        coord.x = getNextNumber();
314
        coord.y = getNextNumber();
315

    
316
        Double thirdOrdinateValue = null;
317
        Double fourthOrdinateValue = null;
318

    
319
        if (this.dimension == 3) {
320
            thirdOrdinateValue = getNextNumber();
321
        } else if (this.dimension == 4) {
322
            thirdOrdinateValue = getNextNumber();
323
            fourthOrdinateValue = getNextNumber();
324
        } else if (this.dimension < 0) {
325
            if (isNumberNext()) thirdOrdinateValue = getNextNumber();
326
            if (isNumberNext()) fourthOrdinateValue = getNextNumber();
327

    
328
            if (fourthOrdinateValue != null) {
329
                this.dimension = 4;
330
                setHasM(true);
331
            } else if (thirdOrdinateValue != null) {
332
                this.dimension = 3;
333
                setHasM(Boolean.TRUE.equals(this.hasM));
334
            } else {
335
                this.dimension = 2;
336
                setHasM(false);
337
            }
338
        }
339

    
340
        switch (this.dimension) {
341
            case 2:
342
                break;
343
            case 3:
344
                if (this.hasM) {
345
                    coord.m = thirdOrdinateValue;
346
                } else {
347
                    coord.z = thirdOrdinateValue;
348
                }
349
                break;
350
            case 4:
351
                if (this.hasM) {
352
                    coord.z = thirdOrdinateValue;
353
                    coord.m = fourthOrdinateValue;
354
                } else {
355
                    throw new ParseException("Unsupported geometry dimension.");
356
                }
357
                break;
358
            default:
359
                throw new ParseException("Unsupported geometry dimension.");
360
        }
361

    
362
        precisionModel.makePrecise(coord);
363
        return coord;
364
    }
365

    
366

    
367
    private boolean isNumberNext() throws IOException {
368
        int type = tokenizer.nextToken();
369
        tokenizer.pushBack();
370
        return type == StreamTokenizer.TT_WORD;
371
    }
372

    
373
    /**
374
     * Parses the next number in the stream.
375
     * Numbers with exponents are handled.
376
     *
377
     * @param tokenizer tokenizer over a stream of text in Well-known Text
378
     *                  format. The next token must be a number.
379
     * @return the next number in the stream
380
     * @throws ParseException if the next token is not a valid number
381
     * @throws IOException    if an I/O error occurs
382
     */
383
    private double getNextNumber() throws IOException,
384
            ParseException {
385
        int type = tokenizer.nextToken();
386
        switch (type) {
387
            case StreamTokenizer.TT_WORD: {
388
                try {
389
                    return Double.parseDouble(tokenizer.sval);
390
                }
391
                catch (NumberFormatException ex) {
392
                    throw new ParseException("Invalid number: " + tokenizer.sval);
393
                }
394
            }
395
        }
396
        parseError("number");
397
        return 0.0;
398
    }
399

    
400
    /**
401
     * Returns the next EMPTY or L_PAREN in the stream as uppercase text.
402
     *
403
     * @param tokenizer tokenizer over a stream of text in Well-known Text
404
     *                  format. The next token must be EMPTY or L_PAREN.
405
     * @return the next EMPTY or L_PAREN in the stream as uppercase
406
     *         text.
407
     * @throws ParseException if the next token is not EMPTY or L_PAREN
408
     * @throws IOException    if an I/O error occurs
409
     */
410
    private String getNextEmptyOrOpener() throws IOException, ParseException {
411
        String nextWord = getNextWord();
412
        if (nextWord.equals(EMPTY) || nextWord.equals(L_PAREN)) {
413
            return nextWord;
414
        }
415
        parseError(EMPTY + " or " + L_PAREN);
416
        return null;
417
    }
418

    
419
    /**
420
     * Returns the next EMPTY, L_PAREN, Z or ZM in the stream as uppercase text.
421
     *
422
     * @param tokenizer tokenizer over a stream of text in Well-known Text
423
     *                  format. The next token must be EMPTY or L_PAREN.
424
     * @return the next EMPTY or L_PAREN in the stream as uppercase
425
     *         text.
426
     * @throws ParseException if the next token is not EMPTY or L_PAREN
427
     * @throws IOException    if an I/O error occurs
428
     */
429
    private String getNextEmptyOrOpenerOrDimension() throws IOException, ParseException {
430
        String nextWord = getNextWord();
431
        if (nextWord.equals(EMPTY) || nextWord.equals(L_PAREN) || nextWord.equalsIgnoreCase(Z) || nextWord.equalsIgnoreCase(M) || nextWord.equalsIgnoreCase(ZM)) {
432
            return nextWord;
433
        }
434
        parseError(EMPTY + " or " + L_PAREN + " or " + Z + " or " + M + " or " + ZM + " or ");
435
        return null;
436
    }
437

    
438
    /**
439
     * Returns the next R_PAREN or COMMA in the stream.
440
     *
441
     * @param tokenizer tokenizer over a stream of text in Well-known Text
442
     *                  format. The next token must be R_PAREN or COMMA.
443
     * @return the next R_PAREN or COMMA in the stream
444
     * @throws ParseException if the next token is not R_PAREN or COMMA
445
     * @throws IOException    if an I/O error occurs
446
     */
447
    private String getNextCloserOrComma() throws IOException, ParseException {
448
        String nextWord = getNextWord();
449
        if (nextWord.equals(COMMA) || nextWord.equals(R_PAREN)) {
450
            return nextWord;
451
        }
452
        parseError(COMMA + " or " + R_PAREN);
453
        return null;
454
    }
455

    
456
    /**
457
     * Returns the next R_PAREN in the stream.
458
     *
459
     * @param tokenizer tokenizer over a stream of text in Well-known Text
460
     *                  format. The next token must be R_PAREN.
461
     * @return the next R_PAREN in the stream
462
     * @throws ParseException if the next token is not R_PAREN
463
     * @throws IOException    if an I/O error occurs
464
     */
465
    private String getNextCloser() throws IOException, ParseException {
466
        String nextWord = getNextWord();
467
        if (nextWord.equals(R_PAREN)) {
468
            return nextWord;
469
        }
470
        parseError(R_PAREN);
471
        return null;
472
    }
473

    
474
    /**
475
     * Returns the next R_PAREN in the stream.
476
     *
477
     * @param tokenizer tokenizer over a stream of text in Well-known Text
478
     *                  format. The next token must be R_PAREN.
479
     * @return the next R_PAREN in the stream
480
     * @throws ParseException if the next token is not R_PAREN
481
     * @throws IOException    if an I/O error occurs
482
     */
483
    private int getSRID() throws IOException, ParseException {
484
        if (!getNextWord().equals(EQUALS)) {
485
            parseError(EQUALS);
486
            return 0;
487
        }
488
        int srid = Integer.parseInt(getNextWord());
489
        if (!getNextWord().equals(SEMICOLON)) {
490
            parseError(SEMICOLON);
491
            return 0;
492
        }
493
        return srid;
494
    }
495

    
496
    /**
497
     * Returns the next word in the stream.
498
     *
499
     * @param tokenizer tokenizer over a stream of text in Well-known Text
500
     *                  format. The next token must be a word.
501
     * @return the next word in the stream as uppercase text
502
     * @throws ParseException if the next token is not a word
503
     * @throws IOException    if an I/O error occurs
504
     */
505
    private String getNextWord() throws IOException, ParseException {
506
        int type = tokenizer.nextToken();
507
        switch (type) {
508
            case StreamTokenizer.TT_WORD:
509

    
510
                String word = tokenizer.sval;
511
                if (word.equalsIgnoreCase(EMPTY))
512
                    return EMPTY;
513
                return word;
514

    
515
            case '(':
516
                return L_PAREN;
517
            case ')':
518
                return R_PAREN;
519
            case ',':
520
                return COMMA;
521
            case '=':
522
                return EQUALS;
523
            case ';':
524
                return SEMICOLON;
525
        }
526
        parseError("word");
527
        return null;
528
    }
529

    
530
    /**
531
     * Throws a formatted ParseException for the current token.
532
     *
533
     * @param expected a description of what was expected
534
     * @throws ParseException
535
     * @throws com.vividsolutions.jts.util.AssertionFailedException
536
     *                        if an invalid token is encountered
537
     */
538
    private void parseError(String expected)
539
            throws ParseException {
540
        // throws Asserts for tokens that should never be seen
541
        if (tokenizer.ttype == StreamTokenizer.TT_NUMBER)
542
            Assert.shouldNeverReachHere("Unexpected NUMBER token");
543
        if (tokenizer.ttype == StreamTokenizer.TT_EOL)
544
            Assert.shouldNeverReachHere("Unexpected EOL token");
545

    
546
        String tokenStr = tokenString();
547
        throw new ParseException("Expected " + expected + " but found " + tokenStr);
548
    }
549

    
550
    /**
551
     * Gets a description of the current token
552
     *
553
     * @return a description of the current token
554
     */
555
    private String tokenString() {
556
        switch (tokenizer.ttype) {
557
            case StreamTokenizer.TT_NUMBER:
558
                return "<NUMBER>";
559
            case StreamTokenizer.TT_EOL:
560
                return "End-of-Line";
561
            case StreamTokenizer.TT_EOF:
562
                return "End-of-Stream";
563
            case StreamTokenizer.TT_WORD:
564
                return "'" + tokenizer.sval + "'";
565
        }
566
        return "'" + (char) tokenizer.ttype + "'";
567
    }
568

    
569
    /**
570
     * Creates a <code>Geometry</code> using the next token in the stream.
571
     *
572
     * @param tokenizer tokenizer over a stream of text in Well-known Text
573
     *                  format. The next tokens must form a &lt;Geometry Tagged Text&gt;.
574
     * @return a <code>Geometry</code> specified by the next token
575
     *         in the stream
576
     * @throws ParseException if the coordinates used to create a <code>Polygon</code>
577
     *                        shell and holes do not form closed linestrings, or if an unexpected
578
     *                        token was encountered
579
     * @throws IOException    if an I/O error occurs
580
     * @throws CreateGeometryException
581
     */
582
    private Geometry readGeometryTaggedText() throws IOException, ParseException, CreateGeometryException {
583

    
584
        String type = null;
585
        Geometry geom;
586

    
587
        int srid = geometryFactory.getSRID();
588

    
589
        try {
590
            String firstWord = getNextWord();
591
            if ("SRID".equals(firstWord)) {
592
                srid = getSRID();
593
                type = getNextWord();
594
            } else {
595
                type = firstWord;
596
            }
597
        } catch (IOException e) {
598
            return null;
599
        } catch (ParseException e) {
600
            return null;
601
        }
602

    
603
        if (type.equals("POINT")) {
604
            geom = readPointText();
605
        } else if (type.equals("POINTZ")) {
606
            this.dimension = 3;
607
            geom = readPointText();
608
        } else if (type.equals("POINTM")) {
609
            setHasM(true);
610
            geom = readPointText();
611
        } else if (type.equals("POINTZM")) {
612
            setHasM(true);
613
            this.dimension = 4;
614
            geom = readPointText();
615
        } else if (type.equalsIgnoreCase("LINESTRING")) {
616
            geom = readLineStringText();
617
        } else if (type.equalsIgnoreCase("LINESTRINGZ")) {
618
            this.dimension = 3;
619
            geom = readLineStringText();
620
        } else if (type.equalsIgnoreCase("LINESTRINGM")) {
621
            setHasM(true);
622
            this.dimension = 3;
623
            geom = readLineStringText();
624
        } else if (type.equalsIgnoreCase("LINESTRINGZM")) {
625
            setHasM(true);
626
            this.dimension = 4;
627
            geom = readLineStringText();
628
        } else if (type.equalsIgnoreCase("LINEARRING")) {
629
            geom = readLinearRingText();
630
        } else if (type.equalsIgnoreCase("LINEARRINGZ")) {
631
            this.dimension = 3;
632
            geom = readLinearRingText();
633
        } else if (type.equalsIgnoreCase("LINEARRINGM")) {
634
            setHasM(true);
635
            this.dimension = 3;
636
            geom = readLinearRingText();
637
        } else if (type.equalsIgnoreCase("LINEARRINGZM")) {
638
            setHasM(true);
639
            this.dimension = 4;
640
            geom = readLinearRingText();
641
        } else if (type.equalsIgnoreCase("POLYGON")) {
642
            geom = readPolygonText();
643
        } else if (type.equalsIgnoreCase("POLYGONZ")) {
644
            this.dimension = 3;
645
            geom = readPolygonText();
646
        } else if (type.equalsIgnoreCase("POLYGONM")) {
647
            setHasM(true);
648
            this.dimension = 3;
649
            geom = readPolygonText();
650
//            throw new RuntimeException("PolygonM is not supported.");
651
        } else if (type.equalsIgnoreCase("POLYGONZM")) {
652
            setHasM(true);
653
            this.dimension = 4;
654
            geom = readPolygonText();
655
//            throw new RuntimeException("PolygonM is not supported.");
656
        } else if (type.equalsIgnoreCase("MULTIPOINT")) {
657
            geom = readMultiPointText();
658
        } else if (type.equalsIgnoreCase("MULTIPOINTZ")) {
659
            this.dimension = 3;
660
            geom = readMultiPointText();
661
        } else if (type.equalsIgnoreCase("MULTIPOINTM")) {
662
            setHasM(true);
663
            this.dimension = 3;
664
            geom = readMultiPointText();
665
        } else if (type.equalsIgnoreCase("MULTIPOINTZM")) {
666
            setHasM(true);
667
            this.dimension = 4;
668
            geom = readMultiPointText();
669
        } else if (type.equalsIgnoreCase("MULTILINESTRING")) {
670
            geom = readMultiLineStringText();
671
        } else if (type.equalsIgnoreCase("MULTILINESTRINGZ")) {
672
            this.dimension = 3;
673
            geom = readMultiLineStringText();
674
        } else if (type.equalsIgnoreCase("MULTILINESTRINGM")) {
675
            setHasM(true);
676
            this.dimension = 3;
677
            geom = readMultiLineStringText();
678
        } else if (type.equalsIgnoreCase("MULTILINESTRINGZM")) {
679
            setHasM(true);
680
            this.dimension = 4;
681
            geom = readMultiLineStringText();
682
        } else if (type.equalsIgnoreCase("MULTIPOLYGON")) {
683
            geom = readMultiPolygonText();
684
        } else if (type.equalsIgnoreCase("MULTIPOLYGONZ")) {
685
            this.dimension = 3;
686
            geom = readMultiPolygonText();
687
        } else if (type.equalsIgnoreCase("MULTIPOLYGONM")) {
688
            setHasM(true);
689
            this.dimension = 3;
690
            geom = readMultiPolygonText();
691
        } else if (type.equalsIgnoreCase("MULTIPOLYGONZM")) {
692
            setHasM(true);
693
            this.dimension = 4;
694
            geom = readMultiPolygonText();
695
//            throw new RuntimeException("MultiPolygonM is not supported.");
696
//        } else if (type.equalsIgnoreCase("GEOMETRYCOLLECTION")) {
697
//            geom = readGeometryCollectionText();
698
//        } else if (type.equalsIgnoreCase("GEOMETRYCOLLECTIONM")) {
699
//            setHasM(true);
700
//            geom = readGeometryCollectionText();
701
        } else {
702
            throw new ParseException("Unknown geometry type: " + type);
703
        }
704
//        geom.setSRID(srid);
705

    
706
        return geom;
707
    }
708

    
709
    /**
710
     * m-values sicherstellen
711
     *
712
     * @throws ParseException
713
     */
714
    private void setHasM(boolean hasM) throws ParseException {
715
        if (this.hasM == null) {
716
            this.hasM = hasM;
717
        } else if (this.hasM != hasM) {
718
            throw new ParseException("Inconsistent use of m-values.");
719
        }
720
    }
721

    
722
    /**
723
     * Creates a <code>Point</code> using the next token in the stream.
724
     *
725
     * @param tokenizer tokenizer over a stream of text in Well-known Text
726
     *                  format. The next tokens must form a &lt;Point Text&gt;.
727
     * @return a <code>Point</code> specified by the next token in
728
     *         the stream
729
     * @throws IOException    if an I/O error occurs
730
     * @throws ParseException if an unexpected token was encountered
731
     * @throws LocatorException
732
     * @throws CreateGeometryException
733
     */
734
    private Point readPointText() throws IOException, ParseException, CreateGeometryException {
735

    
736
        String nextToken = getNextEmptyOrOpenerOrDimension();
737
        if (nextToken.equals(EMPTY)) {
738
            return (Point)manager.create(TYPES.POINT, this.getSubtype());
739
        }
740
        if (nextToken.equals(Z)) {
741
            this.dimension = 3;
742
            setHasM(false);
743
            nextToken = getNextEmptyOrOpener();
744
            if (nextToken.equals(EMPTY)) {
745
                return (Point)manager.create(TYPES.POINT, this.getSubtype());
746
            }
747

    
748
        }else if (nextToken.equals(M)) {
749
            this.dimension = 3;
750
            setHasM(true);
751
            nextToken = getNextEmptyOrOpener();
752
            if (nextToken.equals(EMPTY)) {
753
                return (Point)manager.create(TYPES.POINT, this.getSubtype());
754
            }
755

    
756
        }else if (nextToken.equals(ZM)) {
757
            this.dimension = 4;
758
            setHasM(true);
759
            nextToken = getNextEmptyOrOpener();
760
            if (nextToken.equals(EMPTY)) {
761
                return (Point)manager.create(TYPES.POINT, this.getSubtype());
762
            }
763

    
764
        }
765

    
766
        int subtype = this.getSubtype();
767
        Coordinate coordinates = getPreciseCoordinate();
768
        Point point = (Point)manager.create(TYPES.POINT, subtype);
769
        fillPoint(subtype, coordinates, point);
770
        getNextCloser();
771
        return point;
772
    }
773

    
774
    /**
775
     * @param subtype
776
     * @param coordinates
777
     * @param point
778
     */
779
    private void fillPoint(int subtype, Coordinate coordinates, Point point) {
780
        point.setX(coordinates.x);
781
        point.setY(coordinates.y);
782
        if (subtype == Geometry.SUBTYPES.GEOM2DM) {
783
            point.setCoordinateAt(2, coordinates.getOrdinate(CoordinateSequence.M));
784
        } else if (subtype == Geometry.SUBTYPES.GEOM3DM) {
785
            point.setCoordinateAt(2, coordinates.getOrdinate(CoordinateSequence.Z));
786
            point.setCoordinateAt(3, coordinates.getOrdinate(CoordinateSequence.M));
787
        } else if (subtype == Geometry.SUBTYPES.GEOM3D) {
788
            point.setCoordinateAt(2, coordinates.getOrdinate(CoordinateSequence.Z));
789
        }
790
    }
791

    
792
    /**
793
     * @return
794
     */
795
    private int getSubtype() {
796
        int subtype;
797
        switch (this.dimension) {
798
        case 4:
799
            subtype = SUBTYPES.GEOM3DM;
800
            break;
801
        case 3:
802
            if(this.hasM){
803
                subtype = SUBTYPES.GEOM2DM;
804
            } else {
805
                subtype = SUBTYPES.GEOM3D;
806
            }
807
            break;
808
        default:
809
            subtype = SUBTYPES.GEOM2D;
810
        }
811
        return subtype;
812
    }
813

    
814
    /**
815
     * Creates a <code>LineString</code> using the next token in the stream.
816
     *
817
     * @param tokenizer tokenizer over a stream of text in Well-known Text
818
     *                  format. The next tokens must form a &lt;LineString Text&gt;.
819
     * @return a <code>LineString</code> specified by the next
820
     *         token in the stream
821
     * @throws IOException    if an I/O error occurs
822
     * @throws ParseException if an unexpected token was encountered
823
     * @throws CreateGeometryException
824
     */
825
    private Line readLineStringText() throws IOException, ParseException, CreateGeometryException {
826

    
827
        MCoordinate[] coords = getCoordinates();
828
        Line line = (Line) manager.create(TYPES.LINE, getSubtype());
829

    
830
        int subtype = this.getSubtype();
831
        for(int i=0; i<coords.length; i++){
832
            Point point = (Point)manager.create(TYPES.POINT, getSubtype());
833
            fillPoint(subtype, coords[i], point);
834
            line.addVertex(point);
835
        }
836
        return line;
837
    }
838

    
839
    /**
840
     * Creates a <code>LinearRing</code> using the next token in the stream.
841
     *
842
     * @param tokenizer tokenizer over a stream of text in Well-known Text
843
     *                  format. The next tokens must form a &lt;LineString Text&gt;.
844
     * @return a <code>LinearRing</code> specified by the next
845
     *         token in the stream
846
     * @throws IOException    if an I/O error occurs
847
     * @throws ParseException if the coordinates used to create the <code>LinearRing</code>
848
     *                        do not form a closed linestring, or if an unexpected token was
849
     *                        encountered
850
     * @throws CreateGeometryException
851
     */
852
    private Ring readLinearRingText()
853
            throws IOException, ParseException, CreateGeometryException {
854
        MCoordinate[] coords = getCoordinates();
855
        int subtype = getSubtype();
856
        Ring ring = (Ring) manager.create(TYPES.RING, subtype);
857
        for (int i=0; i < coords.length; i++)
858
        {
859
            Point point = (Point)manager.create(TYPES.POINT, subtype);
860
            fillPoint(subtype, coords[i], point);
861
            ring.addVertex(point);
862
        }
863
        return ring;
864
    }
865

    
866
    /**
867
     * Creates a <code>MultiPoint</code> using the next token in the stream.
868
     *
869
     * @param tokenizer tokenizer over a stream of text in Well-known Text
870
     *                  format. The next tokens must form a &lt;MultiPoint Text&gt;.
871
     * @return a <code>MultiPoint</code> specified by the next
872
     *         token in the stream
873
     * @throws IOException    if an I/O error occurs
874
     * @throws ParseException if an unexpected token was encountered
875
     * @throws CreateGeometryException
876
    */
877
    private MultiPoint readMultiPointText() throws IOException, ParseException, CreateGeometryException { 
878
        String nextToken = getNextEmptyOrOpenerOrDimension();
879
        if (nextToken.equals(Z)) {
880
            this.dimension = 3;
881
            setHasM(false);
882
            nextToken = getNextEmptyOrOpener();
883
        } else if (nextToken.equals(M)) {
884
            this.dimension = 3;
885
            setHasM(true);
886
            nextToken = getNextEmptyOrOpener();
887
        } else if (nextToken.equals(ZM)) {
888
            this.dimension = 4;
889
            setHasM(true);
890
            nextToken = getNextEmptyOrOpener();
891
        } else {
892
            setHasM(false);
893
            this.dimension = 2;
894
        }
895

    
896
        int subtype = getSubtype();
897
        MultiPoint multiPoint = (MultiPoint)manager.create(TYPES.MULTIPOINT, subtype);
898
        if (nextToken.equals(EMPTY)) {
899
            return multiPoint;
900
        }
901

    
902
        int token = tokenizer.nextToken();
903
        tokenizer.pushBack();
904
        if(token == '('){
905
            String stoken;
906
            do {
907
                Point p = readPointText();
908
                multiPoint.addPoint(p);
909
                stoken = getNextCloserOrComma();
910
            } while (StringUtils.equalsIgnoreCase(stoken, COMMA));
911
        } else {
912
            String stoken;
913
            do {
914
                Coordinate coordinates = getPreciseCoordinate();
915
                Point point = (Point) manager.create(TYPES.POINT, subtype);
916
                fillPoint(subtype, coordinates, point);
917
                multiPoint.addPoint(point);
918
                stoken = getNextCloserOrComma();
919
            } while (StringUtils.equalsIgnoreCase(stoken, COMMA));
920
        }
921
        return multiPoint;
922
    }
923

    
924
    /**
925
     * Creates an array of <code>Point</code>s having the given <code>Coordinate</code>
926
     * s.
927
     *
928
     * @param coordinates the <code>Coordinate</code>s with which to create the
929
     *                    <code>Point</code>s
930
     * @return <code>Point</code>s created using this <code>WKTReader</code>
931
     *         s <code>GeometryFactory</code>
932
     */
933
    private Point[] toPoints(Coordinate[] coordinates) {
934
        ArrayList points = new ArrayList();
935
        for (int i = 0; i < coordinates.length; i++) {
936
            points.add(geometryFactory.createPoint(coordinates[i]));
937
        }
938
        return (Point[]) points.toArray(new Point[]{});
939
    }
940

    
941
    /**
942
     * Creates a <code>Polygon</code> using the next token in the stream.
943
     *
944
     * @param hasM
945
     * @param tokenizer tokenizer over a stream of text in Well-known Text
946
     *                  format. The next tokens must form a &lt;Polygon Text&gt;.
947
     * @return a <code>Polygon</code> specified by the next token
948
     *         in the stream
949
     * @throws ParseException if the coordinates used to create the <code>Polygon</code>
950
     *                        shell and holes do not form closed linestrings, or if an unexpected
951
     *                        token was encountered.
952
     * @throws IOException    if an I/O error occurs
953
     * @throws CreateGeometryException
954
     */
955
    private Polygon readPolygonText() throws IOException, ParseException, CreateGeometryException {
956

    
957

    
958
//        // PolygonM is not supported
959
//        setHasM(false);
960
//
961
        String nextToken = getNextEmptyOrOpenerOrDimension();
962
        if (nextToken.equals(EMPTY)) {
963
            return (Polygon) manager.create(TYPES.POLYGON, getSubtype());
964
        }
965
        if (nextToken.equals(Z)) {
966
            this.dimension = 3;
967
            setHasM(false);
968
            nextToken = getNextEmptyOrOpener();
969
            if (nextToken.equals(EMPTY)) {
970
                return (Polygon) manager.create(TYPES.POLYGON, getSubtype());
971
            }
972
        }
973
        if (nextToken.equals(M)) {
974
            this.dimension = 3;
975
            setHasM(true);
976
            nextToken = getNextEmptyOrOpener();
977
            if (nextToken.equals(EMPTY)) {
978
                return (Polygon) manager.create(TYPES.POLYGON, getSubtype());
979
            }
980
        }
981
        if (nextToken.equals(ZM)) {
982
            this.dimension = 4;
983
            setHasM(true);
984
            nextToken = getNextEmptyOrOpener();
985
            if (nextToken.equals(EMPTY)) {
986
                return (Polygon) manager.create(TYPES.POLYGON, getSubtype());
987
            }
988
        }
989

    
990
        Polygon polygon = (Polygon) manager.create(TYPES.POLYGON, getSubtype());
991

    
992
        ArrayList holes = new ArrayList();
993
        Ring shell = readLinearRingText();
994
        for (int i = 0; i < shell.getNumVertices(); i++) {
995
            polygon.addVertex(shell.getVertex(i));
996
        }
997

    
998
        nextToken = getNextCloserOrComma();
999
        while (nextToken.equals(COMMA)) {
1000
            Ring hole = readLinearRingText();
1001
            polygon.addInteriorRing(hole);
1002
            nextToken = getNextCloserOrComma();
1003
        }
1004
        return polygon;
1005
    }
1006

    
1007
    /**
1008
     * Creates a <code>MultiLineString</code> using the next token in the stream.
1009
     *
1010
     * @param hasM
1011
     * @param tokenizer tokenizer over a stream of text in Well-known Text
1012
     *                  format. The next tokens must form a &lt;MultiLineString Text&gt;.
1013
     * @return a <code>MultiLineString</code> specified by the
1014
     *         next token in the stream
1015
     * @throws IOException    if an I/O error occurs
1016
     * @throws ParseException if an unexpected token was encountered
1017
     * @throws CreateGeometryException
1018
     */
1019
    private MultiLine readMultiLineStringText() throws IOException, ParseException, CreateGeometryException {
1020

    
1021
         String nextToken = getNextEmptyOrOpenerOrDimension();
1022
         if (nextToken.equals(EMPTY)) {
1023
             return (MultiLine)manager.create(TYPES.MULTILINE, getSubtype());
1024
         }
1025
         if (nextToken.equals(Z)) {
1026
             this.dimension = 3;
1027
             setHasM(false);
1028
             nextToken = getNextEmptyOrOpener();
1029
             if (nextToken.equals(EMPTY)) {
1030
                 return (MultiLine)manager.create(TYPES.MULTILINE, getSubtype());
1031
             }
1032

    
1033
         }
1034
         if (nextToken.equals(M)) {
1035
             this.dimension = 3;
1036
             setHasM(true);
1037
             nextToken = getNextEmptyOrOpener();
1038
             if (nextToken.equals(EMPTY)) {
1039
                 return (MultiLine)manager.create(TYPES.MULTILINE, getSubtype());
1040
             }
1041
         }
1042
         if (nextToken.equals(ZM)) {
1043
             this.dimension = 4;
1044
             setHasM(true);
1045
             nextToken = getNextEmptyOrOpener();
1046
             if (nextToken.equals(EMPTY)) {
1047
                 return (MultiLine)manager.create(TYPES.MULTILINE, getSubtype());
1048
             }
1049
         }
1050

    
1051
         MultiLine multiline = (MultiLine)manager.create(TYPES.MULTILINE, getSubtype());
1052

    
1053

    
1054
        Line line = readLineStringText();
1055
        if(line!=null){
1056
            multiline.addCurve(line);
1057
        }
1058
        nextToken = getNextCloserOrComma();
1059
        while (nextToken.equals(COMMA)) {
1060
            line = readLineStringText();
1061
            if(line!=null){
1062
                multiline.addCurve(line);
1063
            }
1064
            nextToken = getNextCloserOrComma();
1065
        }
1066

    
1067
        return multiline;
1068
    }
1069

    
1070
    /**
1071
     * Creates a <code>MultiPolygon</code> using the next token in the stream.
1072
     *
1073
     * @param hasM
1074
     * @param tokenizer tokenizer over a stream of text in Well-known Text
1075
     *                  format. The next tokens must form a &lt;MultiPolygon Text&gt;.
1076
     * @return a <code>MultiPolygon</code> specified by the next
1077
     *         token in the stream, or if if the coordinates used to create the
1078
     *         <code>Polygon</code> shells and holes do not form closed linestrings.
1079
     * @throws IOException    if an I/O error occurs
1080
     * @throws ParseException if an unexpected token was encountered
1081
     * @throws CreateGeometryException
1082
     */
1083
    private MultiPolygon readMultiPolygonText()
1084
            throws IOException, ParseException, CreateGeometryException {
1085

    
1086

    
1087
        String nextToken = getNextEmptyOrOpenerOrDimension();
1088
        if (nextToken.equals(Z)) {
1089
            this.dimension = 3;
1090
            setHasM(false);
1091
            nextToken = getNextEmptyOrOpener();
1092
        } else if (nextToken.equals(M)) {
1093
            this.dimension = 3;
1094
            setHasM(true);
1095
            nextToken = getNextEmptyOrOpener();
1096
        } else if (nextToken.equals(ZM)) {
1097
            this.dimension = 4;
1098
            setHasM(true);
1099
            nextToken = getNextEmptyOrOpener();
1100
        } else {
1101
            setHasM(false);
1102
            this.dimension = 2;
1103
        }
1104

    
1105
        MultiPolygon multiPolygon = (MultiPolygon)manager.create(TYPES.MULTIPOLYGON, getSubtype());
1106
        if (nextToken.equals(EMPTY)) {
1107
            return multiPolygon;
1108
        }
1109

    
1110
        Polygon polygon = readPolygonText();
1111

    
1112
        multiPolygon.addSurface(polygon);
1113
        nextToken = getNextCloserOrComma();
1114
        while (nextToken.equals(COMMA)) {
1115
            polygon = readPolygonText();
1116
            multiPolygon.addSurface(polygon);
1117
            nextToken = getNextCloserOrComma();
1118
        }
1119
        return multiPolygon;
1120
    }
1121

    
1122
    /**
1123
     * Creates a <code>GeometryCollection</code> using the next token in the
1124
     * stream.
1125
     *
1126
     * @param tokenizer tokenizer over a stream of text in Well-known Text
1127
     *                  format. The next tokens must form a &lt;GeometryCollection Text&gt;.
1128
     * @return a <code>GeometryCollection</code> specified by the
1129
     *         next token in the stream
1130
     * @throws ParseException if the coordinates used to create a <code>Polygon</code>
1131
     *                        shell and holes do not form closed linestrings, or if an unexpected
1132
     *                        token was encountered
1133
     * @throws IOException    if an I/O error occurs
1134
     */
1135
//    private GeometryCollection readGeometryCollectionText()
1136
//            throws IOException, ParseException {
1137
//
1138
//        String nextToken = getNextEmptyOrOpener();
1139
//        if (nextToken.equals(EMPTY)) {
1140
//            return geometryFactory.createGeometryCollection(new Geometry[]{});
1141
//        }
1142
//        ArrayList geometries = new ArrayList();
1143
//        Geometry geometry = readGeometryTaggedText();
1144
//        geometries.add(geometry);
1145
//        nextToken = getNextCloserOrComma();
1146
//        while (nextToken.equals(COMMA)) {
1147
//            geometry = readGeometryTaggedText();
1148
//            geometries.add(geometry);
1149
//            nextToken = getNextCloserOrComma();
1150
//        }
1151
//        Geometry[] array = new Geometry[geometries.size()];
1152
//        return geometryFactory.createGeometryCollection((Geometry[]) geometries.toArray(array));
1153
//    }
1154

    
1155
}