1
2
3
4
5
6
7
8
9 package org.locationtech.spatial4j.shape.jts;
10
11 import org.locationtech.spatial4j.context.SpatialContext;
12 import org.locationtech.spatial4j.context.jts.DatelineRule;
13 import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
14 import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory;
15 import org.locationtech.spatial4j.context.jts.ValidationRule;
16 import org.locationtech.spatial4j.exception.InvalidShapeException;
17 import org.locationtech.spatial4j.io.ShapeReader;
18 import org.locationtech.spatial4j.shape.Circle;
19 import org.locationtech.spatial4j.shape.Point;
20 import org.locationtech.spatial4j.shape.Rectangle;
21 import org.locationtech.spatial4j.shape.Shape;
22 import org.locationtech.spatial4j.shape.impl.ShapeFactoryImpl;
23 import org.locationtech.jts.algorithm.CGAlgorithms;
24 import org.locationtech.jts.geom.*;
25 import org.locationtech.jts.util.GeometricShapeFactory;
26
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.List;
30
31
32
33
34
35
36
37 public class JtsShapeFactory extends ShapeFactoryImpl {
38
39 protected static final LinearRing[] EMPTY_HOLES = new LinearRing[0];
40
41 protected final GeometryFactory geometryFactory;
42
43 protected final boolean allowMultiOverlap;
44 protected final boolean useJtsPoint;
45 protected final boolean useJtsLineString;
46 protected final boolean useJtsMulti;
47 protected final DatelineRule datelineRule;
48 protected final ValidationRule validationRule;
49 protected final boolean autoIndex;
50
51
52
53
54 public JtsShapeFactory(JtsSpatialContext ctx, JtsSpatialContextFactory factory) {
55 super(ctx, factory);
56 this.geometryFactory = factory.getGeometryFactory();
57
58 this.allowMultiOverlap = factory.allowMultiOverlap;
59 this.useJtsPoint = factory.useJtsPoint;
60 this.useJtsLineString = factory.useJtsLineString;
61 this.useJtsMulti = factory.useJtsMulti;
62 this.datelineRule = factory.datelineRule;
63 this.validationRule = factory.validationRule;
64 this.autoIndex = factory.autoIndex;
65 }
66
67
68
69
70
71
72
73
74 public boolean isAllowMultiOverlap() {
75 return allowMultiOverlap;
76 }
77
78
79
80
81 public DatelineRule getDatelineRule() {
82 return datelineRule;
83 }
84
85
86
87
88
89 public ValidationRule getValidationRule() {
90 return validationRule;
91 }
92
93
94
95
96
97
98 public boolean isAutoIndex() {
99 return autoIndex;
100 }
101
102 @Override
103 public double normX(double x) {
104 x = super.normX(x);
105 return geometryFactory.getPrecisionModel().makePrecise(x);
106 }
107
108 @Override
109 public double normY(double y) {
110 y = super.normY(y);
111 return geometryFactory.getPrecisionModel().makePrecise(y);
112 }
113
114 @Override
115 public double normZ(double z) {
116 z = super.normZ(z);
117 return geometryFactory.getPrecisionModel().makePrecise(z);
118 }
119
120 @Override
121 public double normDist(double d) {
122 return geometryFactory.getPrecisionModel().makePrecise(d);
123 }
124
125
126
127
128
129
130
131 public Geometry getGeometryFrom(Shape shape) {
132 if (shape instanceof JtsGeometry) {
133 return ((JtsGeometry)shape).getGeom();
134 }
135 if (shape instanceof JtsPoint) {
136 return ((JtsPoint) shape).getGeom();
137 }
138 if (shape instanceof Point) {
139 Point point = (Point) shape;
140 return geometryFactory.createPoint(new Coordinate(point.getX(),point.getY()));
141 }
142 if (shape instanceof Rectangle) {
143 Rectangle r = (Rectangle)shape;
144 if (r.getCrossesDateLine()) {
145 Collection<Geometry> pair = new ArrayList<>(2);
146 pair.add(geometryFactory.toGeometry(new Envelope(
147 r.getMinX(), ctx.getWorldBounds().getMaxX(), r.getMinY(), r.getMaxY())));
148 pair.add(geometryFactory.toGeometry(new Envelope(
149 ctx.getWorldBounds().getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY())));
150 return geometryFactory.buildGeometry(pair);
151 } else {
152 return geometryFactory.toGeometry(new Envelope(r.getMinX(), r.getMaxX(), r.getMinY(), r.getMaxY()));
153 }
154 }
155 if (shape instanceof Circle) {
156
157
158
159 Circle circle = (Circle)shape;
160 GeometricShapeFactory gsf = new GeometricShapeFactory(geometryFactory);
161 gsf.setWidth(circle.getBoundingBox().getWidth());
162 gsf.setHeight(circle.getBoundingBox().getHeight());
163 gsf.setNumPoints(4*25);
164 gsf.setCentre(new Coordinate(circle.getCenter().getX(), circle.getCenter().getY()));
165 Geometry geom = gsf.createCircle();
166 if (circle.getBoundingBox().getCrossesDateLine())
167
168 geom = new JtsGeometry(geom, (JtsSpatialContext) getSpatialContext(),false, false).getGeom();
169 return geom;
170 }
171
172 throw new InvalidShapeException("can't make Geometry from: " + shape);
173 }
174
175
176 public boolean useJtsPoint() {
177 return useJtsPoint;
178 }
179
180 @Override
181 public Point pointXY(double x, double y) {
182 return pointXYZ(x, y, Coordinate.NULL_ORDINATE);
183 }
184
185 @Override
186 public Point pointXYZ(double x, double y, double z) {
187 if (!useJtsPoint())
188 return super.pointXY(x, y);
189
190 verifyX(x);
191 verifyY(y);
192 verifyZ(z);
193
194 Coordinate coord = Double.isNaN(x) ? null : new Coordinate(x, y, z);
195 return new JtsPoint(geometryFactory.createPoint(coord), (JtsSpatialContext) ctx);
196 }
197
198
199 public boolean useJtsLineString() {
200
201
202 return useJtsLineString;
203 }
204
205 @Override
206 public Shape lineString(List<Point> points, double bufferDistance) {
207 if (!useJtsLineString())
208 return super.lineString(points, bufferDistance);
209
210 Coordinate[] coords = new Coordinate[points.size()];
211 for (int i = 0; i < coords.length; i++) {
212 Point p = points.get(i);
213 if (p instanceof JtsPoint) {
214 JtsPoint jtsPoint = (JtsPoint) p;
215 coords[i] = jtsPoint.getGeom().getCoordinate();
216 } else {
217 coords[i] = new Coordinate(p.getX(), p.getY());
218 }
219 }
220 JtsGeometry shape = makeShape(geometryFactory.createLineString(coords));
221 if(bufferDistance!=0) {
222 return shape.getBuffered(bufferDistance, ctx);
223 }
224 return shape;
225 }
226
227 @Override
228 public LineStringBuilder lineString() {
229 if (!useJtsLineString())
230 return super.lineString();
231 return new JtsLineStringBuilder();
232 }
233
234 private class JtsLineStringBuilder extends CoordinatesAccumulator<JtsLineStringBuilder>
235 implements LineStringBuilder {
236 protected double bufDistance;
237
238 public JtsLineStringBuilder() {
239 }
240
241 @Override
242 public LineStringBuilder buffer(double distance) {
243 this.bufDistance = distance;
244 return this;
245 }
246
247 @Override
248 public Shape build() {
249 Geometry geom = buildLineStringGeom();
250 if (bufDistance != 0.0) {
251 geom = geom.buffer(bufDistance);
252 }
253 return makeShape(geom);
254 }
255
256 LineString buildLineStringGeom() {
257 return geometryFactory.createLineString(getCoordsArray());
258 }
259 }
260
261 @Override
262 public PolygonBuilder polygon() {
263 return new JtsPolygonBuilder();
264 }
265
266 private class JtsPolygonBuilder extends CoordinatesAccumulator<JtsPolygonBuilder>
267 implements PolygonBuilder {
268
269 List<LinearRing> holes;
270
271 @Override
272 public JtsHoleBuilder hole() {
273 return new JtsHoleBuilder();
274 }
275
276 private class JtsHoleBuilder extends CoordinatesAccumulator<JtsHoleBuilder>
277 implements PolygonBuilder.HoleBuilder {
278
279 @Override
280 public JtsPolygonBuilder endHole() {
281 LinearRing linearRing = geometryFactory.createLinearRing(getCoordsArray());
282 if (JtsPolygonBuilder.this.holes == null) {
283 JtsPolygonBuilder.this.holes = new ArrayList<>(4);
284 }
285 JtsPolygonBuilder.this.holes.add(linearRing);
286 return JtsPolygonBuilder.this;
287 }
288 }
289
290 @Override
291 public Shape build() {
292 return makeShapeFromGeometry(buildPolygonGeom());
293 }
294
295 @Override
296 public Shape buildOrRect() {
297 Polygon geom = buildPolygonGeom();
298 if (geom.isRectangle()) {
299 return makeRectFromRectangularPoly(geom);
300 }
301 return makeShapeFromGeometry(geom);
302 }
303
304 Polygon buildPolygonGeom() {
305 LinearRing outerRing = geometryFactory.createLinearRing(getCoordsArray());
306 LinearRing[] holeRings = holes == null ? EMPTY_HOLES : holes.toArray(new LinearRing[this.holes.size()]);
307 return geometryFactory.createPolygon(outerRing, holeRings);
308 }
309
310 }
311
312 private abstract class CoordinatesAccumulator<T extends CoordinatesAccumulator> {
313 protected List<Coordinate> coordinates = new ArrayList<>();
314
315 public T pointXY(double x, double y) {
316 return pointXYZ(x, y, Coordinate.NULL_ORDINATE);
317 }
318
319 public T pointXYZ(double x, double y, double z) {
320 verifyX(x);
321 verifyY(y);
322 coordinates.add(new Coordinate(x, y, z));
323 return getThis();
324 }
325
326
327
328
329
330
331 protected Coordinate[] getCoordsArray() {
332 return coordinates.toArray(new Coordinate[coordinates.size()]);
333 }
334
335 @SuppressWarnings("unchecked")
336 protected T getThis() { return (T) this; }
337 }
338
339
340
341
342 public boolean useJtsMulti() {
343 return useJtsMulti;
344 }
345
346 @Override
347 public MultiPointBuilder multiPoint() {
348 if (!useJtsMulti) {
349 return super.multiPoint();
350 }
351 return new JtsMultiPointBuilder();
352 }
353
354 private class JtsMultiPointBuilder extends CoordinatesAccumulator<JtsMultiPointBuilder> implements MultiPointBuilder {
355 @Override
356 public Shape build() {
357 return makeShape(geometryFactory.createMultiPoint(getCoordsArray()));
358 }
359 }
360
361 @Override
362 public MultiLineStringBuilder multiLineString() {
363 if (!useJtsMulti) {
364 return super.multiLineString();
365 }
366 return new JtsMultiLineStringBuilder();
367 }
368
369 private class JtsMultiLineStringBuilder implements MultiLineStringBuilder {
370 List<LineString> geoms = new ArrayList<>();
371
372 @Override
373 public LineStringBuilder lineString() {
374 return new JtsLineStringBuilder();
375 }
376
377 @Override
378 public MultiLineStringBuilder add(LineStringBuilder lineStringBuilder) {
379 geoms.add(((JtsLineStringBuilder)lineStringBuilder).buildLineStringGeom());
380 return this;
381 }
382
383 @Override
384 public Shape build() {
385 return makeShape(geometryFactory.createMultiLineString(geoms.toArray(new LineString[geoms.size()])));
386 }
387 }
388
389 @Override
390 public MultiPolygonBuilder multiPolygon() {
391 if (!useJtsMulti) {
392 return super.multiPolygon();
393 }
394 return new JtsMultiPolygonBuilder();
395 }
396
397 private class JtsMultiPolygonBuilder implements MultiPolygonBuilder {
398 List<Polygon> geoms = new ArrayList<>();
399
400 @Override
401 public PolygonBuilder polygon() {
402 return new JtsPolygonBuilder();
403 }
404
405 @Override
406 public MultiPolygonBuilder add(PolygonBuilder polygonBuilder) {
407 geoms.add(((JtsPolygonBuilder)polygonBuilder).buildPolygonGeom());
408 return this;
409 }
410
411 @Override
412 public Shape build() {
413 return makeShape(geometryFactory.createMultiPolygon(geoms.toArray(new Polygon[geoms.size()])));
414 }
415 }
416
417 @Override
418 public <T extends Shape> MultiShapeBuilder<T> multiShape(Class<T> shapeClass) {
419 if (!useJtsMulti()) {
420 return super.multiShape(shapeClass);
421 }
422 return new JtsMultiShapeBuilder<>();
423 }
424
425
426
427 private class JtsMultiShapeBuilder<T extends Shape> extends GeneralShapeMultiShapeBuilder<T> {
428 @Override
429 public Shape build() {
430 Class<?> last = null;
431 List<Geometry> geoms = new ArrayList<>(shapes.size());
432 for(Shape s : shapes) {
433 if (last != null && last != s.getClass()) {
434 return super.build();
435 }
436 if (s instanceof JtsGeometry) {
437 geoms.add(((JtsGeometry)s).getGeom());
438 } else if (s instanceof JtsPoint) {
439 geoms.add(((JtsPoint)s).getGeom());
440 } else {
441 return super.build();
442 }
443 last = s.getClass();
444 }
445
446 return makeShapeFromGeometry(geometryFactory.buildGeometry(geoms));
447 }
448 }
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467 public Shape makeShapeFromGeometry(Geometry geom) {
468 if (geom instanceof GeometryCollection) {
469
470
471 if (!useJtsMulti || geom.getClass() == GeometryCollection.class) {
472 List<Shape> shapes = new ArrayList<>(geom.getNumGeometries());
473 for (int i = 0; i < geom.getNumGeometries(); i++) {
474 Geometry geomN = geom.getGeometryN(i);
475 shapes.add(makeShapeFromGeometry(geomN));
476 }
477 return multiShape(shapes);
478 }
479 } else if (geom instanceof org.locationtech.jts.geom.Point) {
480 org.locationtech.jts.geom.Point pt = (org.locationtech.jts.geom.Point) geom;
481 if (pt.isEmpty()) {
482 return pointXY(Double.NaN, Double.NaN);
483 } else {
484 return pointXY(pt.getX(), pt.getY());
485 }
486 } else if (geom instanceof LineString) {
487 if (!useJtsLineString()) {
488 LineString lineString = (LineString) geom;
489 List<Point> points = new ArrayList<>(lineString.getNumPoints());
490 for (int i = 0; i < lineString.getNumPoints(); i++) {
491 Coordinate coord = lineString.getCoordinateN(i);
492 points.add(pointXY(coord.x, coord.y));
493 }
494 return lineString(points, 0);
495 }
496 }
497
498 JtsGeometry jtsGeom;
499 try {
500 jtsGeom = makeShape(geom);
501 if (getValidationRule() != ValidationRule.none)
502 jtsGeom.validate();
503 } catch (RuntimeException e) {
504
505 if (getValidationRule() == ValidationRule.repairConvexHull) {
506 jtsGeom = makeShape(geom.convexHull());
507 } else if (getValidationRule() == ValidationRule.repairBuffer0) {
508 jtsGeom = makeShape(geom.buffer(0));
509 } else {
510
511
512
513 throw e;
514 }
515 }
516 return jtsGeom;
517 }
518
519
520
521
522
523
524
525
526
527
528
529
530 public JtsGeometry makeShape(Geometry geom, boolean dateline180Check, boolean allowMultiOverlap) {
531 JtsGeometry jtsGeom = new JtsGeometry(geom, (JtsSpatialContext) ctx, dateline180Check, allowMultiOverlap);
532 if (isAutoIndex()) {
533 jtsGeom.index();
534 }
535 return jtsGeom;
536 }
537
538
539
540
541
542
543
544
545
546
547 public JtsGeometry makeShape(Geometry geom) {
548 return makeShape(geom, datelineRule != DatelineRule.none, allowMultiOverlap);
549 }
550
551 public GeometryFactory getGeometryFactory() {
552 return geometryFactory;
553 }
554
555
556
557
558
559
560
561 public Rectangle makeRectFromRectangularPoly(Geometry geom) {
562
563
564 assert geom.isRectangle();
565 Envelope env = geom.getEnvelopeInternal();
566 boolean crossesDateline = false;
567 if (ctx.isGeo() && getDatelineRule() != DatelineRule.none) {
568 if (getDatelineRule() == DatelineRule.ccwRect) {
569
570 crossesDateline = !CGAlgorithms.isCCW(geom.getCoordinates());
571 } else {
572 crossesDateline = env.getWidth() > 180;
573 }
574 }
575 if (crossesDateline)
576 return rect(env.getMaxX(), env.getMinX(), env.getMinY(), env.getMaxY());
577 else
578 return rect(env.getMinX(), env.getMaxX(), env.getMinY(), env.getMaxY());
579 }
580
581 }