1
2
3
4
5
6
7
8
9
10
11
12 package org.locationtech.spatial4j.io;
13
14
15 import org.locationtech.spatial4j.context.SpatialContext;
16 import org.locationtech.spatial4j.context.SpatialContextFactory;
17 import org.locationtech.spatial4j.exception.InvalidShapeException;
18 import org.locationtech.spatial4j.shape.Shape;
19 import org.locationtech.spatial4j.shape.ShapeFactory;
20
21 import java.io.IOException;
22 import java.io.Reader;
23 import java.text.ParseException;
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 public class WKTReader implements ShapeReader {
59 protected final SpatialContext ctx;
60 protected final ShapeFactory shapeFactory;
61
62
63
64
65
66
67
68 public WKTReader(SpatialContext ctx, SpatialContextFactory factory) {
69 this.ctx = ctx;
70 this.shapeFactory = ctx.getShapeFactory();
71 }
72
73
74
75
76
77
78
79
80 public Shape parse(String wktString) throws ParseException, InvalidShapeException {
81 Shape shape = parseIfSupported(wktString);
82 if (shape != null)
83 return shape;
84 String shortenedString =
85 (wktString.length() <= 128 ? wktString : wktString.substring(0, 128 - 3) + "...");
86 throw new ParseException("Unknown Shape definition [" + shortenedString + "]", 0);
87 }
88
89
90
91
92
93
94
95
96
97
98
99 public Shape parseIfSupported(String wktString) throws ParseException, InvalidShapeException {
100 State state = newState(wktString);
101 state.nextIfWhitespace();
102 if (state.eof())
103 return null;
104
105 if (!Character.isLetter(state.rawString.charAt(state.offset)))
106 return null;
107 String shapeType = state.nextWord();
108 Shape result = null;
109 try {
110 result = parseShapeByType(state, shapeType);
111 } catch (ParseException | InvalidShapeException e) {
112 throw e;
113 } catch (IllegalArgumentException e) {
114 throw new InvalidShapeException(e.getMessage(), e);
115 } catch (Exception e) {
116 ParseException pe = new ParseException(e.toString(), state.offset);
117 pe.initCause(e);
118 throw pe;
119 }
120 if (result != null && !state.eof())
121 throw new ParseException("end of shape expected", state.offset);
122 return result;
123 }
124
125
126
127
128
129 protected State newState(String wktString) {
130
131
132
133 return new State(wktString);
134 }
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151 protected Shape parseShapeByType(State state, String shapeType) throws ParseException {
152 assert Character.isLetter(shapeType.charAt(0)) : "Shape must start with letter: " + shapeType;
153
154 if (shapeType.equalsIgnoreCase("POINT")) {
155 return parsePointShape(state);
156 } else if (shapeType.equalsIgnoreCase("MULTIPOINT")) {
157 return parseMultiPointShape(state);
158 } else if (shapeType.equalsIgnoreCase("ENVELOPE")) {
159 return parseEnvelopeShape(state);
160 } else if (shapeType.equalsIgnoreCase("LINESTRING")) {
161 return parseLineStringShape(state);
162 } else if (shapeType.equalsIgnoreCase("POLYGON")) {
163 return parsePolygonShape(state);
164 } else if (shapeType.equalsIgnoreCase("GEOMETRYCOLLECTION")) {
165 return parseGeometryCollectionShape(state);
166 } else if (shapeType.equalsIgnoreCase("MULTILINESTRING")) {
167 return parseMultiLineStringShape(state);
168 } else if (shapeType.equalsIgnoreCase("MULTIPOLYGON")) {
169 return parseMulitPolygonShape(state);
170 }
171
172 if (shapeType.equalsIgnoreCase("BUFFER")) {
173 return parseBufferShape(state);
174 }
175
176
177 return null;
178 }
179
180
181
182
183
184
185
186
187
188
189 protected Shape parseBufferShape(State state) throws ParseException {
190 state.nextExpect('(');
191 Shape shape = shape(state);
192 state.nextExpect(',');
193 double distance = shapeFactory.normDist(state.nextDouble());
194 state.nextExpect(')');
195 return shape.getBuffered(distance, ctx);
196 }
197
198
199
200
201
202
203
204
205
206
207 protected Shape parsePointShape(State state) throws ParseException {
208 if (state.nextIfEmptyAndSkipZM())
209 return shapeFactory.pointXY(Double.NaN, Double.NaN);
210 state.nextExpect('(');
211 OnePointsBuilder onePointsBuilder = new OnePointsBuilder(shapeFactory);
212 point(state, onePointsBuilder);
213 state.nextExpect(')');
214 return onePointsBuilder.getPoint();
215 }
216
217
218
219
220
221
222
223
224
225
226
227
228 protected Shape parseMultiPointShape(State state) throws ParseException {
229 ShapeFactory.MultiPointBuilder builder = shapeFactory.multiPoint();
230 if (state.nextIfEmptyAndSkipZM())
231 return builder.build();
232 state.nextExpect('(');
233 do {
234 boolean openParen = state.nextIf('(');
235 point(state, builder);
236 if (openParen)
237 state.nextExpect(')');
238 } while (state.nextIf(','));
239 state.nextExpect(')');
240 return builder.build();
241 }
242
243
244
245
246
247
248
249
250
251
252
253 protected Shape parseEnvelopeShape(State state) throws ParseException {
254
255 state.nextExpect('(');
256 double x1 = state.nextDouble();
257 state.nextExpect(',');
258 double x2 = state.nextDouble();
259 state.nextExpect(',');
260 double y2 = state.nextDouble();
261 state.nextExpect(',');
262 double y1 = state.nextDouble();
263 state.nextExpect(')');
264 return shapeFactory.rect(shapeFactory.normX(x1), shapeFactory.normX(x2),
265 shapeFactory.normY(y1), shapeFactory.normY(y2));
266 }
267
268
269
270
271
272
273
274
275
276
277 protected Shape parseLineStringShape(State state) throws ParseException {
278 ShapeFactory.LineStringBuilder lineStringBuilder = shapeFactory.lineString();
279 if (state.nextIfEmptyAndSkipZM())
280 return lineStringBuilder.build();
281 return pointList(state, lineStringBuilder).build();
282 }
283
284
285
286
287
288
289
290
291
292
293 protected Shape parseMultiLineStringShape(State state) throws ParseException {
294 ShapeFactory.MultiLineStringBuilder multiLineStringBuilder = shapeFactory.multiLineString();
295 if (!state.nextIfEmptyAndSkipZM()) {
296 state.nextExpect('(');
297 do {
298 multiLineStringBuilder.add(pointList(state, multiLineStringBuilder.lineString()));
299 } while (state.nextIf(','));
300 state.nextExpect(')');
301 }
302 return multiLineStringBuilder.build();
303 }
304
305
306
307
308
309
310
311
312
313 protected Shape parsePolygonShape(WKTReader.State state) throws ParseException {
314 ShapeFactory.PolygonBuilder polygonBuilder = shapeFactory.polygon();
315 if (!state.nextIfEmptyAndSkipZM()) {
316 polygonBuilder = polygon(state, polygonBuilder);
317 }
318 return polygonBuilder.buildOrRect();
319 }
320
321
322
323
324
325
326
327
328 protected Shape parseMulitPolygonShape(WKTReader.State state) throws ParseException {
329 ShapeFactory.MultiPolygonBuilder multiPolygonBuilder = shapeFactory.multiPolygon();
330 if (!state.nextIfEmptyAndSkipZM()) {
331 state.nextExpect('(');
332 do {
333 multiPolygonBuilder.add(polygon(state, multiPolygonBuilder.polygon()));
334 } while (state.nextIf(','));
335 state.nextExpect(')');
336 }
337 return multiPolygonBuilder.build();
338 }
339
340
341
342
343
344
345
346
347 protected Shape parseGeometryCollectionShape(State state) throws ParseException {
348 ShapeFactory.MultiShapeBuilder<Shape> multiShapeBuilder = shapeFactory.multiShape(Shape.class);
349 if (state.nextIfEmptyAndSkipZM())
350 return multiShapeBuilder.build();
351 state.nextExpect('(');
352 do {
353 multiShapeBuilder.add(shape(state));
354 } while (state.nextIf(','));
355 state.nextExpect(')');
356 return multiShapeBuilder.build();
357 }
358
359
360
361
362
363
364 protected Shape shape(State state) throws ParseException {
365 String type = state.nextWord();
366 Shape shape = parseShapeByType(state, type);
367 if (shape == null)
368 throw new ParseException("Shape of type " + type + " is unknown", state.offset);
369 return shape;
370 }
371
372
373
374
375
376
377
378
379
380
381 protected <B extends ShapeFactory.PointsBuilder> B pointList(State state, B pointsBuilder) throws ParseException {
382 state.nextExpect('(');
383 do {
384 point(state, pointsBuilder);
385 } while (state.nextIf(','));
386 state.nextExpect(')');
387 return pointsBuilder;
388 }
389
390
391
392
393
394
395
396
397
398 protected ShapeFactory.PointsBuilder point(State state, ShapeFactory.PointsBuilder pointsBuilder) throws ParseException {
399 double x = state.nextDouble();
400 double y = state.nextDouble();
401 state.skipNextDoubles();
402 pointsBuilder.pointXY(shapeFactory.normX(x), shapeFactory.normY(y));
403 return pointsBuilder;
404 }
405
406
407
408
409 protected ShapeFactory.PolygonBuilder polygon(WKTReader.State state, ShapeFactory.PolygonBuilder polygonBuilder) throws ParseException {
410 state.nextExpect('(');
411 pointList(state, polygonBuilder);
412 while (state.nextIf(',')) {
413 ShapeFactory.PolygonBuilder.HoleBuilder holeBuilder = polygonBuilder.hole();
414 pointList(state, holeBuilder);
415 holeBuilder.endHole();
416 }
417 state.nextExpect(')');
418 return polygonBuilder;
419 }
420
421
422 public class State {
423
424 public String rawString;
425
426 public int offset;
427
428 public String dimension;
429
430 public State(String rawString) {
431 this.rawString = rawString;
432 }
433
434 public SpatialContext getCtx() {
435 return ctx;
436 }
437
438 public WKTReader getParser() {
439 return WKTReader.this;
440 }
441
442
443
444
445
446
447
448
449 public String nextWord() throws ParseException {
450 int startOffset = offset;
451 while (offset < rawString.length()
452 && Character.isJavaIdentifierPart(rawString.charAt(offset))) {
453 offset++;
454 }
455 if (startOffset == offset)
456 throw new ParseException("Word expected", startOffset);
457 String result = rawString.substring(startOffset, offset);
458 nextIfWhitespace();
459 return result;
460 }
461
462
463
464
465
466
467
468
469
470
471
472 public boolean nextIfEmptyAndSkipZM() throws ParseException {
473 if (eof())
474 return false;
475 char c = rawString.charAt(offset);
476 if (c == '(' || !Character.isJavaIdentifierPart(c))
477 return false;
478 String word = nextWord();
479 if (word.equalsIgnoreCase("EMPTY"))
480 return true;
481
482 this.dimension = word;
483
484 if (eof())
485 return false;
486 c = rawString.charAt(offset);
487 if (c == '(' || !Character.isJavaIdentifierPart(c))
488 return false;
489 word = nextWord();
490 if (word.equalsIgnoreCase("EMPTY"))
491 return true;
492 throw new ParseException("Expected EMPTY because found dimension; but got [" + word + "]",
493 offset);
494 }
495
496
497
498
499
500
501
502 public double nextDouble() throws ParseException {
503 int startOffset = offset;
504 skipDouble();
505 if (startOffset == offset)
506 throw new ParseException("Expected a number", offset);
507 double result;
508 try {
509 result = Double.parseDouble(rawString.substring(startOffset, offset));
510 } catch (Exception e) {
511 throw new ParseException(e.toString(), offset);
512 }
513 nextIfWhitespace();
514 return result;
515 }
516
517
518 public void skipDouble() {
519 int startOffset = offset;
520 for (; offset < rawString.length(); offset++) {
521 char c = rawString.charAt(offset);
522 if (!(Character.isDigit(c) || c == '.' || c == '-' || c == '+')) {
523
524 if (offset != startOffset && (c == 'e' || c == 'E'))
525 continue;
526 break;
527 }
528 }
529 }
530
531
532 public void skipNextDoubles() {
533 while (!eof()) {
534 int startOffset = offset;
535 skipDouble();
536 if (startOffset == offset)
537 return;
538 nextIfWhitespace();
539 }
540 }
541
542
543
544
545
546
547
548 public void nextExpect(char expected) throws ParseException {
549 if (eof())
550 throw new ParseException("Expected [" + expected + "] found EOF", offset);
551 char c = rawString.charAt(offset);
552 if (c != expected)
553 throw new ParseException("Expected [" + expected + "] found [" + c + "]", offset);
554 offset++;
555 nextIfWhitespace();
556 }
557
558
559 public final boolean eof() {
560 return offset >= rawString.length();
561 }
562
563
564
565
566
567
568
569
570 public boolean nextIf(char expected) {
571 if (!eof() && rawString.charAt(offset) == expected) {
572 offset++;
573 nextIfWhitespace();
574 return true;
575 }
576 return false;
577 }
578
579
580
581
582
583
584 public void nextIfWhitespace() {
585 for (; offset < rawString.length(); offset++) {
586 if (!Character.isWhitespace(rawString.charAt(offset))) {
587 return;
588 }
589 }
590 }
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612 public String nextSubShapeString() throws ParseException {
613 int startOffset = offset;
614 int parenStack = 0;
615 for (; offset < rawString.length(); offset++) {
616 char c = rawString.charAt(offset);
617 if (c == ',') {
618 if (parenStack == 0)
619 break;
620 } else if (c == ')') {
621 if (parenStack == 0)
622 break;
623 parenStack--;
624 } else if (c == '(') {
625 parenStack++;
626 }
627 }
628 if (parenStack != 0)
629 throw new ParseException("Unbalanced parenthesis", startOffset);
630 return rawString.substring(startOffset, offset);
631 }
632
633 }
634
635 @Override
636 public String getFormatName() {
637 return ShapeIO.WKT;
638 }
639
640 static String readString(Reader reader) throws IOException {
641 char[] arr = new char[1024];
642 StringBuilder buffer = new StringBuilder();
643 int numCharsRead;
644 while ((numCharsRead = reader.read(arr, 0, arr.length)) != -1) {
645 buffer.append(arr, 0, numCharsRead);
646 }
647 return buffer.toString();
648 }
649
650 @Override
651 public Shape read(Reader reader) throws IOException, ParseException {
652 return parse(readString(reader));
653 }
654
655 @Override
656 public Shape read(Object value) throws IOException, ParseException, InvalidShapeException {
657 return parse(value.toString());
658 }
659
660 @Override
661 public Shape readIfSupported(Object value) throws InvalidShapeException {
662 try {
663 return parseIfSupported(value.toString());
664 } catch (ParseException e) {
665 }
666 return null;
667 }
668 }