From 4d8af495e7507f48f1ae9104102debdd2043917b Mon Sep 17 00:00:00 2001 From: Andrew Hershberger Date: Sat, 28 Sep 2024 07:36:24 -0400 Subject: [PATCH] Update to GEOS 3.13.0, Update Apple Platform Deployment Targets, Update CI Environment (#26) * Increase minimum supported versions Fixes #23 * update to geos 3.13.0 * update ci environment * bump version in podspec * change how deployment targets are specified for cocoapods --- .github/workflows/main.yml | 94 +- Package.swift | 6 +- README.md | 6 +- Sources/geos/capi/geos_c.cpp | 172 ++- Sources/geos/capi/geos_ts_c.cpp | 912 +++++++---- Sources/geos/include/geos/algorithm/Angle.h | 58 +- Sources/geos/include/geos/algorithm/Area.h | 7 + .../include/geos/algorithm/BoundaryNodeRule.h | 4 + .../include/geos/algorithm/CGAlgorithmsDD.h | 18 +- .../geos/include/geos/algorithm/Centroid.h | 26 +- .../include/geos/algorithm/CircularArcs.h | 37 + .../geos/include/geos/algorithm/ConvexHull.h | 46 +- .../geos/include/geos/algorithm/Distance.h | 32 +- .../geos/include/geos/algorithm/HCoordinate.h | 2 +- .../geos/algorithm/InteriorPointPoint.h | 6 +- .../geos/include/geos/algorithm/Interpolate.h | 182 +++ .../include/geos/algorithm/Intersection.h | 51 +- .../include/geos/algorithm/LineIntersector.h | 345 ++++- .../geos/algorithm/MinimumAreaRectangle.h | 159 ++ .../geos/algorithm/MinimumBoundingCircle.h | 18 +- .../include/geos/algorithm/MinimumDiameter.h | 30 +- .../geos/include/geos/algorithm/Orientation.h | 4 +- .../include/geos/algorithm/PointLocation.h | 32 +- .../include/geos/algorithm/PointLocator.h | 16 +- .../geos/algorithm/PolygonNodeTopology.h | 56 +- .../geos/algorithm/RayCrossingCounter.h | 37 +- .../geos/algorithm/RayCrossingCounterDD.h | 164 -- .../geos/include/geos/algorithm/Rectangle.h | 96 ++ .../construct/IndexedDistanceToPoint.h | 88 ++ .../construct/IndexedPointInPolygonsLocator.h | 79 + .../algorithm/construct/LargestEmptyCircle.h | 75 +- .../construct/MaximumInscribedCircle.h | 35 +- .../distance/DiscreteFrechetDistance.h | 4 +- .../distance/DiscreteHausdorffDistance.h | 12 +- .../geos/algorithm/distance/DistanceToPoint.h | 8 +- .../algorithm/distance/PointPairDistance.h | 14 +- .../include/geos/algorithm/hull/ConcaveHull.h | 97 +- .../include/geos/algorithm/hull/HullTri.h | 4 + .../geos/algorithm/hull/HullTriangulation.h | 4 +- .../locate/IndexedPointInAreaLocator.h | 34 +- .../algorithm/locate/PointOnGeometryLocator.h | 4 +- .../locate/SimplePointInAreaLocator.h | 24 +- Sources/geos/include/geos/constants.h | 4 + Sources/geos/include/geos/coverage/Corner.h | 137 ++ .../coverage/CoverageBoundarySegmentFinder.h | 83 + .../geos/include/geos/coverage/CoverageEdge.h | 171 ++ .../include/geos/coverage/CoverageGapFinder.h | 106 ++ .../include/geos/coverage/CoveragePolygon.h | 57 + .../geos/coverage/CoveragePolygonValidator.h | 381 +++++ .../geos/include/geos/coverage/CoverageRing.h | 205 +++ .../include/geos/coverage/CoverageRingEdges.h | 170 ++ .../geos/coverage/CoverageSimplifier.h | 166 ++ .../include/geos/coverage/CoverageUnion.h | 77 + .../include/geos/coverage/CoverageValidator.h | 159 ++ .../geos/coverage/InvalidSegmentDetector.h | 133 ++ .../include/geos/coverage/TPVWSimplifier.h | 225 +++ .../include/geos/coverage/VertexRingCounter.h | 71 + .../geos/include/geos/edgegraph/EdgeGraph.h | 21 +- .../geos/include/geos/edgegraph/HalfEdge.h | 23 +- .../include/geos/edgegraph/MarkHalfEdge.h | 9 +- Sources/geos/include/geos/geom.h | 3 - Sources/geos/include/geos/geom/CircularArc.h | 273 ++++ .../geos/include/geos/geom/CircularString.h | 85 + .../geos/include/geos/geom/CompoundCurve.h | 121 ++ Sources/geos/include/geos/geom/Coordinate.h | 536 +++++-- .../geos/geom/CoordinateArraySequence.h | 146 -- .../geom/CoordinateArraySequenceFactory.h | 95 -- .../geos/include/geos/geom/CoordinateFilter.h | 75 +- .../geos/include/geos/geom/CoordinateList.h | 21 +- .../include/geos/geom/CoordinateSequence.h | 788 ++++++++-- .../geos/geom/CoordinateSequenceFactory.h | 109 -- .../geos/geom/CoordinateSequenceIterator.h | 126 ++ .../include/geos/geom/CoordinateSequences.h | 76 + Sources/geos/include/geos/geom/Curve.h | 71 + Sources/geos/include/geos/geom/CurvePolygon.h | 58 + .../geom/DefaultCoordinateSequenceFactory.h | 64 - Sources/geos/include/geos/geom/Envelope.h | 113 +- .../geos/geom/FixedSizeCoordinateSequence.h | 132 -- Sources/geos/include/geos/geom/Geometry.h | 135 +- .../include/geos/geom/GeometryCollection.h | 38 +- .../geos/include/geos/geom/GeometryFactory.h | 212 ++- .../geos/include/geos/geom/GeometryTypeName.h | 110 ++ .../geos/include/geos/geom/HeuristicOverlay.h | 68 +- .../include/geos/geom/IntersectionMatrix.h | 5 +- Sources/geos/include/geos/geom/LineSegment.h | 150 +- Sources/geos/include/geos/geom/LineString.h | 119 +- Sources/geos/include/geos/geom/LinearRing.h | 10 +- Sources/geos/include/geos/geom/Location.h | 2 +- Sources/geos/include/geos/geom/MultiCurve.h | 126 ++ .../geos/include/geos/geom/MultiLineString.h | 13 +- Sources/geos/include/geos/geom/MultiPoint.h | 14 +- Sources/geos/include/geos/geom/MultiPolygon.h | 14 +- Sources/geos/include/geos/geom/MultiSurface.h | 94 ++ Sources/geos/include/geos/geom/Point.h | 53 +- Sources/geos/include/geos/geom/Polygon.h | 114 +- .../geos/include/geos/geom/PrecisionModel.h | 13 +- Sources/geos/include/geos/geom/Quadrant.h | 2 +- Sources/geos/include/geos/geom/SimpleCurve.h | 142 ++ Sources/geos/include/geos/geom/Surface.h | 115 ++ Sources/geos/include/geos/geom/SurfaceImpl.h | 159 ++ Sources/geos/include/geos/geom/Triangle.h | 62 +- .../geos/geom/prep/BasicPreparedGeometry.h | 31 +- .../include/geos/geom/prep/PreparedGeometry.h | 24 + .../geos/geom/prep/PreparedLineString.h | 1 + .../geom/prep/PreparedLineStringDistance.h | 2 + .../include/geos/geom/prep/PreparedPolygon.h | 2 + .../geos/geom/prep/PreparedPolygonDistance.h | 10 +- .../geos/geom/prep/PreparedPolygonPredicate.h | 2 +- .../geom/util/ComponentCoordinateExtracter.h | 6 +- .../geos/include/geos/geom/util/Densifier.h | 2 +- .../include/geos/geom/util/GeometryEditor.h | 2 +- .../include/geos/geom/util/GeometryLister.h | 92 ++ .../geos/geom/util/GeometryTransformer.h | 14 +- .../geos/geom/util/LinearComponentExtracter.h | 2 - .../geos/geom/util/PolygonalExtracter.h | 54 + .../include/geos/geomgraph/DirectedEdge.h | 6 +- .../include/geos/geomgraph/DirectedEdgeStar.h | 5 +- Sources/geos/include/geos/geomgraph/Edge.h | 55 +- Sources/geos/include/geos/geomgraph/EdgeEnd.h | 22 +- .../geos/include/geos/geomgraph/EdgeEndStar.h | 43 +- .../include/geos/geomgraph/EdgeIntersection.h | 2 +- .../geos/geomgraph/EdgeIntersectionList.h | 4 +- .../geos/include/geos/geomgraph/EdgeList.h | 4 +- .../geos/geomgraph/EdgeNodingValidator.h | 2 +- .../geos/include/geos/geomgraph/EdgeRing.h | 6 +- .../include/geos/geomgraph/GeometryGraph.h | 2 +- .../include/geos/geomgraph/GraphComponent.h | 18 +- Sources/geos/include/geos/geomgraph/Label.h | 2 +- Sources/geos/include/geos/geomgraph/Node.h | 26 +- .../geos/include/geos/geomgraph/NodeFactory.h | 2 +- Sources/geos/include/geos/geomgraph/NodeMap.h | 28 +- .../geos/include/geos/geomgraph/PlanarGraph.h | 38 +- .../geos/index/VertexSequencePackedRtree.h | 11 +- .../geos/include/geos/index/bintree/Bintree.h | 2 +- .../include/geos/index/chain/MonotoneChain.h | 18 +- .../include/geos/index/quadtree/Quadtree.h | 2 +- .../include/geos/index/strtree/Interval.h | 3 +- .../geos/index/strtree/TemplateSTRNode.h | 2 + .../geos/index/strtree/TemplateSTRNodePair.h | 5 + .../geos/index/strtree/TemplateSTRtree.h | 85 +- .../index/strtree/TemplateSTRtreeDistance.h | 74 + .../include/geos/io/CheckOrdinatesFilter.h | 68 + Sources/geos/include/geos/io/GeoJSON.h | 11 + Sources/geos/include/geos/io/GeoJSONReader.h | 4 +- Sources/geos/include/geos/io/GeoJSONWriter.h | 26 +- Sources/geos/include/geos/io/OrdinateSet.h | 119 ++ Sources/geos/include/geos/io/WKBConstants.h | 7 +- Sources/geos/include/geos/io/WKBReader.h | 36 +- .../geos/include/geos/io/WKBStreamReader.h | 49 + Sources/geos/include/geos/io/WKBWriter.h | 37 +- Sources/geos/include/geos/io/WKTFileReader.h | 46 + Sources/geos/include/geos/io/WKTReader.h | 46 +- .../geos/include/geos/io/WKTStreamReader.h | 48 + Sources/geos/include/geos/io/WKTWriter.h | 164 +- .../geos/linearref/LengthIndexedLine.h | 2 +- .../geos/linearref/LinearGeometryBuilder.h | 6 +- .../geos/linearref/LocationIndexOfPoint.h | 10 +- Sources/geos/include/geos/math/DD.h | 1 + Sources/geos/include/geos/namespaces.h | 2 +- .../include/geos/noding/BasicSegmentString.h | 30 +- .../include/geos/noding/BoundaryChainNoder.h | 171 ++ .../include/geos/noding/IntersectionAdder.h | 4 +- .../geos/include/geos/noding/IteratedNoder.h | 2 +- .../MCIndexSegmentSetMutualIntersector.h | 42 +- .../geos/noding/NodableSegmentString.h | 4 +- .../include/geos/noding/NodedSegmentString.h | 72 +- Sources/geos/include/geos/noding/Octant.h | 8 +- .../geos/noding/SegmentExtractingNoder.h | 3 +- .../geos/noding/SegmentIntersectionDetector.h | 4 +- .../geos/include/geos/noding/SegmentNode.h | 27 +- .../include/geos/noding/SegmentNodeList.h | 37 +- .../geos/noding/SegmentSetMutualIntersector.h | 2 +- .../geos/include/geos/noding/SegmentString.h | 98 +- .../include/geos/noding/SegmentStringUtil.h | 13 +- .../include/geos/noding/snap/SnappingNoder.h | 3 +- .../include/geos/noding/snapround/HotPixel.h | 40 +- .../geos/noding/snapround/HotPixelIndex.h | 22 +- .../snapround/SnapRoundingIntersectionAdder.h | 15 +- .../geos/noding/snapround/SnapRoundingNoder.h | 6 +- .../geos/include/geos/operation/BoundaryOp.h | 2 +- .../geos/operation/GeometryGraphOperation.h | 6 +- .../geos/operation/buffer/BufferBuilder.h | 2 +- .../operation/buffer/BufferCurveSetBuilder.h | 21 +- .../include/geos/operation/buffer/BufferOp.h | 1 + .../geos/operation/buffer/OffsetCurve.h | 308 ++-- .../operation/buffer/OffsetCurveBuilder.h | 96 +- .../operation/buffer/OffsetCurveSection.h | 111 ++ .../operation/buffer/OffsetSegmentGenerator.h | 6 + .../operation/buffer/OffsetSegmentString.h | 7 +- .../operation/cluster/AbstractClusterFinder.h | 136 ++ .../include/geos/operation/cluster/Clusters.h | 72 + .../operation/cluster/DBSCANClusterFinder.h | 59 + .../operation/cluster/DisjointOperation.h | 73 + .../cluster/EnvelopeDistanceClusterFinder.h | 53 + .../cluster/EnvelopeIntersectsClusterFinder.h | 44 + .../cluster/GeometryDistanceClusterFinder.h | 59 + .../operation/cluster/GeometryFlattener.h | 46 + .../cluster/GeometryIntersectsClusterFinder.h | 53 + .../geos/operation/cluster/UnionFind.h | 137 ++ .../distance/ConnectedElementLocationFilter.h | 4 +- .../distance/ConnectedElementPointFilter.h | 8 +- .../geos/operation/distance/DistanceOp.h | 18 +- .../distance/FacetSequenceTreeBuilder.h | 4 +- .../operation/distance/GeometryLocation.h | 15 +- .../operation/distance/IndexedFacetDistance.h | 25 +- .../geos/operation/intersection/Rectangle.h | 5 +- .../intersection/RectangleIntersection.h | 2 - .../RectangleIntersectionBuilder.h | 5 +- .../geos/operation/linemerge/EdgeString.h | 5 +- .../geos/operation/overlay/EdgeSetNoder.h | 74 - .../geos/operation/overlay/ElevationMatrix.h | 111 -- .../operation/overlay/ElevationMatrixCell.h | 63 - .../geos/operation/overlay/LineBuilder.h | 138 -- .../geos/operation/overlay/OverlayOp.h | 409 ----- .../geos/operation/overlay/PointBuilder.h | 106 -- .../geos/operation/overlay/PolygonBuilder.h | 4 +- .../operation/overlay/snap/GeometrySnapper.h | 19 +- .../overlay/snap/LineStringSnapper.h | 6 +- .../operation/overlay/snap/SnapOverlayOp.h | 18 +- .../overlay/validate/OverlayResultValidator.h | 12 +- .../geos/operation/overlayng/CoverageUnion.h | 2 + .../include/geos/operation/overlayng/Edge.h | 2 +- .../operation/overlayng/EdgeNodingBuilder.h | 21 +- .../overlayng/IndexedPointOnLineLocator.h | 2 +- .../geos/operation/overlayng/LineLimiter.h | 8 +- .../geos/operation/overlayng/OverlayEdge.h | 15 +- .../operation/overlayng/OverlayEdgeRing.h | 10 +- .../operation/overlayng/OverlayMixedPoints.h | 16 +- .../geos/operation/overlayng/OverlayNG.h | 9 +- .../geos/operation/overlayng/OverlayPoints.h | 16 +- .../geos/operation/overlayng/RingClipper.h | 6 +- .../geos/operation/polygonize/BuildArea.h | 2 +- .../geos/operation/polygonize/EdgeRing.h | 13 +- .../operation/polygonize/PolygonizeGraph.h | 2 +- .../geos/operation/polygonize/Polygonizer.h | 37 +- .../operation/predicate/RectangleContains.h | 2 +- .../geos/operation/relate/EdgeEndBuilder.h | 9 +- .../geos/operation/relate/EdgeEndBundle.h | 2 +- .../geos/operation/relate/RelateComputer.h | 6 +- .../geos/operation/relate/RelateNode.h | 2 +- .../geos/operation/relate/RelateNodeGraph.h | 4 +- .../operation/relateng/AdjacentEdgeLocator.h | 124 ++ .../geos/operation/relateng/BasicPredicate.h | 105 ++ .../operation/relateng/DimensionLocation.h | 60 + .../relateng/EdgeSegmentIntersector.h | 80 + .../relateng/EdgeSegmentOverlapAction.h | 75 + .../operation/relateng/EdgeSetIntersector.h | 94 ++ .../operation/relateng/IMPatternMatcher.h | 87 ++ .../geos/operation/relateng/IMPredicate.h | 131 ++ .../relateng/IntersectionMatrixPattern.h | 65 + .../operation/relateng/LineStringExtracter.h | 77 + .../geos/operation/relateng/LinearBoundary.h | 88 ++ .../geos/operation/relateng/NodeSection.h | 166 ++ .../geos/operation/relateng/NodeSections.h | 102 ++ .../operation/relateng/PolygonNodeConverter.h | 112 ++ .../geos/operation/relateng/RelateEdge.h | 176 +++ .../geos/operation/relateng/RelateGeometry.h | 272 ++++ .../relateng/RelateMatrixPredicate.h | 85 + .../geos/operation/relateng/RelateNG.h | 295 ++++ .../geos/operation/relateng/RelateNode.h | 146 ++ .../operation/relateng/RelatePointLocator.h | 213 +++ .../geos/operation/relateng/RelatePredicate.h | 652 ++++++++ .../operation/relateng/RelateSegmentString.h | 161 ++ .../operation/relateng/TopologyComputer.h | 236 +++ .../operation/relateng/TopologyPredicate.h | 206 +++ .../operation/sharedpaths/SharedPathsOp.h | 8 +- .../operation/union/CascadedPolygonUnion.h | 2 +- .../geos/operation/union/CoverageUnion.h | 26 +- .../operation/union/DisjointSubsetUnion.h | 49 + .../geos/operation/union/UnaryUnionOp.h | 7 +- .../operation/valid/IndexedNestedHoleTester.h | 6 +- .../valid/IndexedNestedPolygonTester.h | 13 +- .../include/geos/operation/valid/IsSimpleOp.h | 27 +- .../include/geos/operation/valid/IsValidOp.h | 12 +- .../valid/PolygonIntersectionAnalyzer.h | 28 +- .../geos/operation/valid/PolygonNode.h | 113 -- .../geos/operation/valid/PolygonRing.h | 25 +- .../operation/valid/PolygonRingSelfNode.h | 31 +- .../geos/operation/valid/PolygonRingTouch.h | 14 +- .../operation/valid/PolygonTopologyAnalyzer.h | 28 +- .../operation/valid/RepeatedPointRemover.h | 6 +- .../operation/valid/RepeatedPointTester.h | 4 +- .../operation/valid/TopologyValidationError.h | 6 +- .../geos/planargraph/DirectedEdgeStar.h | 2 +- .../geos/include/geos/planargraph/NodeMap.h | 2 +- .../geos/include/geos/planargraph/Subgraph.h | 2 +- .../include/geos/precision/CommonBitsOp.h | 2 +- .../geos/precision/GeometryPrecisionReducer.h | 4 +- .../PointwisePrecisionReducerTransformer.h | 4 +- .../precision/PrecisionReducerTransformer.h | 2 +- .../geos/shape/fractal/HilbertEncoder.h | 40 +- .../geos/simplify/ComponentJumpChecker.h | 120 ++ .../simplify/DouglasPeuckerLineSimplifier.h | 31 +- .../include/geos/simplify/LineSegmentIndex.h | 2 +- .../geos/include/geos/simplify/LinkedLine.h | 84 + .../geos/include/geos/simplify/LinkedRing.h | 24 +- Sources/geos/include/geos/simplify/RingHull.h | 49 +- .../include/geos/simplify/TaggedLineSegment.h | 2 +- .../include/geos/simplify/TaggedLineString.h | 28 +- .../simplify/TaggedLineStringSimplifier.h | 88 +- .../geos/simplify/TaggedLinesSimplifier.h | 33 +- .../IncrementalDelaunayTriangulator.h | 22 + .../geos/triangulate/VoronoiDiagramBuilder.h | 34 +- .../polygon/ConstrainedDelaunayTriangulator.h | 2 +- .../triangulate/polygon/PolygonEarClipper.h | 14 +- .../triangulate/polygon/PolygonHoleJoiner.h | 9 +- .../quadedge/QuadEdgeSubdivision.h | 7 +- .../triangulate/quadedge/TrianglePredicate.h | 29 +- .../geos/triangulate/quadedge/Vertex.h | 2 +- Sources/geos/include/geos/util.h | 70 +- Sources/geos/include/geos/util/Assert.h | 10 +- .../include/geos/util/CoordinateArrayFilter.h | 67 +- .../include/geos/util/GeometricShapeFactory.h | 14 +- .../geos/util/UniqueCoordinateArrayFilter.h | 38 +- .../geos/util/string.h} | 23 +- Sources/geos/include/geos/vend/json.hpp | 14 +- Sources/geos/include/geos/version.h | 6 +- Sources/geos/public/geos/export.h | 20 +- Sources/geos/public/geos_c.h | 1376 ++++++++++++++--- Sources/geos/src/algorithm/Angle.cpp | 36 +- Sources/geos/src/algorithm/Area.cpp | 65 +- .../geos/src/algorithm/BoundaryNodeRule.cpp | 20 +- Sources/geos/src/algorithm/CGAlgorithmsDD.cpp | 21 +- Sources/geos/src/algorithm/Centroid.cpp | 36 +- Sources/geos/src/algorithm/CircularArcs.cpp | 124 ++ Sources/geos/src/algorithm/ConvexHull.cpp | 86 +- Sources/geos/src/algorithm/Distance.cpp | 41 +- .../geos/src/algorithm/InteriorPointArea.cpp | 19 +- .../geos/src/algorithm/InteriorPointLine.cpp | 1 + .../geos/src/algorithm/InteriorPointPoint.cpp | 7 +- Sources/geos/src/algorithm/Intersection.cpp | 116 +- Sources/geos/src/algorithm/Length.cpp | 4 +- .../geos/src/algorithm/LineIntersector.cpp | 520 +------ .../src/algorithm/MinimumAreaRectangle.cpp | 269 ++++ .../src/algorithm/MinimumBoundingCircle.cpp | 48 +- .../geos/src/algorithm/MinimumDiameter.cpp | 26 +- Sources/geos/src/algorithm/Orientation.cpp | 36 +- Sources/geos/src/algorithm/PointLocation.cpp | 39 +- Sources/geos/src/algorithm/PointLocator.cpp | 23 +- .../src/algorithm/PolygonNodeTopology.cpp | 91 +- .../geos/src/algorithm/RayCrossingCounter.cpp | 172 ++- .../src/algorithm/RayCrossingCounterDD.cpp | 179 --- Sources/geos/src/algorithm/Rectangle.cpp | 126 ++ .../geos/src/algorithm/RobustDeterminant.cpp | 2 +- .../construct/IndexedDistanceToPoint.cpp | 69 + .../IndexedPointInPolygonsLocator.cpp | 70 + .../construct/LargestEmptyCircle.cpp | 97 +- .../construct/MaximumInscribedCircle.cpp | 76 +- .../distance/DiscreteFrechetDistance.cpp | 23 +- .../distance/DiscreteHausdorffDistance.cpp | 5 + .../algorithm/distance/DistanceToPoint.cpp | 10 +- .../geos/src/algorithm/hull/ConcaveHull.cpp | 96 +- .../algorithm/hull/ConcaveHullOfPolygons.cpp | 5 +- Sources/geos/src/algorithm/hull/HullTri.cpp | 17 +- .../src/algorithm/hull/HullTriangulation.cpp | 10 +- .../locate/IndexedPointInAreaLocator.cpp | 7 +- .../locate/SimplePointInAreaLocator.cpp | 54 +- Sources/geos/src/coverage/Corner.cpp | 164 ++ .../CoverageBoundarySegmentFinder.cpp | 89 ++ Sources/geos/src/coverage/CoverageEdge.cpp | 172 +++ .../geos/src/coverage/CoverageGapFinder.cpp | 98 ++ Sources/geos/src/coverage/CoveragePolygon.cpp | 76 + .../src/coverage/CoveragePolygonValidator.cpp | 405 +++++ Sources/geos/src/coverage/CoverageRing.cpp | 323 ++++ .../geos/src/coverage/CoverageRingEdges.cpp | 424 +++++ .../geos/src/coverage/CoverageSimplifier.cpp | 156 ++ Sources/geos/src/coverage/CoverageUnion.cpp | 62 + .../geos/src/coverage/CoverageValidator.cpp | 120 ++ .../src/coverage/InvalidSegmentDetector.cpp | 202 +++ Sources/geos/src/coverage/TPVWSimplifier.cpp | 332 ++++ .../geos/src/coverage/VertexRingCounter.cpp | 66 + Sources/geos/src/edgegraph/EdgeGraph.cpp | 12 +- .../geos/src/edgegraph/EdgeGraphBuilder.cpp | 3 +- Sources/geos/src/edgegraph/HalfEdge.cpp | 6 +- Sources/geos/src/geom/CircularString.cpp | 98 ++ Sources/geos/src/geom/CompoundCurve.cpp | 311 ++++ Sources/geos/src/geom/Coordinate.cpp | 98 +- .../geos/src/geom/CoordinateArraySequence.cpp | 257 --- .../geom/CoordinateArraySequenceFactory.cpp | 40 - Sources/geos/src/geom/CoordinateSequence.cpp | 535 ++++++- Sources/geos/src/geom/Curve.cpp | 59 + Sources/geos/src/geom/CurvePolygon.cpp | 92 ++ Sources/geos/src/geom/Envelope.cpp | 34 +- Sources/geos/src/geom/Geometry.cpp | 319 ++-- Sources/geos/src/geom/GeometryCollection.cpp | 110 +- Sources/geos/src/geom/GeometryFactory.cpp | 529 ++++--- Sources/geos/src/geom/HeuristicOverlay.cpp | 1054 ++++--------- Sources/geos/src/geom/IntersectionMatrix.cpp | 2 +- Sources/geos/src/geom/LineSegment.cpp | 72 +- Sources/geos/src/geom/LineString.cpp | 364 +---- Sources/geos/src/geom/LinearRing.cpp | 38 +- Sources/geos/src/geom/MultiCurve.cpp | 116 ++ Sources/geos/src/geom/MultiLineString.cpp | 8 +- Sources/geos/src/geom/MultiPoint.cpp | 8 +- Sources/geos/src/geom/MultiPolygon.cpp | 4 - Sources/geos/src/geom/MultiSurface.cpp | 109 ++ Sources/geos/src/geom/Point.cpp | 124 +- Sources/geos/src/geom/Polygon.cpp | 399 +---- Sources/geos/src/geom/PrecisionModel.cpp | 51 +- Sources/geos/src/geom/SimpleCurve.cpp | 371 +++++ Sources/geos/src/geom/Surface.cpp | 286 ++++ Sources/geos/src/geom/Triangle.cpp | 46 +- .../prep/AbstractPreparedPolygonContains.cpp | 14 +- .../src/geom/prep/BasicPreparedGeometry.cpp | 51 +- .../src/geom/prep/PreparedGeometryFactory.cpp | 2 + .../geos/src/geom/prep/PreparedLineString.cpp | 9 +- .../geom/prep/PreparedLineStringDistance.cpp | 35 +- .../prep/PreparedLineStringIntersects.cpp | 14 +- .../prep/PreparedLineStringNearestPoints.cpp | 5 +- Sources/geos/src/geom/prep/PreparedPoint.cpp | 5 + .../geos/src/geom/prep/PreparedPolygon.cpp | 20 +- .../src/geom/prep/PreparedPolygonDistance.cpp | 48 +- .../geom/prep/PreparedPolygonPredicate.cpp | 11 +- .../util/ComponentCoordinateExtracter.cpp | 4 +- .../src/geom/util/CoordinateOperation.cpp | 2 +- Sources/geos/src/geom/util/Densifier.cpp | 31 +- Sources/geos/src/geom/util/GeometryEditor.cpp | 8 +- Sources/geos/src/geom/util/GeometryFixer.cpp | 2 +- .../src/geom/util/GeometryTransformer.cpp | 13 +- .../geom/util/LinearComponentExtracter.cpp | 19 +- Sources/geos/src/geom/util/PointExtracter.cpp | 12 +- .../geos/src/geom/util/PolygonExtracter.cpp | 4 + .../geos/src/geom/util/PolygonalExtracter.cpp | 49 + .../geos/src/geom/util/SineStarFactory.cpp | 11 +- .../geos/src/geomgraph/DirectedEdgeStar.cpp | 2 +- Sources/geos/src/geomgraph/Edge.cpp | 3 +- Sources/geos/src/geomgraph/EdgeEndStar.cpp | 8 +- .../src/geomgraph/EdgeIntersectionList.cpp | 18 +- Sources/geos/src/geomgraph/EdgeList.cpp | 4 +- Sources/geos/src/geomgraph/EdgeRing.cpp | 9 +- Sources/geos/src/geomgraph/GeometryGraph.cpp | 12 +- Sources/geos/src/geomgraph/Node.cpp | 11 +- Sources/geos/src/geomgraph/NodeMap.cpp | 34 +- Sources/geos/src/geomgraph/PlanarGraph.cpp | 16 +- .../src/index/VertexSequencePackedRtree.cpp | 5 +- .../geos/src/index/chain/MonotoneChain.cpp | 10 +- .../src/index/chain/MonotoneChainBuilder.cpp | 6 +- Sources/geos/src/io/GeoJSON.cpp | 20 +- Sources/geos/src/io/GeoJSONReader.cpp | 57 +- Sources/geos/src/io/GeoJSONWriter.cpp | 43 +- Sources/geos/src/io/StringTokenizer.cpp | 6 +- Sources/geos/src/io/WKBReader.cpp | 192 ++- Sources/geos/src/io/WKBStreamReader.cpp | 58 + Sources/geos/src/io/WKBWriter.cpp | 212 ++- Sources/geos/src/io/WKTFileReader.cpp | 87 ++ Sources/geos/src/io/WKTReader.cpp | 399 +++-- Sources/geos/src/io/WKTStreamReader.cpp | 71 + Sources/geos/src/io/WKTWriter.cpp | 548 ++++--- .../src/linearref/ExtractLineByLocation.cpp | 4 +- .../geos/src/linearref/LengthIndexedLine.cpp | 7 + .../src/linearref/LinearGeometryBuilder.cpp | 22 +- .../src/linearref/LocationIndexOfLine.cpp | 4 +- .../src/linearref/LocationIndexOfPoint.cpp | 10 +- .../geos/src/noding/BasicSegmentString.cpp | 2 +- .../geos/src/noding/BoundaryChainNoder.cpp | 190 +++ Sources/geos/src/noding/GeometryNoder.cpp | 17 +- Sources/geos/src/noding/IntersectionAdder.cpp | 10 +- Sources/geos/src/noding/IteratedNoder.cpp | 4 +- Sources/geos/src/noding/MCIndexNoder.cpp | 25 +- .../MCIndexSegmentSetMutualIntersector.cpp | 22 +- .../geos/src/noding/NodedSegmentString.cpp | 31 +- .../src/noding/NodingIntersectionFinder.cpp | 4 +- Sources/geos/src/noding/Octant.cpp | 2 +- Sources/geos/src/noding/ScaledNoder.cpp | 9 +- .../src/noding/SegmentExtractingNoder.cpp | 20 +- .../noding/SegmentIntersectionDetector.cpp | 13 +- Sources/geos/src/noding/SegmentNode.cpp | 14 +- Sources/geos/src/noding/SegmentNodeList.cpp | 62 +- .../noding/snap/SnappingIntersectionAdder.cpp | 4 +- .../geos/src/noding/snap/SnappingNoder.cpp | 27 +- .../geos/src/noding/snapround/HotPixel.cpp | 26 +- .../src/noding/snapround/HotPixelIndex.cpp | 30 +- .../SnapRoundingIntersectionAdder.cpp | 55 +- .../noding/snapround/SnapRoundingNoder.cpp | 61 +- Sources/geos/src/operation/BoundaryOp.cpp | 16 +- .../src/operation/GeometryGraphOperation.cpp | 13 +- .../src/operation/buffer/BufferBuilder.cpp | 157 +- .../buffer/BufferCurveSetBuilder.cpp | 83 +- .../buffer/BufferInputLineSimplifier.cpp | 3 +- .../src/operation/buffer/BufferParameters.cpp | 3 +- .../src/operation/buffer/BufferSubgraph.cpp | 2 +- .../geos/src/operation/buffer/OffsetCurve.cpp | 536 ++++--- .../operation/buffer/OffsetCurveBuilder.cpp | 174 ++- .../operation/buffer/OffsetCurveSection.cpp | 168 ++ .../buffer/OffsetSegmentGenerator.cpp | 70 +- .../operation/buffer/SubgraphDepthLocater.cpp | 13 +- .../cluster/AbstractClusterFinder.cpp | 141 ++ .../geos/src/operation/cluster/Clusters.cpp | 56 + .../operation/cluster/DBSCANClusterFinder.cpp | 133 ++ .../operation/cluster/GeometryFlattener.cpp | 56 + .../geos/src/operation/cluster/UnionFind.cpp | 34 + .../ConnectedElementLocationFilter.cpp | 6 +- .../distance/ConnectedElementPointFilter.cpp | 4 +- .../src/operation/distance/DistanceOp.cpp | 115 +- .../operation/distance/GeometryLocation.cpp | 8 +- .../distance/IndexedFacetDistance.cpp | 45 +- .../src/operation/intersection/Rectangle.cpp | 14 +- .../intersection/RectangleIntersection.cpp | 111 +- .../RectangleIntersectionBuilder.cpp | 81 +- .../src/operation/linemerge/EdgeString.cpp | 18 +- .../src/operation/linemerge/LineMerger.cpp | 6 +- .../src/operation/linemerge/LineSequencer.cpp | 16 +- .../src/operation/overlay/EdgeSetNoder.cpp | 60 - .../src/operation/overlay/ElevationMatrix.cpp | 249 --- .../operation/overlay/ElevationMatrixCell.cpp | 84 - .../src/operation/overlay/LineBuilder.cpp | 307 ---- .../geos/src/operation/overlay/OverlayOp.cpp | 1121 -------------- .../src/operation/overlay/PointBuilder.cpp | 108 -- .../src/operation/overlay/PolygonBuilder.cpp | 22 +- .../overlay/snap/GeometrySnapper.cpp | 12 +- .../overlay/snap/LineStringSnapper.cpp | 4 +- .../operation/overlay/snap/SnapOverlayOp.cpp | 15 +- .../validate/OverlayResultValidator.cpp | 47 +- .../src/operation/overlayng/CoverageUnion.cpp | 24 +- Sources/geos/src/operation/overlayng/Edge.cpp | 4 +- .../operation/overlayng/EdgeNodingBuilder.cpp | 32 +- .../operation/overlayng/ElevationModel.cpp | 2 +- .../overlayng/IndexedPointOnLineLocator.cpp | 2 +- .../src/operation/overlayng/LineBuilder.cpp | 13 +- .../src/operation/overlayng/LineLimiter.cpp | 18 +- .../operation/overlayng/MaximalEdgeRing.cpp | 2 +- .../src/operation/overlayng/OverlayEdge.cpp | 13 +- .../operation/overlayng/OverlayEdgeRing.cpp | 25 +- .../src/operation/overlayng/OverlayGraph.cpp | 26 +- .../overlayng/OverlayMixedPoints.cpp | 73 +- .../src/operation/overlayng/OverlayNG.cpp | 6 +- .../operation/overlayng/OverlayNGRobust.cpp | 7 +- .../src/operation/overlayng/OverlayPoints.cpp | 59 +- .../src/operation/overlayng/OverlayUtil.cpp | 3 +- .../operation/overlayng/PolygonBuilder.cpp | 1 + .../src/operation/overlayng/RingClipper.cpp | 8 +- .../src/operation/polygonize/BuildArea.cpp | 6 +- .../src/operation/polygonize/EdgeRing.cpp | 25 +- .../src/operation/polygonize/HoleAssigner.cpp | 1 - .../src/operation/polygonize/Polygonizer.cpp | 73 +- .../operation/predicate/RectangleContains.cpp | 2 +- .../predicate/RectangleIntersects.cpp | 2 +- .../src/operation/relate/EdgeEndBuilder.cpp | 22 +- .../src/operation/relate/RelateComputer.cpp | 70 +- .../src/operation/relate/RelateNodeGraph.cpp | 18 +- .../geos/src/operation/relate/RelateOp.cpp | 4 +- .../relateng/AdjacentEdgeLocator.cpp | 159 ++ .../src/operation/relateng/BasicPredicate.cpp | 137 ++ .../operation/relateng/DimensionLocation.cpp | 125 ++ .../relateng/EdgeSegmentIntersector.cpp | 114 ++ .../relateng/EdgeSegmentOverlapAction.cpp | 53 + .../operation/relateng/EdgeSetIntersector.cpp | 92 ++ .../operation/relateng/IMPatternMatcher.cpp | 149 ++ .../src/operation/relateng/IMPredicate.cpp | 155 ++ .../relateng/LineStringExtracter.cpp | 87 ++ .../src/operation/relateng/LinearBoundary.cpp | 114 ++ .../src/operation/relateng/NodeSection.cpp | 253 +++ .../src/operation/relateng/NodeSections.cpp | 163 ++ .../relateng/PolygonNodeConverter.cpp | 193 +++ .../src/operation/relateng/RelateEdge.cpp | 475 ++++++ .../src/operation/relateng/RelateGeometry.cpp | 521 +++++++ .../geos/src/operation/relateng/RelateNG.cpp | 703 +++++++++ .../src/operation/relateng/RelateNode.cpp | 323 ++++ .../operation/relateng/RelatePointLocator.cpp | 338 ++++ .../operation/relateng/RelatePredicate.cpp | 109 ++ .../relateng/RelateSegmentString.cpp | 161 ++ .../operation/relateng/TopologyComputer.cpp | 568 +++++++ .../operation/sharedpaths/SharedPathsOp.cpp | 8 +- .../operation/union/CascadedPolygonUnion.cpp | 4 +- .../src/operation/union/CoverageUnion.cpp | 29 +- .../operation/union/PointGeometryUnion.cpp | 12 +- .../geos/src/operation/union/UnaryUnionOp.cpp | 2 + .../valid/IndexedNestedHoleTester.cpp | 2 +- .../valid/IndexedNestedPolygonTester.cpp | 12 +- .../geos/src/operation/valid/IsSimpleOp.cpp | 55 +- .../geos/src/operation/valid/IsValidOp.cpp | 56 +- .../geos/src/operation/valid/MakeValid.cpp | 10 +- .../valid/PolygonIntersectionAnalyzer.cpp | 39 +- .../geos/src/operation/valid/PolygonNode.cpp | 114 -- .../geos/src/operation/valid/PolygonRing.cpp | 31 +- .../operation/valid/PolygonRingSelfNode.cpp | 8 +- .../src/operation/valid/PolygonRingTouch.cpp | 4 +- .../valid/PolygonTopologyAnalyzer.cpp | 60 +- .../operation/valid/RepeatedPointRemover.cpp | 94 +- .../operation/valid/RepeatedPointTester.cpp | 2 +- .../valid/TopologyValidationError.cpp | 4 +- .../geos/src/planargraph/DirectedEdgeStar.cpp | 3 +- .../precision/GeometryPrecisionReducer.cpp | 60 +- .../geos/src/precision/MinimumClearance.cpp | 8 +- .../PointwisePrecisionReducerTransformer.cpp | 22 +- .../PrecisionReducerCoordinateOperation.cpp | 10 +- .../precision/PrecisionReducerTransformer.cpp | 52 +- .../SimpleGeometryPrecisionReducer.cpp | 10 +- .../geos/src/shape/fractal/HilbertEncoder.cpp | 29 +- .../src/simplify/ComponentJumpChecker.cpp | 202 +++ .../simplify/DouglasPeuckerLineSimplifier.cpp | 56 +- .../src/simplify/DouglasPeuckerSimplifier.cpp | 12 +- .../geos/src/simplify/LineSegmentIndex.cpp | 35 +- Sources/geos/src/simplify/LinkedLine.cpp | 183 +++ Sources/geos/src/simplify/LinkedRing.cpp | 7 +- Sources/geos/src/simplify/RingHull.cpp | 24 +- .../geos/src/simplify/TaggedLineString.cpp | 77 +- .../simplify/TaggedLineStringSimplifier.cpp | 218 ++- .../src/simplify/TaggedLinesSimplifier.cpp | 34 +- .../simplify/TopologyPreservingSimplifier.cpp | 25 +- .../DelaunayTriangulationBuilder.cpp | 13 +- .../IncrementalDelaunayTriangulator.cpp | 84 +- .../src/triangulate/VoronoiDiagramBuilder.cpp | 128 +- .../ConstrainedDelaunayTriangulator.cpp | 4 +- .../triangulate/polygon/PolygonEarClipper.cpp | 29 +- .../triangulate/polygon/PolygonHoleJoiner.cpp | 60 +- .../src/triangulate/polygon/PolygonNoder.cpp | 12 +- .../polygon/TriDelaunayImprover.cpp | 2 +- .../quadedge/QuadEdgeSubdivision.cpp | 72 +- .../quadedge/TrianglePredicate.cpp | 44 +- Sources/geos/src/triangulate/tri/Tri.cpp | 33 +- Sources/geos/src/util/Assert.cpp | 4 +- .../geos/src/util/GeometricShapeFactory.cpp | 90 +- Sources/geos/src/util/math.cpp | 7 +- Sources/geos/src/util/string.cpp | 64 + geos.podspec | 12 +- update.sh | 2 +- 617 files changed, 34035 insertions(+), 12619 deletions(-) create mode 100644 Sources/geos/include/geos/algorithm/CircularArcs.h create mode 100644 Sources/geos/include/geos/algorithm/Interpolate.h create mode 100644 Sources/geos/include/geos/algorithm/MinimumAreaRectangle.h delete mode 100644 Sources/geos/include/geos/algorithm/RayCrossingCounterDD.h create mode 100644 Sources/geos/include/geos/algorithm/Rectangle.h create mode 100644 Sources/geos/include/geos/algorithm/construct/IndexedDistanceToPoint.h create mode 100644 Sources/geos/include/geos/algorithm/construct/IndexedPointInPolygonsLocator.h create mode 100644 Sources/geos/include/geos/coverage/Corner.h create mode 100644 Sources/geos/include/geos/coverage/CoverageBoundarySegmentFinder.h create mode 100644 Sources/geos/include/geos/coverage/CoverageEdge.h create mode 100644 Sources/geos/include/geos/coverage/CoverageGapFinder.h create mode 100644 Sources/geos/include/geos/coverage/CoveragePolygon.h create mode 100644 Sources/geos/include/geos/coverage/CoveragePolygonValidator.h create mode 100644 Sources/geos/include/geos/coverage/CoverageRing.h create mode 100644 Sources/geos/include/geos/coverage/CoverageRingEdges.h create mode 100644 Sources/geos/include/geos/coverage/CoverageSimplifier.h create mode 100644 Sources/geos/include/geos/coverage/CoverageUnion.h create mode 100644 Sources/geos/include/geos/coverage/CoverageValidator.h create mode 100644 Sources/geos/include/geos/coverage/InvalidSegmentDetector.h create mode 100644 Sources/geos/include/geos/coverage/TPVWSimplifier.h create mode 100644 Sources/geos/include/geos/coverage/VertexRingCounter.h create mode 100644 Sources/geos/include/geos/geom/CircularArc.h create mode 100644 Sources/geos/include/geos/geom/CircularString.h create mode 100644 Sources/geos/include/geos/geom/CompoundCurve.h delete mode 100644 Sources/geos/include/geos/geom/CoordinateArraySequence.h delete mode 100644 Sources/geos/include/geos/geom/CoordinateArraySequenceFactory.h delete mode 100644 Sources/geos/include/geos/geom/CoordinateSequenceFactory.h create mode 100644 Sources/geos/include/geos/geom/CoordinateSequenceIterator.h create mode 100644 Sources/geos/include/geos/geom/CoordinateSequences.h create mode 100644 Sources/geos/include/geos/geom/Curve.h create mode 100644 Sources/geos/include/geos/geom/CurvePolygon.h delete mode 100644 Sources/geos/include/geos/geom/DefaultCoordinateSequenceFactory.h delete mode 100644 Sources/geos/include/geos/geom/FixedSizeCoordinateSequence.h create mode 100644 Sources/geos/include/geos/geom/GeometryTypeName.h create mode 100644 Sources/geos/include/geos/geom/MultiCurve.h create mode 100644 Sources/geos/include/geos/geom/MultiSurface.h create mode 100644 Sources/geos/include/geos/geom/SimpleCurve.h create mode 100644 Sources/geos/include/geos/geom/Surface.h create mode 100644 Sources/geos/include/geos/geom/SurfaceImpl.h create mode 100644 Sources/geos/include/geos/geom/util/GeometryLister.h create mode 100644 Sources/geos/include/geos/geom/util/PolygonalExtracter.h create mode 100644 Sources/geos/include/geos/io/CheckOrdinatesFilter.h create mode 100644 Sources/geos/include/geos/io/OrdinateSet.h create mode 100644 Sources/geos/include/geos/io/WKBStreamReader.h create mode 100644 Sources/geos/include/geos/io/WKTFileReader.h create mode 100644 Sources/geos/include/geos/io/WKTStreamReader.h create mode 100644 Sources/geos/include/geos/noding/BoundaryChainNoder.h create mode 100644 Sources/geos/include/geos/operation/buffer/OffsetCurveSection.h create mode 100644 Sources/geos/include/geos/operation/cluster/AbstractClusterFinder.h create mode 100644 Sources/geos/include/geos/operation/cluster/Clusters.h create mode 100644 Sources/geos/include/geos/operation/cluster/DBSCANClusterFinder.h create mode 100644 Sources/geos/include/geos/operation/cluster/DisjointOperation.h create mode 100644 Sources/geos/include/geos/operation/cluster/EnvelopeDistanceClusterFinder.h create mode 100644 Sources/geos/include/geos/operation/cluster/EnvelopeIntersectsClusterFinder.h create mode 100644 Sources/geos/include/geos/operation/cluster/GeometryDistanceClusterFinder.h create mode 100644 Sources/geos/include/geos/operation/cluster/GeometryFlattener.h create mode 100644 Sources/geos/include/geos/operation/cluster/GeometryIntersectsClusterFinder.h create mode 100644 Sources/geos/include/geos/operation/cluster/UnionFind.h delete mode 100644 Sources/geos/include/geos/operation/overlay/EdgeSetNoder.h delete mode 100644 Sources/geos/include/geos/operation/overlay/ElevationMatrix.h delete mode 100644 Sources/geos/include/geos/operation/overlay/ElevationMatrixCell.h delete mode 100644 Sources/geos/include/geos/operation/overlay/LineBuilder.h delete mode 100644 Sources/geos/include/geos/operation/overlay/OverlayOp.h delete mode 100644 Sources/geos/include/geos/operation/overlay/PointBuilder.h create mode 100644 Sources/geos/include/geos/operation/relateng/AdjacentEdgeLocator.h create mode 100644 Sources/geos/include/geos/operation/relateng/BasicPredicate.h create mode 100644 Sources/geos/include/geos/operation/relateng/DimensionLocation.h create mode 100644 Sources/geos/include/geos/operation/relateng/EdgeSegmentIntersector.h create mode 100644 Sources/geos/include/geos/operation/relateng/EdgeSegmentOverlapAction.h create mode 100644 Sources/geos/include/geos/operation/relateng/EdgeSetIntersector.h create mode 100644 Sources/geos/include/geos/operation/relateng/IMPatternMatcher.h create mode 100644 Sources/geos/include/geos/operation/relateng/IMPredicate.h create mode 100644 Sources/geos/include/geos/operation/relateng/IntersectionMatrixPattern.h create mode 100644 Sources/geos/include/geos/operation/relateng/LineStringExtracter.h create mode 100644 Sources/geos/include/geos/operation/relateng/LinearBoundary.h create mode 100644 Sources/geos/include/geos/operation/relateng/NodeSection.h create mode 100644 Sources/geos/include/geos/operation/relateng/NodeSections.h create mode 100644 Sources/geos/include/geos/operation/relateng/PolygonNodeConverter.h create mode 100644 Sources/geos/include/geos/operation/relateng/RelateEdge.h create mode 100644 Sources/geos/include/geos/operation/relateng/RelateGeometry.h create mode 100644 Sources/geos/include/geos/operation/relateng/RelateMatrixPredicate.h create mode 100644 Sources/geos/include/geos/operation/relateng/RelateNG.h create mode 100644 Sources/geos/include/geos/operation/relateng/RelateNode.h create mode 100644 Sources/geos/include/geos/operation/relateng/RelatePointLocator.h create mode 100644 Sources/geos/include/geos/operation/relateng/RelatePredicate.h create mode 100644 Sources/geos/include/geos/operation/relateng/RelateSegmentString.h create mode 100644 Sources/geos/include/geos/operation/relateng/TopologyComputer.h create mode 100644 Sources/geos/include/geos/operation/relateng/TopologyPredicate.h create mode 100644 Sources/geos/include/geos/operation/union/DisjointSubsetUnion.h delete mode 100644 Sources/geos/include/geos/operation/valid/PolygonNode.h create mode 100644 Sources/geos/include/geos/simplify/ComponentJumpChecker.h create mode 100644 Sources/geos/include/geos/simplify/LinkedLine.h rename Sources/geos/{src/geom/DefaultCoordinateSequenceFactory.cpp => include/geos/util/string.h} (55%) create mode 100644 Sources/geos/src/algorithm/CircularArcs.cpp create mode 100644 Sources/geos/src/algorithm/MinimumAreaRectangle.cpp delete mode 100644 Sources/geos/src/algorithm/RayCrossingCounterDD.cpp create mode 100644 Sources/geos/src/algorithm/Rectangle.cpp create mode 100644 Sources/geos/src/algorithm/construct/IndexedDistanceToPoint.cpp create mode 100644 Sources/geos/src/algorithm/construct/IndexedPointInPolygonsLocator.cpp create mode 100644 Sources/geos/src/coverage/Corner.cpp create mode 100644 Sources/geos/src/coverage/CoverageBoundarySegmentFinder.cpp create mode 100644 Sources/geos/src/coverage/CoverageEdge.cpp create mode 100644 Sources/geos/src/coverage/CoverageGapFinder.cpp create mode 100644 Sources/geos/src/coverage/CoveragePolygon.cpp create mode 100644 Sources/geos/src/coverage/CoveragePolygonValidator.cpp create mode 100644 Sources/geos/src/coverage/CoverageRing.cpp create mode 100644 Sources/geos/src/coverage/CoverageRingEdges.cpp create mode 100644 Sources/geos/src/coverage/CoverageSimplifier.cpp create mode 100644 Sources/geos/src/coverage/CoverageUnion.cpp create mode 100644 Sources/geos/src/coverage/CoverageValidator.cpp create mode 100644 Sources/geos/src/coverage/InvalidSegmentDetector.cpp create mode 100644 Sources/geos/src/coverage/TPVWSimplifier.cpp create mode 100644 Sources/geos/src/coverage/VertexRingCounter.cpp create mode 100644 Sources/geos/src/geom/CircularString.cpp create mode 100644 Sources/geos/src/geom/CompoundCurve.cpp delete mode 100644 Sources/geos/src/geom/CoordinateArraySequence.cpp delete mode 100644 Sources/geos/src/geom/CoordinateArraySequenceFactory.cpp create mode 100644 Sources/geos/src/geom/Curve.cpp create mode 100644 Sources/geos/src/geom/CurvePolygon.cpp create mode 100644 Sources/geos/src/geom/MultiCurve.cpp create mode 100644 Sources/geos/src/geom/MultiSurface.cpp create mode 100644 Sources/geos/src/geom/SimpleCurve.cpp create mode 100644 Sources/geos/src/geom/Surface.cpp create mode 100644 Sources/geos/src/geom/util/PolygonalExtracter.cpp create mode 100644 Sources/geos/src/io/WKBStreamReader.cpp create mode 100644 Sources/geos/src/io/WKTFileReader.cpp create mode 100644 Sources/geos/src/io/WKTStreamReader.cpp create mode 100644 Sources/geos/src/noding/BoundaryChainNoder.cpp create mode 100644 Sources/geos/src/operation/buffer/OffsetCurveSection.cpp create mode 100644 Sources/geos/src/operation/cluster/AbstractClusterFinder.cpp create mode 100644 Sources/geos/src/operation/cluster/Clusters.cpp create mode 100644 Sources/geos/src/operation/cluster/DBSCANClusterFinder.cpp create mode 100644 Sources/geos/src/operation/cluster/GeometryFlattener.cpp create mode 100644 Sources/geos/src/operation/cluster/UnionFind.cpp delete mode 100644 Sources/geos/src/operation/overlay/EdgeSetNoder.cpp delete mode 100644 Sources/geos/src/operation/overlay/ElevationMatrix.cpp delete mode 100644 Sources/geos/src/operation/overlay/ElevationMatrixCell.cpp delete mode 100644 Sources/geos/src/operation/overlay/LineBuilder.cpp delete mode 100644 Sources/geos/src/operation/overlay/OverlayOp.cpp delete mode 100644 Sources/geos/src/operation/overlay/PointBuilder.cpp create mode 100644 Sources/geos/src/operation/relateng/AdjacentEdgeLocator.cpp create mode 100644 Sources/geos/src/operation/relateng/BasicPredicate.cpp create mode 100644 Sources/geos/src/operation/relateng/DimensionLocation.cpp create mode 100644 Sources/geos/src/operation/relateng/EdgeSegmentIntersector.cpp create mode 100644 Sources/geos/src/operation/relateng/EdgeSegmentOverlapAction.cpp create mode 100644 Sources/geos/src/operation/relateng/EdgeSetIntersector.cpp create mode 100644 Sources/geos/src/operation/relateng/IMPatternMatcher.cpp create mode 100644 Sources/geos/src/operation/relateng/IMPredicate.cpp create mode 100644 Sources/geos/src/operation/relateng/LineStringExtracter.cpp create mode 100644 Sources/geos/src/operation/relateng/LinearBoundary.cpp create mode 100644 Sources/geos/src/operation/relateng/NodeSection.cpp create mode 100644 Sources/geos/src/operation/relateng/NodeSections.cpp create mode 100644 Sources/geos/src/operation/relateng/PolygonNodeConverter.cpp create mode 100644 Sources/geos/src/operation/relateng/RelateEdge.cpp create mode 100644 Sources/geos/src/operation/relateng/RelateGeometry.cpp create mode 100644 Sources/geos/src/operation/relateng/RelateNG.cpp create mode 100644 Sources/geos/src/operation/relateng/RelateNode.cpp create mode 100644 Sources/geos/src/operation/relateng/RelatePointLocator.cpp create mode 100644 Sources/geos/src/operation/relateng/RelatePredicate.cpp create mode 100644 Sources/geos/src/operation/relateng/RelateSegmentString.cpp create mode 100644 Sources/geos/src/operation/relateng/TopologyComputer.cpp delete mode 100644 Sources/geos/src/operation/valid/PolygonNode.cpp create mode 100644 Sources/geos/src/simplify/ComponentJumpChecker.cpp create mode 100644 Sources/geos/src/simplify/LinkedLine.cpp create mode 100644 Sources/geos/src/util/string.cpp diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 473bf79..5505d4f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,14 +3,14 @@ name: GEOSwift/geos on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: podspec: name: Lint Podspec for ${{ matrix.platform }} - runs-on: macos-12 + runs-on: macos-14 strategy: matrix: platform: [ios, osx, tvos] @@ -24,66 +24,26 @@ jobs: strategy: matrix: include: - - name: "xcodebuild (iOS 16.2, Xcode 14.2)" - os: macos-12 - xcode-version: "14.2" - sdk: iphonesimulator16.2 - destination: "platform=iOS Simulator,OS=16.2,name=iPhone 14" - - name: "xcodebuild (tvOS 16.1, Xcode 14.2)" - os: macos-12 - xcode-version: "14.2" - sdk: appletvsimulator16.1 - destination: "platform=tvOS Simulator,OS=16.1,name=Apple TV" - - name: "xcodebuild (macOS 13.1, Xcode 14.2)" - os: macos-12 - xcode-version: "14.2" - sdk: macosx13.1 + - name: "xcodebuild (iOS 17.5, Xcode 15.4)" + os: macos-14 + xcode-version: "15.4" + sdk: iphonesimulator17.5 + destination: "platform=iOS Simulator,OS=17.5,name=iPhone 15" + - name: "xcodebuild (tvOS 17.5, Xcode 15.4)" + os: macos-14 + xcode-version: "15.4" + sdk: appletvsimulator17.5 + destination: "platform=tvOS Simulator,OS=17.5,name=Apple TV" + - name: "xcodebuild (macOS 14.5, Xcode 15.4)" + os: macos-14 + xcode-version: "15.4" + sdk: macosx14.5 destination: "platform=OS X" - - name: "xcodebuild (watchOS 9.1, Xcode 14.2)" - os: macos-12 - xcode-version: "14.2" - sdk: watchsimulator9.1 - destination: "platform=watchOS Simulator,OS=9.1,name=Apple Watch Series 8 (45mm)" - - name: "xcodebuild (iOS 15.2, Xcode 13.2.1)" - os: macos-11 - xcode-version: "13.2.1" - sdk: iphonesimulator15.2 - destination: "platform=iOS Simulator,OS=15.2,name=iPhone 13" - - name: "xcodebuild (tvOS 15.2, Xcode 13.2.1)" - os: macos-11 - xcode-version: "13.2.1" - sdk: appletvsimulator15.2 - destination: "platform=tvOS Simulator,OS=15.2,name=Apple TV" - - name: "xcodebuild (macOS 12.1, Xcode 13.2.1)" - os: macos-11 - xcode-version: "13.2.1" - sdk: macosx12.1 - destination: "platform=OS X" - - name: "xcodebuild (watchOS 8.3, Xcode 13.2.1)" - os: macos-11 - xcode-version: "13.2.1" - sdk: watchos8.3 - destination: "platform=watchOS Simulator,OS=8.3,name=Apple Watch Series 7 - 45mm" - - name: "xcodebuild (iOS 14.0, Xcode 12.0.1)" - os: macos-10.15 - xcode-version: "12" - sdk: iphonesimulator14.0 - destination: "platform=iOS Simulator,OS=14.0,name=iPhone 11" - - name: "xcodebuild (tvOS 14.0, Xcode 12.0.1)" - os: macos-10.15 - xcode-version: "12" - sdk: appletvsimulator14.0 - destination: "platform=tvOS Simulator,OS=14.0,name=Apple TV" - - name: "xcodebuild (macOS 10.15, Xcode 12.0.1)" - os: macos-10.15 - xcode-version: "12" - sdk: macosx10.15 - destination: "platform=OS X" - - name: "xcodebuild (watchOS 7.0, Xcode 12.0.1)" - os: macos-10.15 - xcode-version: "12" - sdk: watchos7.0 - destination: "platform=watchOS Simulator,OS=7.0,name=Apple Watch Series 6 - 44mm" + - name: "xcodebuild (watchOS 10.5, Xcode 15.4)" + os: macos-14 + xcode-version: "15.4" + sdk: watchsimulator10.5 + destination: "platform=watchOS Simulator,OS=10.5,name=Apple Watch Series 9 (45mm)" steps: - uses: actions/checkout@v3 - name: Select Xcode Version @@ -103,12 +63,8 @@ jobs: strategy: matrix: include: - - os: macos-10.15 - xcode-version: "12" - - os: macos-11 - xcode-version: "13.2.1" - - os: macos-12 - xcode-version: "14.2" + - os: macos-14 + xcode-version: "15.4" steps: - uses: actions/checkout@v3 - name: Select Xcode Version @@ -121,7 +77,7 @@ jobs: strategy: matrix: include: - - os: ubuntu-22.04 + - os: ubuntu-24.04 steps: - uses: actions/checkout@v3 - name: Build diff --git a/Package.swift b/Package.swift index b711a09..f2974b2 100644 --- a/Package.swift +++ b/Package.swift @@ -1,9 +1,9 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.9 import PackageDescription let package = Package( name: "geos", - platforms: [.iOS(.v9), .macOS("10.9"), .tvOS(.v9), .watchOS(.v2)], + platforms: [.iOS(.v12), .macOS(.v10_13), .tvOS(.v12), .watchOS(.v4)], products: [ .library( name: "geos", @@ -20,5 +20,5 @@ let package = Package( .headerSearchPath("include"), .headerSearchPath("src/deps")]) ], - cxxLanguageStandard: .cxx11 + cxxLanguageStandard: .cxx14 ) diff --git a/README.md b/README.md index caff59d..a3e1bcf 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ it in your Swift or Objective-C project. It is commonly used via ## Requirements -* iOS 9.0+, macOS 10.9+, tvOS 9.0+, watchOS 2.0+ (Swift Package Manager, CocoaPods) +* iOS 12.0+, macOS 10.13+, tvOS 12.0+, watchOS 4.0+ (Swift Package Manager, CocoaPods) * Linux (Swift Package Manager) > GEOS is licensed under LGPL 2.1 and its compatibility with static linking is @@ -21,7 +21,7 @@ at least controversial. Use of geos without dynamic linking is discouraged. ## Upstream Version -GEOSwift/geos 8.1.0 packages [libgeos/geos](https://github.com/libgeos/geos) 3.11.2 +GEOSwift/geos 9.0.0 packages [libgeos/geos](https://github.com/libgeos/geos) 3.13.0 ## Installing with CocoaPods @@ -36,7 +36,7 @@ GEOSwift/geos 8.1.0 packages [libgeos/geos](https://github.com/libgeos/geos) 3.1 1. Update the top-level dependencies in your `Package.swift` to include: - .package(url: "https://github.com/GEOSwift/geos.git", from: "8.1.0") + .package(url: "https://github.com/GEOSwift/geos.git", from: "9.0.0") 2. Update the target dependencies in your `Package.swift` to include diff --git a/Sources/geos/capi/geos_c.cpp b/Sources/geos/capi/geos_c.cpp index 84c449c..834f3ef 100644 --- a/Sources/geos/capi/geos_c.cpp +++ b/Sources/geos/capi/geos_c.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -32,10 +33,15 @@ #pragma warning(disable : 4099) #endif -// Some extra magic to make type declarations in geos_c.h work - for cross-checking of types in header. +// Some extra magic to make type declarations in geos_c.h work - +// for cross-checking of types in header. +// NOTE: the below defines or struct definition must be kept in exact +// sync between geos_c.cpp and geos_ts_c.cpp to avoid C++ One Definition Rule +// violations. #define GEOSGeometry geos::geom::Geometry #define GEOSPreparedGeometry geos::geom::prep::PreparedGeometry #define GEOSCoordSequence geos::geom::CoordinateSequence +#define GEOSBufferParams geos::operation::buffer::BufferParameters #define GEOSSTRtree geos::index::strtree::TemplateSTRtree #define GEOSWKTReader geos::io::WKTReader #define GEOSWKTWriter geos::io::WKTWriter @@ -43,8 +49,12 @@ #define GEOSWKBWriter geos::io::WKBWriter #define GEOSGeoJSONReader geos::io::GeoJSONReader #define GEOSGeoJSONWriter geos::io::GeoJSONWriter -typedef struct GEOSBufParams_t GEOSBufferParams; -typedef struct GEOSMakeValidParams_t GEOSMakeValidParams; + +// Implementation struct for the GEOSMakeValidParams object +typedef struct { + int method; + int keepCollapsed; +} GEOSMakeValidParams; #include "geos_c.h" @@ -136,7 +146,7 @@ extern "C" { /**************************************************************** ** relate()-related functions - ** return 0 = false, 1 = true, 2 = error occured + ** return 0 = false, 1 = true, 2 = error occurred ** */ char @@ -203,15 +213,15 @@ extern "C" { //------------------------------------------------------------------ char - GEOSRelatePattern(const Geometry* g1, const Geometry* g2, const char* pat) + GEOSRelatePattern(const Geometry* g1, const Geometry* g2, const char* imPattern) { - return GEOSRelatePattern_r(handle, g1, g2, pat); + return GEOSRelatePattern_r(handle, g1, g2, imPattern); } char - GEOSRelatePatternMatch(const char* mat, const char* pat) + GEOSRelatePatternMatch(const char* intMatrix, const char* imPattern) { - return GEOSRelatePatternMatch_r(handle, mat, pat); + return GEOSRelatePatternMatch_r(handle, intMatrix, imPattern); } char* @@ -267,6 +277,12 @@ extern "C" { return GEOSEqualsExact_r(handle, g1, g2, tolerance); } + char + GEOSEqualsIdentical(const Geometry* g1, const Geometry* g2) + { + return GEOSEqualsIdentical_r(handle, g1, g2); + } + int GEOSDistance(const Geometry* g1, const Geometry* g2, double* dist) { @@ -477,6 +493,15 @@ extern "C" { return GEOSConcaveHull_r(handle, g, ratio, allowHoles); } + Geometry* + GEOSConcaveHullByLength(const Geometry* g, + double length, + unsigned int allowHoles) + + { + return GEOSConcaveHullByLength_r(handle, g, length, allowHoles); + } + Geometry* GEOSPolygonHullSimplify(const Geometry* g, unsigned int isOuter, @@ -600,6 +625,12 @@ extern "C" { return GEOSCoverageUnion_r(handle, g); } + Geometry* + GEOSDisjointSubsetUnion(const Geometry* g) + { + return GEOSDisjointSubsetUnion_r(handle, g); + } + Geometry* GEOSNode(const Geometry* g) { @@ -660,6 +691,12 @@ extern "C" { return GEOSNormalize_r(handle, g); } + int + GEOSOrientPolygons(Geometry* g, int exteriorCW) + { + return GEOSOrientPolygons_r(handle, g, exteriorCW); + } + int GEOSGetNumInteriorRings(const Geometry* g) { @@ -773,6 +810,16 @@ extern "C" { return GEOSGeomGetZ_r(handle, g1, z); } + /* + * For POINT + * returns 0 on exception, otherwise 1 + */ + int + GEOSGeomGetM(const Geometry* g1, double* m) + { + return GEOSGeomGetM_r(handle, g1, m); + } + /* * Call only on polygon * Return a copy of the internal Geometry. @@ -818,6 +865,12 @@ extern "C" { return GEOSGeom_createCollection_r(handle, type, geoms, ngeoms); } + Geometry** + GEOSGeom_releaseCollection(Geometry* collection, unsigned int * ngeoms) + { + return GEOSGeom_releaseCollection_r(handle, collection, ngeoms); + } + Geometry* GEOSPolygonize(const Geometry* const* g, unsigned int ngeoms) { @@ -911,6 +964,12 @@ extern "C" { return GEOSLineMergeDirected_r(handle, g); } + Geometry* + GEOSLineSubstring(const Geometry* g, double start_fraction, double end_fraction) + { + return GEOSLineSubstring_r(handle, g, start_fraction, end_fraction); + } + Geometry* GEOSReverse(const Geometry* g) { @@ -947,6 +1006,12 @@ extern "C" { return GEOSHasZ_r(handle, g); } + char + GEOSHasM(const Geometry* g) + { + return GEOSHasM_r(handle, g); + } + int GEOS_getWKBOutputDims() { @@ -1139,6 +1204,24 @@ extern "C" { return GEOSGeom_createPolygon_r(handle, shell, holes, nholes); } + Geometry* + GEOSGeom_createCircularString(CoordinateSequence* cs) + { + return GEOSGeom_createCircularString_r(handle, cs); + } + + Geometry* + GEOSGeom_createCompoundCurve(Geometry** curves, unsigned int ngeoms) + { + return GEOSGeom_createCompoundCurve_r(handle, curves, ngeoms); + } + + Geometry* + GEOSGeom_createCurvePolygon(Geometry* shell, Geometry** holes, unsigned int nholes) + { + return GEOSGeom_createCurvePolygon_r(handle, shell, holes, nholes); + } + Geometry* GEOSGeom_clone(const Geometry* g) { @@ -1388,6 +1471,11 @@ extern "C" { GEOSWKBWriter_setIncludeSRID_r(handle, writer, newIncludeSRID); } + int + GEOS_printDouble(double d, unsigned int precision, char *result) { + return WKTWriter::writeTrimmedNumber(d, precision, result); + } + /* GeoJSON Reader */ GeoJSONReader* GEOSGeoJSONReader_create() @@ -1449,6 +1537,12 @@ extern "C" { return GEOSPreparedContains_r(handle, pg1, g2); } + char + GEOSPreparedContainsXY(const geos::geom::prep::PreparedGeometry* pg1, double x, double y) + { + return GEOSPreparedContainsXY_r(handle, pg1, x, y); + } + char GEOSPreparedContainsProperly(const geos::geom::prep::PreparedGeometry* pg1, const Geometry* g2) { @@ -1485,6 +1579,12 @@ extern "C" { return GEOSPreparedIntersects_r(handle, pg1, g2); } + char + GEOSPreparedIntersectsXY(const geos::geom::prep::PreparedGeometry* pg1, double x, double y) + { + return GEOSPreparedIntersectsXY_r(handle, pg1, x, y); + } + char GEOSPreparedOverlaps(const geos::geom::prep::PreparedGeometry* pg1, const Geometry* g2) { @@ -1503,6 +1603,18 @@ extern "C" { return GEOSPreparedWithin_r(handle, pg1, g2); } + char * + GEOSPreparedRelate(const geos::geom::prep::PreparedGeometry* pg1, const Geometry* g2) + { + return GEOSPreparedRelate_r(handle, pg1, g2); + } + + char + GEOSPreparedRelatePattern(const geos::geom::prep::PreparedGeometry* pg1, const Geometry* g2, const char* imPattern) + { + return GEOSPreparedRelatePattern_r(handle, pg1, g2, imPattern); + } + CoordinateSequence* GEOSPreparedNearestPoints(const geos::geom::prep::PreparedGeometry* g1, const Geometry* g2) { @@ -1527,6 +1639,12 @@ extern "C" { return GEOSSTRtree_create_r(handle, nodeCapacity); } + int + GEOSSTRtree_build(GEOSSTRtree* tree) + { + return GEOSSTRtree_build_r(handle, tree); + } + void GEOSSTRtree_insert(GEOSSTRtree* tree, const geos::geom::Geometry* g, @@ -1640,6 +1758,24 @@ extern "C" { return GEOSGeom_createEmptyPolygon_r(handle); } + geos::geom::Geometry* + GEOSGeom_createEmptyCircularString() + { + return GEOSGeom_createEmptyCircularString_r(handle); + } + + geos::geom::Geometry* + GEOSGeom_createEmptyCompoundCurve() + { + return GEOSGeom_createEmptyCompoundCurve_r(handle); + } + + geos::geom::Geometry* + GEOSGeom_createEmptyCurvePolygon() + { + return GEOSGeom_createEmptyCurvePolygon_r(handle); + } + geos::geom::Geometry* GEOSGeom_createRectangle(double xmin, double ymin, double xmax, double ymax) @@ -1727,9 +1863,9 @@ extern "C" { } Geometry* - GEOSVoronoiDiagram(const Geometry* g, const Geometry* env, double tolerance, int onlyEdges) + GEOSVoronoiDiagram(const Geometry* g, const Geometry* env, double tolerance, int flags) { - return GEOSVoronoiDiagram_r(handle, g, env, tolerance, onlyEdges); + return GEOSVoronoiDiagram_r(handle, g, env, tolerance, flags); } int @@ -1743,4 +1879,20 @@ extern "C" { cx, cy); } + int + GEOSCoverageIsValid( + const Geometry* input, + double gapWidth, + Geometry** invalidEdges) + { + return GEOSCoverageIsValid_r(handle, input, gapWidth, invalidEdges); + } + + Geometry* + GEOSCoverageSimplifyVW(const Geometry* input, double tolerance, int preserveBoundary) + { + return GEOSCoverageSimplifyVW_r(handle, input, tolerance, preserveBoundary); + } + + } /* extern "C" */ diff --git a/Sources/geos/capi/geos_ts_c.cpp b/Sources/geos/capi/geos_ts_c.cpp index 150137a..ef23822 100644 --- a/Sources/geos/capi/geos_ts_c.cpp +++ b/Sources/geos/capi/geos_ts_c.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -27,11 +28,16 @@ #include #include #include +#include +#include +#include +#include +#include #include -#include -#include +#include +#include +#include #include -#include #include #include #include @@ -39,12 +45,15 @@ #include #include #include +#include #include #include #include +#include #include #include #include +#include #include #include #include @@ -69,7 +78,6 @@ #include #include #include -#include #include #include #include @@ -80,10 +88,13 @@ #include #include #include -#include +#include #include #include #include + +#include + #include #include #include @@ -118,6 +129,9 @@ // Some extra magic to make type declarations in geos_c.h work - // for cross-checking of types in header. +// NOTE: the below defines or struct definition must be kept in exact +// sync between geos_c.cpp and geos_ts_c.cpp to avoid C++ One Definition Rule +// violations. #define GEOSGeometry geos::geom::Geometry #define GEOSPreparedGeometry geos::geom::prep::PreparedGeometry #define GEOSCoordSequence geos::geom::CoordinateSequence @@ -153,17 +167,26 @@ using namespace std; // import the most frequently used definitions globally +using geos::geom::Coordinate; +using geos::geom::CoordinateXY; +using geos::geom::CoordinateXYM; +using geos::geom::CoordinateXYZM; +using geos::geom::CoordinateSequence; +using geos::geom::Curve; +using geos::geom::Envelope; using geos::geom::Geometry; +using geos::geom::GeometryCollection; +using geos::geom::GeometryFactory; using geos::geom::LineString; using geos::geom::LinearRing; +using geos::geom::MultiCurve; using geos::geom::MultiLineString; using geos::geom::MultiPolygon; +using geos::geom::Point; using geos::geom::Polygon; using geos::geom::PrecisionModel; -using geos::geom::CoordinateSequence; -using geos::geom::GeometryCollection; -using geos::geom::GeometryFactory; -using geos::geom::Envelope; +using geos::geom::SimpleCurve; +using geos::geom::Surface; using geos::io::WKTReader; using geos::io::WKTWriter; @@ -185,6 +208,7 @@ using geos::operation::geounion::CascadedPolygonUnion; using geos::operation::overlayng::OverlayNG; using geos::operation::overlayng::UnaryUnionNG; using geos::operation::overlayng::OverlayNGRobust; +using geos::operation::relateng::RelateNG; using geos::operation::valid::TopologyValidationError; using geos::precision::GeometryPrecisionReducer; @@ -193,8 +217,6 @@ using geos::simplify::PolygonHullSimplifier; using geos::util::IllegalArgumentException; -typedef std::unique_ptr GeomPtr; - typedef struct GEOSContextHandle_HS { const GeometryFactory* geomFactory; char msgBuffer[1024]; @@ -207,6 +229,7 @@ typedef struct GEOSContextHandle_HS { uint8_t WKBOutputDims; int WKBByteOrder; int initialized; + std::unique_ptr point2d; GEOSContextHandle_HS() : @@ -216,10 +239,12 @@ typedef struct GEOSContextHandle_HS { noticeData(nullptr), errorMessageOld(nullptr), errorMessageNew(nullptr), - errorData(nullptr) + errorData(nullptr), + point2d(nullptr) { memset(msgBuffer, 0, sizeof(msgBuffer)); geomFactory = GeometryFactory::getDefaultInstance(); + point2d = geomFactory->createPoint(CoordinateXY{0, 0}); WKBOutputDims = 2; WKBByteOrder = getMachineByteOrder(); setNoticeHandler(nullptr); @@ -272,7 +297,7 @@ typedef struct GEOSContextHandle_HS { } void - NOTICE_MESSAGE(const char *fmt, ...) + NOTICE_MESSAGE(GEOS_PRINTF_FORMAT const char *fmt, ...) GEOS_PRINTF_FORMAT_ATTR(2, 3) { if(nullptr == noticeMessageOld && nullptr == noticeMessageNew) { return; @@ -280,7 +305,14 @@ typedef struct GEOSContextHandle_HS { va_list args; va_start(args, fmt); + #ifdef __MINGW32__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wsuggest-attribute=format" + #endif int result = vsnprintf(msgBuffer, sizeof(msgBuffer) - 1, fmt, args); + #ifdef __MINGW32__ + #pragma GCC diagnostic pop + #endif va_end(args); if(result > 0) { @@ -294,7 +326,7 @@ typedef struct GEOSContextHandle_HS { } void - ERROR_MESSAGE(const char *fmt, ...) + ERROR_MESSAGE(GEOS_PRINTF_FORMAT const char *fmt, ...) GEOS_PRINTF_FORMAT_ATTR(2, 3) { if(nullptr == errorMessageOld && nullptr == errorMessageNew) { return; @@ -302,7 +334,14 @@ typedef struct GEOSContextHandle_HS { va_list args; va_start(args, fmt); + #ifdef __MINGW32__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wsuggest-attribute=format" + #endif int result = vsnprintf(msgBuffer, sizeof(msgBuffer) - 1, fmt, args); + #ifdef __MINGW32__ + #pragma GCC diagnostic pop + #endif va_end(args); if(result > 0) { @@ -384,7 +423,7 @@ inline auto execute( decltype(std::declval()())>::type errval, F&& f) -> decltype(errval) { if (extHandle == nullptr) { - return errval; + throw std::runtime_error("GEOS context handle is uninitialized, call initGEOS"); } GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); @@ -408,7 +447,7 @@ inline auto execute( template()())>::value, std::nullptr_t>::type = nullptr> inline auto execute(GEOSContextHandle_t extHandle, F&& f) -> decltype(f()) { if (extHandle == nullptr) { - return nullptr; + throw std::runtime_error("context handle is uninitialized, call initGEOS"); } GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); @@ -610,29 +649,37 @@ extern "C" { }); } + char + GEOSEquals_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) + { + return execute(extHandle, 2, [&]() { + return g1->equals(g2); + }); + } + //------------------------------------------------------------------- // low-level relate functions //------------------------------------------------------------------ char - GEOSRelatePattern_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, const char* pat) + GEOSRelatePattern_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, const char* imPattern) { return execute(extHandle, 2, [&]() { - std::string s(pat); + std::string s(imPattern); return g1->relate(g2, s); }); } char - GEOSRelatePatternMatch_r(GEOSContextHandle_t extHandle, const char* mat, - const char* pat) + GEOSRelatePatternMatch_r(GEOSContextHandle_t extHandle, const char* intMatrix, + const char* imPattern) { return execute(extHandle, 2, [&]() { using geos::geom::IntersectionMatrix; - std::string m(mat); - std::string p(pat); + std::string m(intMatrix); + std::string p(imPattern); IntersectionMatrix im(m); return im.matches(p); @@ -666,19 +713,19 @@ extern "C" { switch (bnr) { case GEOSRELATE_BNR_MOD2: /* same as OGC */ - im = RelateOp::relate(g1, g2, + im = RelateNG::relate(g1, g2, BoundaryNodeRule::getBoundaryRuleMod2()); break; case GEOSRELATE_BNR_ENDPOINT: - im = RelateOp::relate(g1, g2, + im = RelateNG::relate(g1, g2, BoundaryNodeRule::getBoundaryEndPoint()); break; case GEOSRELATE_BNR_MULTIVALENT_ENDPOINT: - im = RelateOp::relate(g1, g2, + im = RelateNG::relate(g1, g2, BoundaryNodeRule::getBoundaryMultivalentEndPoint()); break; case GEOSRELATE_BNR_MONOVALENT_ENDPOINT: - im = RelateOp::relate(g1, g2, + im = RelateNG::relate(g1, g2, BoundaryNodeRule::getBoundaryMonovalentEndPoint()); break; default: @@ -768,7 +815,7 @@ extern "C" { const TopologyValidationError* err = ivo.getValidationError(); if(err != nullptr) { if(location) { - *location = g->getFactory()->createPoint(err->getCoordinate()); + *location = g->getFactory()->createPoint(err->getCoordinate()).release(); } if(reason) { std::string errmsg(err->getMessage()); @@ -793,18 +840,18 @@ extern "C" { //----------------------------------------------------------------- char - GEOSEquals_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) + GEOSEqualsExact_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double tolerance) { return execute(extHandle, 2, [&]() { - return g1->equals(g2); + return g1->equalsExact(g2, tolerance); }); } char - GEOSEqualsExact_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double tolerance) + GEOSEqualsIdentical_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2) { return execute(extHandle, 2, [&]() { - return g1->equalsExact(g2, tolerance); + return g1->equalsIdentical(g2); }); } @@ -920,7 +967,11 @@ extern "C" { GEOSGeomToWKT_r(GEOSContextHandle_t extHandle, const Geometry* g1) { return execute(extHandle, [&]() { - char* result = gstrdup(g1->toString()); + // Deprecated, show untrimmed 2D output + geos::io::WKTWriter writer; + writer.setTrim(false); + writer.setOutputDimension(2); + char* result = gstrdup(writer.write(g1)); return result; }); } @@ -1026,8 +1077,7 @@ extern "C" { GEOSisRing_r(GEOSContextHandle_t extHandle, const Geometry* g) { return execute(extHandle, 2, [&]() { - // both LineString* and LinearRing* can cast to LineString* - const LineString* ls = dynamic_cast(g); + const Curve* ls = dynamic_cast(g); if(ls) { return ls->isRing(); } @@ -1086,8 +1136,6 @@ extern "C" { GEOSIntersectionPrec_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double gridSize) { return execute(extHandle, [&]() { - using geos::geom::PrecisionModel; - std::unique_ptr pm; if(gridSize != 0) { pm.reset(new PrecisionModel(1.0 / gridSize)); @@ -1167,7 +1215,7 @@ extern "C" { { return execute(extHandle, [&]() { BufferParameters bp; - bp.setEndCapStyle(BufferParameters::CAP_FLAT); + //-- use default cap style ROUND bp.setQuadrantSegments(quadsegs); if(joinStyle > BufferParameters::JOIN_BEVEL) { @@ -1237,6 +1285,22 @@ extern "C" { }); } + Geometry* + GEOSConcaveHullByLength_r(GEOSContextHandle_t extHandle, + const Geometry* g1, + double length, + unsigned int allowHoles) + { + return execute(extHandle, [&]() { + ConcaveHull hull(g1); + hull.setMaximumEdgeLength(length); + hull.setHolesAllowed(allowHoles); + std::unique_ptr g3 = hull.getHull(); + g3->setSRID(g1->getSRID()); + return g3.release(); + }); + } + Geometry* GEOSPolygonHullSimplify_r(GEOSContextHandle_t extHandle, const Geometry* g1, @@ -1295,9 +1359,10 @@ extern "C" { Geometry* GEOSMinimumRotatedRectangle_r(GEOSContextHandle_t extHandle, const Geometry* g) { + using geos::algorithm::MinimumAreaRectangle; + return execute(extHandle, [&]() { - geos::algorithm::MinimumDiameter m(g); - auto g3 = m.getMinimumRectangle(); + auto g3 = MinimumAreaRectangle::getMinimumRectangle(g); g3->setSRID(g->getSRID()); return g3.release(); }); @@ -1466,7 +1531,17 @@ extern "C" { GEOSCoverageUnion_r(GEOSContextHandle_t extHandle, const Geometry* g) { return execute(extHandle, [&]() { - auto g3 = geos::operation::geounion::CoverageUnion::Union(g); + auto g3 = geos::coverage::CoverageUnion::Union(g); + g3->setSRID(g->getSRID()); + return g3.release(); + }); + } + + Geometry* + GEOSDisjointSubsetUnion_r(GEOSContextHandle_t extHandle, const Geometry* g) + { + return execute(extHandle, [&]() { + auto g3 = geos::operation::geounion::DisjointSubsetUnion::Union(g); g3->setSRID(g->getSRID()); return g3.release(); }); @@ -1476,7 +1551,7 @@ extern "C" { GEOSUnaryUnion_r(GEOSContextHandle_t extHandle, const Geometry* g) { return execute(extHandle, [&]() { - GeomPtr g3(g->Union()); + std::unique_ptr g3(g->Union()); g3->setSRID(g->getSRID()); return g3.release(); }); @@ -1520,7 +1595,7 @@ extern "C" { // CascadedUnion is the same as UnaryUnion, except that // CascadedUnion only works on MultiPolygon, so we just delegate // now and retain a check on MultiPolygon type. - const geos::geom::MultiPolygon *p = dynamic_cast(g1); + const MultiPolygon *p = dynamic_cast(g1); if (!p) { throw IllegalArgumentException("Invalid argument (must be a MultiPolygon)"); } @@ -1533,11 +1608,6 @@ extern "C" { { return execute(extHandle, [&]() { auto ret = g1->getInteriorPoint(); - if(ret == nullptr) { - const GeometryFactory* gf = g1->getFactory(); - // return an empty point - ret = gf->createPoint(); - } ret->setSRID(g1->getSRID()); return ret.release(); }); @@ -1559,13 +1629,13 @@ extern "C" { Geometry* GEOSGeom_transformXY_r(GEOSContextHandle_t handle, const GEOSGeometry* g, GEOSTransformXYCallback callback, void* userdata) { - struct TransformFilter : public geos::geom::CoordinateFilter { + struct TransformFilter final: public geos::geom::CoordinateFilter { TransformFilter(GEOSTransformXYCallback p_callback, void* p_userdata) : m_callback(p_callback), m_userdata(p_userdata) {} - void filter_rw(geos::geom::Coordinate* c) const final { + void filter_rw(CoordinateXY* c) const override { if (!m_callback(&(c->x), &(c->y), m_userdata)) { throw std::runtime_error(std::string("Failed to transform coordinates.")); } @@ -1638,13 +1708,41 @@ extern "C" { }); } + int + GEOSOrientPolygons_r(GEOSContextHandle_t extHandle, Geometry* g, int exteriorCW) + { + return execute(extHandle, -1, [&]() { + class OrientPolygons : public geos::geom::GeometryComponentFilter { + public: + OrientPolygons(bool isExteriorCW) : exteriorCW(isExteriorCW) {} + + void filter_rw(Geometry* g) override { + if (g->getGeometryTypeId() == geos::geom::GeometryTypeId::GEOS_POLYGON) { + auto p = geos::detail::down_cast(g); + p->orientRings(exteriorCW); + } else if (g->getGeometryTypeId() == geos::geom::GeometryTypeId::GEOS_CURVEPOLYGON) { + throw geos::util::UnsupportedOperationException("Curved geometries not supported."); + } + } + + private: + bool exteriorCW; + }; + + OrientPolygons op(exteriorCW); + g->apply_rw(&op); + + return 0; + }); + } + int GEOSGetNumInteriorRings_r(GEOSContextHandle_t extHandle, const Geometry* g1) { return execute(extHandle, -1, [&]() { - const Polygon* p = dynamic_cast(g1); + const Surface* p = dynamic_cast(g1); if(!p) { - throw IllegalArgumentException("Argument is not a Polygon"); + throw IllegalArgumentException("Argument is not a Surface"); } return static_cast(p->getNumInteriorRing()); }); @@ -1683,8 +1781,6 @@ extern "C" { Geometry* GEOSGeomGetPointN_r(GEOSContextHandle_t extHandle, const Geometry* g1, int n) { - using geos::geom::LineString; - return execute(extHandle, [&]() { const LineString* ls = dynamic_cast(g1); if(!ls) { @@ -1703,8 +1799,6 @@ extern "C" { Geometry* GEOSGeomGetStartPoint_r(GEOSContextHandle_t extHandle, const Geometry* g1) { - using geos::geom::LineString; - return execute(extHandle, [&]() { const LineString* ls = dynamic_cast(g1); if(!ls) { @@ -1721,8 +1815,6 @@ extern "C" { Geometry* GEOSGeomGetEndPoint_r(GEOSContextHandle_t extHandle, const Geometry* g1) { - using geos::geom::LineString; - return execute(extHandle, [&]() { const LineString* ls = dynamic_cast(g1); if(!ls) { @@ -1739,11 +1831,8 @@ extern "C" { char GEOSisClosed_r(GEOSContextHandle_t extHandle, const Geometry* g1) { - using geos::geom::LineString; - using geos::geom::MultiLineString; - return execute(extHandle, 2, [&]() { - const LineString* ls = dynamic_cast(g1); + const Curve* ls = dynamic_cast(g1); if(ls) { return ls->isClosed(); } @@ -1753,7 +1842,12 @@ extern "C" { return mls->isClosed(); } - throw IllegalArgumentException("Argument is not a LineString or MultiLineString"); + const MultiCurve* mc = dynamic_cast(g1); + if(mc) { + return mc->isClosed(); + } + + throw IllegalArgumentException("Argument is not a Curve, MultiLineString, or MultiCurve"); }); } @@ -1764,8 +1858,6 @@ extern "C" { int GEOSGeomGetLength_r(GEOSContextHandle_t extHandle, const Geometry* g1, double* length) { - using geos::geom::LineString; - return execute(extHandle, 0, [&]() { const LineString* ls = dynamic_cast(g1); if(!ls) { @@ -1782,12 +1874,10 @@ extern "C" { int GEOSGeomGetNumPoints_r(GEOSContextHandle_t extHandle, const Geometry* g1) { - using geos::geom::LineString; - return execute(extHandle, -1, [&]() { - const LineString* ls = dynamic_cast(g1); + const SimpleCurve* ls = dynamic_cast(g1); if(!ls) { - throw IllegalArgumentException("Argument is not a LineString"); + throw IllegalArgumentException("Argument is not a SimpleCurve"); } return static_cast(ls->getNumPoints()); }); @@ -1800,8 +1890,6 @@ extern "C" { int GEOSGeomGetX_r(GEOSContextHandle_t extHandle, const Geometry* g1, double* x) { - using geos::geom::Point; - return execute(extHandle, 0, [&]() { const Point* po = dynamic_cast(g1); if(!po) { @@ -1819,8 +1907,6 @@ extern "C" { int GEOSGeomGetY_r(GEOSContextHandle_t extHandle, const Geometry* g1, double* y) { - using geos::geom::Point; - return execute(extHandle, 0, [&]() { const Point* po = dynamic_cast(g1); if(!po) { @@ -1837,6 +1923,23 @@ extern "C" { */ int GEOSGeomGetZ_r(GEOSContextHandle_t extHandle, const Geometry* g1, double* z) + { + return execute(extHandle, 0, [&]() { + const Point* po = dynamic_cast(g1); + if(!po) { + throw IllegalArgumentException("Argument is not a Point"); + } + *z = po->getZ(); + return 1; + }); + } + + /* + * For POINT + * returns 0 on exception, otherwise 1 + */ + int + GEOSGeomGetM_r(GEOSContextHandle_t extHandle, const Geometry* g1, double* m) { using geos::geom::Point; @@ -1845,7 +1948,7 @@ extern "C" { if(!po) { throw IllegalArgumentException("Argument is not a Point"); } - *z = po->getZ(); + *m = po->getM(); return 1; }); } @@ -1858,9 +1961,9 @@ extern "C" { GEOSGetExteriorRing_r(GEOSContextHandle_t extHandle, const Geometry* g1) { return execute(extHandle, [&]() { - const Polygon* p = dynamic_cast(g1); + const Surface* p = dynamic_cast(g1); if(!p) { - throw IllegalArgumentException("Invalid argument (must be a Polygon)"); + throw IllegalArgumentException("Invalid argument (must be a Surface)"); } return p->getExteriorRing(); }); @@ -1874,9 +1977,9 @@ extern "C" { GEOSGetInteriorRingN_r(GEOSContextHandle_t extHandle, const Geometry* g1, int n) { return execute(extHandle, [&]() { - const Polygon* p = dynamic_cast(g1); + const Surface* p = dynamic_cast(g1); if(!p) { - throw IllegalArgumentException("Invalid argument (must be a Polygon)"); + throw IllegalArgumentException("Invalid argument (must be a Surface)"); } if(n < 0) { throw IllegalArgumentException("Index must be non-negative."); @@ -1890,12 +1993,6 @@ extern "C" { { return execute(extHandle, [&]() -> Geometry* { auto ret = g->getCentroid(); - - if(ret == nullptr) { - // TODO check if getCentroid() can really return null - const GeometryFactory* gf = g->getFactory(); - ret = gf->createPoint(); - } ret->setSRID(g->getSRID()); return ret.release(); }); @@ -1909,7 +2006,7 @@ extern "C" { using geos::shape::fractal::HilbertEncoder; return execute(extHandle, 0, [&]() { - geos::geom::Envelope e = *extent->getEnvelopeInternal(); + Envelope e = *extent->getEnvelopeInternal(); HilbertEncoder encoder(level, e); *code = encoder.encode(geom->getEnvelopeInternal()); return 1; @@ -1926,12 +2023,7 @@ extern "C" { geos::algorithm::MinimumBoundingCircle mc(g); std::unique_ptr ret = mc.getCircle(); const GeometryFactory* gf = handle->geomFactory; - if(!ret) { - if (center) *center = NULL; - if (radius) *radius = 0.0; - return gf->createPolygon().release(); - } - if (center) *center = static_cast(gf->createPoint(mc.getCentre())); + if (center) *center = gf->createPoint(mc.getCentre()).release(); if (radius) *radius = mc.getRadius(); ret->setSRID(g->getSRID()); return ret.release(); @@ -1994,14 +2086,53 @@ extern "C" { case GEOS_MULTIPOLYGON: g = gf->createMultiPolygon(std::move(vgeoms)); break; + case GEOS_MULTICURVE: + g = gf->createMultiCurve(std::move(vgeoms)); + break; + case GEOS_MULTISURFACE: + g = gf->createMultiSurface(std::move(vgeoms)); + break; default: - handle->ERROR_MESSAGE("Unsupported type request for PostGIS2GEOS_collection"); + handle->ERROR_MESSAGE("Unsupported type request for GEOSGeom_createCollection_r"); } return g.release(); }); } + Geometry** + GEOSGeom_releaseCollection_r(GEOSContextHandle_t extHandle, Geometry* collection, unsigned int * ngeoms) + { + return execute(extHandle, [&]() { + GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); + + if (ngeoms == nullptr) { + handle->ERROR_MESSAGE("Parameter ngeoms of GEOSGeom_releaseCollection_r must not be null"); + } + + GeometryCollection *col = dynamic_cast(collection); + if (!col) { + handle->ERROR_MESSAGE("Parameter collection of GEOSGeom_releaseCollection_r must not be a collection"); + } else { + *ngeoms = static_cast(col->getNumGeometries()); + } + + // Early exit on empty/null input + if (!col || *ngeoms == 0) { + return static_cast(nullptr); + } + + std::vector> subgeoms = col->releaseGeometries(); + + Geometry** subgeomArray = static_cast(malloc(sizeof(Geometry*) * subgeoms.size())); + for (std::size_t i = 0; i < subgeoms.size(); i++) { + subgeomArray[i] = subgeoms[i].release(); + } + + return subgeomArray; + }); + } + Geometry* GEOSPolygonize_r(GEOSContextHandle_t extHandle, const Geometry* const* g, unsigned int ngeoms) { @@ -2168,7 +2299,6 @@ extern "C" { return execute(extHandle, [&]() { GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); const GeometryFactory* gf = handle->geomFactory; - Geometry* out; // Polygonize Polygonizer plgnzr; @@ -2186,20 +2316,16 @@ extern "C" { // (it's just a waste of processor and memory, btw) // XXX mloskot: See comment for GEOSPolygonize_r - // TODO avoid "new" here - std::vector* linevec = new std::vector(lines.size()); + std::vector> linevec(lines.size()); for(std::size_t i = 0, n = lines.size(); i < n; ++i) { - (*linevec)[i] = lines[i]->clone().release(); + linevec[i] = lines[i]->clone(); } - // The below takes ownership of the passed vector, - // so we must *not* delete it - - out = gf->createGeometryCollection(linevec); + auto out = gf->createGeometryCollection(std::move(linevec)); out->setSRID(srid); - return out; + return out.release(); }); } @@ -2295,6 +2421,30 @@ extern "C" { }); } + Geometry* + GEOSLineSubstring_r(GEOSContextHandle_t extHandle, const Geometry* g, double start_fraction, double end_fraction) + { + using geos::linearref::LengthIndexedLine; + + return execute(extHandle, [&]() { + if (start_fraction < 0 || end_fraction < 0) { + throw IllegalArgumentException("start fraction must be >= 0"); + } + if (start_fraction > 1 || end_fraction > 1) { + throw IllegalArgumentException("end fraction must be <= 1"); + } + + LengthIndexedLine lil(g); + + auto length = g->getLength(); + + auto out = lil.extractLine(start_fraction * length, end_fraction * length); + out->setSRID(g->getSRID()); + + return out.release(); + }); + } + Geometry* GEOSReverse_r(GEOSContextHandle_t extHandle, const Geometry* g) { @@ -2324,7 +2474,7 @@ extern "C" { const char* GEOSversion() { static char version[256]; - snprintf(version, sizeof(version), "%s", GEOS_CAPI_VERSION); + snprintf(version, 256, "%s", GEOS_CAPI_VERSION); return version; } @@ -2336,14 +2486,16 @@ extern "C" { char GEOSHasZ_r(GEOSContextHandle_t extHandle, const Geometry* g) { - return execute(extHandle, -1, [&]() { - if(g->isEmpty()) { - return false; - } - - double az = g->getCoordinate()->z; + return execute(extHandle, 2, [&]() { + return g->hasZ(); + }); + } - return std::isfinite(az); + char + GEOSHasM_r(GEOSContextHandle_t extHandle, const Geometry* g) + { + return execute(extHandle, 2, [&]() { + return g->hasM(); }); } @@ -2362,8 +2514,8 @@ extern "C" { return execute(extHandle, -1, [&]() { GEOSContextHandleInternal_t *handle = reinterpret_cast(extHandle); - if (newdims < 2 || newdims > 3) { - handle->ERROR_MESSAGE("WKB output dimensions out of range 2..3"); + if (newdims < 2 || newdims > 4) { + handle->ERROR_MESSAGE("WKB output dimensions out of range 2..4"); } const int olddims = handle->WKBOutputDims; @@ -2399,18 +2551,7 @@ extern "C" { GEOSCoordSeq_create_r(GEOSContextHandle_t extHandle, unsigned int size, unsigned int dims) { return execute(extHandle, [&]() { - GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); - - switch (size) { - case 1: - return static_cast(new geos::geom::FixedSizeCoordinateSequence<1>(dims)); - case 2: - return static_cast(new geos::geom::FixedSizeCoordinateSequence<2>(dims)); - default: { - const GeometryFactory *gf = handle->geomFactory; - return gf->getCoordinateSequenceFactory()->create(size, dims).release(); - } - } + return new CoordinateSequence(size, dims); }); } @@ -2418,53 +2559,62 @@ extern "C" { GEOSCoordSeq_copyFromBuffer_r(GEOSContextHandle_t extHandle, const double* buf, unsigned int size, int hasZ, int hasM) { return execute(extHandle, [&]() { - GEOSContextHandleInternal_t *handle = reinterpret_cast(extHandle); - const GeometryFactory *gf = handle->geomFactory; - - std::vector coords(size); std::ptrdiff_t stride = 2 + hasZ + hasM; - + auto coords = geos::detail::make_unique(size, hasZ, hasM, false); if (hasZ) { - if (stride == 3) { - // special case, just memcpy the whole block - static_assert(sizeof(geos::geom::Coordinate) == 3 * sizeof(double), "Coordinate is 3D"); - std::memcpy((double*) coords.data(), buf, size * sizeof(geos::geom::Coordinate)); + if (hasM) { + // XYZM + assert(coords->getCoordinateType() == geos::geom::CoordinateType::XYZM); + std::memcpy(coords->data(), buf, size * sizeof(CoordinateXYZM)); } else { + // XYZ + assert(coords->getCoordinateType() == geos::geom::CoordinateType::XYZ); + std::memcpy(coords->data(), buf, size * sizeof(Coordinate)); + } + } else { + if (hasM) { + // XYM for (std::size_t i = 0; i < size; i++) { - coords[i] = { *buf, *(buf + 1), *(buf + 2) }; + coords->setAt(CoordinateXYM{ *buf, *(buf + 1), *(buf + 2)}, i); + buf += stride; + } + } else { + // XY + for (std::size_t i = 0; i < size; i++) { + coords->setAt(Coordinate{ *buf, *(buf + 1) }, i); buf += stride; } - } - } else { - for (std::size_t i = 0; i < size; i++) { - coords[i] = { *buf, *(buf + 1) }; - buf += stride; } } - return gf->getCoordinateSequenceFactory()->create(std::move(coords)).release(); + return coords.release(); }); } CoordinateSequence* GEOSCoordSeq_copyFromArrays_r(GEOSContextHandle_t extHandle, const double* x, const double* y, const double* z, const double* m, unsigned int size) { - (void) m; - return execute(extHandle, [&]() { - GEOSContextHandleInternal_t *handle = reinterpret_cast(extHandle); - const GeometryFactory *gf = handle->geomFactory; + bool hasZ = z != nullptr; + bool hasM = m != nullptr; - std::vector coords(size); + auto coords = geos::detail::make_unique(size, hasZ, hasM, false); + + CoordinateXYZM c; for (std::size_t i = 0; i < size; i++) { + c.x = x[i]; + c.y = y[i]; if (z) { - coords[i] = { x[i], y[i], z[i] }; - } else { - coords[i] = { x[i], y[i] }; + c.z = z[i]; + } + if (m) { + c.m = m[i]; } + + coords->setAt(c, i); } - return gf->getCoordinateSequenceFactory()->create(std::move(coords)).release(); + return coords.release(); }); } @@ -2473,32 +2623,17 @@ extern "C" { double* x, double* y, double* z, double* m) { return execute(extHandle, 0, [&]() { - - class CoordinateArrayCopier : public geos::geom::CoordinateFilter { - public: - CoordinateArrayCopier(double* p_x, double* p_y, double* p_z) : i(0), x(p_x), y(p_y), z(p_z) {} - - void filter_ro(const geos::geom::Coordinate* c) override { - x[i] = c->x; - y[i] = c->y; - if (z) { - z[i] = c->z; - } - i++; + CoordinateXYZM c; + for (std::size_t i = 0; i < cs->size(); i++) { + cs->getAt(i, c); + x[i] = c.x; + y[i] = c.y; + if (z) { + z[i] = c.z; + } + if (m) { + m[i] = c.m; } - - private: - size_t i; - double* x; - double* y; - double* z; - }; - - CoordinateArrayCopier cop(x, y, z); - cs->apply_ro(&cop); - - if (m) { - std::fill(m, m + cs->getSize(), std::numeric_limits::quiet_NaN()); } return 1; @@ -2509,42 +2644,63 @@ extern "C" { GEOSCoordSeq_copyToBuffer_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs, double* buf, int hasZ, int hasM) { - return execute(extHandle, 0, [&]() { - - class CoordinateBufferCopier : public geos::geom::CoordinateFilter { - public: - CoordinateBufferCopier(double* p_buf, bool p_hasZ, bool p_hasM) : buf(p_buf), m(p_hasM), z(p_hasZ) {} + using geos::geom::CoordinateType; - void filter_ro(const geos::geom::Coordinate* c) override { - *buf = c->x; - buf++; - *buf = c->y; - buf++; + return execute(extHandle, 0, [&]() { + CoordinateType srcType = cs->getCoordinateType(); + CoordinateType dstType; + std::size_t stride; + if (hasZ) { + if (hasM) { + dstType = CoordinateType::XYZM; + stride = 4; + } else { + dstType = CoordinateType::XYZ; + stride = 3; + } + } else { + if (hasM) { + dstType = CoordinateType::XYM; + stride = 3; + } else { + dstType = CoordinateType::XY; + stride = 2; + } + } - if (z) { - *buf = c->z; - buf++; + if (srcType == dstType) { + std::memcpy(buf, cs->data(), cs->size() * stride * sizeof(double)); + } else { + switch(dstType) { + case CoordinateType::XY: { + for (std::size_t i = 0; i < cs->size(); i++) { + CoordinateXY* c = reinterpret_cast(buf + i*stride); + cs->getAt(i, *c); + } + break; } - - if (m) { - *buf = std::numeric_limits::quiet_NaN(); - buf++; + case CoordinateType::XYZ: { + for (std::size_t i = 0; i < cs->size(); i++) { + Coordinate* c = reinterpret_cast(buf + i*stride); + cs->getAt(i, *c); + } + break; + } + case CoordinateType::XYM: { + for (std::size_t i = 0; i < cs->size(); i++) { + CoordinateXYM* c = reinterpret_cast(buf + i*stride); + cs->getAt(i, *c); + } + break; + } + case CoordinateType::XYZM: { + for (std::size_t i = 0; i < cs->size(); i++) { + CoordinateXYZM* c = reinterpret_cast(buf + i*stride); + cs->getAt(i, *c); + } + break; } } - - private: - double* buf; - const bool m; - const bool z; - }; - - CoordinateBufferCopier cop(buf, hasZ, hasM); - // Speculatively check to see if our input is a CoordinateArraySequence. - // If so, gcc can inline the filter. - if (auto cas = dynamic_cast(cs)) { - cas->apply_ro(&cop); - } else { - cs->apply_ro(&cop); } return 1; @@ -2583,7 +2739,7 @@ extern "C" { GEOSCoordSeq_setXY_r(GEOSContextHandle_t extHandle, CoordinateSequence* cs, unsigned int idx, double x, double y) { return execute(extHandle, 0, [&]() { - cs->setAt({x, y}, idx); + cs->setAt(CoordinateXY{x, y}, idx); return 1; }); } @@ -2592,7 +2748,7 @@ extern "C" { GEOSCoordSeq_setXYZ_r(GEOSContextHandle_t extHandle, CoordinateSequence* cs, unsigned int idx, double x, double y, double z) { return execute(extHandle, 0, [&]() { - cs->setAt({x, y, z}, idx); + cs->setAt(Coordinate{x, y, z}, idx); return 1; }); } @@ -2637,7 +2793,7 @@ extern "C" { GEOSCoordSeq_getXY_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs, unsigned int idx, double* x, double* y) { return execute(extHandle, 0, [&]() { - auto& c = cs->getAt(idx); + auto& c = cs->getAt(idx); *x = c.x; *y = c.y; return 1; @@ -2697,10 +2853,8 @@ extern "C" { const CoordinateSequence* GEOSGeom_getCoordSeq_r(GEOSContextHandle_t extHandle, const Geometry* g) { - using geos::geom::Point; - return execute(extHandle, [&]() { - const LineString* ls = dynamic_cast(g); + const SimpleCurve* ls = dynamic_cast(g); if(ls) { return ls->getCoordinatesRO(); } @@ -2731,7 +2885,7 @@ extern "C" { GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); const GeometryFactory* gf = handle->geomFactory; - return gf->createPoint(cs); + return gf->createPoint(std::unique_ptr(cs)).release(); }); } @@ -2742,8 +2896,8 @@ extern "C" { GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); const GeometryFactory* gf = handle->geomFactory; - geos::geom::Coordinate c(x, y); - return gf->createPoint(c); + CoordinateXY c(x, y); + return gf->createPoint(c).release(); }); } @@ -2754,7 +2908,7 @@ extern "C" { GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); const GeometryFactory* gf = handle->geomFactory; - return gf->createLinearRing(cs); + return gf->createLinearRing(std::unique_ptr(cs)).release(); }); } @@ -2776,7 +2930,7 @@ extern "C" { GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); const GeometryFactory* gf = handle->geomFactory; - return gf->createLineString(cs); + return gf->createLineString(std::unique_ptr(cs)).release(); }); } @@ -2793,8 +2947,6 @@ extern "C" { Geometry* GEOSGeom_createPolygon_r(GEOSContextHandle_t extHandle, Geometry* shell, Geometry** holes, unsigned int nholes) { - using geos::geom::LinearRing; - return execute(extHandle, [&]() { GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); const GeometryFactory* gf = handle->geomFactory; @@ -2848,11 +3000,118 @@ extern "C" { return execute(extHandle, [&]() { GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); const GeometryFactory* gf = handle->geomFactory; - geos::geom::Envelope env(xmin, xmax, ymin, ymax); + Envelope env(xmin, xmax, ymin, ymax); return (gf->toGeometry(&env)).release(); }); } + Geometry* + GEOSGeom_createCircularString_r(GEOSContextHandle_t extHandle, CoordinateSequence* cs) + { + return execute(extHandle, [&]() { + GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); + const GeometryFactory* gf = handle->geomFactory; + + return gf->createCircularString(std::unique_ptr(cs)).release(); + }); + } + + Geometry* + GEOSGeom_createEmptyCircularString_r(GEOSContextHandle_t extHandle) + { + return execute(extHandle, [&]() { + GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); + const GeometryFactory* gf = handle->geomFactory; + + return gf->createCircularString(false, false).release(); + }); + } + + Geometry* + GEOSGeom_createCompoundCurve_r(GEOSContextHandle_t extHandle, Geometry** geoms, unsigned int ngeoms) + { + return execute(extHandle, [&]() -> Geometry* { + GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); + const GeometryFactory* gf = handle->geomFactory; + + bool invalid_input = false; + std::vector> geom_vec(ngeoms); + for (std::size_t i = 0; i < ngeoms; i++) { + if (SimpleCurve* c = dynamic_cast(geoms[i])) { + geom_vec[i].reset(c); + } else { + delete geoms[i]; + invalid_input = true; + } + } + + if (invalid_input) { + throw IllegalArgumentException("Input is not a SimpleCurve"); + } + + return gf->createCompoundCurve(std::move(geom_vec)).release(); + }); + } + + Geometry* + GEOSGeom_createEmptyCompoundCurve_r(GEOSContextHandle_t extHandle) + { + return execute(extHandle, [&]() { + GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); + const GeometryFactory* gf = handle->geomFactory; + + return gf->createCompoundCurve().release(); + }); + } + + Geometry* + GEOSGeom_createCurvePolygon_r(GEOSContextHandle_t extHandle, Geometry* p_shell, Geometry** p_holes, unsigned int nholes) + { + return execute(extHandle, [&]() { + GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); + const GeometryFactory* gf = handle->geomFactory; + bool good_holes = true, good_shell = true; + + std::unique_ptr shell; + std::vector> holes(nholes); + + if (Curve* c = dynamic_cast(p_shell)) { + shell.reset(c); + } else { + good_shell = false; + delete p_shell; + } + + for (std::size_t i = 0; i < nholes; i++) { + if (Curve* c = dynamic_cast(p_holes[i])) { + holes[i].reset(c); + } else { + good_shell = false; + delete p_holes[i]; + } + } + + if (good_shell && good_holes) { + return gf->createCurvePolygon(std::move(shell), std::move(holes)).release(); + } else if (!good_shell) { + throw IllegalArgumentException("Shell is not a Curve"); + } else { + throw IllegalArgumentException("Hole is not a Curve"); + } + }); + } + + + Geometry* + GEOSGeom_createEmptyCurvePolygon_r(GEOSContextHandle_t extHandle) + { + return execute(extHandle, [&]() { + GEOSContextHandleInternal_t* handle = reinterpret_cast(extHandle); + const GeometryFactory* gf = handle->geomFactory; + return gf->createCurvePolygon(false, false).release(); + }); + } + Geometry* GEOSGeom_clone_r(GEOSContextHandle_t extHandle, const Geometry* g) { @@ -2868,34 +3127,31 @@ extern "C" { using namespace geos::geom; return execute(extHandle, [&]() { - std::unique_ptr newpm; + PrecisionModel newpm; if(gridSize != 0) { - // Use negative scale to indicate you actually want a gridSize - double scale = -1.0 * std::abs(gridSize); - newpm.reset(new PrecisionModel(scale)); - } - else { - newpm.reset(new PrecisionModel()); + // Convert gridSize to scale factor + double scale = 1.0 / std::abs(gridSize); + newpm = PrecisionModel(scale); } const PrecisionModel* pm = g->getPrecisionModel(); double cursize = pm->isFloating() ? 0 : 1.0 / pm->getScale(); - Geometry* ret; + std::unique_ptr ret; GeometryFactory::Ptr gf = - GeometryFactory::create(newpm.get(), g->getSRID()); + GeometryFactory::create(&newpm, g->getSRID()); if(gridSize != 0 && cursize != gridSize) { GeometryPrecisionReducer reducer(*gf); reducer.setChangePrecisionModel(true); reducer.setUseAreaReducer(!(flags & GEOS_PREC_NO_TOPO)); reducer.setPointwise(flags & GEOS_PREC_NO_TOPO); reducer.setRemoveCollapsedComponents(!(flags & GEOS_PREC_KEEP_COLLAPSED)); - ret = reducer.reduce(*g).release(); + ret = reducer.reduce(*g); } else { // No need or willing to snap, just change the factory ret = gf->createGeometry(g); } - return ret; + return ret.release(); }); } @@ -3024,8 +3280,6 @@ extern "C" { WKTReader* GEOSWKTReader_create_r(GEOSContextHandle_t extHandle) { - using geos::io::WKTReader; - return execute(extHandle, [&]() { GEOSContextHandleInternal_t *handle = reinterpret_cast(extHandle); return new WKTReader((GeometryFactory *) handle->geomFactory); @@ -3273,7 +3527,7 @@ extern "C" { char GEOSWKBWriter_getIncludeSRID_r(GEOSContextHandle_t extHandle, const GEOSWKBWriter* writer) { - return execute(extHandle, -1, [&]{ + return execute(extHandle, 2, [&]{ return writer->getIncludeSRID(); }); } @@ -3395,6 +3649,15 @@ extern "C" { }); } + char + GEOSPreparedContainsXY_r(GEOSContextHandle_t extHandle, + const geos::geom::prep::PreparedGeometry* pg, double x, double y) + { + extHandle->point2d->setXY(x, y); + + return GEOSPreparedContains_r(extHandle, pg, extHandle->point2d.get()); + } + char GEOSPreparedContainsProperly_r(GEOSContextHandle_t extHandle, const geos::geom::prep::PreparedGeometry* pg, const Geometry* g) @@ -3449,6 +3712,15 @@ extern "C" { }); } + char + GEOSPreparedIntersectsXY_r(GEOSContextHandle_t extHandle, + const geos::geom::prep::PreparedGeometry* pg, double x, double y) + { + extHandle->point2d->setXY(x, y); + + return GEOSPreparedIntersects_r(extHandle, pg, extHandle->point2d.get()); + } + char GEOSPreparedOverlaps_r(GEOSContextHandle_t extHandle, const geos::geom::prep::PreparedGeometry* pg, const Geometry* g) @@ -3476,6 +3748,24 @@ extern "C" { }); } + char * + GEOSPreparedRelate_r(GEOSContextHandle_t extHandle, + const geos::geom::prep::PreparedGeometry* pg, const Geometry* g) + { + return execute(extHandle, [&]() -> char * { + return gstrdup(pg->relate(g)->toString()); + }); + } + + char + GEOSPreparedRelatePattern_r(GEOSContextHandle_t extHandle, + const geos::geom::prep::PreparedGeometry* pg, const Geometry* g, const char* imPattern) + { + return execute(extHandle, 2, [&]() { + return pg->relate(g, std::string(imPattern)); + }); + } + CoordinateSequence* GEOSPreparedNearestPoints_r(GEOSContextHandle_t extHandle, const geos::geom::prep::PreparedGeometry* pg, const Geometry* g) @@ -3521,6 +3811,16 @@ extern "C" { }); } + int + GEOSSTRtree_build_r(GEOSContextHandle_t extHandle, + GEOSSTRtree* tree) + { + return execute(extHandle, 0, [&]() { + tree->build(); + return 1; + }); + } + void GEOSSTRtree_insert_r(GEOSContextHandle_t extHandle, GEOSSTRtree* tree, @@ -3640,8 +3940,8 @@ extern "C" { if(!point) { throw std::runtime_error("third argument of GEOSProject_r must be Point"); } - const geos::geom::Coordinate* inputPt = p->getCoordinate(); - return geos::linearref::LengthIndexedLine(g).project(*inputPt); + const geos::geom::Coordinate inputPt(*p->getCoordinate()); + return geos::linearref::LengthIndexedLine(g).project(inputPt); }); } @@ -3655,9 +3955,9 @@ extern "C" { geos::linearref::LengthIndexedLine lil(g); geos::geom::Coordinate coord = lil.extractPoint(d); const GeometryFactory* gf = handle->geomFactory; - Geometry* point = gf->createPoint(coord); + auto point = coord.isNull() ? gf->createPoint(g->getCoordinateDimension()) : gf->createPoint(coord); point->setSRID(g->getSRID()); - return point; + return point.release(); }); } @@ -3712,20 +4012,20 @@ extern "C" { g->apply_ro(&filter); /* 2: for each point, create a geometry and put into a vector */ - std::vector* points = new std::vector(); - points->reserve(coords.size()); + std::vector> points; + points.reserve(coords.size()); const GeometryFactory* factory = g->getFactory(); for(std::vector::iterator it = coords.begin(), itE = coords.end(); it != itE; ++it) { - Geometry* point = factory->createPoint(*(*it)); - points->push_back(point); + auto point = factory->createPoint(*(*it)); + points.push_back(std::move(point)); } /* 3: create a multipoint */ - Geometry* out = factory->createMultiPoint(points); + auto out = factory->createMultiPoint(std::move(points)); out->setSRID(g->getSRID()); - return out; + return out.release(); }); } @@ -3733,7 +4033,6 @@ extern "C" { int GEOSOrientationIndex_r(GEOSContextHandle_t extHandle, double Ax, double Ay, double Bx, double By, double Px, double Py) { - using geos::geom::Coordinate; using geos::algorithm::Orientation; return execute(extHandle, 2, [&]() { @@ -3781,39 +4080,33 @@ extern "C" { const GeometryFactory* factory = g1->getFactory(); std::size_t count; - std::unique_ptr< std::vector > out1( - new std::vector() - ); + std::vector> out1; count = forw.size(); - out1->reserve(count); + out1.reserve(count); for(std::size_t i = 0; i < count; ++i) { - out1->push_back(forw[i]); + out1.emplace_back(forw[i]); } std::unique_ptr out1g( - factory->createMultiLineString(out1.release()) + factory->createMultiLineString(std::move(out1)) ); - std::unique_ptr< std::vector > out2( - new std::vector() - ); + std::vector> out2; count = back.size(); - out2->reserve(count); + out2.reserve(count); for(std::size_t i = 0; i < count; ++i) { - out2->push_back(back[i]); + out2.emplace_back(back[i]); } std::unique_ptr out2g( - factory->createMultiLineString(out2.release()) + factory->createMultiLineString(std::move(out2)) ); - std::unique_ptr< std::vector > out( - new std::vector() - ); - out->reserve(2); - out->push_back(out1g.release()); - out->push_back(out2g.release()); + std::vector> out; + out.reserve(2); + out.push_back(std::move(out1g)); + out.push_back(std::move(out2g)); std::unique_ptr outg( - factory->createGeometryCollection(out.release()) + factory->createGeometryCollection(std::move(out)) ); outg->setSRID(g1->getSRID()); @@ -3954,7 +4247,7 @@ extern "C" { Geometry* GEOSVoronoiDiagram_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* env, double tolerance, - int onlyEdges) + int flags) { using geos::triangulate::VoronoiDiagramBuilder; @@ -3962,19 +4255,20 @@ extern "C" { VoronoiDiagramBuilder builder; builder.setSites(*g1); builder.setTolerance(tolerance); + builder.setOrdered(flags & GEOS_VORONOI_PRESERVE_ORDER); + std::unique_ptr out; if(env) { builder.setClipEnvelope(env->getEnvelopeInternal()); } - if(onlyEdges) { - Geometry* out = builder.getDiagramEdges(*g1->getFactory()).release(); - out->setSRID(g1->getSRID()); - return out; + if(flags & GEOS_VORONOI_ONLY_EDGES) { + out = builder.getDiagramEdges(*g1->getFactory()); } else { - Geometry* out = builder.getDiagram(*g1->getFactory()).release(); - out->setSRID(g1->getSRID()); - return out; + out = builder.getDiagram(*g1->getFactory()); } + + out->setSRID(g1->getSRID()); + return out.release(); }); } @@ -4000,5 +4294,83 @@ extern "C" { }); } -} /* extern "C" */ + int + GEOSCoverageIsValid_r(GEOSContextHandle_t extHandle, + const Geometry* input, + double gapWidth, + Geometry** invalidEdges) + { + using geos::coverage::CoverageValidator; + + return execute(extHandle, 2, [&]() { + const GeometryCollection* col = dynamic_cast(input); + if (!col) + throw geos::util::IllegalArgumentException("input is not a collection"); + + // Initialize to nullptr + if (invalidEdges) *invalidEdges = nullptr; + + std::vector coverage; + for (const auto& g : *col) { + coverage.push_back(g.get()); + } + + CoverageValidator cov(coverage); + cov.setGapWidth(gapWidth); + std::vector> invalid = cov.validate(); + bool hasInvalid = CoverageValidator::hasInvalidResult(invalid); + + if (invalidEdges) { + const GeometryFactory* gf = input->getFactory(); + for (auto& g : invalid) { + // Replace nullptr with 'MULTILINESTRING EMPTY' + if (g == nullptr) { + auto empty = gf->createEmpty(1); + g.reset(empty.release()); + } + } + auto r = gf->createGeometryCollection(std::move(invalid)); + *invalidEdges = r.release(); + } + + return hasInvalid ? 0 : 1; + }); + } + + Geometry* + GEOSCoverageSimplifyVW_r(GEOSContextHandle_t extHandle, + const Geometry* input, + double tolerance, + int preserveBoundary) + { + using geos::coverage::CoverageSimplifier; + + return execute(extHandle, [&]() -> Geometry* { + const GeometryCollection* col = dynamic_cast(input); + if (!col) + return nullptr; + std::vector coverage; + for (const auto& g : *col) { + coverage.push_back(g.get()); + } + CoverageSimplifier cov(coverage); + std::vector> simple; + if (preserveBoundary == 1) { + simple = cov.simplifyInner(tolerance); + } + else if (preserveBoundary == 0) { + simple = cov.simplify(tolerance); + } + else return nullptr; + + const GeometryFactory* gf = input->getFactory(); + std::unique_ptr r = gf->createGeometryCollection(std::move(simple)); + return r.release(); + }); + } + + + + +} /* extern "C" */ diff --git a/Sources/geos/include/geos/algorithm/Angle.h b/Sources/geos/include/geos/algorithm/Angle.h index e3b9bdd..a2ea53a 100644 --- a/Sources/geos/include/geos/algorithm/Angle.h +++ b/Sources/geos/include/geos/algorithm/Angle.h @@ -74,8 +74,8 @@ class GEOS_DLL Angle { /// @return the normalized angle (in radians) that p0-p1 makes /// with the positive x-axis. /// - static double angle(const geom::Coordinate& p0, - const geom::Coordinate& p1); + static double angle(const geom::CoordinateXY& p0, + const geom::CoordinateXY& p1); /// \brief /// Returns the angle that the vector from (0,0) to p, @@ -86,7 +86,7 @@ class GEOS_DLL Angle { /// @return the normalized angle (in radians) that p makes /// with the positive x-axis. /// - static double angle(const geom::Coordinate& p); + static double angle(const geom::CoordinateXY& p); /// Tests whether the angle between p0-p1-p2 is acute. /// @@ -99,9 +99,9 @@ class GEOS_DLL Angle { /// @param p1 the base of the angle /// @param p2 the other endpoint of the angle /// - static bool isAcute(const geom::Coordinate& p0, - const geom::Coordinate& p1, - const geom::Coordinate& p2); + static bool isAcute(const geom::CoordinateXY& p0, + const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2); /// Tests whether the angle between p0-p1-p2 is obtuse. /// @@ -114,9 +114,9 @@ class GEOS_DLL Angle { /// @param p1 the base of the angle /// @param p2 the other endpoint of the angle /// - static bool isObtuse(const geom::Coordinate& p0, - const geom::Coordinate& p1, - const geom::Coordinate& p2); + static bool isObtuse(const geom::CoordinateXY& p0, + const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2); /// Returns the unoriented smallest angle between two vectors. /// @@ -127,9 +127,9 @@ class GEOS_DLL Angle { /// @param tip2 the tip of the other vector /// @return the angle between tail-tip1 and tail-tip2 /// - static double angleBetween(const geom::Coordinate& tip1, - const geom::Coordinate& tail, - const geom::Coordinate& tip2); + static double angleBetween(const geom::CoordinateXY& tip1, + const geom::CoordinateXY& tail, + const geom::CoordinateXY& tip2); /// Returns the oriented smallest angle between two vectors. /// @@ -143,9 +143,9 @@ class GEOS_DLL Angle { /// @param tip2 the tip of v2 /// @return the angle between v1 and v2, relative to v1 /// - static double angleBetweenOriented(const geom::Coordinate& tip1, - const geom::Coordinate& tail, - const geom::Coordinate& tip2); + static double angleBetweenOriented(const geom::CoordinateXY& tip1, + const geom::CoordinateXY& tail, + const geom::CoordinateXY& tip2); /// Computes the interior angle between two segments of a ring. /// @@ -160,9 +160,9 @@ class GEOS_DLL Angle { /// the next point of the ring /// @return the interior angle based at p1 /// - static double interiorAngle(const geom::Coordinate& p0, - const geom::Coordinate& p1, - const geom::Coordinate& p2); + static double interiorAngle(const geom::CoordinateXY& p0, + const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2); /// \brief /// Returns whether an angle must turn clockwise or counterclockwise @@ -216,6 +216,28 @@ class GEOS_DLL Angle { /// (in range [0, Pi] ) /// static double diff(double ang1, double ang2); + + /// \brief + /// Computes both sin and cos of an angle, snapping near-zero values + /// to zero. + /// + /// The angle does not need to be normalized. Unlike std::sin + /// and std::cos, this method will snap near-zero values to zero + /// for (e.g.) sin(pi) and cos(pi/2). + /// + /// @param ang the input angle (in radians) + /// @param rSin the result of sin(ang) + /// @param rCos the result of cos(ang) + /// + static inline void sinCosSnap(const double ang, double& rSin, double& rCos) { + // calculate both; may be optimized with FSINCOS instruction + rSin = std::sin(ang); + rCos = std::cos(ang); + // snap near-zero values + if (std::fabs(rSin) < 5e-16) rSin = 0.0; + if (std::fabs(rCos) < 5e-16) rCos = 0.0; + } + }; diff --git a/Sources/geos/include/geos/algorithm/Area.h b/Sources/geos/include/geos/algorithm/Area.h index f53938c..c3c799f 100644 --- a/Sources/geos/include/geos/algorithm/Area.h +++ b/Sources/geos/include/geos/algorithm/Area.h @@ -23,12 +23,19 @@ #include namespace geos { + +namespace geom { +class Curve; +} + namespace algorithm { // geos::algorithm class GEOS_DLL Area { public: + static double ofClosedCurve(const geom::Curve& ring); + /** * Computes the area for a ring. * diff --git a/Sources/geos/include/geos/algorithm/BoundaryNodeRule.h b/Sources/geos/include/geos/algorithm/BoundaryNodeRule.h index 07c3826..19cd6e1 100644 --- a/Sources/geos/include/geos/algorithm/BoundaryNodeRule.h +++ b/Sources/geos/include/geos/algorithm/BoundaryNodeRule.h @@ -18,6 +18,8 @@ #pragma once +#include + #include // Forward declarations @@ -55,6 +57,8 @@ class GEOS_DLL BoundaryNodeRule { virtual ~BoundaryNodeRule() {} + virtual std::string toString() const = 0; + /** \brief * Tests whether a point that lies in `boundaryCount` * geometry component boundaries is considered to form part of diff --git a/Sources/geos/include/geos/algorithm/CGAlgorithmsDD.h b/Sources/geos/include/geos/algorithm/CGAlgorithmsDD.h index 54efa09..ef251c3 100644 --- a/Sources/geos/include/geos/algorithm/CGAlgorithmsDD.h +++ b/Sources/geos/include/geos/algorithm/CGAlgorithmsDD.h @@ -24,7 +24,7 @@ // Forward declarations namespace geos { namespace geom { -class Coordinate; +class CoordinateXY; class CoordinateSequence; } } @@ -64,9 +64,9 @@ class GEOS_DLL CGAlgorithmsDD { * @return -1 if q is clockwise (right) from p1-p2 * @return 0 if q is collinear with p1-p2 */ - static int orientationIndex(const geom::Coordinate& p1, - const geom::Coordinate& p2, - const geom::Coordinate& q); + static int orientationIndex(const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2, + const geom::CoordinateXY& q); static int orientationIndex(double p1x, double p1y, @@ -87,7 +87,7 @@ class GEOS_DLL CGAlgorithmsDD { * * Uses an approach due to Jonathan Shewchuk, which is in the public domain. */ - static int orientationIndexFilter( + static inline int orientationIndexFilter( double pax, double pay, double pbx, double pby, double pcx, double pcy) @@ -151,8 +151,8 @@ class GEOS_DLL CGAlgorithmsDD { * @param q2 an endpoint of line segment 2 * @return an intersection point if one exists, or null if the lines are parallel */ - static geom::Coordinate intersection(const geom::Coordinate& p1, const geom::Coordinate& p2, - const geom::Coordinate& q1, const geom::Coordinate& q2); + static geom::CoordinateXY intersection(const geom::CoordinateXY& p1, const geom::CoordinateXY& p2, + const geom::CoordinateXY& q1, const geom::CoordinateXY& q2); static int signOfDet2x2(double dx1, double dy1, double dx2, double dy2); @@ -171,14 +171,14 @@ class GEOS_DLL CGAlgorithmsDD { * the circumcentre of an obtuse isosceles triangle lies outside the triangle. * * This method uses @ref geos::math::DD extended-precision arithmetic to provide more accurate - * results than (@ref geos::geom::Triangle::circumcentre()). + * results than geos::geom::Triangle::circumcentre. * * @param a a vertex of the triangle * @param b a vertex of the triangle * @param c a vertex of the triangle * @return the circumcentre of the triangle */ - static geom::Coordinate circumcentreDD(const geom::Coordinate& a, const geom::Coordinate& b, const geom::Coordinate& c); + static geom::CoordinateXY circumcentreDD(const geom::CoordinateXY& a, const geom::CoordinateXY& b, const geom::CoordinateXY& c); protected: diff --git a/Sources/geos/include/geos/algorithm/Centroid.h b/Sources/geos/include/geos/algorithm/Centroid.h index 28b9135..5cef5bd 100644 --- a/Sources/geos/include/geos/algorithm/Centroid.h +++ b/Sources/geos/include/geos/algorithm/Centroid.h @@ -70,7 +70,7 @@ class GEOS_DLL Centroid { * @return `true` if a centroid could be computed, * `false` otherwise (empty geom) */ - static bool getCentroid(const geom::Geometry& geom, geom::Coordinate& cent); + static bool getCentroid(const geom::Geometry& geom, geom::CoordinateXY& cent); /** \brief * Creates a new instance for computing the centroid of a geometry @@ -92,15 +92,15 @@ class GEOS_DLL Centroid { * @return `true` if a centroid could be computed, * `false` otherwise (empty geom) */ - bool getCentroid(geom::Coordinate& cent) const; + bool getCentroid(geom::CoordinateXY& cent) const; private: - std::unique_ptr areaBasePt; - geom::Coordinate triangleCent3; - geom::Coordinate cg3; - geom::Coordinate lineCentSum; - geom::Coordinate ptCentSum; + std::unique_ptr areaBasePt; + geom::CoordinateXY triangleCent3; + geom::CoordinateXY cg3; + geom::CoordinateXY lineCentSum; + geom::CoordinateXY ptCentSum; double areasum2; double totalLength; int ptCount; @@ -112,7 +112,7 @@ class GEOS_DLL Centroid { */ void add(const geom::Geometry& geom); - void setAreaBasePoint(const geom::Coordinate& basePt); + void setAreaBasePoint(const geom::CoordinateXY& basePt); void add(const geom::Polygon& poly); @@ -120,7 +120,7 @@ class GEOS_DLL Centroid { void addHole(const geom::CoordinateSequence& pts); - void addTriangle(const geom::Coordinate& p0, const geom::Coordinate& p1, const geom::Coordinate& p2, + void addTriangle(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, const geom::CoordinateXY& p2, bool isPositiveArea); /** @@ -128,14 +128,14 @@ class GEOS_DLL Centroid { * The factor of 3 is * left in to permit division to be avoided until later. */ - static void centroid3(const geom::Coordinate& p1, const geom::Coordinate& p2, const geom::Coordinate& p3, - geom::Coordinate& c); + static void centroid3(const geom::CoordinateXY& p1, const geom::CoordinateXY& p2, const geom::CoordinateXY& p3, + geom::CoordinateXY& c); /** * Returns twice the signed area of the triangle p1-p2-p3. * The area is positive if the triangle is oriented CCW, and negative if CW. */ - static double area2(const geom::Coordinate& p1, const geom::Coordinate& p2, const geom::Coordinate& p3); + static double area2(const geom::CoordinateXY& p1, const geom::CoordinateXY& p2, const geom::CoordinateXY& p3); /** * Adds the line segments defined by an array of coordinates @@ -149,7 +149,7 @@ class GEOS_DLL Centroid { * Adds a point to the point centroid accumulator. * @param pt a {@link Coordinate} */ - void addPoint(const geom::Coordinate& pt); + void addPoint(const geom::CoordinateXY& pt); }; } // namespace geos::algorithm diff --git a/Sources/geos/include/geos/algorithm/CircularArcs.h b/Sources/geos/include/geos/algorithm/CircularArcs.h new file mode 100644 index 0000000..54f0a9b --- /dev/null +++ b/Sources/geos/include/geos/algorithm/CircularArcs.h @@ -0,0 +1,37 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + +namespace geos { +namespace algorithm { + +class GEOS_DLL CircularArcs { +public: + + /// Return the circle center of an arc defined by three points + static geom::CoordinateXY getCenter(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2); + + /// Expand an envelope to include an arc defined by three points + static void expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2); +}; + +} +} diff --git a/Sources/geos/include/geos/algorithm/ConvexHull.h b/Sources/geos/include/geos/algorithm/ConvexHull.h index a3dfb7a..e8eba46 100644 --- a/Sources/geos/include/geos/algorithm/ConvexHull.h +++ b/Sources/geos/include/geos/algorithm/ConvexHull.h @@ -25,12 +25,15 @@ #include #include -// FIXME: avoid using Cordinate:: typedefs to avoid full include +// FIXME: avoid using Coordinate:: typedefs to avoid full include #include #include #include #include #include +#include + +#include "geos/util.h" #ifdef _MSC_VER #pragma warning(push) @@ -60,27 +63,26 @@ namespace algorithm { // geos::algorithm */ class GEOS_DLL ConvexHull { private: + + static constexpr std::size_t TUNING_REDUCE_SIZE = 50; + + const geom::Geometry* inputGeom; const geom::GeometryFactory* geomFactory; geom::Coordinate::ConstVect inputPts; - void - extractCoordinates(const geom::Geometry* geom) - { - util::UniqueCoordinateArrayFilter filter(inputPts); - geom->apply_ro(&filter); - } - /// Create a CoordinateSequence from the Coordinate::ConstVect /// This is needed to construct the geometries. /// Here coordinate copies happen /// The returned object is newly allocated !NO EXCEPTION SAFE! std::unique_ptr toCoordinateSequence(geom::Coordinate::ConstVect& cv); - void computeOctPts(const geom::Coordinate::ConstVect& src, - geom::Coordinate::ConstVect& tgt); + void computeInnerOctolateralPts( + const geom::Coordinate::ConstVect& src, + geom::Coordinate::ConstVect& tgt); - bool computeOctRing(const geom::Coordinate::ConstVect& src, - geom::Coordinate::ConstVect& tgt); + bool computeInnerOctolateralRing( + const geom::Coordinate::ConstVect& src, + geom::Coordinate::ConstVect& tgt); /** * Uses a heuristic to reduce the number of points scanned @@ -132,7 +134,8 @@ class GEOS_DLL ConvexHull { * equal to or greater than q */ int polarCompare(const geom::Coordinate& o, - const geom::Coordinate& p, const geom::Coordinate& q); + const geom::Coordinate& p, + const geom::Coordinate& q); void grahamScan(const geom::Coordinate::ConstVect& c, geom::Coordinate::ConstVect& ps); @@ -150,7 +153,7 @@ class GEOS_DLL ConvexHull { /** * Write in 'cleaned' a version of 'input' with collinear - * vertexes removed. + * vertices removed. */ void cleanRing(const geom::Coordinate::ConstVect& input, geom::Coordinate::ConstVect& cleaned); @@ -159,7 +162,15 @@ class GEOS_DLL ConvexHull { * @return whether the three coordinates are collinear * and c2 lies between c1 and c3 inclusive */ - bool isBetween(const geom::Coordinate& c1, const geom::Coordinate& c2, const geom::Coordinate& c3); + bool isBetween( + const geom::Coordinate& c1, + const geom::Coordinate& c2, + const geom::Coordinate& c3); + + bool extractUnique(geom::Coordinate::ConstVect& pts, std::size_t maxPts); + std::unique_ptr createFewPointsResult(); + + public: @@ -167,9 +178,10 @@ class GEOS_DLL ConvexHull { * Create a new convex hull construction for the input Geometry. */ ConvexHull(const geom::Geometry* newGeometry) - : geomFactory(newGeometry->getFactory()) + : inputGeom(newGeometry) + , geomFactory(newGeometry->getFactory()) { - extractCoordinates(newGeometry); + util::ensureNoCurvedComponents(inputGeom); }; ~ConvexHull() {}; diff --git a/Sources/geos/include/geos/algorithm/Distance.h b/Sources/geos/include/geos/algorithm/Distance.h index 6bd190e..5d82384 100644 --- a/Sources/geos/include/geos/algorithm/Distance.h +++ b/Sources/geos/include/geos/algorithm/Distance.h @@ -49,10 +49,11 @@ class GEOS_DLL Distance { * another point of the line (must be different to A) */ // formerly distanceLineLine - static double segmentToSegment(const geom::Coordinate& A, - const geom::Coordinate& B, - const geom::Coordinate& C, - const geom::Coordinate& D); + static double segmentToSegment( + const geom::CoordinateXY& A, + const geom::CoordinateXY& B, + const geom::CoordinateXY& C, + const geom::CoordinateXY& D); /** * Computes the distance from a point to a sequence of line segments. @@ -63,8 +64,9 @@ class GEOS_DLL Distance { * a sequence of contiguous line segments defined by their vertices * @return the minimum distance between the point and the line segments */ - static double pointToSegmentString(const geom::Coordinate& p, - const geom::CoordinateSequence* seq); + static double pointToSegmentString( + const geom::CoordinateXY& p, + const geom::CoordinateSequence* seq); /** * Computes the distance from a point p to a line segment AB @@ -80,9 +82,10 @@ class GEOS_DLL Distance { * @return the distance from p to line segment AB */ // formerly distancePointLine - static double pointToSegment(const geom::Coordinate& p, - const geom::Coordinate& A, - const geom::Coordinate& B); + static double pointToSegment( + const geom::CoordinateXY& p, + const geom::CoordinateXY& A, + const geom::CoordinateXY& B); /** * Computes the perpendicular distance from a point p to the (infinite) line @@ -97,10 +100,15 @@ class GEOS_DLL Distance { * @return the distance from p to line AB */ // formerly distancePointLinePerpendicular - static double pointToLinePerpendicular(const geom::Coordinate& p, - const geom::Coordinate& A, - const geom::Coordinate& B); + static double pointToLinePerpendicular( + const geom::CoordinateXY& p, + const geom::CoordinateXY& A, + const geom::CoordinateXY& B); + static double pointToLinePerpendicularSigned( + const geom::CoordinateXY& p, + const geom::CoordinateXY& A, + const geom::CoordinateXY& B); }; } // namespace geos::algorithm diff --git a/Sources/geos/include/geos/algorithm/HCoordinate.h b/Sources/geos/include/geos/algorithm/HCoordinate.h index c7c328b..294981c 100644 --- a/Sources/geos/include/geos/algorithm/HCoordinate.h +++ b/Sources/geos/include/geos/algorithm/HCoordinate.h @@ -72,7 +72,7 @@ class GEOS_DLL HCoordinate { /** \brief * Constructs a homogeneous coordinate which is the intersection - * of the lines define by the homogenous coordinates represented + * of the lines define by the homogeneous coordinates represented * by two [Coordinates](@ref geom::Coordinate). * * @param p1 diff --git a/Sources/geos/include/geos/algorithm/InteriorPointPoint.h b/Sources/geos/include/geos/algorithm/InteriorPointPoint.h index 78ceefa..08fe688 100644 --- a/Sources/geos/include/geos/algorithm/InteriorPointPoint.h +++ b/Sources/geos/include/geos/algorithm/InteriorPointPoint.h @@ -42,7 +42,7 @@ class GEOS_DLL InteriorPointPoint { bool hasInterior; - geom::Coordinate centroid; + geom::CoordinateXY centroid; double minDistance; @@ -55,7 +55,7 @@ class GEOS_DLL InteriorPointPoint { */ void add(const geom::Geometry* geom); - void add(const geom::Coordinate* point); + void add(const geom::CoordinateXY* point); public: @@ -63,7 +63,7 @@ class GEOS_DLL InteriorPointPoint { ~InteriorPointPoint() {} - bool getInteriorPoint(geom::Coordinate& ret) const; + bool getInteriorPoint(geom::CoordinateXY& ret) const; }; diff --git a/Sources/geos/include/geos/algorithm/Interpolate.h b/Sources/geos/include/geos/algorithm/Interpolate.h new file mode 100644 index 0000000..1768c89 --- /dev/null +++ b/Sources/geos/include/geos/algorithm/Interpolate.h @@ -0,0 +1,182 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2016 Vivid Solutions Inc. + * Copyright (C) 2023 ISciences LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +#include + +namespace geos { +namespace algorithm { + +class GEOS_DLL Interpolate { + +private: + + template + static double + interpolate(const geom::CoordinateXY& p, const CoordType& p1, const CoordType& p2) + { + double p1z = p1.template get(); + double p2z = p2.template get(); + + if (std::isnan(p1z)) { + return p2z; // may be NaN + } + if (std::isnan(p2z)) { + return p1z; // may be NaN + } + if (p.equals2D(p1)) { + return p1z; // not NaN + } + if (p.equals2D(p2)) { + return p2z; // not NaN + } + double dz = p2z - p1z; + if (dz == 0.0) { + return p1z; + } + + // interpolate Z from distance of p along p1-p2 + double dx = (p2.x - p1.x); + double dy = (p2.y - p1.y); + // seg has non-zero length since p1 < p < p2 + double seglen = (dx * dx + dy * dy); + double xoff = (p.x - p1.x); + double yoff = (p.y - p1.y); + double plen = (xoff * xoff + yoff * yoff); + double frac = std::sqrt(plen / seglen); + double zoff = dz * frac; + double zInterpolated = p1z + zoff; + + return zInterpolated; + } + + template + static double + interpolate(const geom::CoordinateXY& p, const C1& p1, const C1& p2, const C2& q1, const C2& q2) + { + double zp = interpolate(p, p1, p2); + double zq = interpolate(p, q1, q2); + + if (std::isnan(zp)) { + return zq; // may be NaN + } + if (std::isnan(zq)) { + return zp; // may be NaN + } + + return (zp + zq) / 2.0; + } + + template + static double + get(const C1& p, const C2& q) + { + double a = p.template get(); + double b = q.template get(); + if (std::isnan(a)) { + return b; + } + return a; + } + + template + static double + getOrInterpolate(const C1& p, const C2& p1, const C2& p2) + { + double z = p.template get(); + if (!std::isnan(z)) return z; + return interpolate(p, p1, p2); + } + + static double + interpolate(const geom::CoordinateXY& p, const geom::CoordinateXY& p1, const geom::CoordinateXY& p2) + { + (void) p; (void) p1; (void) p2; + return DoubleNotANumber; + } + +public: + /// Interpolate a Z value for a coordinate from two other coordinates. + template + static double + zInterpolate(const geom::CoordinateXY& p, const CoordType& p1, const CoordType& p2) + { + return interpolate(p, p1, p2); + } + + /// Calculate an average interpolated Z value from two pairs of other coordinates. + template + static double + zInterpolate(const geom::CoordinateXY& p, const C1& p1, const C1& p2, const C2& q1, const C2& q2) + { + return interpolate(p, p1, p2, q1, q2); + } + + /// Interpolate an M value for a coordinate from two other coordinates. + template + static double + mInterpolate(const geom::CoordinateXY& p, const CoordType& p1, const CoordType& p2) + { + return interpolate(p, p1, p2); + } + + /// Calculate an average interpolated M value from two pairs of other coordinates. + template + static double + mInterpolate(const geom::CoordinateXY& p, const C1& p1, const C1& p2, const C2& q1, const C2& q2) + { + return interpolate(p, p1, p2, q1, q2); + } + + /// Return the first non-NaN Z value from two coordinates, or NaN if both values are NaN. + template + static double + zGet(const C1& p, const C2& q) + { + return get(p, q); + } + + /// Return the first non-NaN M value from two coordinates, or NaN if both values are NaN. + template + static double + mGet(const C1& p, const C2& q) + { + return get(p, q); + } + + /// Return a coordinates's non-NaN Z value or interpolate it from two other coordinates if it is NaN. + template + static double + zGetOrInterpolate(const C1& p, const C2& p1, const C2& p2) + { + return getOrInterpolate(p, p1, p2); + } + + /// Return a coordinates's non-NaN M value or interpolate it from two other coordinates if it is NaN. + template + static double + mGetOrInterpolate(const C1& p, const C2& p1, const C2& p2) + { + return getOrInterpolate(p, p1, p2); + } + +}; + +} +} diff --git a/Sources/geos/include/geos/algorithm/Intersection.h b/Sources/geos/include/geos/algorithm/Intersection.h index fd2fe5a..de413d4 100644 --- a/Sources/geos/include/geos/algorithm/Intersection.h +++ b/Sources/geos/include/geos/algorithm/Intersection.h @@ -19,19 +19,28 @@ #include namespace geos { -namespace algorithm { // geos::algorithm +namespace algorithm { + + +/** \brief + * Functions to compute intersection points between lines and line segments. + * + * In general it is not possible to compute + * the intersection point of two lines exactly, due to numerical roundoff. + * This is particularly true when the lines are nearly parallel. + * These routines uses numerical conditioning on the input values + * to ensure that the computed value is very close to the correct value. + * + * The Z-ordinate is ignored, and not populated. + */ +class GEOS_DLL Intersection { + +public: /** \brief * Computes the intersection point of two lines. * If the lines are parallel or collinear this case is detected * and null is returned. - *

- * In general it is not possible to accurately compute - * the intersection point of two lines, due to - * numerical roundoff. - * This is particularly true when the input lines are nearly parallel. - * This routine uses numerical conditioning on the input values - * to ensure that the computed value should be very close to the correct value. * * @param p1 an endpoint of line 1 * @param p2 an endpoint of line 1 @@ -42,12 +51,30 @@ namespace algorithm { // geos::algorithm * * @see CGAlgorithmsDD#intersection(Coordinate, Coordinate, Coordinate, Coordinate) */ -class GEOS_DLL Intersection { +static geom::CoordinateXY intersection(const geom::CoordinateXY& p1, const geom::CoordinateXY& p2, + const geom::CoordinateXY& q1, const geom::CoordinateXY& q2); -public: +/** +* Computes the intersection point of a line and a line segment (if any). +* There will be no intersection point if: +* +* * the segment does not intersect the line +* * the line or the segment are degenerate (have zero length) +* +* If the segment is collinear with the line the first segment endpoint is returned. +* +* @param line1 a point on the line +* @param line2 a point on the line +* @param seg1 an endpoint of the line segment +* @param seg2 an endpoint of the line segment +* @return the intersection point, or null if it is not possible to find an intersection +*/ +static geom::CoordinateXY intersectionLineSegment( + const geom::CoordinateXY& line1, + const geom::CoordinateXY& line2, + const geom::CoordinateXY& seg1, + const geom::CoordinateXY& seg2); -static geom::Coordinate intersection(const geom::Coordinate& p1, const geom::Coordinate& p2, - const geom::Coordinate& q1, const geom::Coordinate& q2); }; diff --git a/Sources/geos/include/geos/algorithm/LineIntersector.h b/Sources/geos/include/geos/algorithm/LineIntersector.h index b6635b5..ebf676b 100644 --- a/Sources/geos/include/geos/algorithm/LineIntersector.h +++ b/Sources/geos/include/geos/algorithm/LineIntersector.h @@ -21,8 +21,11 @@ #include #include +#include +#include #include #include +#include #include @@ -50,12 +53,6 @@ namespace algorithm { // geos::algorithm class GEOS_DLL LineIntersector { public: - /// \brief - /// Return a Z value being the interpolation of Z from p0 and p1 at - /// the given point p - static double interpolateZ(const geom::Coordinate& p, const geom::Coordinate& p0, const geom::Coordinate& p1); - - /// Computes the "edge distance" of an intersection point p in an edge. /// /// The edge distance is a metric of the point along the edge. @@ -74,7 +71,7 @@ class GEOS_DLL LineIntersector { /// result of rounding points which lie on the line, /// but not safe to use for truncated points. /// - static double computeEdgeDistance(const geom::Coordinate& p, const geom::Coordinate& p0, const geom::Coordinate& p1); + static double computeEdgeDistance(const geom::CoordinateXY& p, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1); static double nonRobustComputeEdgeDistance(const geom::Coordinate& p, const geom::Coordinate& p1, const geom::Coordinate& p2); @@ -137,17 +134,6 @@ class GEOS_DLL LineIntersector { precisionModel = newPM; } - /// Compute the intersection of a point p and the line p1-p2. - /// - /// This function computes the boolean value of the hasIntersection test. - /// The actual value of the intersection (if there is one) - /// is equal to the value of p. - /// - void computeIntersection(const geom::Coordinate& p, const geom::Coordinate& p1, const geom::Coordinate& p2); - - /// Same as above but doesn't compute intersection point. Faster. - static bool hasIntersection(const geom::Coordinate& p, const geom::Coordinate& p1, const geom::Coordinate& p2); - enum intersection_type : uint8_t { /// Indicates that line segments do not intersect NO_INTERSECTION = 0, @@ -160,8 +146,20 @@ class GEOS_DLL LineIntersector { }; /// Computes the intersection of the lines p1-p2 and p3-p4 - void computeIntersection(const geom::Coordinate& p1, const geom::Coordinate& p2, - const geom::Coordinate& p3, const geom::Coordinate& p4); + template + void computeIntersection(const C1& p1, const C1& p2, + const C2& p3, const C2& p4) + { + inputLines[0][0] = &p1; + inputLines[0][1] = &p2; + inputLines[1][0] = &p3; + inputLines[1][1] = &p4; + result = computeIntersect(p1, p2, p3, p4); + } + + /// Compute the intersection between two segments, given a sequence and starting index of each + void computeIntersection(const geom::CoordinateSequence& p, std::size_t p0, + const geom::CoordinateSequence& q, std::size_t q0); std::string toString() const; @@ -184,7 +182,7 @@ class GEOS_DLL LineIntersector { * @param ptIndex the index of the endpoint (0 or 1) * @return the specified endpoint */ - const geom::Coordinate* + const geom::CoordinateXY* getEndpoint(std::size_t segmentIndex, std::size_t ptIndex) const { return inputLines[segmentIndex][ptIndex]; @@ -207,7 +205,7 @@ class GEOS_DLL LineIntersector { /// /// @return the intIndex'th intersection point /// - const geom::Coordinate& + const geom::CoordinateXYZM& getIntersection(std::size_t intIndex) const { return intPt[intIndex]; @@ -303,13 +301,13 @@ class GEOS_DLL LineIntersector { std::size_t result; - const geom::Coordinate* inputLines[2][2]; + const geom::CoordinateXY* inputLines[2][2]; /** * We store real Coordinates here because * we must compute the Z of intersection point. */ - geom::Coordinate intPt[2]; + geom::CoordinateXYZM intPt[2]; /** * The indexes of the endpoints of the intersection lines, in order along @@ -327,8 +325,136 @@ class GEOS_DLL LineIntersector { return result == COLLINEAR_INTERSECTION; } - uint8_t computeIntersect(const geom::Coordinate& p1, const geom::Coordinate& p2, - const geom::Coordinate& q1, const geom::Coordinate& q2); + template + uint8_t computeIntersect(const C1& p1, const C1& p2, const C2& q1, const C2& q2) + { + isProperVar = false; + + // first try a fast test to see if the envelopes of the lines intersect + if(!geom::Envelope::intersects(p1, p2, q1, q2)) { + return NO_INTERSECTION; + } + + // for each endpoint, compute which side of the other segment it lies + // if both endpoints lie on the same side of the other segment, + // the segments do not intersect + int Pq1 = Orientation::index(p1, p2, q1); + int Pq2 = Orientation::index(p1, p2, q2); + + if((Pq1 > 0 && Pq2 > 0) || (Pq1 < 0 && Pq2 < 0)) { + return NO_INTERSECTION; + } + + int Qp1 = Orientation::index(q1, q2, p1); + int Qp2 = Orientation::index(q1, q2, p2); + + if((Qp1 > 0 && Qp2 > 0) || (Qp1 < 0 && Qp2 < 0)) { + return NO_INTERSECTION; + } + + /** + * Intersection is collinear if each endpoint lies on the other line. + */ + bool collinear = Pq1 == 0 && Pq2 == 0 && Qp1 == 0 && Qp2 == 0; + if(collinear) { + return computeCollinearIntersection(p1, p2, q1, q2); + } + + /* + * At this point we know that there is a single intersection point + * (since the lines are not collinear). + */ + + /* + * Check if the intersection is an endpoint. + * If it is, copy the endpoint as + * the intersection point. Copying the point rather than + * computing it ensures the point has the exact value, + * which is important for robustness. It is sufficient to + * simply check for an endpoint which is on the other line, + * since at this point we know that the inputLines must + * intersect. + */ + geom::CoordinateXYZM p; + double z = DoubleNotANumber; + double m = DoubleNotANumber; + + if(Pq1 == 0 || Pq2 == 0 || Qp1 == 0 || Qp2 == 0) { + + isProperVar = false; + + /* Check for two equal endpoints. + * This is done explicitly rather than by the orientation tests + * below in order to improve robustness. + * + * (A example where the orientation tests fail + * to be consistent is: + * + * LINESTRING ( 19.850257749638203 46.29709338043669, + * 20.31970698357233 46.76654261437082 ) + * and + * LINESTRING ( -48.51001596420236 -22.063180333403878, + * 19.850257749638203 46.29709338043669 ) + * + * which used to produce the INCORRECT result: + * (20.31970698357233, 46.76654261437082, NaN) + */ + + if (p1.equals2D(q1)) { + p = p1; + z = Interpolate::zGet(p1, q1); + m = Interpolate::mGet(p1, q1); + } + else if (p1.equals2D(q2)) { + p = p1; + z = Interpolate::zGet(p1, q2); + m = Interpolate::mGet(p1, q2); + } + else if (p2.equals2D(q1)) { + p = p2; + z = Interpolate::zGet(p2, q1); + m = Interpolate::mGet(p2, q1); + } + else if (p2.equals2D(q2)) { + p = p2; + z = Interpolate::zGet(p2, q2); + m = Interpolate::mGet(p2, q2); + } + /* + * Now check to see if any endpoint lies on the interior of the other segment. + */ + else if(Pq1 == 0) { + p = q1; + z = Interpolate::zGetOrInterpolate(q1, p1, p2); + m = Interpolate::mGetOrInterpolate(q1, p1, p2); + } + else if(Pq2 == 0) { + p = q2; + z = Interpolate::zGetOrInterpolate(q2, p1, p2); + m = Interpolate::mGetOrInterpolate(q2, p1, p2); + } + else if(Qp1 == 0) { + p = p1; + z = Interpolate::zGetOrInterpolate(p1, q1, q2); + m = Interpolate::mGetOrInterpolate(p1, q1, q2); + } + else if(Qp2 == 0) { + p = p2; + z = Interpolate::zGetOrInterpolate(p2, q1, q2); + m = Interpolate::mGetOrInterpolate(p2, q1, q2); + } + } else { + isProperVar = true; + p = intersection(p1, p2, q1, q2); + z = Interpolate::zInterpolate(p, p1, p2, q1, q2); + m = Interpolate::mInterpolate(p, p1, p2, q1, q2); + } + intPt[0] = geom::CoordinateXYZM(p.x, p.y, z, m); + #if GEOS_DEBUG + std::cerr << " POINT_INTERSECTION; intPt[0]:" << intPt[0].toString() << std::endl; + #endif // GEOS_DEBUG + return POINT_INTERSECTION; + } bool isEndPoint() const @@ -340,8 +466,53 @@ class GEOS_DLL LineIntersector { void computeIntLineIndex(std::size_t segmentIndex); - uint8_t computeCollinearIntersection(const geom::Coordinate& p1, const geom::Coordinate& p2, - const geom::Coordinate& q1, const geom::Coordinate& q2); + template + uint8_t computeCollinearIntersection(const C1& p1, const C1& p2, const C2& q1, const C2& q2) + { + bool q1inP = geom::Envelope::intersects(p1, p2, q1); + bool q2inP = geom::Envelope::intersects(p1, p2, q2); + bool p1inQ = geom::Envelope::intersects(q1, q2, p1); + bool p2inQ = geom::Envelope::intersects(q1, q2, p2); + + if(q1inP && q2inP) { + intPt[0] = zmGetOrInterpolateCopy(q1, p1, p2); + intPt[1] = zmGetOrInterpolateCopy(q2, p1, p2); + return COLLINEAR_INTERSECTION; + } + if(p1inQ && p2inQ) { + intPt[0] = zmGetOrInterpolateCopy(p1, q1, q2); + intPt[1] = zmGetOrInterpolateCopy(p2, q1, q2); + return COLLINEAR_INTERSECTION; + } + if(q1inP && p1inQ) { + // if pts are equal Z is chosen arbitrarily + intPt[0] = zmGetOrInterpolateCopy(q1, p1, p2); + intPt[1] = zmGetOrInterpolateCopy(p1, q1, q2); + + return (q1 == p1) && !q2inP && !p2inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION; + } + if(q1inP && p2inQ) { + // if pts are equal Z is chosen arbitrarily + intPt[0] = zmGetOrInterpolateCopy(q1, p1, p2); + intPt[1] = zmGetOrInterpolateCopy(p2, q1, q2); + + return (q1 == p2) && !q2inP && !p1inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION; + } + if(q2inP && p1inQ) { + // if pts are equal Z is chosen arbitrarily + intPt[0] = zmGetOrInterpolateCopy(q2, p1, p2); + intPt[1] = zmGetOrInterpolateCopy(p1, q1, q2); + + return (q2 == p1) && !q1inP && !p2inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION; + } + if(q2inP && p2inQ) { + // if pts are equal Z is chosen arbitrarily + intPt[0] = zmGetOrInterpolateCopy(q2, p1, p2); + intPt[1] = zmGetOrInterpolateCopy(p2, q1, q2); + return (q2 == p2) && !q1inP && !p1inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION; + } + return NO_INTERSECTION; + } /** \brief * This method computes the actual value of the intersection point. @@ -352,10 +523,36 @@ class GEOS_DLL LineIntersector { * removing common significant digits from the calculation to * maintain more bits of precision. */ - geom::Coordinate intersection(const geom::Coordinate& p1, - const geom::Coordinate& p2, - const geom::Coordinate& q1, - const geom::Coordinate& q2) const; + template + geom::CoordinateXYZM intersection (const C1& p1, const C1& p2, const C2& q1, const C2& q2) const { + auto intPtOut = intersectionSafe(p1, p2, q1, q2); + + /* + * Due to rounding it can happen that the computed intersection is + * outside the envelopes of the input segments. Clearly this + * is inconsistent. + * This code checks this condition and forces a more reasonable answer + * + * MD - May 4 2005 - This is still a problem. Here is a failure case: + * + * LINESTRING (2089426.5233462777 1180182.3877339689, + * 2085646.6891757075 1195618.7333999649) + * LINESTRING (1889281.8148903656 1997547.0560044837, + * 2259977.3672235999 483675.17050843034) + * int point = (2097408.2633752143,1144595.8008114607) + */ + + if(! isInSegmentEnvelopes(intPtOut)) { + //intPt = CentralEndpointIntersector::getIntersection(p1, p2, q1, q2); + intPtOut = nearestEndpoint(p1, p2, q1, q2); + } + + if(precisionModel != nullptr) { + precisionModel->makePrecise(intPtOut); + } + + return intPtOut; + } /** * Test whether a point lies in the envelopes of both input segments. @@ -367,7 +564,7 @@ class GEOS_DLL LineIntersector { * @return true if the input point lies within both * input segment envelopes */ - bool isInSegmentEnvelopes(const geom::Coordinate& pt) const + bool isInSegmentEnvelopes(const geom::CoordinateXY& pt) const { geom::Envelope env0(*inputLines[0][0], *inputLines[0][1]); geom::Envelope env1(*inputLines[1][0], *inputLines[1][1]); @@ -386,15 +583,29 @@ class GEOS_DLL LineIntersector { * @param q2 a segment endpoint * @return the computed intersection point is stored there */ - geom::Coordinate intersectionSafe(const geom::Coordinate& p1, const geom::Coordinate& p2, - const geom::Coordinate& q1, const geom::Coordinate& q2) const + template + geom::CoordinateXYZM intersectionSafe(const C1& p1, const C1& p2, + const C2& q1, const C2& q2) const { - geom::Coordinate ptInt = Intersection::intersection(p1, p2, q1, q2); + geom::CoordinateXYZM ptInt(Intersection::intersection(p1, p2, q1, q2)); if (ptInt.isNull()) { - ptInt = nearestEndpoint(p1, p2, q1, q2); + const geom::CoordinateXY& nearest = nearestEndpoint(p1, p2, q1, q2); +#if __cplusplus >= 201703L + if constexpr (std::is_same::value) { +#else + if (std::is_same::value) { +#endif + ptInt = static_cast(nearest); + } else { + if (&nearest == static_cast(&p1) || &nearest == static_cast(&p2)) { + ptInt = static_cast(nearest); + } else { + ptInt = static_cast(nearest); + } + } } return ptInt; - }; + } /** * Finds the endpoint of the segments P and Q which @@ -415,55 +626,23 @@ class GEOS_DLL LineIntersector { * @param q2 an endpoint of segment Q * @return the nearest endpoint to the other segment */ - static geom::Coordinate nearestEndpoint(const geom::Coordinate& p1, - const geom::Coordinate& p2, - const geom::Coordinate& q1, - const geom::Coordinate& q2); - - static double zGet( - const geom::Coordinate& p, - const geom::Coordinate& q) - { - double z = p.z; - if ( std::isnan(z) ) { - z = q.z; // may be NaN - } - return z; - }; + static const geom::CoordinateXY& nearestEndpoint(const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2, + const geom::CoordinateXY& q1, + const geom::CoordinateXY& q2); - static double zGetOrInterpolate( - const geom::Coordinate& p, - const geom::Coordinate& p1, - const geom::Coordinate& p2) - { - double z = p.z; - if (! std::isnan(z) ) return z; - return zInterpolate(p, p1, p2); // may be NaN - }; - static geom::Coordinate zGetOrInterpolateCopy( - const geom::Coordinate& p, - const geom::Coordinate& p1, - const geom::Coordinate& p2) + template + static geom::CoordinateXYZM zmGetOrInterpolateCopy( + const C1& p, + const C2& p1, + const C2& p2) { - geom::Coordinate pCopy = p; - double z = zGetOrInterpolate(p, p1, p2); - pCopy.z = z; + geom::CoordinateXYZM pCopy(p); + pCopy.z = Interpolate::zGetOrInterpolate(p, p1, p2); + pCopy.m = Interpolate::mGetOrInterpolate(p, p1, p2); return pCopy; - }; - - /// \brief - /// Return a Z value being the interpolation of Z from p0 to p1 at - /// the given point p - static double zInterpolate(const geom::Coordinate& p, - const geom::Coordinate& p0, - const geom::Coordinate& p1); - - static double zInterpolate(const geom::Coordinate& p, - const geom::Coordinate& p1, - const geom::Coordinate& p2, - const geom::Coordinate& q1, - const geom::Coordinate& q2); + } }; diff --git a/Sources/geos/include/geos/algorithm/MinimumAreaRectangle.h b/Sources/geos/include/geos/algorithm/MinimumAreaRectangle.h new file mode 100644 index 0000000..0e340a9 --- /dev/null +++ b/Sources/geos/include/geos/algorithm/MinimumAreaRectangle.h @@ -0,0 +1,159 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +#include +#include + +// Forward declarations +namespace geos { + namespace geom { + class CoordinateSequence; + class CoordinateXY; + class Geometry; + class GeometryFactory; + class LineSegment; + class LineString; + class Polygon; + } +} + +using geos::geom::CoordinateSequence; +using geos::geom::CoordinateXY; +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::LineSegment; +using geos::geom::LineString; +using geos::geom::Polygon; + + +namespace geos { +namespace algorithm { // geos::algorithm + + +/** + * Computes the minimum-area rectangle enclosing a Geometry. + * Unlike the Envelope, the rectangle may not be axis-parallel. + * + * The first step in the algorithm is computing the convex hull of the Geometry. + * If the input Geometry is known to be convex, a hint can be supplied to + * avoid this computation. + * + * In degenerate cases the minimum enclosing geometry + * may be a LineString or a Point. + * + * The minimum-area enclosing rectangle does not necessarily + * have the minimum possible width. + * Use MinimumDiameter to compute this. + * + * @see MinimumDiameter + * @see ConvexHull + * + */ +class GEOS_DLL MinimumAreaRectangle { + +private: + + // Members + const Geometry* m_inputGeom; + bool m_isConvex; + + // Methods + std::unique_ptr getMinimumRectangle(); + + std::unique_ptr computeConvex(const Geometry* convexGeom); + + /** + * Computes the minimum-area rectangle for a convex ring of Coordinate. + * + * This algorithm uses the "dual rotating calipers" technique. + * Performance is linear in the number of segments. + * + * @param ring the convex ring to scan + */ + std::unique_ptr computeConvexRing(const CoordinateSequence* ring); + + std::size_t findFurthestVertex( + const CoordinateSequence* pts, + const LineSegment& baseSeg, + std::size_t startIndex, + int orient); + + bool isFurtherOrEqual(double d1, double d2, int orient); + + static double orientedDistance( + const LineSegment& seg, + const CoordinateXY& p, + int orient); + + static std::size_t getNextIndex( + const CoordinateSequence* ring, + std::size_t index); + + /** + * Creates a line of maximum extent from the provided vertices + * @param pts the vertices + * @param factory the geometry factory + * @return the line of maximum extent + */ + static std::unique_ptr computeMaximumLine( + const CoordinateSequence* pts, + const GeometryFactory* factory); + + +public: + + /** + * Compute a minimum-area rectangle for a given Geometry. + * + * @param inputGeom a Geometry + */ + MinimumAreaRectangle(const Geometry* inputGeom) + : m_inputGeom(inputGeom) + , m_isConvex(false) + {}; + + /** + * Compute a minimum rectangle for a Geometry, + * with a hint if the geometry is convex + * (e.g. a convex Polygon or LinearRing, + * or a two-point LineString, or a Point). + * + * @param inputGeom a Geometry which is convex + * @param isConvex true if the input geometry is convex + */ + MinimumAreaRectangle(const Geometry* inputGeom, bool isConvex) + : m_inputGeom(inputGeom) + , m_isConvex(isConvex) + {}; + + /** + * Gets the minimum-area rectangular Polygon which encloses the input geometry. + * If the convex hull of the input is degenerate (a line or point) + * a LineString or Point is returned. + * + * @param geom the geometry + * @return the minimum rectangle enclosing the geometry + */ + static std::unique_ptr getMinimumRectangle(const Geometry* geom); + +}; + + +} // namespace geos::algorithm +} // namespace geos + diff --git a/Sources/geos/include/geos/algorithm/MinimumBoundingCircle.h b/Sources/geos/include/geos/algorithm/MinimumBoundingCircle.h index 576bb81..66bc075 100644 --- a/Sources/geos/include/geos/algorithm/MinimumBoundingCircle.h +++ b/Sources/geos/include/geos/algorithm/MinimumBoundingCircle.h @@ -44,18 +44,18 @@ class GEOS_DLL MinimumBoundingCircle { // member variables const geom::Geometry* input; - std::vector extremalPts; - geom::Coordinate centre; + std::vector extremalPts; + geom::CoordinateXY centre; double radius; void computeCentre(); void compute(); void computeCirclePoints(); - geom::Coordinate lowestPoint(std::vector& pts); - geom::Coordinate pointWitMinAngleWithX(std::vector& pts, geom::Coordinate& P); - geom::Coordinate pointWithMinAngleWithSegment(std::vector& pts, - geom::Coordinate& P, geom::Coordinate& Q); - std::vector farthestPoints(std::vector& pts); + geom::CoordinateXY lowestPoint(std::vector& pts); + geom::CoordinateXY pointWitMinAngleWithX(std::vector& pts, geom::CoordinateXY& P); + geom::CoordinateXY pointWithMinAngleWithSegment(std::vector& pts, + geom::CoordinateXY& P, geom::CoordinateXY& Q); + std::vector farthestPoints(std::vector& pts); public: @@ -113,7 +113,7 @@ class GEOS_DLL MinimumBoundingCircle { * * @return the points defining the Minimum Bounding Circle */ - std::vector getExtremalPoints(); + std::vector getExtremalPoints(); /** * Gets the centre point of the computed Minimum Bounding Circle. @@ -121,7 +121,7 @@ class GEOS_DLL MinimumBoundingCircle { * @return the centre point of the Minimum Bounding Circle * @return null if the input is empty */ - geom::Coordinate getCentre(); + geom::CoordinateXY getCentre(); /** * Gets the radius of the computed Minimum Bounding Circle. diff --git a/Sources/geos/include/geos/algorithm/MinimumDiameter.h b/Sources/geos/include/geos/algorithm/MinimumDiameter.h index 956f90d..ada2d84 100644 --- a/Sources/geos/include/geos/algorithm/MinimumDiameter.h +++ b/Sources/geos/include/geos/algorithm/MinimumDiameter.h @@ -3,6 +3,7 @@ * GEOS - Geometry Engine Open Source * http://geos.osgeo.org * + * Copyright (C) 2023 Paul Ramsey * Copyright (C) 2005-2006 Refractions Research Inc. * Copyright (C) 2001-2002 Vivid Solutions Inc. * @@ -11,10 +12,6 @@ * by the Free Software Foundation. * See the COPYING file for more information. * - ********************************************************************** - * - * Last port: algorithm/MinimumDiameter.java r966 - * **********************************************************************/ #pragma once @@ -53,12 +50,16 @@ namespace algorithm { // geos::algorithm * * This class can also be used to compute a line segment representing * the minimum diameter, the supporting line segment of the minimum diameter, - * and a minimum rectangle enclosing the input geometry. + * and a minimum-width rectangle of the input geometry. * This rectangle will have width equal to the minimum diameter, and have * one side parallel to the supporting segment. * - * @see ConvexHull + * In degenerate cases the rectangle may be a LineString or a Point. + * (Note that this may not be the enclosing rectangle with minimum area; + * use MinimumAreaRectangle to compute this.) * + * @see ConvexHull + * @see MinimumAreaRectangle */ class GEOS_DLL MinimumDiameter { private: @@ -75,7 +76,7 @@ class GEOS_DLL MinimumDiameter { void computeWidthConvex(const geom::Geometry* geom); /** - * Compute the width information for a ring of {@link geom::Coordinate}s. + * Compute the width information for a ring of Coordinate. * Leaves the width information in the instance variables. * * @param pts @@ -127,7 +128,7 @@ class GEOS_DLL MinimumDiameter { double getLength(); /** \brief - * Gets the {@link geom::Coordinate} forming one end of the minimum diameter. + * Gets the geom::Coordinate forming one end of the minimum diameter. * * @return a coordinate forming one end of the minimum diameter */ @@ -148,15 +149,17 @@ class GEOS_DLL MinimumDiameter { std::unique_ptr getDiameter(); /** \brief - * Gets the minimum rectangular Polygon which encloses the input geometry. + * Gets the rectangular Polygon which encloses the input geometry + * and is based on the minimum diameter supporting segment. * * The rectangle has width equal to the minimum diameter, and a longer * length. If the convex hill of the input is degenerate (a line or point) * a LineString or Point is returned. - * The minimum rectangle can be used as an extremely generalized - * representation for the given geometry. + * This is not necessarily the rectangle with minimum area. + * Use MinimumAreaRectangle to compute this. * - * @return the minimum rectangle enclosing the input (or a line or point if degenerate) + * @return the the minimum-width rectangle enclosing the geometry + * @see MinimumAreaRectangle */ std::unique_ptr getMinimumRectangle(); @@ -164,7 +167,8 @@ class GEOS_DLL MinimumDiameter { * Gets the minimum rectangle enclosing a geometry. * * @param geom the geometry - * @return the minimum rectangle enclosing the geometry + * @return a rectangle enclosing the input (or a line or point if degenerate) + * @see MinimumAreaRectangle */ static std::unique_ptr getMinimumRectangle(geom::Geometry* geom); diff --git a/Sources/geos/include/geos/algorithm/Orientation.h b/Sources/geos/include/geos/algorithm/Orientation.h index 0cd6d7b..135a4f0 100644 --- a/Sources/geos/include/geos/algorithm/Orientation.h +++ b/Sources/geos/include/geos/algorithm/Orientation.h @@ -64,8 +64,8 @@ class GEOS_DLL Orientation { * ( `Orientation::COUNTERCLOCKWISE`, * `Orientation::CLOCKWISE`, or `Orientation::STRAIGHT` ) */ - static int index(const geom::Coordinate& p1, const geom::Coordinate& p2, - const geom::Coordinate& q); + static int index(const geom::CoordinateXY& p1, const geom::CoordinateXY& p2, + const geom::CoordinateXY& q); /** * Computes whether a ring defined by a geom::CoordinateSequence is diff --git a/Sources/geos/include/geos/algorithm/PointLocation.h b/Sources/geos/include/geos/algorithm/PointLocation.h index bfcea1c..6c3f74b 100644 --- a/Sources/geos/include/geos/algorithm/PointLocation.h +++ b/Sources/geos/include/geos/algorithm/PointLocation.h @@ -19,11 +19,18 @@ #pragma once #include -#include -#include #include +#include namespace geos { + +namespace geom { +class Coordinate; +class CoordinateXY; +class CoordinateSequence; +class Curve; +} + namespace algorithm { // geos::algorithm /** \brief @@ -36,6 +43,16 @@ namespace algorithm { // geos::algorithm class GEOS_DLL PointLocation { public: + /** \brief + * Tests whether a point lies on a line segment. + * + * @param p the point to test + * @param p0 a point of the line segment + * @param p1 a point of the line segment + * @return true if the point lies on the line segment + */ + static bool isOnSegment(const geom::CoordinateXY& p, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1); + /** \brief * Tests whether a point lies on the line defined by a * [CoordinateSequence](@ref geom::CoordinateSequence). @@ -45,7 +62,7 @@ class GEOS_DLL PointLocation { * @return true if the point is a vertex of the line or lies in the interior * of a line segment in the line */ - static bool isOnLine(const geom::Coordinate& p, const geom::CoordinateSequence* line); + static bool isOnLine(const geom::CoordinateXY& p, const geom::CoordinateSequence* line); /** \brief * Tests whether a point lies inside or on a ring. @@ -63,8 +80,8 @@ class GEOS_DLL PointLocation { * * @see RayCrossingCounter::locatePointInRing() */ - static bool isInRing(const geom::Coordinate& p, const std::vector& ring); - static bool isInRing(const geom::Coordinate& p, const geom::CoordinateSequence* ring); + static bool isInRing(const geom::CoordinateXY& p, const std::vector& ring); + static bool isInRing(const geom::CoordinateXY& p, const geom::CoordinateSequence* ring); /** \brief * Determines whether a point lies in the interior, on the boundary, or in the @@ -78,8 +95,9 @@ class GEOS_DLL PointLocation { * first point identical to last point) * @return the [Location](@ref geom::Location) of p relative to the ring */ - static geom::Location locateInRing(const geom::Coordinate& p, const std::vector& ring); - static geom::Location locateInRing(const geom::Coordinate& p, const geom::CoordinateSequence& ring); + static geom::Location locateInRing(const geom::CoordinateXY& p, const std::vector& ring); + static geom::Location locateInRing(const geom::CoordinateXY& p, const geom::CoordinateSequence& ring); + static geom::Location locateInRing(const geom::CoordinateXY& p, const geom::Curve& ring); }; diff --git a/Sources/geos/include/geos/algorithm/PointLocator.h b/Sources/geos/include/geos/algorithm/PointLocator.h index 21d9711..6221b5d 100644 --- a/Sources/geos/include/geos/algorithm/PointLocator.h +++ b/Sources/geos/include/geos/algorithm/PointLocator.h @@ -25,7 +25,7 @@ // Forward declarations namespace geos { namespace geom { -class Coordinate; +class CoordinateXY; class Geometry; class LinearRing; class LineString; @@ -66,7 +66,7 @@ class GEOS_DLL PointLocator { * * @return the Location of the point relative to the input Geometry */ - geom::Location locate(const geom::Coordinate& p, const geom::Geometry* geom); + geom::Location locate(const geom::CoordinateXY& p, const geom::Geometry* geom); /** * Convenience method to test a point for intersection with @@ -77,7 +77,7 @@ class GEOS_DLL PointLocator { * @return true if the point is in the interior or boundary of the Geometry */ bool - intersects(const geom::Coordinate& p, const geom::Geometry* geom) + intersects(const geom::CoordinateXY& p, const geom::Geometry* geom) { return locate(p, geom) != geom::Location::EXTERIOR; } @@ -88,17 +88,17 @@ class GEOS_DLL PointLocator { int numBoundaries; // the number of sub-elements whose boundaries the point lies in - void computeLocation(const geom::Coordinate& p, const geom::Geometry* geom); + void computeLocation(const geom::CoordinateXY& p, const geom::Geometry* geom); void updateLocationInfo(geom::Location loc); - geom::Location locate(const geom::Coordinate& p, const geom::Point* pt); + geom::Location locate(const geom::CoordinateXY& p, const geom::Point* pt); - geom::Location locate(const geom::Coordinate& p, const geom::LineString* l); + geom::Location locate(const geom::CoordinateXY& p, const geom::LineString* l); - geom::Location locateInPolygonRing(const geom::Coordinate& p, const geom::LinearRing* ring); + geom::Location locateInPolygonRing(const geom::CoordinateXY& p, const geom::LinearRing* ring); - geom::Location locate(const geom::Coordinate& p, const geom::Polygon* poly); + geom::Location locate(const geom::CoordinateXY& p, const geom::Polygon* poly); }; diff --git a/Sources/geos/include/geos/algorithm/PolygonNodeTopology.h b/Sources/geos/include/geos/algorithm/PolygonNodeTopology.h index 60d5f0e..ca3de04 100644 --- a/Sources/geos/include/geos/algorithm/PolygonNodeTopology.h +++ b/Sources/geos/include/geos/algorithm/PolygonNodeTopology.h @@ -21,11 +21,11 @@ // Forward declarations namespace geos { namespace geom { -class Coordinate; +class CoordinateXY; } } -using geos::geom::Coordinate; +using geos::geom::CoordinateXY; namespace geos { @@ -55,9 +55,9 @@ class GEOS_DLL PolygonNodeTopology { * @return true if the rings cross at the node */ static bool - isCrossing(const Coordinate* nodePt, - const Coordinate* a0, const Coordinate* a1, - const Coordinate* b0, const Coordinate* b1); + isCrossing(const CoordinateXY* nodePt, + const CoordinateXY* a0, const CoordinateXY* a1, + const CoordinateXY* b0, const CoordinateXY* b1); /** @@ -73,8 +73,23 @@ class GEOS_DLL PolygonNodeTopology { * @param b the other vertex of the test segment * @return true if the segment is interior to the ring corner */ - static bool isInteriorSegment(const Coordinate* nodePt, - const Coordinate* a0, const Coordinate* a1, const Coordinate* b); + static bool isInteriorSegment(const CoordinateXY* nodePt, + const CoordinateXY* a0, const CoordinateXY* a1, const CoordinateXY* b); + + /** + * Compares the angles of two vectors + * relative to the positive X-axis at their origin. + * Angles increase CCW from the X-axis. + * + * @param origin the origin of the vectors + * @param p the endpoint of the vector P + * @param q the endpoint of the vector Q + * @return a negative integer, zero, or a positive integer as this vector P has angle less than, equal to, or greater than vector Q + */ + static int compareAngle( + const CoordinateXY* origin, + const CoordinateXY* p, + const CoordinateXY* q); private: @@ -91,9 +106,26 @@ class GEOS_DLL PolygonNodeTopology { * @param e1 the destination point of edge e1 * @return true if p is between e0 and e1 */ - static bool isBetween(const Coordinate* origin, - const Coordinate* p, - const Coordinate* e0, const Coordinate* e1); + static bool isBetween(const CoordinateXY* origin, + const CoordinateXY* p, + const CoordinateXY* e0, const CoordinateXY* e1); + + /** + * Compares whether an edge p is between or outside the edges e0 and e1, + * where the edges all originate at a common origin. + * The "inside" of e0 and e1 is the arc which does not include + * the positive X-axis at the origin. + * If p is collinear with an edge 0 is returned. + * + * @param origin the origin + * @param p the destination point of edge p + * @param e0 the destination point of edge e0 + * @param e1 the destination point of edge e1 + * @return a negative integer, zero or positive integer as the vector P lies outside, collinear with, or inside the vectors E0 and E1 + */ + static int compareBetween(const CoordinateXY* origin, const CoordinateXY* p, + const CoordinateXY* e0, const CoordinateXY* e1); + /** * Tests if the angle with the origin of a vector P is greater than that of the @@ -104,9 +136,9 @@ class GEOS_DLL PolygonNodeTopology { * @param q the endpoint of the vector Q * @return true if vector P has angle greater than Q */ - static bool isAngleGreater(const Coordinate* origin, const Coordinate* p, const Coordinate* q); + static bool isAngleGreater(const CoordinateXY* origin, const CoordinateXY* p, const CoordinateXY* q); - static int quadrant(const Coordinate* origin, const Coordinate* p); + static int quadrant(const CoordinateXY* origin, const CoordinateXY* p); }; diff --git a/Sources/geos/include/geos/algorithm/RayCrossingCounter.h b/Sources/geos/include/geos/algorithm/RayCrossingCounter.h index cc35b36..2120e23 100644 --- a/Sources/geos/include/geos/algorithm/RayCrossingCounter.h +++ b/Sources/geos/include/geos/algorithm/RayCrossingCounter.h @@ -22,13 +22,17 @@ #include #include +#include #include // forward declarations namespace geos { namespace geom { class Coordinate; +class CoordinateXY; class CoordinateSequence; +class CircularArc; +class Curve; } } @@ -63,9 +67,9 @@ namespace algorithm { */ class GEOS_DLL RayCrossingCounter { private: - const geom::Coordinate& point; + const geom::CoordinateXY& point; - int crossingCount; + std::size_t crossingCount; // true if the test point lies on an input segment bool isPointOnSegment; @@ -84,14 +88,17 @@ class GEOS_DLL RayCrossingCounter { * @param ring an array of Coordinates forming a ring * @return the location of the point in the ring */ - static geom::Location locatePointInRing(const geom::Coordinate& p, + static geom::Location locatePointInRing(const geom::CoordinateXY& p, const geom::CoordinateSequence& ring); /// Semantically equal to the above, just different args encoding - static geom::Location locatePointInRing(const geom::Coordinate& p, + static geom::Location locatePointInRing(const geom::CoordinateXY& p, const std::vector& ring); - RayCrossingCounter(const geom::Coordinate& p_point) + static geom::Location locatePointInRing(const geom::CoordinateXY& p, + const geom::Curve& ring); + + RayCrossingCounter(const geom::CoordinateXY& p_point) : point(p_point), crossingCount(0), isPointOnSegment(false) @@ -103,8 +110,17 @@ class GEOS_DLL RayCrossingCounter { * @param p1 an endpoint of the segment * @param p2 another endpoint of the segment */ - void countSegment(const geom::Coordinate& p1, - const geom::Coordinate& p2); + void countSegment(const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2); + + void countArc(const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2, + const geom::CoordinateXY& p3); + + /** \brief + * Counts all segments or arcs in the sequence + */ + void processSequence(const geom::CoordinateSequence& seq, bool isLinear); /** \brief * Reports whether the point lies exactly on one of the supplied segments. @@ -144,6 +160,13 @@ class GEOS_DLL RayCrossingCounter { */ bool isPointInPolygon() const; + std::size_t getCount() const { return crossingCount; }; + + static bool shouldCountCrossing(const geom::CircularArc& arc, const geom::CoordinateXY& q); + + static std::array + pointsIntersectingHorizontalRay(const geom::CircularArc& arc, const geom::CoordinateXY& origin); + }; } // geos::algorithm diff --git a/Sources/geos/include/geos/algorithm/RayCrossingCounterDD.h b/Sources/geos/include/geos/algorithm/RayCrossingCounterDD.h deleted file mode 100644 index 84f4164..0000000 --- a/Sources/geos/include/geos/algorithm/RayCrossingCounterDD.h +++ /dev/null @@ -1,164 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2018 Paul Ramsey - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - * - ********************************************************************** - * - * Last port: algorithm/RayCrossingCounterDD.java rev. 1.2 (JTS-1.9) - * - **********************************************************************/ - -#pragma once - -#include -#include - -#include - -// forward declarations -namespace geos { -namespace geom { -class Coordinate; -class CoordinateSequence; -} -} - - -namespace geos { -namespace algorithm { - -/** \brief - * Counts the number of segments crossed by a horizontal ray extending to - * the right from a given point, in an incremental fashion. - * - * This can be used to determine whether a point lies in a polygonal geometry. - * The class determines the situation where the point lies exactly on a segment. - * When being used for Point-In-Polygon determination, this case allows - * short-circuiting the evaluation. - * - * This class handles polygonal geometries with any number of shells and holes. - * The orientation of the shell and hole rings is unimportant. - * In order to compute a correct location for a given polygonal geometry, - * it is essential that **all** segments are counted which - * - * - touch the ray - * - lie in in any ring which may contain the point - * - * The only exception is when the point-on-segment situation is detected, - * in which case no further processing is required. - * The implication of the above rule is that segments which can be a - * priori determined to *not* touch the ray (i.e. by a test of their - * bounding box or Y-extent) do not need to be counted. This allows - * for optimization by indexing. - */ -class GEOS_DLL RayCrossingCounterDD { -private: - const geom::Coordinate& point; - - int crossingCount; - - // true if the test point lies on an input segment - bool isPointOnSegment; - - // Declare type as noncopyable - RayCrossingCounterDD(const RayCrossingCounterDD& other) = delete; - RayCrossingCounterDD& operator=(const RayCrossingCounterDD& rhs) = delete; - -public: - /** \brief - * Determines the [Location](@ref geom::Location) of a point in a ring. - * This method is an exemplar of how to use this class. - * - * @param p the point to test - * @param ring an array of Coordinates forming a ring - * @return the location of the point in the ring - */ - static geom::Location locatePointInRing(const geom::Coordinate& p, - const geom::CoordinateSequence& ring); - - /// Semantically equal to the above, just different args encoding - static geom::Location locatePointInRing(const geom::Coordinate& p, - const std::vector& ring); - - /** \brief - * Returns the index of the direction of the point `q` - * relative to a vector specified by `p1-p2`. - * - * @param p1 the origin point of the vector - * @param p2 the final point of the vector - * @param q the point to compute the direction to - * - * @return 1 if q is counter-clockwise (left) from p1-p2 - * @return -1 if q is clockwise (right) from p1-p2 - * @return 0 if q is collinear with p1-p2 - */ - static int orientationIndex(const geom::Coordinate& p1, - const geom::Coordinate& p2, - const geom::Coordinate& q); - - RayCrossingCounterDD(const geom::Coordinate& p_point): - point(p_point), - crossingCount(0), - isPointOnSegment(false) - { } - - /** \brief - * Counts a segment. - * - * @param p1 an endpoint of the segment - * @param p2 another endpoint of the segment - */ - void countSegment(const geom::Coordinate& p1, - const geom::Coordinate& p2); - - /** \brief - * Reports whether the point lies exactly on one of the supplied segments. - * - * This method may be called at any time as segments are processed. - * If the result of this method is `true`, no further segments need be - * supplied, since the result will never change again. - * - * @return `true` if the point lies exactly on a segment - */ - bool - isOnSegment() - { - return isPointOnSegment; - } - - /** \brief - * Gets the [Location](@ref geom::Location) of the point relative to the ring, - * polygon or multipolygon from which the processed segments were provided. - * - * This method only determines the correct location - * if **all** relevant segments must have been processed. - * - * @return the Location of the point - */ - geom::Location getLocation(); - - /** \brief - * Tests whether the point lies in or on the ring, polygon or multipolygon - * from which the processed segments were provided. - * - * This method only determines the correct location - * if **all** relevant segments must have been processed. - * - * @return true if the point lies in or on the supplied polygon - */ - bool isPointInPolygon(); - -}; - -} // geos::algorithm -} // geos - diff --git a/Sources/geos/include/geos/algorithm/Rectangle.h b/Sources/geos/include/geos/algorithm/Rectangle.h new file mode 100644 index 0000000..ef073ee --- /dev/null +++ b/Sources/geos/include/geos/algorithm/Rectangle.h @@ -0,0 +1,96 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +// Forward declarations +namespace geos { +namespace geom { +class Coordinate; +class GeometryFactory; +class Polygon; +} +} + +using geos::geom::CoordinateXY; +using geos::geom::GeometryFactory; +using geos::geom::LineSegment; +using geos::geom::Polygon; + +namespace geos { +namespace algorithm { + +class GEOS_DLL Rectangle { + +public: + + /** + * Creates a rectangular {@link Polygon} from a base segment + * defining the position and orientation of one side of the rectangle, and + * three points defining the locations of the line segments + * forming the opposite, left and right sides of the rectangle. + * The base segment and side points must be presented so that the + * rectangle has CW orientation. + * + * The rectangle corners are computed as intersections of + * lines, which generally cannot produce exact values. + * If a rectangle corner is determined to coincide with a side point + * the side point value is used to avoid numerical inaccuracy. + * + * The first side of the constructed rectangle contains the base segment. + * + * @param baseRightPt the right point of the base segment + * @param baseLeftPt the left point of the base segment + * @param oppositePt the point defining the opposite side + * @param leftSidePt the point defining the left side + * @param rightSidePt the point defining the right side + * @param factory the geometry factory to use + * @return the rectangular polygon + */ + static std::unique_ptr + createFromSidePts( + const CoordinateXY& baseRightPt, + const CoordinateXY& baseLeftPt, + const CoordinateXY& oppositePt, + const CoordinateXY& leftSidePt, + const CoordinateXY& rightSidePt, + const GeometryFactory* factory); + +private: + + /** + * Computes the constant C in the standard line equation Ax + By = C + * from A and B and a point on the line. + * + * @param a the X coefficient + * @param b the Y coefficient + * @param p a point on the line + * @return the constant C + */ + static double + computeLineEquationC(double a, double b, const CoordinateXY& p); + + static LineSegment + createLineForStandardEquation(double a, double b, double c); + +}; + + +} // namespace geos::algorithm +} // namespace geos + + diff --git a/Sources/geos/include/geos/algorithm/construct/IndexedDistanceToPoint.h b/Sources/geos/include/geos/algorithm/construct/IndexedDistanceToPoint.h new file mode 100644 index 0000000..68d8452 --- /dev/null +++ b/Sources/geos/include/geos/algorithm/construct/IndexedDistanceToPoint.h @@ -0,0 +1,88 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + ********************************************************************** + * + * Last port: algorithm/construct/IndexedDistanceToPoint.java + * https://github.com/locationtech/jts/commit/d92f783163d9440fcc10c729143787bf7b9fe8f9 + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +using geos::geom::Geometry; +using geos::geom::Point; +using geos::operation::distance::IndexedFacetDistance; + +namespace geos { +namespace algorithm { // geos::algorithm +namespace construct { // geos::algorithm::construct + +/** + * \brief Computes the distance between a point and a geometry + * (which may be a collection containing any type of geometry). + * + * Also computes the pair of points containing the input + * point and the nearest point on the geometry. + * + * \author Martin Davis + */ +class GEOS_DLL IndexedDistanceToPoint { + +public: + /** + * \brief Creates an instance to find the distance from points to a geometry. + * + * \param geom the geometry to compute distances to + */ + IndexedDistanceToPoint(const Geometry& geom); + + /** + * \brief Computes the distance from the base geometry to the given point. + * + * \param pt the point to compute the distance to + * + * \return the computed distance + */ + double distance(const Point& pt); + + /** + * \brief Computes the nearest point on the geometry to the point. + * + * The first location lies on the geometry, + * and the second location is the provided point. + * + * \param pt the point to compute the nearest point for + * + * \return the points that are nearest + */ + std::unique_ptr nearestPoints(const Point& pt); + +private: + void init(); + + bool isInArea(const Point& pt); + + //-- members + const Geometry& targetGeometry; + std::unique_ptr facetDistance; + std::unique_ptr ptLocator; + +}; + +}}} \ No newline at end of file diff --git a/Sources/geos/include/geos/algorithm/construct/IndexedPointInPolygonsLocator.h b/Sources/geos/include/geos/algorithm/construct/IndexedPointInPolygonsLocator.h new file mode 100644 index 0000000..dd82d55 --- /dev/null +++ b/Sources/geos/include/geos/algorithm/construct/IndexedPointInPolygonsLocator.h @@ -0,0 +1,79 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + ********************************************************************** + * + * Last port: algorithm/construct/IndexedDistanceToPoint.java + * https://github.com/locationtech/jts/commit/d92f783163d9440fcc10c729143787bf7b9fe8f9 + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +using geos::geom::Geometry; +using geos::geom::CoordinateXY; +using geos::geom::Location; +using geos::index::strtree::TemplateSTRtree; +using geos::algorithm::locate::IndexedPointInAreaLocator; + +namespace geos { +namespace algorithm { // geos::algorithm +namespace construct { // geos::algorithm::construct + +/** + * \brief Determines the location of a point in the polygonal elements of a geometry. + * + * Uses spatial indexing to provide efficient performance. + * + * \author Martin Davis + */ +class GEOS_DLL IndexedPointInPolygonsLocator { + +public: + /** + * \brief Creates an instance to locate a point in polygonal elements. + * + * \param geom the geometry to locate in + */ + IndexedPointInPolygonsLocator(const Geometry& geom); + + /** \brief + * Determines the [Location](@ref geom::Location) of a point in + * the polygonal elements of a + * [Geometry](@ref geom::Geometry). + * + * @param p the point to test + * @return the location of the point in the geometry + */ + Location locate(const CoordinateXY* /*const*/ p); + +private: + void init(); + + // Declare type as noncopyable + IndexedPointInPolygonsLocator(const IndexedPointInPolygonsLocator& other) = delete; + IndexedPointInPolygonsLocator& operator=(const IndexedPointInPolygonsLocator& rhs) = delete; + + //-- members + const Geometry& geom; + bool isInitialized; + TemplateSTRtree index; + std::vector> locators; +}; + +}}} \ No newline at end of file diff --git a/Sources/geos/include/geos/algorithm/construct/LargestEmptyCircle.h b/Sources/geos/include/geos/algorithm/construct/LargestEmptyCircle.h index 5d701b3..f199314 100644 --- a/Sources/geos/include/geos/algorithm/construct/LargestEmptyCircle.h +++ b/Sources/geos/include/geos/algorithm/construct/LargestEmptyCircle.h @@ -22,14 +22,13 @@ #include #include #include +#include #include #include #include #include - - namespace geos { namespace geom { class Coordinate; @@ -39,13 +38,9 @@ class GeometryFactory; class LineString; class Point; } -namespace operation { -namespace distance { -class IndexedFacetDistance; -} -} } +using geos::operation::distance::IndexedFacetDistance; namespace geos { namespace algorithm { // geos::algorithm @@ -53,15 +48,23 @@ namespace construct { // geos::algorithm::construct /** * Constructs the Largest Empty Circle for a set of obstacle geometries, -* up to a specified tolerance. The obstacles are point and line geometries. +* up to a specified tolerance. +* The obstacles may be any combination of point, linear and polygonal geometries. * -* The Largest Empty Circle is the largest circle which has its center -* in the convex hull of the obstacles (the boundary), and whose -* interior does not intersect with any obstacle. The circle center -* is the point in the interior of the boundary which has the -* farthest distance from the obstacles (up to tolerance). -* The circle is determined by the center point and a point lying -* on an obstacle indicating the circle radius. +* The Largest Empty Circle (LEC) is the largest circle +* whose interior does not intersect with any obstacle +* and whose center lies within a polygonal boundary. +* The circle center is the point in the interior of the boundary +* which has the farthest distance from the obstacles +* (up to the accuracy of the distance tolerance). +* The circle itself is determined by the center point +* and a point lying on an obstacle determining the circle radius. +* +* The polygonal boundary may be supplied explicitly. +* If it is not specified the convex hull of the obstacles is used as the boundary. +* +* To compute an LEC which lies wholly within +* a polygonal boundary, include the boundary of the polygon(s) as an obstacle. * * The implementation uses a successive-approximation technique * over a grid of square cells covering the obstacles and boundary. @@ -77,19 +80,36 @@ class GEOS_DLL LargestEmptyCircle { /** * Creates a new instance of a Largest Empty Circle construction. + * The obstacles may be any collection of points, lines and polygons. + * The constructed circle center lies within the convex hull of the obstacles. * - * @param p_obstacles a geometry representing the obstacles (points and lines) + * @param p_obstacles a geometry representing the obstacles * @param p_tolerance the distance tolerance for computing the circle center point */ LargestEmptyCircle(const geom::Geometry* p_obstacles, double p_tolerance); + + /** + * Creates a new instance of a Largest Empty Circle construction, + * interior-disjoint to a set of obstacle geometries + * and having its center within a polygonal boundary. + * The obstacles may be any collection of points, lines and polygons. + * If the boundary is null or empty the convex hull + * of the obstacles is used as the boundary. + * + * @param p_obstacles a geometry representing the obstacles + * @param p_boundary a polygonal geometry to contain the LEC center + * @param p_tolerance the distance tolerance for computing the circle center point + */ LargestEmptyCircle(const geom::Geometry* p_obstacles, const geom::Geometry* p_boundary, double p_tolerance); + ~LargestEmptyCircle() = default; /** * Computes the center point of the Largest Empty Circle * within a set of obstacles, up to a given tolerance distance. + * The obstacles may be any collection of points, lines and polygons. * - * @param p_obstacles a geometry representing the obstacles (points and lines) + * @param p_obstacles a geometry representing the obstacles * @param p_tolerance the distance tolerance for computing the center point * @return the center point of the Largest Empty Circle */ @@ -98,8 +118,9 @@ class GEOS_DLL LargestEmptyCircle { /** * Computes a radius line of the Largest Empty Circle * within a set of obstacles, up to a given distance tolerance. + * The obstacles may be any collection of points, lines and polygons. * - * @param p_obstacles a geometry representing the obstacles (points and lines) + * @param p_obstacles a geometry representing the obstacles * @param p_tolerance the distance tolerance for computing the center point * @return a line from the center of the circle to a point on the edge */ @@ -115,17 +136,15 @@ class GEOS_DLL LargestEmptyCircle { /* private members */ double tolerance; const geom::Geometry* obstacles; + std::unique_ptr boundary; const geom::GeometryFactory* factory; - std::unique_ptr boundary; // convexhull(obstacles) - operation::distance::IndexedFacetDistance obstacleDistance; + geom::Envelope gridEnv; bool done; - std::unique_ptr ptLocator; - std::unique_ptr boundaryDistance; - geom::Coordinate centerPt; - geom::Coordinate radiusPt; - - /* private methods */ - void setBoundary(const geom::Geometry* obstacles); + std::unique_ptr boundaryPtLocater; + IndexedDistanceToPoint obstacleDistance; + std::unique_ptr boundaryDistance; + geom::CoordinateXY centerPt; + geom::CoordinateXY radiusPt; /** * Computes the signed distance from a point to the constraints @@ -139,6 +158,7 @@ class GEOS_DLL LargestEmptyCircle { */ double distanceToConstraints(const geom::Coordinate& c); double distanceToConstraints(double x, double y); + void initBoundary(); void compute(); /* private class */ @@ -218,4 +238,3 @@ class GEOS_DLL LargestEmptyCircle { } // geos::algorithm::construct } // geos::algorithm } // geos - diff --git a/Sources/geos/include/geos/algorithm/construct/MaximumInscribedCircle.h b/Sources/geos/include/geos/algorithm/construct/MaximumInscribedCircle.h index e44dd76..1da12c0 100644 --- a/Sources/geos/include/geos/algorithm/construct/MaximumInscribedCircle.h +++ b/Sources/geos/include/geos/algorithm/construct/MaximumInscribedCircle.h @@ -107,6 +107,22 @@ class GEOS_DLL MaximumInscribedCircle { */ static std::unique_ptr getRadiusLine(const geom::Geometry* polygonal, double tolerance); + /** + * Computes the maximum number of iterations allowed. + * Uses a heuristic based on the area of the input geometry + * and the tolerance distance. + * The number of tolerance-sized cells that cover the input geometry area + * is computed, times a safety factor. + * This prevents massive numbers of iterations and created cells + * for casees where the input geometry has extremely small area + * (e.g. is very thin). + * + * @param geom the input geometry + * @param toleranceDist the tolerance distance + * @return the maximum number of iterations allowed + */ + static std::size_t computeMaximumIterations(const geom::Geometry* geom, double toleranceDist); + private: /* private members */ @@ -117,8 +133,8 @@ class GEOS_DLL MaximumInscribedCircle { IndexedPointInAreaLocator ptLocator; const geom::GeometryFactory* factory; bool done; - geom::Coordinate centerPt; - geom::Coordinate radiusPt; + geom::CoordinateXY centerPt; + geom::CoordinateXY radiusPt; /* private methods */ double distanceToBoundary(const geom::Coordinate& c); @@ -170,22 +186,32 @@ class GEOS_DLL MaximumInscribedCircle { { return y; } + bool operator< (const Cell& rhs) const { return maxDist < rhs.maxDist; } + bool operator> (const Cell& rhs) const { return maxDist > rhs.maxDist; } + bool operator==(const Cell& rhs) const { return maxDist == rhs.maxDist; } + + /** + * The Cell priority queue is sorted by the natural order of maxDistance. + * std::priority_queue sorts with largest first, + * which is what is needed for this algorithm. + */ + using CellQueue = std::priority_queue; }; - void createInitialGrid(const geom::Envelope* env, std::priority_queue& cellQueue); - Cell createCentroidCell(const geom::Geometry* geom); + void createInitialGrid(const geom::Envelope* env, Cell::CellQueue& cellQueue); + Cell createInteriorPointCell(const geom::Geometry* geom); }; @@ -193,4 +219,3 @@ class GEOS_DLL MaximumInscribedCircle { } // geos::algorithm::construct } // geos::algorithm } // geos - diff --git a/Sources/geos/include/geos/algorithm/distance/DiscreteFrechetDistance.h b/Sources/geos/include/geos/algorithm/distance/DiscreteFrechetDistance.h index 050d8ca..da72b38 100644 --- a/Sources/geos/include/geos/algorithm/distance/DiscreteFrechetDistance.h +++ b/Sources/geos/include/geos/algorithm/distance/DiscreteFrechetDistance.h @@ -137,7 +137,7 @@ class GEOS_DLL DiscreteFrechetDistance { return ptDist.getDistance(); } - const std::array + const std::array getCoordinates() const { return ptDist.getCoordinates(); @@ -146,7 +146,7 @@ class GEOS_DLL DiscreteFrechetDistance { private: geom::Coordinate getSegmentAt(const geom::CoordinateSequence& seq, std::size_t index); - PointPairDistance& getFrecheDistance(std::vector< std::vector >& ca, std::size_t i, std::size_t j, + PointPairDistance& getFrechetDistance(std::vector< std::vector >& ca, std::size_t i, std::size_t j, const geom::CoordinateSequence& p, const geom::CoordinateSequence& q); void compute(const geom::Geometry& discreteGeom, const geom::Geometry& geom); diff --git a/Sources/geos/include/geos/algorithm/distance/DiscreteHausdorffDistance.h b/Sources/geos/include/geos/algorithm/distance/DiscreteHausdorffDistance.h index b8da5ee..be136ed 100644 --- a/Sources/geos/include/geos/algorithm/distance/DiscreteHausdorffDistance.h +++ b/Sources/geos/include/geos/algorithm/distance/DiscreteHausdorffDistance.h @@ -36,19 +36,11 @@ #endif namespace geos { -namespace algorithm { -//class RayCrossingCounter; -} namespace geom { class Geometry; class Coordinate; //class CoordinateSequence; } -namespace index { -namespace intervalrtree { -//class SortedPackedIntervalRTree; -} -} } namespace geos { @@ -138,7 +130,7 @@ class GEOS_DLL DiscreteHausdorffDistance { return ptDist.getDistance(); } - const std::array + const std::array getCoordinates() const { return ptDist.getCoordinates(); @@ -152,7 +144,7 @@ class GEOS_DLL DiscreteHausdorffDistance { {} void - filter_ro(const geom::Coordinate* pt) override + filter_ro(const geom::CoordinateXY* pt) override { minPtDist.initialize(); DistanceToPoint::computeDistance(geom, *pt, diff --git a/Sources/geos/include/geos/algorithm/distance/DistanceToPoint.h b/Sources/geos/include/geos/algorithm/distance/DistanceToPoint.h index d56bb66..108b855 100644 --- a/Sources/geos/include/geos/algorithm/distance/DistanceToPoint.h +++ b/Sources/geos/include/geos/algorithm/distance/DistanceToPoint.h @@ -49,19 +49,19 @@ class DistanceToPoint { DistanceToPoint() {} static void computeDistance(const geom::Geometry& geom, - const geom::Coordinate& pt, + const geom::CoordinateXY& pt, PointPairDistance& ptDist); static void computeDistance(const geom::LineString& geom, - const geom::Coordinate& pt, + const geom::CoordinateXY& pt, PointPairDistance& ptDist); static void computeDistance(const geom::LineSegment& geom, - const geom::Coordinate& pt, + const geom::CoordinateXY& pt, PointPairDistance& ptDist); static void computeDistance(const geom::Polygon& geom, - const geom::Coordinate& pt, + const geom::CoordinateXY& pt, PointPairDistance& ptDist); }; diff --git a/Sources/geos/include/geos/algorithm/distance/PointPairDistance.h b/Sources/geos/include/geos/algorithm/distance/PointPairDistance.h index a82f183..7589abb 100644 --- a/Sources/geos/include/geos/algorithm/distance/PointPairDistance.h +++ b/Sources/geos/include/geos/algorithm/distance/PointPairDistance.h @@ -49,7 +49,7 @@ class PointPairDistance { } void - initialize(const geom::Coordinate& p0, const geom::Coordinate& p1) + initialize(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1) { pt[0] = p0; pt[1] = p1; @@ -63,13 +63,13 @@ class PointPairDistance { return std::sqrt(distanceSquared); } - const std::array& + const std::array& getCoordinates() const { return pt; } - const geom::Coordinate& + const geom::CoordinateXY& getCoordinate(std::size_t i) const { assert(i < pt.size()); @@ -83,7 +83,7 @@ class PointPairDistance { } void - setMaximum(const geom::Coordinate& p0, const geom::Coordinate& p1) + setMaximum(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1) { if(isNull) { initialize(p0, p1); @@ -102,7 +102,7 @@ class PointPairDistance { } void - setMinimum(const geom::Coordinate& p0, const geom::Coordinate& p1) + setMinimum(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1) { if(isNull) { initialize(p0, p1); @@ -129,7 +129,7 @@ class PointPairDistance { * @param dist the distance between p0 and p1 */ void - initialize(const geom::Coordinate& p0, const geom::Coordinate& p1, + initialize(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, double distSquared) { pt[0] = p0; @@ -138,7 +138,7 @@ class PointPairDistance { isNull = false; } - std::array pt; + std::array pt; double distanceSquared; diff --git a/Sources/geos/include/geos/algorithm/hull/ConcaveHull.h b/Sources/geos/include/geos/algorithm/hull/ConcaveHull.h index 770cf61..6201e21 100644 --- a/Sources/geos/include/geos/algorithm/hull/ConcaveHull.h +++ b/Sources/geos/include/geos/algorithm/hull/ConcaveHull.h @@ -57,15 +57,22 @@ typedef std::priority_queue, HullTri::HullTriCom /** * Constructs a concave hull of a set of points. -* The hull is constructed by eroding the Delaunay Triangulation of the points -* until specified target criteria are reached. +* The hull is constructed by removing border triangles +* of the Delaunay Triangulation of the points +* as long as their "size" is larger than the target criterion. * The target criteria are: * -* * Maximum Edge Length Ratio - determine the Maximum Edge Length -* as a fraction of the difference between the longest and shortest edge lengths -* in the Delaunay Triangulation. This normalizes the Maximum Edge Length to be scale-independent. +* * Maximum Edge Length Ratio - determines the Maximum Edge Length +* by a fraction of the difference between +* the longest and shortest edge lengths +* in the Delaunay Triangulation. This normalizes the +* Maximum Edge Length to be scale-independent. * * Maximum Area Ratio - the ratio of the concave hull area to the convex * hull area will be no larger than this value +* * Alpha - produces Alpha-shapes, by removing border triangles +* with a circumradius greater than alpha. +* Large values produce the convex hull; a value of 0 +* produces maximum concaveness. * * The preferred criterium is the Maximum Edge Length Ratio, since it is * scale-free and local (so that no assumption needs to be made about the @@ -80,9 +87,8 @@ typedef std::priority_queue, HullTri::HullTriCom * (unless it is degenerate, in which case it will be a geom::Point or a geom::LineString). * This constraint may cause the concave hull to fail to meet the target criteria. * -* Optionally the concave hull can be allowed to contain holes. -* Note that this may be substantially slower than not permitting holes, -* and it can produce results of lower quality. +* Optionally the concave hull can be allowed +* to contain holes by calling setHolesAllowed(boolean). * * @author mdavis */ @@ -90,13 +96,7 @@ class GEOS_DLL ConcaveHull { public: - ConcaveHull(const Geometry* geom) - : inputGeometry(geom) - , maxEdgeLength(0.0) - , maxEdgeLengthRatio(-1.0) - , isHolesAllowed(false) - , geomFactory(geom->getFactory()) - {}; + ConcaveHull(const Geometry* geom); /** * Computes the approximate edge length of @@ -154,7 +154,23 @@ class GEOS_DLL ConcaveHull { * @return the concave hull */ static std::unique_ptr concaveHullByLengthRatio( - const Geometry* geom, double lengthRatio, bool isHolesAllowed); + const Geometry* geom, + double lengthRatio, + bool isHolesAllowed); + + /** + * Computes the alpha shape of a geometry as a polygon. + * The alpha parameter is the radius of the eroding disc. + * + * @param geom the input geometry + * @param alpha the radius of the eroding disc + * @param isHolesAllowed whether holes are allowed in the result + * @return the alpha shape polygon + */ + static std::unique_ptr alphaShape( + const Geometry* geom, + double alpha, + bool isHolesAllowed); /** * Sets the target maximum edge length for the concave hull. @@ -193,6 +209,15 @@ class GEOS_DLL ConcaveHull { */ void setHolesAllowed(bool holesAllowed); + /** + * Sets the alpha parameter to compute an alpha shape of the input. + * Alpha is the radius of the eroding disc. + * Border triangles with circumradius greater than alpha are removed. + * + * @param newAlpha the alpha radius + */ + void setAlpha(double newAlpha); + /** * Gets the computed concave hull. * @@ -203,13 +228,20 @@ class GEOS_DLL ConcaveHull { private: + // Constants + static constexpr int PARAM_EDGE_LENGTH = 1; + static constexpr int PARAM_ALPHA = 2; + // Members const Geometry* inputGeometry; - double maxEdgeLength; double maxEdgeLengthRatio; + double alpha; bool isHolesAllowed; + int criteriaType; + double maxSizeInHull; const GeometryFactory* geomFactory; + // Methods static double computeTargetEdgeLength( TriList& triList, double edgeLengthFactor); @@ -221,19 +253,44 @@ class GEOS_DLL ConcaveHull { /** * Adds a Tri to the queue. * Only add tris with a single border edge. - * The ordering size is the length of the border edge. + * since otherwise that would risk isolating a vertex if + * the tri ends up being eroded from the hull. + * Sets the tri size according to the threshold parameter being used. * * @param tri the Tri to add * @param queue the priority queue */ void addBorderTri(HullTri* tri, HullTriQueue& queue); - bool isBelowLengthThreshold(const HullTri* tri) const; void computeHullHoles(TriList& triList); + void setSize(HullTri* tri); + + /** + * Finds tris which may be the start of holes. + * Only tris which have a long enough edge and which do not touch the current hull + * boundary are included. + * This avoids the risk of disconnecting the result polygon. + * The list is sorted in decreasing order of edge length. + * The list is sorted in decreasing order of size. + * + * @param triList + * @param maxSizeInHull maximum tri size which is not in a hole + * @return + */ static std::vector findCandidateHoles( - TriList& triList, double minEdgeLen); + TriList& triList, double maxSizeInHull); void removeHole(TriList& triList, HullTri* triHole); + void setSize(TriList& triList); + + /** + * Tests if a tri is included in the hull. + * Tris with size less than the maximum are included in the hull. + * + * @param tri the tri to test + * @return true if the tri is included in the hull + */ + bool isInHull(const HullTri* tri) const; bool isRemovableBorder(const HullTri* tri) const; bool isRemovableHole(const HullTri* tri) const; diff --git a/Sources/geos/include/geos/algorithm/hull/HullTri.h b/Sources/geos/include/geos/algorithm/hull/HullTri.h index 135e373..4fbc587 100644 --- a/Sources/geos/include/geos/algorithm/hull/HullTri.h +++ b/Sources/geos/include/geos/algorithm/hull/HullTri.h @@ -83,6 +83,10 @@ class HullTri : public Tri */ void setSizeToBoundary(); + void setSizeToLongestEdge(); + void setSizeToCircumradius(); + + bool isMarked() const; void setMarked(bool marked); bool isRemoved(); diff --git a/Sources/geos/include/geos/algorithm/hull/HullTriangulation.h b/Sources/geos/include/geos/algorithm/hull/HullTriangulation.h index d9b8f6e..05808c8 100644 --- a/Sources/geos/include/geos/algorithm/hull/HullTriangulation.h +++ b/Sources/geos/include/geos/algorithm/hull/HullTriangulation.h @@ -68,7 +68,7 @@ class HullTriangulation * @param triList the triangulation * @return the points in the boundary of the triangulation */ - static std::vector traceBoundary( + static geom::CoordinateSequence traceBoundary( TriList& triList); static HullTri* findBorderTri( @@ -124,7 +124,7 @@ class HullTriangulation : triList(p_triList) {}; - void visit(std::array& triEdges); + void visit(std::array& triEdges) override; }; // HullTriVisitor diff --git a/Sources/geos/include/geos/algorithm/locate/IndexedPointInAreaLocator.h b/Sources/geos/include/geos/algorithm/locate/IndexedPointInAreaLocator.h index 7a647e4..ac327fe 100644 --- a/Sources/geos/include/geos/algorithm/locate/IndexedPointInAreaLocator.h +++ b/Sources/geos/include/geos/algorithm/locate/IndexedPointInAreaLocator.h @@ -51,25 +51,35 @@ namespace locate { // geos::algorithm::locate * The index is lazy-loaded, which allows creating instances even if they are not used. * */ -class IndexedPointInAreaLocator : public PointOnGeometryLocator { +class GEOS_DLL IndexedPointInAreaLocator : public PointOnGeometryLocator { private: struct SegmentView { - SegmentView(const geom::Coordinate* p_p0, const geom::Coordinate* p_p1) : m_p0(p_p0) { - // All GEOS CoordinateSequences store their coordinates sequentially. - // Should that ever change, this assert will fail. - (void) p_p1; - assert(p_p0 + 1 == p_p1); + SegmentView(const geom::CoordinateXY* p0, const geom::CoordinateXY* p1) { + // There is a significant performance benefit in fitting our + // line segment into 8 bytes (about 15-20%). Because we know that + // p1 follows p0 in a CoordinateSequence, we know that the address + // of p1 is 16, 24, or 32 bytes greater than the address of p0. + // By packing this offset into the least significant bits of p0, + // we can retrieve both p0 and p1 while only using 8 bytes. + std::size_t os = static_cast(reinterpret_cast(p1) - reinterpret_cast(p0)) - 2u; + m_p0 = reinterpret_cast(p0) | os; + + assert(&this->p0() == p0); + assert(&this->p1() == p1); } - const geom::Coordinate& p0() const { - return *m_p0; + const geom::CoordinateXY& p0() const { + auto ret = reinterpret_cast(m_p0 >> 2 << 2); + return *ret; } - const geom::Coordinate& p1() const { - return *(m_p0 + 1); + const geom::CoordinateXY& p1() const { + auto offset = (m_p0 & 0x03) + 2; + auto ret = reinterpret_cast(reinterpret_cast(m_p0 >> 2 << 2) + offset); + return *ret; } - const geom::Coordinate* m_p0; + std::size_t m_p0; }; class IntervalIndexedGeometry { @@ -119,7 +129,7 @@ class IndexedPointInAreaLocator : public PointOnGeometryLocator { * @param p the point to test * @return the location of the point in the geometry */ - geom::Location locate(const geom::Coordinate* /*const*/ p) override; + geom::Location locate(const geom::CoordinateXY* /*const*/ p) override; }; diff --git a/Sources/geos/include/geos/algorithm/locate/PointOnGeometryLocator.h b/Sources/geos/include/geos/algorithm/locate/PointOnGeometryLocator.h index cb24c40..47d8410 100644 --- a/Sources/geos/include/geos/algorithm/locate/PointOnGeometryLocator.h +++ b/Sources/geos/include/geos/algorithm/locate/PointOnGeometryLocator.h @@ -19,7 +19,7 @@ namespace geos { namespace geom { -class Coordinate; +class CoordinateXY; } } @@ -47,7 +47,7 @@ class GEOS_DLL PointOnGeometryLocator { * @param p the point to test * @return the location of the point in the geometry */ - virtual geom::Location locate(const geom::Coordinate* /*const*/ p) = 0; + virtual geom::Location locate(const geom::CoordinateXY* /*const*/ p) = 0; }; } // geos::algorithm::locate diff --git a/Sources/geos/include/geos/algorithm/locate/SimplePointInAreaLocator.h b/Sources/geos/include/geos/algorithm/locate/SimplePointInAreaLocator.h index d3f2660..be77ff9 100644 --- a/Sources/geos/include/geos/algorithm/locate/SimplePointInAreaLocator.h +++ b/Sources/geos/include/geos/algorithm/locate/SimplePointInAreaLocator.h @@ -22,7 +22,7 @@ namespace geos { namespace geom { class Geometry; class Coordinate; -class Polygon; +class Surface; } } @@ -47,11 +47,11 @@ class GEOS_DLL SimplePointInAreaLocator : public PointOnGeometryLocator { public: - static geom::Location locate(const geom::Coordinate& p, - const geom::Geometry* geom); + static geom::Location locate(const geom::CoordinateXY& p, + const geom::Geometry* geom); /** \brief - * Determines the Location of a point in a [Polygon](@ref geom::Polygon). + * Determines the Location of a point in a [Surface](@ref geom::Surface). * * The return value is one of: * @@ -69,8 +69,8 @@ class GEOS_DLL SimplePointInAreaLocator : public PointOnGeometryLocator { * @param poly the geometry to test * @return the Location of the point in the polygon */ - static geom::Location locatePointInPolygon(const geom::Coordinate& p, - const geom::Polygon* poly); + static geom::Location locatePointInSurface(const geom::CoordinateXY& p, + const geom::Surface& poly); /** \brief * Determines whether a point is contained in a [Geometry](@ref geom::Geometry), @@ -84,23 +84,27 @@ class GEOS_DLL SimplePointInAreaLocator : public PointOnGeometryLocator { * @param geom the geometry to test * @return true if the point lies in or on the geometry */ - static bool isContained(const geom::Coordinate& p, + static bool isContained(const geom::CoordinateXY& p, const geom::Geometry* geom); SimplePointInAreaLocator(const geom::Geometry* p_g) : g(p_g) { } + SimplePointInAreaLocator(const geom::Geometry& p_g) + : g(&p_g) + { } + geom::Location - locate(const geom::Coordinate* p) override + locate(const geom::CoordinateXY* p) override { return locate(*p, g); } private: - static geom::Location locateInGeometry(const geom::Coordinate& p, - const geom::Geometry* geom); + static geom::Location locateInGeometry(const geom::CoordinateXY& p, + const geom::Geometry* geom); const geom::Geometry* g; diff --git a/Sources/geos/include/geos/constants.h b/Sources/geos/include/geos/constants.h index 2ad3be2..7be6921 100644 --- a/Sources/geos/include/geos/constants.h +++ b/Sources/geos/include/geos/constants.h @@ -29,6 +29,7 @@ typedef __int64 int64; #include #include #include +#include // for std::size_t namespace geos { @@ -41,5 +42,8 @@ constexpr double DoubleInfinity = (std::numeric_limits::infinity)(); constexpr double DoubleNegInfinity = (-(std::numeric_limits::infinity)()); constexpr double DoubleEpsilon = std::numeric_limits::epsilon(); +constexpr std::size_t NO_COORD_INDEX = std::numeric_limits::max(); +constexpr std::size_t INDEX_UNKNOWN = std::numeric_limits::max(); + } // namespace geos diff --git a/Sources/geos/include/geos/coverage/Corner.h b/Sources/geos/include/geos/coverage/Corner.h new file mode 100644 index 0000000..5cada24 --- /dev/null +++ b/Sources/geos/include/geos/coverage/Corner.h @@ -0,0 +1,137 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2021 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace geos { +namespace simplify { +class LinkedLine; +} +namespace geom { +class Coordinate; +class LineString; +} +} + + +using geos::geom::Coordinate; +using geos::geom::Envelope; +using geos::geom::LineString; +using geos::simplify::LinkedLine; + + +namespace geos { +namespace coverage { // geos::coverage + + +class Corner +{ + +public: + + Corner(const LinkedLine* edge, std::size_t i); + + bool isVertex(std::size_t index) const; + + inline std::size_t getIndex() const { + return m_index; + } + + inline double getArea() const { + return m_area; + }; + + const Coordinate& prev() const; + const Coordinate& next() const; + + Envelope envelope() const; + + bool isVertex(const Coordinate& v) const; + bool isBaseline(const Coordinate& p0, const Coordinate& p1) const; + bool intersects(const Coordinate& v) const; + bool isRemoved() const; + + const Coordinate& getCoordinate() { + return m_edge->getCoordinate(m_index); + } + + std::unique_ptr toLineString() const; + + inline int compareTo(const Corner& rhs) const { + double area_lhs = getArea(); + double area_rhs = rhs.getArea(); + + if (area_lhs == area_rhs) { + std::size_t index_lhs = getIndex(); + std::size_t index_rhs = rhs.getIndex(); + if (index_lhs == index_rhs) return 0; + else return index_lhs < index_rhs ? -1 : 1; + } + else + return area_lhs < area_rhs ? -1 : 1; + } + + bool operator< (const Corner& rhs) const { + return compareTo(rhs) < 0; + }; + + bool operator> (const Corner& rhs) const { + return compareTo(rhs) > 0; + }; + + bool operator==(const Corner& rhs) const { + return compareTo(rhs) == 0; + }; + + struct Greater { + inline bool operator()(const Corner & a, const Corner & b) const { + return a.compareTo(b) > 0; + } + }; + + // Order using greater for compatibility with the Java PriorityQueue + // implementation, which returns the smallest item off the top of the + // queue + using PriorityQueue = std::priority_queue, Corner::Greater>; + + +private: + + // members + const LinkedLine* m_edge; + std::size_t m_index; + std::size_t m_prev; + std::size_t m_next; + double m_area; + + // methods + static double area(const LinkedLine& edge, std::size_t index); + +}; // Corner + + + +GEOS_DLL std::ostream& operator<< (std::ostream& os, const Corner& c); + + +} // geos::coverage +} // geos diff --git a/Sources/geos/include/geos/coverage/CoverageBoundarySegmentFinder.h b/Sources/geos/include/geos/coverage/CoverageBoundarySegmentFinder.h new file mode 100644 index 0000000..cc23093 --- /dev/null +++ b/Sources/geos/include/geos/coverage/CoverageBoundarySegmentFinder.h @@ -0,0 +1,83 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2021 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace geos { +namespace geom { +class CoordinateSequence; +class Geometry; +} +} + +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; +using geos::geom::CoordinateSequenceFilter; +using geos::geom::Geometry; +using geos::geom::LineSegment; + +namespace geos { +namespace coverage { // geos::coverage + +class CoverageBoundarySegmentFinder : public CoordinateSequenceFilter +{ + + +public: + + CoverageBoundarySegmentFinder(LineSegment::UnorderedSet& segs) + : m_boundarySegs(segs) + {}; + + bool isGeometryChanged() const override { + return false; + } + + bool isDone() const override { + return false; + } + + void filter_ro(const CoordinateSequence& seq, std::size_t i) override; + + + static LineSegment::UnorderedSet + findBoundarySegments(const std::vector& geoms); + + static bool isBoundarySegment( + const LineSegment::UnorderedSet& boundarySegs, + const CoordinateSequence* seq, + std::size_t i); + +private: + + static LineSegment + createSegment(const CoordinateSequence& seq, std::size_t i); + + + LineSegment::UnorderedSet& m_boundarySegs; + + +}; // CoverageBoundarySegmentFinder + + + +} // geos::coverage +} // geos diff --git a/Sources/geos/include/geos/coverage/CoverageEdge.h b/Sources/geos/include/geos/coverage/CoverageEdge.h new file mode 100644 index 0000000..6404b65 --- /dev/null +++ b/Sources/geos/include/geos/coverage/CoverageEdge.h @@ -0,0 +1,171 @@ + + + +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + +// Forward declarations +namespace geos { +namespace geom { +class Coordinate; +class LinearRing; +class LineString; +class MultiLineString; +class GeometryFactory; +} +} + +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; +using geos::geom::GeometryFactory; +using geos::geom::LinearRing; +using geos::geom::LineString; +using geos::geom::LineSegment; +using geos::geom::MultiLineString; + +namespace geos { // geos. +namespace coverage { // geos.coverage + +/** + * An edge of a polygonal coverage formed from all or a section of a polygon ring. + * An edge may be a free ring, which is a ring which has not node points + * (i.e. does not touch any other rings in the parent coverage). + * + * @author mdavis + * + */ +class GEOS_DLL CoverageEdge { + +private: + + // Members + std::unique_ptr m_pts; + std::size_t m_ringCount ; + bool m_isFreeRing = true; + + // Methods + + static std::unique_ptr + extractEdgePoints(const CoordinateSequence& ring, + std::size_t start, std::size_t end); + + static const Coordinate& + findDistinctPoint( + const CoordinateSequence& pts, + std::size_t index, + bool isForward, + const Coordinate& pt); + + +public: + + CoverageEdge(std::unique_ptr && pts, bool isFreeRing) + : m_pts(pts ? std::move(pts) : detail::make_unique()) + , m_ringCount(0) + , m_isFreeRing(isFreeRing) + {} + + /** + * Computes a key segment for a ring. + * The key is the segment starting at the lowest vertex, + * towards the lowest adjacent distinct vertex. + * + * @param ring a linear ring + * @return a LineSegment representing the key + */ + static LineSegment key( + const CoordinateSequence& ring); + + /** + * Computes a distinct key for a section of a linear ring. + * + * @param ring the linear ring + * @param start index of the start of the section + * @param end end index of the end of the section + * @return a LineSegment representing the key + */ + static LineSegment key( + const CoordinateSequence& ring, + std::size_t start, + std::size_t end); + + static std::unique_ptr createEdge( + const CoordinateSequence& ring); + + static std::unique_ptr createEdge( + const CoordinateSequence& ring, + std::size_t start, + std::size_t end); + + static std::unique_ptr createLines( + const std::vector& edges, + const GeometryFactory* geomFactory); + + std::unique_ptr toLineString( + const GeometryFactory* geomFactory); + + /* public */ + void incRingCount() + { + m_ringCount++; + } + + /* public */ + std::size_t getRingCount() const + { + return m_ringCount; + } + + /** + * Returns whether this edge is a free ring; + * i.e. one with no constrained nodes. + * + * @return true if this is a free ring + */ + bool isFreeRing() const + { + return m_isFreeRing; + } + + void setCoordinates(const CoordinateSequence* pts) + { + m_pts = pts->clone(); + } + + const CoordinateSequence* getCoordinates() const + { + return m_pts.get(); + } + + const Coordinate& getEndCoordinate() const + { + return m_pts->getAt(m_pts->size() - 1); + } + + const Coordinate& getStartCoordinate() const + { + return m_pts->getAt(0); + } + + +}; + +} // namespace geos.coverage +} // namespace geos diff --git a/Sources/geos/include/geos/coverage/CoverageGapFinder.h b/Sources/geos/include/geos/coverage/CoverageGapFinder.h new file mode 100644 index 0000000..3a9780f --- /dev/null +++ b/Sources/geos/include/geos/coverage/CoverageGapFinder.h @@ -0,0 +1,106 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + + +// Forward declarations +namespace geos { +namespace geom { +class Geometry; +class LinearRing; +} +} + +using geos::geom::Geometry; +using geos::geom::LinearRing; + + +namespace geos { // geos +namespace coverage { // geos::coverage + +/** + * Finds gaps in a polygonal coverage. + * Gaps are holes in the coverage which are narrower than a given width. + * + * The coverage should be valid according to CoverageValidator. + * If this is not the case, some gaps may not be reported, or the invocation may fail. + * + * This is a more accurate way of identifying gaps + * than using CoverageValidator::setGapWidth(double). + * Gaps which separate the coverage into two disjoint regions are not detected. + * Gores are not identified as gaps. + * + * @author mdavis + * + */ +class GEOS_DLL CoverageGapFinder { + +private: + + std::vector& m_coverage; + + bool isGap(const LinearRing* hole, double gapWidth); + + +public: + + /** + * Creates a new polygonal coverage gap finder. + * + * @param coverage a set of polygons forming a polygonal coverage + */ + CoverageGapFinder(std::vector& coverage) + : m_coverage(coverage) + {}; + + /** + * Finds gaps in a polygonal coverage. + * Returns lines indicating the locations of the gaps. + * + * @param coverage a set of polygons forming a polygonal coverage + * @param gapWidth the maximum width of gap to detect + * @return a geometry indicating the locations of gaps (which is empty if no gaps were found), or null if the coverage was empty + */ + static std::unique_ptr findGaps( + std::vector& coverage, + double gapWidth); + + /** + * Finds gaps in the coverage. + * Returns lines indicating the locations of the gaps. + * + * @param gapWidth the maximum width of gap to detect + * @return a geometry indicating the locations of gaps (which is empty if no gaps were found), or null if the coverage was empty + */ + std::unique_ptr findGaps(double gapWidth); + + +}; + +} // namespace geos::coverage +} // namespace geos + + + + + + + + + diff --git a/Sources/geos/include/geos/coverage/CoveragePolygon.h b/Sources/geos/include/geos/coverage/CoveragePolygon.h new file mode 100644 index 0000000..e56668c --- /dev/null +++ b/Sources/geos/include/geos/coverage/CoveragePolygon.h @@ -0,0 +1,57 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +// Forward declarations +namespace geos { +namespace geom { +class Coordinate; +class Envelope; +class Polygon; +} +} + +using geos::geom::CoordinateXY; +using geos::geom::Envelope; +using geos::geom::Polygon; +using geos::algorithm::locate::IndexedPointInAreaLocator; + +namespace geos { // geos +namespace coverage { // geos::coverage + +class GEOS_DLL CoveragePolygon { + + // Members + const Polygon* m_polygon; + Envelope polyEnv; + mutable std::unique_ptr m_locator; + +public: + CoveragePolygon(const Polygon* poly); + + bool intersectsEnv(const Envelope& env) const; + bool intersectsEnv(const CoordinateXY& p) const; + bool contains(const CoordinateXY& p) const; + +private: + IndexedPointInAreaLocator& getLocator() const; + +}; + +} // namespace geos::coverage +} // namespace geos + diff --git a/Sources/geos/include/geos/coverage/CoveragePolygonValidator.h b/Sources/geos/include/geos/coverage/CoveragePolygonValidator.h new file mode 100644 index 0000000..8f635de --- /dev/null +++ b/Sources/geos/include/geos/coverage/CoveragePolygonValidator.h @@ -0,0 +1,381 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +// Forward declarations +namespace geos { +namespace geom { +class Coordinate; +class Envelope; +class Geometry; +class GeometryFactory; +} +} + +using geos::geom::Coordinate; +using geos::geom::Envelope; +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::LineSegment; +using geos::algorithm::locate::IndexedPointInAreaLocator; + +namespace geos { // geos +namespace coverage { // geos::coverage + + +/** + * Validates that a polygon forms a valid polygonal coverage + * with the set of polygons adjacent to it. + * If the polygon is coverage-valid an empty {@link geos::geom::LineString} is returned. + * Otherwise, the result is a linear geometry containing + * the polygon boundary linework causing the invalidity. + * + * A polygon is coverage-valid if: + * + * * The polygon interior does not intersect the interior of other polygons. + * * If the polygon boundary intersects another polygon boundary, the vertices + * and line segments of the intersection match exactly. + * + * The algorithm detects the following coverage errors: + * + * * the polygon is a duplicate of another one + * * a polygon boundary segment equals an adjacent segment (with same orientation). + * This determines that the polygons overlap + * * a polygon boundary segment is collinear and overlaps an adjacent segment + * but is not equal to it + * * a polygon boundary segment touches an adjacent segment at a non-vertex point + * * a polygon boundary segment crosses into an adjacent polygon + * * a polygon boundary segment is in the interior of an adjacent polygon + * + * If any of these errors is present, the target polygon + * does not form a valid coverage with the adjacent polygons. + * + * The validity rules do not preclude gaps between coverage polygons. + * However, this class can detect narrow gaps, + * by specifying a maximum gap width using {@link #setGapWidth(double)}. + * Note that this will also identify narrow gaps separating disjoint coverage regions, + * and narrow gores. + * In some situations it may also produce false positives + * (i.e. linework identified as part of a gap which is wider than the given width). + * To fully identify gaps it maybe necessary to use {@link CoverageUnion} and analyze + * the holes in the result to see if they are acceptable. + * + * A polygon may be coverage-valid with respect to + * a set of surrounding polygons, but the collection as a whole may not + * form a clean coverage. For example, the target polygon boundary may be fully matched + * by adjacent boundary segments, but the adjacent set contains polygons + * which are not coverage-valid relative to other ones in the set. + * A coverage is valid only if every polygon in the coverage is coverage-valid. + * Use {@link CoverageValidator} to validate an entire set of polygons. + * + * The adjacent set may contain polygons which do not intersect the target polygon. + * These are effectively ignored during validation (but may decrease performance). + * + * @author Martin Davis + * + */ +class GEOS_DLL CoveragePolygonValidator { + +private: + + /** + * Models a segment in a CoverageRing. + * The segment is normalized so it can be compared with segments + * in any orientation. + * Records valid matching segments in a coverage, + * which must have opposite orientations. + * Also detects equal segments with identical + * orientation, and marks them as coverage-invalid. + */ + class CoverageRingSegment : public LineSegment + { + public: + + // Members + CoverageRing* ringForward; + std::size_t indexForward; + CoverageRing* ringOpp; + std::size_t indexOpp; + + CoverageRingSegment( + const Coordinate& p_p0, const Coordinate& p_p1, + CoverageRing* p_ring, std::size_t p_index) + : LineSegment(p_p0, p_p1) + , ringForward(nullptr) + , indexForward(0) + , ringOpp(nullptr) + , indexOpp(0) + { + if (p_p1.compareTo(p_p0) < 0) { + reverse(); + ringOpp = p_ring; + indexOpp = p_index; + } + else { + ringForward = p_ring; + indexForward = p_index; + } + }; + + void match(const CoverageRingSegment* seg) { + bool isInvalid = checkInvalid(seg); + if (isInvalid) { + return; + } + //-- record the match + if (ringForward == nullptr) { + ringForward = seg->ringForward; + indexForward = seg->indexForward; + } + else { + ringOpp = seg->ringOpp; + indexOpp = seg->indexOpp; + } + //-- mark ring segments as matched + ringForward->markMatched(indexForward); + ringOpp->markMatched(indexOpp); + } + + bool checkInvalid(const CoverageRingSegment* seg) const { + if (ringForward != nullptr && seg->ringForward != nullptr) { + ringForward->markInvalid(indexForward); + seg->ringForward->markInvalid(seg->indexForward); + return true; + } + if (ringOpp != nullptr && seg->ringOpp != nullptr) { + ringOpp->markInvalid(indexOpp); + seg->ringOpp->markInvalid(seg->indexOpp); + return true; + } + return false; + } + + struct CoverageRingSegHash { + std::size_t + operator() (CoverageRingSegment const* s) const { + std::size_t h = std::hash{}(s->p0.x); + h ^= (std::hash{}(s->p0.y) << 1); + h ^= (std::hash{}(s->p1.x) << 1); + return h ^ (std::hash{}(s->p1.y) << 1); + } + }; + + struct CoverageRingSegEq { + bool + operator() (CoverageRingSegment const* lhs, CoverageRingSegment const* rhs) const { + return lhs->p0.x == rhs->p0.x + && lhs->p0.y == rhs->p0.y + && lhs->p1.x == rhs->p1.x + && lhs->p1.y == rhs->p1.y; + } + }; + + }; + + // Members + const Geometry* targetGeom; + std::vector adjGeoms; + //std::vector m_adjPolygons; + const GeometryFactory* geomFactory; + double gapWidth = 0.0; + std::vector> m_adjCovPolygons; + std::deque coverageRingStore; + std::vector> localCoordinateSequences; + std::deque coverageRingSegmentStore; + + typedef std::unordered_map CoverageRingSegmentMap; + + // Declare type as noncopyable + CoveragePolygonValidator(const CoveragePolygonValidator& other) = delete; + CoveragePolygonValidator& operator=(const CoveragePolygonValidator& rhs) = delete; + +public: + + /** + * Validates that a polygon is coverage-valid against the + * surrounding polygons in a polygonal coverage. + * + * @param targetPolygon the polygon to validate + * @param adjPolygons the adjacent polygons + * @return a linear geometry containing the segments causing invalidity (if any) + */ + static std::unique_ptr validate( + const Geometry* targetPolygon, + std::vector& adjPolygons); + + /** + * Validates that a polygon is coverage-valid against the + * surrounding polygons in a polygonal coverage, + * and forms no gaps narrower than a specified width. + *

+ * The set of surrounding polygons should include all polygons which + * are within the gap width distance of the target polygon. + * + * @param targetPolygon the polygon to validate + * @param adjPolygons a collection of the adjacent polygons + * @param gapWidth the maximum width of invalid gaps + * @return a linear geometry containing the segments causing invalidity (if any) + */ + static std::unique_ptr validate( + const Geometry* targetPolygon, + std::vector& adjPolygons, + double gapWidth); + + /** + * Create a new validator. + * + * If the gap width is specified, the set of surrounding polygons + * should include all polygons which + * are within the gap width distance of the target polygon. + * + * @param targetPolygon the geometry to validate + * @param adjPolygons the adjacent polygons in the polygonal coverage + */ + CoveragePolygonValidator( + const Geometry* targetPolygon, + std::vector& adjPolygons); + + /** + * Sets the maximum gap width, if narrow gaps are to be detected. + * + * @param p_gapWidth the maximum width of gaps to detect + */ + void setGapWidth(double p_gapWidth); + + /** + * Validates the coverage polygon against the set of adjacent polygons + * in the coverage. + * + * @return a linear geometry containing the segments causing invalidity (if any) + */ + std::unique_ptr validate(); + +private: + + static std::vector> + toCoveragePolygons(const std::vector polygons); + static std::vector extractPolygons(std::vector& geoms); + + /* private */ + std::unique_ptr createEmptyResult(); + + /** + * Marks matched segments. + * This improves the efficiency of validity testing, since in valid coverages + * all segments (except exterior ones) are matched, + * and hence do not need to be tested further. + * Segments which are equal and have same orientation + * are detected and marked invalid. + * In fact, the entire target polygon may be matched and valid, + * which allows avoiding further tests. + * Segments matched between adjacent polygons are also marked valid, + * since this prevents them from being detected as misaligned, + * if this is being done. + * + * @param targetRings the target rings + * @param adjRings the adjacent rings + * @param targetEnv the tolerance envelope of the target + */ + void markMatchedSegments( + std::vector& targetRings, + std::vector& adjRings, + const Envelope& targetEnv); + + /** + * Adds ring segments to the segment map, + * and detects if they match an existing segment. + * Matched segments are marked. + * + * @param rings + * @param envLimit + * @param segmentMap + */ + void markMatchedSegments( + std::vector& rings, + const Envelope& envLimit, + CoverageRingSegmentMap& segmentMap); + + CoverageRingSegment* createCoverageRingSegment( + CoverageRing* ring, std::size_t index); + + /** + * Marks invalid target segments which cross an adjacent ring segment, + * lie partially in the interior of an adjacent ring, + * or are nearly collinear with an adjacent ring segment up to the distance tolerance + * + * @param targetRings the rings with segments to test + * @param adjRings the adjacent rings + * @param distanceTolerance the gap distance tolerance, if any + */ + void markInvalidInteractingSegments( + std::vector& targetRings, + std::vector& adjRings, + double distanceTolerance); + + /** + * Marks invalid target segments which are fully interior + * to an adjacent polygon. + * + * @param targetRings the rings with segments to test + * @param adjCovPolygons the adjacent polygons + */ + void markInvalidInteriorSegments( + std::vector& targetRings, + std::vector>& adjCovPolygons); + + void markInvalidInteriorSection( + CoverageRing& ring, + std::size_t iStart, + std::size_t iEnd, + std::vector>& adjCovPolygons ); + + void markInvalidInteriorSegment( + CoverageRing& ring, std::size_t i, CoveragePolygon* adjPoly); + + void checkTargetRings( + std::vector& targetRings, + std::vector& adjRngs, + const Envelope& targetEnv); + + std::unique_ptr createInvalidLines(std::vector& rings); + + std::vector createRings(const Geometry* geom); + + std::vector createRings(std::vector& polygons); + + void createRings(const Polygon* poly, std::vector& rings); + + void addRing( + const LinearRing* ring, + bool isShell, + std::vector& rings); + + CoverageRing* createRing(const LinearRing* ring, bool isShell); + + + +}; + +} // namespace geos::coverage +} // namespace geos diff --git a/Sources/geos/include/geos/coverage/CoverageRing.h b/Sources/geos/include/geos/coverage/CoverageRing.h new file mode 100644 index 0000000..76bd44a --- /dev/null +++ b/Sources/geos/include/geos/coverage/CoverageRing.h @@ -0,0 +1,205 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +#include + +// Forward declarations +namespace geos { +namespace geom { +class Coordinate; +class CoordinateSequence; +class Geometry; +class GeometryFactory; +class LineString; +class LinearRing; +class Polygon; +} +} + +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::Polygon; +using geos::geom::LineString; +using geos::geom::LinearRing; + +namespace geos { // geos. +namespace coverage { // geos.coverage + +class GEOS_DLL CoverageRing : public noding::BasicSegmentString { + +private: + + // Members + bool m_isInteriorOnRight; + std::vector m_isInvalid; + std::vector m_isMatched; + + std::size_t findInvalidStart(std::size_t index); + + std::size_t findInvalidEnd(std::size_t index); + + std::size_t nextMarkIndex(std::size_t index); + + /** + * Creates a line from a sequence of ring segments between startIndex and endIndex (inclusive). + * If the endIndex < startIndex the sequence wraps around the ring endpoint. + * + * @param startIndex + * @param endIndex + * @param geomFactory + * @return a line representing the section + */ + std::unique_ptr createLine( + std::size_t startIndex, + std::size_t endIndex, + const GeometryFactory* geomFactory); + + std::unique_ptr extractSection( + std::size_t startIndex, std::size_t endIndex); + + std::unique_ptr extractSectionWrap( + std::size_t startIndex, std::size_t endIndex); + +public: + + CoverageRing(CoordinateSequence* pts, bool interiorOnRight); + + CoverageRing(const LinearRing* ring, bool isShell); + + geom::Envelope getEnvelope(std::size_t start, std::size_t end); + + /** + * Tests if all rings have known status (matched or invalid) + * for all segments. + * + * @param rings a list of rings + * @return true if all ring segments have known status + */ + static bool isKnown(std::vector& rings); + + /** + * Reports if the ring has canonical orientation, + * with the polygon interior on the right (shell is CW). + * + * @return true if the polygon interior is on the right + */ + bool isInteriorOnRight() const; + + /** + * Marks a segment as invalid. + * + * @param i the segment index + */ + void markInvalid(std::size_t index); + + /** + * Marks a segment as matched. + * + * @param i the segment index + */ + void markMatched(std::size_t index); + + /** + * Tests if all segments in the ring have known status + * (matched or invalid). + * + * @return true if all segments have known status + */ + bool isKnown() const; + + /** + * Tests if a segment is marked invalid. + * + * @param index the segment index + * @return true if the segment is invalid + */ + bool isInvalid(std::size_t i) const; + + /** + * Tests whether all segments are invalid. + * + * @return true if all segments are invalid + */ + bool isInvalid() const; + + /** + * Tests whether any segment is invalid. + * + * @return true if some segment is invalid + */ + bool hasInvalid() const; + + /** + * Tests whether the validity state of a ring segment is known. + * + * @param i the index of the ring segment + * @return true if the segment validity state is known + */ + bool isKnown(std::size_t i) const; + + /** + * Finds the previous vertex in the ring which is distinct + * from a given coordinate value. + * + * @param index the index to start the search + * @param pt a coordinate value (which may not be a ring vertex) + * @return the previous distinct vertex in the ring + */ + const Coordinate& findVertexPrev(std::size_t index, const Coordinate& pt) const; + + /** + * Finds the next vertex in the ring which is distinct + * from a given coordinate value. + * + * @param index the index to start the search + * @param pt a coordinate value (which may not be a ring vertex) + * @return the next distinct vertex in the ring + */ + const Coordinate& findVertexNext(std::size_t index, const Coordinate& pt) const; + + /** + * Gets the index of the previous segment in the ring. + * + * @param index a segment index + * @return the index of the previous segment + */ + std::size_t prev(std::size_t index) const; + + /** + * Gets the index of the next segment in the ring. + * + * @param index a segment index + * @return the index of the next segment + */ + std::size_t next(std::size_t index) const; + + void createInvalidLines( + const GeometryFactory* geomFactory, + std::vector>& lines); + +}; + +} // namespace geos.coverage +} // namespace geos + + + + + diff --git a/Sources/geos/include/geos/coverage/CoverageRingEdges.h b/Sources/geos/include/geos/coverage/CoverageRingEdges.h new file mode 100644 index 0000000..ec28098 --- /dev/null +++ b/Sources/geos/include/geos/coverage/CoverageRingEdges.h @@ -0,0 +1,170 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 Paul Ramsey + * Copyright (c) 2023 Martin Davis. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include // to materialize CoverageEdge + +#include +#include + +// Forward declarations +namespace geos { +namespace geom { +class CoordinateSequence; +class Geometry; +class LinearRing; +class MultiPolygon; +class Polygon; +} +namespace coverage { +class CoverageEdge; +} +} + +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; +using geos::geom::Geometry; +using geos::geom::LinearRing; +using geos::geom::LineSegment; +using geos::geom::MultiPolygon; +using geos::geom::Polygon; + +namespace geos { // geos +namespace coverage { // geos.coverage + +/** + * Models a polygonal coverage as a set of unique {@link CoverageEdge}s, + * linked to the parent rings in the coverage polygons. + * Each edge has either one or two parent rings, depending on whether + * it is an inner or outer edge of the coverage. + * The source coverage is represented as a array of polygonal geometries + * (either {@link geos::geom::Polygon}s or {@link geos::geom::MultiPolygon}s). + * + * @author Martin Davis + */ +class GEOS_DLL CoverageRingEdges { + +private: + + // Members + const std::vector& m_coverage; + std::map> m_ringEdgesMap; + std::vector m_edges; + std::vector> m_edgeStore; + + /* Turn off copy constructors for MSVC */ + CoverageRingEdges(const CoverageRingEdges&) = delete; + CoverageRingEdges& operator=(const CoverageRingEdges&) = delete; + +public: + + CoverageRingEdges(const std::vector& coverage) + : m_coverage(coverage) + { + build(); + }; + + + std::vector& getEdges() + { + return m_edges; + }; + + /** + * Selects the edges with a given ring count (which can be 1 or 2). + * + * @param ringCount the edge ring count to select (1 or 2) + * @return the selected edges + */ + std::vector selectEdges( + std::size_t ringCount) const; + + /** + * Recreates the polygon coverage from the current edge values. + * + * @return an array of polygonal geometries representing the coverage + */ + std::vector> buildCoverage() const; + + +private: + + void build(); + + void addRingEdges( + const LinearRing* ring, + Coordinate::UnorderedSet& nodes, + LineSegment::UnorderedSet& boundarySegs, + std::map& uniqueEdgeMap); + + void addBoundaryInnerNodes( + const LinearRing* ring, + LineSegment::UnorderedSet& boundarySegs, + Coordinate::UnorderedSet& nodes); + + std::vector extractRingEdges( + const LinearRing* ring, + std::map& uniqueEdgeMap, + Coordinate::UnorderedSet& nodes); + + CoverageEdge* createEdge( + const CoordinateSequence& ring, + std::map& uniqueEdgeMap); + + CoverageEdge* createEdge( + const CoordinateSequence& ring, + std::size_t start, std::size_t end, + std::map& uniqueEdgeMap); + + std::size_t findNextNodeIndex( + const CoordinateSequence& ring, + std::size_t start, + Coordinate::UnorderedSet& nodes) const; + + static std::size_t next( + std::size_t index, + const CoordinateSequence& ring); + + Coordinate::UnorderedSet findMultiRingNodes( + const std::vector& coverage); + + Coordinate::UnorderedSet findBoundaryNodes( + LineSegment::UnorderedSet& lineSegments); + + std::unique_ptr buildPolygonal( + const Geometry* geom) const; + + std::unique_ptr buildMultiPolygon( + const MultiPolygon* geom) const; + + std::unique_ptr buildPolygon( + const Polygon* polygon) const; + + std::unique_ptr buildRing( + const LinearRing* ring) const; + + bool isEdgeDirForward( + const std::vector& ringEdges, + std::size_t index, + const Coordinate& prevPt) const; + + +}; + +} // namespace geos.coverage +} // namespace geos diff --git a/Sources/geos/include/geos/coverage/CoverageSimplifier.h b/Sources/geos/include/geos/coverage/CoverageSimplifier.h new file mode 100644 index 0000000..5e23028 --- /dev/null +++ b/Sources/geos/include/geos/coverage/CoverageSimplifier.h @@ -0,0 +1,166 @@ + +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 Paul Ramsey + * Copyright (c) 2023 Martin Davis. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + + +namespace geos { +namespace geom { +class Geometry; +class GeometryFactory; +class MultiLineString; +} +namespace coverage { +class CoverageEdge; +} +} + + +using geos::coverage::CoverageEdge; +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::MultiLineString; + + +namespace geos { +namespace coverage { // geos::coverage + +/** + * Simplifies the boundaries of the polygons in a polygonal coverage + * while preserving the original coverage topology. + * An area-based simplification algorithm + * (similar to Visvalingam-Whyatt simplification) + * is used to provide high-quality results. + * Also supports simplifying just the inner edges in a coverage, + * which allows simplifying "patches" without affecting their boundary. + * + * The amount of simplification is determined by a tolerance value, + * which is a non-negative quantity. It equates roughly to the maximum + * distance by which a simplified line can change from the original. + * (In fact, it is the square root of the area tolerance used + * in the Visvalingam-Whyatt algorithm.) + * + * The simplified result coverage has the following characteristics: + * + * * It has the same number and types of polygonal geometries as the input + * * Node points (inner vertices shared by three or more polygons, + * or boundary vertices shared by two or more) are not changed + * * If the input is a valid coverage, then so is the result + * + * This class also supports inner simplification, which simplifies + * only edges of the coverage which are adjacent to two polygons. + * This allows partial simplification of a coverage, since a simplified + * subset of a coverage still matches the remainder of the coverage. + * + * The input coverage should be valid according to {@link CoverageValidator}. + * + * @author Martin Davis + */ +class GEOS_DLL CoverageSimplifier { + + +public: + + /** + * Create a new coverage simplifier instance. + * + * @param coverage a set of polygonal geometries forming a coverage + */ + CoverageSimplifier(const std::vector& coverage); + + /** + * Simplifies the boundaries of a set of polygonal geometries forming a coverage, + * preserving the coverage topology. + * + * @param coverage a set of polygonal geometries forming a coverage + * @param tolerance the simplification tolerance + * @return the simplified polygons + */ + static std::vector> simplify( + std::vector& coverage, + double tolerance); + + static std::vector> simplify( + const std::vector>& coverage, + double tolerance); + + /** + * Simplifies the inner boundaries of a set of polygonal geometries forming a coverage, + * preserving the coverage topology. + * Edges which form the exterior boundary of the coverage are left unchanged. + * + * @param coverage a set of polygonal geometries forming a coverage + * @param tolerance the simplification tolerance + * @return the simplified polygons + */ + static std::vector> simplifyInner( + std::vector& coverage, + double tolerance); + + static std::vector> simplifyInner( + const std::vector>& coverage, + double tolerance); + + /** + * Computes the simplified coverage, preserving the coverage topology. + * + * @param tolerance the simplification tolerance + * @return the simplified polygons + */ + std::vector> simplify( + double tolerance); + + /** + * Computes the inner-boundary simplified coverage, + * preserving the coverage topology, + * and leaving outer boundary edges unchanged. + * + * @param tolerance the simplification tolerance + * @return the simplified polygons + */ + std::vector> simplifyInner( + double tolerance); + + +private: + + // Members + const std::vector& m_input; + const GeometryFactory* m_geomFactory; + + // Methods + void simplifyEdges( + std::vector edges, + const MultiLineString* constraints, + double tolerance); + + void setCoordinates( + std::vector& edges, + const MultiLineString* lines); + + std::vector getFreeRings( + const std::vector& edges) const; + + +}; // CoverageSimplifier + + +} // geos::coverage +} // geos diff --git a/Sources/geos/include/geos/coverage/CoverageUnion.h b/Sources/geos/include/geos/coverage/CoverageUnion.h new file mode 100644 index 0000000..8983c19 --- /dev/null +++ b/Sources/geos/include/geos/coverage/CoverageUnion.h @@ -0,0 +1,77 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + +// Forward declarations +namespace geos { +namespace geom { +class Geometry; +} +} + +using geos::geom::Geometry; + +namespace geos { // geos +namespace coverage { // geos::coverage + +/** + * Unions a polygonal coverage in an efficient way. + * + * Valid polygonal coverage topology allows merging polygons in a very efficient way. + * + * @author Martin Davis + * + */ +class GEOS_DLL CoverageUnion { + +private: + + +public: + + /** + * Unions a polygonal coverage. + * + * @param coverage a vector of polygons in the coverage + * @return the union of the coverage polygons + */ + static std::unique_ptr Union(std::vector& coverage); + + /** + * Unions a polygonal coverage. + * + * @param coverage a collection of the polygons in the coverage + * @return the union of the coverage polygons + */ + static std::unique_ptr Union(const Geometry* coverage); + +}; + +} // namespace geos::coverage +} // namespace geos + + + + + + + + + + diff --git a/Sources/geos/include/geos/coverage/CoverageValidator.h b/Sources/geos/include/geos/coverage/CoverageValidator.h new file mode 100644 index 0000000..86a53e6 --- /dev/null +++ b/Sources/geos/include/geos/coverage/CoverageValidator.h @@ -0,0 +1,159 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + + +// Forward declarations +namespace geos { +namespace geom { +class Geometry; +} +} + +using geos::geom::Geometry; +using geos::index::strtree::TemplateSTRtree; + +namespace geos { // geos +namespace coverage { // geos::coverage + + +/** + * Validates a polygonal coverage, and returns the locations of + * invalid polygon boundary segments if found. + * + * A polygonal coverage is a set of polygons which may be edge-adjacent but do + * not overlap. + * Coverage algorithms (such as {@link CoverageUnion} or simplification) + * generally require the input coverage to be valid to produce correct results. + * A polygonal coverage is valid if: + * + * * The interiors of all polygons do not intersect (are disjoint). + * This is the case if no polygon has a boundary which intersects + * the interior of another polygon, and no two polygons are identical. + * * If the boundaries of polygons intersect, the vertices + * and line segments of the intersection match exactly. + * + * A valid coverage may contain holes (regions of no coverage). + * Sometimes it is desired to detect whether coverages contain + * narrow gaps between polygons + * (which can be a result of digitizing error or misaligned data). + * This class can detect narrow gaps, + * by specifying a maximum gap width using {@link #setGapWidth(double)}. + * Note that this also identifies narrow gaps separating disjoint coverage regions, + * and narrow gores. + * In some situations it may also produce false positives + * (linework identified as part of a gap which is actually wider). + * See CoverageGapFinder for an alternate way to detect gaps which may be more accurate. + * + * @author Martin Davis + * + */ +class GEOS_DLL CoverageValidator { + +private: + + std::vector& m_coverage; + double m_gapWidth = 0.0; + + std::unique_ptr validate( + const Geometry* targetGeom, + TemplateSTRtree& index); + + +public: + + /* + * Creates a new coverage validator + * + * @param coverage a array of polygons representing a polygonal coverage + */ + CoverageValidator(std::vector& coverage) + : m_coverage(coverage) + {}; + + /** + * Sets the maximum gap width, if narrow gaps are to be detected. + * + * @param gapWidth the maximum width of gaps to detect + */ + void setGapWidth(double gapWidth) { + m_gapWidth = gapWidth; + }; + + /** + * Validates the polygonal coverage. + * The result is an array of the same size as the input coverage. + * Each array entry is either null, or if the polygon does not form a valid coverage, + * a linear geometry containing the boundary segments + * which intersect polygon interiors, which are mismatched, + * or form gaps (if checked). + * + * @return an array of nulls or linear geometries + */ + std::vector> validate(); + + /** + * Tests whether a polygonal coverage is valid. + * + * @param coverage an array of polygons forming a coverage + * @return true if the coverage is valid + */ + static bool isValid( + std::vector& coverage); + + /** + * Tests if some element of an array of geometries is a coverage invalidity + * indicator. + * + * @param validateResult an array produced by a polygonal coverage validation + * @return true if the result has at least one invalid indicator + */ + static bool hasInvalidResult( + const std::vector>& validateResult); + + /** + * Validates that a set of polygons forms a valid polygonal coverage. + * The result is a list of the same length as the input, + * containing for each input geometry either + * a linear geometry indicating the locations of invalidities, + * or a null if the geometry is coverage-valid. + * + * @param coverage an array of polygons forming a coverage + * @return an array of linear geometries indicating coverage errors, or nulls + */ + static std::vector> validate( + std::vector& coverage); + + /** + * Validates that a set of polygons forms a valid polygonal coverage + * and contains no gaps narrower than a specified width. + * The result is a list of the same length as the input, + * containing for each input geometry either + * a linear geometry indicating the locations of invalidities, + * or a null if the geometry is coverage-valid. + * + * @param coverage an array of polygons forming a coverage + * @param gapWidth the maximum width of invalid gaps + * @return an array of linear geometries indicating coverage errors, or nulls + */ + static std::vector> validate( + std::vector& coverage, + double gapWidth); +}; + +} // namespace geos::coverage +} // namespace geos diff --git a/Sources/geos/include/geos/coverage/InvalidSegmentDetector.h b/Sources/geos/include/geos/coverage/InvalidSegmentDetector.h new file mode 100644 index 0000000..66f9783 --- /dev/null +++ b/Sources/geos/include/geos/coverage/InvalidSegmentDetector.h @@ -0,0 +1,133 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + + +#include + +// Forward declarations +namespace geos { +namespace geom { +class Coordinate; +} +namespace noding { +class SegmentString; +} +namespace coverage { +class CoverageRing; +} +} + + +using geos::noding::SegmentIntersector; +using geos::noding::SegmentString; +using geos::geom::Coordinate; + + +namespace geos { // geos. +namespace coverage { // geos.coverage + +/** + * Detects invalid coverage topology where ring segments interact. + * The inputs to processIntersections(SegmentString, int, SegmentString, int)} + * must be CoverageRing s. + * If an invalid situation is detected the input target segment is + * marked invalid using CoverageRing#markInvalid(int). + * + * This class assumes it is used with SegmentSetMutualIntersector, + * so that segments in the same ring are not evaluated. + * + * @author Martin Davis + * + */ +class GEOS_DLL InvalidSegmentDetector : public SegmentIntersector { + +private: + + // Members + double distanceTol; + + // Methods + bool isInvalid(const Coordinate& tgt0, const Coordinate& tgt1, + const Coordinate& adj0, const Coordinate& adj1, + CoverageRing* adj, std::size_t indexAdj); + + bool isEqual( + const Coordinate& t0, const Coordinate& t1, + const Coordinate& adj0, const Coordinate& adj1); + + /** + * Checks if the segments are collinear, or if the target segment + * intersects the interior of the adjacent ring. + * Segments which are collinear must be non-equal and hence invalid, + * since matching segments have already been marked as valid and + * are not passed to this code. + * + * @param tgt0 + * @param tgt1 + * @param adj0 + * @param adj1 + * @return + */ + bool isCollinearOrInterior( + const Coordinate& tgt0, const Coordinate& tgt1, + const Coordinate& adj0, const Coordinate& adj1, + CoverageRing* adj, std::size_t indexAdj); + + bool isInteriorSegment( + const Coordinate& intVertex, + const Coordinate& tgt0, const Coordinate& tgt1, + CoverageRing* adj, std::size_t indexAdj); + + static bool isNearlyParallel( + const Coordinate& p00, const Coordinate& p01, + const Coordinate& p10, const Coordinate& p11, + double distanceTol); + + +public: + + /** + * Creates an invalid segment detector. + */ + InvalidSegmentDetector() {}; + + InvalidSegmentDetector(double p_distanceTol) + : distanceTol(p_distanceTol) {}; + + + bool isDone() const override { + // process all intersections + return false; + }; + + /** + * Process interacting segments. + * The input order is important. + * The adjacent segment is first, the target is second. + * The inputs must be CoverageRing. + */ + void processIntersections( + SegmentString* ssAdj, std::size_t iAdj, + SegmentString* ssTarget, std::size_t iTarget) override; + + + +}; + +} // namespace geos.coverage +} // namespace geos + + diff --git a/Sources/geos/include/geos/coverage/TPVWSimplifier.h b/Sources/geos/include/geos/coverage/TPVWSimplifier.h new file mode 100644 index 0000000..657616e --- /dev/null +++ b/Sources/geos/include/geos/coverage/TPVWSimplifier.h @@ -0,0 +1,225 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2021 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +#include +#include +#include +#include +#include + + +namespace geos { +namespace geom { +class Coordinate; +class CoordinateSequence; +class Envelope; +class Geometry; +class GeometryFactory; +class LineString; +class MultiLineString; +} +} + + +using geos::coverage::Corner; +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; +using geos::geom::Envelope; +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::LineString; +using geos::geom::MultiLineString; +using geos::index::VertexSequencePackedRtree; +using geos::index::strtree::TemplateSTRtree; +using geos::simplify::LinkedLine; + + +namespace geos { +namespace coverage { // geos::coverage + + +/** + * Computes a Topology-Preserving Visvalingam-Whyatt simplification + * of a set of input lines. + * The simplified lines will contain no more intersections than are present + * in the original input. + * Line and ring endpoints are preserved, except for rings + * which are flagged as "free". + * + * The amount of simplification is determined by a tolerance value, + * which is a non-zero quantity. + * It is the square root of the area tolerance used + * in the Visvalingam-Whyatt algorithm. + * This equates roughly to the maximum + * distance by which a simplified line can change from the original. + * + * @author mdavis + * + */ +class GEOS_DLL TPVWSimplifier +{ + +public: + + // Prototype + class EdgeIndex; + + /* private static */ + class Edge { + + public: + + // Members + double areaTolerance; + bool isFreeRing; + const Envelope* envelope; + std::size_t nbPts; + LinkedLine linkedLine; + VertexSequencePackedRtree vertexIndex; + std::size_t minEdgeSize; + + /** + * Creates a new edge. + * The endpoints of the edge are preserved during simplification, + * unless it is a ring and the isFreeRing flag is set. + * + * @param p_inputLine the line or ring + * @param p_isFreeRing whether a ring endpoint can be removed + * @param p_areaTolerance the simplification tolerance + */ + Edge(const LineString* p_inputLine, bool p_isFreeRing, double p_areaTolerance); + + const Coordinate& getCoordinate(std::size_t index) const; + + const Envelope* getEnvelopeInternal() const; + + std::size_t size() const; + + std::unique_ptr simplify(EdgeIndex& edgeIndex); + + void createQueue(Corner::PriorityQueue& pq); + + void addCorner(std::size_t i, Corner::PriorityQueue& cornerQueue); + + bool isRemovable(Corner& corner, EdgeIndex& edgeIndex) const; + + /** + * Tests if any vertices in a line intersect the corner triangle. + * Uses the vertex spatial index for efficiency. + * + * @param corner the corner vertices + * @param cornerEnv the envelope of the corner + * @param edge the hull to test + * @return true if there is an intersecting vertex + */ + bool hasIntersectingVertex(const Corner& corner, + const Envelope& cornerEnv, + const Edge& edge) const; + + std::vector query(const Envelope& cornerEnv) const; + + /** + * Removes a corner by removing the apex vertex from the ring. + * Two new corners are created with apexes + * at the other vertices of the corner + * (if they are non-convex and thus removable). + * + * @param corner the corner to remove + * @param cornerQueue the corner queue + */ + void removeCorner( + Corner& corner, + Corner::PriorityQueue& cornerQueue); + + }; // Edge + + class EdgeIndex + { + public: + + TemplateSTRtree index; + + void add(std::vector& edges); + + std::vector query(const Envelope& queryEnv); + + }; // EdgeIndex + + + /** + * Simplifies a set of lines, preserving the topology of the lines. + * + * @param lines the lines to simplify + * @param distanceTolerance the simplification tolerance + * @return the simplified lines + */ + static std::unique_ptr simplify( + const MultiLineString* lines, + double distanceTolerance); + + /** + * Simplifies a set of lines, preserving the topology of the lines between + * themselves and a set of linear constraints. + * The endpoints of lines are preserved. + * The endpoint of rings are preserved as well, unless + * the ring is indicated as "free" via a bit flag with the same index. + * + * @param lines the lines to simplify + * @param freeRings flags indicating which ring edges do not have node endpoints + * @param constraintLines the linear constraints + * @param distanceTolerance the simplification tolerance + * @return the simplified lines + */ + static std::unique_ptr simplify( + const MultiLineString* lines, + std::vector& freeRings, + const MultiLineString* constraintLines, + double distanceTolerance); + + // Constructor + TPVWSimplifier(const MultiLineString* lines, + double distanceTolerance); + + +private: + + // Members + const MultiLineString* inputLines; + std::vector isFreeRing; + double areaTolerance; + const GeometryFactory* geomFactory; + const MultiLineString* constraintLines; + + + // Methods + void setFreeRingIndices(std::vector& freeRing); + + void setConstraints(const MultiLineString* constraints); + + std::unique_ptr simplify(); + + std::vector createEdges( + const MultiLineString* lines, + std::vector& freeRing); + + +}; // TPVWSimplifier + + +} // geos::coverage +} // geos diff --git a/Sources/geos/include/geos/coverage/VertexRingCounter.h b/Sources/geos/include/geos/coverage/VertexRingCounter.h new file mode 100644 index 0000000..a8899ae --- /dev/null +++ b/Sources/geos/include/geos/coverage/VertexRingCounter.h @@ -0,0 +1,71 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2021 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +#include +#include +#include + +namespace geos { +namespace geom { +class CoordinateSequence; +class Geometry; +} +} + +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; +using geos::geom::CoordinateSequenceFilter; +using geos::geom::Geometry; + + +namespace geos { +namespace coverage { // geos::coverage + +class VertexRingCounter : public CoordinateSequenceFilter +{ + +public: + + VertexRingCounter(std::map& counts) + : vertexCounts(counts) + {}; + + bool isGeometryChanged() const override { + return false; + } + + bool isDone() const override { + return false; + } + + void filter_ro(const CoordinateSequence& seq, std::size_t i) override; + + static void count( + const std::vector& geoms, + std::map& counts); + +private: + + std::map& vertexCounts; + +}; // VertexRingCounter + + + +} // geos::coverage +} // geos diff --git a/Sources/geos/include/geos/edgegraph/EdgeGraph.h b/Sources/geos/include/geos/edgegraph/EdgeGraph.h index 9edc20f..23422a6 100644 --- a/Sources/geos/include/geos/edgegraph/EdgeGraph.h +++ b/Sources/geos/include/geos/edgegraph/EdgeGraph.h @@ -25,13 +25,6 @@ #include #include -// Forward declarations -namespace geos { -namespace geom { -class Coordinate; -} -} - #undef EDGEGRAPH_HEAPHACK namespace geos { @@ -60,9 +53,9 @@ class GEOS_DLL EdgeGraph { private: std::deque edges; - std::map vertexMap; + std::map vertexMap; - HalfEdge* create(const geom::Coordinate& p0, const geom::Coordinate& p1); + HalfEdge* create(const geom::CoordinateXYZM& p0, const geom::CoordinateXYZM& p1); protected: @@ -74,7 +67,7 @@ class GEOS_DLL EdgeGraph { * @param orig the origin location * @return a new HalfEdge with the given origin */ - HalfEdge* createEdge(const geom::Coordinate& orig); + HalfEdge* createEdge(const geom::CoordinateXYZM& orig); /** * Inserts an edge not already present into the graph. @@ -84,7 +77,7 @@ class GEOS_DLL EdgeGraph { * @param eAdj an existing edge with same orig (if any) * @return the created edge */ - HalfEdge* insert(const geom::Coordinate& orig, const geom::Coordinate& dest, HalfEdge* eAdj); + HalfEdge* insert(const geom::CoordinateXYZM& orig, const geom::CoordinateXYZM& dest, HalfEdge* eAdj); public: @@ -106,7 +99,7 @@ class GEOS_DLL EdgeGraph { * * @see isValidEdge(Coordinate, Coordinate) */ - HalfEdge* addEdge(const geom::Coordinate& orig, const geom::Coordinate& dest); + HalfEdge* addEdge(const geom::CoordinateXYZM& orig, const geom::CoordinateXYZM& dest); /** * Tests if the given coordinates form a valid edge (with non-zero length). @@ -115,7 +108,7 @@ class GEOS_DLL EdgeGraph { * @param dest the end coordinate * @return true if the edge formed is valid */ - static bool isValidEdge(const geom::Coordinate& orig, const geom::Coordinate& dest); + static bool isValidEdge(const geom::CoordinateXY& orig, const geom::CoordinateXY& dest); void getVertexEdges(std::vector& edgesOut); @@ -127,7 +120,7 @@ class GEOS_DLL EdgeGraph { * @param dest the destination location. * @return an edge with the given orig and dest, or null if none exists */ - HalfEdge* findEdge(const geom::Coordinate& orig, const geom::Coordinate& dest); + HalfEdge* findEdge(const geom::CoordinateXY& orig, const geom::CoordinateXY& dest); diff --git a/Sources/geos/include/geos/edgegraph/HalfEdge.h b/Sources/geos/include/geos/edgegraph/HalfEdge.h index 1e18271..d66d984 100644 --- a/Sources/geos/include/geos/edgegraph/HalfEdge.h +++ b/Sources/geos/include/geos/edgegraph/HalfEdge.h @@ -20,13 +20,6 @@ #include #include -// Forward declarations -namespace geos { -namespace geom { -class Coordinate; -} -} - namespace geos { namespace edgegraph { // geos.edgegraph @@ -65,7 +58,7 @@ class GEOS_DLL HalfEdge { private: /* members */ - geom::Coordinate m_orig; + geom::CoordinateXYZM m_orig; HalfEdge* m_sym; HalfEdge* m_next; @@ -116,7 +109,7 @@ class GEOS_DLL HalfEdge { * * @return the direction point for the edge */ - virtual const geom::Coordinate& directionPt() const { return dest(); }; + virtual const geom::CoordinateXYZM& directionPt() const { return dest(); }; public: @@ -126,7 +119,7 @@ class GEOS_DLL HalfEdge { * * @param p_orig the origin coordinate */ - HalfEdge(const geom::Coordinate& p_orig) : + HalfEdge(const geom::CoordinateXYZM& p_orig) : m_orig(p_orig) {}; @@ -139,7 +132,7 @@ class GEOS_DLL HalfEdge { * @param p1 a vertex coordinate * @return the HalfEdge with origin at p0 */ - static HalfEdge* create(const geom::Coordinate& p0, const geom::Coordinate& p1); + static HalfEdge* create(const geom::CoordinateXYZM& p0, const geom::CoordinateXYZM& p1); /** * Links this edge with its sym (opposite) edge. @@ -154,14 +147,14 @@ class GEOS_DLL HalfEdge { * * @return the origin coordinate */ - const geom::Coordinate& orig() const { return m_orig; }; + const geom::CoordinateXYZM& orig() const { return m_orig; }; /** * Gets the destination coordinate of this edge. * * @return the destination coordinate */ - const geom::Coordinate& dest() const { return m_sym->m_orig; } + const geom::CoordinateXYZM& dest() const { return m_sym->m_orig; } /** * The X component of the direction vector. @@ -233,7 +226,7 @@ class GEOS_DLL HalfEdge { * @return the edge with the required dest vertex, if it exists, * or null */ - HalfEdge* find(const geom::Coordinate& dest); + HalfEdge* find(const geom::CoordinateXY& dest); /** * Tests whether this edge has the given orig and dest vertices. @@ -242,7 +235,7 @@ class GEOS_DLL HalfEdge { * @param p1 the destination vertex to test * @return true if the vertices are equal to the ones of this edge */ - bool equals(const geom::Coordinate& p0, const geom::Coordinate& p1) const; + bool equals(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1) const; /** * Inserts an edge diff --git a/Sources/geos/include/geos/edgegraph/MarkHalfEdge.h b/Sources/geos/include/geos/edgegraph/MarkHalfEdge.h index 76bb3b8..5a73d6d 100644 --- a/Sources/geos/include/geos/edgegraph/MarkHalfEdge.h +++ b/Sources/geos/include/geos/edgegraph/MarkHalfEdge.h @@ -21,13 +21,6 @@ #include #include -// Forward declarations -namespace geos { -namespace geom { -class Coordinate; -} -} - namespace geos { namespace edgegraph { // geos.edgegraph @@ -44,7 +37,7 @@ class GEOS_DLL MarkHalfEdge : public HalfEdge { * * @param orig the coordinate of the edge origin */ - MarkHalfEdge(const geom::Coordinate& p_orig) : + MarkHalfEdge(const geom::CoordinateXYZM& p_orig) : HalfEdge(p_orig), m_isMarked(false) {}; diff --git a/Sources/geos/include/geos/geom.h b/Sources/geos/include/geos/geom.h index 0d56cb2..5eadd2f 100644 --- a/Sources/geos/include/geos/geom.h +++ b/Sources/geos/include/geos/geom.h @@ -115,11 +115,8 @@ namespace geom { // geos::geom } // namespace geos #include -#include -#include #include #include -#include #include #include #include diff --git a/Sources/geos/include/geos/geom/CircularArc.h b/Sources/geos/include/geos/geom/CircularArc.h new file mode 100644 index 0000000..283eaf3 --- /dev/null +++ b/Sources/geos/include/geos/geom/CircularArc.h @@ -0,0 +1,273 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace geos { +namespace geom { + +/// A CircularArc is a reference to three points that define a circular arc. +/// It provides for the lazy calculation of various arc properties such as the center, radius, and orientation +class GEOS_DLL CircularArc { +public: + + using CoordinateXY = geom::CoordinateXY; + + CircularArc(const CoordinateXY& q0, const CoordinateXY& q1, const CoordinateXY& q2) + : p0(q0) + , p1(q1) + , p2(q2) + , m_center_known(false) + , m_radius_known(false) + , m_orientation_known(false) + {} + + const CoordinateXY& p0; + const CoordinateXY& p1; + const CoordinateXY& p2; + + /// Return the orientation of the arc as one of: + /// - algorithm::Orientation::CLOCKWISE, + /// - algorithm::Orientation::COUNTERCLOCKWISE + /// - algorithm::Orientation::COLLINEAR + int orientation() const { + if (!m_orientation_known) { + m_orientation = algorithm::Orientation::index(p0, p1, p2); + m_orientation_known = true; + } + return m_orientation; + } + + /// Return the center point of the circle associated with this arc + const CoordinateXY& getCenter() const { + if (!m_center_known) { + m_center = algorithm::CircularArcs::getCenter(p0, p1, p2); + m_center_known = true; + } + + return m_center; + } + + /// Return the radius of the circle associated with this arc + double getRadius() const { + if (!m_radius_known) { + m_radius = getCenter().distance(p0); + m_radius_known = true; + } + + return m_radius; + } + + /// Return whether this arc forms a complete circle + bool isCircle() const { + return p0.equals(p2); + } + + /// Returns whether this arc forms a straight line (p0, p1, and p2 are collinear) + bool isLinear() const { + return std::isnan(getRadius()); + } + + /// Return the inner angle of the sector associated with this arc + double getAngle() const { + if (isCircle()) { + return 2*MATH_PI; + } + + /// Even Rouault: + /// potential optimization?: using crossproduct(p0 - center, p2 - center) = radius * radius * sin(angle) + /// could yield the result by just doing a single asin(), instead of 2 atan2() + /// actually one should also likely compute dotproduct(p0 - center, p2 - center) = radius * radius * cos(angle), + /// and thus angle = atan2(crossproduct(p0 - center, p2 - center) , dotproduct(p0 - center, p2 - center) ) + auto t0 = theta0(); + auto t2 = theta2(); + + if (orientation() == algorithm::Orientation::COUNTERCLOCKWISE) { + std::swap(t0, t2); + } + + if (t0 < t2) { + t0 += 2*MATH_PI; + } + + auto diff = t0-t2; + + return diff; + } + + /// Return the length of the arc + double getLength() const { + if (isLinear()) { + return p0.distance(p2); + } + + return getAngle()*getRadius(); + } + + /// Return the area enclosed by the arc p0-p1-p2 and the line segment p2-p0 + double getArea() const { + if (isLinear()) { + return 0; + } + + auto R = getRadius(); + auto theta = getAngle(); + return R*R/2*(theta - std::sin(theta)); + } + + /// Return the angle of p0 + double theta0() const { + return std::atan2(p0.y - getCenter().y, p0.x - getCenter().x); + } + + /// Return the angle of p2 + double theta2() const { + return std::atan2(p2.y - getCenter().y, p2.x - getCenter().x); + } + + /// Check to see if a coordinate lies on the arc + /// Only the angle is checked, so it is assumed that the point lies on + /// the circle of which this arc is a part. + bool containsPointOnCircle(const CoordinateXY& q) const { + double theta = std::atan2(q.y - getCenter().y, q.x - getCenter().x); + return containsAngle(theta); + } + + /// Check to see if a coordinate lies on the arc, after testing whether + /// it lies on the circle. + bool containsPoint(const CoordinateXY& q) { + if (q == p0 || q == p1 || q == p2) { + return true; + } + + auto dist = std::abs(q.distance(getCenter()) - getRadius()); + + if (dist > 1e-8) { + return false; + } + + if (triangulate::quadedge::TrianglePredicate::isInCircleNormalized(p0, p1, p2, q) != geom::Location::BOUNDARY) { + return false; + } + + return containsPointOnCircle(q); + } + + /// Check to see if a given angle lies on this arc + bool containsAngle(double theta) const { + auto t0 = theta0(); + auto t2 = theta2(); + + if (theta == t0 || theta == t2) { + return true; + } + + if (orientation() == algorithm::Orientation::COUNTERCLOCKWISE) { + std::swap(t0, t2); + } + + t2 -= t0; + theta -= t0; + + if (t2 < 0) { + t2 += 2*MATH_PI; + } + if (theta < 0) { + theta += 2*MATH_PI; + } + + return theta >= t2; + } + + /// Return true if the arc is pointing positive in the y direction + /// at the location of a specified point. The point is assumed to + /// be on the arc. + bool isUpwardAtPoint(const CoordinateXY& q) const { + auto quad = geom::Quadrant::quadrant(getCenter(), q); + bool isUpward; + + if (orientation() == algorithm::Orientation::CLOCKWISE) { + isUpward = (quad == geom::Quadrant::SW || quad == geom::Quadrant::NW); + } else { + isUpward = (quad == geom::Quadrant::SE || quad == geom::Quadrant::NE); + } + + return isUpward; + } + + class Iterator { + public: + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = geom::CoordinateXY; + using pointer = const geom::CoordinateXY*; + using reference = const geom::CoordinateXY&; + + Iterator(const CircularArc& arc, int i) : m_arc(arc), m_i(i) {} + + reference operator*() const { + return m_i == 0 ? m_arc.p0 : (m_i == 1 ? m_arc.p1 : m_arc.p2); + } + + Iterator& operator++() { + m_i++; + return *this; + } + + Iterator operator++(int) { + Iterator ret = *this; + m_i++; + return ret; + } + + bool operator==(const Iterator& other) const { + return m_i == other.m_i; + } + + bool operator!=(const Iterator& other) const { + return !(*this == other); + } + + private: + const CircularArc& m_arc; + int m_i; + + }; + + Iterator begin() const { + return Iterator(*this, 0); + } + + Iterator end() const { + return Iterator(*this, 3); + } + +private: + mutable CoordinateXY m_center; + mutable double m_radius; + mutable int m_orientation; + mutable bool m_center_known = false; + mutable bool m_radius_known = false; + mutable bool m_orientation_known = false; +}; + +} +} diff --git a/Sources/geos/include/geos/geom/CircularString.h b/Sources/geos/include/geos/geom/CircularString.h new file mode 100644 index 0000000..85dc301 --- /dev/null +++ b/Sources/geos/include/geos/geom/CircularString.h @@ -0,0 +1,85 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +namespace geos { +namespace geom { + +class GEOS_DLL CircularString : public SimpleCurve { + +public: + using SimpleCurve::SimpleCurve; + + friend class GeometryFactory; + + ~CircularString() override; + + std::unique_ptr clone() const; + + std::string getGeometryType() const override; + + GeometryTypeId getGeometryTypeId() const override; + + double getLength() const override; + + bool hasCurvedComponents() const override + { + return true; + } + + bool isCurved() const override { + return true; + } + + std::unique_ptr reverse() const + { + return std::unique_ptr(reverseImpl()); + } + +protected: + + /// \brief + /// Constructs a CircularString taking ownership the + /// given CoordinateSequence. + CircularString(std::unique_ptr&& pts, + const GeometryFactory& newFactory); + + CircularString* cloneImpl() const override + { + return new CircularString(*this); + } + + void geometryChangedAction() override + { + envelope = computeEnvelopeInternal(false); + } + + int + getSortIndex() const override + { + return SORTINDEX_LINESTRING; + }; + + CircularString* reverseImpl() const override; + + void validateConstruction(); + +}; + + +} +} diff --git a/Sources/geos/include/geos/geom/CompoundCurve.h b/Sources/geos/include/geos/geom/CompoundCurve.h new file mode 100644 index 0000000..1dbdf0f --- /dev/null +++ b/Sources/geos/include/geos/geom/CompoundCurve.h @@ -0,0 +1,121 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + +namespace geos { +namespace geom { + +class GEOS_DLL CompoundCurve : public Curve { + friend class GeometryFactory; + +public: + using Curve::apply_ro; + using Curve::apply_rw; + + void apply_ro(CoordinateFilter* filter) const override; + + void apply_ro(CoordinateSequenceFilter& filter) const override; + + void apply_rw(CoordinateSequenceFilter& filter) override; + + void apply_rw(const CoordinateFilter* filter) override; + + int compareToSameClass(const Geometry* geom) const override; + + std::unique_ptr clone() const; + + bool equalsExact(const Geometry* other, double tolerance = 0) + const override; + + bool equalsIdentical(const Geometry* other) const override; + + std::unique_ptr getBoundary() const override; + + const CoordinateXY* getCoordinate() const override; + + uint8_t getCoordinateDimension() const override; + + std::unique_ptr getCoordinates() const override; + + /// Returns the nth section of the CompoundCurve + const SimpleCurve* getCurveN(std::size_t) const override; + + const Envelope* getEnvelopeInternal() const override + { + return &envelope; + } + + std::string getGeometryType() const override; + + GeometryTypeId getGeometryTypeId() const override; + + double getLength() const override; + + /// Returns the number of sections in the CompoundCurve + std::size_t getNumCurves() const override; + + std::size_t getNumPoints() const override; + + bool hasCurvedComponents() const override; + + bool hasM() const override; + + bool hasZ() const override; + + bool isClosed() const override; + + bool isEmpty() const override; + + void normalize() override; + + std::unique_ptr reverse() const; + +protected: + /// Construct a CompoundCurve, taking ownership of the + /// provided CoordinateSequence + CompoundCurve(std::vector>&&, + const GeometryFactory&); + + CompoundCurve(const CompoundCurve&); + + CompoundCurve& operator=(const CompoundCurve&); + + CompoundCurve* cloneImpl() const override; + + Envelope computeEnvelopeInternal() const; + + void geometryChangedAction() override + { + envelope = computeEnvelopeInternal(); + } + + int getSortIndex() const override + { + return SORTINDEX_COMPOUNDCURVE; + } + + CompoundCurve* reverseImpl() const override; + +private: + std::vector> curves; + Envelope envelope; +}; + +} +} diff --git a/Sources/geos/include/geos/geom/Coordinate.h b/Sources/geos/include/geos/geom/Coordinate.h index 4e82a68..0d8bced 100644 --- a/Sources/geos/include/geos/geom/Coordinate.h +++ b/Sources/geos/include/geos/geom/Coordinate.h @@ -17,10 +17,12 @@ #include #include // for DoubleNotANumber #include +#include #include #include // for typedefs #include #include +#include #ifdef _MSC_VER #pragma warning(push) @@ -30,49 +32,51 @@ namespace geos { namespace geom { // geos.geom -struct CoordinateLessThen; +// Forward declarations +struct CoordinateLessThan; +class CoordinateXYZM; +class CoordinateXYM; +class Coordinate; + +enum class CoordinateType : std::uint8_t { + XY, + XYZ, + XYZM, + XYM, +}; -/** - * \class Coordinate geom.h geos.h - * - * \brief - * Coordinate is the lightweight class used to store coordinates. - * - * It is distinct from Point, which is a subclass of Geometry. - * Unlike objects of type Point (which contain additional - * information such as an envelope, a precision model, and spatial - * reference system information), a Coordinate only contains - * ordinate values and accessor methods. - * - * Coordinate objects are two-dimensional points, with an additional - * z-ordinate. JTS does not support any operations on the z-ordinate except - * the basic accessor functions. - * - * Constructed coordinates will have a z-ordinate of DoubleNotANumber. - * The standard comparison functions will ignore the z-ordinate. - * - */ -// Define the following to make assignments and copy constructions -// NON-(will let profilers report usages) -//#define PROFILE_COORDINATE_COPIES 1 -class GEOS_DLL Coordinate { +enum class Ordinate : std::uint8_t { + X, + Y, + Z, + M +}; -private: +GEOS_DLL std::ostream& operator<< (std::ostream&, const CoordinateType); - static Coordinate _nullCoord; +class GEOS_DLL CoordinateXY { -public: - /// A set of const Coordinate pointers - typedef std::set ConstSet; + const static CoordinateXY _nullCoord; - /// A vector of const Coordinate pointers - typedef std::vector ConstVect; +protected: + constexpr const static double DEFAULT_X = 0.0; + constexpr const static double DEFAULT_Y = 0.0; + constexpr const static double DEFAULT_Z = DoubleNotANumber; + constexpr const static double DEFAULT_M = DoubleNotANumber; - /// A stack of const Coordinate pointers - typedef std::stack ConstStack; +public: + CoordinateXY() + : x(DEFAULT_X) + , y(DEFAULT_Y) + {} - /// A vector of Coordinate objects (real object, not pointers) - typedef std::vector Vect; + CoordinateXY(double xNew, double yNew) + : x(xNew) + , y(yNew) + {} + + template + double get() const; /// x-coordinate double x; @@ -80,56 +84,24 @@ class GEOS_DLL Coordinate { /// y-coordinate double y; - /// z-coordinate - double z; - - /// Output function - GEOS_DLL friend std::ostream& operator<< (std::ostream& os, const Coordinate& c); - /// Equality operator for Coordinate. 2D only. - GEOS_DLL friend bool operator==(const Coordinate& a, const Coordinate& b) + GEOS_DLL friend bool operator==(const CoordinateXY& a, const CoordinateXY& b) { return a.equals2D(b); }; /// Inequality operator for Coordinate. 2D only. - GEOS_DLL friend bool operator!=(const Coordinate& a, const Coordinate& b) + GEOS_DLL friend bool operator!=(const CoordinateXY& a, const CoordinateXY& b) { return ! a.equals2D(b); }; - Coordinate() - : x(0.0) - , y(0.0) - , z(DoubleNotANumber) - {}; - - Coordinate(double xNew, double yNew, double zNew = DoubleNotANumber) - : x(xNew) - , y(yNew) - , z(zNew) - {}; - - void setNull() - { - x = DoubleNotANumber; - y = DoubleNotANumber; - z = DoubleNotANumber; - }; - - static Coordinate& getNull(); - - bool isNull() const - { - return (std::isnan(x) && std::isnan(y) && std::isnan(z)); - }; - bool isValid() const { return std::isfinite(x) && std::isfinite(y); }; - bool equals2D(const Coordinate& other) const + bool equals2D(const CoordinateXY& other) const { if(x != other.x) { return false; @@ -140,7 +112,7 @@ class GEOS_DLL Coordinate { return true; }; - bool equals2D(const Coordinate& other, double tolerance) const + bool equals2D(const CoordinateXY& other, double tolerance) const { if (std::abs(x - other.x) > tolerance) { return false; @@ -152,13 +124,13 @@ class GEOS_DLL Coordinate { }; /// 2D only - bool equals(const Coordinate& other) const + bool equals(const CoordinateXY& other) const { return equals2D(other); }; - /// TODO: deprecate this, move logic to CoordinateLessThen instead - int compareTo(const Coordinate& other) const + /// TODO: deprecate this, move logic to CoordinateLessThan instead + inline int compareTo(const CoordinateXY& other) const { if(x < other.x) { return -1; @@ -175,36 +147,36 @@ class GEOS_DLL Coordinate { return 0; }; - /// 3D comparison - bool equals3D(const Coordinate& other) const - { - return (x == other.x) && (y == other.y) && - ((z == other.z) || (std::isnan(z) && std::isnan(other.z))); - }; - - /// Returns a string of the form (x,y,z) . - std::string toString() const; + static const CoordinateXY& getNull(); - /// TODO: obsoleted this, can use PrecisionModel::makePrecise(Coordinate*) - /// instead - //void makePrecise(const PrecisionModel *pm); - double distance(const Coordinate& p) const + double distance(const CoordinateXY& p) const { double dx = x - p.x; double dy = y - p.y; return std::sqrt(dx * dx + dy * dy); }; - double distanceSquared(const Coordinate& p) const + double distanceSquared(const CoordinateXY& p) const { double dx = x - p.x; double dy = y - p.y; return dx * dx + dy * dy; }; + bool isNull() const + { + return (std::isnan(x) && std::isnan(y)); + }; + + void setNull() + { + x = DoubleNotANumber; + y = DoubleNotANumber; + }; + struct GEOS_DLL HashCode { - std::size_t operator()(const Coordinate & c) const + inline std::size_t operator()(const CoordinateXY& c) const { size_t h = std::hash{}(c.x); h ^= std::hash{}(c.y) << 1; @@ -213,13 +185,254 @@ class GEOS_DLL Coordinate { }; }; + using UnorderedSet = std::unordered_set; + + /// Returns a string of the form (x,y,z) . + std::string toString() const; +}; + +/** + * \class Coordinate geom.h geos.h + * + * \brief + * Coordinate is the lightweight class used to store coordinates. + * + * It is distinct from Point, which is a subclass of Geometry. + * Unlike objects of type Point (which contain additional + * information such as an envelope, a precision model, and spatial + * reference system information), a Coordinate only contains + * ordinate values and accessor methods. + * + * Coordinate objects are two-dimensional points, with an additional + * z-ordinate. JTS does not support any operations on the z-ordinate except + * the basic accessor functions. + * + * Constructed coordinates will have a z-ordinate of DoubleNotANumber. + * The standard comparison functions will ignore the z-ordinate. + * + */ +// Define the following to make assignments and copy constructions +// NON-(will let profilers report usages) +//#define PROFILE_COORDINATE_COPIES 1 +class GEOS_DLL Coordinate : public CoordinateXY { + +private: + + static const Coordinate _nullCoord; + +public: + /// A set of const Coordinate pointers + typedef std::set ConstSet; + typedef std::set ConstXYSet; + + /// A vector of const Coordinate pointers + typedef std::vector ConstVect; + + /// A stack of const Coordinate pointers + typedef std::stack ConstStack; + + /// A vector of Coordinate objects (real object, not pointers) + typedef std::vector Vect; + + /// A map of const Coordinate pointers to integers + typedef std::map ConstIntMap; + + /// z-coordinate + double z; + + Coordinate() + : CoordinateXY() + , z(DEFAULT_Z) + {}; + + Coordinate(double xNew, double yNew, double zNew = DEFAULT_Z) + : CoordinateXY(xNew, yNew) + , z(zNew) + {}; + + explicit Coordinate(const CoordinateXY& other) + : CoordinateXY(other) + , z(DEFAULT_Z) + {}; + + template + double get() const; + + void setNull() + { + CoordinateXY::setNull(); + z = DoubleNotANumber; + }; + + static const Coordinate& getNull(); + + bool isNull() const + { + return CoordinateXY::isNull() && std::isnan(z); + }; + + /// 3D comparison + bool equals3D(const Coordinate& other) const + { + return (x == other.x) && (y == other.y) && + ((z == other.z) || (std::isnan(z) && std::isnan(other.z))); + }; + + /// Returns a string of the form (x,y,z) . + std::string toString() const; + + Coordinate& operator=(const CoordinateXY& other){ + x = other.x; + y = other.y; + z = DEFAULT_Z; + + return *this; + } +}; + + +class GEOS_DLL CoordinateXYM : public CoordinateXY { +private: + static const CoordinateXYM _nullCoord; + +public: + CoordinateXYM() : CoordinateXYM(DEFAULT_X, DEFAULT_Y, DEFAULT_M) {} + + explicit CoordinateXYM(const CoordinateXY& c) + : CoordinateXY(c) + , m(DEFAULT_M) {} + + CoordinateXYM(double x_, double y_, double m_) + : CoordinateXY(x_, y_) + , m(m_) {} + + double m; + + template + double get() const; + + static const CoordinateXYM& getNull(); + + void setNull() + { + CoordinateXY::setNull(); + m = DoubleNotANumber; + }; + + bool isNull() const + { + return CoordinateXY::isNull() && std::isnan(m); + } + bool equals3D(const CoordinateXYM& other) const { + return x == other.x && y == other.y && (m == other.m || (std::isnan(m) && std::isnan(other.m))); + } + + CoordinateXYM& operator=(const CoordinateXYZM& other); + + CoordinateXYM& operator=(const CoordinateXY& other) { + x = other.x; + y = other.y; + m = DEFAULT_M; + + return *this; + } + + std::string toString() const; +}; + + +class GEOS_DLL CoordinateXYZM : public Coordinate { +private: + static const CoordinateXYZM _nullCoord; + +public: + CoordinateXYZM() : CoordinateXYZM(DEFAULT_X, DEFAULT_Y, DEFAULT_Z, DEFAULT_M) {} + + explicit CoordinateXYZM(const CoordinateXY& c) + : Coordinate(c) + , m(DEFAULT_M) {} + + explicit CoordinateXYZM(const CoordinateXYM& c) + : Coordinate(c) + , m(c.m) {} + + explicit CoordinateXYZM(const Coordinate& c) + : Coordinate(c) + , m(DEFAULT_M) {} + + CoordinateXYZM(double x_, double y_, double z_, double m_) + : Coordinate(x_, y_, z_) + , m(m_) {} + + double m; + + template + double get() const; + + static const CoordinateXYZM& getNull(); + + void setNull() + { + Coordinate::setNull(); + m = DoubleNotANumber; + }; + + + bool isNull() const + { + return Coordinate::isNull() && std::isnan(m); + } + + bool equals4D(const CoordinateXYZM& other) const { + return x == other.x && y == other.y && + (z == other.z || (std::isnan(z) && std::isnan(other.z))) && + (m == other.m || (std::isnan(m) && std::isnan(other.m))); + } + + CoordinateXYZM& operator=(const CoordinateXY& other) { + x = other.x; + y = other.y; + z = DEFAULT_Z; + m = DEFAULT_M; + + return *this; + } + + CoordinateXYZM& operator=(const Coordinate& other) { + x = other.x; + y = other.y; + z = other.z; + m = DEFAULT_M; + + return *this; + } + + CoordinateXYZM& operator=(const CoordinateXYM& other) { + x = other.x; + y = other.y; + z = DEFAULT_Z; + m = other.m; + + return *this; + } + + std::string toString() const; }; +inline CoordinateXYM& +CoordinateXYM::operator=(const CoordinateXYZM& other) { + x = other.x; + y = other.y; + m = other.m; + + return *this; +} + /// Strict weak ordering Functor for Coordinate -struct GEOS_DLL CoordinateLessThen { +struct GEOS_DLL CoordinateLessThan { - bool operator()(const Coordinate* a, const Coordinate* b) const + bool operator()(const CoordinateXY* a, const CoordinateXY* b) const { if(a->compareTo(*b) < 0) { return true; @@ -229,7 +442,7 @@ struct GEOS_DLL CoordinateLessThen { } }; - bool operator()(const Coordinate& a, const Coordinate& b) const + bool operator()(const CoordinateXY& a, const CoordinateXY& b) const { if(a.compareTo(b) < 0) { return true; @@ -242,14 +455,147 @@ struct GEOS_DLL CoordinateLessThen { }; /// Strict weak ordering operator for Coordinate -inline bool operator<(const Coordinate& a, const Coordinate& b) +inline bool operator<(const CoordinateXY& a, const CoordinateXY& b) +{ + return CoordinateLessThan()(a, b); +} + + +// Generic accessors, XY + +template<> +inline double CoordinateXY::get() const +{ + return x; +} + +template<> +inline double CoordinateXY::get() const +{ + return y; +} + +template<> +inline double CoordinateXY::get() const +{ + return DEFAULT_Z; +} + +template<> +inline double CoordinateXY::get() const +{ + return DEFAULT_M; +} + +// Generic accessors, XYZ + +template<> +inline double Coordinate::get() const +{ + return x; +} + +template<> +inline double Coordinate::get() const +{ + return y; +} + +template<> +inline double Coordinate::get() const +{ + return z; +} + +template<> +inline double Coordinate::get() const +{ + return DEFAULT_M; +} + +// Generic accessors, XYM + +template<> +inline double CoordinateXYM::get() const +{ + return x; +} + +template<> +inline double CoordinateXYM::get() const +{ + return y; +} + +template<> +inline double CoordinateXYM::get() const +{ + return DEFAULT_Z; +} + +template<> +inline double CoordinateXYM::get() const { - return CoordinateLessThen()(a, b); + return m; } +// Generic accessors, XYZM + +template<> +inline double CoordinateXYZM::get() const +{ + return x; +} + +template<> +inline double CoordinateXYZM::get() const +{ + return y; +} + +template<> +inline double CoordinateXYZM::get() const +{ + return z; +} + +template<> +inline double CoordinateXYZM::get() const +{ + return m; +} + +GEOS_DLL std::ostream& operator<< (std::ostream& os, const CoordinateXY& c); +GEOS_DLL std::ostream& operator<< (std::ostream& os, const Coordinate& c); +GEOS_DLL std::ostream& operator<< (std::ostream& os, const CoordinateXYM& c); +GEOS_DLL std::ostream& operator<< (std::ostream& os, const CoordinateXYZM& c); + } // namespace geos.geom } // namespace geos +// Add specializations of std::common_type for Coordinate types +namespace std { + template<> struct common_type { using type = geos::geom::CoordinateXY; }; + template<> struct common_type { using type = geos::geom::Coordinate; }; + template<> struct common_type { using type = geos::geom::CoordinateXYM; }; + template<> struct common_type { using type = geos::geom::CoordinateXYZM; }; + + template<> struct common_type { using type = geos::geom::Coordinate; }; + template<> struct common_type { using type = geos::geom::Coordinate; }; + template<> struct common_type { using type = geos::geom::CoordinateXYZM; }; + template<> struct common_type { using type = geos::geom::CoordinateXYZM; }; + + template<> struct common_type { using type = geos::geom::CoordinateXYM; }; + template<> struct common_type { using type = geos::geom::CoordinateXYZM; }; + template<> struct common_type { using type = geos::geom::CoordinateXYM; }; + template<> struct common_type { using type = geos::geom::CoordinateXYZM; }; + + template<> struct common_type { using type = geos::geom::CoordinateXYZM; }; + template<> struct common_type { using type = geos::geom::CoordinateXYZM; }; + template<> struct common_type { using type = geos::geom::CoordinateXYZM; }; + template<> struct common_type { using type = geos::geom::CoordinateXYZM; }; +} + #ifdef _MSC_VER #pragma warning(pop) #endif diff --git a/Sources/geos/include/geos/geom/CoordinateArraySequence.h b/Sources/geos/include/geos/geom/CoordinateArraySequence.h deleted file mode 100644 index b4a5ce9..0000000 --- a/Sources/geos/include/geos/geom/CoordinateArraySequence.h +++ /dev/null @@ -1,146 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2006 Refractions Research Inc. - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - **********************************************************************/ - -#pragma once - -#include -#include - -#include -#include - -// Forward declarations -namespace geos { -namespace geom { -class Coordinate; -} -} - - -namespace geos { -namespace geom { // geos.geom - -/// The default implementation of CoordinateSequence -class GEOS_DLL CoordinateArraySequence : public CoordinateSequence { -public: - - CoordinateArraySequence(const CoordinateArraySequence& cl); - - CoordinateArraySequence(const CoordinateSequence& cl); - - std::unique_ptr clone() const override; - - const Coordinate& getAt(std::size_t pos) const override; - - /// Copy Coordinate at position i to Coordinate c - void getAt(std::size_t i, Coordinate& c) const override; - - std::size_t getSize() const override; - - // See dox in CoordinateSequence.h - void toVector(std::vector&) const override; - - /// Construct an empty sequence - CoordinateArraySequence(); - - /// Construct sequence moving from given Coordinate vector - CoordinateArraySequence(std::vector && coords, - std::size_t dimension = 0); - - /// Construct sequence taking ownership of given Coordinate vector - CoordinateArraySequence(std::vector* coords, - std::size_t dimension = 0); - - /// Construct sequence allocating space for n coordinates - CoordinateArraySequence(std::size_t n, std::size_t dimension = 0); - - ~CoordinateArraySequence() override = default; - - bool - isEmpty() const override - { - return empty(); - } - - bool - empty() const - { - return vect.empty(); - } - - /// Reset this CoordinateArraySequence to the empty state - void - clear() - { - vect.clear(); - } - - /// Add a Coordinate to the list - void add(const Coordinate& c); - - /** - * \brief Add a coordinate - * @param c the coordinate to add - * @param allowRepeated if set to false, repeated coordinates - * are collapsed - */ - void add(const Coordinate& c, bool allowRepeated); - - /** \brief - * Inserts the specified coordinate at the specified position in - * this list. - * - * @param i the position at which to insert - * @param coord the coordinate to insert - * @param allowRepeated if set to false, repeated coordinates are - * collapsed - * - * @note this is a CoordinateList interface in JTS - */ - void add(std::size_t i, const Coordinate& coord, bool allowRepeated); - - void add(const CoordinateSequence* cl, bool allowRepeated, bool direction); - - void setAt(const Coordinate& c, std::size_t pos) override; - - void setPoints(const std::vector& v) override; - - void setOrdinate(std::size_t index, std::size_t ordinateIndex, - double value) override; - - void expandEnvelope(Envelope& env) const override; - - void closeRing(); - - std::size_t getDimension() const override; - - void apply_rw(const CoordinateFilter* filter) override; - - void apply_ro(CoordinateFilter* filter) const override { - for(const auto& coord : vect) { - filter->filter_ro(&coord); - } - } - -private: - std::vector vect; - mutable std::size_t dimension; -}; - -/// This is for backward API compatibility -typedef CoordinateArraySequence DefaultCoordinateSequence; - -} // namespace geos.geom -} // namespace geos - diff --git a/Sources/geos/include/geos/geom/CoordinateArraySequenceFactory.h b/Sources/geos/include/geos/geom/CoordinateArraySequenceFactory.h deleted file mode 100644 index 948bf3f..0000000 --- a/Sources/geos/include/geos/geom/CoordinateArraySequenceFactory.h +++ /dev/null @@ -1,95 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2006 Refractions Research Inc. - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - **********************************************************************/ - -#pragma once - - -#include -#include -#include // for inheritance -#include - - - -// Forward declarations -namespace geos { -namespace geom { -class Coordinate; -} -} - -namespace geos { -namespace geom { // geos::geom - -/** - * \class CoordinateArraySequenceFactory geom.h geos.h - * - * \brief - * Creates CoordinateSequences internally represented as an array of - * Coordinates. - */ -class GEOS_DLL CoordinateArraySequenceFactory: public CoordinateSequenceFactory { - -public: - std::unique_ptr create() const override; - - std::unique_ptr create( - std::vector* coords, - size_t dimension) const override - { - return std::unique_ptr( - new CoordinateArraySequence(coords, dimension)); - }; - - std::unique_ptr create( - std::vector && coords, - size_t dimension) const override - { - return std::unique_ptr(new CoordinateArraySequence(std::move(coords), dimension)); - }; - - /** @see CoordinateSequenceFactory::create(std::size_t, int) */ - std::unique_ptr create(std::size_t size, std::size_t dimension) const override - { - return std::unique_ptr( - new CoordinateArraySequence(size, dimension)); - }; - - std::unique_ptr create(const CoordinateSequence& seq) const override - { - return std::unique_ptr( - new CoordinateArraySequence(seq)); - }; - - /** \brief - * Returns the singleton instance of CoordinateArraySequenceFactory - */ - static const CoordinateSequenceFactory* instance(); -}; - -/// This is for backward API compatibility -typedef CoordinateArraySequenceFactory DefaultCoordinateSequenceFactory; - -} // namespace geos::geom -} // namespace geos - - - - - - - - - - diff --git a/Sources/geos/include/geos/geom/CoordinateFilter.h b/Sources/geos/include/geos/geom/CoordinateFilter.h index 792e3a2..2555a79 100644 --- a/Sources/geos/include/geos/geom/CoordinateFilter.h +++ b/Sources/geos/include/geos/geom/CoordinateFilter.h @@ -15,14 +15,13 @@ #pragma once #include +#include #include namespace geos { namespace geom { // geos::geom -class Coordinate; - /** \brief * Geometry classes support the concept of applying a * coordinate filter to every coordinate in the Geometry. @@ -34,8 +33,11 @@ class Coordinate; * used to implement such things as coordinate transformations, centroid and * envelope computation, and many other functions. * - * TODO: provide geom::CoordinateInspector and geom::CoordinateMutator instead - * of having the two versions of filter_rw and filter_ro + * A CoordinateFilter should be able to process a CoordinateXY object and can + * optionally provide specialized implementations for higher-dimensionality + * Coordinates. If the behavior can be templated on coordinate type, then + * a filter may inherit from CoordinateInspector or CoordinateMutator to + * generate these implementations from a template. * */ class GEOS_DLL CoordinateFilter { @@ -43,13 +45,18 @@ class GEOS_DLL CoordinateFilter { virtual ~CoordinateFilter() {} + virtual bool isDone() const + { + return false; + } + /** \brief * Performs an operation on `coord`. * * **param** `coord` a Coordinate to which the filter is applied. */ virtual void - filter_rw(Coordinate* /*coord*/) const + filter_rw(CoordinateXY* /*coord*/) const { assert(0); } @@ -60,10 +67,66 @@ class GEOS_DLL CoordinateFilter { * **param** `coord` a Coordinate to which the filter is applied. */ virtual void - filter_ro(const Coordinate* /*coord*/) + filter_ro(const CoordinateXY* /*coord*/) { assert(0); } + + virtual void + filter_rw(Coordinate* c) const + { + filter_rw(static_cast(c)); + } + + virtual void + filter_ro(const Coordinate* c) + { + filter_ro(static_cast(c)); + } + + virtual void + filter_rw(CoordinateXYM* c) const + { + filter_rw(static_cast(c)); + } + + virtual void + filter_ro(const CoordinateXYM* c) + { + filter_ro(static_cast(c)); + } + + virtual void + filter_rw(CoordinateXYZM* c) const + { + filter_rw(static_cast(c)); + } + + virtual void + filter_ro(const CoordinateXYZM* c) + { + filter_ro(static_cast(c)); + } +}; + +template +class CoordinateInspector : public CoordinateFilter +{ +public: + virtual void filter_ro(const CoordinateXY* c) override { static_cast(this)->filter(c); } + virtual void filter_ro(const Coordinate* c) override { static_cast(this)->filter(c); } + virtual void filter_ro(const CoordinateXYM* c) override { static_cast(this)->filter(c); } + virtual void filter_ro(const CoordinateXYZM* c) override { static_cast(this)->filter(c); } +}; + +template +class CoordinateMutator : public CoordinateFilter +{ +public: + virtual void filter_rw(CoordinateXY* c) const override { static_cast(this)->filter(c); } + virtual void filter_rw(Coordinate* c) const override { static_cast(this)->filter(c); } + virtual void filter_rw(CoordinateXYM* c) const override { static_cast(this)->filter(c); } + virtual void filter_rw(CoordinateXYZM* c) const override { static_cast(this)->filter(c); } }; } // namespace geos::geom diff --git a/Sources/geos/include/geos/geom/CoordinateList.h b/Sources/geos/include/geos/geom/CoordinateList.h index 54dcb4d..532fd3d 100644 --- a/Sources/geos/include/geos/geom/CoordinateList.h +++ b/Sources/geos/include/geos/geom/CoordinateList.h @@ -21,6 +21,8 @@ #include #include +#include +#include #include #include // for operator<< @@ -44,7 +46,7 @@ namespace geom { // geos::geom /** \brief * A list of {@link Coordinate}s, which may - * be set to prevent repeated coordinates from occuring in the list. + * be set to prevent repeated coordinates from occurring in the list. * * Use this class when fast insertions and removal at arbitrary * position is needed. @@ -70,12 +72,18 @@ class GEOS_DLL CoordinateList { * * @param v the initial coordinates */ - CoordinateList(const std::vector& v) + template + CoordinateList(const T& v) : coords(v.begin(), v.end()) { } + CoordinateList(const CoordinateSequence& v) + : CoordinateList(v.items()) + { + } + CoordinateList() : coords() @@ -175,6 +183,15 @@ class GEOS_DLL CoordinateList { ret->assign(coords.begin(), coords.end()); return ret; } + + std::unique_ptr + toCoordinateSequence() const + { + auto ret = detail::make_unique(); + ret->add(begin(), end()); + return ret; + } + void closeRing() { diff --git a/Sources/geos/include/geos/geom/CoordinateSequence.h b/Sources/geos/include/geos/geom/CoordinateSequence.h index a8a4f07..b49b869 100644 --- a/Sources/geos/include/geos/geom/CoordinateSequence.h +++ b/Sources/geos/include/geos/geom/CoordinateSequence.h @@ -3,6 +3,7 @@ * GEOS - Geometry Engine Open Source * http://geos.osgeo.org * + * Copyright (C) 2022 ISciences LLC * Copyright (C) 2006 Refractions Research Inc. * * This is free software; you can redistribute and/or modify it under @@ -17,8 +18,11 @@ #include #include // for applyCoordinateFilter +#include +#include #include +#include #include // ostream #include // for unique_ptr typedef @@ -27,7 +31,6 @@ namespace geos { namespace geom { class Envelope; class CoordinateFilter; -class Coordinate; } } @@ -40,103 +43,510 @@ namespace geom { // geos::geom * \brief * The internal representation of a list of coordinates inside a Geometry. * + * A CoordinateSequence is capable of storing XY, XYZ, XYM, or XYZM Coordinates. For efficient + * storage, the dimensionality of the CoordinateSequence should be specified at creation using + * the constructor with `hasz` and `hasm` arguments. Currently most of the GEOS code base + * stores 2D Coordinates and accesses using the Coordinate type. Sequences used by these parts + * of the code must be created with constructors without `hasz` and `hasm` arguments. + * + * If a high-dimension Coordinate coordinate is read from a low-dimension CoordinateSequence, + * the higher dimensions will be populated with incorrect values or a segfault may occur. + * */ class GEOS_DLL CoordinateSequence { -protected: - - CoordinateSequence() {} +public: - CoordinateSequence(const CoordinateSequence&) {} + /// Standard ordinate index values + enum { X, Y, Z, M }; -public: + using iterator = CoordinateSequenceIterator; + using const_iterator = CoordinateSequenceIterator; typedef std::unique_ptr Ptr; - virtual - ~CoordinateSequence() {} + /// \defgroup construct Constructors + /// @{ + + /** + * Create an CoordinateSequence capable of storing XY or XYZ coordinates. + */ + CoordinateSequence(); + + /** + * Create a CoordinateSequence capable of storing XY, XYZ or XYZM coordinates. + * + * @param size size of the sequence to create. + * @param dim 2 for 2D, 3 for XYZ, 4 for XYZM, or 0 to determine + * this based on the first coordinate in the sequence + */ + CoordinateSequence(std::size_t size, std::size_t dim = 0); + + /** + * Create a CoordinateSequence that packs coordinates of any dimension. + * Code using a CoordinateSequence constructed in this way must not + * attempt to access references to coordinates with dimensions that + * are not actually stored in the sequence. + * + * @param size size of the sequence to create + * @param hasz true if the stored + * @param hasm + * @param initialize + */ + CoordinateSequence(std::size_t size, bool hasz, bool hasm, bool initialize = true); + + /** + * Create a CoordinateSequence from a list of XYZ coordinates. + * Code using the sequence may only access references to CoordinateXY + * or Coordinate objects. + */ + CoordinateSequence(const std::initializer_list&); + + /** + * Create a CoordinateSequence from a list of XY coordinates. + * Code using the sequence may only access references to CoordinateXY objects. + */ + CoordinateSequence(const std::initializer_list&); + + /** + * Create a CoordinateSequence from a list of XYM coordinates. + * Code using the sequence may only access references to CoordinateXY + * or CoordinateXYM objects. + */ + CoordinateSequence(const std::initializer_list&); + + /** + * Create a CoordinateSequence from a list of XYZM coordinates. + */ + CoordinateSequence(const std::initializer_list&); + + /** + * Create a CoordinateSequence storing XY values only. + * + * @param size size of the sequence to create + */ + static CoordinateSequence XY(std::size_t size) { + return CoordinateSequence(size, false, false); + } + + /** + * Create a CoordinateSequence storing XYZ values only. + * + * @param size size of the sequence to create + */ + static CoordinateSequence XYZ(std::size_t size) { + return CoordinateSequence(size, true, false); + } + + /** + * Create a CoordinateSequence storing XYZM values only. + * + * @param size size of the sequence to create + */ + static CoordinateSequence XYZM(std::size_t size) { + return CoordinateSequence(size, true, true); + } + + /** + * Create a CoordinateSequence storing XYM values only. + * + * @param size size of the sequence to create + */ + static CoordinateSequence XYM(std::size_t size) { + return CoordinateSequence(size, false, true); + } /** \brief - * Returns a deep copy of this collection. + * Returns a heap-allocated deep copy of this CoordinateSequence. */ - virtual std::unique_ptr clone() const = 0; + std::unique_ptr clone() const; + + /// @} + /// \defgroup prop Properties + /// @{ + + /** + * Return the Envelope containing all points in this sequence. + * The Envelope is not cached and is computed each time the + * method is called. + */ + Envelope getEnvelope() const; /** \brief - * Returns a read-only reference to Coordinate at position i. - * - * Whether or not the Coordinate returned is the actual underlying - * Coordinate or merely a copy depends on the implementation. + * Returns the number of Coordinates */ - virtual const Coordinate& getAt(std::size_t i) const = 0; + std::size_t getSize() const { + return size(); + } - /// Return last Coordinate in the sequence - const Coordinate& - back() const + /** \brief + * Returns the number of Coordinates + */ + size_t size() const { - return getAt(size() - 1); + assert(stride() == 2 || stride() == 3 || stride() == 4); + switch(stride()) { + case 2: return m_vect.size() / 2; + case 4: return m_vect.size() / 4; + default : return m_vect.size() / 3; + } } - /// Return first Coordinate in the sequence - const Coordinate& - front() const + /// Returns true if list contains no coordinates. + bool isEmpty() const { + return m_vect.empty(); + } + + /** \brief + * Tests whether an a {@link CoordinateSequence} forms a ring, + * by checking length and closure. Self-intersection is not checked. + * + * @return true if the coordinate form a ring. + */ + bool isRing() const; + + /** + * Returns the dimension (number of ordinates in each coordinate) + * for this sequence. + * + * @return the dimension of the sequence. + */ + std::size_t getDimension() const; + + bool hasZ() const { + return m_hasdim ? m_hasz : (m_vect.empty() || !std::isnan(m_vect[2])); + } + + bool hasM() const { + return m_hasm; + } + + /// Returns true if contains any two consecutive points + bool hasRepeatedPoints() const; + + /// Returns true if contains any NaN/Inf coordinates + bool hasRepeatedOrInvalidPoints() const; + + /// Get the backing type of this CoordinateSequence. This is not necessarily + /// consistent with the dimensionality of the stored Coordinates; 2D Coordinates + /// may be stored as a XYZ coordinates. + CoordinateType getCoordinateType() const { + switch(stride()) { + case 4: return CoordinateType::XYZM; + case 2: return CoordinateType::XY; + default: return hasM() ? CoordinateType::XYM : CoordinateType::XYZ; + } + } + + /// @} + /// \defgroup access Accessors + /// @{ + + /** \brief + * Returns a read-only reference to Coordinate at position i. + */ + template + const T& getAt(std::size_t i) const { + static_assert(std::is_base_of::value, "Must be a Coordinate class"); + assert(sizeof(T) <= sizeof(double) * stride()); + assert(i*stride() < m_vect.size()); + const T* orig = reinterpret_cast(&m_vect[i*stride()]); + return *orig; + } + + /** \brief + * Returns a reference to Coordinate at position i. + */ + template + T& getAt(std::size_t i) { + static_assert(std::is_base_of::value, "Must be a Coordinate class"); + assert(sizeof(T) <= sizeof(double) * stride()); + assert(i*stride() < m_vect.size()); + T* orig = reinterpret_cast(&m_vect[i*stride()]); + return *orig; + } + + /** \brief + * Write Coordinate at position i to given Coordinate. + */ + template + void getAt(std::size_t i, T& c) const { + switch(getCoordinateType()) { + case CoordinateType::XY: c = getAt(i); break; + case CoordinateType::XYZ: c = getAt(i); break; + case CoordinateType::XYZM: c = getAt(i); break; + case CoordinateType::XYM: c = getAt(i); break; + default: getAt(i); + } + } + + void getAt(std::size_t i, CoordinateXY& c) const { + c = getAt(i); + } + + // TODO: change to return CoordinateXY + /** + * Returns a read-only reference to Coordinate at i + */ + const Coordinate& operator[](std::size_t i) const { - return getAt(0); + return getAt(i); } - const Coordinate& - operator[](std::size_t i) const + // TODO: change to return CoordinateXY + /** + * Returns a reference to Coordinate at i + */ + Coordinate& + operator[](std::size_t i) { return getAt(i); } - virtual Envelope getEnvelope() const; + /** + * Returns the ordinate of a coordinate in this sequence. + * Ordinate indices 0 and 1 are assumed to be X and Y. + * Ordinates indices greater than 1 have user-defined semantics + * (for instance, they may contain other dimensions or measure values). + * + * @param index the coordinate index in the sequence + * @param ordinateIndex the ordinate index in the coordinate + * (in range [0, dimension-1]) + */ + double getOrdinate(std::size_t index, std::size_t ordinateIndex) const; - /** \brief - * Write Coordinate at position i to given Coordinate. + /** + * Returns ordinate X (0) of the specified coordinate. + * + * @param index + * @return the value of the X ordinate in the index'th coordinate */ - virtual void getAt(std::size_t i, Coordinate& c) const = 0; + double getX(std::size_t index) const + { + return m_vect[index * stride()]; + } - /** \brief - * Returns the number of Coordinates (actual or otherwise, as - * this implementation may not store its data in Coordinate objects). + /** + * Returns ordinate Y (1) of the specified coordinate. + * + * @param index + * @return the value of the Y ordinate in the index'th coordinate */ - virtual std::size_t getSize() const = 0; + double getY(std::size_t index) const + { + return m_vect[index * stride() + 1]; + } + + /// Return last Coordinate in the sequence + template + const T& back() const + { + return getAt(size() - 1); + } - size_t - size() const + /// Return last Coordinate in the sequence + template + T& back() { - return getSize(); + return getAt(size() - 1); + } + + /// Return first Coordinate in the sequence + template + const T& front() const + { + return *(reinterpret_cast(m_vect.data())); + } + + /// Return first Coordinate in the sequence + template + T& front() + { + return *(reinterpret_cast(m_vect.data())); } /// Pushes all Coordinates of this sequence into the provided vector. - /// - /// This method is a port of the toCoordinateArray() method of JTS. - /// - virtual void toVector(std::vector& coords) const = 0; + void toVector(std::vector& coords) const; + + void toVector(std::vector& coords) const; + - /// Returns true it list contains no coordinates. - virtual bool isEmpty() const = 0; + /// @} + /// \defgroup mutate Mutators + /// @{ /// Copy Coordinate c to position pos - virtual void setAt(const Coordinate& c, std::size_t pos) = 0; + template + void setAt(const T& c, std::size_t pos) { + switch(getCoordinateType()) { + case CoordinateType::XY: setAtImpl(c, pos); break; + case CoordinateType::XYZ: setAtImpl(c, pos); break; + case CoordinateType::XYZM: setAtImpl(c, pos); break; + case CoordinateType::XYM: setAtImpl(c, pos); break; + default: setAtImpl(c, pos); + } + } - /// Get a string representation of CoordinateSequence - std::string toString() const; + /** + * Sets the value for a given ordinate of a coordinate in this sequence. + * + * @param index the coordinate index in the sequence + * @param ordinateIndex the ordinate index in the coordinate + * (in range [0, dimension-1]) + * @param value the new ordinate value + */ + void setOrdinate(std::size_t index, std::size_t ordinateIndex, double value); /// Substitute Coordinate list with a copy of the given vector - virtual void setPoints(const std::vector& v) = 0; + void setPoints(const std::vector& v); + + /// @} + /// \defgroup add Adding methods + /// @{ + + /// Adds the specified coordinate to the end of the sequence. Dimensions + /// present in the coordinate but not in the sequence will be ignored. + /// If multiple coordinates are to be added, a multiple-insert method should + /// be used for best performance. + template + void add(const T& c) { + add(c, size()); + } - /// Returns true if contains any two consecutive points - bool hasRepeatedPoints() const; + /// Adds the specified coordinate to the end of the sequence. Dimensions + /// present in the coordinate but not in the sequence will be ignored. If + /// allowRepeated is false, the coordinate will not be added if it is the + /// same as the last coordinate in the sequence. + /// If multiple coordinates are to be added, a multiple-insert method should + /// be used for best performance. + template + void add(const T& c, bool allowRepeated) + { + if(!allowRepeated && !isEmpty()) { + const CoordinateXY& last = back(); + if(last.equals2D(c)) { + return; + } + } - /// Returns lower-left Coordinate in list - const Coordinate* minCoordinate() const; + add(c); + } /** \brief - * Returns true if given CoordinateSequence contains - * any two consecutive Coordinate + * Inserts the specified coordinate at the specified position in + * this sequence. If multiple coordinates are to be added, a multiple- + * insert method should be used for best performance. + * + * @param c the coordinate to insert + * @param pos the position at which to insert */ - static bool hasRepeatedPoints(const CoordinateSequence* cl); + template + void add(const T& c, std::size_t pos) + { + static_assert(std::is_base_of::value, "Must be a Coordinate class"); + + // c may be a reference inside m_vect, so we make sure it will not + // grow before adding it + if (m_vect.size() + stride() <= m_vect.capacity()) { + make_space(pos, 1); + setAt(c, static_cast(pos)); + } else { + T tmp{c}; + make_space(pos, 1); + setAt(tmp, static_cast(pos)); + } + } + + /** \brief + * Inserts the specified coordinate at the specified position in + * this list. + * + * @param i the position at which to insert + * @param coord the coordinate to insert + * @param allowRepeated if set to false, repeated coordinates are + * collapsed + */ + template + void add(std::size_t i, const T& coord, bool allowRepeated) + { + // don't add duplicate coordinates + if(! allowRepeated) { + std::size_t sz = size(); + if(sz > 0) { + if(i > 0) { + const CoordinateXY& prev = getAt(i - 1); + if(prev.equals2D(coord)) { + return; + } + } + if(i < sz) { + const CoordinateXY& next = getAt(i); + if(next.equals2D(coord)) { + return; + } + } + } + } + + add(coord, i); + } + + void add(double x, double y) { + CoordinateXY c(x, y); + add(c); + } + + void add(const CoordinateSequence& cs); + + void add(const CoordinateSequence& cs, bool allowRepeated); + + void add(const CoordinateSequence& cl, bool allowRepeated, bool forwardDirection); + + void add(const CoordinateSequence& cs, std::size_t from, std::size_t to); + + void add(const CoordinateSequence& cs, std::size_t from, std::size_t to, bool allowRepeated); + + template + void add(T begin, T end, Args... args) { + for (auto it = begin; it != end; ++it) { + add(*it, args...); + } + } + + template + void add(std::size_t i, T from, T to) { + auto npts = static_cast(std::distance(from, to)); + make_space(i, npts); + + for (auto it = from; it != to; ++it) { + setAt(*it, i); + i++; + } + } + + /// @} + /// \defgroup util Utilities + /// @{ + + void clear() { + m_vect.clear(); + } + + void reserve(std::size_t capacity) { + m_vect.reserve(capacity * stride()); + } + + void resize(std::size_t capacity) { + m_vect.resize(capacity * stride()); + } + + void pop_back(); + + /// Get a string representation of CoordinateSequence + std::string toString() const; + + /// Returns lower-left Coordinate in list + const CoordinateXY* minCoordinate() const; /** \brief * Returns either the given CoordinateSequence if its length @@ -149,19 +559,26 @@ class GEOS_DLL CoordinateSequence { // /// or numeric_limits::max() if not found /// - static std::size_t indexOf(const Coordinate* coordinate, - const CoordinateSequence* cl); + static std::size_t indexOf(const CoordinateXY* coordinate, + const CoordinateSequence* cl); /** * \brief * Returns true if the two arrays are identical, both null, - * or pointwise equal + * or pointwise equal in two dimensions */ static bool equals(const CoordinateSequence* cl1, const CoordinateSequence* cl2); + /** + * \brief + * Returns true if the two sequences are identical (pointwise + * equal in all dimensions, with NaN == NaN). + */ + bool equalsIdentical(const CoordinateSequence& other) const; + /// Scroll given CoordinateSequence so to start with given Coordinate. - static void scroll(CoordinateSequence* cl, const Coordinate* firstCoordinate); + static void scroll(CoordinateSequence* cl, const CoordinateXY* firstCoordinate); /** \brief * Determines which orientation of the {@link Coordinate} array @@ -182,110 +599,199 @@ class GEOS_DLL CoordinateSequence { */ static int increasingDirection(const CoordinateSequence& pts); - - /** \brief - * Tests whether an a {@link CoordinateSequence} forms a ring, - * by checking length and closure. Self-intersection is not checked. - * - * @return true if the coordinate form a ring. - */ - bool isRing() const; - /// Reverse Coordinate order in given CoordinateSequence - static void reverse(CoordinateSequence* cl); + void reverse(); + + void sort(); - /// Standard ordinate index values - enum { X, Y, Z, M }; /** - * Returns the dimension (number of ordinates in each coordinate) - * for this sequence. - * - * @return the dimension of the sequence. + * Expands the given Envelope to include the coordinates in the + * sequence. + * @param env the envelope to expand */ - virtual std::size_t getDimension() const = 0; + void expandEnvelope(Envelope& env) const; + + void closeRing(bool allowRepeated = false); + + /// @} + /// \defgroup iterate Iteration + /// @{ + + template + void apply_rw(const Filter* filter) { + switch(getCoordinateType()) { + case CoordinateType::XY: + for (auto& c : items()) { + if (filter->isDone()) break; + filter->filter_rw(&c); + } + break; + case CoordinateType::XYZ: + for (auto& c : items()) { + if (filter->isDone()) break; + filter->filter_rw(&c); + } + break; + case CoordinateType::XYM: + for (auto& c : items()) { + if (filter->isDone()) break; + filter->filter_rw(&c); + } + break; + case CoordinateType::XYZM: + for (auto& c : items()) { + if (filter->isDone()) break; + filter->filter_rw(&c); + } + break; + } + m_hasdim = m_hasz = false; // re-check (see http://trac.osgeo.org/geos/ticket/435) + } - bool hasZ() const { - return getDimension() > 2; + template + void apply_ro(Filter* filter) const { + switch(getCoordinateType()) { + case CoordinateType::XY: + for (const auto& c : items()) { + if (filter->isDone()) break; + filter->filter_ro(&c); + } + break; + case CoordinateType::XYZ: + for (const auto& c : items()) { + if (filter->isDone()) break; + filter->filter_ro(&c); + } + break; + case CoordinateType::XYM: + for (const auto& c : items()) { + if (filter->isDone()) break; + filter->filter_ro(&c); + } + break; + case CoordinateType::XYZM: + for (const auto& c : items()) { + if (filter->isDone()) break; + filter->filter_ro(&c); + } + break; + } } - /** - * Returns the ordinate of a coordinate in this sequence. - * Ordinate indices 0 and 1 are assumed to be X and Y. - * Ordinates indices greater than 1 have user-defined semantics - * (for instance, they may contain other dimensions or measure values). - * - * @param index the coordinate index in the sequence - * @param ordinateIndex the ordinate index in the coordinate - * (in range [0, dimension-1]) - */ - virtual double getOrdinate(std::size_t index, std::size_t ordinateIndex) const; + template + void forEach(F&& fun) const { + switch(getCoordinateType()) { + case CoordinateType::XY: for (const auto& c : items()) { fun(c); } break; + case CoordinateType::XYZ: for (const auto& c : items()) { fun(c); } break; + case CoordinateType::XYM: for (const auto& c : items()) { fun(c); } break; + case CoordinateType::XYZM: for (const auto& c : items()) { fun(c); } break; + } + } - /** - * Returns ordinate X (0) of the specified coordinate. - * - * @param index - * @return the value of the X ordinate in the index'th coordinate - */ - virtual double - getX(std::size_t index) const + template + void forEach(F&& fun) const { - return getOrdinate(index, X); + for (std::size_t i = 0; i < size(); i++) { + fun(getAt(i)); + } } - /** - * Returns ordinate Y (1) of the specified coordinate. - * - * @param index - * @return the value of the Y ordinate in the index'th coordinate - */ - virtual double - getY(std::size_t index) const + template + void forEach(std::size_t from, std::size_t to, F&& fun) const { - return getOrdinate(index, Y); + for (std::size_t i = from; i <= to; i++) { + fun(getAt(i)); + } } + template + class Coordinates { + public: + using SequenceType = typename std::conditional::value, const CoordinateSequence, CoordinateSequence>::type; - /** - * Sets the value for a given ordinate of a coordinate in this sequence. - * - * @param index the coordinate index in the sequence - * @param ordinateIndex the ordinate index in the coordinate - * (in range [0, dimension-1]) - * @param value the new ordinate value - */ - virtual void setOrdinate(std::size_t index, std::size_t ordinateIndex, double value) = 0; + explicit Coordinates(SequenceType* seq) : m_seq(seq) {} - /** - * Expands the given Envelope to include the coordinates in the - * sequence. - * Allows implementing classes to optimize access to coordinate values. - * - * @param env the envelope to expand - */ - virtual void expandEnvelope(Envelope& env) const; + CoordinateSequenceIterator begin() { + return {m_seq}; + } - virtual void apply_rw(const CoordinateFilter* filter) = 0; //Abstract - virtual void apply_ro(CoordinateFilter* filter) const = 0; //Abstract + CoordinateSequenceIterator end() { + return {m_seq, m_seq->getSize()}; + } - /** \brief - * Apply a filter to each Coordinate of this sequence. - * The filter is expected to provide a .filter(Coordinate&) - * method. - * - * TODO: accept a Functor instead, will be more flexible. - * actually, define iterators on Geometry - */ - template - void - applyCoordinateFilter(T& f) - { - Coordinate c; - for(std::size_t i = 0, n = size(); i < n; ++i) { - getAt(i, c); - f.filter(c); - setAt(c, i); + CoordinateSequenceIterator::type> + begin() const { + return CoordinateSequenceIterator::type>{m_seq}; + } + + CoordinateSequenceIterator::type> + end() const { + return CoordinateSequenceIterator::type>{m_seq, m_seq->getSize()}; } + + CoordinateSequenceIterator::type> + cbegin() const { + return CoordinateSequenceIterator::type>{m_seq}; + } + + CoordinateSequenceIterator::type> + cend() const { + return CoordinateSequenceIterator::type>{m_seq, m_seq->getSize()}; + } + + private: + SequenceType* m_seq; + }; + + template + Coordinates::type> items() const { + return Coordinates::type>(this); + } + + template + Coordinates items() { + return Coordinates(this); + } + + + /// @} + + double* data() { + return m_vect.data(); + } + + const double* data() const { + return m_vect.data(); + } + +private: + std::vector m_vect; // Vector to store values + + uint8_t m_stride; // Stride of stored values, corresponding to underlying type + + mutable bool m_hasdim; // Has the dimension of this sequence been determined? Or was it created with no + // explicit dimensionality, and we're waiting for getDimension() to be called + // after some coordinates have been added? + mutable bool m_hasz; + bool m_hasm; + + void initialize(); + + template + void setAtImpl(const T2& c, std::size_t pos) { + auto& orig = getAt(pos); + orig = c; + } + + void make_space(std::size_t pos, std::size_t n) { + m_vect.insert(std::next(m_vect.begin(), static_cast(pos * stride())), + m_stride * n, + DoubleNotANumber); + } + + std::uint8_t stride() const { + return m_stride; } }; diff --git a/Sources/geos/include/geos/geom/CoordinateSequenceFactory.h b/Sources/geos/include/geos/geom/CoordinateSequenceFactory.h deleted file mode 100644 index e2a10a2..0000000 --- a/Sources/geos/include/geos/geom/CoordinateSequenceFactory.h +++ /dev/null @@ -1,109 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2006 Refractions Research Inc. - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - ********************************************************************** - * - * Last port: geom/CoordinateSequenceFactory.java r591 (JTS-1.12) - * - **********************************************************************/ - -#pragma once - - -#include -#include -#include - -// Forward declarations -namespace geos { -namespace geom { -class CoordinateSequence; -class Coordinate; -} -} - -namespace geos { -namespace geom { // geos::geom - -/** - * \brief - * A factory to create concrete instances of {@link CoordinateSequence}s. - * - * Used to configure {@link GeometryFactory}s - * to provide specific kinds of CoordinateSequences. - */ -class GEOS_DLL CoordinateSequenceFactory { -public: - - /** \brief - * Returns an empty CoordinateSequence, the dimensions will be autodetected - * when it is populated. - */ - virtual std::unique_ptr create() const = 0; - - /** \brief - * Returns a CoordinateSequence based on the given array. - * - * Whether the array is copied or simply referenced - * is implementation-dependent. - * For this reason caller does give up ownership of it. - * Implementations that will not copy it will need take care - * of deleting it. - * - * This method must handle null arguments by creating - * an empty sequence. - * - * @param coordinates the coordinates - * @param dimension 0, 2 or 3 with 0 indicating unknown at this time. - */ - virtual std::unique_ptr create( - std::vector* coordinates, - std::size_t dimension = 0) const = 0; - - /** \brief - * Returns a CoordinateSequence based on the given array. - * - * @param coordinates the coordinates - * @param dimension 0, 2 or 3 with 0 indicating unknown at this time. - */ - virtual std::unique_ptr create( - std::vector && coordinates, - std::size_t dimension = 0) const = 0; - - /** \brief - * Creates a CoordinateSequence of the specified size and dimension. - * - * For this to be useful, the CoordinateSequence implementation must - * be mutable. - * - * @param size the number of coordinates in the sequence - * @param dimension the dimension of the coordinates in the sequence - * (0=unknown, 2, or 3 - ignored if not user specifiable) - */ - virtual std::unique_ptr create(std::size_t size, - std::size_t dimension = 0) const = 0; - - /** \brief - * Creates a CoordinateSequence which is a copy of the given one. - * - * This method must handle null arguments by creating an empty sequence. - * - * @param coordSeq the coordinate sequence to copy - */ - virtual std::unique_ptr create(const CoordinateSequence& coordSeq) const = 0; - - virtual ~CoordinateSequenceFactory() = default; -}; - -} // namespace geos::geom -} // namespace geos - diff --git a/Sources/geos/include/geos/geom/CoordinateSequenceIterator.h b/Sources/geos/include/geos/geom/CoordinateSequenceIterator.h new file mode 100644 index 0000000..7a9944a --- /dev/null +++ b/Sources/geos/include/geos/geom/CoordinateSequenceIterator.h @@ -0,0 +1,126 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + + +#pragma once + +#include +#include + +namespace geos { +namespace geom { + +template +class CoordinateSequenceIterator { + +public: + using iterator_category = std::random_access_iterator_tag; + using value_type = CoordinateType; + using reference = CoordinateType&; + using pointer = CoordinateType; + using difference_type = std::ptrdiff_t; + +private: + SequenceType* m_seq; + difference_type m_pos; + +public: + CoordinateSequenceIterator(SequenceType* seq) : m_seq(seq), m_pos(0) {} + + CoordinateSequenceIterator(SequenceType* seq, std::size_t size) : m_seq(seq), m_pos(static_cast(size)) {} + + reference operator*() const { + return m_seq->template getAt(static_cast(m_pos)); + } + + pointer operator->() const { + return &m_seq->template getAt(static_cast(m_pos)); + } + + CoordinateSequenceIterator& operator++() { + m_pos++; + return *this; + } + + CoordinateSequenceIterator operator++(int) { + CoordinateSequenceIterator ret = *this; + m_pos++; + return ret; + } + + CoordinateSequenceIterator& operator--() { + m_pos--; + return *this; + } + + CoordinateSequenceIterator operator--(int) { + CoordinateSequenceIterator ret = *this; + m_pos--; + return ret; + } + + difference_type operator-(const CoordinateSequenceIterator& other) const { + return this->m_pos - other.m_pos; + } + + CoordinateSequenceIterator operator+(difference_type n) const { + return CoordinateSequenceIterator(m_seq, static_cast(m_pos + n)); + } + + CoordinateSequenceIterator operator+=(difference_type n) { + this->m_pos += n; + return *this; + } + + CoordinateSequenceIterator operator-(difference_type n) const { + return CoordinateSequenceIterator(m_seq, static_cast(m_pos - n)); + } + + CoordinateSequenceIterator operator-=(difference_type n) { + this->m_pos -= n; + return *this; + } + + CoordinateType& operator[](difference_type n) const { + return *(*this + n); + } + + bool operator==(const CoordinateSequenceIterator& other) const { + return this->m_pos == other.m_pos; + } + + bool operator!=(const CoordinateSequenceIterator& other) const { + return !(*this == other); + } + + bool operator<(const CoordinateSequenceIterator& other) const { + return this->m_pos < other.m_pos; + } + + bool operator<=(const CoordinateSequenceIterator& other) const { + return this->m_pos <= other.m_pos; + } + + bool operator>(const CoordinateSequenceIterator& other) const { + return this->m_pos > other.m_pos; + } + + bool operator>=(const CoordinateSequenceIterator& other) const { + return this->m_pos >= other.m_pos; + } + +}; + +} +} diff --git a/Sources/geos/include/geos/geom/CoordinateSequences.h b/Sources/geos/include/geos/geom/CoordinateSequences.h new file mode 100644 index 0000000..93595be --- /dev/null +++ b/Sources/geos/include/geos/geom/CoordinateSequences.h @@ -0,0 +1,76 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +namespace geos { +namespace geom { + +/// +/// \brief The CoordinateSequences class provides utility methods to operate +/// on CoordinateSequences. Methods that do not benefit from access to the +/// CoordinateSequence internals can be placed here. +/// +class CoordinateSequences { + +private: + template + static constexpr int + type_pair(const CoordinateType& typ1, const CoordinateType& typ2) { + return (static_cast(typ1) << 4) | static_cast(typ2); + } + +public: + /// + /// \brief binaryDispatch calls a functor template, explicitly providing the backing types of two CoordinateSequences. The + /// CoordinateSequences are not provided to the functor as arguments but can be provided along with any other arguments + /// through the `args` argument. + template + static void binaryDispatch(const CoordinateSequence& seq1, const CoordinateSequence& seq2, F& fun, Args... args) + { + using CoordinateXYZ = Coordinate; + + auto typ1 = seq1.getCoordinateType(); + auto typ2 = seq2.getCoordinateType(); + + switch(type_pair(typ1, typ2)) { + case type_pair(CoordinateType::XY, CoordinateType::XY): fun.template operator()(args...); break; + case type_pair(CoordinateType::XY, CoordinateType::XYZ): fun.template operator()(args...); break; + case type_pair(CoordinateType::XY, CoordinateType::XYM): fun.template operator()(args...); break; + case type_pair(CoordinateType::XY, CoordinateType::XYZM): fun.template operator()(args...); break; + + case type_pair(CoordinateType::XYZ, CoordinateType::XY): fun.template operator()(args...); break; + case type_pair(CoordinateType::XYZ, CoordinateType::XYZ): fun.template operator()(args...); break; + case type_pair(CoordinateType::XYZ, CoordinateType::XYM): fun.template operator()(args...); break; + case type_pair(CoordinateType::XYZ, CoordinateType::XYZM): fun.template operator()(args...); break; + + case type_pair(CoordinateType::XYM, CoordinateType::XY): fun.template operator()(args...); break; + case type_pair(CoordinateType::XYM, CoordinateType::XYZ): fun.template operator()(args...); break; + case type_pair(CoordinateType::XYM, CoordinateType::XYM): fun.template operator()(args...); break; + case type_pair(CoordinateType::XYM, CoordinateType::XYZM): fun.template operator()(args...); break; + + case type_pair(CoordinateType::XYZM, CoordinateType::XY): fun.template operator()(args...); break; + case type_pair(CoordinateType::XYZM, CoordinateType::XYZ): fun.template operator()(args...); break; + case type_pair(CoordinateType::XYZM, CoordinateType::XYM): fun.template operator()(args...); break; + case type_pair(CoordinateType::XYZM, CoordinateType::XYZM): fun.template operator()(args...); break; + } + } + +}; + +} +} diff --git a/Sources/geos/include/geos/geom/Curve.h b/Sources/geos/include/geos/geom/Curve.h new file mode 100644 index 0000000..352dff6 --- /dev/null +++ b/Sources/geos/include/geos/geom/Curve.h @@ -0,0 +1,71 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +namespace geos { +namespace geom { + +class SimpleCurve; + +class GEOS_DLL Curve : public Geometry { + +public: + using Geometry::apply_ro; + using Geometry::apply_rw; + + void apply_ro(GeometryComponentFilter* filter) const override; + + void apply_ro(GeometryFilter* filter) const override; + + void apply_rw(GeometryComponentFilter* filter) override; + + void apply_rw(GeometryFilter* filter) override; + + /** + * \brief + * Returns Dimension::False for a closed Curve, + * 0 otherwise (Curve boundary is a MultiPoint) + */ + int + getBoundaryDimension() const override + { + return isClosed() ? Dimension::False : 0; + } + + /// Returns line dimension (1) + Dimension::DimensionType getDimension() const override + { + return Dimension::L; // line + } + + /// Returns true if the first and last coordinate in the Curve are the same + virtual bool isClosed() const = 0; + + /// Returns true if the curve is closed and simple + bool isRing() const; + + virtual std::size_t getNumCurves() const = 0; + + virtual const SimpleCurve* getCurveN(std::size_t) const = 0; + +protected: + Curve(const GeometryFactory& factory) : Geometry(&factory) {} + +}; + +} +} diff --git a/Sources/geos/include/geos/geom/CurvePolygon.h b/Sources/geos/include/geos/geom/CurvePolygon.h new file mode 100644 index 0000000..1a12b48 --- /dev/null +++ b/Sources/geos/include/geos/geom/CurvePolygon.h @@ -0,0 +1,58 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +namespace geos { +namespace geom { + +class GEOS_DLL CurvePolygon : public SurfaceImpl { + friend class GeometryFactory; + +public: + ~CurvePolygon() override = default; + + double getArea() const override; + + std::unique_ptr getBoundary() const override; + + std::unique_ptr getCoordinates() const override; + + std::string getGeometryType() const override; + + GeometryTypeId getGeometryTypeId() const override; + + bool hasCurvedComponents() const override; + + void normalize() override; + +protected: + using SurfaceImpl::SurfaceImpl; + + Geometry* cloneImpl() const override; + + int + getSortIndex() const override + { + return SORTINDEX_CURVEPOLYGON; + } + + Geometry* reverseImpl() const override; +}; + + +} +} diff --git a/Sources/geos/include/geos/geom/DefaultCoordinateSequenceFactory.h b/Sources/geos/include/geos/geom/DefaultCoordinateSequenceFactory.h deleted file mode 100644 index 3c9d361..0000000 --- a/Sources/geos/include/geos/geom/DefaultCoordinateSequenceFactory.h +++ /dev/null @@ -1,64 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2019 Daniel Baston - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - **********************************************************************/ - -#pragma once - -#include -#include -#include - -namespace geos { -namespace geom { - -class GEOS_DLL DefaultCoordinateSequenceFactory : public CoordinateSequenceFactory { -public: - - std::unique_ptr create() const final override { - return detail::make_unique(); - } - - std::unique_ptr create(std::vector *coords, std::size_t dims = 0) const final override { - return detail::make_unique(coords, dims); - } - - std::unique_ptr create(std::vector &&coords, std::size_t dims = 0) const final override { - return detail::make_unique(std::move(coords), dims); - } - - std::unique_ptr create(std::size_t size, std::size_t dims = 0) const final override { - switch(size) { - case 5: return detail::make_unique>(dims); - case 4: return detail::make_unique>(dims); - case 3: return detail::make_unique>(dims); - case 2: return detail::make_unique>(dims); - case 1: return detail::make_unique>(dims); - default: - return detail::make_unique(size, dims); - } - } - - std::unique_ptr create(const CoordinateSequence &coordSeq) const final override { - auto cs = create(coordSeq.size(), coordSeq.getDimension()); - for (std::size_t i = 0; i < cs->size(); i++) { - cs->setAt(coordSeq[i], i); - } - return cs; - } - - static const CoordinateSequenceFactory *instance(); -}; - -} -} - diff --git a/Sources/geos/include/geos/geom/Envelope.h b/Sources/geos/include/geos/geom/Envelope.h index c4c192d..823a0dc 100644 --- a/Sources/geos/include/geos/geom/Envelope.h +++ b/Sources/geos/include/geos/geom/Envelope.h @@ -48,7 +48,8 @@ class Coordinate; * It is often used to represent the bounding box of a Geometry, * e.g. the minimum and maximum x and y values of the Coordinates. * - * Note that Envelopes support infinite or half-infinite regions, by using + * Envelopes allow null values, which are represented with NaN values for ordinates. + * Envelopes support infinite or half-infinite regions, by using * the values of `Double_POSITIVE_INFINITY` and `Double_NEGATIVE_INFINITY`. * * When Envelope objects are created or initialized, the supplies extent @@ -92,7 +93,7 @@ class GEOS_DLL Envelope { * @param p1 the first Coordinate * @param p2 the second Coordinate */ - Envelope(const Coordinate& p1, const Coordinate& p2) + Envelope(const CoordinateXY& p1, const CoordinateXY& p2) { init(p1, p2); } @@ -102,9 +103,12 @@ class GEOS_DLL Envelope { * * @param p the Coordinate */ - explicit Envelope(const Coordinate& p) + explicit Envelope(const CoordinateXY& p) + : minx(p.x) + , maxx(p.x) + , miny(p.y) + , maxy(p.y) { - init(p); } /** \brief @@ -122,8 +126,8 @@ class GEOS_DLL Envelope { * @param q the point to test for intersection * @return `true` if q intersects the envelope p1-p2 */ - static bool intersects(const Coordinate& p1, const Coordinate& p2, - const Coordinate& q); + static bool intersects(const CoordinateXY& p1, const CoordinateXY& p2, + const CoordinateXY& q); /** \brief * Test the envelope defined by `p1-p2` for intersection @@ -137,8 +141,8 @@ class GEOS_DLL Envelope { * @return `true` if Q intersects P */ static bool intersects( - const Coordinate& p1, const Coordinate& p2, - const Coordinate& q1, const Coordinate& q2) + const CoordinateXY& p1, const CoordinateXY& p2, + const CoordinateXY& q1, const CoordinateXY& q2) { double minq = std::min(q1.x, q2.x); double maxq = std::max(q1.x, q2.x); @@ -171,7 +175,7 @@ class GEOS_DLL Envelope { * @param b another point * @return `true` if the extents intersect */ - bool intersects(const Coordinate& a, const Coordinate& b) const; + bool intersects(const CoordinateXY& a, const CoordinateXY& b) const; /** \brief * Initialize to a null Envelope. @@ -215,7 +219,7 @@ class GEOS_DLL Envelope { * @param p1 the first Coordinate * @param p2 the second Coordinate */ - void init(const Coordinate& p1, const Coordinate& p2) + void init(const CoordinateXY& p1, const CoordinateXY& p2) { init(p1.x, p2.x, p1.y, p2.y); }; @@ -225,7 +229,7 @@ class GEOS_DLL Envelope { * * @param p the Coordinate */ - void init(const Coordinate& p) + void init(const CoordinateXY& p) { init(p.x, p.x, p.y, p.y); }; @@ -289,8 +293,17 @@ class GEOS_DLL Envelope { } /** \brief - * Returns the Envelope maximum y-value. `min y > max y` indicates - * that this is a null Envelope. + * Returns true if this Envelope covers a finite region + */ + bool + isFinite() const + { + return std::isfinite(getArea()); + } + + /** \brief + * Returns the Envelope maximum y-value. + * Null envelopes do not have maximum values. */ double getMaxY() const { @@ -299,8 +312,8 @@ class GEOS_DLL Envelope { }; /** \brief - * Returns the Envelope maximum x-value. `min x > max x` indicates - * that this is a null Envelope. + * Returns the Envelope maximum x-value. + * Null envelopes do not have maximum values. */ double getMaxX() const { @@ -309,8 +322,8 @@ class GEOS_DLL Envelope { }; /** \brief - * Returns the Envelope minimum y-value. `min y > max y` indicates - * that this is a null Envelope. + * Returns the Envelope minimum y-value. + * Null envelopes do not have maximum values. */ double getMinY() const { @@ -319,8 +332,8 @@ class GEOS_DLL Envelope { }; /** \brief - * Returns the Envelope minimum x-value. `min x > max x` indicates - * that this is a null Envelope. + * Returns the Envelope minimum x-value. + * Null envelopes do not have maximum values. */ double getMinX() const { @@ -350,7 +363,7 @@ class GEOS_DLL Envelope { * @param centre The coordinate to write results into * @return `false` if the center could not be found (null envelope). */ - bool centre(Coordinate& centre) const; + bool centre(CoordinateXY& centre) const; /** \brief * Computes the intersection of two [Envelopes](@ref Envelope). @@ -359,7 +372,7 @@ class GEOS_DLL Envelope { * @param result the envelope representing the intersection of * the envelopes (this will be the null envelope * if either argument is null, or they do not intersect) - * @return false if not intersection is found + * @return false if no intersection is found */ bool intersection(const Envelope& env, Envelope& result) const; @@ -399,7 +412,7 @@ class GEOS_DLL Envelope { * * @param p the Coordinate to include */ - void expandToInclude(const Coordinate& p) + void expandToInclude(const CoordinateXY& p) { expandToInclude(p.x, p.y); }; @@ -506,7 +519,7 @@ class GEOS_DLL Envelope { * of this Envelope. */ bool - contains(const Coordinate& p) const + contains(const CoordinateXY& p) const { return covers(p.x, p.y); } @@ -533,7 +546,7 @@ class GEOS_DLL Envelope { * @param other the Coordinate to be tested * @return true if the point intersects this Envelope */ - bool intersects(const Coordinate& other) const + bool intersects(const CoordinateXY& other) const { return (std::islessequal(other.x, maxx) && std::isgreaterequal(other.x, minx) && std::islessequal(other.y, maxy) && std::isgreaterequal(other.y, miny)); @@ -597,7 +610,12 @@ class GEOS_DLL Envelope { * @param y the y-coordinate of the point which this Envelope is being checked for containing * @return `true` if `(x, y)` lies in the interior or on the boundary of this Envelope. */ - bool covers(double x, double y) const; + bool covers(double x, double y) const { + return std::isgreaterequal(x, minx) && + std::islessequal(x, maxx) && + std::isgreaterequal(y, miny) && + std::islessequal(y, maxy); + } /** \brief * Tests if the given point lies in or on the envelope. @@ -605,7 +623,7 @@ class GEOS_DLL Envelope { * @param p the point which this Envelope is being checked for containing * @return `true` if the point lies in the interior or on the boundary of this Envelope. */ - bool covers(const Coordinate* p) const + bool covers(const CoordinateXY* p) const { return covers(p->x, p->y); } @@ -658,6 +676,16 @@ class GEOS_DLL Envelope { return std::sqrt(distanceSquared(env)); } + /** \brief + * Computes the maximum distance between points in this and another Envelope. + */ + double maxDistance(const Envelope& other) const + { + Coordinate p(std::min(minx, other.minx), std::min(miny, other.miny)); + Coordinate q(std::max(maxx, other.maxx), std::max(maxy, other.maxy)); + return p.distance(q); + } + /** \brief * Computes the square of the distance between this and another Envelope. * @@ -686,9 +714,9 @@ class GEOS_DLL Envelope { * @param p1 second coordinate defining an envelope. */ static double distanceToCoordinate( - const Coordinate& c, - const Coordinate& p0, - const Coordinate& p1) + const CoordinateXY& c, + const CoordinateXY& p0, + const CoordinateXY& p1) { return std::sqrt(distanceSquaredToCoordinate(c, p0, p1)); }; @@ -703,9 +731,9 @@ class GEOS_DLL Envelope { * @param p1 second coordinate defining an envelope. */ static double distanceSquaredToCoordinate( - const Coordinate& c, - const Coordinate& p0, - const Coordinate& p1) + const CoordinateXY& c, + const CoordinateXY& p0, + const CoordinateXY& p1) { double xa = c.x - p0.x; double xb = c.x - p1.x; @@ -719,7 +747,26 @@ class GEOS_DLL Envelope { return dx*dx + dy*dy; } - std::size_t hashCode() const; + std::size_t hashCode() const + { + auto hash = std::hash{}; + + //Algorithm from Effective Java by Joshua Bloch [Jon Aquino] + std::size_t result = 17; + result = 37 * result + hash(minx); + result = 37 * result + hash(maxx); + result = 37 * result + hash(miny); + result = 37 * result + hash(maxy); + return result; + } + + struct GEOS_DLL HashCode + { + std::size_t operator()(const Envelope& e) const + { + return e.hashCode(); + }; + }; /// Checks if two Envelopes are equal (2D only check) // GEOS_DLL bool operator==(const Envelope& a, const Envelope& b); diff --git a/Sources/geos/include/geos/geom/FixedSizeCoordinateSequence.h b/Sources/geos/include/geos/geom/FixedSizeCoordinateSequence.h deleted file mode 100644 index 190852a..0000000 --- a/Sources/geos/include/geos/geom/FixedSizeCoordinateSequence.h +++ /dev/null @@ -1,132 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2019 Daniel Baston - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - **********************************************************************/ - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace geos { -namespace geom { - - template - class FixedSizeCoordinateSequence : public CoordinateSequence { - public: - explicit FixedSizeCoordinateSequence(std::size_t dimension_in = 0) : dimension(dimension_in) {} - - std::unique_ptr clone() const final override { - auto seq = detail::make_unique>(dimension); - seq->m_data = m_data; - return RETURN_UNIQUE_PTR(seq); - } - - const Coordinate& getAt(std::size_t i) const final override { - return m_data[i]; - } - - void getAt(std::size_t i, Coordinate& c) const final override { - c = m_data[i]; - } - - std::size_t getSize() const final override { - return N; - } - - bool isEmpty() const final override { - return N == 0; - } - - void setAt(const Coordinate & c, std::size_t pos) final override { - m_data[pos] = c; - } - - void setOrdinate(std::size_t index, std::size_t ordinateIndex, double value) final override - { - switch(ordinateIndex) { - case CoordinateSequence::X: - m_data[index].x = value; - break; - case CoordinateSequence::Y: - m_data[index].y = value; - break; - case CoordinateSequence::Z: - m_data[index].z = value; - break; - default: { - std::stringstream ss; - ss << "Unknown ordinate index " << ordinateIndex; - throw geos::util::IllegalArgumentException(ss.str()); - break; - } - } - } - - std::size_t getDimension() const final override { - if(dimension != 0) { - return dimension; - } - - if(isEmpty()) { - return 3; - } - - if(std::isnan(m_data[0].z)) { - dimension = 2; - } - else { - dimension = 3; - } - - return dimension; - } - - void toVector(std::vector & out) const final override { - out.insert(out.end(), m_data.begin(), m_data.end()); - } - - void setPoints(const std::vector & v) final override { - assert(v.size() == N); - if (N > 0) { - std::copy(v.begin(), v.end(), m_data.begin()); - } - - } - - void apply_ro(CoordinateFilter* filter) const final override { - std::for_each(m_data.begin(), m_data.end(), - [&filter](const Coordinate & c) { filter->filter_ro(&c); }); - } - - void apply_rw(const CoordinateFilter* filter) final override { - std::for_each(m_data.begin(), m_data.end(), - [&filter](Coordinate &c) { filter->filter_rw(&c); }); - dimension = 0; // re-check (see http://trac.osgeo.org/geos/ticket/435) - } - - private: - std::array m_data; - mutable std::size_t dimension; - }; - -} -} diff --git a/Sources/geos/include/geos/geom/Geometry.h b/Sources/geos/include/geos/geom/Geometry.h index 807ecbb..c2378f4 100644 --- a/Sources/geos/include/geos/geom/Geometry.h +++ b/Sources/geos/include/geos/geom/Geometry.h @@ -34,6 +34,7 @@ #include #include // for Dimension::DimensionType #include // for inheritance +#include // to materialize CoordinateSequence #include #include @@ -66,11 +67,11 @@ class Unload; } // namespace geos.io } -namespace geos { +namespace geos { // geos namespace geom { // geos::geom /// Geometry types -enum GeometryTypeId { +enum GeometryTypeId : int { /// a point GEOS_POINT, /// a linestring @@ -86,7 +87,12 @@ enum GeometryTypeId { /// a collection of polygons GEOS_MULTIPOLYGON, /// a collection of heterogeneus geometries - GEOS_GEOMETRYCOLLECTION + GEOS_GEOMETRYCOLLECTION, + GEOS_CIRCULARSTRING, + GEOS_COMPOUNDCURVE, + GEOS_CURVEPOLYGON, + GEOS_MULTICURVE, + GEOS_MULTISURFACE, }; enum GeometrySortIndex { @@ -97,7 +103,12 @@ enum GeometrySortIndex { SORTINDEX_MULTILINESTRING = 4, SORTINDEX_POLYGON = 5, SORTINDEX_MULTIPOLYGON = 6, - SORTINDEX_GEOMETRYCOLLECTION = 7 + SORTINDEX_GEOMETRYCOLLECTION = 7, + SORTINDEX_CIRCULARSTRING = 8, + SORTINDEX_COMPOUNDCURVE = 9, + SORTINDEX_CURVEPOLYGON = 10, + SORTINDEX_MULTICURVE = 11, + SORTINDEX_MULTISURFACE = 12, }; /** @@ -281,7 +292,7 @@ class GEOS_DLL Geometry { const PrecisionModel* getPrecisionModel() const; /// Returns a vertex of this Geometry, or NULL if this is the empty geometry. - virtual const Coordinate* getCoordinate() const = 0; //Abstract + virtual const CoordinateXY* getCoordinate() const = 0; //Abstract /** * \brief @@ -299,11 +310,19 @@ class GEOS_DLL Geometry { /// Return a string representation of this Geometry type virtual std::string getGeometryType() const = 0; //Abstract + /// Returns whether the Geometry contains curved components + virtual bool hasCurvedComponents() const; + /// Return an integer representation of this Geometry type virtual GeometryTypeId getGeometryTypeId() const = 0; //Abstract - /// Returns the number of geometries in this collection - /// (or 1 if this is not a collection) + /** + * \brief Returns the number of geometries in this collection, + * or 1 if this is not a collection. + * + * Empty collection or multi-geometry types return 0, + * and empty simple geometry types return 1. + */ virtual std::size_t getNumGeometries() const { @@ -342,6 +361,11 @@ class GEOS_DLL Geometry { /// Returns the dimension of this Geometry (0=point, 1=line, 2=surface) virtual Dimension::DimensionType getDimension() const = 0; //Abstract + /// Checks whether any component of this geometry has dimension d + virtual bool hasDimension(Dimension::DimensionType d) const { + return getDimension() == d; + } + /// Checks whether this Geometry consists only of components having dimension d. virtual bool isDimensionStrict(Dimension::DimensionType d) const { return d == getDimension(); @@ -359,6 +383,9 @@ class GEOS_DLL Geometry { return isDimensionStrict(Dimension::A); } + bool isMixedDimension() const; + bool isMixedDimension(Dimension::DimensionType* baseDim) const; + bool isCollection() const { int t = getGeometryTypeId(); return t == GEOS_GEOMETRYCOLLECTION || @@ -367,9 +394,22 @@ class GEOS_DLL Geometry { t == GEOS_MULTIPOLYGON; } - /// Returns the coordinate dimension of this Geometry (2=XY, 3=XYZ, 4=XYZM in future). + static GeometryTypeId multiTypeId(GeometryTypeId typeId) { + switch (typeId) { + case GEOS_POINT: return GEOS_MULTIPOINT; + case GEOS_LINESTRING: return GEOS_MULTILINESTRING; + case GEOS_POLYGON: return GEOS_MULTIPOLYGON; + default: return typeId; + } + } + + /// Returns the coordinate dimension of this Geometry (2=XY, 3=XYZ or XYM, 4=XYZM). virtual uint8_t getCoordinateDimension() const = 0; //Abstract + virtual bool hasZ() const = 0; + + virtual bool hasM() const = 0; + /** * \brief * Returns the boundary, or an empty geometry of appropriate @@ -398,7 +438,7 @@ class GEOS_DLL Geometry { * Returns the minimum and maximum x and y values in this Geometry, * or a null Envelope if this Geometry is empty. */ - virtual const Envelope* getEnvelopeInternal() const; + virtual const Envelope* getEnvelopeInternal() const = 0; /** * Tests whether this geometry is disjoint from the specified geometry. @@ -572,11 +612,7 @@ class GEOS_DLL Geometry { * @see Geometry#within * @see Geometry#covers */ - bool - coveredBy(const Geometry* g) const - { - return g->covers(this); - } + bool coveredBy(const Geometry* g) const; /// Returns the Well-known Text representation of this Geometry. @@ -719,11 +755,19 @@ class GEOS_DLL Geometry { /** \brief * Returns true iff the two Geometrys are of the same type and their - * vertices corresponding by index are equal up to a specified tolerance. + * vertices corresponding by index are equal up to a specified distance + * tolerance. Geometries are not required to have the same dimemsion; + * any Z/M values are ignored. */ virtual bool equalsExact(const Geometry* other, double tolerance = 0) const = 0; // Abstract + /** \brief + * Returns true if the two geometries are of the same type and their + * vertices corresponding by index are equal in all dimensions. + */ + virtual bool equalsIdentical(const Geometry* other) const = 0; + virtual void apply_rw(const CoordinateFilter* filter) = 0; //Abstract virtual void apply_ro(CoordinateFilter* filter) const = 0; //Abstract virtual void apply_rw(GeometryFilter* filter); @@ -777,6 +821,12 @@ class GEOS_DLL Geometry { /// Comparator for sorting geometry virtual int compareTo(const Geometry* geom) const; + /// Returns the area of this Geometry. + virtual double getArea() const; + + /// Returns the length of this Geometry. + virtual double getLength() const; + /** Returns the minimum distance between this Geometry and the Geometry g * * @param g the Geometry to calculate distance to @@ -784,11 +834,6 @@ class GEOS_DLL Geometry { */ virtual double distance(const Geometry* g) const; - /// Returns the area of this Geometry. - virtual double getArea() const; - - /// Returns the length of this Geometry. - virtual double getLength() const; /** \brief * Tests whether the distance from this Geometry to another @@ -819,7 +864,7 @@ class GEOS_DLL Geometry { // /// Returns false if centroid cannot be computed (EMPTY geometry) /// - virtual bool getCentroid(Coordinate& ret) const; + virtual bool getCentroid(CoordinateXY& ret) const; /** \brief * Computes an interior point of this Geometry. @@ -845,13 +890,9 @@ class GEOS_DLL Geometry { * Notifies this Geometry that its Coordinates have been changed * by an external party. */ - void geometryChangedAction(); + virtual void geometryChangedAction() = 0; protected: - - /// The bounding box of this Geometry - mutable std::unique_ptr envelope; - /// Make a deep-copy of this Geometry virtual Geometry* cloneImpl() const = 0; @@ -885,23 +926,39 @@ class GEOS_DLL Geometry { virtual bool isEquivalentClass(const Geometry* other) const; static void checkNotGeometryCollection(const Geometry* g); - // throw(IllegalArgumentException *); - - //virtual void checkEqualSRID(Geometry *other); - - //virtual void checkEqualPrecisionModel(Geometry *other); - - virtual Envelope::Ptr computeEnvelopeInternal() const = 0; //Abstract virtual int compareToSameClass(const Geometry* geom) const = 0; //Abstract - int compare(std::vector a, std::vector b) const; + template + static int compare(const T& a, const T& b) + { + std::size_t i = 0; + std::size_t j = 0; + while(i < a.size() && j < b.size()) { + const auto& aGeom = *a[i]; + const auto& bGeom = *b[j]; + + int comparison = aGeom.compareTo(&bGeom); + if(comparison != 0) { + return comparison; + } + + i++; + j++; + } - int compare(std::vector a, std::vector b) const; + if(i < a.size()) { + return 1; + } - int compare(const std::vector> & a, const std::vector> & b) const; + if(j < b.size()) { + return -1; + } + + return 0; + } - bool equal(const Coordinate& a, const Coordinate& b, + bool equal(const CoordinateXY& a, const CoordinateXY& b, double tolerance) const; int SRID; @@ -928,6 +985,10 @@ class GEOS_DLL Geometry { return gv; } + static std::vector> toGeometryArray(std::vector> && v) { + return std::move(v); + } + protected: virtual int getSortIndex() const = 0; diff --git a/Sources/geos/include/geos/geom/GeometryCollection.h b/Sources/geos/include/geos/geom/GeometryCollection.h index b6cf4df..9437f6a 100644 --- a/Sources/geos/include/geos/geom/GeometryCollection.h +++ b/Sources/geos/include/geos/geom/GeometryCollection.h @@ -32,7 +32,6 @@ namespace geos { namespace geom { // geos::geom class Coordinate; -class CoordinateArraySequence; class CoordinateSequenceFilter; } } @@ -109,11 +108,17 @@ class GEOS_DLL GeometryCollection : public Geometry { */ Dimension::DimensionType getDimension() const override; + bool hasDimension(Dimension::DimensionType d) const override; + bool isDimensionStrict(Dimension::DimensionType d) const override; /// Returns coordinate dimension. uint8_t getCoordinateDimension() const override; + bool hasM() const override; + + bool hasZ() const override; + std::unique_ptr getBoundary() const override; /** @@ -132,6 +137,8 @@ class GEOS_DLL GeometryCollection : public Geometry { bool equalsExact(const Geometry* other, double tolerance = 0) const override; + bool equalsIdentical(const Geometry* other) const override; + void apply_ro(CoordinateFilter* filter) const override; void apply_rw(const CoordinateFilter* filter) override; @@ -150,7 +157,7 @@ class GEOS_DLL GeometryCollection : public Geometry { void normalize() override; - const Coordinate* getCoordinate() const override; + const CoordinateXY* getCoordinate() const override; /// Returns the total area of this collection double getArea() const override; @@ -182,9 +189,17 @@ class GEOS_DLL GeometryCollection : public Geometry { */ std::unique_ptr reverse() const { return std::unique_ptr(reverseImpl()); } + const Envelope* getEnvelopeInternal() const override { + if (envelope.isNull()) { + envelope = computeEnvelopeInternal(); + } + return &envelope; + } + protected: GeometryCollection(const GeometryCollection& gc); + GeometryCollection& operator=(const GeometryCollection& gc); /** \brief * Construct a GeometryCollection with the given GeometryFactory. @@ -201,17 +216,8 @@ class GEOS_DLL GeometryCollection : public Geometry { * Elements may be empty Geometrys, * but not nulls. * - * If construction succeed the created object will take - * ownership of newGeoms vector and elements. - * - * If construction fails "IllegalArgumentException *" - * is thrown and it is your responsibility to delete newGeoms - * vector and content. - * * @param newFactory the GeometryFactory used to create this geometry */ - GeometryCollection(std::vector* newGeoms, const GeometryFactory* newFactory); - GeometryCollection(std::vector> && newGeoms, const GeometryFactory& newFactory); /// Convenience constructor to build a GeometryCollection from vector of Geometry subclass pointers @@ -230,11 +236,19 @@ class GEOS_DLL GeometryCollection : public Geometry { }; std::vector> geometries; + mutable Envelope envelope; + + Envelope computeEnvelopeInternal() const; - Envelope::Ptr computeEnvelopeInternal() const override; + void geometryChangedAction() override { + envelope.setToNull(); + } int compareToSameClass(const Geometry* gc) const override; + bool hasCurvedComponents() const override; + + }; } // namespace geos::geom diff --git a/Sources/geos/include/geos/geom/GeometryFactory.h b/Sources/geos/include/geos/geom/GeometryFactory.h index 47b9b1a..10c1e86 100644 --- a/Sources/geos/include/geos/geom/GeometryFactory.h +++ b/Sources/geos/include/geos/geom/GeometryFactory.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -35,8 +36,9 @@ namespace geos { namespace geom { -class CoordinateSequenceFactory; class Coordinate; +class CircularString; +class CompoundCurve; class CoordinateSequence; class Envelope; class Geometry; @@ -44,9 +46,11 @@ class GeometryCollection; class LineString; class LinearRing; class MultiLineString; +class MultiCurve; class MultiPoint; class MultiPolygon; -class Polygon; +class MultiSurface; +class CurvePolygon; } } @@ -85,29 +89,6 @@ class GEOS_DLL GeometryFactory { */ static GeometryFactory::Ptr create(); - /** - * \brief - * Constructs a GeometryFactory that generates Geometries having - * the given PrecisionModel, spatial-reference ID, and - * CoordinateSequence implementation. - * - * NOTES: - * (1) the given PrecisionModel is COPIED - * (2) the CoordinateSequenceFactory is NOT COPIED - * and must be available for the whole lifetime - * of the GeometryFactory - */ - static GeometryFactory::Ptr create(const PrecisionModel* pm, int newSRID, - CoordinateSequenceFactory* nCoordinateSequenceFactory); - - /** - * \brief - * Constructs a GeometryFactory that generates Geometries having the - * given CoordinateSequence implementation, a double-precision floating - * PrecisionModel and a spatial-reference ID of 0. - */ - static GeometryFactory::Ptr create(CoordinateSequenceFactory* nCoordinateSequenceFactory); - /** * \brief * Constructs a GeometryFactory that generates Geometries having @@ -145,10 +126,10 @@ class GEOS_DLL GeometryFactory { static const GeometryFactory* getDefaultInstance(); -//Skipped a lot of list to array convertors +//Skipped a lot of list to array converters - Point* createPointFromInternalCoord(const Coordinate* coord, - const Geometry* exemplar) const; + static std::unique_ptr createPointFromInternalCoord(const Coordinate* coord, + const Geometry* exemplar); /// Converts an Envelope to a Geometry. /// @@ -166,26 +147,27 @@ class GEOS_DLL GeometryFactory { /// Creates an EMPTY Point std::unique_ptr createPoint(std::size_t coordinateDimension = 2) const; + std::unique_ptr createPoint(bool hasZ, bool hasM) const; /// Creates a Point using the given Coordinate - Point* createPoint(const Coordinate& coordinate) const; + std::unique_ptr createPoint(const Coordinate& coordinate) const; + std::unique_ptr createPoint(const CoordinateXY& coordinate) const; + std::unique_ptr createPoint(const CoordinateXYM& coordinate) const; + std::unique_ptr createPoint(const CoordinateXYZM& coordinate) const; /// Creates a Point taking ownership of the given CoordinateSequence - Point* createPoint(CoordinateSequence* coordinates) const; + std::unique_ptr createPoint(std::unique_ptr&& coordinates) const; /// Creates a Point with a deep-copy of the given CoordinateSequence. - Point* createPoint(const CoordinateSequence& coordinates) const; + std::unique_ptr createPoint(const CoordinateSequence& coordinates) const; /// Construct an EMPTY GeometryCollection std::unique_ptr createGeometryCollection() const; /// Construct the EMPTY Geometry - std::unique_ptr createEmptyGeometry() const; + std::unique_ptr createEmptyGeometry(GeometryTypeId type = GEOS_GEOMETRYCOLLECTION, bool hasZ=false, bool hasM=false) const; /// Construct a GeometryCollection taking ownership of given arguments - GeometryCollection* createGeometryCollection( - std::vector* newGeoms) const; - template std::unique_ptr createGeometryCollection( std::vector> && newGeoms) const { @@ -194,124 +176,161 @@ class GEOS_DLL GeometryFactory { } /// Constructs a GeometryCollection with a deep-copy of args - GeometryCollection* createGeometryCollection( + std::unique_ptr createGeometryCollection( const std::vector& newGeoms) const; /// Construct an EMPTY MultiLineString std::unique_ptr createMultiLineString() const; - /// Construct a MultiLineString taking ownership of given arguments - MultiLineString* createMultiLineString( - std::vector* newLines) const; - /// Construct a MultiLineString with a deep-copy of given arguments - MultiLineString* createMultiLineString( + std::unique_ptr createMultiLineString( const std::vector& fromLines) const; + /// Construct a MultiLineString taking ownership of given arguments std::unique_ptr createMultiLineString( std::vector> && fromLines) const; std::unique_ptr createMultiLineString( std::vector> && fromLines) const; + /// Construct an EMPTY MultiCurve + std::unique_ptr createMultiCurve() const; + + /// Construct a MultiCurve taking ownership of given arguments + std::unique_ptr createMultiCurve( + std::vector> && fromCurves) const; + + std::unique_ptr createMultiCurve( + std::vector> && fromCurves) const; + /// Construct an EMPTY MultiPolygon std::unique_ptr createMultiPolygon() const; - /// Construct a MultiPolygon taking ownership of given arguments - MultiPolygon* createMultiPolygon(std::vector* newPolys) const; - /// Construct a MultiPolygon with a deep-copy of given arguments - MultiPolygon* createMultiPolygon( + std::unique_ptr createMultiPolygon( const std::vector& fromPolys) const; + /// Construct a MultiPolygon taking ownership of given arguments std::unique_ptr createMultiPolygon( std::vector> && fromPolys) const; std::unique_ptr createMultiPolygon( std::vector> && fromPolys) const; + /// Construct an EMPTY MultiSurface + std::unique_ptr createMultiSurface() const; + + /// Construct a MultiSurface taking ownership of given arguments + std::unique_ptr createMultiSurface( + std::vector> && from) const; + + std::unique_ptr createMultiSurface( + std::vector> && from) const; + /// Construct an EMPTY LinearRing - std::unique_ptr createLinearRing() const; + std::unique_ptr createLinearRing(std::size_t coordinateDimension = 2) const; + std::unique_ptr createLinearRing(bool hasZ, bool hasM) const; /// Construct a LinearRing taking ownership of given arguments - LinearRing* createLinearRing(CoordinateSequence* newCoords) const; - std::unique_ptr createLinearRing( std::unique_ptr && newCoords) const; - std::unique_ptr createLinearRing( - std::vector && coordinates) const; - /// Construct a LinearRing with a deep-copy of given arguments - LinearRing* createLinearRing( + std::unique_ptr createLinearRing( const CoordinateSequence& coordinates) const; /// Constructs an EMPTY MultiPoint. std::unique_ptr createMultiPoint() const; - /// Construct a MultiPoint taking ownership of given arguments - MultiPoint* createMultiPoint(std::vector* newPoints) const; + template + std::unique_ptr createMultiPoint(const T& fromCoords) const + { + std::vector> pts; + pts.reserve(fromCoords.size()); + for (const auto& c : fromCoords) { + pts.emplace_back(createPoint(c)); + } - std::unique_ptr createMultiPoint(std::vector && newPoints) const; + return createMultiPoint(std::move(pts)); + } + /// Construct a MultiPoint taking ownership of given arguments std::unique_ptr createMultiPoint(std::vector> && newPoints) const; std::unique_ptr createMultiPoint(std::vector> && newPoints) const; /// Construct a MultiPoint with a deep-copy of given arguments - MultiPoint* createMultiPoint( + std::unique_ptr createMultiPoint( const std::vector& fromPoints) const; /// \brief /// Construct a MultiPoint containing a Point geometry /// for each Coordinate in the given list. - MultiPoint* createMultiPoint( + std::unique_ptr createMultiPoint( const CoordinateSequence& fromCoords) const; - /// \brief - /// Construct a MultiPoint containing a Point geometry - /// for each Coordinate in the given vector. - MultiPoint* createMultiPoint( - const std::vector& fromCoords) const; - /// Construct an EMPTY Polygon std::unique_ptr createPolygon(std::size_t coordinateDimension = 2) const; + std::unique_ptr createPolygon(bool hasZ, bool hasM) const; /// Construct a Polygon taking ownership of given arguments - Polygon* createPolygon(LinearRing* shell, - std::vector* holes) const; - std::unique_ptr createPolygon(std::unique_ptr && shell) const; std::unique_ptr createPolygon(std::unique_ptr && shell, std::vector> && holes) const; /// Construct a Polygon from a Coordinate vector, taking ownership of the vector - std::unique_ptr createPolygon(std::vector && coords) const; + std::unique_ptr createPolygon(CoordinateSequence && coords) const; /// Construct a Polygon with a deep-copy of given arguments Polygon* createPolygon(const LinearRing& shell, const std::vector& holes) const; + + /// Construct an EMPTY CurvePolygon + std::unique_ptr createCurvePolygon(bool hasZ, bool hasM) const; + + /// Construct a CurvePolygon taking ownership of given arguments + std::unique_ptr createCurvePolygon(std::unique_ptr&& shell) const; + + std::unique_ptr createCurvePolygon(std::unique_ptr&& shell, + std::vector> && holes) const; + /// Construct an EMPTY LineString std::unique_ptr createLineString(std::size_t coordinateDimension = 2) const; + std::unique_ptr createLineString(bool hasZ, bool hasM) const; /// Copy a LineString std::unique_ptr createLineString(const LineString& ls) const; /// Construct a LineString taking ownership of given argument - LineString* createLineString(CoordinateSequence* coordinates) const; - std::unique_ptr createLineString( std::unique_ptr && coordinates) const; + /// Construct a LineString with a deep-copy of given argument std::unique_ptr createLineString( - std::vector && coordinates) const; + const CoordinateSequence& coordinates) const; - /// Construct a LineString with a deep-copy of given argument - LineString* createLineString( + /// Construct an EMPTY CircularString + std::unique_ptr createCircularString(bool hasZ, bool hasM) const; + + /// Copy a CircularString + std::unique_ptr createCircularString(const CircularString& ls) const; + + /// Construct a CircularString taking ownership of given argument + std::unique_ptr createCircularString( + std::unique_ptr && coordinates) const; + + /// Construct a CircularString with a deep-copy of given argument + std::unique_ptr createCircularString( const CoordinateSequence& coordinates) const; + /// Construct an EMPTY CompoundCurve + std::unique_ptr createCompoundCurve() const; + + /// Construct a CompoundCurve taking ownership of given argument + std::unique_ptr createCompoundCurve(std::vector>&&) const; + /** * Creates an empty atomic geometry of the given dimension. * If passed a dimension of -1 will create an empty {@link GeometryCollection}. @@ -321,6 +340,15 @@ class GEOS_DLL GeometryFactory { */ std::unique_ptr createEmpty(int dimension) const; + /** + * Creates an empty atomic geometry of the given type. + * @param typeId the desired GeometryTypeId + * @return an empty atomic geometry of given dimension + */ + std::unique_ptr createEmpty(GeometryTypeId typeId) const; + + std::unique_ptr createMulti(std::unique_ptr && geom) const; + /** * Build an appropriate Geometry, MultiGeometry, or * GeometryCollection to contain the Geometrys in @@ -351,8 +379,6 @@ class GEOS_DLL GeometryFactory { * NOTE: the returned Geometry will take ownership of the * given vector AND its elements */ - Geometry* buildGeometry(std::vector* geoms) const; - std::unique_ptr buildGeometry(std::vector> && geoms) const; std::unique_ptr buildGeometry(std::vector> && geoms) const; @@ -363,7 +389,7 @@ class GEOS_DLL GeometryFactory { /// See buildGeometry(std::vector&) for semantics // - /// Will clone the geometries accessible trough the iterator. + /// Will clone the geometries accessible through the iterator. /// /// @tparam T an iterator yielding something which casts to const Geometry* /// @param from start iterator @@ -430,23 +456,15 @@ class GEOS_DLL GeometryFactory { * The difference is that this version will copy needed data * leaving ownership to the caller. */ - Geometry* buildGeometry(const std::vector& geoms) const; + std::unique_ptr buildGeometry(const std::vector& geoms) const; int getSRID() const { return SRID; }; - /// \brief - /// Returns the CoordinateSequenceFactory associated - /// with this GeometryFactory - const CoordinateSequenceFactory* getCoordinateSequenceFactory() const - { - return coordinateListFactory; - }; - /// Returns a clone of given Geometry. - Geometry* createGeometry(const Geometry* g) const; + std::unique_ptr createGeometry(const Geometry* g) const; /// Destroy a Geometry, or release it void destroyGeometry(Geometry* g) const; @@ -468,29 +486,6 @@ class GEOS_DLL GeometryFactory { */ GeometryFactory(); - /** - * \brief - * Constructs a GeometryFactory that generates Geometries having - * the given PrecisionModel, spatial-reference ID, and - * CoordinateSequence implementation. - * - * NOTES: - * (1) the given PrecisionModel is COPIED - * (2) the CoordinateSequenceFactory is NOT COPIED - * and must be available for the whole lifetime - * of the GeometryFactory - */ - GeometryFactory(const PrecisionModel* pm, int newSRID, - CoordinateSequenceFactory* nCoordinateSequenceFactory); - - /** - * \brief - * Constructs a GeometryFactory that generates Geometries having the - * given CoordinateSequence implementation, a double-precision floating - * PrecisionModel and a spatial-reference ID of 0. - */ - GeometryFactory(CoordinateSequenceFactory* nCoordinateSequenceFactory); - /** * \brief * Constructs a GeometryFactory that generates Geometries having @@ -526,7 +521,6 @@ class GEOS_DLL GeometryFactory { PrecisionModel precisionModel; int SRID; - const CoordinateSequenceFactory* coordinateListFactory; mutable int _refCount; bool _autoDestroy; diff --git a/Sources/geos/include/geos/geom/GeometryTypeName.h b/Sources/geos/include/geos/geom/GeometryTypeName.h new file mode 100644 index 0000000..9807e5a --- /dev/null +++ b/Sources/geos/include/geos/geom/GeometryTypeName.h @@ -0,0 +1,110 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +namespace geos { +namespace geom { + +class Curve; +class CurvePolygon; +class GeometryCollection; +class LineString; +class LinearRing; +class MultiCurve; +class MultiLineString; +class MultiPoint; +class MultiPolygon; +class MultiSurface; +class Point; +class Polygon; +class SimpleCurve; +class Surface; + +// These structures allow templates to have compile-time access to a type's human-readable name. +template +struct GeometryTypeName {}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "Curve"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "CurvePolygon"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "GeometryCollection"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "LineString"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "LinearRing"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "MultiCurve"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "MultiLineString"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "MultiPoint"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "MultiPolygon"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "MultiSurface"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "Point"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "Polygon"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "SimpleCurve"; +}; + +template<> +struct GeometryTypeName { + static constexpr const char* name = "Surface"; +}; + +} +} diff --git a/Sources/geos/include/geos/geom/HeuristicOverlay.h b/Sources/geos/include/geos/geom/HeuristicOverlay.h index b6146bb..bc1217a 100644 --- a/Sources/geos/include/geos/geom/HeuristicOverlay.h +++ b/Sources/geos/include/geos/geom/HeuristicOverlay.h @@ -20,16 +20,80 @@ #pragma once #include +#include +#include + + #include // for unique_ptr +#include -namespace geos { -namespace geom { // geos::geom +namespace geos { +namespace geom { class Geometry; +class GeometryFactory; +} +} + + +namespace geos { +namespace geom { // geos::geom std::unique_ptr GEOS_DLL HeuristicOverlay(const Geometry* g0, const Geometry* g1, int opCode); +class StructuredCollection { + +public: + + StructuredCollection(const Geometry* g) + : factory(g->getFactory()) + , pt_union(nullptr) + , line_union(nullptr) + , poly_union(nullptr) + { + readCollection(g); + unionByDimension(); + }; + + StructuredCollection() + : factory(nullptr) + , pt_union(nullptr) + , line_union(nullptr) + , poly_union(nullptr) + {}; + + void readCollection(const Geometry* g); + const Geometry* getPolyUnion() const { return poly_union.get(); } + const Geometry* getLineUnion() const { return line_union.get(); } + const Geometry* getPointUnion() const { return pt_union.get(); } + + std::unique_ptr doUnion(const StructuredCollection& a) const; + std::unique_ptr doIntersection(const StructuredCollection& a) const; + std::unique_ptr doSymDifference(const StructuredCollection& a) const; + std::unique_ptr doDifference(const StructuredCollection& a) const; + std::unique_ptr doUnaryUnion() const; + + static void toVector(const Geometry* g, std::vector& v); + void unionByDimension(void); + + +private: + + const GeometryFactory* factory; + std::vector pts; + std::vector lines; + std::vector polys; + std::unique_ptr pt_union; + std::unique_ptr line_union; + std::unique_ptr poly_union; + +}; + + + + + } // namespace geos::geom } // namespace geos diff --git a/Sources/geos/include/geos/geom/IntersectionMatrix.h b/Sources/geos/include/geos/geom/IntersectionMatrix.h index 76823ab..c7ad506 100644 --- a/Sources/geos/include/geos/geom/IntersectionMatrix.h +++ b/Sources/geos/include/geos/geom/IntersectionMatrix.h @@ -61,7 +61,7 @@ class GEOS_DLL IntersectionMatrix { IntersectionMatrix(); /** \brief - * Overriden constructor. + * Overridden constructor. * * Creates an IntersectionMatrix with the given dimension symbols. * @@ -226,7 +226,7 @@ class GEOS_DLL IntersectionMatrix { * * @return the dimension value at the given matrix position. */ - int get(geom::Location row, geom::Location column) const { + int get(Location row, Location column) const { return matrix[static_cast(row)][static_cast(column)]; } @@ -363,6 +363,7 @@ class GEOS_DLL IntersectionMatrix { */ std::string toString() const; + private: static const int firstDim; // = 3; diff --git a/Sources/geos/include/geos/geom/LineSegment.h b/Sources/geos/include/geos/geom/LineSegment.h index f9625f1..aea0fef 100644 --- a/Sources/geos/include/geos/geom/LineSegment.h +++ b/Sources/geos/include/geos/geom/LineSegment.h @@ -31,6 +31,7 @@ #include // for std::hash #include // for unique_ptr #include +#include // Forward declarations namespace geos { @@ -60,17 +61,10 @@ namespace geom { // geos::geom class GEOS_DLL LineSegment { public: + Coordinate p0; /// Segment start Coordinate p1; /// Segment end - friend std::ostream& operator<< (std::ostream& o, const LineSegment& l); - - /// Checks if two LineSegment are equal (2D only check) - friend bool operator==(const LineSegment& a, const LineSegment& b) - { - return a.p0 == b.p0 && a.p1 == b.p1; - }; - LineSegment(const Coordinate& c0, const Coordinate& c1) : p0(c0) , p1(c1) @@ -194,7 +188,21 @@ class GEOS_DLL LineSegment { return orientationIndex(*seg); }; - + /** + * Determines the orientation index of a Coordinate relative to this segment. + * The orientation index is as defined in Orientation::index(Coordinate, Coordinate, Coordinate). + * + * @param p the coordinate to compare + * + * @return 1 (LEFT) if "p" is to the left of this segment + * @return -1 (RIGHT) if "p" is to the right of this segment + * @return 0 (COLLINEAR) if "p" is collinear with this segment + * + */ + int orientationIndex(const CoordinateXY& p) const + { + return algorithm::Orientation::index(p0, p1, p); + } /** \brief * Determines the orientation index of a Coordinate @@ -238,17 +246,23 @@ class GEOS_DLL LineSegment { return std::atan2(p1.y - p0.y, p1.x - p0.x); }; - /// Computes the midpoint of the segment - // - /// @param ret will be set to the midpoint of the segment - /// - void midPoint(Coordinate& ret) const + /** \brief + * Computes the midpoint of the segment + * + * @return the midpoint of the segment + */ + CoordinateXY midPoint() const { - ret = Coordinate( - (p0.x + p1.x) / 2, - (p0.y + p1.y) / 2); + return midPoint(p0, p1); }; + static CoordinateXY midPoint(const CoordinateXY& pt0, const CoordinateXY& pt1) + { + return CoordinateXY( + (pt0.x + pt1.x) / 2, + (pt0.y + pt1.y) / 2); + } + /// Computes the distance between this line segment and another one. double distance(const LineSegment& ls) const { @@ -256,20 +270,40 @@ class GEOS_DLL LineSegment { }; /// Computes the distance between this line segment and a point. - double distance(const Coordinate& p) const + double distance(const CoordinateXY& p) const { return algorithm::Distance::pointToSegment(p, p0, p1); }; - /** \brief + /** * Computes the perpendicular distance between the (infinite) * line defined by this line segment and a point. + * If the segment has zero length this returns the distance between + * the segment and the point. + * + * @param p the point to compute the distance to + * @return the perpendicular distance between the line and point */ - double distancePerpendicular(const Coordinate& p) const + double distancePerpendicular(const CoordinateXY& p) const { + if (p0.equals2D(p1)) + return p0.distance(p); return algorithm::Distance::pointToLinePerpendicular(p, p0, p1); }; + /** + * Computes the oriented perpendicular distance between the (infinite) line + * defined by this line segment and a point. + * The oriented distance is positive if the point on the left of the line, + * and negative if it is on the right. + * If the segment has zero length this returns the distance between + * the segment and the point. + * + * @param p the point to compute the distance to + * @return the oriented perpendicular distance between the line and point + */ + double distancePerpendicularOriented(const CoordinateXY& p) const; + /** \brief * Computes the Coordinate that lies a given * fraction along the line defined by this segment. @@ -288,7 +322,8 @@ class GEOS_DLL LineSegment { { ret = Coordinate( p0.x + segmentLengthFraction * (p1.x - p0.x), - p0.y + segmentLengthFraction * (p1.y - p0.y)); + p0.y + segmentLengthFraction * (p1.y - p0.y), + p0.z + segmentLengthFraction * (p1.z - p0.z)); }; /** \brief @@ -352,7 +387,7 @@ class GEOS_DLL LineSegment { * @return the projection factor for the point * */ - double projectionFactor(const Coordinate& p) const; + double projectionFactor(const CoordinateXY& p) const; /** \brief * Computes the fraction of distance (in [0.0, 1.0]) @@ -369,7 +404,7 @@ class GEOS_DLL LineSegment { * @return the fraction along the line segment the projection * of the point occurs */ - double segmentFraction(const Coordinate& inputPt) const; + double segmentFraction(const CoordinateXY& inputPt) const; /** \brief * Compute the projection of a point onto the line determined @@ -381,6 +416,8 @@ class GEOS_DLL LineSegment { */ void project(const Coordinate& p, Coordinate& ret) const; + CoordinateXY project(const CoordinateXY& p) const; + /** \brief * Project a line segment onto this line segment and return the resulting * line segment. @@ -404,20 +441,7 @@ class GEOS_DLL LineSegment { /// @param ret the Coordinate to which the closest point on the line segment /// to the point p will be written /// - void closestPoint(const Coordinate& p, Coordinate& ret) const; - - /** \brief - * Compares this object with the specified object for order. - * - * Uses the standard lexicographic ordering for the points in the LineSegment. - * - * @param other the LineSegment with which this LineSegment - * is being compared - * @return a negative integer, zero, or a positive integer as this - * LineSegment is less than, equal to, or greater than the - * specified LineSegment - */ - int compareTo(const LineSegment& other) const; + void closestPoint(const CoordinateXY& p, CoordinateXY& ret) const; /** \brief * Returns true if other is @@ -484,22 +508,68 @@ class GEOS_DLL LineSegment { */ std::unique_ptr toGeometry(const GeometryFactory& gf) const; + + /** \brief + * Compares this object with the specified object for order. + * + * Uses the standard lexicographic ordering for the points in the LineSegment. + * + * @param other the LineSegment with which this LineSegment + * is being compared + * @return a negative integer, zero, or a positive integer as this + * LineSegment is less than, equal to, or greater than the + * specified LineSegment + */ + inline int compareTo(const LineSegment& other) const + { + int comp0 = p0.compareTo(other.p0); + if (comp0 != 0) { + return comp0; + } + return p1.compareTo(other.p1); + } + + std::ostream& operator<< (std::ostream& o); + + inline bool operator==(const LineSegment& rhs) const { + return compareTo(rhs) == 0; + }; + + inline bool operator<(const LineSegment& rhs) const { + return compareTo(rhs) < 0; + }; + + inline bool operator>(const LineSegment& rhs) const { + return compareTo(rhs) > 0; + }; + struct HashCode { - std::size_t operator()(const LineSegment & s) const { + inline std::size_t operator()(const LineSegment & s) const { std::size_t h = std::hash{}(s.p0.x); h ^= (std::hash{}(s.p0.y) << 1); h ^= (std::hash{}(s.p1.x) << 1); return h ^ (std::hash{}(s.p1.y) << 1); } + + inline std::size_t operator()(const LineSegment * s) const { + std::size_t h = std::hash{}(s->p0.x); + h ^= (std::hash{}(s->p0.y) << 1); + h ^= (std::hash{}(s->p1.x) << 1); + return h ^ (std::hash{}(s->p1.y) << 1); + } + }; + using UnorderedSet = std::unordered_set; + + private: - void project(double factor, Coordinate& ret) const; + void project(double factor, CoordinateXY& ret) const; }; -// std::ostream& operator<< (std::ostream& o, const LineSegment& l); +// std::ostream& operator<< (std::ostream& o, const LineSegment& l); } // namespace geos::geom diff --git a/Sources/geos/include/geos/geom/LineString.h b/Sources/geos/include/geos/geom/LineString.h index bb7add6..4789708 100644 --- a/Sources/geos/include/geos/geom/LineString.h +++ b/Sources/geos/include/geos/geom/LineString.h @@ -25,6 +25,7 @@ #include // for proper use of unique_ptr<> #include // for proper use of unique_ptr<> #include // for Dimension::DimensionType +#include #include #include @@ -39,7 +40,6 @@ namespace geos { namespace geom { class Coordinate; -class CoordinateArraySequence; class CoordinateSequenceFilter; } } @@ -63,7 +63,7 @@ namespace geom { // geos::geom * If these conditions are not met, the constructors throw * an {@link util::IllegalArgumentException}. */ -class GEOS_DLL LineString: public Geometry { +class GEOS_DLL LineString: public SimpleCurve { public: @@ -86,105 +86,16 @@ class GEOS_DLL LineString: public Geometry { return std::unique_ptr(cloneImpl()); } - std::unique_ptr getCoordinates() const override; - - /// Returns a read-only pointer to internal CoordinateSequence - const CoordinateSequence* getCoordinatesRO() const; - - virtual const Coordinate& getCoordinateN(std::size_t n) const; - - /** - * \brief - * Take ownership of the CoordinateSequence managed by this geometry. - * After releasing the coordinates, the geometry should be considered - * in a moved-from state and should not be accessed. - * @return this Geometry's CoordinateSequence. - */ - std::unique_ptr releaseCoordinates(); - - /// Returns line dimension (1) - Dimension::DimensionType getDimension() const override; - - /** - * \brief - * Returns Dimension::False for a closed LineString, - * 0 otherwise (LineString boundary is a MultiPoint) - */ - int getBoundaryDimension() const override; - - /// Returns coordinate dimension. - uint8_t getCoordinateDimension() const override; - - /** - * \brief - * Returns a MultiPoint. - * Empty for closed LineString, a Point for each vertex otherwise. - */ - std::unique_ptr getBoundary() const override; - - bool isEmpty() const override; - - std::size_t getNumPoints() const override; - - virtual std::unique_ptr getPointN(std::size_t n) const; - - /// \brief - /// Return the start point of the LineString - /// or NULL if this is an EMPTY LineString. - /// - virtual std::unique_ptr getStartPoint() const; - - /// \brief - /// Return the end point of the LineString - /// or NULL if this is an EMPTY LineString. - /// - virtual std::unique_ptr getEndPoint() const; - - virtual bool isClosed() const; - - virtual bool isRing() const; - std::string getGeometryType() const override; GeometryTypeId getGeometryTypeId() const override; - virtual bool isCoordinate(Coordinate& pt) const; - - bool equalsExact(const Geometry* other, double tolerance = 0) - const override; - - void apply_rw(const CoordinateFilter* filter) override; - - void apply_ro(CoordinateFilter* filter) const override; - - void apply_rw(GeometryFilter* filter) override; - - void apply_ro(GeometryFilter* filter) const override; - - void apply_rw(GeometryComponentFilter* filter) override; - - void apply_ro(GeometryComponentFilter* filter) const override; - - void apply_rw(CoordinateSequenceFilter& filter) override; - - void apply_ro(CoordinateSequenceFilter& filter) const override; - - /** \brief - * Normalizes a LineString. - * - * A normalized linestring - * has the first point which is not equal to its reflected point - * less than the reflected point. - */ - void normalize() override; - - //was protected - int compareToSameClass(const Geometry* ls) const override; - - const Coordinate* getCoordinate() const override; - double getLength() const override; + bool isCurved() const override { + return false; + } + /** * Creates a LineString whose coordinates are in the reverse * order of this object's @@ -200,35 +111,27 @@ class GEOS_DLL LineString: public Geometry { /// \brief /// Constructs a LineString taking ownership the /// given CoordinateSequence. - LineString(CoordinateSequence* pts, const GeometryFactory* newFactory); - - /// Hopefully cleaner version of the above LineString(CoordinateSequence::Ptr && pts, const GeometryFactory& newFactory); - LineString(std::vector && pts, - const GeometryFactory& newFactory); - LineString* cloneImpl() const override { return new LineString(*this); } LineString* reverseImpl() const override; - Envelope::Ptr computeEnvelopeInternal() const override; - - CoordinateSequence::Ptr points; - int getSortIndex() const override { return SORTINDEX_LINESTRING; }; + void geometryChangedAction() override + { + envelope = computeEnvelopeInternal(true); + } + private: void validateConstruction(); - void normalizeClosed(); - - }; struct GEOS_DLL LineStringLT { diff --git a/Sources/geos/include/geos/geom/LinearRing.h b/Sources/geos/include/geos/geom/LinearRing.h index 68af32c..b04b3bd 100644 --- a/Sources/geos/include/geos/geom/LinearRing.h +++ b/Sources/geos/include/geos/geom/LinearRing.h @@ -29,7 +29,6 @@ namespace geos { namespace geom { // geos::geom class Coordinate; -class CoordinateArraySequence; } } @@ -76,16 +75,9 @@ class GEOS_DLL LinearRing : public LineString { * @param newFactory the GeometryFactory used to create this geometry * */ - LinearRing(CoordinateSequence* points, - const GeometryFactory* newFactory); - - /// Hopefully cleaner version of the above LinearRing(CoordinateSequence::Ptr && points, const GeometryFactory& newFactory); - LinearRing(std::vector && pts, - const GeometryFactory& newFactory); - std::unique_ptr clone() const { return std::unique_ptr(cloneImpl()); @@ -111,6 +103,8 @@ class GEOS_DLL LinearRing : public LineString { std::unique_ptr reverse() const { return std::unique_ptr(reverseImpl()); } + void orient(bool isCW); + protected: int diff --git a/Sources/geos/include/geos/geom/Location.h b/Sources/geos/include/geos/geom/Location.h index c16ba9e..c42367e 100644 --- a/Sources/geos/include/geos/geom/Location.h +++ b/Sources/geos/include/geos/geom/Location.h @@ -29,7 +29,7 @@ namespace geom { // geos::geom * [OpenGIS Simple Features Specification for SQL](http://www.opengis.org/techno/specs.htm"). */ -enum class GEOS_DLL Location : char { +enum class Location : char { /** * Used for uninitialized location values. */ diff --git a/Sources/geos/include/geos/geom/MultiCurve.h b/Sources/geos/include/geos/geom/MultiCurve.h new file mode 100644 index 0000000..5505f5d --- /dev/null +++ b/Sources/geos/include/geos/geom/MultiCurve.h @@ -0,0 +1,126 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +namespace geos { +namespace geom { + +class GEOS_DLL MultiCurve : public GeometryCollection { + friend class GeometryFactory; + +public: + ~MultiCurve() override = default; + + std::unique_ptr clone() const + { + return std::unique_ptr(cloneImpl()); + }; + + /// Returns a (possibly empty) [MultiPoint](@ref geom::MultiPoint) + std::unique_ptr getBoundary() const override; + + /** + * \brief + * Returns Dimension::False if all [Curves](@ref geom::Curve) in the collection + * are closed, 0 otherwise. + */ + int getBoundaryDimension() const override; + + /// Returns line dimension (1) + Dimension::DimensionType getDimension() const override; + + const Curve* getGeometryN(std::size_t n) const override; + + std::string getGeometryType() const override; + + GeometryTypeId getGeometryTypeId() const override; + + bool hasDimension(Dimension::DimensionType d) const override + { + return d == Dimension::L; + } + + /// Returns true if the MultiCurve is not empty, and every included + /// Curve is also closed. + bool isClosed() const; + + bool isDimensionStrict(Dimension::DimensionType d) const override + { + return d == Dimension::L; + } + + /** + * Creates a MultiCurve in the reverse + * order to this object. + * Both the order of the component Curves + * and the order of their coordinate sequences + * are reversed. + * + * @return a MultiCurve in the reverse order + */ + std::unique_ptr reverse() const + { + return std::unique_ptr(reverseImpl()); + } + +protected: + + /** + * \brief Constructs a MultiCurve. + * + * @param newLines The [Curves](@ref geom::Curve) for this + * MultiCurve, or `null` + * or an empty array to create the empty geometry. + * Elements may be empty Curve, + * but not `null`s. + * + * @param newFactory The GeometryFactory used to create this geometry. + * Caller must keep the factory alive for the life-time + * of the constructed MultiCurve. + * + * @note Constructed object will take ownership of + * the vector and its elements. + * + */ + MultiCurve(std::vector>&& newLines, + const GeometryFactory& newFactory); + + MultiCurve(std::vector>&& newLines, + const GeometryFactory& newFactory); + + MultiCurve(const MultiCurve& mp) + : GeometryCollection(mp) + {} + + MultiCurve* cloneImpl() const override + { + return new MultiCurve(*this); + } + + MultiCurve* reverseImpl() const override; + + int + getSortIndex() const override + { + return SORTINDEX_MULTICURVE; + }; + +}; + +} +} diff --git a/Sources/geos/include/geos/geom/MultiLineString.h b/Sources/geos/include/geos/geom/MultiLineString.h index ec04b07..2b6cc76 100644 --- a/Sources/geos/include/geos/geom/MultiLineString.h +++ b/Sources/geos/include/geos/geom/MultiLineString.h @@ -34,7 +34,6 @@ namespace geos { namespace geom { // geos::geom class Coordinate; -class CoordinateArraySequence; } } @@ -58,6 +57,10 @@ class GEOS_DLL MultiLineString: public GeometryCollection { /// Returns line dimension (1) Dimension::DimensionType getDimension() const override; + bool hasDimension(Dimension::DimensionType d) const override { + return d == Dimension::L; + } + bool isDimensionStrict(Dimension::DimensionType d) const override { return d == Dimension::L; } @@ -115,9 +118,6 @@ class GEOS_DLL MultiLineString: public GeometryCollection { * the vector and its elements. * */ - MultiLineString(std::vector* newLines, - const GeometryFactory* newFactory); - MultiLineString(std::vector> && newLines, const GeometryFactory& newFactory); @@ -138,6 +138,11 @@ class GEOS_DLL MultiLineString: public GeometryCollection { return SORTINDEX_MULTILINESTRING; }; + bool hasCurvedComponents() const override + { + return false; + } + }; diff --git a/Sources/geos/include/geos/geom/MultiPoint.h b/Sources/geos/include/geos/geom/MultiPoint.h index 8bfaf93..0e1d564 100644 --- a/Sources/geos/include/geos/geom/MultiPoint.h +++ b/Sources/geos/include/geos/geom/MultiPoint.h @@ -31,7 +31,6 @@ namespace geos { namespace geom { // geos::geom class Coordinate; -class CoordinateArraySequence; } } @@ -63,6 +62,10 @@ class GEOS_DLL MultiPoint: public GeometryCollection { return d == Dimension::P; } + bool hasDimension(Dimension::DimensionType d) const override { + return d == Dimension::P; + } + /// Returns Dimension::False (Point has no boundary) int getBoundaryDimension() const override; @@ -113,8 +116,6 @@ class GEOS_DLL MultiPoint: public GeometryCollection { * Caller must keep the factory alive for the life-time * of the constructed MultiPoint. */ - MultiPoint(std::vector* newPoints, const GeometryFactory* newFactory); - MultiPoint(std::vector> && newPoints, const GeometryFactory& newFactory); MultiPoint(std::vector> && newPoints, const GeometryFactory& newFactory); @@ -125,7 +126,7 @@ class GEOS_DLL MultiPoint: public GeometryCollection { MultiPoint* reverseImpl() const override { return new MultiPoint(*this); } - const Coordinate* getCoordinateN(std::size_t n) const; + const CoordinateXY* getCoordinateN(std::size_t n) const; int getSortIndex() const override @@ -133,6 +134,11 @@ class GEOS_DLL MultiPoint: public GeometryCollection { return SORTINDEX_MULTIPOINT; }; + bool hasCurvedComponents() const override + { + return false; + } + }; #ifdef _MSC_VER diff --git a/Sources/geos/include/geos/geom/MultiPolygon.h b/Sources/geos/include/geos/geom/MultiPolygon.h index 365a4da..f4cca3a 100644 --- a/Sources/geos/include/geos/geom/MultiPolygon.h +++ b/Sources/geos/include/geos/geom/MultiPolygon.h @@ -26,7 +26,6 @@ #include // for inheritance #include // for inheritance #include // for Dimension::DimensionType -#include #include @@ -34,8 +33,8 @@ namespace geos { namespace geom { // geos::geom class Coordinate; -class CoordinateArraySequence; class MultiPoint; +class Polygon; } } @@ -66,6 +65,10 @@ class GEOS_DLL MultiPolygon: public GeometryCollection { /// Returns surface dimension (2) Dimension::DimensionType getDimension() const override; + bool hasDimension(Dimension::DimensionType d) const override { + return d == Dimension::A; + } + bool isDimensionStrict(Dimension::DimensionType d) const override { return d == Dimension::A; } @@ -117,8 +120,6 @@ class GEOS_DLL MultiPolygon: public GeometryCollection { * Caller must keep the factory alive for the life-time * of the constructed MultiPolygon. */ - MultiPolygon(std::vector* newPolys, const GeometryFactory* newFactory); - MultiPolygon(std::vector> && newPolys, const GeometryFactory& newFactory); @@ -139,6 +140,11 @@ class GEOS_DLL MultiPolygon: public GeometryCollection { return SORTINDEX_MULTIPOLYGON; }; + bool hasCurvedComponents() const override + { + return false; + } + }; #ifdef _MSC_VER diff --git a/Sources/geos/include/geos/geom/MultiSurface.h b/Sources/geos/include/geos/geom/MultiSurface.h new file mode 100644 index 0000000..73871e4 --- /dev/null +++ b/Sources/geos/include/geos/geom/MultiSurface.h @@ -0,0 +1,94 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +namespace geos { +namespace geom { +class GEOS_DLL MultiSurface : public GeometryCollection { + friend class GeometryFactory; + +public: + + ~MultiSurface() override; + + std::unique_ptr clone() const + { + return std::unique_ptr(cloneImpl()); + }; + + /** \brief + * Computes the boundary of this geometry + * + * @return a lineal geometry (which may be empty) + * @see Geometry#getBoundary + */ + std::unique_ptr getBoundary() const override; + + /// Returns 1 (MultiSurface boundary is MultiCurve) + int getBoundaryDimension() const override; + + /// Returns surface dimension (2) + Dimension::DimensionType getDimension() const override; + + std::string getGeometryType() const override; + + GeometryTypeId getGeometryTypeId() const override; + + bool hasDimension(Dimension::DimensionType d) const override + { + return d == Dimension::A; + } + + bool isDimensionStrict(Dimension::DimensionType d) const override + { + return d == Dimension::A; + } + + std::unique_ptr reverse() const + { + return std::unique_ptr(reverseImpl()); + } + +protected: + + MultiSurface(std::vector>&& newPolys, + const GeometryFactory& newFactory); + + MultiSurface(std::vector>&& newPolys, + const GeometryFactory& newFactory); + + MultiSurface(const MultiSurface& mp) + : GeometryCollection(mp) + {}; + + MultiSurface* cloneImpl() const override + { + return new MultiSurface(*this); + } + + int + getSortIndex() const override + { + return SORTINDEX_MULTISURFACE; + }; + + MultiSurface* reverseImpl() const override; + +}; +} +} diff --git a/Sources/geos/include/geos/geom/Point.h b/Sources/geos/include/geos/geom/Point.h index a22d2ca..cc944d9 100644 --- a/Sources/geos/include/geos/geom/Point.h +++ b/Sources/geos/include/geos/geom/Point.h @@ -23,7 +23,6 @@ #include #include // for inheritance #include // for proper use of unique_ptr<> -#include #include // for proper use of unique_ptr<> #include // for Dimension::DimensionType @@ -40,7 +39,6 @@ namespace geos { namespace geom { // geos::geom class Coordinate; -class CoordinateArraySequence; class CoordinateFilter; class CoordinateSequenceFilter; class GeometryComponentFilter; @@ -96,6 +94,10 @@ class GEOS_DLL Point : public Geometry { /// Returns coordinate dimension. uint8_t getCoordinateDimension() const override; + bool hasM() const override; + + bool hasZ() const override; + /// Returns Dimension::False (Point has no boundary) int getBoundaryDimension() const override; @@ -109,10 +111,26 @@ class GEOS_DLL Point : public Geometry { */ std::unique_ptr getBoundary() const override; + void setXY(double x, double y) { + if (isEmpty()) { + coordinates.add(x, y); + } else { + CoordinateXY& prev = coordinates.front(); + prev.x = x; + prev.y = y; + } + geometryChangedAction(); + } + + const CoordinateXY* getCoordinate() const override { + return isEmpty() ? nullptr : &coordinates.getAt(0); + } + double getX() const; double getY() const; double getZ() const; - const Coordinate* getCoordinate() const override; + double getM() const; + std::string getGeometryType() const override; GeometryTypeId getGeometryTypeId() const override; void apply_ro(CoordinateFilter* filter) const override; @@ -126,6 +144,8 @@ class GEOS_DLL Point : public Geometry { bool equalsExact(const Geometry* other, double tolerance = 0) const override; + bool equalsIdentical(const Geometry* other) const override; + void normalize(void) override { @@ -137,6 +157,10 @@ class GEOS_DLL Point : public Geometry { return std::unique_ptr(reverseImpl()); } + const Envelope* getEnvelopeInternal() const override { + return &envelope; + } + protected: /** @@ -151,17 +175,23 @@ class GEOS_DLL Point : public Geometry { * * @param newFactory the GeometryFactory used to create this geometry */ - Point(CoordinateSequence* newCoords, const GeometryFactory* newFactory); + Point(CoordinateSequence&& newCoords, const GeometryFactory* newFactory); Point(const Coordinate& c, const GeometryFactory* newFactory); + Point(const CoordinateXY& c, const GeometryFactory* newFactory); + + Point(const CoordinateXYM& c, const GeometryFactory* newFactory); + + Point(const CoordinateXYZM& c, const GeometryFactory* newFactory); + Point(const Point& p); Point* cloneImpl() const override { return new Point(*this); } Point* reverseImpl() const override { return new Point(*this); } - Envelope::Ptr computeEnvelopeInternal() const override; + Envelope computeEnvelopeInternal() const; int compareToSameClass(const Geometry* p) const override; @@ -171,15 +201,14 @@ class GEOS_DLL Point : public Geometry { return SORTINDEX_POINT; }; -private: + void geometryChangedAction() override { + envelope = computeEnvelopeInternal(); + } - /** - * The Coordinate wrapped by this Point. - */ - FixedSizeCoordinateSequence<1> coordinates; +private: - bool empty2d; - bool empty3d; + CoordinateSequence coordinates; + Envelope envelope; }; } // namespace geos::geom diff --git a/Sources/geos/include/geos/geom/Polygon.h b/Sources/geos/include/geos/geom/Polygon.h index 38aad31..aabc4d8 100644 --- a/Sources/geos/include/geos/geom/Polygon.h +++ b/Sources/geos/include/geos/geom/Polygon.h @@ -27,6 +27,7 @@ #include // for proper use of unique_ptr<> #include #include // for Dimension::DimensionType +#include #include // for unique_ptr @@ -34,7 +35,6 @@ namespace geos { namespace geom { // geos::geom class Coordinate; -class CoordinateArraySequence; class CoordinateSequenceFilter; class LineString; } @@ -58,7 +58,7 @@ namespace geom { // geos::geom * Specification for SQL . * */ -class GEOS_DLL Polygon: public Geometry { +class GEOS_DLL Polygon: public SurfaceImpl { public: @@ -69,6 +69,9 @@ class GEOS_DLL Polygon: public Geometry { ~Polygon() override = default; + std::unique_ptr + getCoordinates() const override; + /** * Creates and returns a full copy of this {@link Polygon} object. * (including all coordinates contained by it). @@ -80,19 +83,6 @@ class GEOS_DLL Polygon: public Geometry { return std::unique_ptr(cloneImpl()); } - std::unique_ptr getCoordinates() const override; - - std::size_t getNumPoints() const override; - - /// Returns surface dimension (2) - Dimension::DimensionType getDimension() const override; - - /// Returns coordinate dimension. - uint8_t getCoordinateDimension() const override; - - /// Returns 1 (Polygon boundary is a MultiLineString) - int getBoundaryDimension() const override; - /** \brief * Computes the boundary of this geometry * @@ -101,109 +91,35 @@ class GEOS_DLL Polygon: public Geometry { */ std::unique_ptr getBoundary() const override; - bool isEmpty() const override; - - /// Returns the exterior ring (shell) - const LinearRing* getExteriorRing() const; - - /** - * \brief - * Take ownership of this Polygon's exterior ring. - * After releasing the exterior ring, the Polygon should be - * considered in a moved-from state and should not be accessed, - * except to release the interior rings (if desired.) - * @return exterior ring - */ - std::unique_ptr releaseExteriorRing(); - - /// Returns number of interior rings (hole) - std::size_t getNumInteriorRing() const; - - /// Get nth interior ring (hole) - const LinearRing* getInteriorRingN(std::size_t n) const; - - /** - * \brief - * Take ownership of this Polygon's interior rings. - * After releasing the rings, the Polygon should be - * considered in a moved-from state and should not be accessed, - * except to release the exterior ring (if desired.) - * @return vector of rings (may be empty) - */ - std::vector> releaseInteriorRings(); - std::string getGeometryType() const override; GeometryTypeId getGeometryTypeId() const override; - bool equalsExact(const Geometry* other, double tolerance = 0) const override; - void apply_rw(const CoordinateFilter* filter) override; - void apply_ro(CoordinateFilter* filter) const override; - void apply_rw(GeometryFilter* filter) override; - void apply_ro(GeometryFilter* filter) const override; - void apply_rw(CoordinateSequenceFilter& filter) override; - void apply_ro(CoordinateSequenceFilter& filter) const override; - void apply_rw(GeometryComponentFilter* filter) override; - void apply_ro(GeometryComponentFilter* filter) const override; - - std::unique_ptr convexHull() const override; void normalize() override; std::unique_ptr reverse() const { return std::unique_ptr(reverseImpl()); } - const Coordinate* getCoordinate() const override; - double getArea() const override; - /// Returns the perimeter of this Polygon - double getLength() const override; - bool isRectangle() const override; -protected: - - - Polygon(const Polygon& p); - - int compareToSameClass(const Geometry* p) const override; - /** - * Constructs a Polygon with the given exterior - * and interior boundaries. - * - * @param newShell the outer boundary of the new Polygon, - * or null or an empty - * LinearRing if the empty geometry - * is to be created. - * - * @param newHoles the LinearRings defining the inner - * boundaries of the new Polygon, or - * null or empty LinearRing - * if the empty geometry is to be created. - * - * @param newFactory the GeometryFactory used to create this geometry - * - * Polygon will take ownership of Shell and Holes LinearRings - */ - Polygon(LinearRing* newShell, std::vector* newHoles, - const GeometryFactory* newFactory); + * \brief + * Apply a ring ordering convention to this polygon, with + * interior rings having an opposite orientation to the + * specified exterior orientation. + * + * \param exteriorCW should exterior ring be clockwise? + */ + void orientRings(bool exteriorCW); - Polygon(std::unique_ptr && newShell, - const GeometryFactory& newFactory); +protected: - Polygon(std::unique_ptr && newShell, - std::vector> && newHoles, - const GeometryFactory& newFactory); + using SurfaceImpl::SurfaceImpl; Polygon* cloneImpl() const override { return new Polygon(*this); } Polygon* reverseImpl() const override; - std::unique_ptr shell; - - std::vector> holes; - - Envelope::Ptr computeEnvelopeInternal() const override; - int getSortIndex() const override { diff --git a/Sources/geos/include/geos/geom/PrecisionModel.h b/Sources/geos/include/geos/geom/PrecisionModel.h index 6305f6c..2228067 100644 --- a/Sources/geos/include/geos/geom/PrecisionModel.h +++ b/Sources/geos/include/geos/geom/PrecisionModel.h @@ -77,8 +77,6 @@ namespace geom { // geos::geom * * It is also supported to specify a precise grid size * by providing it as a negative scale factor. - * This allows setting a precise grid size rather than using a fractional scale, - * which provides more accurate and robust rounding. * For example, to specify rounding to the nearest 1000 use a scale factor of -1000. * * Coordinates are represented internally as Java double-precision values. @@ -183,7 +181,7 @@ class GEOS_DLL PrecisionModel { double makePrecise(double val) const; /// Rounds the given Coordinate to the PrecisionModel grid. - void makePrecise(Coordinate& coord) const + void makePrecise(CoordinateXY& coord) const { // optimization for full precision if(modelType == FLOATING) { @@ -194,7 +192,7 @@ class GEOS_DLL PrecisionModel { coord.y = makePrecise(coord.y); }; - void makePrecise(Coordinate* coord) const + void makePrecise(CoordinateXY* coord) const { assert(coord); return makePrecise(*coord); @@ -238,7 +236,7 @@ class GEOS_DLL PrecisionModel { /** * Computes the grid size for a fixed precision model. * This is equal to the reciprocal of the scale factor. - * If the grid size has been set explicity (via a negative scale factor) + * If the grid size has been set explicitly (via a negative scale factor) * it will be returned. * * @return the grid size at a fixed precision scale. @@ -348,6 +346,11 @@ class GEOS_DLL PrecisionModel { void setScale(double newScale); // throw IllegalArgumentException + /** \brief + * Snaps a value to nearest integer, if within tolerance. + */ + static double snapToInt(double val, double tolerance); + Type modelType; /** diff --git a/Sources/geos/include/geos/geom/Quadrant.h b/Sources/geos/include/geos/geom/Quadrant.h index 18c5ace..6705f1f 100644 --- a/Sources/geos/include/geos/geom/Quadrant.h +++ b/Sources/geos/include/geos/geom/Quadrant.h @@ -95,7 +95,7 @@ class GEOS_DLL Quadrant { * * @throws IllegalArgumentException if the points are equal */ - static int quadrant(const geom::Coordinate& p0, const geom::Coordinate& p1) + static int quadrant(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1) { if(p1.x == p0.x && p1.y == p0.y) { throw util::IllegalArgumentException("Cannot compute the quadrant for two identical points " + p0.toString()); diff --git a/Sources/geos/include/geos/geom/SimpleCurve.h b/Sources/geos/include/geos/geom/SimpleCurve.h new file mode 100644 index 0000000..1bf674f --- /dev/null +++ b/Sources/geos/include/geos/geom/SimpleCurve.h @@ -0,0 +1,142 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * Copyright (C) 2005 2006 Refractions Research Inc. + * Copyright (C) 2011 Sandro Santilli + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +namespace geos { +namespace geom { + +class GEOS_DLL SimpleCurve : public Curve { +public: + + using Curve::apply_ro; + using Curve::apply_rw; + + void apply_ro(CoordinateFilter* filter) const override; + + void apply_ro(CoordinateSequenceFilter& filter) const override; + + void apply_rw(CoordinateSequenceFilter& filter) override; + + void apply_rw(const CoordinateFilter* filter) override; + + bool equalsExact(const Geometry* other, double tolerance = 0) + const override; + + bool equalsIdentical(const Geometry* other) const override; + + /** + * \brief + * Returns a MultiPoint. + * Empty for closed Curve, a Point for each vertex otherwise. + */ + std::unique_ptr getBoundary() const override; + + const CoordinateXY* getCoordinate() const override; + + /// Returns coordinate dimension. + uint8_t getCoordinateDimension() const override; + + virtual const Coordinate& getCoordinateN(std::size_t n) const; + + std::unique_ptr getCoordinates() const override; + + /// Returns a read-only pointer to internal CoordinateSequence + const CoordinateSequence* getCoordinatesRO() const; + + const SimpleCurve* getCurveN(std::size_t) const override; + + /// \brief + /// Return the end point of the LineString + /// or NULL if this is an EMPTY LineString. + /// + virtual std::unique_ptr getEndPoint() const; + + const Envelope* getEnvelopeInternal() const override + { + return &envelope; + } + + std::size_t getNumCurves() const override; + + std::size_t getNumPoints() const override; + + virtual std::unique_ptr getPointN(std::size_t n) const; + + /// \brief + /// Return the start point of the LineString + /// or NULL if this is an EMPTY LineString. + /// + virtual std::unique_ptr getStartPoint() const; + + bool hasM() const override; + + bool hasZ() const override; + + bool isClosed() const override; + + virtual bool isCoordinate(CoordinateXY& pt) const; + + virtual bool isCurved() const = 0; + + bool isEmpty() const override; + + /** \brief + * Normalizes a SimpleCurve. + * + * A normalized simple curve + * has the first point which is not equal to its reflected point + * less than the reflected point. + */ + void normalize() override; + + /** + * \brief + * Take ownership of the CoordinateSequence managed by this geometry. + * After releasing the coordinates, the geometry should be considered + * in a moved-from state and should not be accessed. + * @return this Geometry's CoordinateSequence. + */ + std::unique_ptr releaseCoordinates(); + +protected: + + SimpleCurve(const SimpleCurve& other); + + SimpleCurve(std::unique_ptr&& newCoords, + bool isLinear, + const GeometryFactory& factory); + + int compareToSameClass(const Geometry* ls) const override; + + Envelope computeEnvelopeInternal(bool isLinear) const; + + // TODO: hold value or shared_ptr instead of unique_ptr? + std::unique_ptr points; + mutable Envelope envelope; + + +private: + + void normalizeClosed(); +}; + +} +} diff --git a/Sources/geos/include/geos/geom/Surface.h b/Sources/geos/include/geos/geom/Surface.h new file mode 100644 index 0000000..7b1bb6f --- /dev/null +++ b/Sources/geos/include/geos/geom/Surface.h @@ -0,0 +1,115 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +namespace geos { +namespace geom { + +class Curve; + +/// A Surface is an abstract class representing a Geometry of dimension 2. +/// It is extended by Polygon, which represents a Surface with linear edges, +/// and by CurvePolygon, whose edges may include circular arcs. +class GEOS_DLL Surface : public Geometry { + +private: + +protected: + using Geometry::Geometry; + +public: + + void apply_ro(CoordinateFilter* filter) const override; + + void apply_ro(CoordinateSequenceFilter& filter) const override; + + void apply_ro(GeometryComponentFilter* filter) const override; + + void apply_ro(GeometryFilter* filter) const override; + + void apply_rw(CoordinateSequenceFilter& filter) override; + + void apply_rw(GeometryComponentFilter* filter) override; + + void apply_rw(GeometryFilter* filter) override; + + void apply_rw(const CoordinateFilter* filter) override; + + std::unique_ptr convexHull() const override; + + bool + equalsExact(const Geometry* other, double tolerance = 0.0) const override; + + bool + equalsIdentical(const Geometry* other) const override; + + int + getBoundaryDimension() const override + { + return 1; + } + + const CoordinateXY* getCoordinate() const override; + + uint8_t getCoordinateDimension() const override; + + Dimension::DimensionType + getDimension() const override + { + return Dimension::A; // area + } + + const Envelope* getEnvelopeInternal() const override; + + /// Returns the exterior ring (shell) + virtual const Curve* getExteriorRing() const = 0; + + /// Get nth interior ring (hole) + virtual const Curve* getInteriorRingN(std::size_t n) const = 0; + + /// Returns the perimeter of this Surface + double getLength() const override; + + /// Returns number of interior rings (holes) + virtual size_t getNumInteriorRing() const = 0; + + size_t getNumPoints() const override; + + bool hasM() const override; + + bool hasZ() const override; + + bool isEmpty() const override; + +protected: + + int + compareToSameClass(const Geometry* g) const override; + + // Helper method allowing PolygonImpl to use GeometryFactory without cirular imports + static std::unique_ptr createEmptyRing(const GeometryFactory&); + + virtual Curve* getExteriorRing() = 0; + + virtual Curve* getInteriorRingN(std::size_t i) = 0; + + void geometryChangedAction() override {} + +}; + +} +} diff --git a/Sources/geos/include/geos/geom/SurfaceImpl.h b/Sources/geos/include/geos/geom/SurfaceImpl.h new file mode 100644 index 0000000..60da401 --- /dev/null +++ b/Sources/geos/include/geos/geom/SurfaceImpl.h @@ -0,0 +1,159 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * Copyright (C) 2011 Sandro Santilli + * Copyright (C) 2005 2006 Refractions Research Inc. + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace geos { +namespace geom { + +template +class SurfaceImpl : public Surface { + +protected: + + SurfaceImpl(const SurfaceImpl& p) + : + Surface(p), + shell(static_cast(p.shell->clone().release())), + holes(p.holes.size()) + { + for (std::size_t i = 0; i < holes.size(); ++i) { + holes[i].reset(static_cast(p.holes[i]->clone().release())); + } + } + + /** + * Constructs a Surface with the given exterior + * and interior boundaries. + * + * @param newShell the outer boundary of the new Polygon, + * or null or an empty + * Curve if the empty geometry + * is to be created. + * + * @param newHoles the rings defining the inner + * boundaries of the new Surface, or + * null or empty Curve + * if the empty geometry is to be created. + * + * @param newFactory the GeometryFactory used to create this geometry + * + * Polygon will take ownership of shell and hole curves + */ + SurfaceImpl(std::unique_ptr&& newShell, + const GeometryFactory& newFactory) : + Surface(&newFactory), + shell(std::move(newShell)) + { + if (shell == nullptr) { + shell.reset(static_cast(createEmptyRing(newFactory).release())); + } + } + + SurfaceImpl(std::unique_ptr&& newShell, + std::vector>&& newHoles, + const GeometryFactory& newFactory) : + Surface(&newFactory), + shell(std::move(newShell)), + holes(std::move(newHoles)) + { + if (shell == nullptr) { + shell.reset(static_cast(createEmptyRing(newFactory).release())); + } + + if(shell->isEmpty() && hasNonEmptyElements(&holes)) { + throw geos::util::IllegalArgumentException("shell is empty but holes are not"); + } + if (hasNullElements(&holes)) { + throw geos::util::IllegalArgumentException("holes must not contain null elements"); + } + } + +public: + + const RingType* + getExteriorRing() const override + { + return shell.get(); + } + + RingType* + getExteriorRing() override + { + return shell.get(); + } + + const RingType* + getInteriorRingN(std::size_t n) const override + { + return holes[n].get(); + } + + RingType* + getInteriorRingN(std::size_t n) override + { + return holes[n].get(); + } + + size_t getNumInteriorRing() const override + { + return holes.size(); + } + + /** + * \brief + * Take ownership of this Surface's exterior ring. + * After releasing the exterior ring, the Surface should be + * considered in a moved-from state and should not be accessed, + * except to release the interior rings (if desired.) + * @return exterior ring + */ + std::unique_ptr + releaseExteriorRing() + { + return std::move(shell); + } + + /** + * \brief + * Take ownership of this Surfaces's interior rings. + * After releasing the rings, the Surface should be + * considered in a moved-from state and should not be accessed, + * except to release the exterior ring (if desired.) + * @return vector of rings (may be empty) + */ + std::vector> releaseInteriorRings() + { + return std::move(holes); + } + +protected: + std::unique_ptr shell; + std::vector> holes; + +}; + +} +} diff --git a/Sources/geos/include/geos/geom/Triangle.h b/Sources/geos/include/geos/geom/Triangle.h index 26d3249..663f50e 100644 --- a/Sources/geos/include/geos/geom/Triangle.h +++ b/Sources/geos/include/geos/geom/Triangle.h @@ -27,9 +27,9 @@ namespace geom { // geos::geom */ class GEOS_DLL Triangle { public: - Coordinate p0, p1, p2; + CoordinateXY p0, p1, p2; - Triangle(const Coordinate& nP0, const Coordinate& nP1, const Coordinate& nP2) + Triangle(const CoordinateXY& nP0, const CoordinateXY& nP1, const CoordinateXY& nP2) : p0(nP0) , p1(nP1) , p2(nP2) {} @@ -43,7 +43,7 @@ class GEOS_DLL Triangle { * * @param resultPoint the point into which to write the inCentre of the triangle */ - void inCentre(Coordinate& resultPoint); + void inCentre(CoordinateXY& resultPoint); /** \brief * Computes the circumcentre of a triangle. @@ -60,23 +60,45 @@ class GEOS_DLL Triangle { * to the origin to improve the accuracy of computation. (See *Lecture Notes * on Geometric Robustness*, Jonathan Richard Shewchuk, 1999). * - * @param resultPoint the point into which to write the center of the triangle + * @param resultPoint the point into which to write the inCentre of the triangle */ - void circumcentre(Coordinate& resultPoint); + void circumcentre(CoordinateXY& resultPoint); /** Calculates the circumcentre using double precision math - * @param resultPoint the point into which to write the center of the triangle + * @param resultPoint the point into which to write the inCentre of the triangle */ - void circumcentreDD(Coordinate& resultPoint); + void circumcentreDD(CoordinateXY& resultPoint); - /** Computes the circumcentre of a triangle. + /** Computes the circumcentre of a triangle. The circumcentre is the centre + * of the circumcircle, the smallest circle which passes through all the triangle vertices. + * It is also the common intersection point of the perpendicular bisectors of the * @param p0 corner of the triangle * @param p1 corner of the triangle * @param p2 corner of the triangle * @return the center of the the smallest circle that encloses the triangle - * @overload */ - static const Coordinate circumcentre(const Coordinate& p0, const Coordinate& p1, const Coordinate& p2); + static const CoordinateXY circumcentre(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2); + + /** + * Computes the radius of the circumcircle of a triangle. + * Formula is as per https://math.stackexchange.com/a/3610959 + * + * @param a a vertex of the triangle + * @param b a vertex of the triangle + * @param c a vertex of the triangle + * @return the circumradius of the triangle + */ + static double circumradius(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c); + + /** + * Computes the radius of the circumcircle of a triangle. + * + * @return the triangle circumradius + */ + double circumradius() const + { + return circumradius(p0, p1, p2); + }; bool isIsoceles(); @@ -93,7 +115,7 @@ class GEOS_DLL Triangle { * @param c a vertex of the triangle * @return true if the triangle is acute */ - static bool isAcute(const Coordinate& a, const Coordinate& b, const Coordinate& c); + static bool isAcute(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c); /** * Tests whether a triangle is oriented counter-clockwise. @@ -103,7 +125,7 @@ class GEOS_DLL Triangle { * @param c a vertex of the triangle * @return true if the triangle orientation is counter-clockwise */ - static bool isCCW(const Coordinate& a, const Coordinate& b, const Coordinate& c); + static bool isCCW(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c); /** @@ -115,8 +137,8 @@ class GEOS_DLL Triangle { * @param p the point to test * @return true if the triangle intersects the point */ - static bool intersects(const Coordinate& a, const Coordinate& b, const Coordinate& c, - const Coordinate& p); + static bool intersects(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c, + const CoordinateXY& p); /** @@ -124,7 +146,7 @@ class GEOS_DLL Triangle { * @param p the point to test * @return true if the triangle intersects the point */ - bool intersects(const Coordinate& p) { return intersects(p0, p1, p2, p); }; + bool intersects(const CoordinateXY& p) { return intersects(p0, p1, p2, p); }; /** * Tests whether this triangle is oriented counter-clockwise. @@ -147,9 +169,9 @@ class GEOS_DLL Triangle { * @return the length of the longest side of the triangle */ static double longestSideLength( - const Coordinate& a, - const Coordinate& b, - const Coordinate& c); + const CoordinateXY& a, + const CoordinateXY& b, + const CoordinateXY& c); /** * Compute the length of the perimeter of a triangle @@ -159,7 +181,7 @@ class GEOS_DLL Triangle { * @param c a vertex of the triangle * @return the length of the triangle perimeter */ - static double length(const Coordinate& a, const Coordinate& b, const Coordinate& c); + static double length(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c); /** * Computes the length of the perimeter of this triangle. @@ -177,7 +199,7 @@ class GEOS_DLL Triangle { * @return the area of the triangle * */ - static double area(const Coordinate& a, const Coordinate& b, const Coordinate& c); + static double area(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c); double area() const; diff --git a/Sources/geos/include/geos/geom/prep/BasicPreparedGeometry.h b/Sources/geos/include/geos/geom/prep/BasicPreparedGeometry.h index 10786c8..f061de4 100644 --- a/Sources/geos/include/geos/geom/prep/BasicPreparedGeometry.h +++ b/Sources/geos/include/geos/geom/prep/BasicPreparedGeometry.h @@ -20,10 +20,8 @@ #pragma once #include // for inheritance -//#include -//#include #include -//#include +#include #include #include @@ -40,6 +38,8 @@ namespace geos { namespace geom { // geos::geom namespace prep { // geos::geom::prep +using geos::operation::relateng::RelateNG; + // * \class BasicPreparedGeometry /** @@ -58,7 +58,16 @@ namespace prep { // geos::geom::prep class BasicPreparedGeometry: public PreparedGeometry { private: const geom::Geometry* baseGeom; - Coordinate::ConstVect representativePts; + std::vector representativePts; + mutable std::unique_ptr relate_ng; + + RelateNG& getRelateNG() const + { + if (relate_ng == nullptr) + relate_ng = RelateNG::prepare(baseGeom); + + return *relate_ng; + } protected: /** @@ -103,7 +112,7 @@ class BasicPreparedGeometry: public PreparedGeometry { * * @return a List of Coordinate */ - const Coordinate::ConstVect* + const std::vector* getRepresentativePoints() const { return &representativePts; @@ -119,6 +128,11 @@ class BasicPreparedGeometry: public PreparedGeometry { */ bool isAnyTargetComponentInTest(const geom::Geometry* testGeom) const; + /** + * Default implementation. + */ + bool within(const geom::Geometry* g) const override; + /** * Default implementation. */ @@ -168,7 +182,12 @@ class BasicPreparedGeometry: public PreparedGeometry { /** * Default implementation. */ - bool within(const geom::Geometry* g) const override; + std::unique_ptr relate(const geom::Geometry* g) const override; + + /** + * Default implementation. + */ + bool relate(const geom::Geometry* g, const std::string& pat) const override; /** * Default implementation. diff --git a/Sources/geos/include/geos/geom/prep/PreparedGeometry.h b/Sources/geos/include/geos/geom/prep/PreparedGeometry.h index d0cf237..6c77513 100644 --- a/Sources/geos/include/geos/geom/prep/PreparedGeometry.h +++ b/Sources/geos/include/geos/geom/prep/PreparedGeometry.h @@ -20,6 +20,7 @@ #include #include +#include #include // Forward declarations @@ -28,6 +29,7 @@ namespace geos { class Geometry; class Coordinate; class CoordinateSequence; + class IntersectionMatrix; } } @@ -227,6 +229,28 @@ class GEOS_DLL PreparedGeometry { * */ virtual bool isWithinDistance(const geom::Geometry* geom, double dist) const = 0; + + /** \brief + * Compares the prepared geometry to the given geometry + * and returns the DE9IM intersection matrix as a string. + * + * @param geom the Geometry to test the + * @return the DE9IM matrix + */ + virtual std::unique_ptr relate(const geom::Geometry* geom) const = 0; + + /** \brief + * Compares the prepared geometry to the given geometry + * and the provided DE9IM pattern, and returns true if the + * pattern is consistent with the relationship between the + * prepared and provided geometries. + * + * @param geom the Geometry to test the distance to + * @param pat the DE9IM pattern + * @return true if the patterns are consistent + */ + virtual bool relate(const geom::Geometry* geom, const std::string& pat) const = 0; + }; diff --git a/Sources/geos/include/geos/geom/prep/PreparedLineString.h b/Sources/geos/include/geos/geom/prep/PreparedLineString.h index 1fb4439..017e06e 100644 --- a/Sources/geos/include/geos/geom/prep/PreparedLineString.h +++ b/Sources/geos/include/geos/geom/prep/PreparedLineString.h @@ -59,6 +59,7 @@ class PreparedLineString : public BasicPreparedGeometry { bool intersects(const geom::Geometry* g) const override; std::unique_ptr nearestPoints(const geom::Geometry* g) const override; double distance(const geom::Geometry* g) const override; + bool isWithinDistance(const geom::Geometry* g, double d) const override; operation::distance::IndexedFacetDistance* getIndexedFacetDistance() const; }; diff --git a/Sources/geos/include/geos/geom/prep/PreparedLineStringDistance.h b/Sources/geos/include/geos/geom/prep/PreparedLineStringDistance.h index 491d1b2..89c886d 100644 --- a/Sources/geos/include/geos/geom/prep/PreparedLineStringDistance.h +++ b/Sources/geos/include/geos/geom/prep/PreparedLineStringDistance.h @@ -40,6 +40,8 @@ class PreparedLineStringDistance { double distance(const geom::Geometry* g) const; + bool isWithinDistance(const geom::Geometry* g, double d) const; + protected: const PreparedLineString& prepLine; diff --git a/Sources/geos/include/geos/geom/prep/PreparedPolygon.h b/Sources/geos/include/geos/geom/prep/PreparedPolygon.h index 58cc75e..a2feee6 100644 --- a/Sources/geos/include/geos/geom/prep/PreparedPolygon.h +++ b/Sources/geos/include/geos/geom/prep/PreparedPolygon.h @@ -53,6 +53,7 @@ class PreparedPolygon : public BasicPreparedGeometry { bool isRectangle; mutable std::unique_ptr segIntFinder; mutable std::unique_ptr ptOnGeomLoc; + mutable std::unique_ptr indexedPtOnGeomLoc; mutable noding::SegmentString::ConstVect segStrings; mutable std::unique_ptr indexedDistance; @@ -70,6 +71,7 @@ class PreparedPolygon : public BasicPreparedGeometry { bool covers(const geom::Geometry* g) const override; bool intersects(const geom::Geometry* g) const override; double distance(const geom::Geometry* g) const override; + bool isWithinDistance(const geom::Geometry* g, double d) const override; }; diff --git a/Sources/geos/include/geos/geom/prep/PreparedPolygonDistance.h b/Sources/geos/include/geos/geom/prep/PreparedPolygonDistance.h index 0e42996..c429aa0 100644 --- a/Sources/geos/include/geos/geom/prep/PreparedPolygonDistance.h +++ b/Sources/geos/include/geos/geom/prep/PreparedPolygonDistance.h @@ -19,6 +19,8 @@ #pragma once +#include + // Forward declarations namespace geos { namespace geom { @@ -35,7 +37,7 @@ namespace prep { // geos::geom::prep class PreparedPolygon; -class PreparedPolygonDistance { +class PreparedPolygonDistance : public PreparedPolygonPredicate { public: static double distance(const PreparedPolygon& prep, const geom::Geometry* geom) @@ -45,14 +47,14 @@ class PreparedPolygonDistance { } PreparedPolygonDistance(const PreparedPolygon& prep) - : prepPoly(prep) + : PreparedPolygonPredicate(&prep) { } double distance(const geom::Geometry* g) const; -protected: + bool isWithinDistance(const geom::Geometry* g, double d) const; - const PreparedPolygon& prepPoly; +protected: // Declare type as noncopyable PreparedPolygonDistance(const PreparedPolygonDistance& other) = delete; diff --git a/Sources/geos/include/geos/geom/prep/PreparedPolygonPredicate.h b/Sources/geos/include/geos/geom/prep/PreparedPolygonPredicate.h index 83bc2f6..3103165 100644 --- a/Sources/geos/include/geos/geom/prep/PreparedPolygonPredicate.h +++ b/Sources/geos/include/geos/geom/prep/PreparedPolygonPredicate.h @@ -117,7 +117,7 @@ class PreparedPolygonPredicate { * @return true if any component intersects the areal test geometry */ bool isAnyTargetComponentInAreaTest(const geom::Geometry* testGeom, - const geom::Coordinate::ConstVect* targetRepPts) const; + const std::vector* targetRepPts) const; public: /** \brief diff --git a/Sources/geos/include/geos/geom/util/ComponentCoordinateExtracter.h b/Sources/geos/include/geos/geom/util/ComponentCoordinateExtracter.h index eccfc2d..d323271 100644 --- a/Sources/geos/include/geos/geom/util/ComponentCoordinateExtracter.h +++ b/Sources/geos/include/geos/geom/util/ComponentCoordinateExtracter.h @@ -42,13 +42,13 @@ class ComponentCoordinateExtracter : public GeometryComponentFilter { * efficient to create a single ComponentCoordinateFilter instance * and pass it to multiple geometries. */ - static void getCoordinates(const Geometry& geom, std::vector& ret); + static void getCoordinates(const Geometry& geom, std::vector& ret); /** * Constructs a ComponentCoordinateFilter with a list in which * to store Coordinates found. */ - ComponentCoordinateExtracter(std::vector& newComps); + ComponentCoordinateExtracter(std::vector& newComps); void filter_rw(Geometry* geom) override; @@ -56,7 +56,7 @@ class ComponentCoordinateExtracter : public GeometryComponentFilter { private: - Coordinate::ConstVect& comps; + std::vector& comps; // Declare type as noncopyable ComponentCoordinateExtracter(const ComponentCoordinateExtracter& other) = delete; diff --git a/Sources/geos/include/geos/geom/util/Densifier.h b/Sources/geos/include/geos/geom/util/Densifier.h index 18c1bd2..2e1c8e6 100644 --- a/Sources/geos/include/geos/geom/util/Densifier.h +++ b/Sources/geos/include/geos/geom/util/Densifier.h @@ -65,7 +65,7 @@ class GEOS_DLL Densifier { private: double distanceTolerance; const Geometry* inputGeom; - static std::unique_ptr densifyPoints(const Coordinate::Vect pts, double distanceTolerance, + static std::unique_ptr densifyPoints(const CoordinateSequence& pts, double distanceTolerance, const PrecisionModel* precModel); class GEOS_DLL DensifyTransformer: public GeometryTransformer { diff --git a/Sources/geos/include/geos/geom/util/GeometryEditor.h b/Sources/geos/include/geos/geom/util/GeometryEditor.h index 86e1a32..294aad3 100644 --- a/Sources/geos/include/geos/geom/util/GeometryEditor.h +++ b/Sources/geos/include/geos/geom/util/GeometryEditor.h @@ -55,7 +55,7 @@ namespace util { // geos.geom.util * this is not checked by the GeometryEditor * - the coordinate lists may be changed * (e.g. by adding or deleting coordinates). - * The modifed coordinate lists must be consistent with their original + * The modified coordinate lists must be consistent with their original * parent component * (e.g. a LinearRing must always have at least 4 coordinates, and the * first and last coordinate must be equal) diff --git a/Sources/geos/include/geos/geom/util/GeometryLister.h b/Sources/geos/include/geos/geom/util/GeometryLister.h new file mode 100644 index 0000000..912518e --- /dev/null +++ b/Sources/geos/include/geos/geom/util/GeometryLister.h @@ -0,0 +1,92 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2011 Sandro Santilli + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * Copyright (C) 2006 Refractions Research Inc. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + ********************************************************************** + * + * Last port: geom/util/GeometryExtracter.java r320 (JTS-1.12) + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include + +namespace geos { +namespace geom { // geos.geom +namespace util { // geos.geom.util + +/** + * Extracts all the components of a collection, or just echoes back a + * pointers to singletons. + */ +class GEOS_DLL GeometryLister { + +public: + + /** + * Extracts the components from a {@link Geometry} + * and adds them to the provided container. + * + * Useful for iterating over components of a collection. + * + * @param geom the geometry from which to extract + * @param lst the list to add the extracted elements to + */ + static void + list(const Geometry* geom, std::vector& lst) + { + if(geom->isCollection()) { + GeometryLister::Lister lister(lst); + geom->apply_ro(&lister); + } + else { + lst.push_back(geom); + } + } + +private: + + struct Lister : public GeometryFilter { + + /** + * Constructs a filter with a list in which to store the elements found. + * + * @param comps the container to extract into (will push_back to it) + */ + Lister(std::vector& p_geoms) : geoms(p_geoms) {} + + std::vector& geoms; + + void + filter_ro(const Geometry* geom) override + { + if(!geom->isCollection()) { + geoms.push_back(geom); + } + } + + // // Declare type as noncopyable + // Lister(const Lister& other); + // Lister& operator=(const Lister& rhs); + }; +}; + + +} // namespace geos.geom.util +} // namespace geos.geom +} // namespace geos + diff --git a/Sources/geos/include/geos/geom/util/GeometryTransformer.h b/Sources/geos/include/geos/geom/util/GeometryTransformer.h index dcd5098..7cbe0c3 100644 --- a/Sources/geos/include/geos/geom/util/GeometryTransformer.h +++ b/Sources/geos/include/geos/geom/util/GeometryTransformer.h @@ -101,18 +101,6 @@ class GEOS_DLL GeometryTransformer { const GeometryFactory* factory; - /** \brief - * Convenience method which provides standard way of - * creating a CoordinateSequence. - * - * @param coords the coordinate array to copy - * @return a coordinate sequence for the array - * - * [final] - */ - CoordinateSequence::Ptr createCoordinateSequence( - std::unique_ptr< std::vector > coords); - virtual CoordinateSequence::Ptr transformCoordinates( const CoordinateSequence* coords, const Geometry* parent); @@ -160,7 +148,7 @@ class GEOS_DLL GeometryTransformer { bool pruneEmptyGeometry; /** - * `true` if a homogenous collection result + * `true` if a homogeneous collection result * from a {@link GeometryCollection} should still * be a general GeometryCollection */ diff --git a/Sources/geos/include/geos/geom/util/LinearComponentExtracter.h b/Sources/geos/include/geos/geom/util/LinearComponentExtracter.h index b1ff1aa..0efcbb7 100644 --- a/Sources/geos/include/geos/geom/util/LinearComponentExtracter.h +++ b/Sources/geos/include/geos/geom/util/LinearComponentExtracter.h @@ -55,8 +55,6 @@ class GEOS_DLL LinearComponentExtracter: public GeometryComponentFilter { */ LinearComponentExtracter(std::vector& newComps); - void filter_rw(Geometry* geom) override; - void filter_ro(const Geometry* geom) override; }; diff --git a/Sources/geos/include/geos/geom/util/PolygonalExtracter.h b/Sources/geos/include/geos/geom/util/PolygonalExtracter.h new file mode 100644 index 0000000..9dfba65 --- /dev/null +++ b/Sources/geos/include/geos/geom/util/PolygonalExtracter.h @@ -0,0 +1,54 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * Copyright (C) 2006 Refractions Research Inc. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + +namespace geos { +namespace geom { // geos.geom +namespace util { // geos.geom.util + +/** + * \brief Extracts the polygonal (Polygon and MultiPolygon) elements from a Geometry. + */ +class GEOS_DLL PolygonalExtracter { + +public: + + /** + * Pushes the polygonal (Polygon and MultiPolygon) elements from a geometry into + * the provided vector. + * + * @param geom the geometry to extract from + * @param polys the vector to add the polygonal elements to + */ + static void getPolygonals(const Geometry& geom, std::vector& polys); + +private: + + static void getPolygonals(const Geometry* geom, std::vector& polys); + + // Declare type as noncopyable + PolygonalExtracter(const PolygonalExtracter& other) = delete; + PolygonalExtracter& operator=(const PolygonalExtracter& rhs) = delete; +}; + +} // namespace geos.geom.util +} // namespace geos.geom +} // namespace geos + diff --git a/Sources/geos/include/geos/geomgraph/DirectedEdge.h b/Sources/geos/include/geos/geomgraph/DirectedEdge.h index 8b4d8f8..ab88f62 100644 --- a/Sources/geos/include/geos/geomgraph/DirectedEdge.h +++ b/Sources/geos/include/geos/geomgraph/DirectedEdge.h @@ -39,7 +39,7 @@ namespace geos { namespace geomgraph { // geos.geomgraph /// A directed EdgeEnd -class GEOS_DLL DirectedEdge: public EdgeEnd { +class GEOS_DLL DirectedEdge final: public EdgeEnd { public: @@ -159,7 +159,7 @@ class GEOS_DLL DirectedEdge: public EdgeEnd { }; /** \brief - * Tells wheter this edge is a Line + * Tells whether this edge is a Line * * This edge is a line edge if * - at least one of the labels is a line label @@ -169,7 +169,7 @@ class GEOS_DLL DirectedEdge: public EdgeEnd { bool isLineEdge(); /** \brief - * Tells wheter this edge is an Area + * Tells whether this edge is an Area * * This is an interior Area edge if * - its label is an Area label for both Geometries diff --git a/Sources/geos/include/geos/geomgraph/DirectedEdgeStar.h b/Sources/geos/include/geos/geomgraph/DirectedEdgeStar.h index 3e70ada..4400daa 100644 --- a/Sources/geos/include/geos/geomgraph/DirectedEdgeStar.h +++ b/Sources/geos/include/geos/geomgraph/DirectedEdgeStar.h @@ -21,6 +21,7 @@ #pragma once +#include #include #include #include @@ -81,7 +82,7 @@ class GEOS_DLL DirectedEdgeStar: public EdgeEndStar { /** \brief * Compute the labelling for all dirEdges in this star, as well as the overall labelling */ - void computeLabelling(std::vector* geom) override; // throw(TopologyException *); + void computeLabelling(const std::vector>&geom) override; // throw(TopologyException *); /** \brief * For each dirEdge in the star, merge the label from the sym dirEdge into the label @@ -116,7 +117,7 @@ class GEOS_DLL DirectedEdgeStar: public EdgeEndStar { void linkAllDirectedEdges(); /** \brief - * Traverse the star of edges, maintaing the current location in the result + * Traverse the star of edges, maintaining the current location in the result * area at this node (if any). * * If any L edges are found in the interior of the result, mark them as covered. diff --git a/Sources/geos/include/geos/geomgraph/Edge.h b/Sources/geos/include/geos/geomgraph/Edge.h index 100ae71..52a9f57 100644 --- a/Sources/geos/include/geos/geomgraph/Edge.h +++ b/Sources/geos/include/geos/geomgraph/Edge.h @@ -60,7 +60,7 @@ namespace geos { namespace geomgraph { // geos.geomgraph /** The edge component of a geometry graph */ -class GEOS_DLL Edge: public GraphComponent { +class GEOS_DLL Edge final: public GraphComponent { using GraphComponent::updateIM; private: @@ -102,27 +102,27 @@ class GEOS_DLL Edge: public GraphComponent { ~Edge() override; - virtual size_t + size_t getNumPoints() const { return pts->getSize(); } - virtual const geom::CoordinateSequence* + const geom::CoordinateSequence* getCoordinates() const { testInvariant(); return pts.get(); } - virtual const geom::Coordinate& + const geom::Coordinate& getCoordinate(std::size_t i) const { testInvariant(); return pts->getAt(i); } - virtual const geom::Coordinate& + const geom::Coordinate& getCoordinate() const { testInvariant(); @@ -130,8 +130,8 @@ class GEOS_DLL Edge: public GraphComponent { } - virtual Depth& - getDepth() + const Depth& + getDepth() const { testInvariant(); return depth; @@ -142,41 +142,48 @@ class GEOS_DLL Edge: public GraphComponent { * * @return the change in depth as the edge is crossed from R to L */ - virtual int + int getDepthDelta() const { testInvariant(); return depthDelta; } - virtual void + void setDepthDelta(int newDepthDelta) { depthDelta = newDepthDelta; testInvariant(); } - virtual size_t + size_t getMaximumSegmentIndex() const { testInvariant(); return getNumPoints() - 1; } - virtual EdgeIntersectionList& + EdgeIntersectionList& getEdgeIntersectionList() { testInvariant(); return eiList; } + const EdgeIntersectionList& + getEdgeIntersectionList() const + { + testInvariant(); + return eiList; + } + /// \brief /// Return this Edge's index::MonotoneChainEdge, /// ownership is retained by this object. /// - virtual index::MonotoneChainEdge* getMonotoneChainEdge(); + index::MonotoneChainEdge* getMonotoneChainEdge(); - virtual bool + bool isClosed() const { testInvariant(); @@ -187,11 +194,11 @@ class GEOS_DLL Edge: public GraphComponent { * An Edge is collapsed if it is an Area edge and it consists of * two segments which are equal and opposite (eg a zero-width V). */ - virtual bool isCollapsed() const; + bool isCollapsed() const; - virtual Edge* getCollapsedEdge(); + Edge* getCollapsedEdge(); - virtual void + void setIsolated(bool newIsIsolated) { isIsolatedVar = newIsIsolated; @@ -209,7 +216,7 @@ class GEOS_DLL Edge: public GraphComponent { * Adds EdgeIntersections for one or both * intersections found for a segment of an edge to the edge intersection list. */ - virtual void addIntersections(algorithm::LineIntersector* li, std::size_t segmentIndex, + void addIntersections(algorithm::LineIntersector* li, std::size_t segmentIndex, std::size_t geomIndex); /// Add an EdgeIntersection for intersection intIndex. @@ -217,7 +224,7 @@ class GEOS_DLL Edge: public GraphComponent { /// An intersection that falls exactly on a vertex of the edge is normalized /// to use the higher of the two possible segmentIndexes /// - virtual void addIntersection(algorithm::LineIntersector* li, std::size_t segmentIndex, + void addIntersection(algorithm::LineIntersector* li, std::size_t segmentIndex, std::size_t geomIndex, std::size_t intIndex); /// Update the IM with the contribution for this component. @@ -233,11 +240,11 @@ class GEOS_DLL Edge: public GraphComponent { } /// return true if the coordinate sequences of the Edges are identical - virtual bool isPointwiseEqual(const Edge* e) const; + bool isPointwiseEqual(const Edge* e) const; - virtual std::string print() const; + std::string print() const; - virtual std::string printReverse() const; + std::string printReverse() const; /** * equals is defined to be: @@ -246,16 +253,16 @@ class GEOS_DLL Edge: public GraphComponent { * iff * the coordinates of e1 are the same or the reverse of the coordinates in e2 */ - virtual bool equals(const Edge& e) const; + bool equals(const Edge& e) const; - virtual bool + bool equals(const Edge* e) const { assert(e); return equals(*e); } - virtual const geom::Envelope* getEnvelope(); + const geom::Envelope* getEnvelope(); }; diff --git a/Sources/geos/include/geos/geomgraph/EdgeEnd.h b/Sources/geos/include/geos/geomgraph/EdgeEnd.h index f497d94..86ac11a 100644 --- a/Sources/geos/include/geos/geomgraph/EdgeEnd.h +++ b/Sources/geos/include/geos/geomgraph/EdgeEnd.h @@ -51,7 +51,7 @@ namespace geomgraph { // geos.geomgraph * "a has a greater angle with the x-axis than b". * This ordering is used to sort EdgeEnds around a node. */ -class GEOS_DLL EdgeEnd { +class GEOS_DLL EdgeEnd /* non-final */ { public: @@ -101,7 +101,7 @@ class GEOS_DLL EdgeEnd { return label; } - virtual geom::Coordinate& getCoordinate() { + geom::Coordinate& getCoordinate() { return p0; } @@ -111,19 +111,19 @@ class GEOS_DLL EdgeEnd { return p0; } - virtual geom::Coordinate& getDirectedCoordinate(); + geom::Coordinate& getDirectedCoordinate(); - virtual int getQuadrant(); + int getQuadrant(); - virtual double getDx(); + double getDx(); - virtual double getDy(); + double getDy(); - virtual void setNode(Node* newNode); + void setNode(Node* newNode); - virtual Node* getNode(); + Node* getNode(); - virtual int compareTo(const EdgeEnd* e) const; + int compareTo(const EdgeEnd* e) const; /** * Implements the total order relation: @@ -141,7 +141,7 @@ class GEOS_DLL EdgeEnd { * computeOrientation function can be used to decide * the relative orientation of the vectors. */ - virtual int compareDirection(const EdgeEnd* e) const; + int compareDirection(const EdgeEnd* e) const; virtual void computeLabel(const algorithm::BoundaryNodeRule& bnr); @@ -155,7 +155,7 @@ class GEOS_DLL EdgeEnd { EdgeEnd(Edge* newEdge); - virtual void init(const geom::Coordinate& newP0, + void init(const geom::Coordinate& newP0, const geom::Coordinate& newP1); private: diff --git a/Sources/geos/include/geos/geomgraph/EdgeEndStar.h b/Sources/geos/include/geos/geomgraph/EdgeEndStar.h index 2982703..b249b60 100644 --- a/Sources/geos/include/geos/geomgraph/EdgeEndStar.h +++ b/Sources/geos/include/geos/geomgraph/EdgeEndStar.h @@ -27,6 +27,7 @@ #include // for p0,p1 #include +#include #include #include #include @@ -59,7 +60,7 @@ namespace geomgraph { // geos.geomgraph * * @version 1.4 */ -class GEOS_DLL EdgeEndStar { +class GEOS_DLL EdgeEndStar /* non-final */ { public: typedef std::set container; @@ -85,46 +86,46 @@ class GEOS_DLL EdgeEndStar { * a Coordinate owned by the specific EdgeEnd happening * to be the first in the star (ordered CCW) */ - virtual geom::Coordinate& getCoordinate(); + geom::Coordinate& getCoordinate(); const geom::Coordinate& getCoordinate() const; - virtual std::size_t getDegree(); + std::size_t getDegree(); - virtual iterator begin(); + iterator begin(); - virtual iterator end(); + iterator end(); - virtual reverse_iterator rbegin(); + reverse_iterator rbegin(); - virtual reverse_iterator rend(); + reverse_iterator rend(); - virtual const_iterator + const_iterator begin() const { return edgeMap.begin(); } - virtual const_iterator + const_iterator end() const { return edgeMap.end(); } - virtual container& getEdges(); + container& getEdges(); - virtual EdgeEnd* getNextCW(EdgeEnd* ee); + EdgeEnd* getNextCW(EdgeEnd* ee); - virtual void computeLabelling(std::vector* geomGraph); + virtual void computeLabelling(const std::vector>&geomGraph); // throw(TopologyException *); - virtual bool isAreaLabelsConsistent(const GeometryGraph& geomGraph); + bool isAreaLabelsConsistent(const GeometryGraph& geomGraph); - virtual void propagateSideLabels(uint32_t geomIndex); + void propagateSideLabels(uint32_t geomIndex); // throw(TopologyException *); //virtual int findIndex(EdgeEnd *eSearch); - virtual iterator find(EdgeEnd* eSearch); + iterator find(EdgeEnd* eSearch); virtual std::string print() const; @@ -139,7 +140,7 @@ class GEOS_DLL EdgeEndStar { /** \brief * Insert an EdgeEnd into the map. */ - virtual void + void insertEdgeEnd(EdgeEnd* e) { edgeMap.insert(e); @@ -147,9 +148,9 @@ class GEOS_DLL EdgeEndStar { private: - virtual geom::Location getLocation(uint32_t geomIndex, - const geom::Coordinate& p, - std::vector* geom); + geom::Location getLocation(uint32_t geomIndex, + const geom::Coordinate&p, + const std::vector>&geom); /** \brief * The location of the point for this star in @@ -157,9 +158,9 @@ class GEOS_DLL EdgeEndStar { */ std::array ptInAreaLocation; - virtual void computeEdgeEndLabels(const algorithm::BoundaryNodeRule&); + void computeEdgeEndLabels(const algorithm::BoundaryNodeRule&); - virtual bool checkAreaLabelsConsistent(uint32_t geomIndex); + bool checkAreaLabelsConsistent(uint32_t geomIndex); }; diff --git a/Sources/geos/include/geos/geomgraph/EdgeIntersection.h b/Sources/geos/include/geos/geomgraph/EdgeIntersection.h index 7e43c91..6a65131 100644 --- a/Sources/geos/include/geos/geomgraph/EdgeIntersection.h +++ b/Sources/geos/include/geos/geomgraph/EdgeIntersection.h @@ -39,7 +39,7 @@ namespace geomgraph { // geos.geomgraph * The intersection point must be precise. * */ -class GEOS_DLL EdgeIntersection { +class GEOS_DLL EdgeIntersection final { public: // the point of intersection diff --git a/Sources/geos/include/geos/geomgraph/EdgeIntersectionList.h b/Sources/geos/include/geos/geomgraph/EdgeIntersectionList.h index 915bbc8..7c8bdba 100644 --- a/Sources/geos/include/geos/geomgraph/EdgeIntersectionList.h +++ b/Sources/geos/include/geos/geomgraph/EdgeIntersectionList.h @@ -27,7 +27,7 @@ #include #include // for EdgeIntersectionLessThen -#include // for CoordinateLessThen +#include // for CoordinateLessThan #ifdef _MSC_VER #pragma warning(push) @@ -54,7 +54,7 @@ namespace geomgraph { // geos.geomgraph * Implements splitting an edge with intersections * into multiple resultant edges. */ -class GEOS_DLL EdgeIntersectionList { +class GEOS_DLL EdgeIntersectionList final { public: // Instead of storing edge intersections in a set, as JTS does, we store them // in a vector and then sort the vector if needed before iterating among the diff --git a/Sources/geos/include/geos/geomgraph/EdgeList.h b/Sources/geos/include/geos/geomgraph/EdgeList.h index 01e6675..56fd225 100644 --- a/Sources/geos/include/geos/geomgraph/EdgeList.h +++ b/Sources/geos/include/geos/geomgraph/EdgeList.h @@ -52,7 +52,7 @@ namespace geomgraph { // geos.geomgraph * It supports locating edges * that are pointwise equals to a target edge. */ -class GEOS_DLL EdgeList { +class GEOS_DLL EdgeList final { private: @@ -85,7 +85,7 @@ class GEOS_DLL EdgeList { ocaMap() {} - virtual ~EdgeList() = default; + ~EdgeList() = default; /** * Insert an edge unless it is already in the list diff --git a/Sources/geos/include/geos/geomgraph/EdgeNodingValidator.h b/Sources/geos/include/geos/geomgraph/EdgeNodingValidator.h index 287bf67..6f844d1 100644 --- a/Sources/geos/include/geos/geomgraph/EdgeNodingValidator.h +++ b/Sources/geos/include/geos/geomgraph/EdgeNodingValidator.h @@ -51,7 +51,7 @@ namespace geomgraph { // geos.geomgraph * * Throws an appropriate exception if an noding error is found. */ -class GEOS_DLL EdgeNodingValidator { +class GEOS_DLL EdgeNodingValidator final { private: std::vector& toSegmentStrings(std::vector& edges); diff --git a/Sources/geos/include/geos/geomgraph/EdgeRing.h b/Sources/geos/include/geos/geomgraph/EdgeRing.h index 11844ab..257453c 100644 --- a/Sources/geos/include/geos/geomgraph/EdgeRing.h +++ b/Sources/geos/include/geos/geomgraph/EdgeRing.h @@ -23,7 +23,7 @@ #include #include // for composition -#include +#include #include #include // for testInvariant @@ -54,7 +54,7 @@ namespace geos { namespace geomgraph { // geos.geomgraph /** EdgeRing */ -class GEOS_DLL EdgeRing { +class GEOS_DLL EdgeRing /* non-final */ { public: friend std::ostream& operator<< (std::ostream& os, const EdgeRing& er); @@ -173,7 +173,7 @@ class GEOS_DLL EdgeRing { /// the DirectedEdges making up this EdgeRing std::vector edges; - std::vector pts; + geom::CoordinateSequence pts; // label stores the locations of each geometry on the // face surrounded by this ring diff --git a/Sources/geos/include/geos/geomgraph/GeometryGraph.h b/Sources/geos/include/geos/geomgraph/GeometryGraph.h index 322bb3f..78976cd 100644 --- a/Sources/geos/include/geos/geomgraph/GeometryGraph.h +++ b/Sources/geos/include/geos/geomgraph/GeometryGraph.h @@ -68,7 +68,7 @@ namespace geomgraph { // geos.geomgraph /** \brief * A GeometryGraph is a graph that models a given Geometry. */ -class GEOS_DLL GeometryGraph: public PlanarGraph { +class GEOS_DLL GeometryGraph final: public PlanarGraph { using PlanarGraph::add; using PlanarGraph::findEdge; diff --git a/Sources/geos/include/geos/geomgraph/GraphComponent.h b/Sources/geos/include/geos/geomgraph/GraphComponent.h index 44ba7b7..2ad8058 100644 --- a/Sources/geos/include/geos/geomgraph/GraphComponent.h +++ b/Sources/geos/include/geos/geomgraph/GraphComponent.h @@ -42,7 +42,7 @@ namespace geomgraph { // geos.geomgraph * * Each GraphComponent can carry a Label. */ -class GEOS_DLL GraphComponent { +class GEOS_DLL GraphComponent /* non-final */ { public: GraphComponent(); @@ -69,39 +69,39 @@ class GEOS_DLL GraphComponent { label = newLabel; } - virtual void + void setInResult(bool p_isInResult) { isInResultVar = p_isInResult; } - virtual bool + bool isInResult() const { return isInResultVar; } - virtual void setCovered(bool isCovered); - virtual bool + void setCovered(bool isCovered); + bool isCovered() const { return isCoveredVar; } - virtual bool + bool isCoveredSet() const { return isCoveredSetVar; } - virtual bool + bool isVisited() const { return isVisitedVar; } - virtual void + void setVisited(bool p_isVisited) { isVisitedVar = p_isVisited; } virtual bool isIsolated() const = 0; - virtual void updateIM(geom::IntersectionMatrix& im); + void updateIM(geom::IntersectionMatrix& im); protected: Label label; virtual void computeIM(geom::IntersectionMatrix& im) = 0; diff --git a/Sources/geos/include/geos/geomgraph/Label.h b/Sources/geos/include/geos/geomgraph/Label.h index 025e454..773b822 100644 --- a/Sources/geos/include/geos/geomgraph/Label.h +++ b/Sources/geos/include/geos/geomgraph/Label.h @@ -54,7 +54,7 @@ namespace geomgraph { // geos.geomgraph * with specific geometries. * */ -class GEOS_DLL Label { +class GEOS_DLL Label final { public: diff --git a/Sources/geos/include/geos/geomgraph/Node.h b/Sources/geos/include/geos/geomgraph/Node.h index d4276a2..3f63f64 100644 --- a/Sources/geos/include/geos/geomgraph/Node.h +++ b/Sources/geos/include/geos/geomgraph/Node.h @@ -56,7 +56,7 @@ namespace geos { namespace geomgraph { // geos.geomgraph /** \brief The node component of a geometry graph. */ -class GEOS_DLL Node: public GraphComponent { +class GEOS_DLL Node /* non-final */: public GraphComponent { using GraphComponent::setLabel; public: @@ -67,18 +67,18 @@ class GEOS_DLL Node: public GraphComponent { ~Node() override; - virtual const geom::Coordinate& getCoordinate() const; + const geom::Coordinate& getCoordinate() const; - virtual EdgeEndStar* getEdges(); + EdgeEndStar* getEdges(); bool isIsolated() const override; /** \brief * Add the edge to the list of edges at this node */ - virtual void add(EdgeEnd* e); + void add(EdgeEnd* e); - virtual void mergeLabel(const Node& n); + void mergeLabel(const Node& n); /** \brief * To merge labels for two nodes, @@ -87,15 +87,15 @@ class GEOS_DLL Node: public GraphComponent { * The location for the corresponding node LabelElement is set * to the result, as long as the location is non-null. */ - virtual void mergeLabel(const Label& label2); + void mergeLabel(const Label& label2); - virtual void setLabel(uint8_t argIndex, geom::Location onLocation); + void setLabel(uint8_t argIndex, geom::Location onLocation); /** \brief * Updates the label of a node to BOUNDARY, * obeying the mod-2 boundaryDetermination rule. */ - virtual void setLabelBoundary(uint8_t argIndex); + void setLabelBoundary(uint8_t argIndex); /** * The location for a given eltIndex for a node will be one @@ -105,13 +105,13 @@ class GEOS_DLL Node: public GraphComponent { * in the boundary. * The merged location is the maximum of the two input values. */ - virtual geom::Location computeMergedLocation(const Label& label2, uint8_t eltIndex); + geom::Location computeMergedLocation(const Label& label2, uint8_t eltIndex); - virtual std::string print(); + std::string print() const; - virtual const std::vector& getZ() const; + const std::vector& getZ() const; - virtual void addZ(double); + void addZ(double); /** \brief * Tests whether any incident edge is flagged as @@ -124,7 +124,7 @@ class GEOS_DLL Node: public GraphComponent { * @return true if any indicident edge in the in * the result */ - virtual bool isIncidentEdgeInResult() const; + bool isIncidentEdgeInResult() const; protected: diff --git a/Sources/geos/include/geos/geomgraph/NodeFactory.h b/Sources/geos/include/geos/geomgraph/NodeFactory.h index 641de8b..fe577b6 100644 --- a/Sources/geos/include/geos/geomgraph/NodeFactory.h +++ b/Sources/geos/include/geos/geomgraph/NodeFactory.h @@ -35,7 +35,7 @@ class Node; namespace geos { namespace geomgraph { // geos.geomgraph -class GEOS_DLL NodeFactory { +class GEOS_DLL NodeFactory /* non-final */ { public: virtual Node* createNode(const geom::Coordinate& coord) const; static const NodeFactory& instance(); diff --git a/Sources/geos/include/geos/geomgraph/NodeMap.h b/Sources/geos/include/geos/geomgraph/NodeMap.h index 7f6635f..a3a769e 100644 --- a/Sources/geos/include/geos/geomgraph/NodeMap.h +++ b/Sources/geos/include/geos/geomgraph/NodeMap.h @@ -22,10 +22,11 @@ #include #include +#include #include #include -#include // for CoordinateLessThen +#include // for CoordinateLessThan #include // for testInvariant @@ -46,17 +47,15 @@ class NodeFactory; namespace geos { namespace geomgraph { // geos.geomgraph -class GEOS_DLL NodeMap { +class GEOS_DLL NodeMap final { public: - typedef std::map container; + typedef std::map, geom::CoordinateLessThan> container; typedef container::iterator iterator; typedef container::const_iterator const_iterator; - typedef std::pair pair; - container nodeMap; const NodeFactory& nodeFact; @@ -66,14 +65,21 @@ class GEOS_DLL NodeMap { /// keep it alive for the whole NodeMap lifetime NodeMap(const NodeFactory& newNodeFact); - virtual ~NodeMap(); - Node* addNode(const geom::Coordinate& coord); Node* addNode(Node* n); + /// \brief + /// Adds a node for the start point of this EdgeEnd + /// (if one does not already exist in this map). + /// Adds the EdgeEnd to the (possibly new) node. + /// + /// If ownership of the EdgeEnd should be transferred + /// to the Node, use the unique_ptr overload instead. void add(EdgeEnd* e); + void add(std::unique_ptr&& e); + Node* find(const geom::Coordinate& coord) const; const_iterator @@ -110,13 +116,13 @@ class GEOS_DLL NodeMap { { #ifndef NDEBUG // Each Coordinate key is a pointer inside the Node value - for(iterator it = begin(), itEnd = end(); it != itEnd; ++it) { - pair p = *it; - geomgraph::Node* n = p.second; + for(const auto& nodeIt: nodeMap) { + const auto* n = nodeIt.second.get(); geom::Coordinate* c = const_cast( &(n->getCoordinate()) ); - assert(p.first == c); + assert(nodeIt.first == c); + (void)c; } #endif } diff --git a/Sources/geos/include/geos/geomgraph/PlanarGraph.h b/Sources/geos/include/geos/geomgraph/PlanarGraph.h index 8ba0c8c..8684d3b 100644 --- a/Sources/geos/include/geos/geomgraph/PlanarGraph.h +++ b/Sources/geos/include/geos/geomgraph/PlanarGraph.h @@ -69,7 +69,7 @@ namespace geomgraph { // geos.geomgraph * different graphs * */ -class GEOS_DLL PlanarGraph { +class GEOS_DLL PlanarGraph /* non-final */ { public: /** \brief @@ -105,36 +105,36 @@ class GEOS_DLL PlanarGraph { virtual ~PlanarGraph(); - virtual std::vector::iterator getEdgeIterator(); + std::vector::iterator getEdgeIterator(); - virtual std::vector* getEdgeEnds(); + std::vector* getEdgeEnds(); - virtual bool isBoundaryNode(uint8_t geomIndex, const geom::Coordinate& coord); + bool isBoundaryNode(uint8_t geomIndex, const geom::Coordinate& coord); - virtual void add(EdgeEnd* e); + void add(EdgeEnd* e); - virtual NodeMap::iterator getNodeIterator(); + NodeMap::iterator getNodeIterator(); - virtual void getNodes(std::vector&); + void getNodes(std::vector&); - virtual Node* addNode(Node* node); + Node* addNode(Node* node); - virtual Node* addNode(const geom::Coordinate& coord); + Node* addNode(const geom::Coordinate& coord); /** * @return the node if found; null otherwise */ - virtual Node* find(geom::Coordinate& coord); + Node* find(geom::Coordinate& coord); /** \brief * Add a set of edges to the graph. For each edge two DirectedEdges * will be created. DirectedEdges are NOT linked by this method. */ - virtual void addEdges(const std::vector& edgesToAdd); + void addEdges(const std::vector& edgesToAdd); - virtual void linkResultDirectedEdges(); + void linkResultDirectedEdges(); - virtual void linkAllDirectedEdges(); + void linkAllDirectedEdges(); /** \brief * Returns the EdgeEnd which has edge e as its base edge @@ -143,7 +143,7 @@ class GEOS_DLL PlanarGraph { * @return the edge, if found * null if the edge was not found */ - virtual EdgeEnd* findEdgeEnd(Edge* e); + EdgeEnd* findEdgeEnd(Edge* e); /** \brief * Returns the edge whose first two coordinates are p0 and p1 @@ -151,7 +151,7 @@ class GEOS_DLL PlanarGraph { * @return the edge, if found * null if the edge was not found */ - virtual Edge* findEdge(const geom::Coordinate& p0, + Edge* findEdge(const geom::Coordinate& p0, const geom::Coordinate& p1); /** \brief @@ -161,12 +161,12 @@ class GEOS_DLL PlanarGraph { * @return the edge, if found * null if the edge was not found */ - virtual Edge* findEdgeInSameDirection(const geom::Coordinate& p0, + Edge* findEdgeInSameDirection(const geom::Coordinate& p0, const geom::Coordinate& p1); - virtual std::string printEdges(); + std::string printEdges(); - virtual NodeMap* getNodeMap(); + NodeMap* getNodeMap(); protected: @@ -176,7 +176,7 @@ class GEOS_DLL PlanarGraph { std::vector* edgeEndList; - virtual void insertEdge(Edge* e); + void insertEdge(Edge* e); private: diff --git a/Sources/geos/include/geos/index/VertexSequencePackedRtree.h b/Sources/geos/include/geos/index/VertexSequencePackedRtree.h index 0a2c040..cab7eee 100644 --- a/Sources/geos/include/geos/index/VertexSequencePackedRtree.h +++ b/Sources/geos/include/geos/index/VertexSequencePackedRtree.h @@ -14,15 +14,20 @@ #pragma once +#include +#include + // Forward declarations namespace geos { namespace geom { class Coordinate; +class CoordinateSequence; class Envelope; } } using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; using geos::geom::Envelope; @@ -58,7 +63,7 @@ class GEOS_DLL VertexSequencePackedRtree { static constexpr std::size_t NODE_CAPACITY = 16; // Members - const std::vector& items; + const CoordinateSequence& items; std::vector removedItems; std::vector levelOffset; std::size_t nodeCapacity = NODE_CAPACITY; @@ -93,7 +98,7 @@ class GEOS_DLL VertexSequencePackedRtree { static Envelope computeNodeEnvelope(const std::vector& bounds, std::size_t start, std::size_t end); - static Envelope computeItemEnvelope(const std::vector& items, + static Envelope computeItemEnvelope(const CoordinateSequence& items, std::size_t start, std::size_t end); void queryNode(const Envelope& queryEnv, @@ -118,7 +123,7 @@ class GEOS_DLL VertexSequencePackedRtree { * * @param pts a sequence of points */ - VertexSequencePackedRtree(const std::vector& pts); + VertexSequencePackedRtree(const CoordinateSequence& pts); std::vector getBounds(); diff --git a/Sources/geos/include/geos/index/bintree/Bintree.h b/Sources/geos/include/geos/index/bintree/Bintree.h index b0d897c..4c41476 100644 --- a/Sources/geos/include/geos/index/bintree/Bintree.h +++ b/Sources/geos/include/geos/index/bintree/Bintree.h @@ -44,7 +44,7 @@ namespace bintree { // geos::index::bintree * may be a single point). * * This implementation does not require specifying the extent of the inserted - * items beforehand. It will automatically expand to accomodate any extent + * items beforehand. It will automatically expand to accommodate any extent * of dataset. * * This index is different to the "Interval Tree of Edelsbrunner" diff --git a/Sources/geos/include/geos/index/chain/MonotoneChain.h b/Sources/geos/include/geos/index/chain/MonotoneChain.h index ccdac8c..a5172f9 100644 --- a/Sources/geos/include/geos/index/chain/MonotoneChain.h +++ b/Sources/geos/include/geos/index/chain/MonotoneChain.h @@ -167,15 +167,20 @@ class GEOS_DLL MonotoneChain { const MonotoneChain& mc, std::size_t start1, std::size_t end1, double overlapTolerance) const { if (overlapTolerance > 0.0) { - return overlaps(pts->getAt(start0), pts->getAt(end0), - mc.pts->getAt(start1), mc.pts->getAt(end1), overlapTolerance); + return overlaps(pts->getAt(start0), + pts->getAt(end0), + mc.pts->getAt(start1), + mc.pts->getAt(end1), + overlapTolerance); } - return geom::Envelope::intersects(pts->getAt(start0), pts->getAt(end0), - mc.pts->getAt(start1), mc.pts->getAt(end1)); + return geom::Envelope::intersects(pts->getAt(start0), + pts->getAt(end0), + mc.pts->getAt(start1), + mc.pts->getAt(end1)); } - static bool overlaps(const geom::Coordinate& p1, const geom::Coordinate& p2, - const geom::Coordinate& q1, const geom::Coordinate& q2, + static bool overlaps(const geom::CoordinateXY& p1, const geom::CoordinateXY& p2, + const geom::CoordinateXY& q1, const geom::CoordinateXY& q2, double overlapTolerance); /// Externally owned @@ -192,6 +197,7 @@ class GEOS_DLL MonotoneChain { /// Owned by this class mutable geom::Envelope env; + }; } // namespace geos::index::chain diff --git a/Sources/geos/include/geos/index/quadtree/Quadtree.h b/Sources/geos/include/geos/index/quadtree/Quadtree.h index aa9fcf7..9f92f60 100644 --- a/Sources/geos/include/geos/index/quadtree/Quadtree.h +++ b/Sources/geos/include/geos/index/quadtree/Quadtree.h @@ -61,7 +61,7 @@ namespace quadtree { // geos::index::quadtree * intersection, such as testing other kinds of spatial relationships. * * This implementation does not require specifying the extent of the inserted - * items beforehand. It will automatically expand to accomodate any extent + * items beforehand. It will automatically expand to accommodate any extent * of dataset. * * This data structure is also known as an MX-CIF quadtree diff --git a/Sources/geos/include/geos/index/strtree/Interval.h b/Sources/geos/include/geos/index/strtree/Interval.h index f8e49cd..9842356 100644 --- a/Sources/geos/include/geos/index/strtree/Interval.h +++ b/Sources/geos/include/geos/index/strtree/Interval.h @@ -17,6 +17,7 @@ #include #include #include +#include namespace geos { namespace index { // geos::index @@ -29,7 +30,7 @@ namespace strtree { // geos::index::strtree class GEOS_DLL Interval { public: Interval(double newMin, double newMax) : imin(newMin), imax(newMax) { - assert(imin <= imax); + assert(std::isnan(newMin) || std::isnan(newMax) || imin <= imax); } double getMin() const { return imin; } diff --git a/Sources/geos/include/geos/index/strtree/TemplateSTRNode.h b/Sources/geos/include/geos/index/strtree/TemplateSTRNode.h index cf3f38d..1212e77 100644 --- a/Sources/geos/include/geos/index/strtree/TemplateSTRNode.h +++ b/Sources/geos/include/geos/index/strtree/TemplateSTRNode.h @@ -14,6 +14,8 @@ #pragma once +#include + namespace geos { namespace index { namespace strtree { diff --git a/Sources/geos/include/geos/index/strtree/TemplateSTRNodePair.h b/Sources/geos/include/geos/index/strtree/TemplateSTRNodePair.h index e29c408..f164f89 100644 --- a/Sources/geos/include/geos/index/strtree/TemplateSTRNodePair.h +++ b/Sources/geos/include/geos/index/strtree/TemplateSTRNodePair.h @@ -15,6 +15,7 @@ #pragma once #include +#include namespace geos { namespace index { @@ -57,6 +58,10 @@ class TemplateSTRNodePair { } } + double maximumDistance() { + return BoundsTraits::maxDistance(getFirst().getBounds(), getSecond().getBounds()); + } + private: const Node* m_node1; const Node* m_node2; diff --git a/Sources/geos/include/geos/index/strtree/TemplateSTRtree.h b/Sources/geos/include/geos/index/strtree/TemplateSTRtree.h index 163ec3a..baae429 100644 --- a/Sources/geos/include/geos/index/strtree/TemplateSTRtree.h +++ b/Sources/geos/include/geos/index/strtree/TemplateSTRtree.h @@ -203,10 +203,9 @@ class TemplateSTRtreeImpl { return nearestNeighbour(*this, distance); } - /** Determine the two closest items in the tree using distance metric `distance`. */ + /** Determine the two closest items in the tree using distance metric `ItemDistance`. */ template std::pair nearestNeighbour() { - ItemDistance id; return nearestNeighbour(*this); } @@ -222,7 +221,7 @@ class TemplateSTRtreeImpl { return td.nearestNeighbour(*root, *other.root); } - /** Determine the two closest items this tree and `other` tree using distance metric `distance`. */ + /** Determine the two closest items this tree and `other` tree using distance metric `ItemDistance`. */ template std::pair nearestNeighbour(TemplateSTRtreeImpl& other) { ItemDistance id; @@ -250,6 +249,18 @@ class TemplateSTRtreeImpl { return nearestNeighbour(env, item, id); } + template + bool isWithinDistance(TemplateSTRtreeImpl& other, double maxDistance) { + ItemDistance itemDist; + + if (!getRoot() || !other.getRoot()) { + return false; + } + + TemplateSTRtreeDistance td(itemDist); + return td.isWithinDistance(*root, *other.root, maxDistance); + } + /// @} /// \defgroup query Query /// @{ @@ -274,6 +285,27 @@ class TemplateSTRtreeImpl { } } + // Query the tree for all pairs whose bounds intersect. The visitor must + // be callable with arguments (const ItemType&, const ItemType&). + // The visitor will be called for each pair once, with first-inserted + // item used for the first argument. + // The visitor need not return a value, but if it does return a value, + // false values will be taken as a signal to stop the query. + template + void queryPairs(Visitor&& visitor) { + if (!built()) { + build(); + } + + if (numItems < 2) { + return; + } + + for (std::size_t i = 0; i < numItems; i++) { + queryPairs(nodes[i], *root, visitor); + } + } + // Query the tree and collect items in the provided vector. void query(const BoundsType& queryEnv, std::vector& results) { query(queryEnv, [&results](const ItemType& x) { @@ -509,6 +541,14 @@ class TemplateSTRtreeImpl { return true; } + template()(std::declval(), std::declval()))>::value, std::nullptr_t>::type = nullptr > + bool visitLeaves(Visitor&& visitor, const Node& node1, const Node& node2) + { + visitor(node1.getItem(), node2.getItem()); + return true; + } + // MSVC 2015 does not implement C++11 expression SFINAE and considers this a // redefinition of a previous method #if !defined(_MSC_VER) || _MSC_VER >= 1910 @@ -530,6 +570,13 @@ class TemplateSTRtreeImpl { return visitor(node.getItem()); } + template()(std::declval(), std::declval()))>::value, std::nullptr_t>::type = nullptr > + bool visitLeaves(Visitor&& visitor, const Node& node1, const Node& node2) + { + return visitor(node1.getItem(), node2.getItem()); + } + // MSVC 2015 does not implement C++11 expression SFINAE and considers this a // redefinition of a previous method #if !defined(_MSC_VER) || _MSC_VER >= 1910 @@ -566,6 +613,34 @@ class TemplateSTRtreeImpl { return true; // continue searching } + template + bool queryPairs(const Node& queryNode, + const Node& searchNode, + Visitor&& visitor) { + + assert(!searchNode.isLeaf()); + + for (auto* child = searchNode.beginChildren(); child < searchNode.endChildren(); ++child) { + if (child->isLeaf()) { + // Only visit leaf nodes if they have a higher address than the query node, + // to avoid processing the same pairs twice. + if (child > &queryNode && !child->isDeleted() && child->boundsIntersect(queryNode.getBounds())) { + if (!visitLeaves(visitor, queryNode, *child)) { + return false; // abort query + } + } + } else { + if (child->boundsIntersect(queryNode.getBounds())) { + if (!queryPairs(queryNode, *child, visitor)) { + return false; // abort query + } + } + } + } + + return true; // continue searching + } + bool remove(const BoundsType& queryEnv, const Node& node, const ItemType& item) { @@ -621,6 +696,10 @@ struct EnvelopeTraits { return a.distance(b); } + static double maxDistance(const BoundsType& a, const BoundsType& b) { + return a.maxDistance(b); + } + static BoundsType empty() { return {}; } diff --git a/Sources/geos/include/geos/index/strtree/TemplateSTRtreeDistance.h b/Sources/geos/include/geos/index/strtree/TemplateSTRtreeDistance.h index 9c4cca4..19d7109 100644 --- a/Sources/geos/include/geos/index/strtree/TemplateSTRtreeDistance.h +++ b/Sources/geos/include/geos/index/strtree/TemplateSTRtreeDistance.h @@ -14,11 +14,14 @@ #pragma once +#include #include #include #include +#include #include +#include #include namespace geos { @@ -51,6 +54,11 @@ class TemplateSTRtreeDistance { return nearestNeighbour(initPair, DoubleInfinity); } + bool isWithinDistance(const Node& root1, const Node& root2, double maxDistance) { + NodePair initPair(root1, root2, m_id); + return isWithinDistance(initPair, maxDistance); + } + private: ItemPair nearestNeighbour(NodePair& initPair, double maxDistance) { @@ -152,6 +160,72 @@ class TemplateSTRtreeDistance { } } + bool isWithinDistance(const NodePair& initPair, double maxDistance) { + double distanceUpperBound = DoubleInfinity; + + // initialize search queue + PairQueue priQ; + priQ.push(initPair); + + while (! priQ.empty()) { + // pop head of queue and expand one side of pair + NodePair pair = priQ.top(); + double pairDistance = pair.getDistance(); + + /** + * If the distance for the first pair in the queue + * is > maxDistance, all other pairs + * in the queue must have a greater distance as well. + * So can conclude no items are within the distance + * and terminate with result = false + */ + if (pairDistance > maxDistance) + return false; + + priQ.pop(); + + /** + * If the maximum distance between the nodes + * is less than the maxDistance, + * than all items in the nodes must be + * closer than the max distance. + * Then can terminate with result = true. + * + * NOTE: using Envelope MinMaxDistance + * would provide a tighter bound, + * but not much performance improvement has been observed + */ + if (pair.maximumDistance() <= maxDistance) + return true; + /** + * If the pair items are leaves + * then their actual distance is an upper bound. + * Update the distanceUpperBound to reflect this + */ + if (pair.isLeaves()) { + // assert: currentDistance < minimumDistanceFound + distanceUpperBound = pairDistance; + /** + * If the items are closer than maxDistance + * can terminate with result = true. + */ + if (distanceUpperBound <= maxDistance) + return true; + } + else { + /** + * Otherwise, expand one side of the pair, + * and insert the expanded pairs into the queue. + * The choice of which side to expand is determined heuristically. + */ + expandToQueue(pair, priQ, distanceUpperBound); + } + } + return false; + + } + + ItemDistance& m_id; }; } diff --git a/Sources/geos/include/geos/io/CheckOrdinatesFilter.h b/Sources/geos/include/geos/io/CheckOrdinatesFilter.h new file mode 100644 index 0000000..d4d9a81 --- /dev/null +++ b/Sources/geos/include/geos/io/CheckOrdinatesFilter.h @@ -0,0 +1,68 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 ISciences LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include + +namespace geos { +namespace io { + +class CheckOrdinatesFilter : public geom::CoordinateSequenceFilter { +public: + CheckOrdinatesFilter(OrdinateSet checkOrdinateFlags) : + m_checkFlags(checkOrdinateFlags), + m_foundFlags(OrdinateSet::createXY()) + {} + + void filter_ro(const geom::CoordinateSequence& seq, std::size_t i) override { + bool checkZ = m_checkFlags.hasZ() && !m_foundFlags.hasZ(); + bool checkM = m_checkFlags.hasM() && !m_foundFlags.hasM(); + + if (checkZ || checkM) { + seq.getAt(i, m_coord); + + if (checkZ && !std::isnan(m_coord.z)) { + m_foundFlags.setZ(true); + } + + if (checkM && !std::isnan(m_coord.m)) { + m_foundFlags.setM(true); + } + } + } + + bool isGeometryChanged() const override { + return false; + } + + bool isDone() const override { + return m_checkFlags == m_foundFlags; + } + + OrdinateSet getFoundOrdinates() const { + return m_foundFlags; + } + +private: + OrdinateSet m_checkFlags; + OrdinateSet m_foundFlags; + geom::CoordinateXYZM m_coord; +}; + +} +} diff --git a/Sources/geos/include/geos/io/GeoJSON.h b/Sources/geos/include/geos/io/GeoJSON.h index 0fd045b..8281786 100644 --- a/Sources/geos/include/geos/io/GeoJSON.h +++ b/Sources/geos/include/geos/io/GeoJSON.h @@ -98,6 +98,13 @@ class GEOS_DLL GeoJSONFeature { GeoJSONFeature(std::unique_ptr g, std::map&& p); + GeoJSONFeature(std::unique_ptr g, + const std::map& p, + const std::string& id); + + GeoJSONFeature(std::unique_ptr g, + std::map&& p, std::string id); + GeoJSONFeature(GeoJSONFeature const& other); GeoJSONFeature(GeoJSONFeature&& other); @@ -110,12 +117,16 @@ class GEOS_DLL GeoJSONFeature { const std::map& getProperties() const; + const std::string& getId() const; + private: std::unique_ptr geometry; std::map properties; + std::string id; + }; class GEOS_DLL GeoJSONFeatureCollection { diff --git a/Sources/geos/include/geos/io/GeoJSONReader.h b/Sources/geos/include/geos/io/GeoJSONReader.h index c475555..41834f1 100644 --- a/Sources/geos/include/geos/io/GeoJSONReader.h +++ b/Sources/geos/include/geos/io/GeoJSONReader.h @@ -50,7 +50,7 @@ class GEOS_DLL GeoJSONReader { public: /** - * \brief Inizialize parser with given GeometryFactory. + * \brief Initialize parser with given GeometryFactory. * * Note that all Geometry objects created by the * parser will contain a pointer to the given factory @@ -60,7 +60,7 @@ class GEOS_DLL GeoJSONReader { GeoJSONReader(const geom::GeometryFactory& gf); /** - * \brief Inizialize parser with default GeometryFactory. + * \brief Initialize parser with default GeometryFactory. * */ GeoJSONReader(); diff --git a/Sources/geos/include/geos/io/GeoJSONWriter.h b/Sources/geos/include/geos/io/GeoJSONWriter.h index 17a5c1b..002a6f5 100644 --- a/Sources/geos/include/geos/io/GeoJSONWriter.h +++ b/Sources/geos/include/geos/io/GeoJSONWriter.h @@ -74,11 +74,33 @@ class GEOS_DLL GeoJSONWriter { std::string write(const GeoJSONFeatureCollection& features); + /* + * \brief + * Returns the output dimension used by the + * GeoJSONWriter. + */ + int + getOutputDimension() const + { + return defaultOutputDimension; + } + + /* + * Sets the output dimension used by the GeoJSONWriter. + * + * @param newOutputDimension Supported values are 2 or 3. + * Default since GEOS 3.12 is 3. + * Note that 3 indicates up to 3 dimensions will be + * written but 2D GeoJSON is still produced for 2D geometries. + */ + void setOutputDimension(uint8_t newOutputDimension); + private: + uint8_t defaultOutputDimension = 3; - std::pair convertCoordinate(const geom::Coordinate* c); + std::vector convertCoordinate(const geom::Coordinate* c); - std::vector> convertCoordinateSequence(const geom::CoordinateSequence* c); + std::vector> convertCoordinateSequence(const geom::CoordinateSequence* c); void encode(const geom::Geometry* g, GeoJSONType type, geos_nlohmann::ordered_json& j); diff --git a/Sources/geos/include/geos/io/OrdinateSet.h b/Sources/geos/include/geos/io/OrdinateSet.h new file mode 100644 index 0000000..ebd2297 --- /dev/null +++ b/Sources/geos/include/geos/io/OrdinateSet.h @@ -0,0 +1,119 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 ISciences LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +namespace geos { +namespace io { + +/** + * \class OrdinateSet + * \brief Utility class to manipulate a set of flags indicating whether + * X, Y, Z, or M dimensions are present. + * Based on JTS EnumSet. + */ +class GEOS_DLL OrdinateSet { +private: + enum Ordinate : unsigned char { + X = 1, + Y = 2, + Z = 4, + M = 8, + }; + + enum Ordinates : unsigned char { + XY = Ordinate::X | Ordinate::Y, + XYZ = Ordinate::X | Ordinate::Y | Ordinate::Z, + XYM = Ordinate::X | Ordinate::Y | Ordinate::M, + XYZM = Ordinate::X | Ordinate::Y | Ordinate::Z | Ordinate::M + }; + + explicit OrdinateSet(Ordinates o) : m_value(o), m_changesAllowed(true) {} + + Ordinates m_value; + bool m_changesAllowed; + +public: + + static OrdinateSet createXY() { + return OrdinateSet(Ordinates::XY); + } + + static OrdinateSet createXYZ() { + return OrdinateSet(Ordinates::XYZ); + } + + static OrdinateSet createXYM() { + return OrdinateSet(Ordinates::XYM); + } + + static OrdinateSet createXYZM() { + return OrdinateSet(Ordinates::XYZM); + } + + void setZ(bool value) { + if (hasZ() != value) { + if (m_changesAllowed) { + m_value = static_cast(static_cast(m_value) ^ Ordinate::Z); + } else { + throw util::GEOSException("Cannot add additional ordinates."); + } + } + } + + void setM(bool value) { + if (hasM() != value) { + if (m_changesAllowed){ + m_value = static_cast(static_cast(m_value) ^ Ordinate::M); + } else { + throw util::GEOSException("Cannot add additional ordinates."); + } + } + } + + bool hasZ() const { + return static_cast(m_value) & static_cast(Ordinate::Z); + } + + bool hasM() const { + return static_cast(m_value) & static_cast(Ordinate::M); + } + + int size() const { + return 2 + hasZ() + hasM(); + } + + bool changesAllowed() const { + return m_changesAllowed; + } + + void setChangesAllowed(bool allowed) { + m_changesAllowed = allowed; + } + + bool operator==(const OrdinateSet& other) const { + return this->m_value == other.m_value; + } + + bool operator!=(const OrdinateSet& other) const { + return !(*this == other); + } + +}; + +} +} diff --git a/Sources/geos/include/geos/io/WKBConstants.h b/Sources/geos/include/geos/io/WKBConstants.h index 51d9615..4385748 100644 --- a/Sources/geos/include/geos/io/WKBConstants.h +++ b/Sources/geos/include/geos/io/WKBConstants.h @@ -37,7 +37,12 @@ namespace WKBConstants { wkbMultiPoint = 4, wkbMultiLineString = 5, wkbMultiPolygon = 6, - wkbGeometryCollection = 7 + wkbGeometryCollection = 7, + wkbCircularString = 8, + wkbCompoundCurve = 9, + wkbCurvePolygon = 10, + wkbMultiCurve = 11, + wkbMultiSurface = 12, }; enum wkbFlavour { diff --git a/Sources/geos/include/geos/io/WKBReader.h b/Sources/geos/include/geos/io/WKBReader.h index c050f26..9d410c2 100644 --- a/Sources/geos/include/geos/io/WKBReader.h +++ b/Sources/geos/include/geos/io/WKBReader.h @@ -20,7 +20,8 @@ #pragma once #include - +#include +#include #include // for composition #include // ostream, istream @@ -28,8 +29,6 @@ // #include #include -#define BAD_GEOM_TYPE_MSG "Bad geometry type encountered in" - #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class @@ -41,17 +40,24 @@ namespace geom { class GeometryFactory; class Coordinate; +class CircularString; +class CompoundCurve; +class CurvePolygon; class Geometry; +enum GeometryTypeId : int; class GeometryCollection; class Point; class LineString; class LinearRing; class Polygon; +class MultiCurve; class MultiPoint; class MultiLineString; class MultiPolygon; +class MultiSurface; class PrecisionModel; class CoordinateSequence; +class SimpleCurve; } // namespace geom } // namespace geos @@ -82,7 +88,7 @@ class GEOS_DLL WKBReader { WKBReader(geom::GeometryFactory const& f); - /// Inizialize parser with default GeometryFactory. + /// Initialize parser with default GeometryFactory. WKBReader(); void setFixStructure(bool doFixStructure); @@ -148,22 +154,42 @@ class GEOS_DLL WKBReader { std::unique_ptr readLinearRing(); + std::unique_ptr readCircularString(); + + std::unique_ptr readCompoundCurve(); + std::unique_ptr readPolygon(); + std::unique_ptr readCurvePolygon(); + std::unique_ptr readMultiPoint(); std::unique_ptr readMultiLineString(); + std::unique_ptr readMultiCurve(); + std::unique_ptr readMultiPolygon(); + std::unique_ptr readMultiSurface(); + std::unique_ptr readGeometryCollection(); std::unique_ptr readCoordinateSequence(unsigned int); // throws IOException - void minMemSize(int geomType, uint64_t size); + void minMemSize(geom::GeometryTypeId geomType, uint64_t size) const; void readCoordinate(); // throws IOException + template + std::unique_ptr readChild() + { + auto g = readGeometry(); + if (dynamic_cast(g.get())) { + return std::unique_ptr(static_cast(g.release())); + } + throw io::ParseException(std::string("Expected ") + geom::GeometryTypeName::name + " but got " + g->getGeometryType()); + } + // Declare type as noncopyable WKBReader(const WKBReader& other) = delete; WKBReader& operator=(const WKBReader& rhs) = delete; diff --git a/Sources/geos/include/geos/io/WKBStreamReader.h b/Sources/geos/include/geos/io/WKBStreamReader.h new file mode 100644 index 0000000..5402ff9 --- /dev/null +++ b/Sources/geos/include/geos/io/WKBStreamReader.h @@ -0,0 +1,49 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + +// Forward declarations +namespace geos { +namespace geom { +class Geometry; +class PrecisionModel; +} +} + +namespace geos { +namespace io { + +class GEOS_DLL WKBStreamReader { + +public: + WKBStreamReader(std::istream& instr); + ~WKBStreamReader(); + + std::unique_ptr next(); + +private: + + std::istream& instr; + WKBReader rdr; +}; + +} +} + + diff --git a/Sources/geos/include/geos/io/WKBWriter.h b/Sources/geos/include/geos/io/WKBWriter.h index b384d14..90b2fbc 100644 --- a/Sources/geos/include/geos/io/WKBWriter.h +++ b/Sources/geos/include/geos/io/WKBWriter.h @@ -22,6 +22,7 @@ #include #include // for getMachineByteOrder +#include #include #include #include @@ -32,6 +33,8 @@ namespace geos { namespace geom { class CoordinateSequence; +class CompoundCurve; +class CurvePolygon; class Geometry; class GeometryCollection; class Point; @@ -42,6 +45,7 @@ class MultiPoint; class MultiLineString; class MultiPolygon; class PrecisionModel; +class SimpleCurve; } // namespace geom } // namespace geos @@ -57,7 +61,7 @@ namespace io { * * The WKB format is specified in the OGC Simple Features for SQL specification. * This implementation supports the extended WKB standard for representing - * 3-dimensional coordinates. The presence of 3D coordinates is signified + * Z and M coordinates. The presence of Z and/or M coordinates is signified * by setting the high bit of the wkbType word. * * Empty Points cannot be represented in WKB; an @@ -79,15 +83,16 @@ class GEOS_DLL WKBWriter { * Initializes writer with target coordinate dimension, endianness * flag and SRID value. * - * @param dims Supported values are 2 or 3. Note that 3 indicates - * up to 3 dimensions will be written but 2D WKB is still produced for 2D geometries. + * @param dims Supported values are 2, 3 or 4. Note that 4 indicates + * up to 4 dimensions will be written but (e.g.) 2D WKB is still produced + * for 2D geometries. Default since GEOS 3.12 is 4. * @param bo output byte order - default to native machine byte order. * Legal values include 0 (big endian/xdr) and 1 (little endian/ndr). * @param incudeSRID true if SRID should be included in WKB (an * extension). */ WKBWriter( - uint8_t dims = 2, + uint8_t dims = 4, int bo = getMachineByteOrder(), bool includeSRID = false, int flv = WKBConstants::wkbExtended); @@ -112,9 +117,9 @@ class GEOS_DLL WKBWriter { /* * Sets the output dimension used by the WKBWriter. * - * @param newOutputDimension Supported values are 2 or 3. - * Note that 3 indicates up to 3 dimensions will be written but - * 2D WKB is still produced for 2D geometries. + * @param newOutputDimension Supported values are 2, 3 or 4. + * Note that 4 indicates up to 4 dimensions will be written but + * (e.g.) 2D WKB is still produced for 2D geometries. */ void setOutputDimension(uint8_t newOutputDimension); @@ -192,11 +197,13 @@ class GEOS_DLL WKBWriter { void writeHEX(const geom::Geometry& g, std::ostream& os); // throws IOException, ParseException + static int getWkbType(const geom::Geometry&); + private: - // 2 or 3 + // 2, 3, or 4 uint8_t defaultOutputDimension; - uint8_t outputDimension; + OrdinateSet outputOrdinates; // WKBConstants::wkbwkbXDR | WKBConstants::wkbNDR int byteOrder; @@ -213,19 +220,23 @@ class GEOS_DLL WKBWriter { void writePointEmpty(const geom::Point& p); // throws IOException - void writeLineString(const geom::LineString& ls); + void writeSimpleCurve(const geom::SimpleCurve& ls); // throws IOException + void writeCompoundCurve(const geom::CompoundCurve& curve); + void writePolygon(const geom::Polygon& p); // throws IOException - void writeGeometryCollection(const geom::GeometryCollection& c, int wkbtype); + void writeCurvePolygon(const geom::CurvePolygon& p); + + void writeGeometryCollection(const geom::GeometryCollection& gc); // throws IOException, ParseException void writeCoordinateSequence(const geom::CoordinateSequence& cs, bool sized); // throws IOException - void writeCoordinate(const geom::CoordinateSequence& cs, std::size_t idx, bool is3d); + void writeCoordinate(const geom::CoordinateSequence& cs, std::size_t idx); // throws IOException void writeGeometryType(int geometryType, int SRID); @@ -240,6 +251,8 @@ class GEOS_DLL WKBWriter { void writeInt(int intValue); // throws IOException + OrdinateSet getOutputOrdinates(OrdinateSet ordinates); + }; } // namespace io diff --git a/Sources/geos/include/geos/io/WKTFileReader.h b/Sources/geos/include/geos/io/WKTFileReader.h new file mode 100644 index 0000000..9785d6a --- /dev/null +++ b/Sources/geos/include/geos/io/WKTFileReader.h @@ -0,0 +1,46 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + +// Forward declarations +namespace geos { +namespace geom { + +class Geometry; +class PrecisionModel; +} +} + +namespace geos { +namespace io { + +class GEOS_DLL WKTFileReader { + +public: + WKTFileReader(); + ~WKTFileReader(); + + std::vector> read(std::string fname); + +private: + std::unique_ptr readGeom(std::ifstream& f, geos::io::WKTReader& rdr); +}; + +} +} diff --git a/Sources/geos/include/geos/io/WKTReader.h b/Sources/geos/include/geos/io/WKTReader.h index c2428fa..7294d64 100644 --- a/Sources/geos/include/geos/io/WKTReader.h +++ b/Sources/geos/include/geos/io/WKTReader.h @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -39,10 +40,15 @@ class GeometryCollection; class Point; class LineString; class LinearRing; +class CircularString; +class CompoundCurve; class Polygon; +class CurvePolygon; class MultiPoint; class MultiLineString; +class MultiCurve; class MultiPolygon; +class MultiSurface; class PrecisionModel; } } @@ -111,29 +117,45 @@ class GEOS_DLL WKTReader { std::unique_ptr read(const std::string& wellKnownText) const; protected: - std::unique_ptr getCoordinates(io::StringTokenizer* tokenizer) const; + std::unique_ptr getCoordinates(io::StringTokenizer* tokenizer, OrdinateSet& ordinates) const; static double getNextNumber(io::StringTokenizer* tokenizer); - static std::string getNextEmptyOrOpener(io::StringTokenizer* tokenizer, std::size_t& dim); + static std::string getNextEmptyOrOpener(io::StringTokenizer* tokenizer, OrdinateSet& dim); static std::string getNextCloserOrComma(io::StringTokenizer* tokenizer); static std::string getNextCloser(io::StringTokenizer* tokenizer); static std::string getNextWord(io::StringTokenizer* tokenizer); - std::unique_ptr readGeometryTaggedText(io::StringTokenizer* tokenizer) const; - std::unique_ptr readPointText(io::StringTokenizer* tokenizer) const; - std::unique_ptr readLineStringText(io::StringTokenizer* tokenizer) const; - std::unique_ptr readLinearRingText(io::StringTokenizer* tokenizer) const; - std::unique_ptr readMultiPointText(io::StringTokenizer* tokenizer) const; - std::unique_ptr readPolygonText(io::StringTokenizer* tokenizer) const; - std::unique_ptr readMultiLineStringText(io::StringTokenizer* tokenizer) const; - std::unique_ptr readMultiPolygonText(io::StringTokenizer* tokenizer) const; - std::unique_ptr readGeometryCollectionText(io::StringTokenizer* tokenizer) const; + std::unique_ptr readGeometryTaggedText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags, const geom::GeometryTypeId* emptyType = nullptr) const; + + std::unique_ptr readPointText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + std::unique_ptr readLineStringText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + std::unique_ptr readLinearRingText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + std::unique_ptr readMultiPointText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + std::unique_ptr readPolygonText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + std::unique_ptr readMultiLineStringText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + std::unique_ptr readMultiPolygonText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + std::unique_ptr readGeometryCollectionText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + std::unique_ptr readCircularStringText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + std::unique_ptr readCompoundCurveText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + std::unique_ptr readCurvePolygonText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + std::unique_ptr readMultiCurveText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + std::unique_ptr readMultiSurfaceText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + + /// Read the contents of a LINEARRING, LINESTRING, CIRCULARSTRING, or COMPOUNDCURVE + std::unique_ptr readCurveText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; + + /// Read the contents of a POLYGON or a CURVEPOLYGON + std::unique_ptr readSurfaceText(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const; private: const geom::GeometryFactory* geometryFactory; const geom::PrecisionModel* precisionModel; bool fixStructure; - void getPreciseCoordinate(io::StringTokenizer* tokenizer, geom::Coordinate&, std::size_t& dim) const; + void getPreciseCoordinate(io::StringTokenizer* tokenizer, OrdinateSet& ordinateFlags, geom::CoordinateXYZM&) const; static bool isNumberNext(io::StringTokenizer* tokenizer); + static bool isOpenerNext(io::StringTokenizer* tokenizer); + + static void readOrdinateFlags(const std::string & s, OrdinateSet& ordinateFlags); + static bool isTypeName(const std::string & type, const std::string & typeName); }; } // namespace io diff --git a/Sources/geos/include/geos/io/WKTStreamReader.h b/Sources/geos/include/geos/io/WKTStreamReader.h new file mode 100644 index 0000000..e9b7d7c --- /dev/null +++ b/Sources/geos/include/geos/io/WKTStreamReader.h @@ -0,0 +1,48 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + +// Forward declarations +namespace geos { +namespace geom { + +class Geometry; +class PrecisionModel; +} +} + +namespace geos { +namespace io { + +class GEOS_DLL WKTStreamReader { + +public: + WKTStreamReader(std::istream& instr); + ~WKTStreamReader(); + + std::unique_ptr next(); + +private: + + std::istream& instr; + WKTReader rdr; +}; + +} +} diff --git a/Sources/geos/include/geos/io/WKTWriter.h b/Sources/geos/include/geos/io/WKTWriter.h index f1b9a7e..105b637 100644 --- a/Sources/geos/include/geos/io/WKTWriter.h +++ b/Sources/geos/include/geos/io/WKTWriter.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include #include @@ -35,7 +36,11 @@ namespace geos { namespace geom { class Coordinate; +class CoordinateXY; +class CoordinateXYZM; class CoordinateSequence; +class Curve; +class CompoundCurve; class Geometry; class GeometryCollection; class Point; @@ -46,6 +51,8 @@ class MultiPoint; class MultiLineString; class MultiPolygon; class PrecisionModel; +class SimpleCurve; +class Surface; } namespace io { class Writer; @@ -88,6 +95,8 @@ class GEOS_DLL WKTWriter { /// Returns WKT string for the given Geometry std::string write(const geom::Geometry* geometry); + std::string write(const geom::Geometry& geometry); + // Send Geometry's WKT to the given Writer void write(const geom::Geometry* geometry, Writer* writer); @@ -112,7 +121,7 @@ class GEOS_DLL WKTWriter { * * @return the WKT */ - static std::string toLineString(const geom::Coordinate& p0, const geom::Coordinate& p1); + static std::string toLineString(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1); /** * Generates the WKT for a Point. @@ -122,6 +131,7 @@ class GEOS_DLL WKTWriter { * @return the WKT */ static std::string toPoint(const geom::Coordinate& p0); + static std::string toPoint(const geom::CoordinateXY& p0); /** * Sets the rounding precision when writing the WKT @@ -140,10 +150,22 @@ class GEOS_DLL WKTWriter { */ void setTrim(bool p0); + /** + * Enables/disables removal of Z/M dimensions that have + * no non-NaN values in a geometry. + * + * @brief setRemoveEmptyDimensions + * @param remove + */ + void setRemoveEmptyDimensions(bool remove) + { + removeEmptyDimensions = remove; + } + /** * Enable old style 3D/4D WKT generation. * - * By default the WKBWriter produces new style 3D/4D WKT + * By default the WKTWriter produces new style 3D/4D WKT * (ie. "POINT Z (10 20 30)") but if this method is used * to turn on old style WKT production then the WKT will * be formatted in the style "POINT (10 20 30)". @@ -159,7 +181,7 @@ class GEOS_DLL WKTWriter { /* * \brief * Returns the output dimension used by the - * WKBWriter. + * WKTWriter. */ int getOutputDimension() const @@ -168,83 +190,122 @@ class GEOS_DLL WKTWriter { } /* - * Sets the output dimension used by the WKBWriter. + * Sets the output dimension used by the WKTWriter. * - * @param newOutputDimension Supported values are 2 or 3. + * @param newOutputDimension Supported values are 2, 3 or 4. + * Default since GEOS 3.12 is 4. * Note that 3 indicates up to 3 dimensions will be - * written but 2D WKB is still produced for 2D geometries. + * written but 2D WKT is still produced for 2D geometries. */ void setOutputDimension(uint8_t newOutputDimension); + static std::string writeNumber(double d, bool trim, uint32_t precision); + static int writeTrimmedNumber(double d, uint32_t precision, char* buf); + protected: int decimalPlaces; - void appendGeometryTaggedText(const geom::Geometry* geometry, int level, Writer* writer); + void appendGeometryTaggedText( + const geom::Geometry& geometry, + OrdinateSet outputOrdinates, + int level, + Writer& writer) const; + + void appendTag( + const geom::Geometry& geometry, + OrdinateSet outputOrdinates, + Writer& writer) const; void appendPointTaggedText( - const geom::Coordinate* coordinate, - int level, Writer* writer); + const geom::Point& point, + OrdinateSet outputOrdinates, + int level, Writer& writer) const; - void appendLineStringTaggedText( - const geom::LineString* lineString, - int level, Writer* writer); + void appendSimpleCurveTaggedText( + const geom::SimpleCurve& lineString, + OrdinateSet outputOrdinates, + int level, Writer& writer) const; - void appendLinearRingTaggedText( - const geom::LinearRing* lineString, - int level, Writer* writer); + void appendCompoundCurveTaggedText( + const geom::CompoundCurve& lineString, + OrdinateSet outputOrdinates, + int level, Writer& writer) const; - void appendPolygonTaggedText( - const geom::Polygon* polygon, - int level, Writer* writer); + void appendSurfaceTaggedText( + const geom::Surface& polygon, + OrdinateSet outputOrdinates, + int level, Writer& writer) const; void appendMultiPointTaggedText( - const geom::MultiPoint* multipoint, - int level, Writer* writer); + const geom::MultiPoint& multipoint, + OrdinateSet outputOrdinates, + int level, Writer& writer) const; - void appendMultiLineStringTaggedText( - const geom::MultiLineString* multiLineString, - int level, Writer* writer); + void appendMultiCurveTaggedText( + const geom::GeometryCollection& multiCurve, + OrdinateSet outputOrdinates, + int level, Writer& writer) const; - void appendMultiPolygonTaggedText( - const geom::MultiPolygon* multiPolygon, - int level, Writer* writer); + void appendMultiSurfaceTaggedText( + const geom::GeometryCollection& multiSurface, + OrdinateSet outputOrdinates, + int level, Writer& writer) const; void appendGeometryCollectionTaggedText( - const geom::GeometryCollection* geometryCollection, - int level, Writer* writer); + const geom::GeometryCollection& geometryCollection, + OrdinateSet outputOrdinates, + int level, Writer& writer) const; + + void appendOrdinateText(OrdinateSet outputOrdinates, + Writer& writer) const; - void appendPointText(const geom::Coordinate* coordinate, int level, - Writer* writer); + void appendSequenceText(const geom::CoordinateSequence& seq, + OrdinateSet outputOrdinates, + int level, + bool doIntent, + Writer& writer) const; - void appendCoordinate(const geom::Coordinate* coordinate, - Writer* writer); + void appendCoordinate(const geom::CoordinateXYZM& coordinate, + OrdinateSet outputOrdinates, + Writer& writer) const; std::string writeNumber(double d) const; - void appendLineStringText( - const geom::LineString* lineString, - int level, bool doIndent, Writer* writer); + void appendCurveText( + const geom::Curve& lineString, + OrdinateSet outputOrdinates, + int level, bool doIndent, Writer& writer) const; - void appendPolygonText( - const geom::Polygon* polygon, - int level, bool indentFirst, Writer* writer); + void appendSimpleCurveText( + const geom::SimpleCurve& lineString, + OrdinateSet outputOrdinates, + int level, bool doIndent, Writer& writer) const; + + void appendSurfaceText( + const geom::Surface& polygon, + OrdinateSet outputOrdinates, + int level, bool indentFirst, Writer& writer) const; void appendMultiPointText( - const geom::MultiPoint* multiPoint, - int level, Writer* writer); + const geom::MultiPoint& multiPoint, + OrdinateSet outputOrdinates, + int level, Writer& writer) const; - void appendMultiLineStringText( - const geom::MultiLineString* multiLineString, - int level, bool indentFirst, Writer* writer); + void appendMultiCurveText( + const geom::GeometryCollection& multiCurve, + OrdinateSet outputOrdinates, + int level, bool indentFirst, Writer& writer) const; - void appendMultiPolygonText( - const geom::MultiPolygon* multiPolygon, - int level, Writer* writer); + void appendMultiSurfaceText( + const geom::GeometryCollection& multiSurface, + OrdinateSet outputOrdinates, + int level, Writer& writer) const; void appendGeometryCollectionText( - const geom::GeometryCollection* geometryCollection, - int level, Writer* writer); + const geom::GeometryCollection& geometryCollection, + OrdinateSet outputOrdinates, + int level, Writer& writer) const; private: @@ -252,18 +313,17 @@ class GEOS_DLL WKTWriter { INDENT = 2 }; -// static const int INDENT = 2; - bool isFormatted; int roundingPrecision; bool trim; - int level; + bool removeEmptyDimensions = false; + + static constexpr int coordsPerLine = 10; uint8_t defaultOutputDimension; - uint8_t outputDimension; bool old3D; void writeFormatted( diff --git a/Sources/geos/include/geos/linearref/LengthIndexedLine.h b/Sources/geos/include/geos/linearref/LengthIndexedLine.h index 4629571..d5c8237 100644 --- a/Sources/geos/include/geos/linearref/LengthIndexedLine.h +++ b/Sources/geos/include/geos/linearref/LengthIndexedLine.h @@ -157,7 +157,7 @@ class GEOS_DLL LengthIndexedLine { * * (The subline must **conform** to the line; that is, * all vertices in the subline (except possibly the first and last) - * must be vertices of the line and occcur in the same order). + * must be vertices of the line and occur in the same order). * * @param subLine a subLine of the line * @return a pair of indices for the start and end of the subline. diff --git a/Sources/geos/include/geos/linearref/LinearGeometryBuilder.h b/Sources/geos/include/geos/linearref/LinearGeometryBuilder.h index cb1a2fe..7c20e6b 100644 --- a/Sources/geos/include/geos/linearref/LinearGeometryBuilder.h +++ b/Sources/geos/include/geos/linearref/LinearGeometryBuilder.h @@ -40,14 +40,14 @@ class LinearGeometryBuilder { private: const geom::GeometryFactory* geomFact; - typedef std::vector GeomPtrVect; + typedef std::vector> GeomPtrVect; // Geometry elements owned by this class GeomPtrVect lines; bool ignoreInvalidLines; bool fixInvalidLines; - geom::CoordinateArraySequence* coordList; + std::unique_ptr coordList; geom::Coordinate lastPt; @@ -98,7 +98,7 @@ class LinearGeometryBuilder { /// Terminate the current LineString. void endLine(); - geom::Geometry* getGeometry(); + std::unique_ptr getGeometry(); }; } // namespace geos.linearref diff --git a/Sources/geos/include/geos/linearref/LocationIndexOfPoint.h b/Sources/geos/include/geos/linearref/LocationIndexOfPoint.h index 00c17c8..bbb69ae 100644 --- a/Sources/geos/include/geos/linearref/LocationIndexOfPoint.h +++ b/Sources/geos/include/geos/linearref/LocationIndexOfPoint.h @@ -39,12 +39,12 @@ class LocationIndexOfPoint { private: const geom::Geometry* linearGeom; - LinearLocation indexOfFromStart(const geom::Coordinate& inputPt, const LinearLocation* minIndex) const; + LinearLocation indexOfFromStart(const geom::CoordinateXY& inputPt, const LinearLocation* minIndex) const; public: - static LinearLocation indexOf(const geom::Geometry* linearGeom, const geom::Coordinate& inputPt); + static LinearLocation indexOf(const geom::Geometry* linearGeom, const geom::CoordinateXY& inputPt); - static LinearLocation indexOfAfter(const geom::Geometry* linearGeom, const geom::Coordinate& inputPt, + static LinearLocation indexOfAfter(const geom::Geometry* linearGeom, const geom::CoordinateXY& inputPt, const LinearLocation* minIndex); LocationIndexOfPoint(const geom::Geometry* linearGeom); @@ -56,7 +56,7 @@ class LocationIndexOfPoint { * @param inputPt the coordinate to locate * @return the location of the nearest point */ - LinearLocation indexOf(const geom::Coordinate& inputPt) const; + LinearLocation indexOf(const geom::CoordinateXY& inputPt) const; /** \brief * Find the nearest LinearLocation along the linear [Geometry](@ref geom::Geometry) @@ -71,7 +71,7 @@ class LocationIndexOfPoint { * @param minIndex the minimum location for the point location * @return the location of the nearest point */ - LinearLocation indexOfAfter(const geom::Coordinate& inputPt, const LinearLocation* minIndex) const; + LinearLocation indexOfAfter(const geom::CoordinateXY& inputPt, const LinearLocation* minIndex) const; }; } } diff --git a/Sources/geos/include/geos/math/DD.h b/Sources/geos/include/geos/math/DD.h index 9f2ea97..61e3a08 100644 --- a/Sources/geos/include/geos/math/DD.h +++ b/Sources/geos/include/geos/math/DD.h @@ -93,6 +93,7 @@ #pragma once #include +#include namespace geos { namespace math { // geos.math diff --git a/Sources/geos/include/geos/namespaces.h b/Sources/geos/include/geos/namespaces.h index dd71a21..61132b8 100644 --- a/Sources/geos/include/geos/namespaces.h +++ b/Sources/geos/include/geos/namespaces.h @@ -143,7 +143,7 @@ namespace index { // geos.index /// formats. /// /// The Java Topology Suite (JTS) is a Java API that implements a core set of -/// spatial data operations usin g an explicit precision model and robust +/// spatial data operations using an explicit precision model and robust /// geometric algorithms. JTS is intended to be used in the devel opment of /// applications that support the validation, cleaning, integration and /// querying of spatial data sets. diff --git a/Sources/geos/include/geos/noding/BasicSegmentString.h b/Sources/geos/include/geos/noding/BasicSegmentString.h index 5b06bbf..1d253d6 100644 --- a/Sources/geos/include/geos/noding/BasicSegmentString.h +++ b/Sources/geos/include/geos/noding/BasicSegmentString.h @@ -53,38 +53,12 @@ class GEOS_DLL BasicSegmentString : public SegmentString { BasicSegmentString(geom::CoordinateSequence* newPts, const void* newContext) : - SegmentString(newContext), - pts(newPts) + SegmentString(newContext, newPts) {} ~BasicSegmentString() override {} - // see dox in SegmentString.h - size_t - size() const override - { - return pts->size(); - } - - // see dox in SegmentString.h - const geom::Coordinate& getCoordinate(std::size_t i) const override - { - return pts->getAt(i); - }; - - /// @see SegmentString::getCoordinates() const - geom::CoordinateSequence* getCoordinates() const override - { - return pts; - }; - - // see dox in SegmentString.h - bool isClosed() const override - { - return pts->getAt(0) == pts->getAt(size() - 1); - }; - // see dox in SegmentString.h std::ostream& print(std::ostream& os) const override; @@ -105,8 +79,6 @@ class GEOS_DLL BasicSegmentString : public SegmentString { private: - geom::CoordinateSequence* pts; - // Declare type as noncopyable BasicSegmentString(const BasicSegmentString& other) = delete; BasicSegmentString& operator=(const BasicSegmentString& rhs) = delete; diff --git a/Sources/geos/include/geos/noding/BoundaryChainNoder.h b/Sources/geos/include/geos/noding/BoundaryChainNoder.h new file mode 100644 index 0000000..890f400 --- /dev/null +++ b/Sources/geos/include/geos/noding/BoundaryChainNoder.h @@ -0,0 +1,171 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) Martin Davis 2022 + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include // for composition +#include // for composition +#include // for composition + +#include + +// Forward declarations +namespace geos { +namespace geom { +class CoordinateSequence; +class Coordinate; +} +namespace noding { +class NodedSegmentString; +} +} + +using geos::geom::Coordinate; + +namespace geos { // geos +namespace noding { // geos::noding + +/** + * A noder which extracts chains of boundary segments + * as SegmentStrings from a polygonal coverage. + * Boundary segments are those which are not duplicated in the input polygonal coverage. + * Extracting chains of segments minimize the number of segment strings created, + * which produces a more efficient topological graph structure. + *

+ * This enables fast overlay of polygonal coverages in CoverageUnion. + * Using this noder is faster than SegmentExtractingNoder + * and BoundarySegmentNoder. + *

+ * No precision reduction is carried out. + * If that is required, another noder must be used (such as a snap-rounding noder), + * or the input must be precision-reduced beforehand. + * + * @author Martin Davis + * + */ +class GEOS_DLL BoundaryChainNoder : public Noder { + +private: + + class BoundarySegmentMap { + + private: + + // Members + SegmentString* segString; + std::vector isBoundary; + + static SegmentString* createChain( + const SegmentString* segString, + std::size_t startIndex, + std::size_t endIndex, + bool constructZ, + bool constructM); + + std::size_t findChainStart(std::size_t index) const; + std::size_t findChainEnd(std::size_t index) const; + + public: + + BoundarySegmentMap(SegmentString* ss) + : segString(ss) { + isBoundary.resize(ss->size()-1, false); + }; + + void setBoundarySegment(std::size_t index); + void createChains(std::vector& chainList, bool constructZ, bool constructM); + }; + + class Segment { + public: + Segment(const geom::CoordinateSequence& seq, + BoundarySegmentMap& segMap, + std::size_t index) + : m_seq(seq) + , m_segMap(segMap) + , m_index(index) + , m_flip(seq.getAt(index).compareTo(seq.getAt(index + 1)) < 0) + {} + + const geom::CoordinateXY& p0() const { + return m_seq.getAt(m_flip ? m_index : m_index + 1); + } + + const geom::CoordinateXY& p1() const { + return m_seq.getAt(m_flip ? m_index + 1 : m_index); + } + + void markInBoundary() const { + m_segMap.setBoundarySegment(m_index); + }; + + bool operator==(const Segment& other) const { + return p0().equals2D(other.p0()) && p1().equals2D(other.p1()); + } + + struct HashCode { + std::size_t operator()(const Segment& s) const { + std::size_t h = std::hash{}(s.p0().x); + h ^= (std::hash{}(s.p0().y) << 1); + h ^= (std::hash{}(s.p1().x) << 1); + h ^= (std::hash{}(s.p1().y) << 1); + return h; + } + }; + + private: + const geom::CoordinateSequence& m_seq; + BoundarySegmentMap& m_segMap; + std::size_t m_index; + bool m_flip; + }; + +public: + using SegmentSet = std::unordered_set; + + BoundaryChainNoder() : chainList(nullptr), m_constructZ(false), m_constructM(false) {}; + + // Noder virtual methods + std::vector* getNodedSubstrings() const override; + void computeNodes(std::vector* inputSegStrings) override; + + +private: + + // Members + std::vector* chainList; + bool m_constructZ; + bool m_constructM; + + // Methods + void addSegments(std::vector* segStrings, + SegmentSet& segSet, + std::vector& includedSegs); + + static void addSegments(SegmentString* segString, + BoundarySegmentMap& segInclude, + SegmentSet& segSet); + + static void markBoundarySegments(SegmentSet& segSet); + + std::vector* extractChains(std::vector& sections) const; + + static bool segSetContains(SegmentSet& segSet, Segment& seg); + +}; + +} // namespace geos::noding +} // namespace geos + + diff --git a/Sources/geos/include/geos/noding/IntersectionAdder.h b/Sources/geos/include/geos/noding/IntersectionAdder.h index 50ebe24..14fbf00 100644 --- a/Sources/geos/include/geos/noding/IntersectionAdder.h +++ b/Sources/geos/include/geos/noding/IntersectionAdder.h @@ -65,7 +65,7 @@ class GEOS_DLL IntersectionAdder: public SegmentIntersector { bool hasInterior; // the proper intersection point found - geom::Coordinate properIntersectionPoint; + geom::CoordinateXYZM properIntersectionPoint; algorithm::LineIntersector& li; // bool isSelfIntersection; @@ -117,7 +117,7 @@ class GEOS_DLL IntersectionAdder: public SegmentIntersector { * @return the proper intersection point, or `Coordinate::getNull()` * if none was found */ - const geom::Coordinate& + const geom::CoordinateXYZM& getProperIntersectionPoint() { return properIntersectionPoint; diff --git a/Sources/geos/include/geos/noding/IteratedNoder.h b/Sources/geos/include/geos/noding/IteratedNoder.h index 32803a2..5f231fc 100644 --- a/Sources/geos/include/geos/noding/IteratedNoder.h +++ b/Sources/geos/include/geos/noding/IteratedNoder.h @@ -66,7 +66,7 @@ class GEOS_DLL IteratedNoder : public Noder { // implements Noder */ void node(std::vector* segStrings, int& numInteriorIntersections, - geom::Coordinate& intersectionPoint); + geom::CoordinateXY& intersectionPoint); public: diff --git a/Sources/geos/include/geos/noding/MCIndexSegmentSetMutualIntersector.h b/Sources/geos/include/geos/noding/MCIndexSegmentSetMutualIntersector.h index a4e04c8..770784b 100644 --- a/Sources/geos/include/geos/noding/MCIndexSegmentSetMutualIntersector.h +++ b/Sources/geos/include/geos/noding/MCIndexSegmentSetMutualIntersector.h @@ -10,11 +10,6 @@ * by the Free Software Foundation. * See the COPYING file for more information. * - * - ********************************************************************** - * - * Last port: noding/MCIndexSegmentSetMutualIntersector.java r388 (JTS-1.12) - * **********************************************************************/ #pragma once @@ -25,18 +20,15 @@ #include // inherited namespace geos { -namespace index { -class SpatialIndex; - -namespace chain { -} -namespace strtree { -//class STRtree; +namespace geom { + class Envelope; } +namespace index { + class SpatialIndex; } namespace noding { -class SegmentString; -class SegmentIntersector; + class SegmentString; + class SegmentIntersector; } } @@ -52,15 +44,31 @@ namespace noding { // geos::noding * * @version 1.7 */ -class MCIndexSegmentSetMutualIntersector : public SegmentSetMutualIntersector { +class GEOS_DLL MCIndexSegmentSetMutualIntersector : public SegmentSetMutualIntersector { public: - MCIndexSegmentSetMutualIntersector() + MCIndexSegmentSetMutualIntersector(double p_tolerance) + : monoChains() + , indexCounter(0) + , processCounter(0) + , nOverlaps(0) + , overlapTolerance(p_tolerance) + , indexBuilt(false) + , envelope(nullptr) + {} + + MCIndexSegmentSetMutualIntersector(const geom::Envelope* p_envelope) : monoChains() , indexCounter(0) , processCounter(0) , nOverlaps(0) + , overlapTolerance(0.0) , indexBuilt(false) + , envelope(p_envelope) + {} + + MCIndexSegmentSetMutualIntersector() + : MCIndexSegmentSetMutualIntersector(0.0) {} ~MCIndexSegmentSetMutualIntersector() override @@ -116,12 +124,14 @@ class MCIndexSegmentSetMutualIntersector : public SegmentSetMutualIntersector { int processCounter; // statistics int nOverlaps; + double overlapTolerance; /* memory management helper, holds MonotoneChain objects used * in the SpatialIndex. It's cleared when the SpatialIndex is */ bool indexBuilt; MonoChains indexChains; + const geom::Envelope* envelope; void addToIndex(SegmentString* segStr); diff --git a/Sources/geos/include/geos/noding/NodableSegmentString.h b/Sources/geos/include/geos/noding/NodableSegmentString.h index 2d96d0a..8b4e0e1 100644 --- a/Sources/geos/include/geos/noding/NodableSegmentString.h +++ b/Sources/geos/include/geos/noding/NodableSegmentString.h @@ -37,9 +37,9 @@ class GEOS_DLL NodableSegmentString : public SegmentString { private: protected: public: - NodableSegmentString(const void* newContext) + NodableSegmentString(const void* newContext, geom::CoordinateSequence* newSeq) : - SegmentString(newContext) + SegmentString(newContext, newSeq) { } /** diff --git a/Sources/geos/include/geos/noding/NodedSegmentString.h b/Sources/geos/include/geos/noding/NodedSegmentString.h index 2e19c07..18f6f18 100644 --- a/Sources/geos/include/geos/noding/NodedSegmentString.h +++ b/Sources/geos/include/geos/noding/NodedSegmentString.h @@ -28,7 +28,6 @@ #include // for inlines #include #include // for inheritance -#include #include #include #include @@ -87,7 +86,7 @@ class GEOS_DLL NodedSegmentString : public NodableSegmentString { static SegmentString::NonConstVect* getNodedSubstrings( const SegmentString::NonConstVect& segStrings); - std::vector getNodedCoordinates(); + std::unique_ptr getNodedCoordinates(); bool hasNodes() const { @@ -99,60 +98,34 @@ class GEOS_DLL NodedSegmentString : public NodableSegmentString { * * @param newPts CoordinateSequence representing the string, * ownership transferred. - * + * @param constructZ should newly-constructed coordinates store Z values? + * @param constructM should newly-constructed coordinates store M values? * @param newContext the user-defined data of this segment string * (may be null) */ - NodedSegmentString(geom::CoordinateSequence* newPts, const void* newContext) - : NodableSegmentString(newContext) - , nodeList(this) - , pts(newPts) + NodedSegmentString(geom::CoordinateSequence* newPts, bool constructZ, bool constructM, const void* newContext) + : NodableSegmentString(newContext, newPts) + , nodeList(*this, constructZ, constructM) {} - NodedSegmentString(SegmentString* ss) - : NodableSegmentString(ss->getData()) - , nodeList(this) - , pts(ss->getCoordinates()->clone()) + NodedSegmentString(SegmentString* ss, bool constructZ, bool constructM) + : NodableSegmentString(ss->getData(), ss->getCoordinates()->clone().release()) + , nodeList(*this, constructZ, constructM) {} - ~NodedSegmentString() override = default; + ~NodedSegmentString() override { + delete seq; + } SegmentNodeList& getNodeList(); const SegmentNodeList& getNodeList() const; - size_t - size() const override - { - return pts->size(); - } - - const geom::Coordinate& getCoordinate(std::size_t i) const override; - - geom::CoordinateSequence* getCoordinates() const override; - geom::CoordinateSequence* releaseCoordinates(); - - bool isClosed() const override; + std::unique_ptr releaseCoordinates(); std::ostream& print(std::ostream& os) const override; - /** \brief - * Gets the octant of the segment starting at vertex index. - * - * @param index the index of the vertex starting the segment. - * Must not be the last index in the vertex list - * @return the octant of the segment at the vertex - */ - int getSegmentOctant(std::size_t index) const - { - if (index >= size() - 1) { - return -1; - } - return safeOctant(getCoordinate(index), getCoordinate(index + 1)); - //return Octant::octant(getCoordinate(index), getCoordinate(index+1)); - }; - /** \brief * Add {@link SegmentNode}s for one or both * intersections found for a segment of an edge to the edge @@ -179,7 +152,7 @@ class GEOS_DLL NodedSegmentString : public NodableSegmentString { { ::geos::ignore_unused_variable_warning(geomIndex); - const geom::Coordinate& intPt = li->getIntersection(intIndex); + const auto& intPt = li->getIntersection(intIndex); addIntersection(intPt, segmentIndex); }; @@ -190,7 +163,8 @@ class GEOS_DLL NodedSegmentString : public NodableSegmentString { * edge is normalized * to use the higher of the two possible segmentIndexes */ - void addIntersection(const geom::Coordinate& intPt, + template + void addIntersection(const CoordType& intPt, std::size_t segmentIndex) { std::size_t normalizedSegmentIndex = segmentIndex; @@ -202,7 +176,7 @@ class GEOS_DLL NodedSegmentString : public NodableSegmentString { // normalize the intersection point location auto nextSegIndex = normalizedSegmentIndex + 1; if (nextSegIndex < size()) { - const geom::Coordinate& nextPt = pts->getAt(nextSegIndex); + const auto& nextPt = getCoordinate(nextSegIndex); // Normalize segment index if intPt falls on vertex // The check for point equality is 2D only - @@ -217,22 +191,12 @@ class GEOS_DLL NodedSegmentString : public NodableSegmentString { * (unless the node is already known) */ nodeList.add(intPt, normalizedSegmentIndex); - }; + } private: SegmentNodeList nodeList; - std::unique_ptr pts; - - static int safeOctant(const geom::Coordinate& p0, const geom::Coordinate& p1) - { - if(p0.equals2D(p1)) { - return 0; - } - return Octant::octant(p0, p1); - }; - }; } // namespace geos::noding diff --git a/Sources/geos/include/geos/noding/Octant.h b/Sources/geos/include/geos/noding/Octant.h index 47a6f01..7b96c0a 100644 --- a/Sources/geos/include/geos/noding/Octant.h +++ b/Sources/geos/include/geos/noding/Octant.h @@ -21,7 +21,7 @@ // Forward declarations namespace geos { namespace geom { -class Coordinate; +class CoordinateXY; } } @@ -46,7 +46,7 @@ namespace noding { // geos.noding */ class GEOS_DLL Octant { private: - Octant() {} // Can't instanciate it + Octant() {} // Can't instantiate it public: /** @@ -58,10 +58,10 @@ class GEOS_DLL Octant { /** * Returns the octant of a directed line segment from p0 to p1. */ - static int octant(const geom::Coordinate& p0, const geom::Coordinate& p1); + static int octant(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1); static int - octant(const geom::Coordinate* p0, const geom::Coordinate* p1) + octant(const geom::CoordinateXY* p0, const geom::CoordinateXY* p1) { ::geos::ignore_unused_variable_warning(p0); return octant(*p0, *p1); diff --git a/Sources/geos/include/geos/noding/SegmentExtractingNoder.h b/Sources/geos/include/geos/noding/SegmentExtractingNoder.h index 905fc76..6a72771 100644 --- a/Sources/geos/include/geos/noding/SegmentExtractingNoder.h +++ b/Sources/geos/include/geos/noding/SegmentExtractingNoder.h @@ -69,8 +69,7 @@ class GEOS_DLL SegmentExtractingNoder : public Noder { /** * Creates a new segment-extracting noder. */ - SegmentExtractingNoder() - : segList(nullptr) + SegmentExtractingNoder() : segList(nullptr) {}; void computeNodes(std::vector* segStrings) override; diff --git a/Sources/geos/include/geos/noding/SegmentIntersectionDetector.h b/Sources/geos/include/geos/noding/SegmentIntersectionDetector.h index cef3e67..2165bec 100644 --- a/Sources/geos/include/geos/noding/SegmentIntersectionDetector.h +++ b/Sources/geos/include/geos/noding/SegmentIntersectionDetector.h @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include namespace geos { @@ -50,7 +50,7 @@ class SegmentIntersectionDetector : public SegmentIntersector { bool _hasNonProperIntersection; const geom::Coordinate* intPt; - geom::CoordinateArraySequence* intSegments; + geom::CoordinateSequence* intSegments; protected: public: diff --git a/Sources/geos/include/geos/noding/SegmentNode.h b/Sources/geos/include/geos/noding/SegmentNode.h index b240d2b..6a2576e 100644 --- a/Sources/geos/include/geos/noding/SegmentNode.h +++ b/Sources/geos/include/geos/noding/SegmentNode.h @@ -21,17 +21,11 @@ #include #include #include +#include #include #include -// Forward declarations -namespace geos { -namespace noding { -class NodedSegmentString; -} -} - namespace geos { namespace noding { // geos.noding @@ -43,8 +37,6 @@ namespace noding { // geos.noding */ class GEOS_DLL SegmentNode { private: - // const NodedSegmentString* segString; - int segmentOctant; bool isInteriorVar; @@ -53,7 +45,7 @@ class GEOS_DLL SegmentNode { friend std::ostream& operator<< (std::ostream& os, const SegmentNode& n); /// the point of intersection (own copy) - geom::Coordinate coord; + geom::CoordinateXYZM coord; /// the index of the containing line segment in the parent edge std::size_t segmentIndex; @@ -70,9 +62,18 @@ class GEOS_DLL SegmentNode { /// /// @param nSegmentOctant /// - SegmentNode(const NodedSegmentString& ss, - const geom::Coordinate& nCoord, - std::size_t nSegmentIndex, int nSegmentOctant); + template + SegmentNode(const SegmentString& ss, + const CoordType& nCoord, + std::size_t nSegmentIndex, int nSegmentOctant) + : segmentOctant(nSegmentOctant) + , coord(nCoord) + , segmentIndex(nSegmentIndex) + { + // Number of points in NodedSegmentString is one-more number of segments + assert(segmentIndex < ss.size()); + isInteriorVar = !coord.equals2D(ss.getCoordinate(segmentIndex)); + } ~SegmentNode() {} diff --git a/Sources/geos/include/geos/noding/SegmentNodeList.h b/Sources/geos/include/geos/noding/SegmentNodeList.h index 11afc2d..d199fde 100644 --- a/Sources/geos/include/geos/noding/SegmentNodeList.h +++ b/Sources/geos/include/geos/noding/SegmentNodeList.h @@ -61,6 +61,9 @@ class GEOS_DLL SegmentNodeList { mutable std::vector nodeMap; mutable bool ready = false; + bool constructZ; + bool constructM; + void prepare() const; // the parent edge @@ -107,7 +110,7 @@ class GEOS_DLL SegmentNodeList { void addCollapsedNodes(); /** - * Adds nodes for any collapsed edge pairs + / * Adds nodes for any collapsed edge pairs * which are pre-existing in the vertex list. */ void findCollapsesFromExistingVertices( @@ -126,7 +129,7 @@ class GEOS_DLL SegmentNodeList { static bool findCollapseIndex(const SegmentNode& ei0, const SegmentNode& ei1, size_t& collapsedVertexIndex); - void addEdgeCoordinates(const SegmentNode* ei0, const SegmentNode* ei1, std::vector& coordList) const; + void addEdgeCoordinates(const SegmentNode* ei0, const SegmentNode* ei1, geom::CoordinateSequence& coordList) const; public: @@ -140,9 +143,12 @@ class GEOS_DLL SegmentNodeList { using iterator = container::iterator; using const_iterator = container::const_iterator; - explicit SegmentNodeList(const NodedSegmentString* newEdge): edge(*newEdge) {} - - explicit SegmentNodeList(const NodedSegmentString& newEdge): edge(newEdge) {} + explicit SegmentNodeList(const NodedSegmentString& newEdge, + bool p_constructZ, + bool p_constructM) + : constructZ(p_constructZ) + , constructM(p_constructM) + , edge(newEdge) {} ~SegmentNodeList() = default; @@ -152,6 +158,14 @@ class GEOS_DLL SegmentNodeList { return edge; } + bool getConstructZ() const { + return constructZ; + } + + bool getConstructM() const { + return constructM; + } + /** * Adds an intersection into the list, if it isn't already there. * The input segmentIndex is expected to be normalized. @@ -159,12 +173,11 @@ class GEOS_DLL SegmentNodeList { * @param intPt the intersection Coordinate, will be copied * @param segmentIndex */ - void add(const geom::Coordinate& intPt, std::size_t segmentIndex); - - void - add(const geom::Coordinate* intPt, std::size_t segmentIndex) - { - add(*intPt, segmentIndex); + template + void add(const CoordType& intPt, std::size_t segmentIndex) { + // Cast edge to SegmentString to avoid circular dependency between NodedSegmentString and SegmentNodeList + nodeMap.emplace_back(edge, intPt, segmentIndex, reinterpret_cast(edge).getSegmentOctant(segmentIndex)); + ready = false; } /// Return the number of nodes in this list @@ -224,7 +237,7 @@ class GEOS_DLL SegmentNodeList { * @return an array of Coordinates * */ - std::vector getSplitCoordinates(); + std::unique_ptr getSplitCoordinates(); }; diff --git a/Sources/geos/include/geos/noding/SegmentSetMutualIntersector.h b/Sources/geos/include/geos/noding/SegmentSetMutualIntersector.h index f5fda95..cdd99b2 100644 --- a/Sources/geos/include/geos/noding/SegmentSetMutualIntersector.h +++ b/Sources/geos/include/geos/noding/SegmentSetMutualIntersector.h @@ -33,7 +33,7 @@ namespace noding { // geos::noding * @author Martin Davis * @version 1.10 */ -class SegmentSetMutualIntersector { +class GEOS_DLL SegmentSetMutualIntersector { public: SegmentSetMutualIntersector() diff --git a/Sources/geos/include/geos/noding/SegmentString.h b/Sources/geos/include/geos/noding/SegmentString.h index e486e77..af7cee2 100644 --- a/Sources/geos/include/geos/noding/SegmentString.h +++ b/Sources/geos/include/geos/noding/SegmentString.h @@ -21,7 +21,9 @@ #pragma once #include -#include +#include +#include +#include #include @@ -53,9 +55,11 @@ class GEOS_DLL SegmentString { /// \brief Construct a SegmentString. /// /// @param newContext the context associated to this SegmentString + /// @param newSeq coordinates of this SegmentString /// - SegmentString(const void* newContext) + SegmentString(const void* newContext, geom::CoordinateSequence* newSeq) : + seq(newSeq), context(newContext) {} @@ -84,10 +88,14 @@ class GEOS_DLL SegmentString { context = data; } + std::size_t size() const { + return seq->size(); + } - virtual std::size_t size() const = 0; - - virtual const geom::Coordinate& getCoordinate(std::size_t i) const = 0; + template + const CoordType& getCoordinate(std::size_t i) const { + return seq->getAt(i); + } /// \brief /// Return a pointer to the CoordinateSequence associated @@ -95,16 +103,90 @@ class GEOS_DLL SegmentString { /// /// @note The CoordinateSequence is owned by this SegmentString! /// - virtual geom::CoordinateSequence* getCoordinates() const = 0; + const geom::CoordinateSequence* getCoordinates() const { + return seq; + } + + geom::CoordinateSequence* getCoordinates() { + return seq; + } + + /** \brief + * Gets the octant of the segment starting at vertex index. + * + * @param index the index of the vertex starting the segment. + * Must not be the last index in the vertex list + * @return the octant of the segment at the vertex + */ + int getSegmentOctant(std::size_t index) const + { + if (index >= size() - 1) { + return -1; + } + return safeOctant(seq->getAt(index), + seq->getAt(index + 1)); + }; + + static int getSegmentOctant(const SegmentString& ss, std::size_t index) { + return ss.getSegmentOctant(index); + } + + /** + * Gets the next vertex in a ring from a vertex index. + * + * @param index the vertex index + * @return the next vertex in the ring + * + * @see isClosed + */ + const geom::CoordinateXY& nextInRing(std::size_t index) const + { + std::size_t nextIndex = index + 1; + if (nextIndex > size() - 1) { + nextIndex = 1; + } + return getCoordinate(nextIndex); + } - virtual bool isClosed() const = 0; + /** + * Gets the previous vertex in a ring from a vertex index. + * + * @param index the vertex index + * @return the previous vertex in the ring + * + * @see isClosed + */ + const geom::CoordinateXY& prevInRing(std::size_t index) const + { + std::size_t prevIndex; + if (index == 0) + prevIndex = size() - 2; + else + prevIndex = index - 1; + return getCoordinate( prevIndex ); + } + + + bool isClosed() const { + return seq->front().equals(seq->back()); + } virtual std::ostream& print(std::ostream& os) const; -private: +protected: + geom::CoordinateSequence* seq; +private: const void* context; + static int safeOctant(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1) + { + if(p0.equals2D(p1)) { + return 0; + } + return Octant::octant(p0, p1); + }; + // Declare type as noncopyable SegmentString(const SegmentString& other) = delete; SegmentString& operator=(const SegmentString& rhs) = delete; diff --git a/Sources/geos/include/geos/noding/SegmentStringUtil.h b/Sources/geos/include/geos/noding/SegmentStringUtil.h index c630839..b780a8f 100644 --- a/Sources/geos/include/geos/noding/SegmentStringUtil.h +++ b/Sources/geos/include/geos/noding/SegmentStringUtil.h @@ -19,6 +19,7 @@ #pragma once +#include #include #include #include @@ -53,15 +54,9 @@ class SegmentStringUtil { geom::LineString::ConstVect lines; geom::util::LinearComponentExtracter::getLines(*g, lines); - for(std::size_t i = 0, n = lines.size(); i < n; i++) { - geom::LineString* line = (geom::LineString*)(lines[i]); - - // we take ownership of the coordinates here - // TODO: check if this can be optimized by getting - // the internal CS. - auto pts = line->getCoordinates(); - - segStr.push_back(new NodedSegmentString(pts.release(), g)); + for(const geom::LineString* line : lines) { + auto pts = line->getCoordinatesRO(); + segStr.push_back(new BasicSegmentString(const_cast(pts), g)); } } diff --git a/Sources/geos/include/geos/noding/snap/SnappingNoder.h b/Sources/geos/include/geos/noding/snap/SnappingNoder.h index b55a350..40f057a 100644 --- a/Sources/geos/include/geos/noding/snap/SnappingNoder.h +++ b/Sources/geos/include/geos/noding/snap/SnappingNoder.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -80,7 +81,7 @@ class GEOS_DLL SnappingNoder : public Noder { SegmentString* snapVertices(SegmentString* ss); - std::unique_ptr> snap(geom::CoordinateSequence* cs); + std::unique_ptr snap(const geom::CoordinateSequence* cs); /** * Computes all interior intersections in the collection of {@link SegmentString}s, diff --git a/Sources/geos/include/geos/noding/snapround/HotPixel.h b/Sources/geos/include/geos/noding/snapround/HotPixel.h index fc95c7e..a7b52d5 100644 --- a/Sources/geos/include/geos/noding/snapround/HotPixel.h +++ b/Sources/geos/include/geos/noding/snapround/HotPixel.h @@ -21,8 +21,7 @@ #include #include // for composition -#include // for unique_ptr -#include +#include #include #include @@ -69,16 +68,17 @@ class GEOS_DLL HotPixel { static constexpr int LOWER_LEFT = 2; static constexpr int LOWER_RIGHT = 3; - geom::Coordinate originalPt; + // Store all ordinates because we may use them in constructing a SegmentNode. + geom::CoordinateXYZM originalPt; double scaleFactor; - /* Indicates if this hot pixel must be a node in the output. */ - bool hpIsNode; - /* The scaled ordinates of the hot pixel point */ double hpx; double hpy; + /* Indicates if this hot pixel must be a node in the output. */ + bool hpIsNode; + double scaleRound(double val) const { // Use Java-compatible round implementation @@ -112,16 +112,30 @@ class GEOS_DLL HotPixel { * Creates a new hot pixel. * * @param pt the coordinate at the centre of the pixel. - * Will be kept by reference, so make sure to keep it alive. - * @param scaleFactor the scaleFactor determining the pixel size */ - HotPixel(const geom::Coordinate& pt, double scaleFactor); + * @param scaleFact the scaleFactor determining the pixel size */ + template + HotPixel(const CoordType& pt, double scaleFact) + : originalPt(pt) + , scaleFactor(scaleFact) + , hpx(pt.x) + , hpy(pt.y) + , hpIsNode(false) + { + if(scaleFactor <= 0.0) { + throw util::IllegalArgumentException("Scale factor must be non-zero"); + } + if(scaleFactor != 1.0) { + hpx = scaleRound(pt.x); + hpy = scaleRound(pt.y); + } + } /* * Gets the coordinate this hot pixel is based at. * * @return the coordinate of the pixel */ - const geom::Coordinate& getCoordinate() const; + const geom::CoordinateXYZM& getCoordinate() const; /** * Tests whether the line segment (p0-p1) intersects this hot pixel. @@ -130,8 +144,8 @@ class GEOS_DLL HotPixel { * @param p1 the second coordinate of the line segment to test * @return true if the line segment intersects this hot pixel */ - bool intersects(const geom::Coordinate& p0, - const geom::Coordinate& p1) const; + bool intersects(const geom::CoordinateXY& p0, + const geom::CoordinateXY& p1) const; /** * Tests whether a coordinate lies in (intersects) this hot pixel. @@ -139,7 +153,7 @@ class GEOS_DLL HotPixel { * @param p the coordinate to test * @return true if the coordinate intersects this hot pixel */ - bool intersects(const geom::Coordinate& p) const; + bool intersects(const geom::CoordinateXY& p) const; bool isNode() const { return hpIsNode; }; void setToNode() { hpIsNode = true; }; diff --git a/Sources/geos/include/geos/noding/snapround/HotPixelIndex.h b/Sources/geos/include/geos/noding/snapround/HotPixelIndex.h index 2240a4f..690b5f9 100644 --- a/Sources/geos/include/geos/noding/snapround/HotPixelIndex.h +++ b/Sources/geos/include/geos/noding/snapround/HotPixelIndex.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -67,13 +68,28 @@ class GEOS_DLL HotPixelIndex { std::deque hotPixelQue; /* methods */ - geom::Coordinate round(const geom::Coordinate& c); + template + geom::CoordinateXYZM round(const CoordType& pt) { + geom::CoordinateXYZM p2(pt); + pm->makePrecise(p2); + return p2; + } + HotPixel* find(const geom::Coordinate& pixelPt); public: HotPixelIndex(const geom::PrecisionModel* p_pm); - HotPixel* add(const geom::Coordinate& pt); + HotPixel* addRounded(const geom::CoordinateXYZM& pt); + + template + HotPixel* add(const CoordType& p) { + static_assert(std::is_base_of(), "Only valid for Coordinate types"); + + auto pRound = round(p); + return addRounded(pRound); + } + void add(const geom::CoordinateSequence* pts); void add(const std::vector& pts); void addNodes(const geom::CoordinateSequence* pts); @@ -84,7 +100,7 @@ class GEOS_DLL HotPixelIndex { * The visitor must determine whether each hot pixel actually intersects * the segment. */ - void query(const geom::Coordinate& p0, const geom::Coordinate& p1, + void query(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, index::kdtree::KdNodeVisitor& visitor); }; diff --git a/Sources/geos/include/geos/noding/snapround/SnapRoundingIntersectionAdder.h b/Sources/geos/include/geos/noding/snapround/SnapRoundingIntersectionAdder.h index 45e94b5..2e82da7 100644 --- a/Sources/geos/include/geos/noding/snapround/SnapRoundingIntersectionAdder.h +++ b/Sources/geos/include/geos/noding/snapround/SnapRoundingIntersectionAdder.h @@ -22,6 +22,7 @@ #include // for inheritance #include // for composition #include // for use in vector +#include #include // for inlines (should drop) #include @@ -65,7 +66,7 @@ class GEOS_DLL SnapRoundingIntersectionAdder: public SegmentIntersector { // imp private: algorithm::LineIntersector li; - std::unique_ptr> intersections; + geom::CoordinateSequence intersections; // const geom::PrecisionModel* pm; double nearnessTol; @@ -82,19 +83,23 @@ class GEOS_DLL SnapRoundingIntersectionAdder: public SegmentIntersector { // imp * result in the snapped segment A crossing segment B * without a node being introduced. */ - void processNearVertex(const geom::Coordinate& p, SegmentString* edge, std::size_t segIndex, - const geom::Coordinate& p0, const geom::Coordinate& p1); + void processNearVertex(const geom::CoordinateSequence& seq0, + std::size_t ptIndex, + const geom::CoordinateSequence& seq1, + std::size_t segIndex, + SegmentString* edge); + bool isNearSegmentInterior(const geom::CoordinateXY& p, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1) const; public: SnapRoundingIntersectionAdder(double p_nearnessTol) : SegmentIntersector() - , intersections(new std::vector) + , intersections(geom::CoordinateSequence::XYZM(0)) , nearnessTol(p_nearnessTol) {} - std::unique_ptr> getIntersections() { return std::move(intersections); }; + geom::CoordinateSequence getIntersections() { return std::move(intersections); }; /** * This method is called by clients diff --git a/Sources/geos/include/geos/noding/snapround/SnapRoundingNoder.h b/Sources/geos/include/geos/noding/snapround/SnapRoundingNoder.h index 369b080..78dc8c7 100644 --- a/Sources/geos/include/geos/noding/snapround/SnapRoundingNoder.h +++ b/Sources/geos/include/geos/noding/snapround/SnapRoundingNoder.h @@ -108,7 +108,7 @@ class GEOS_DLL SnapRoundingNoder : public Noder { * @param pts the coordinates to round * @return array of rounded coordinates */ - std::vector round(const std::vector& pts) const; + std::unique_ptr round(const geom::CoordinateSequence& pts) const; /** * Computes new segment strings which are rounded and contain @@ -128,7 +128,7 @@ class GEOS_DLL SnapRoundingNoder : public Noder { * @param ss the segment string to add intersections to * @param segIndex the index of the segment */ - void snapSegment(geom::Coordinate& p0, geom::Coordinate& p1, NodedSegmentString* ss, std::size_t segIndex); + void snapSegment(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, NodedSegmentString* ss, std::size_t segIndex); /** * Add nodes for any vertices in hot pixels that were @@ -136,7 +136,7 @@ class GEOS_DLL SnapRoundingNoder : public Noder { */ void addVertexNodeSnaps(NodedSegmentString* ss); - void snapVertexNode(const geom::Coordinate& p0, NodedSegmentString* ss, std::size_t segIndex); + void snapVertexNode(const geom::CoordinateXY& p0, NodedSegmentString* ss, std::size_t segIndex); public: diff --git a/Sources/geos/include/geos/operation/BoundaryOp.h b/Sources/geos/include/geos/operation/BoundaryOp.h index e6d04da..4a59027 100644 --- a/Sources/geos/include/geos/operation/BoundaryOp.h +++ b/Sources/geos/include/geos/operation/BoundaryOp.h @@ -110,7 +110,7 @@ class GEOS_DLL BoundaryOp { std::unique_ptr boundaryMultiLineString(const geom::MultiLineString& mLine); - std::vector computeBoundaryCoordinates(const geom::MultiLineString& mLine); + std::unique_ptr computeBoundaryCoordinates(const geom::MultiLineString& mLine); std::unique_ptr boundaryLineString(const geom::LineString& line); }; diff --git a/Sources/geos/include/geos/operation/GeometryGraphOperation.h b/Sources/geos/include/geos/operation/GeometryGraphOperation.h index a088bcc..0886e65 100644 --- a/Sources/geos/include/geos/operation/GeometryGraphOperation.h +++ b/Sources/geos/include/geos/operation/GeometryGraphOperation.h @@ -74,9 +74,13 @@ class GEOS_DLL GeometryGraphOperation { /** \brief * The operation args into an array so they can be accessed by index */ - std::vector arg; + std::vector> arg; void setComputationPrecision(const geom::PrecisionModel* pm); + + // Declare type as noncopyable + GeometryGraphOperation(const GeometryGraphOperation& other) = delete; + GeometryGraphOperation& operator=(const GeometryGraphOperation& rhs) = delete; }; } // namespace geos.operation diff --git a/Sources/geos/include/geos/operation/buffer/BufferBuilder.h b/Sources/geos/include/geos/operation/buffer/BufferBuilder.h index efb67ca..259f469 100644 --- a/Sources/geos/include/geos/operation/buffer/BufferBuilder.h +++ b/Sources/geos/include/geos/operation/buffer/BufferBuilder.h @@ -14,7 +14,7 @@ * ********************************************************************** * - * Last port: operation/buffer/BufferBuilder.java r378 (JTS-1.12) + * Last port: operation/buffer/BufferBuilder.java 149b38907 (JTS-1.12) * **********************************************************************/ diff --git a/Sources/geos/include/geos/operation/buffer/BufferCurveSetBuilder.h b/Sources/geos/include/geos/operation/buffer/BufferCurveSetBuilder.h index 8021c52..e731840 100644 --- a/Sources/geos/include/geos/operation/buffer/BufferCurveSetBuilder.h +++ b/Sources/geos/include/geos/operation/buffer/BufferCurveSetBuilder.h @@ -13,7 +13,7 @@ * ********************************************************************** * - * Last port: operation/buffer/BufferCurveSetBuilder.java r378 (JTS-1.12) + * Last port: operation/buffer/BufferCurveSetBuilder.java 4c343e79f (JTS-1.19) * **********************************************************************/ @@ -169,14 +169,19 @@ class GEOS_DLL BufferCurveSetBuilder { const geom::CoordinateSequence* curvePts); /** - * Computes the maximum distance out of a set of points to a linestring. - * - * @param pts the points - * @param line the linestring vertices - * @return the maximum distance + * Tests if there are points on the raw offset curve which may + * lie on the final buffer curve + * (i.e. they are (approximately) at the buffer distance from the input ring). + * For efficiency this only tests a limited set of points on the curve. + * + * @param inputRing + * @param distance + * @param curveRing + * @return true if the curve contains points lying at the required buffer distance */ - static double maxDistance( - const geom::CoordinateSequence* pts, const geom::CoordinateSequence* line); + static bool hasPointOnBuffer( + const CoordinateSequence* inputRing, double dist, + const CoordinateSequence* curveRing); /** * The ringCoord is assumed to contain no repeated points. diff --git a/Sources/geos/include/geos/operation/buffer/BufferOp.h b/Sources/geos/include/geos/operation/buffer/BufferOp.h index 9d2b18a..6339ca8 100644 --- a/Sources/geos/include/geos/operation/buffer/BufferOp.h +++ b/Sources/geos/include/geos/operation/buffer/BufferOp.h @@ -25,6 +25,7 @@ #include #include // for enum values +#include // to materialize Geometry #include // for composition diff --git a/Sources/geos/include/geos/operation/buffer/OffsetCurve.h b/Sources/geos/include/geos/operation/buffer/OffsetCurve.h index f584c3b..403ca98 100644 --- a/Sources/geos/include/geos/operation/buffer/OffsetCurve.h +++ b/Sources/geos/include/geos/operation/buffer/OffsetCurve.h @@ -15,45 +15,34 @@ #pragma once #include -#include -#include -#include - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class -#endif +#include +#include +#include // Forward declarations namespace geos { namespace geom { +class Coordinate; +class CoordinateSequence; class Geometry; class LineString; -class LinearRing; class Polygon; -class CoordinateSequence; -class Coordinate; } namespace operation { namespace buffer { +class OffsetCurveSection; class SegmentMCIndex; } } -namespace index { -namespace chain { -class MonotoneChain; -} -} } +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; using geos::geom::Geometry; using geos::geom::GeometryFactory; using geos::geom::LineString; -using geos::geom::LinearRing; using geos::geom::Polygon; -using geos::geom::CoordinateSequence; -using geos::geom::Coordinate; namespace geos { namespace operation { @@ -61,145 +50,166 @@ namespace buffer { /** * Computes an offset curve from a geometry. - * The offset curve is a linear geometry which is offset a specified distance + * An offset curve is a linear geometry which is offset a given distance * from the input. * If the offset distance is positive the curve lies on the left side of the input; * if it is negative the curve is on the right side. + * The curve(s) have the same direction as the input line(s). * - * The offset curve of a line is a LineString which - * The offset curve of a Point is an empty LineString. - * The offset curve of a Polygon is the boundary of the polygon buffer (which - * may be a MultiLineString. - * For a collection the output is a MultiLineString of the element offset curves. + * The offset curve is based on the boundary of the buffer for the geometry + * at the offset distance (see BufferOp). + * The normal mode of operation is to return the sections of the buffer boundary + * which lie on the raw offset curve + * (obtained via rawOffset(LineString, double). + * The offset curve will contain multiple sections + * if the input self-intersects or has close approaches. + * The computed sections are ordered along the raw offset curve. + * Sections are disjoint. They never self-intersect, but may be rings. * - * The offset curve is computed as a single contiguous section of the geometry buffer boundary. - * In some geometric situations this definition is ill-defined. - * This algorithm provides a "best-effort" interpretation. - * In particular: + * * For a LineString the offset curve is a linear geometry + * (LineString or MultiLineString). + * * For a Point or MultiPoint the offset curve is an empty LineString. + * * For a Polygon the offset curve is the boundary of the polygon buffer (which + * may be a MultiLineString. + * * For a collection the output is a MultiLineString containing + * the offset curves of the elements. * - * * For self-intersecting lines, the buffer boundary includes - * offset lines for both left and right sides of the input line. - * Only a single contiguous portion on the specified side is returned. - * * If the offset corresponds to buffer holes, only the largest hole is used. + * In "joined" mode (see setJoined(bool)) + * the sections computed for each input line are joined into a single offset curve line. + * The joined curve may self-intersect. + * At larger offset distances the curve may contain "flat-line" artifacts + * in places where the input self-intersects. * * Offset curves support setting the number of quadrant segments, * the join style, and the mitre limit (if applicable) via * the BufferParameters. * * @author Martin Davis + * */ class GEOS_DLL OffsetCurve { private: - // Constants - static constexpr int NEARNESS_FACTOR = 10000; - // Members const Geometry& inputGeom; double distance; + bool isJoined = false; + BufferParameters bufferParams; double matchDistance; const GeometryFactory* geomFactory; // Methods - std::unique_ptr computeCurve(const LineString& lineGeom, double distance); + std::unique_ptr computeCurve( + const LineString& lineGeom, double distance); - std::unique_ptr offsetSegment(const CoordinateSequence* pts, double distance); + std::vector> computeSections( + const LineString& lineGeom, double distance); - static std::unique_ptr getBufferOriented(const LineString& geom, double distance, BufferParameters& bufParms); + std::unique_ptr offsetSegment( + const CoordinateSequence* pts, double distance); + + static std::unique_ptr getBufferOriented( + const LineString& geom, double distance, + BufferParameters& bufParams); /** * Extracts the largest polygon by area from a geometry. - * Used here to avoid issues with non-robust buffer results which have spurious extra polygons. + * Used here to avoid issues with non-robust buffer results + * which have spurious extra polygons. * * @param geom a geometry * @return the polygon element of largest area */ - static std::unique_ptr extractMaxAreaPolygon(const Geometry& geom); - - static std::unique_ptr extractLongestHole(const Polygon& poly); - - std::unique_ptr computeCurve( - const CoordinateSequence* bufferPts, - std::vector& rawOffsetList); - - int markMatchingSegments(const Coordinate& p0, const Coordinate& p1, - SegmentMCIndex& segIndex, const CoordinateSequence* bufferPts, - std::vector& isInCurve); + static const Polygon* extractMaxAreaPolygon(const Geometry* geom); - static double subsegmentMatchFrac(const Coordinate& p0, const Coordinate& p1, - const Coordinate& seg0, const Coordinate& seg1, double matchDistance); + void computeCurveSections( + const CoordinateSequence* bufferRingPts, + const CoordinateSequence& rawCurve, + std::vector>& sections); /** - * Extracts a section of a ring of coordinates, starting at a given index, - * and keeping coordinates which are flagged as being required. + * Matches the segments in a buffer ring to the raw offset curve + * to obtain their match positions (if any). * - * @param ring the ring of points - * @param startIndex the index of the start coordinate - * @param isExtracted flag indicating if coordinate is to be extracted - * @return + * @param raw0 a raw curve segment start point + * @param raw1 a raw curve segment end point + * @param rawCurveIndex the index of the raw curve segment + * @param bufferSegIndex the spatial index of the buffer ring segments + * @param bufferPts the points of the buffer ring + * @param rawCurvePos the raw curve positions of the buffer ring segments + * @return the index of the minimum matched buffer segment */ - static void extractSection(const CoordinateSequence* ring, int iStartIndex, - std::vector& isExtracted, std::vector& extractedPoints); - - static std::size_t next(std::size_t i, std::size_t size); - + std::size_t matchSegments( + const Coordinate& raw0, const Coordinate& raw1, + std::size_t rawCurveIndex, + SegmentMCIndex& bufferSegIndex, + const CoordinateSequence* bufferPts, + std::vector& rawCurvePos); - /* private */ - class MatchCurveSegmentAction : public index::chain::MonotoneChainSelectAction - { + static double segmentMatchFrac( + const Coordinate& p0, const Coordinate& p1, + const Coordinate& seg0, const Coordinate& seg1, + double matchDistance); - private: + /** + * This is only called when there is at least one ring segment matched + * (so rawCurvePos has at least one entry != NOT_IN_CURVE). + * The start index of the first section must be provided. + * This is intended to be the section with lowest position + * along the raw curve. + * @param ringPts the points in a buffer ring + * @param rawCurveLoc the position of buffer ring segments along the raw curve + * @param startIndex the index of the start of a section + * @param sections the list of extracted offset curve sections + */ + void extractSections( + const CoordinateSequence* ringPts, + std::vector& rawCurveLoc, + std::size_t startIndex, + std::vector>& sections); - const Coordinate& p0; - const Coordinate& p1; - const CoordinateSequence* bufferPts; - double matchDistance; - std::vector& isInCurve; - double minFrac = -1; - int minCurveIndex = -1; + std::size_t findSectionStart( + const std::vector& loc, + std::size_t end); - public: + std::size_t findSectionEnd( + const std::vector& loc, + std::size_t start, + std::size_t firstStartIndex); - MatchCurveSegmentAction( - const Coordinate& p_p0, const Coordinate& p_p1, - const CoordinateSequence* p_bufferPts, double p_matchDistance, - std::vector& p_isInCurve) - : p0(p_p0) - , p1(p_p1) - , bufferPts(p_bufferPts) - , matchDistance(p_matchDistance) - , isInCurve(p_isInCurve) - , minFrac(-1) - , minCurveIndex(-1) - {}; + static std::size_t nextIndex(std::size_t i, std::size_t size); + static std::size_t prevIndex(std::size_t i, std::size_t size); - void select(const index::chain::MonotoneChain& mc, std::size_t segIndex) override; - void select(const geom::LineSegment& seg) override { (void)seg; return; }; - int getMinCurveIndex() { return minCurveIndex; } - }; +public: + // Constants + static constexpr int MATCH_DISTANCE_FACTOR = 10000; -public: + /** + * A QuadSegs minimum value that will prevent generating + * unwanted offset curve artifacts near end caps. + */ + static constexpr int MIN_QUADRANT_SEGMENTS = 8; /** - * Creates a new instance for computing an offset curve for a geometryat a given distance. - * with default quadrant segments BufferParameters::DEFAULT_QUADRANT_SEGMENTS - * and join style BufferParameters::JOIN_STYLE. + * Creates a new instance for computing an offset curve for a geometry at a given distance. + * with default quadrant segments (BufferParameters::DEFAULT_QUADRANT_SEGMENTS) + * and join style (BufferParameters::JOIN_STYLE). * * @param geom the geometry to offset - * @param dist the offset distance (positive = left, negative = right) + * @param dist the offset distance (positive for left, negative for right) * - * \see BufferParameters + * @see BufferParameters */ OffsetCurve(const Geometry& geom, double dist) : inputGeom(geom) , distance(dist) - , matchDistance(std::abs(dist)/NEARNESS_FACTOR) + , matchDistance(std::abs(dist)/MATCH_DISTANCE_FACTOR) , geomFactory(geom.getFactory()) { if (!std::isfinite(dist)) { @@ -209,70 +219,110 @@ class GEOS_DLL OffsetCurve { /** * Creates a new instance for computing an offset curve for a geometry at a given distance. - * allowing the quadrant segments and join style and mitre limit to be set - * via BufferParameters. + * setting the quadrant segments and join style and mitre limit + * via {@link BufferParameters}. * - * @param geom - * @param dist - * @param bp + * @param geom the geometry to offset + * @param dist the offset distance (positive for left, negative for right) + * @param bp the buffer parameters to use */ OffsetCurve(const Geometry& geom, double dist, BufferParameters& bp) : inputGeom(geom) , distance(dist) - , bufferParams(bp) - , matchDistance(std::abs(dist)/NEARNESS_FACTOR) + , matchDistance(std::abs(dist)/MATCH_DISTANCE_FACTOR) , geomFactory(geom.getFactory()) { if (!std::isfinite(dist)) { throw util::IllegalArgumentException("OffsetCurve distance must be a finite value"); } + //-- set buffer params, leaving cap style as the default CAP_ROUND + + /** + * Prevent using a very small QuadSegs value, to avoid + * offset curve artifacts near the end caps. + */ + int quadSegs = bp.getQuadrantSegments(); + if (quadSegs < MIN_QUADRANT_SEGMENTS) { + quadSegs = MIN_QUADRANT_SEGMENTS; + } + bufferParams.setQuadrantSegments(quadSegs); + + bufferParams.setJoinStyle( bp.getJoinStyle()); + bufferParams.setMitreLimit( bp.getMitreLimit()); }; /** - * Computes the offset curve of a geometry at a given distance, - * and for a specified quadrant segments, join style and mitre limit. + * Computes a single curve line for each input linear component, + * by joining curve sections in order along the raw offset curve. + * The default mode is to compute separate curve sections. * - * @param geom a geometry - * @param dist the offset distance (positive = left, negative = right) - * @param quadSegs the quadrant segments (-1 for default) - * @param joinStyle the join style (-1 for default) - * @param mitreLimit the mitre limit (-1 for default) - * @return the offset curve + * @param pIsJoined true if joined mode should be used. */ + void setJoined(bool pIsJoined); + static std::unique_ptr getCurve( const Geometry& geom, - double dist, int quadSegs, BufferParameters::JoinStyle joinStyle, double mitreLimit); + double dist, + int quadSegs, + BufferParameters::JoinStyle joinStyle, + double mitreLimit); + + static std::unique_ptr getCurve( + const Geometry& geom, double dist); - static std::unique_ptr getCurve(const Geometry& geom, double dist); + /** + * Computes the offset curve of a geometry at a given distance, + * joining curve sections into a single line for each input line. + * + * @param geom a geometry + * @param dist the offset distance (positive for left, negative for right) + * @return the joined offset curve + */ + static std::unique_ptr getCurveJoined( + const Geometry& geom, double dist); + + /** + * Gets the computed offset curve lines. + * + * @return the offset curve geometry + */ std::unique_ptr getCurve(); /** - * Gets the raw offset line. - * The quadrant segments and join style and mitre limit to be set + * Gets the raw offset curve for a line at a given distance. + * The quadrant segments, join style and mitre limit can be specified * via BufferParameters. * * The raw offset line may contain loops and other artifacts which are * not present in the true offset curve. - * The raw offset line is matched to the buffer ring (which is clean) - * to extract the offset curve. * - * @param geom the linestring to offset - * @param dist the offset distance + * @param line the line to offset + * @param distance the offset distance (positive for left, negative for right) * @param bufParams the buffer parameters to use - * @param lineList the vector to populate with the return value + * @return the raw offset curve points */ - static void rawOffset(const LineString& geom, double dist, BufferParameters& bufParams, std::vector& lineList); - static void rawOffset(const LineString& geom, double dist, std::vector& lineList); + static std::unique_ptr rawOffsetCurve( + const LineString& line, + double distance, + BufferParameters& bufParams); -}; + /** + * Gets the raw offset curve for a line at a given distance, + * with default buffer parameters. + * + * @param line the line to offset + * @param distance the offset distance (positive for left, negative for right) + * @return the raw offset curve points + */ + static std::unique_ptr rawOffset( + const LineString& line, + double distance); +}; } // namespace geos::operation::buffer } // namespace geos::operation } // namespace geos -#ifdef _MSC_VER -#pragma warning(pop) -#endif diff --git a/Sources/geos/include/geos/operation/buffer/OffsetCurveBuilder.h b/Sources/geos/include/geos/operation/buffer/OffsetCurveBuilder.h index 8411c67..f73672b 100644 --- a/Sources/geos/include/geos/operation/buffer/OffsetCurveBuilder.h +++ b/Sources/geos/include/geos/operation/buffer/OffsetCurveBuilder.h @@ -40,6 +40,9 @@ class PrecisionModel; } } +using geos::geom::CoordinateSequence; +using geos::geom::PrecisionModel; + namespace geos { namespace operation { // geos.operation namespace buffer { // geos.operation.buffer @@ -55,8 +58,12 @@ namespace buffer { // geos.operation.buffer * it may contain self-intersections (and usually will). * The final buffer polygon is computed by forming a topological graph * of all the noded raw curves and tracing outside contours. - * The points in the raw curve are rounded to a given geom::PrecisionModel. + * The points in the raw curve are rounded to a given PrecisionModel. * + * Note: this may not produce correct results if the input + * contains repeated or invalid points. + * Repeated points should be removed before calling. + * See removeRepeatedAndInvalidPoints. */ class GEOS_DLL OffsetCurveBuilder { public: @@ -68,13 +75,13 @@ class GEOS_DLL OffsetCurveBuilder { * kept alive for the whole lifetime of * the buffer builder. */ - OffsetCurveBuilder(const geom::PrecisionModel* newPrecisionModel, - const BufferParameters& nBufParams) - : - distance(0.0), - precisionModel(newPrecisionModel), - bufParams(nBufParams) - {} + OffsetCurveBuilder( + const PrecisionModel* newPrecisionModel, + const BufferParameters& nBufParams) + : distance(0.0) + , precisionModel(newPrecisionModel) + , bufParams(nBufParams) + {} /** \brief * Gets the buffer parameters being used to generate the curve. @@ -91,10 +98,9 @@ class GEOS_DLL OffsetCurveBuilder { * Tests whether the offset curve for line or point geometries * at the given offset distance is empty (does not exist). * This is the case if: - *

    - *
  • the distance is zero, - *
  • the distance is negative, except for the case of singled-sided buffers - *
+ * + * * the distance is zero, + * * the distance is negative, except for the case of singled-sided buffers * * @param distance the offset curve distance * @return true if the offset curve is empty @@ -113,9 +119,23 @@ class GEOS_DLL OffsetCurveBuilder { * CoordinateSequences will be pushed_back. * Caller is responsible to delete these new elements. */ - void getLineCurve(const geom::CoordinateSequence* inputPts, - double distance, - std::vector& lineList); + void getLineCurve(const CoordinateSequence* inputPts, + double distance, + std::vector& lineList); + + /** + * This method handles single points as well as LineStrings. + * LineStrings are assumed not to be closed (the function will not + * fail for closed lines, but will generate superfluous line caps). + * + * @param inputPts the vertices of the line to offset + * @param pDistance the offset distance + * + * @return a Coordinate array representing the curve + * or null if the curve is empty + */ + std::unique_ptr getLineCurve( + const CoordinateSequence* inputPts, double pDistance); /** \brief * This method handles single points as well as lines. @@ -135,8 +155,8 @@ class GEOS_DLL OffsetCurveBuilder { * * @note This is a GEOS extension. */ - void getSingleSidedLineCurve(const geom::CoordinateSequence* inputPts, - double distance, std::vector& lineList, + void getSingleSidedLineCurve(const CoordinateSequence* inputPts, + double distance, std::vector& lineList, bool leftSide, bool rightSide) ; /** \brief @@ -149,19 +169,38 @@ class GEOS_DLL OffsetCurveBuilder { * @param lineList the std::vector to which CoordinateSequences will * be pushed_back */ - void getRingCurve(const geom::CoordinateSequence* inputPts, int side, + void getRingCurve(const CoordinateSequence* inputPts, int side, double distance, - std::vector& lineList); + std::vector& lineList); - void getOffsetCurve(const geom::CoordinateSequence* inputPts, + /** + * This method handles the degenerate cases of single points and lines, + * as well as valid rings. + * + * @param inputPts the coordinates of the ring (must not contain repeated points) + * @param side side the side Position of the ring on which to construct the buffer line + * @param pDistance the positive distance at which to create the offset + * @return a Coordinate array representing the curve, + * or null if the curve is empty + */ + std::unique_ptr getRingCurve( + const CoordinateSequence* inputPts, + int side, double pDistance); + + void getOffsetCurve(const CoordinateSequence* inputPts, double p_distance, - std::vector& lineList); + std::vector& lineList); + + std::unique_ptr getOffsetCurve( + const CoordinateSequence* inputPts, + double pDistance); + private: double distance; - const geom::PrecisionModel* precisionModel; + const PrecisionModel* precisionModel; const BufferParameters& bufParams; @@ -183,21 +222,24 @@ class GEOS_DLL OffsetCurveBuilder { */ double simplifyTolerance(double bufDistance); - void computeLineBufferCurve(const geom::CoordinateSequence& inputPts, + void computeLineBufferCurve(const CoordinateSequence& inputPts, OffsetSegmentGenerator& segGen); - void computeSingleSidedBufferCurve(const geom::CoordinateSequence& inputPts, + void computeSingleSidedBufferCurve(const CoordinateSequence& inputPts, bool isRightSide, OffsetSegmentGenerator& segGen); - void computeRingBufferCurve(const geom::CoordinateSequence& inputPts, + void computeRingBufferCurve(const CoordinateSequence& inputPts, int side, OffsetSegmentGenerator& segGen); - std::unique_ptr getSegGen(double dist); - void computePointCurve(const geom::Coordinate& pt, OffsetSegmentGenerator& segGen); + void computeOffsetCurve( + const CoordinateSequence* inputPts, + bool isRightSide, + OffsetSegmentGenerator& segGen); + // Declare type as noncopyable diff --git a/Sources/geos/include/geos/operation/buffer/OffsetCurveSection.h b/Sources/geos/include/geos/operation/buffer/OffsetCurveSection.h new file mode 100644 index 0000000..e73d759 --- /dev/null +++ b/Sources/geos/include/geos/operation/buffer/OffsetCurveSection.h @@ -0,0 +1,111 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2021 Martin Davis + * Copyright (C) 2021 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include // to materialize CoordinateSequence +#include +#include + +// Forward declarations +namespace geos { +namespace geom { +class Coordinate; +class CoordinateSequence; +class Geometry; +class GeometryFactory; +class LineString; +} +} + +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::LineString; + +namespace geos { // geos. +namespace operation { // geos.operation +namespace buffer { // geos.operation.buffer + +/** + * Models a section of a raw offset curve, + * starting at a given location along the raw curve. + * The location is a decimal number, with the integer part + * containing the segment index and the fractional part + * giving the fractional distance along the segment. + * The location of the last section segment + * is also kept, to allow optimizing joining sections together. + * + * @author mdavis + */ +class GEOS_DLL OffsetCurveSection { + +private: + + std::unique_ptr sectionPts; + double location; + double locLast; + + bool isEndInSameSegment(double nextLoc) const; + + +public: + + OffsetCurveSection(std::unique_ptr && secPts, double pLoc, double pLocLast) + : sectionPts(std::move(secPts)) + , location(pLoc) + , locLast(pLocLast) + {}; + + const CoordinateSequence* getCoordinates() const; + std::unique_ptr releaseCoordinates(); + + double getLocation() const { return location; }; + + /** + * Joins section coordinates into a LineString. + * Join vertices which lie in the same raw curve segment + * are removed, to simplify the result linework. + * + * @param sections the sections to join + * @param geomFactory the geometry factory to use + * @return the simplified linestring for the joined sections + */ + static std::unique_ptr toLine( + std::vector>& sections, + const GeometryFactory* geomFactory); + + static std::unique_ptr toGeometry( + std::vector>& sections, + const GeometryFactory* geomFactory); + + static std::unique_ptr create( + const CoordinateSequence* srcPts, + std::size_t start, std::size_t end, + double loc, double locLast); + + static bool OffsetCurveSectionComparator( + const std::unique_ptr& a, + const std::unique_ptr& b); + +}; + + +} // namespace geos.operation.buffer +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/buffer/OffsetSegmentGenerator.h b/Sources/geos/include/geos/operation/buffer/OffsetSegmentGenerator.h index 0ae9258..2f3a369 100644 --- a/Sources/geos/include/geos/operation/buffer/OffsetSegmentGenerator.h +++ b/Sources/geos/include/geos/operation/buffer/OffsetSegmentGenerator.h @@ -106,6 +106,12 @@ class GEOS_DLL OffsetSegmentGenerator { to.push_back(segList.getCoordinates()); } + std::unique_ptr + getCoordinates() + { + return std::unique_ptr(segList.getCoordinates()); + } + void closeRing() { diff --git a/Sources/geos/include/geos/operation/buffer/OffsetSegmentString.h b/Sources/geos/include/geos/operation/buffer/OffsetSegmentString.h index 9b42c1a..a3fdf19 100644 --- a/Sources/geos/include/geos/operation/buffer/OffsetSegmentString.h +++ b/Sources/geos/include/geos/operation/buffer/OffsetSegmentString.h @@ -21,7 +21,6 @@ #include // for inlines #include // for inlines -#include // for composition #include // for inlines #include @@ -43,7 +42,7 @@ class OffsetSegmentString { private: - geom::CoordinateArraySequence* ptList; + geom::CoordinateSequence* ptList; const geom::PrecisionModel* precisionModel; @@ -85,7 +84,7 @@ class OffsetSegmentString { OffsetSegmentString() : - ptList(new geom::CoordinateArraySequence()), + ptList(new geom::CoordinateSequence()), precisionModel(nullptr), minimumVertexDistance(0.0) { @@ -103,7 +102,7 @@ class OffsetSegmentString { ptList->clear(); } else { - ptList = new geom::CoordinateArraySequence(); + ptList = new geom::CoordinateSequence(); } precisionModel = nullptr; diff --git a/Sources/geos/include/geos/operation/cluster/AbstractClusterFinder.h b/Sources/geos/include/geos/operation/cluster/AbstractClusterFinder.h new file mode 100644 index 0000000..dfa5362 --- /dev/null +++ b/Sources/geos/include/geos/operation/cluster/AbstractClusterFinder.h @@ -0,0 +1,136 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020-2022 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#ifndef GEOS_OPERATION_CLUSTER_ABSTRACTCLUSTERFINDER +#define GEOS_OPERATION_CLUSTER_ABSTRACTCLUSTERFINDER + +#include +#include +#include + +#include +#include +#include + +// Forward declarations +namespace geos { +namespace geom { + class Envelope; + class Geometry; +} +namespace operation { +namespace cluster { + class UnionFind; +} +} +} + +namespace geos { +namespace operation { +namespace cluster { + +/** AbstractClusterFinder defines an interface for bottom-up clustering algorithms, + * where spatial index queries can be used to identify geometries that should be + * clustered together. + */ +class GEOS_DLL AbstractClusterFinder { + +public: + /** + * Cluster the provided geometries, returning an object that provides access + * to the components of each cluster. + * + * @param g A vector of geometries to cluster + */ + Clusters cluster(const std::vector& g); + + /** + * Cluster the components of the provided geometry, returning a vector of clusters. + * This function will take ownership of the provided geometry. Any components that + * are included in a cluster will be returned. Components that are not included in + * any cluster will be destroyed. + * + * @param g A geometry whose components should be clustered. + * @return a Geometry vector, with each entry representing a single cluster. + */ + std::vector> clusterToVector(std::unique_ptr && g); + + /** + * Cluster the components of the provided geometry, returning a vector of clusters. + * The input geometry will not be modified. + * + * @param g A geometry whose components should be clustered. + * @return a Geometry vector, with each entry representing a single cluster. + */ + std::vector> clusterToVector(const geom::Geometry& g); + + /** + * Cluster the components of the provided geometry, returning a GeometryCollection. + * This function will take ownership of the provided geometry. Any components that + * are included in a cluster will be returned. Components that are not included in + * any cluster will be destroyed. + * + * @param g A geometry whose components should be clustered. + * @return a GeometryCollection, with each sub-geometry representing a single cluster. + */ + std::unique_ptr clusterToCollection(std::unique_ptr && g); + + /** + * Cluster the components of the provided geometry, returning a GeometryCollection. + * The input geometry will not be modified. + * + * @param g A geometry whose components should be clustered. + * @return a GeometryCollection, with each sub-geometry representing a single cluster. + */ + std::unique_ptr clusterToCollection(const geom::Geometry & g); + +protected: + /** + * Determine whether two geometries should be considered in the same cluster. + * @param a Geometry + * @param b Geometry + * @return `true` if the clusters associated with `a` and `b` should be merged. + */ + virtual bool shouldJoin(const geom::Geometry* a, const geom::Geometry* b) = 0; + + /** + * Provide an query Envelope that can be used to find all geometries possibly + * in the same cluster as the input. + * @param a Geometry + * @return an Envelope suitable for querying + */ + virtual const geom::Envelope& queryEnvelope(const geom::Geometry* a) = 0; + + /** + * Given a vector and index of components, + * @param components a vector of Geometry components + * @param index a spatial index storing pointers to those components + * @param uf a UnionFind + * @return a vector of with the indices of all components that should be included in a cluster + */ + virtual Clusters process(const std::vector & components, + index::strtree::TemplateSTRtree & index, + UnionFind & uf); + +private: + static std::vector> getComponents(std::unique_ptr&& g); +}; + + + +} +} +} + +#endif diff --git a/Sources/geos/include/geos/operation/cluster/Clusters.h b/Sources/geos/include/geos/operation/cluster/Clusters.h new file mode 100644 index 0000000..f4e4961 --- /dev/null +++ b/Sources/geos/include/geos/operation/cluster/Clusters.h @@ -0,0 +1,72 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2021-2022 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#ifndef GEOS_OPERATION_CLUSTER_CLUSTERS +#define GEOS_OPERATION_CLUSTER_CLUSTERS + +#include +#include +#include + +namespace geos { +namespace operation { +namespace cluster { + +class UnionFind; + +class GEOS_DLL Clusters { +private: + std::vector m_elemsInCluster; // The IDs of elements that are included in a cluster + std::vector m_starts; // Start position of each cluster in m_elemsInCluster + std::size_t m_numElems; // The number of elements from which clusters were generated + +public: + using const_iterator = decltype(m_elemsInCluster)::const_iterator; + + explicit Clusters(UnionFind & uf, std::vector elemsInCluster, std::size_t numElems); + + // Get the number of clusters available + std::size_t getNumClusters() const { + return m_starts.size(); + } + + // Get the size of a given cluster + std::size_t getSize(std::size_t cluster) const { + return static_cast(std::distance(begin(cluster), end(cluster))); + } + + // Get a vector containing the cluster ID for each item in `elems` + std::vector getClusterIds(std::size_t noClusterValue = std::numeric_limits::max()) const; + + // Get an iterator to the first element in a given cluster + const_iterator begin(std::size_t cluster) const { + return std::next(m_elemsInCluster.begin(), static_cast(m_starts[cluster])); + } + + // Get an iterator beyond the last element in a given cluster + const_iterator end(std::size_t cluster) const { + if (cluster == static_cast(m_starts.size() - 1)) { + return m_elemsInCluster.end(); + } + + return begin(cluster + 1); + } + +}; + +} +} +} + +#endif diff --git a/Sources/geos/include/geos/operation/cluster/DBSCANClusterFinder.h b/Sources/geos/include/geos/operation/cluster/DBSCANClusterFinder.h new file mode 100644 index 0000000..dc63f3d --- /dev/null +++ b/Sources/geos/include/geos/operation/cluster/DBSCANClusterFinder.h @@ -0,0 +1,59 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020-2021 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#ifndef GEOS_OPERATION_CLUSTER_DBSCANCLUSTERFINDER +#define GEOS_OPERATION_CLUSTER_DBSCANCLUSTERFINDER + +#include +#include +#include + +namespace geos { +namespace operation { +namespace cluster { + +/** DBSCANClusterFinder clusters geometries according to the DBSCAN algorithm. + * + */ +class GEOS_DLL DBSCANClusterFinder : public AbstractClusterFinder { +public: + DBSCANClusterFinder(double eps, size_t minPoints) : m_eps(eps), m_minPoints(minPoints) {} + +protected: + + const geom::Envelope& queryEnvelope(const geom::Geometry* a) override { + m_envelope = *a->getEnvelopeInternal(); + m_envelope.expandBy(m_eps); + return m_envelope; + } + + Clusters process(const std::vector & components, + index::strtree::TemplateSTRtree & index, + UnionFind & uf) override; + + bool shouldJoin(const geom::Geometry*, const geom::Geometry*) override { + throw std::runtime_error("Never get here."); + } + +private: + double m_eps; + size_t m_minPoints; + geom::Envelope m_envelope; +}; + +} +} +} + +#endif diff --git a/Sources/geos/include/geos/operation/cluster/DisjointOperation.h b/Sources/geos/include/geos/operation/cluster/DisjointOperation.h new file mode 100644 index 0000000..50ae521 --- /dev/null +++ b/Sources/geos/include/geos/operation/cluster/DisjointOperation.h @@ -0,0 +1,73 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 ISciences LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace geos { +namespace operation { +namespace cluster { + +class GEOS_DLL DisjointOperation { +public: + DisjointOperation(AbstractClusterFinder& finder) : m_finder(finder), m_split_inputs(false) {} + + /** Splits multipart geometries into their underlying components before identifying + * disjoint subsets. + */ + void setSplitInputs(bool b) { + m_split_inputs = b; + } + + /** Decompose a geometry into disjoint subsets using the provided `ClusterFinder`, + * process each subset using `f`, and combine/flatten the results. It is assumed that + * the processed results of each subset will also be disjoint; therefore, this + * algorithm may not be suitable for operations such as buffering. + * + * @brief process + * @param g a geometry to be processed + * @param f function taking a single argument of `const Geometry&` + */ + template + std::unique_ptr processDisjointSubsets(const geom::Geometry& g, Function&& f) { + if (g.getNumGeometries() == 1) { + return f(g); + } + + auto flattened = m_split_inputs ? operation::cluster::GeometryFlattener::flatten(g.clone()) : g.clone(); + auto clustered = m_finder.clusterToVector(std::move(flattened)); + + for (auto& subset : clustered) { + subset = f(*subset); + } + + auto coll = g.getFactory()->createGeometryCollection(std::move(clustered)); + + return operation::cluster::GeometryFlattener::flatten(std::move(coll)); + } + +private: + AbstractClusterFinder& m_finder; + bool m_split_inputs; +}; + + +} +} +} diff --git a/Sources/geos/include/geos/operation/cluster/EnvelopeDistanceClusterFinder.h b/Sources/geos/include/geos/operation/cluster/EnvelopeDistanceClusterFinder.h new file mode 100644 index 0000000..f847f03 --- /dev/null +++ b/Sources/geos/include/geos/operation/cluster/EnvelopeDistanceClusterFinder.h @@ -0,0 +1,53 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020-2021 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#ifndef GEOS_OPERATION_CLUSTER_ENVELOPEDISTANCECLUSTERFINDER +#define GEOS_OPERATION_CLUSTER_ENVELOPEDISTANCECLUSTERFINDER + +#include +#include + +namespace geos { +namespace operation { +namespace cluster { + +/** EnvelopeDistanceClusterFinder clusters geometries by the distance between their envelopes. + * Any two geometries whose envelopes are within the specified distance will be included in the same cluster. + */ +class GEOS_DLL EnvelopeDistanceClusterFinder : public AbstractClusterFinder { +public: + explicit EnvelopeDistanceClusterFinder(double d) : m_distance(d), m_distance_squared(d*d) {} + +protected: + const geom::Envelope& queryEnvelope(const geom::Geometry* a) override { + m_envelope = *a->getEnvelopeInternal(); + m_envelope.expandBy(m_distance); + return m_envelope; + } + + bool shouldJoin(const geom::Geometry* a, const geom::Geometry *b) override { + return a->getEnvelopeInternal()->distanceSquared(*b->getEnvelopeInternal()) <= m_distance_squared; + } + +private: + geom::Envelope m_envelope; + double m_distance; + double m_distance_squared; +}; + +} +} +} + +#endif diff --git a/Sources/geos/include/geos/operation/cluster/EnvelopeIntersectsClusterFinder.h b/Sources/geos/include/geos/operation/cluster/EnvelopeIntersectsClusterFinder.h new file mode 100644 index 0000000..17bb039 --- /dev/null +++ b/Sources/geos/include/geos/operation/cluster/EnvelopeIntersectsClusterFinder.h @@ -0,0 +1,44 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020-2021 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#ifndef GEOS_OPERATION_CLUSTER_ENVELOPEINTERSECTSCLUSTERFINDER +#define GEOS_OPERATION_CLUSTER_ENVELOPEINTERSECTSCLUSTERFINDER + +#include + +namespace geos { +namespace operation { +namespace cluster { + +/** EnvelopeIntersectsClusterFinder clusters geometries by envelope intersection. + * Any two geometries whose envelopes intersect will be included in the same cluster. + */ +class GEOS_DLL EnvelopeIntersectsClusterFinder : public AbstractClusterFinder { +protected: + + const geom::Envelope& queryEnvelope(const geom::Geometry* a) override { + return *(a->getEnvelopeInternal()); + } + + bool shouldJoin(const geom::Geometry* a, const geom::Geometry *b) override { + return a->getEnvelopeInternal()->intersects(b->getEnvelopeInternal()); + } + +}; + +} +} +} + +#endif \ No newline at end of file diff --git a/Sources/geos/include/geos/operation/cluster/GeometryDistanceClusterFinder.h b/Sources/geos/include/geos/operation/cluster/GeometryDistanceClusterFinder.h new file mode 100644 index 0000000..51645b6 --- /dev/null +++ b/Sources/geos/include/geos/operation/cluster/GeometryDistanceClusterFinder.h @@ -0,0 +1,59 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020-2021 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#ifndef GEOS_OPERATION_CLUSTER_GEOMETRYDISTANCECLUSTERFINDER +#define GEOS_OPERATION_CLUSTER_GEOMETRYDISTANCECLUSTERFINDER + +#include +#include +#include + +namespace geos { +namespace operation { +namespace cluster { + +/** GeometryDistanceClusterFinder clusters geometries according to the distance between + * them. Any two geometries that are within the specified threshold distance will be + * included in the same cluster. + */ +class GEOS_DLL GeometryDistanceClusterFinder : public AbstractClusterFinder { +public: + explicit GeometryDistanceClusterFinder(double distance) : m_distance(distance) {} + +protected: + bool shouldJoin(const geom::Geometry* a, const geom::Geometry *b) override { + if (m_prep == nullptr || &(m_prep->getGeometry()) != a) { + m_prep = geom::prep::PreparedGeometryFactory::prepare(a); + } + + return m_prep->isWithinDistance(b, m_distance); + } + + const geom::Envelope& queryEnvelope(const geom::Geometry* a) override { + m_envelope = *a->getEnvelopeInternal(); + m_envelope.expandBy(m_distance); + return m_envelope; + } + +private: + std::unique_ptr m_prep; + double m_distance; + geom::Envelope m_envelope; +}; + +} +} +} + +#endif diff --git a/Sources/geos/include/geos/operation/cluster/GeometryFlattener.h b/Sources/geos/include/geos/operation/cluster/GeometryFlattener.h new file mode 100644 index 0000000..6b43212 --- /dev/null +++ b/Sources/geos/include/geos/operation/cluster/GeometryFlattener.h @@ -0,0 +1,46 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 ISciences LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include + +namespace geos { +namespace operation { +namespace cluster { + + +class GEOS_DLL GeometryFlattener { +public: + /** Flatten a geometry such that it contains no nested components (e.g., MultiPolygons within + * GeometryCollections, etc.). The flattened geometry will use the most narrow representation + * possible (Polygon preferred to MultiPolygon, MultiPolygon preferred to GeometryCollection.) + * + * @brief flatten + * @param g a geometry to be flattened; consumed by the function + * @return a geometry with no nested components + */ + static std::unique_ptr flatten(std::unique_ptr&& g); + +private: + static void flatten(std::unique_ptr&& g, std::vector>& components); +}; + + +} +} +} diff --git a/Sources/geos/include/geos/operation/cluster/GeometryIntersectsClusterFinder.h b/Sources/geos/include/geos/operation/cluster/GeometryIntersectsClusterFinder.h new file mode 100644 index 0000000..8e4e1a1 --- /dev/null +++ b/Sources/geos/include/geos/operation/cluster/GeometryIntersectsClusterFinder.h @@ -0,0 +1,53 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020-2021 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#ifndef GEOS_OPERATION_CLUSTER_GEOMETRYINTERSECTSCLUSTERFINDER +#define GEOS_OPERATION_CLUSTER_GEOMETRYINTERSECTSCLUSTERFINDER + +#include +#include +#include +#include + +namespace geos { +namespace operation { +namespace cluster { + +/** GeometryIntersectsClusterFinder clusters geometries by intersection. + * Any two geometries that intersect will be included in the same cluster. + */ +class GEOS_DLL GeometryIntersectsClusterFinder : public AbstractClusterFinder { +protected: + const geom::Envelope& queryEnvelope(const geom::Geometry* a) override { + return *(a->getEnvelopeInternal()); + } + + bool shouldJoin(const geom::Geometry* a, const geom::Geometry *b) override { + if (m_prep == nullptr || &(m_prep->getGeometry()) != a) { + m_prep = geom::prep::PreparedGeometryFactory::prepare(a); + } + + return m_prep->intersects(b); + } + +private: + std::unique_ptr m_prep; +}; + + +} +} +} + +#endif diff --git a/Sources/geos/include/geos/operation/cluster/UnionFind.h b/Sources/geos/include/geos/operation/cluster/UnionFind.h new file mode 100644 index 0000000..2fe29e1 --- /dev/null +++ b/Sources/geos/include/geos/operation/cluster/UnionFind.h @@ -0,0 +1,137 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020-2021 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#ifndef GEOS_OPERATION_CLUSTER_UNIONFIND +#define GEOS_OPERATION_CLUSTER_UNIONFIND + +#include +#include +#include + +#include +#include + +namespace geos { +namespace operation { +namespace cluster { + +/** UnionFind provides an implementation of a disjoint set data structure that + * is useful in clustering. Elements to be clustered are referred to according + * to a numeric index. + */ +class GEOS_DLL UnionFind { + +public: + /** Create a UnionFind object + * + * @param n the number of elements to be clustered (fixed size) + */ + explicit UnionFind(size_t n) : + clusters(n), + sizes(n), + num_clusters(n) { + std::iota(clusters.begin(), clusters.end(), 0); + std::fill(sizes.begin(), sizes.end(), 1); + } + + // Are two elements in the same cluster? + bool same(size_t i, size_t j) { + return i == j || (find(i) == find(j)); + } + + // Are two elements in a different cluster? + bool different(size_t i, size_t j) { + return !same(i, j); + } + + /** + * Return the ID of the cluster associated with an item + * @param i index of the item to lookup + * @return a numeric cluster ID + */ + size_t find(size_t i) { + size_t root = i; + + while(clusters[root] != root) { + root = clusters[root]; + } + + while (i != root) { + size_t next = clusters[i]; + clusters[i] = root; + i = next; + } + + return i; + } + + /** + * Merge the clusters associated with two items + * @param i ID of an item associated with the first cluster + * @param j ID of an item associated with the second cluster + */ + void join(size_t i, size_t j) { + auto a = find(i); + auto b = find(j); + + if (a == b) { + return; + } + + if ((sizes[b] > sizes[a]) || (sizes[a] == sizes[b] && b <= a)) { + std::swap(a, b); + } + + clusters[a] = clusters[b]; + sizes[b] += sizes[a]; + sizes[a] = 0; + + num_clusters--; + } + + size_t getNumClusters() const { + return num_clusters; + } + + template + void sortByCluster(T begin, T end) { + std::sort(begin, end, [this](size_t a, size_t b) { + return find(a) < find(b); + }); + } + + /** + * Return the clusters associated with all elements + * @return an object that allows iteration over the elements of each cluster + */ + Clusters getClusters(); + + /** + * Return the clusters associated with the given elements + * @param elems a vector of element ids + * @return an object that allows iteration over the elements of each cluster + */ + Clusters getClusters(std::vector elems); + +private: + std::vector clusters; + std::vector sizes; + size_t num_clusters; +}; + +} +} +} + +#endif diff --git a/Sources/geos/include/geos/operation/distance/ConnectedElementLocationFilter.h b/Sources/geos/include/geos/operation/distance/ConnectedElementLocationFilter.h index 0c6ab18..82e4261 100644 --- a/Sources/geos/include/geos/operation/distance/ConnectedElementLocationFilter.h +++ b/Sources/geos/include/geos/operation/distance/ConnectedElementLocationFilter.h @@ -50,7 +50,7 @@ namespace distance { // geos::operation::distance class GEOS_DLL ConnectedElementLocationFilter: public geom::GeometryFilter { private: - std::vector> locations; + std::vector locations; ConnectedElementLocationFilter() = default; ConnectedElementLocationFilter(const ConnectedElementLocationFilter&) = delete; ConnectedElementLocationFilter& operator=(const ConnectedElementLocationFilter&) = delete; @@ -63,7 +63,7 @@ class GEOS_DLL ConnectedElementLocationFilter: public geom::GeometryFilter { * an empty list will be returned. The elements of the list * are [GeometryLocations](@ref operation::distance::GeometryLocation). */ - static std::vector> getLocations(const geom::Geometry* geom); + static std::vector getLocations(const geom::Geometry* geom); void filter_ro(const geom::Geometry* geom) override; void filter_rw(geom::Geometry* geom) override; diff --git a/Sources/geos/include/geos/operation/distance/ConnectedElementPointFilter.h b/Sources/geos/include/geos/operation/distance/ConnectedElementPointFilter.h index 4ebce4c..64e47dc 100644 --- a/Sources/geos/include/geos/operation/distance/ConnectedElementPointFilter.h +++ b/Sources/geos/include/geos/operation/distance/ConnectedElementPointFilter.h @@ -27,7 +27,7 @@ // Forward declarations namespace geos { namespace geom { -class Coordinate; +class CoordinateXY; class Geometry; } } @@ -46,7 +46,7 @@ namespace distance { // geos::operation::distance class GEOS_DLL ConnectedElementPointFilter: public geom::GeometryFilter { private: - std::vector* pts; + std::vector* pts; public: /** @@ -54,9 +54,9 @@ class GEOS_DLL ConnectedElementPointFilter: public geom::GeometryFilter { * found inside the specified geometry. Thus, if the specified geometry is * not a GeometryCollection, an empty list will be returned. */ - static std::vector* getCoordinates(const geom::Geometry* geom); + static std::vector* getCoordinates(const geom::Geometry* geom); - ConnectedElementPointFilter(std::vector* newPts) + ConnectedElementPointFilter(std::vector* newPts) : pts(newPts) {} diff --git a/Sources/geos/include/geos/operation/distance/DistanceOp.h b/Sources/geos/include/geos/operation/distance/DistanceOp.h index da13ab1..b4d6fed 100644 --- a/Sources/geos/include/geos/operation/distance/DistanceOp.h +++ b/Sources/geos/include/geos/operation/distance/DistanceOp.h @@ -172,19 +172,19 @@ class GEOS_DLL DistanceOp { // working algorithm::PointLocator ptLocator; - std::array, 2> minDistanceLocation; + std::array minDistanceLocation; double minDistance; bool computed = false; - void updateMinDistance(std::array, 2> & locGeom, bool flip); + void updateMinDistance(std::array & locGeom, bool flip); void computeMinDistance(); void computeContainmentDistance(); - void computeInside(std::vector> & locs, + void computeInside(std::vector & locs, const std::vector& polys, - std::array, 2> & locPtPoly); + std::array & locPtPoly); /** @@ -196,25 +196,25 @@ class GEOS_DLL DistanceOp { void computeMinDistanceLines( const std::vector& lines0, const std::vector& lines1, - std::array, 2> & locGeom); + std::array & locGeom); void computeMinDistancePoints( const std::vector& points0, const std::vector& points1, - std::array, 2> & locGeom); + std::array & locGeom); void computeMinDistanceLinesPoints( const std::vector& lines0, const std::vector& points1, - std::array, 2> & locGeom); + std::array & locGeom); void computeMinDistance(const geom::LineString* line0, const geom::LineString* line1, - std::array, 2> & locGeom); + std::array & locGeom); void computeMinDistance(const geom::LineString* line, const geom::Point* pt, - std::array, 2> & locGeom); + std::array & locGeom); }; diff --git a/Sources/geos/include/geos/operation/distance/FacetSequenceTreeBuilder.h b/Sources/geos/include/geos/operation/distance/FacetSequenceTreeBuilder.h index 9b3fa3f..2a8731f 100644 --- a/Sources/geos/include/geos/operation/distance/FacetSequenceTreeBuilder.h +++ b/Sources/geos/include/geos/operation/distance/FacetSequenceTreeBuilder.h @@ -30,10 +30,10 @@ namespace distance { class GEOS_DLL FacetSequenceTreeBuilder { private: // 6 seems to be a good facet sequence size - static const int FACET_SEQUENCE_SIZE = 6; + static const std::size_t FACET_SEQUENCE_SIZE = 6; // Seems to be better to use a minimum node capacity - static const int STR_TREE_NODE_CAPACITY = 4; + static const std::size_t STR_TREE_NODE_CAPACITY = 4; static void addFacetSequences(const geom::Geometry* geom, const geom::CoordinateSequence* pts, diff --git a/Sources/geos/include/geos/operation/distance/GeometryLocation.h b/Sources/geos/include/geos/operation/distance/GeometryLocation.h index 37f4987..1fb4437 100644 --- a/Sources/geos/include/geos/operation/distance/GeometryLocation.h +++ b/Sources/geos/include/geos/operation/distance/GeometryLocation.h @@ -51,7 +51,7 @@ class GEOS_DLL GeometryLocation { const geom::Geometry* component; std::size_t segIndex; bool inside_area; - geom::Coordinate pt; + geom::CoordinateXY pt; public: /** \brief * A Special value of segmentIndex used for locations @@ -62,6 +62,13 @@ class GEOS_DLL GeometryLocation { */ static const int INSIDE_AREA = -1; + GeometryLocation() : + component(nullptr), + segIndex(0), + inside_area(false), + pt() + {} + /** \brief * Constructs a GeometryLocation specifying a point on a geometry, * as well as the segment that the point is on (or INSIDE_AREA if @@ -72,7 +79,7 @@ class GEOS_DLL GeometryLocation { * @param pt the coordinate of the location */ GeometryLocation(const geom::Geometry* component, - std::size_t segIndex, const geom::Coordinate& pt); + std::size_t segIndex, const geom::CoordinateXY& pt); /** \brief * Constructs a GeometryLocation specifying a point inside an @@ -82,7 +89,7 @@ class GEOS_DLL GeometryLocation { * @param pt the coordinate of the location */ GeometryLocation(const geom::Geometry* component, - const geom::Coordinate& pt); + const geom::CoordinateXY& pt); /** * Returns the geometry component on (or in) which this location occurs. @@ -102,7 +109,7 @@ class GEOS_DLL GeometryLocation { /** * Returns the geom::Coordinate of this location. */ - geom::Coordinate& getCoordinate(); + geom::CoordinateXY& getCoordinate(); /** \brief * Tests whether this location represents a point diff --git a/Sources/geos/include/geos/operation/distance/IndexedFacetDistance.h b/Sources/geos/include/geos/operation/distance/IndexedFacetDistance.h index 14787dd..87044c2 100644 --- a/Sources/geos/include/geos/operation/distance/IndexedFacetDistance.h +++ b/Sources/geos/include/geos/operation/distance/IndexedFacetDistance.h @@ -38,7 +38,7 @@ namespace distance { /// an repeated query situation. /// /// Using this technique is usually much more performant than using the -/// brute-force \ref geom::Geometry::distance() when one +/// brute-force geom::Geometry::distance() when one /// or both input geometries are large, or when evaluating many distance /// computations against a single geometry. /// @@ -58,7 +58,8 @@ class GEOS_DLL IndexedFacetDistance { /// /// \param g a Geometry, which may be of any type. IndexedFacetDistance(const geom::Geometry* g) : - cachedTree(FacetSequenceTreeBuilder::build(g)) + cachedTree(FacetSequenceTreeBuilder::build(g)), + baseGeometry(*g) {} /// \brief Computes the distance between facets of two geometries. @@ -76,7 +77,7 @@ class GEOS_DLL IndexedFacetDistance { /// \param g1 a geometry /// \param g2 a geometry /// \return the nearest points on the facets of the geometries - static std::vector nearestPoints(const geom::Geometry* g1, const geom::Geometry* g2); + static std::unique_ptr nearestPoints(const geom::Geometry* g1, const geom::Geometry* g2); /// \brief Computes the distance from the base geometry to the given geometry. /// @@ -85,6 +86,14 @@ class GEOS_DLL IndexedFacetDistance { /// \return the computed distance double distance(const geom::Geometry* g) const; + /// \brief Tests whether the base geometry lies within a specified distance of the given geometry. + /// + /// \param g the geometry to test + /// \param maxDistance the maximum distance to test + /// + /// \return true of the geometry lies within the specified distance + bool isWithinDistance(const geom::Geometry* g, double maxDistance) const; + /// \brief Computes the nearest locations on the base geometry and the given geometry. /// /// \param g the geometry to compute the nearest location to @@ -95,10 +104,18 @@ class GEOS_DLL IndexedFacetDistance { /// /// \param g the geometry to compute the nearest point to /// \return the nearest points - std::vector nearestPoints(const geom::Geometry* g) const; + std::unique_ptr nearestPoints(const geom::Geometry* g) const; private: + struct FacetDistance { + double operator()(const FacetSequence* a, const FacetSequence* b) const + { + return a->distance(*b); + } + }; + std::unique_ptr> cachedTree; + const geom::Geometry& baseGeometry; }; } diff --git a/Sources/geos/include/geos/operation/intersection/Rectangle.h b/Sources/geos/include/geos/operation/intersection/Rectangle.h index 88b065d..683c051 100644 --- a/Sources/geos/include/geos/operation/intersection/Rectangle.h +++ b/Sources/geos/include/geos/operation/intersection/Rectangle.h @@ -15,6 +15,7 @@ #pragma once #include +#include #ifdef _MSC_VER #pragma warning(push) @@ -108,9 +109,9 @@ class GEOS_DLL Rectangle { * * @note Ownership transferred to caller */ - geom::Polygon* toPolygon(const geom::GeometryFactory& f) const; + std::unique_ptr toPolygon(const geom::GeometryFactory& f) const; - geom::LinearRing* toLinearRing(const geom::GeometryFactory& f) const; + std::unique_ptr toLinearRing(const geom::GeometryFactory& f) const; /** * @brief Position with respect to a clipping rectangle diff --git a/Sources/geos/include/geos/operation/intersection/RectangleIntersection.h b/Sources/geos/include/geos/operation/intersection/RectangleIntersection.h index 82c88a0..d3c500a 100644 --- a/Sources/geos/include/geos/operation/intersection/RectangleIntersection.h +++ b/Sources/geos/include/geos/operation/intersection/RectangleIntersection.h @@ -35,7 +35,6 @@ class MultiLineString; class Geometry; class GeometryCollection; class GeometryFactory; -class CoordinateSequenceFactory; } namespace operation { namespace intersection { @@ -110,7 +109,6 @@ class GEOS_DLL RectangleIntersection { const geom::Geometry& _geom; const Rectangle& _rect; const geom::GeometryFactory* _gf; - const geom::CoordinateSequenceFactory* _csf; void clip_geom(const geom::Geometry* g, RectangleIntersectionBuilder& parts, diff --git a/Sources/geos/include/geos/operation/intersection/RectangleIntersectionBuilder.h b/Sources/geos/include/geos/operation/intersection/RectangleIntersectionBuilder.h index 08353b1..ecdf5d7 100644 --- a/Sources/geos/include/geos/operation/intersection/RectangleIntersectionBuilder.h +++ b/Sources/geos/include/geos/operation/intersection/RectangleIntersectionBuilder.h @@ -30,6 +30,7 @@ namespace geos { namespace geom { class Coordinate; +class CoordinateSequence; class Geometry; class GeometryFactory; class Polygon; @@ -139,11 +140,11 @@ class GEOS_DLL RectangleIntersectionBuilder { */ void close_boundary( const Rectangle& rect, - std::vector* ring, + geom::CoordinateSequence* ring, double x1, double y1, double x2, double y2); - void close_ring(const Rectangle& rect, std::vector* ring); + void close_ring(const Rectangle& rect, geom::CoordinateSequence* ring); RectangleIntersectionBuilder(const geom::GeometryFactory& f) : _gf(f) {} diff --git a/Sources/geos/include/geos/operation/linemerge/EdgeString.h b/Sources/geos/include/geos/operation/linemerge/EdgeString.h index a8953fa..5bacf83 100644 --- a/Sources/geos/include/geos/operation/linemerge/EdgeString.h +++ b/Sources/geos/include/geos/operation/linemerge/EdgeString.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include #ifdef _MSC_VER @@ -55,7 +56,7 @@ class GEOS_DLL EdgeString { private: const geom::GeometryFactory* factory; std::vector directedEdges; - geom::CoordinateSequence* getCoordinates(); + std::unique_ptr getCoordinates() const; public: /** * \brief @@ -74,7 +75,7 @@ class GEOS_DLL EdgeString { /** * Converts this EdgeString into a LineString. */ - geom::LineString* toLineString(); + std::unique_ptr toLineString() const; }; } // namespace geos::operation::linemerge diff --git a/Sources/geos/include/geos/operation/overlay/EdgeSetNoder.h b/Sources/geos/include/geos/operation/overlay/EdgeSetNoder.h deleted file mode 100644 index 1865e5d..0000000 --- a/Sources/geos/include/geos/operation/overlay/EdgeSetNoder.h +++ /dev/null @@ -1,74 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2006 Refractions Research Inc. - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - *********************************************************************** - * - * Last port: operation/overlay/EdgeSetNoder.java rev. 1.12 (JTS-1.10) - * - **********************************************************************/ - -#pragma once - -#include - -#include - -// Forward declarations -namespace geos { -namespace geomgraph { -class Edge; -} -namespace algorithm { -class LineIntersector; -} -} - -namespace geos { -namespace operation { // geos::operation -namespace overlay { // geos::operation::overlay - -/** \brief - * Nodes a set of edges. - * - * Takes one or more sets of edges and constructs a - * new set of edges consisting of all the split edges created by - * noding the input edges together - */ -class GEOS_DLL EdgeSetNoder { -private: - algorithm::LineIntersector* li; - std::vector* inputEdges; - - EdgeSetNoder(const EdgeSetNoder&) = delete; - EdgeSetNoder& operator=(const EdgeSetNoder&) = delete; - -public: - EdgeSetNoder(algorithm::LineIntersector* newLi) - : - li(newLi), - inputEdges(new std::vector()) - {} - - ~EdgeSetNoder() - { - delete inputEdges; // TODO: avoid heap allocation - } - - void addEdges(std::vector* edges); - std::vector* getNodedEdges(); -}; - - -} // namespace geos::operation::overlay -} // namespace geos::operation -} // namespace geos - diff --git a/Sources/geos/include/geos/operation/overlay/ElevationMatrix.h b/Sources/geos/include/geos/operation/overlay/ElevationMatrix.h deleted file mode 100644 index 0908b75..0000000 --- a/Sources/geos/include/geos/operation/overlay/ElevationMatrix.h +++ /dev/null @@ -1,111 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2006 Refractions Research Inc. - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - *********************************************************************** - * - * Last port: original (by strk) - * - **********************************************************************/ - -#pragma once - -#include - -#include // for inheritance -#include // for composition -#include // for composition - -#include -#include - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class -#endif - -// Forward declarations -namespace geos { -namespace geom { -class Coordinate; -class Geometry; -} -namespace operation { -namespace overlay { -class ElevationMatrixFilter; -class ElevationMatrix; -} -} -} - -namespace geos { -namespace operation { // geos::operation -namespace overlay { // geos::operation::overlay - - -/* - * This is the CoordinateFilter used by ElevationMatrix. - * filter_ro is used to add Geometry Coordinate's Z - * values to the matrix. - * filter_rw is used to actually elevate Geometries. - */ -class GEOS_DLL ElevationMatrixFilter: public geom::CoordinateFilter { -public: - ElevationMatrixFilter(ElevationMatrix& em); - ~ElevationMatrixFilter() override = default; - void filter_rw(geom::Coordinate* c) const override; - void filter_ro(const geom::Coordinate* c) override; -private: - ElevationMatrix& em; - double avgElevation; - - // Declare type as noncopyable - ElevationMatrixFilter(const ElevationMatrixFilter& other) = delete; - ElevationMatrixFilter& operator=(const ElevationMatrixFilter& rhs) = delete; -}; - - -/* - */ -class GEOS_DLL ElevationMatrix { - friend class ElevationMatrixFilter; -public: - ElevationMatrix(const geom::Envelope& extent, unsigned int rows, - unsigned int cols); - ~ElevationMatrix() = default; - void add(const geom::Geometry* geom); - void elevate(geom::Geometry* geom) const; - // set Z value for each cell w/out one - double getAvgElevation() const; - ElevationMatrixCell& getCell(const geom::Coordinate& c); - const ElevationMatrixCell& getCell(const geom::Coordinate& c) const; - std::string print() const; -private: - ElevationMatrixFilter filter; - void add(const geom::Coordinate& c); - geom::Envelope env; - unsigned int cols; - unsigned int rows; - double cellwidth; - double cellheight; - mutable bool avgElevationComputed; - mutable double avgElevation; - std::vectorcells; -}; - -} // namespace geos::operation::overlay -} // namespace geos::operation -} // namespace geos - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - diff --git a/Sources/geos/include/geos/operation/overlay/ElevationMatrixCell.h b/Sources/geos/include/geos/operation/overlay/ElevationMatrixCell.h deleted file mode 100644 index 36bbba2..0000000 --- a/Sources/geos/include/geos/operation/overlay/ElevationMatrixCell.h +++ /dev/null @@ -1,63 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2006 Refractions Research Inc. - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - *********************************************************************** - * - * Last port: original (by strk) - * - **********************************************************************/ - -#pragma once - -#include - -#include - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class -#endif - -// Forward declarations -namespace geos { -namespace geom { -class Coordinate; -} -} - -namespace geos { -namespace operation { // geos::operation -namespace overlay { // geos::operation::overlay - - -class GEOS_DLL ElevationMatrixCell { -public: - ElevationMatrixCell(); - ~ElevationMatrixCell() = default; - void add(const geom::Coordinate& c); - void add(double z); - double getAvg(void) const; - double getTotal(void) const; - std::string print() const; -private: - std::setzvals; - double ztot; -}; - -} // namespace geos::operation::overlay -} // namespace geos::operation -} // namespace geos - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - diff --git a/Sources/geos/include/geos/operation/overlay/LineBuilder.h b/Sources/geos/include/geos/operation/overlay/LineBuilder.h deleted file mode 100644 index ab944d5..0000000 --- a/Sources/geos/include/geos/operation/overlay/LineBuilder.h +++ /dev/null @@ -1,138 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2006 Refractions Research Inc. - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - *********************************************************************** - * - * Last port: operation/overlay/LineBuilder.java rev. 1.15 (JTS-1.10) - * - **********************************************************************/ - -#pragma once - -#include - -#include // for OverlayOp::OpCode enum - -#include - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class -#endif - -// Forward declarations -namespace geos { -namespace geom { -class GeometryFactory; -class CoordinateSequence; -class LineString; -} -namespace geomgraph { -class DirectedEdge; -class Edge; -} -namespace algorithm { -class PointLocator; -} -namespace operation { -namespace overlay { -class OverlayOp; -} -} -} - -namespace geos { -namespace operation { // geos::operation -namespace overlay { // geos::operation::overlay - -/** \brief - * Forms JTS LineStrings out of a the graph of geomgraph::DirectedEdge - * created by an OverlayOp. - * - */ -class GEOS_DLL LineBuilder { - -public: - - LineBuilder(OverlayOp* newOp, - const geom::GeometryFactory* newGeometryFactory, - algorithm::PointLocator* newPtLocator); - - ~LineBuilder() = default; - - /** - * @return a list of the LineStrings in the result of the specified overlay operation - */ - std::vector* build(OverlayOp::OpCode opCode); - - /** - * Collect line edges which are in the result. - * - * Line edges are in the result if they are not part of - * an area boundary, if they are in the result of the overlay operation, - * and if they are not covered by a result area. - * - * @param de the directed edge to test. - * @param opCode the overlap operation - * @param edges the list of included line edges. - */ - void collectLineEdge(geomgraph::DirectedEdge* de, - OverlayOp::OpCode opCode, - std::vector* edges); - -private: - OverlayOp* op; - const geom::GeometryFactory* geometryFactory; - algorithm::PointLocator* ptLocator; - std::vector lineEdgesList; - std::vector* resultLineList; - void findCoveredLineEdges(); - void collectLines(OverlayOp::OpCode opCode); - void buildLines(OverlayOp::OpCode opCode); - void labelIsolatedLines(std::vector* edgesList); - - /** - * Collect edges from Area inputs which should be in the result but - * which have not been included in a result area. - * This happens ONLY: - * - * - during an intersection when the boundaries of two - * areas touch in a line segment - * - OR as a result of a dimensional collapse. - * - */ - void collectBoundaryTouchEdge(geomgraph::DirectedEdge* de, - OverlayOp::OpCode opCode, - std::vector* edges); - - /** - * Label an isolated node with its relationship to the target geometry. - */ - void labelIsolatedLine(geomgraph::Edge* e, uint8_t targetIndex); - - /* - * If the given CoordinateSequence has mixed 3d/2d vertexes - * set Z for all vertexes missing it. - * The Z value is interpolated between 3d vertexes and copied - * from a 3d vertex to the end. - */ - void propagateZ(geom::CoordinateSequence* cs); -}; - -} // namespace geos::operation::overlay -} // namespace geos::operation -} // namespace geos - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - diff --git a/Sources/geos/include/geos/operation/overlay/OverlayOp.h b/Sources/geos/include/geos/operation/overlay/OverlayOp.h deleted file mode 100644 index bd1c283..0000000 --- a/Sources/geos/include/geos/operation/overlay/OverlayOp.h +++ /dev/null @@ -1,409 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2006 Refractions Research Inc. - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - *********************************************************************** - * - * Last port: operation/overlay/OverlayOp.java r567 (JTS-1.12+) - * - **********************************************************************/ - -#pragma once - -#include - -#include // for composition -#include // for Dimension::DimensionType -#include -#include // for composition -#include // for inline (GeometryGraph->PlanarGraph) -#include // for inheritance - -#include - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class -#endif - -// Forward declarations -namespace geos { -namespace geom { -class Geometry; -class Coordinate; -class Envelope; -class GeometryFactory; -class Polygon; -class LineString; -class Point; -} -namespace geomgraph { -class Label; -class Edge; -class Node; -} -namespace operation { -namespace overlay { -class ElevationMatrix; -} -} -} - -namespace geos { -namespace operation { // geos::operation -namespace overlay { // geos::operation::overlay - -/// \brief Computes the geometric overlay of two Geometry. -/// -/// The overlay can be used to determine any -/// boolean combination of the geometries. -/// -class GEOS_DLL OverlayOp: public GeometryGraphOperation { - -public: - - /// \brief The spatial functions supported by this class. - /// - /// These operations implement various boolean combinations of - /// the resultants of the overlay. - /// - enum OpCode { - /// The code for the Intersection overlay operation. - opINTERSECTION = 1, - /// The code for the Union overlay operation. - opUNION = 2, - /// The code for the Difference overlay operation. - opDIFFERENCE = 3, - /// The code for the Symmetric Difference overlay operation. - opSYMDIFFERENCE = 4 - }; - - /** \brief - * Computes an overlay operation for the given geometry arguments. - * - * @param geom0 the first geometry argument - * @param geom1 the second geometry argument - * @param opCode the code for the desired overlay operation - * @return the result of the overlay operation - * @throws TopologyException if a robustness problem is encountered - */ - static geom::Geometry* overlayOp(const geom::Geometry* geom0, - const geom::Geometry* geom1, - OpCode opCode); - //throw(TopologyException *); - - /** \brief - * Tests whether a point with a given topological [Label](@ref geomgraph::Label) - * relative to two geometries is contained in - * the result of overlaying the geometries using - * a given overlay operation. - * - * The method handles arguments of [Location::NONE](@ref geom::Location::NONE) correctly - * - * @param label the topological label of the point - * @param opCode the code for the overlay operation to test - * @return true if the label locations correspond to the overlayOpCode - */ - static bool isResultOfOp(const geomgraph::Label& label, OpCode opCode); - - /// \brief This method will handle arguments of Location.NULL correctly - /// - /// @return true if the locations correspond to the opCode - /// - static bool isResultOfOp(geom::Location loc0, geom::Location loc1, OpCode opCode); - - /// \brief Construct an OverlayOp with the given Geometry args. - /// - /// Ownership of passed args will remain to caller, and - /// the OverlayOp won't change them in any way. - /// - OverlayOp(const geom::Geometry* g0, const geom::Geometry* g1); - - ~OverlayOp() override; // FIXME: virtual ? - - /** \brief - * Gets the result of the overlay for a given overlay operation. - * - * Note: this method can be called once only. - * - * @param overlayOpCode the overlay operation to perform - * @return the compute result geometry - * @throws TopologyException if a robustness problem is encountered - */ - geom::Geometry* getResultGeometry(OpCode overlayOpCode); - // throw(TopologyException *); - - /** \brief - * Gets the graph constructed to compute the overlay. - * - * @return the overlay graph - */ - geomgraph::PlanarGraph& - getGraph() - { - return graph; - } - - /** \brief - * This method is used to decide if a point node should be included - * in the result or not. - * - * @return `true` if the coord point is covered by a result Line - * or Area geometry - */ - bool isCoveredByLA(const geom::Coordinate& coord); - - /** \brief - * This method is used to decide if an L edge should be included - * in the result or not. - * - * @return `true` if the coord point is covered by a result Area geometry - */ - bool isCoveredByA(const geom::Coordinate& coord); - - /* - * @return true if the coord is located in the interior or boundary of - * a geometry in the list. - */ - - /** - * Creates an empty result geometry of the appropriate dimension, - * based on the given overlay operation and the dimensions of the inputs. - * The created geometry is always an atomic geometry, - * not a collection. - * - * The empty result is constructed using the following rules: - * - * * #opINTERSECTION result has the dimension of the lowest input dimension - * * #opUNION - result has the dimension of the highest input dimension - * * #opDIFFERENCE - result has the dimension of the left-hand input - * * #opSYMDIFFERENCE - result has the dimension of the highest input dimension - */ - static std::unique_ptr createEmptyResult( - OverlayOp::OpCode overlayOpCode, const geom::Geometry* a, - const geom::Geometry* b, const geom::GeometryFactory* geomFact); - -protected: - - /** \brief - * Insert an edge from one of the noded input graphs. - * - * Checks edges that are inserted to see if an - * identical edge already exists. - * If so, the edge is not inserted, but its label is merged - * with the existing edge. - */ - void insertUniqueEdge(geomgraph::Edge* e); - -private: - - algorithm::PointLocator ptLocator; - - const geom::GeometryFactory* geomFact; - - geom::Geometry* resultGeom; - - geomgraph::PlanarGraph graph; - - geomgraph::EdgeList edgeList; - - std::vector* resultPolyList; - - std::vector* resultLineList; - - std::vector* resultPointList; - - void computeOverlay(OpCode opCode); // throw(TopologyException *); - - void insertUniqueEdges(std::vector* edges, const geom::Envelope* env = nullptr); - - /* - * If either of the GeometryLocations for the existing label is - * exactly opposite to the one in the labelToMerge, - * this indicates a dimensional collapse has happened. - * In this case, convert the label for that Geometry to a Line label - */ - //Not needed - //void checkDimensionalCollapse(geomgraph::Label labelToMerge, geomgraph::Label existingLabel); - - /** \brief - * Update the labels for edges according to their depths. - * - * For each edge, the depths are first normalized. - * Then, if the depths for the edge are equal, - * this edge must have collapsed into a line edge. - * If the depths are not equal, update the label - * with the locations corresponding to the depths - * (i.e. a depth of 0 corresponds to a Location of EXTERIOR, - * a depth of 1 corresponds to INTERIOR) - */ - void computeLabelsFromDepths(); - - /** \brief - * If edges which have undergone dimensional collapse are found, - * replace them with a new edge which is a L edge - */ - void replaceCollapsedEdges(); - - /** \brief - * Copy all nodes from an arg geometry into this graph. - * - * The node label in the arg geometry overrides any previously - * computed label for that argIndex. - * (E.g. a node may be an intersection node with - * a previously computed label of BOUNDARY, - * but in the original arg Geometry it is actually - * in the interior due to the Boundary Determination Rule) - */ - void copyPoints(uint8_t argIndex, const geom::Envelope* env = nullptr); - - /** \brief - * Compute initial labelling for all DirectedEdges at each node. - * - * In this step, DirectedEdges will acquire a complete labelling - * (i.e. one with labels for both Geometries) - * only if they - * are incident on a node which has edges for both Geometries - */ - void computeLabelling(); // throw(TopologyException *); - - /** - * For nodes which have edges from only one Geometry incident on them, - * the previous step will have left their dirEdges with no - * labelling for the other Geometry. - * However, the sym dirEdge may have a labelling for the other - * Geometry, so merge the two labels. - */ - void mergeSymLabels(); - - void updateNodeLabelling(); - - /** - * Incomplete nodes are nodes whose labels are incomplete. - * - * (e.g. the location for one Geometry is NULL). - * These are either isolated nodes, - * or nodes which have edges from only a single Geometry incident - * on them. - * - * Isolated nodes are found because nodes in one graph which - * don't intersect nodes in the other are not completely - * labelled by the initial process of adding nodes to the nodeList. - * To complete the labelling we need to check for nodes that - * lie in the interior of edges, and in the interior of areas. - * - * When each node labelling is completed, the labelling of the - * incident edges is updated, to complete their labelling as well. - */ - void labelIncompleteNodes(); - - /** \brief - * Label an isolated node with its relationship to the target geometry. - */ - void labelIncompleteNode(geomgraph::Node* n, uint8_t targetIndex); - - /** \brief - * Find all edges whose label indicates that they are in the result - * area(s), according to the operation being performed. - * - * Since we want polygon shells to be - * oriented CW, choose dirEdges with the interior of the result - * on the RHS. - * Mark them as being in the result. - * Interior Area edges are the result of dimensional collapses. - * They do not form part of the result area boundary. - */ - void findResultAreaEdges(OpCode opCode); - - /** - * If both a dirEdge and its sym are marked as being in the result, - * cancel them out. - */ - void cancelDuplicateResultEdges(); - - /** - * @return true if the coord is located in the interior or boundary of - * a geometry in the list. - */ - bool isCovered(const geom::Coordinate& coord, - std::vector* geomList); - - /** - * @return true if the coord is located in the interior or boundary of - * a geometry in the list. - */ - bool isCovered(const geom::Coordinate& coord, - std::vector* geomList); - - /** - * @return true if the coord is located in the interior or boundary of - * a geometry in the list. - */ - bool isCovered(const geom::Coordinate& coord, - std::vector* geomList); - /** - * For empty result, what is the correct geometry type to apply to - * the empty? - */ - static geom::Dimension::DimensionType resultDimension(OverlayOp::OpCode overlayOpCode, - const geom::Geometry* g0, const geom::Geometry* g1); - - /** - * Build a Geometry containing all Geometries in the given vectors. - * Takes element's ownership, vector control is left to caller. - */ - geom::Geometry* computeGeometry( - std::vector* nResultPointList, - std::vector* nResultLineList, - std::vector* nResultPolyList, - OverlayOp::OpCode opCode); - - /// Caches for memory management - std::vectordupEdges; - - /** \brief - * Merge Z values of node with those of the segment or vertex in - * the given Polygon it is on. - */ - int mergeZ(geomgraph::Node* n, const geom::Polygon* poly) const; - - /** - * Merge Z values of node with those of the segment or vertex in - * the given LineString it is on. - * @returns 1 if an intersection is found, 0 otherwise. - */ - int mergeZ(geomgraph::Node* n, const geom::LineString* line) const; - - /** - * Average Z of input geometries - */ - double avgz[2]; - bool avgzcomputed[2]; - - double getAverageZ(uint8_t targetIndex); - static double getAverageZ(const geom::Polygon* poly); - - ElevationMatrix* elevationMatrix; - - /// Throw TopologyException if an obviously wrong result has - /// been computed. - void checkObviouslyWrongResult(OpCode opCode); - -}; - -} // namespace geos::operation::overlay -} // namespace geos::operation -} // namespace geos - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - diff --git a/Sources/geos/include/geos/operation/overlay/PointBuilder.h b/Sources/geos/include/geos/operation/overlay/PointBuilder.h deleted file mode 100644 index 896cca6..0000000 --- a/Sources/geos/include/geos/operation/overlay/PointBuilder.h +++ /dev/null @@ -1,106 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2006 Refractions Research Inc. - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - *********************************************************************** - * - * Last port: operation/overlay/PointBuilder.java rev. 1.16 (JTS-1.10) - * - **********************************************************************/ - -#pragma once - -#include - -#include // for inlines -#include // for OpCode enum - -#include - -// Forward declarations -namespace geos { -namespace geom { -class GeometryFactory; -class Point; -} -namespace geomgraph { -class Node; -} -namespace algorithm { -class PointLocator; -} -namespace operation { -namespace overlay { -class OverlayOp; -} -} -} - -namespace geos { -namespace operation { // geos::operation -namespace overlay { // geos::operation::overlay - -/** \brief - * Constructs geom::Point s from the nodes of an overlay graph. - */ -class GEOS_DLL PointBuilder { -private: - - OverlayOp* op; - const geom::GeometryFactory* geometryFactory; - void extractNonCoveredResultNodes(OverlayOp::OpCode opCode); - - /* - * Converts non-covered nodes to Point objects and adds them to - * the result. - * - * A node is covered if it is contained in another element Geometry - * with higher dimension (e.g. a node point might be contained in - * a polygon, in which case the point can be eliminated from - * the result). - * - * @param n the node to test - */ - void filterCoveredNodeToPoint(const geomgraph::Node*); - - /// Allocated a construction time, but not owned. - /// Make sure you take ownership of it, getting - /// it from build() - std::vector* resultPointList; - - PointBuilder(const PointBuilder&) = delete; - PointBuilder& operator=(const PointBuilder&) = delete; - -public: - - PointBuilder(OverlayOp* newOp, - const geom::GeometryFactory* newGeometryFactory, - algorithm::PointLocator* newPtLocator = nullptr) - : - op(newOp), - geometryFactory(newGeometryFactory), - resultPointList(new std::vector()) - { - ::geos::ignore_unused_variable_warning(newPtLocator); - } - - /** - * @return a list of the Points in the result of the specified - * overlay operation - */ - std::vector* build(OverlayOp::OpCode opCode); -}; - - -} // namespace geos::operation::overlay -} // namespace geos::operation -} // namespace geos - diff --git a/Sources/geos/include/geos/operation/overlay/PolygonBuilder.h b/Sources/geos/include/geos/operation/overlay/PolygonBuilder.h index c1f47dd..e39e9b6 100644 --- a/Sources/geos/include/geos/operation/overlay/PolygonBuilder.h +++ b/Sources/geos/include/geos/operation/overlay/PolygonBuilder.h @@ -82,7 +82,7 @@ class GEOS_DLL PolygonBuilder { const std::vector* nodes); // throw(const TopologyException &) - std::vector* getPolygons(); + std::vector> getPolygons(); private: @@ -190,7 +190,7 @@ class GEOS_DLL PolygonBuilder { geomgraph::EdgeRing* findEdgeRingContaining(geomgraph::EdgeRing* testEr, std::vector& newShellList); - std::vector* computePolygons( + std::vector> computePolygons( std::vector& newShellList); /** diff --git a/Sources/geos/include/geos/operation/overlay/snap/GeometrySnapper.h b/Sources/geos/include/geos/operation/overlay/snap/GeometrySnapper.h index 09c7275..63f092f 100644 --- a/Sources/geos/include/geos/operation/overlay/snap/GeometrySnapper.h +++ b/Sources/geos/include/geos/operation/overlay/snap/GeometrySnapper.h @@ -43,16 +43,15 @@ namespace snap { // geos::operation::overlay::snap * Snaps the vertices and segments of a {@link geom::Geometry} * to another Geometry's vertices. * - * A snap distance tolerance is used to control where snapping is performed. - * Snapping one geometry to another can improve - * robustness for overlay operations by eliminating - * nearly-coincident edges - * (which cause problems during noding and intersection calculation). - * Too much snapping can result in invalid topology - * being created, so the number and location of snapped vertices - * is decided using heuristics to determine when it - * is safe to snap. - * This can result in some potential snaps being omitted, however. + * Where possible, this operation tries to avoid creating invalid geometries; + * however, it does not guarantee that output geometries will be valid. It is + * the responsibility of the caller to check for and handle invalid geometries. + * + * Because too much snapping can result in invalid + * topology being created, heuristics are used to determine the number and + * location of snapped vertices that are likely safe to snap. These heuristics + * may omit some potential snaps that are otherwise within the tolerance. + * */ class GEOS_DLL GeometrySnapper { diff --git a/Sources/geos/include/geos/operation/overlay/snap/LineStringSnapper.h b/Sources/geos/include/geos/operation/overlay/snap/LineStringSnapper.h index bae90f1..b0c29e6 100644 --- a/Sources/geos/include/geos/operation/overlay/snap/LineStringSnapper.h +++ b/Sources/geos/include/geos/operation/overlay/snap/LineStringSnapper.h @@ -58,7 +58,7 @@ class GEOS_DLL LineStringSnapper { * @param nSrcPts the points to snap * @param nSnapTol the snap tolerance to use */ - LineStringSnapper(const geom::Coordinate::Vect& nSrcPts, + LineStringSnapper(const geom::CoordinateSequence& nSrcPts, double nSnapTol) : srcPts(nSrcPts), @@ -70,7 +70,7 @@ class GEOS_DLL LineStringSnapper { } // Snap points are assumed to be all distinct points (a set would be better, uh ?) - std::unique_ptr snapTo(const geom::Coordinate::ConstVect& snapPts); + std::unique_ptr snapTo(const geom::Coordinate::ConstVect& snapPts); void setAllowSnappingToSourceVertices(bool allow) @@ -80,7 +80,7 @@ class GEOS_DLL LineStringSnapper { private: - const geom::Coordinate::Vect& srcPts; + const geom::CoordinateSequence& srcPts; double snapTolerance; diff --git a/Sources/geos/include/geos/operation/overlay/snap/SnapOverlayOp.h b/Sources/geos/include/geos/operation/overlay/snap/SnapOverlayOp.h index e7f60a3..0c2f1f3 100644 --- a/Sources/geos/include/geos/operation/overlay/snap/SnapOverlayOp.h +++ b/Sources/geos/include/geos/operation/overlay/snap/SnapOverlayOp.h @@ -18,8 +18,8 @@ #pragma once -#include // for enums #include // for dtor visibility by unique_ptr +#include #include // for unique_ptr @@ -57,8 +57,7 @@ class GEOS_DLL SnapOverlayOp { public: static std::unique_ptr - overlayOp(const geom::Geometry& g0, const geom::Geometry& g1, - OverlayOp::OpCode opCode) + overlayOp(const geom::Geometry& g0, const geom::Geometry& g1, int opCode) { SnapOverlayOp op(g0, g1); return op.getResultGeometry(opCode); @@ -67,25 +66,25 @@ class GEOS_DLL SnapOverlayOp { static std::unique_ptr intersection(const geom::Geometry& g0, const geom::Geometry& g1) { - return overlayOp(g0, g1, OverlayOp::opINTERSECTION); + return overlayOp(g0, g1, overlayng::OverlayNG::INTERSECTION); } static std::unique_ptr Union(const geom::Geometry& g0, const geom::Geometry& g1) { - return overlayOp(g0, g1, OverlayOp::opUNION); + return overlayOp(g0, g1, overlayng::OverlayNG::UNION); } static std::unique_ptr difference(const geom::Geometry& g0, const geom::Geometry& g1) { - return overlayOp(g0, g1, OverlayOp::opDIFFERENCE); + return overlayOp(g0, g1, overlayng::OverlayNG::DIFFERENCE); } static std::unique_ptr symDifference(const geom::Geometry& g0, const geom::Geometry& g1) { - return overlayOp(g0, g1, OverlayOp::opSYMDIFFERENCE); + return overlayOp(g0, g1, overlayng::OverlayNG::SYMDIFFERENCE); } SnapOverlayOp(const geom::Geometry& g1, const geom::Geometry& g2) @@ -96,10 +95,7 @@ class GEOS_DLL SnapOverlayOp { computeSnapTolerance(); } - - typedef std::unique_ptr GeomPtr; - - GeomPtr getResultGeometry(OverlayOp::OpCode opCode); + std::unique_ptr getResultGeometry(int opCode); private: diff --git a/Sources/geos/include/geos/operation/overlay/validate/OverlayResultValidator.h b/Sources/geos/include/geos/operation/overlay/validate/OverlayResultValidator.h index e6bf13d..92838d6 100644 --- a/Sources/geos/include/geos/operation/overlay/validate/OverlayResultValidator.h +++ b/Sources/geos/include/geos/operation/overlay/validate/OverlayResultValidator.h @@ -19,7 +19,6 @@ #pragma once #include -#include // for OpCode enum #include // composition #include // for Location::Value type @@ -67,7 +66,7 @@ class GEOS_DLL OverlayResultValidator { static bool isValid( const geom::Geometry& geom0, const geom::Geometry& geom1, - OverlayOp::OpCode opCode, + int opCode, const geom::Geometry& result); OverlayResultValidator( @@ -75,7 +74,7 @@ class GEOS_DLL OverlayResultValidator { const geom::Geometry& geom1, const geom::Geometry& result); - bool isValid(OverlayOp::OpCode opCode); + bool isValid(int opCode); geom::Coordinate& getInvalidLocation() @@ -107,12 +106,11 @@ class GEOS_DLL OverlayResultValidator { void addVertices(const geom::Geometry& g); - bool testValid(OverlayOp::OpCode overlayOp); + bool testValid(int overlayOp); - bool testValid(OverlayOp::OpCode overlayOp, const geom::Coordinate& pt); + bool testValid(int overlayOp, const geom::Coordinate& pt); - bool isValidResult(OverlayOp::OpCode overlayOp, - std::vector& location); + bool isValidResult(int overlayOp, std::vector& location); static double computeBoundaryDistanceTolerance( const geom::Geometry& g0, const geom::Geometry& g1); diff --git a/Sources/geos/include/geos/operation/overlayng/CoverageUnion.h b/Sources/geos/include/geos/operation/overlayng/CoverageUnion.h index 9f9e651..6c7a8be 100644 --- a/Sources/geos/include/geos/operation/overlayng/CoverageUnion.h +++ b/Sources/geos/include/geos/operation/overlayng/CoverageUnion.h @@ -79,6 +79,8 @@ class GEOS_DLL CoverageUnion { public: + static constexpr double AREA_PCT_DIFF_TOL = 1e-6; + /** * Unions a valid polygonal coverage or linear network. * diff --git a/Sources/geos/include/geos/operation/overlayng/Edge.h b/Sources/geos/include/geos/operation/overlayng/Edge.h index a8631b3..9db3362 100644 --- a/Sources/geos/include/geos/operation/overlayng/Edge.h +++ b/Sources/geos/include/geos/operation/overlayng/Edge.h @@ -198,7 +198,7 @@ class GEOS_DLL Edge { static bool isCollapsed(const geom::CoordinateSequence* pts); // takes ownership of pts from caller - Edge(geom::CoordinateSequence* p_pts, const EdgeSourceInfo* info); + Edge(std::unique_ptr&& p_pts, const EdgeSourceInfo* info); // return a clone of the underlying points std::unique_ptr getCoordinates() diff --git a/Sources/geos/include/geos/operation/overlayng/EdgeNodingBuilder.h b/Sources/geos/include/geos/operation/overlayng/EdgeNodingBuilder.h index e395113..aec4d7b 100644 --- a/Sources/geos/include/geos/operation/overlayng/EdgeNodingBuilder.h +++ b/Sources/geos/include/geos/operation/overlayng/EdgeNodingBuilder.h @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include #include @@ -102,13 +102,15 @@ class GEOS_DLL EdgeNodingBuilder { // EdgeSourceInfo*, Edge* owned by EdgeNodingBuilder, stored in deque std::deque edgeSourceInfoQue; std::deque edgeQue; + bool inputHasZ; + bool inputHasM; /** * Gets a noder appropriate for the precision model supplied. * This is one of: * * - Fixed precision: a snap-rounding noder (which should be fully robust) - * - Floating precision: a conventional nodel (which may be non-robust). + * - Floating precision: a conventional model (which may be non-robust). * In this case, a validation step is applied to the output from the noder. */ Noder* getNoder(); @@ -121,9 +123,8 @@ class GEOS_DLL EdgeNodingBuilder { void addPolygon(const Polygon* poly, uint8_t geomIndex); void addPolygonRing(const LinearRing* ring, bool isHole, uint8_t geomIndex); void addLine(const LineString* line, uint8_t geomIndex); - void addLine(std::unique_ptr& pts, uint8_t geomIndex); - void addEdge(std::unique_ptr> pts, const EdgeSourceInfo* info); - void addEdge(std::unique_ptr& cas, const EdgeSourceInfo* info); + void addLine(std::unique_ptr& pts, uint8_t geomIndex); + void addEdge(std::unique_ptr& cas, const EdgeSourceInfo* info); // Create a EdgeSourceInfo* owned by EdgeNodingBuilder const EdgeSourceInfo* createEdgeSourceInfo(uint8_t index, int depthDelta, bool isHole); @@ -133,7 +134,7 @@ class GEOS_DLL EdgeNodingBuilder { * Tests whether a geometry (represented by its envelope) * lies completely outside the clip extent(if any). */ - bool isClippedCompletely(const Envelope* env); + bool isClippedCompletely(const Envelope* env) const; /** * Tests whether it is worth limiting a line. @@ -147,7 +148,7 @@ class GEOS_DLL EdgeNodingBuilder { * limit the line to the clip envelope. * */ - std::vector>& limit(const LineString* line); + std::vector>& limit(const LineString* line); /** * If a clipper is present, @@ -163,7 +164,7 @@ class GEOS_DLL EdgeNodingBuilder { * @param ring the line to clip * @return the points in the clipped line */ - std::unique_ptr clip(const LinearRing* line); + std::unique_ptr clip(const LinearRing* line); /** * Removes any repeated points from a linear component. @@ -172,7 +173,7 @@ class GEOS_DLL EdgeNodingBuilder { * @param line the line to process * @return the points of the line with repeated points removed */ - static std::unique_ptr removeRepeatedPoints(const LineString* line); + static std::unique_ptr removeRepeatedPoints(const LineString* line); static int computeDepthDelta(const LinearRing* ring, bool isHole); @@ -202,6 +203,8 @@ class GEOS_DLL EdgeNodingBuilder { , hasEdges{{false,false}} , clipEnv(nullptr) , intAdder(lineInt) + , inputHasZ(false) + , inputHasM(false) {}; ~EdgeNodingBuilder() diff --git a/Sources/geos/include/geos/operation/overlayng/IndexedPointOnLineLocator.h b/Sources/geos/include/geos/operation/overlayng/IndexedPointOnLineLocator.h index cbe60e5..dc7874e 100644 --- a/Sources/geos/include/geos/operation/overlayng/IndexedPointOnLineLocator.h +++ b/Sources/geos/include/geos/operation/overlayng/IndexedPointOnLineLocator.h @@ -53,7 +53,7 @@ class IndexedPointOnLineLocator : public algorithm::locate::PointOnGeometryLocat : inputGeom(geomLinear) {} - geom::Location locate(const geom::Coordinate* p) override; + geom::Location locate(const geom::CoordinateXY* p) override; }; diff --git a/Sources/geos/include/geos/operation/overlayng/LineLimiter.h b/Sources/geos/include/geos/operation/overlayng/LineLimiter.h index 54f453c..f4ec5fb 100644 --- a/Sources/geos/include/geos/operation/overlayng/LineLimiter.h +++ b/Sources/geos/include/geos/operation/overlayng/LineLimiter.h @@ -14,7 +14,7 @@ #pragma once -#include +#include #include #include @@ -59,9 +59,9 @@ class GEOS_DLL LineLimiter { // Members const Envelope* limitEnv; - std::unique_ptr> ptList; + std::unique_ptr ptList; const Coordinate* lastOutside; - std::vector> sections; + std::vector> sections; // Methods void addPoint(const Coordinate* p); @@ -80,7 +80,7 @@ class GEOS_DLL LineLimiter { , lastOutside(nullptr) {}; - std::vector>& limit(const CoordinateSequence *pts); + std::vector>& limit(const CoordinateSequence *pts); }; diff --git a/Sources/geos/include/geos/operation/overlayng/OverlayEdge.h b/Sources/geos/include/geos/operation/overlayng/OverlayEdge.h index 7f062d3..7cb6d41 100644 --- a/Sources/geos/include/geos/operation/overlayng/OverlayEdge.h +++ b/Sources/geos/include/geos/operation/overlayng/OverlayEdge.h @@ -27,7 +27,7 @@ namespace geos { namespace geom { class Coordinate; -class CoordinateArraySequence; +class CoordinateSequence; } namespace operation { namespace overlayng { @@ -38,7 +38,7 @@ class MaximalEdgeRing; } using geos::geom::Coordinate; -using geos::geom::CoordinateArraySequence; +using geos::geom::CoordinateXYZM; using geos::geom::CoordinateSequence; using geos::geom::Location; @@ -61,7 +61,7 @@ class GEOS_DLL OverlayEdge : public edgegraph::HalfEdge { * The label must be interpreted accordingly. */ bool direction; - Coordinate dirPt; + CoordinateXYZM dirPt; OverlayLabel* label; bool m_isInResultArea; bool m_isInResultLine; @@ -79,8 +79,7 @@ class GEOS_DLL OverlayEdge : public edgegraph::HalfEdge { public: - // takes ownershiph of CoordinateSequence - OverlayEdge(const Coordinate& p_orig, const Coordinate& p_dirPt, + OverlayEdge(const CoordinateXYZM& p_orig, const CoordinateXYZM& p_dirPt, bool p_direction, OverlayLabel* p_label, const CoordinateSequence* p_pts) : HalfEdge(p_orig) @@ -104,7 +103,7 @@ class GEOS_DLL OverlayEdge : public edgegraph::HalfEdge { return direction; }; - const Coordinate& directionPt() const override + const CoordinateXYZM& directionPt() const override { return dirPt; }; @@ -119,7 +118,7 @@ class GEOS_DLL OverlayEdge : public edgegraph::HalfEdge { return label->getLocation(index, position, direction); }; - const Coordinate& getCoordinate() const + const CoordinateXYZM& getCoordinate() const { return orig(); }; @@ -146,7 +145,7 @@ class GEOS_DLL OverlayEdge : public edgegraph::HalfEdge { * * @param coords the coordinate list to add to */ - void addCoordinates(CoordinateArraySequence* coords) const; + void addCoordinates(CoordinateSequence* coords) const; OverlayEdge* symOE() const { diff --git a/Sources/geos/include/geos/operation/overlayng/OverlayEdgeRing.h b/Sources/geos/include/geos/operation/overlayng/OverlayEdgeRing.h index 2c4fa4d..a5c3237 100644 --- a/Sources/geos/include/geos/operation/overlayng/OverlayEdgeRing.h +++ b/Sources/geos/include/geos/operation/overlayng/OverlayEdgeRing.h @@ -15,7 +15,7 @@ #pragma once #include -#include +#include #include #include @@ -62,17 +62,17 @@ class GEOS_DLL OverlayEdgeRing { std::vector holes; // Methods - void computeRingPts(OverlayEdge* start, CoordinateArraySequence& pts); - void computeRing(std::unique_ptr && ringPts, const GeometryFactory* geometryFactory); + void computeRingPts(OverlayEdge* start, CoordinateSequence& pts); + void computeRing(std::unique_ptr && ringPts, const GeometryFactory* geometryFactory); /** * Computes the list of coordinates which are contained in this ring. * The coordinates are computed once only and cached. * @return an array of the {@link Coordinate}s in this ring */ - const CoordinateArraySequence& getCoordinates(); + const CoordinateSequence& getCoordinates(); PointOnGeometryLocator* getLocator(); - static void closeRing(CoordinateArraySequence& pts); + static void closeRing(CoordinateSequence& pts); public: diff --git a/Sources/geos/include/geos/operation/overlayng/OverlayMixedPoints.h b/Sources/geos/include/geos/operation/overlayng/OverlayMixedPoints.h index c9e6733..b83b880 100644 --- a/Sources/geos/include/geos/operation/overlayng/OverlayMixedPoints.h +++ b/Sources/geos/include/geos/operation/overlayng/OverlayMixedPoints.h @@ -33,7 +33,7 @@ class GeometryFactory; class PrecisionModel; class Geometry; class Coordinate; -class CoordinateArraySequence; +class CoordinateSequence; } namespace algorithm { namespace locate { @@ -98,23 +98,23 @@ class GEOS_DLL OverlayMixedPoints { std::unique_ptr prepareNonPoint(const Geometry* geomInput); - std::unique_ptr computeIntersection(const CoordinateArraySequence* coords) const; + std::unique_ptr computeIntersection(const CoordinateSequence* coords) const; - std::unique_ptr computeUnion(const CoordinateArraySequence* coords); + std::unique_ptr computeUnion(const CoordinateSequence* coords); - std::unique_ptr computeDifference(const CoordinateArraySequence* coords); + std::unique_ptr computeDifference(const CoordinateSequence* coords); std::unique_ptr createPointResult(std::vector>& points) const; - std::vector> findPoints(bool isCovered, const CoordinateArraySequence* coords) const; + std::vector> findPoints(bool isCovered, const CoordinateSequence* coords) const; - std::vector> createPoints(std::set& coords) const; + std::vector> createPoints(const CoordinateSequence& coords) const; - bool hasLocation(bool isCovered, const Coordinate& coord) const; + bool hasLocation(bool isCovered, const CoordinateXY& coord) const; std::unique_ptr copyNonPoint() const; - std::unique_ptr extractCoordinates(const Geometry* points, const PrecisionModel* pm) const; + std::unique_ptr extractCoordinates(const Geometry* points, const PrecisionModel* pm) const; std::vector> extractPolygons(const Geometry* geom) const; diff --git a/Sources/geos/include/geos/operation/overlayng/OverlayNG.h b/Sources/geos/include/geos/operation/overlayng/OverlayNG.h index baec92f..12b10ea 100644 --- a/Sources/geos/include/geos/operation/overlayng/OverlayNG.h +++ b/Sources/geos/include/geos/operation/overlayng/OverlayNG.h @@ -21,7 +21,6 @@ #include #include -#include #include #include #include @@ -170,10 +169,10 @@ class GEOS_DLL OverlayNG { */ static constexpr bool STRICT_MODE_DEFAULT = false; - static constexpr int INTERSECTION = overlay::OverlayOp::opINTERSECTION; - static constexpr int UNION = overlay::OverlayOp::opUNION; - static constexpr int DIFFERENCE = overlay::OverlayOp::opDIFFERENCE; - static constexpr int SYMDIFFERENCE = overlay::OverlayOp::opSYMDIFFERENCE; + static constexpr int INTERSECTION = 1; + static constexpr int UNION = 2; + static constexpr int DIFFERENCE = 3; + static constexpr int SYMDIFFERENCE = 4; /** * Creates an overlay operation on the given geometries, diff --git a/Sources/geos/include/geos/operation/overlayng/OverlayPoints.h b/Sources/geos/include/geos/operation/overlayng/OverlayPoints.h index 2e9c153..e357dcd 100644 --- a/Sources/geos/include/geos/operation/overlayng/OverlayPoints.h +++ b/Sources/geos/include/geos/operation/overlayng/OverlayPoints.h @@ -64,23 +64,25 @@ class GEOS_DLL OverlayPoints { const GeometryFactory* geometryFactory; std::vector> resultList; + using PointMap = std::map>; + // Methods void - computeIntersection(std::map>& map0, - std::map>& map1, + computeIntersection(PointMap& map0, + PointMap& map1, std::vector>& resultList); void - computeDifference(std::map>& map0, - std::map>& map1, + computeDifference(PointMap& map0, + PointMap& map1, std::vector>& resultList); void - computeUnion(std::map>& map0, - std::map>& map1, + computeUnion(PointMap& map0, + PointMap& map1, std::vector>& resultList); - std::map> buildPointMap(const Geometry* geom); + PointMap buildPointMap(const Geometry* geom); public: diff --git a/Sources/geos/include/geos/operation/overlayng/RingClipper.h b/Sources/geos/include/geos/operation/overlayng/RingClipper.h index c81bed4..dffd1fd 100644 --- a/Sources/geos/include/geos/operation/overlayng/RingClipper.h +++ b/Sources/geos/include/geos/operation/overlayng/RingClipper.h @@ -17,7 +17,6 @@ #include #include -#include #include // Forward declarations @@ -25,7 +24,6 @@ namespace geos { namespace geom { class Coordinate; class CoordinateSequence; -class CoordinateArraySequence; } } @@ -81,7 +79,7 @@ class GEOS_DLL RingClipper { /** * Clips line to the axis-parallel line defined by a single box edge. */ - std::unique_ptr clipToBoxEdge(const CoordinateSequence* pts, int edgeIndex, bool closeRing) const; + std::unique_ptr clipToBoxEdge(const CoordinateSequence* pts, int edgeIndex, bool closeRing) const; /** * Computes the intersection point of a segment @@ -103,7 +101,7 @@ class GEOS_DLL RingClipper { /** * Clips a list of points to the clipping rectangle box. */ - std::unique_ptr clip(const CoordinateSequence* cs) const; + std::unique_ptr clip(const CoordinateSequence* cs) const; }; diff --git a/Sources/geos/include/geos/operation/polygonize/BuildArea.h b/Sources/geos/include/geos/operation/polygonize/BuildArea.h index 0dd7d64..679698a 100644 --- a/Sources/geos/include/geos/operation/polygonize/BuildArea.h +++ b/Sources/geos/include/geos/operation/polygonize/BuildArea.h @@ -62,7 +62,7 @@ class GEOS_DLL BuildArea { ~BuildArea() = default; - /** \brief Return the area built fromthe constituent linework of the input geometry. */ + /** \brief Return the area built from the constituent linework of the input geometry. */ std::unique_ptr build(const geom::Geometry* geom); }; diff --git a/Sources/geos/include/geos/operation/polygonize/EdgeRing.h b/Sources/geos/include/geos/operation/polygonize/EdgeRing.h index 71556c3..30f5be2 100644 --- a/Sources/geos/include/geos/operation/polygonize/EdgeRing.h +++ b/Sources/geos/include/geos/operation/polygonize/EdgeRing.h @@ -65,13 +65,14 @@ class GEOS_DLL EdgeRing { // cache the following data for efficiency std::unique_ptr ring; - std::unique_ptr ringPts; + std::unique_ptr ringPts; std::unique_ptr ringLocator; std::unique_ptr>> holes; EdgeRing* shell = nullptr; bool is_hole; + bool is_valid = false; bool is_processed = false; bool is_included_set = false; bool is_included = false; @@ -87,7 +88,7 @@ class GEOS_DLL EdgeRing { static void addEdge(const geom::CoordinateSequence* coords, bool isForward, - geom::CoordinateArraySequence* coordList); + geom::CoordinateSequence* coordList); algorithm::locate::PointOnGeometryLocator* getLocator() { if (ringLocator == nullptr) { @@ -169,6 +170,10 @@ class GEOS_DLL EdgeRing { void computeHole(); + const DeList& getEdges() const { + return deList; + } + /** \brief * Tests whether this ring is a hole. * @@ -298,7 +303,9 @@ class GEOS_DLL EdgeRing { * Tests if the LinearRing ring formed by this edge ring * is topologically valid. */ - bool isValid(); + bool isValid() const; + + void computeValid(); /** \brief * Gets the coordinates for this ring as a LineString. diff --git a/Sources/geos/include/geos/operation/polygonize/PolygonizeGraph.h b/Sources/geos/include/geos/operation/polygonize/PolygonizeGraph.h index b7b7af5..694f8fe 100644 --- a/Sources/geos/include/geos/operation/polygonize/PolygonizeGraph.h +++ b/Sources/geos/include/geos/operation/polygonize/PolygonizeGraph.h @@ -196,7 +196,7 @@ class GEOS_DLL PolygonizeGraph: public planargraph::PlanarGraph { EdgeRing* findEdgeRing(PolygonizeDirectedEdge* startDE); - /* Tese are for memory management */ + /* These are for memory management */ std::vector newEdges; std::vector newDirEdges; std::vector newNodes; diff --git a/Sources/geos/include/geos/operation/polygonize/Polygonizer.h b/Sources/geos/include/geos/operation/polygonize/Polygonizer.h index 73d5db6..1c21a3a 100644 --- a/Sources/geos/include/geos/operation/polygonize/Polygonizer.h +++ b/Sources/geos/include/geos/operation/polygonize/Polygonizer.h @@ -109,7 +109,32 @@ class GEOS_DLL Polygonizer { static void findValidRings(const std::vector& edgeRingList, std::vector& validEdgeRingList, - std::vector>& invalidRingList); + std::vector& invalidRingList); + + /** + * Extracts unique lines for invalid rings, + * discarding rings which correspond to outer rings and hence contain + * duplicate linework. + */ + std::vector> extractInvalidLines( + std::vector& invalidRings); + + /** + * Tests if a invalid ring should be included in + * the list of reported invalid rings. + * + * Rings are included only if they contain + * linework which is not already in a valid ring, + * or in an already-included ring. + * + * Because the invalid rings list is sorted by extent area, + * this results in outer rings being discarded, + * since all their linework is reported in the rings they contain. + * + * @param invalidRing the ring to test + * @return true if the ring should be included + */ + bool isIncludedInvalid(EdgeRing* invalidRing); void findShellsAndHoles(const std::vector& edgeRingList); @@ -167,16 +192,6 @@ class GEOS_DLL Polygonizer { */ void add(std::vector* geomList); - /** - * Add a geometry to the linework to be polygonized. - * May be called multiple times. - * Any dimension of Geometry may be added; - * the constituent linework will be extracted and used - * - * @param g a Geometry with linework to be polygonized - */ - void add(geom::Geometry* g); - /** * Add a geometry to the linework to be polygonized. * May be called multiple times. diff --git a/Sources/geos/include/geos/operation/predicate/RectangleContains.h b/Sources/geos/include/geos/operation/predicate/RectangleContains.h index 040a7ad..2051d7b 100644 --- a/Sources/geos/include/geos/operation/predicate/RectangleContains.h +++ b/Sources/geos/include/geos/operation/predicate/RectangleContains.h @@ -64,7 +64,7 @@ class GEOS_DLL RectangleContains { * @param pt the point to test * @return true if the point is contained in the boundary */ - bool isPointContainedInBoundary(const geom::Coordinate& pt); + bool isPointContainedInBoundary(const geom::CoordinateXY& pt); /** \brief * Tests if a linestring is completely contained in the boundary diff --git a/Sources/geos/include/geos/operation/relate/EdgeEndBuilder.h b/Sources/geos/include/geos/operation/relate/EdgeEndBuilder.h index 7e57d1a..a6c4143 100644 --- a/Sources/geos/include/geos/operation/relate/EdgeEndBuilder.h +++ b/Sources/geos/include/geos/operation/relate/EdgeEndBuilder.h @@ -20,6 +20,7 @@ #include +#include #include // Forward declarations @@ -48,18 +49,18 @@ class GEOS_DLL EdgeEndBuilder { public: EdgeEndBuilder() {} - std::vector computeEdgeEnds(std::vector* edges); - void computeEdgeEnds(geomgraph::Edge* edge, std::vector* l); + std::vector> computeEdgeEnds(std::vector* edges); + void computeEdgeEnds(geomgraph::Edge* edge, std::vector>& l); protected: void createEdgeEndForPrev(geomgraph::Edge* edge, - std::vector* l, + std::vector>& l, const geomgraph::EdgeIntersection* eiCurr, const geomgraph::EdgeIntersection* eiPrev); void createEdgeEndForNext(geomgraph::Edge* edge, - std::vector* l, + std::vector>& l, const geomgraph::EdgeIntersection* eiCurr, const geomgraph::EdgeIntersection* eiNext); }; diff --git a/Sources/geos/include/geos/operation/relate/EdgeEndBundle.h b/Sources/geos/include/geos/operation/relate/EdgeEndBundle.h index 9098ae2..291d49e 100644 --- a/Sources/geos/include/geos/operation/relate/EdgeEndBundle.h +++ b/Sources/geos/include/geos/operation/relate/EdgeEndBundle.h @@ -81,7 +81,7 @@ class GEOS_DLL EdgeEndBundle: public geomgraph::EdgeEnd { * on the boundary and in the interior (e.g. a LineString segment * lying on * top of a Polygon edge.) In this case the Boundary is - * given precendence. + * given precedence. * * These observations result in the following rules for computing * the ON location: diff --git a/Sources/geos/include/geos/operation/relate/RelateComputer.h b/Sources/geos/include/geos/operation/relate/RelateComputer.h index dfdcbd4..6a9d928 100644 --- a/Sources/geos/include/geos/operation/relate/RelateComputer.h +++ b/Sources/geos/include/geos/operation/relate/RelateComputer.h @@ -76,7 +76,7 @@ namespace relate { // geos::operation::relate */ class GEOS_DLL RelateComputer { public: - RelateComputer(std::vector* newArg); + RelateComputer(std::vector>& newArg); ~RelateComputer() = default; std::unique_ptr computeIM(); @@ -87,7 +87,7 @@ class GEOS_DLL RelateComputer { algorithm::PointLocator ptLocator; /// the arg(s) of the operation - std::vector* arg; + const std::vector>& arg; geomgraph::NodeMap nodes; @@ -99,7 +99,7 @@ class GEOS_DLL RelateComputer { /// the intersection point found (if any) geom::Coordinate invalidPoint; - void insertEdgeEnds(std::vector* ee); + void insertEdgeEnds(std::vector>& ee); void computeProperIntersectionIM( geomgraph::index::SegmentIntersector* intersector, diff --git a/Sources/geos/include/geos/operation/relate/RelateNode.h b/Sources/geos/include/geos/operation/relate/RelateNode.h index 5f7727d..047533d 100644 --- a/Sources/geos/include/geos/operation/relate/RelateNode.h +++ b/Sources/geos/include/geos/operation/relate/RelateNode.h @@ -42,7 +42,7 @@ namespace relate { // geos::operation::relate * Represents a node in the topological graph used to compute spatial * relationships. */ -class GEOS_DLL RelateNode: public geomgraph::Node { +class GEOS_DLL RelateNode final: public geomgraph::Node { public: diff --git a/Sources/geos/include/geos/operation/relate/RelateNodeGraph.h b/Sources/geos/include/geos/operation/relate/RelateNodeGraph.h index 257037e..5a13cc7 100644 --- a/Sources/geos/include/geos/operation/relate/RelateNodeGraph.h +++ b/Sources/geos/include/geos/operation/relate/RelateNodeGraph.h @@ -28,7 +28,7 @@ namespace geos { namespace geom { class Coordinate; -struct CoordinateLessThen; +struct CoordinateLessThan; } namespace geomgraph { //class EdgeEndStar; @@ -81,7 +81,7 @@ class GEOS_DLL RelateNodeGraph { void copyNodesAndLabels(geomgraph::GeometryGraph* geomGraph, uint8_t argIndex); - void insertEdgeEnds(std::vector* ee); + void insertEdgeEnds(std::vector>& ee); private: diff --git a/Sources/geos/include/geos/operation/relateng/AdjacentEdgeLocator.h b/Sources/geos/include/geos/operation/relateng/AdjacentEdgeLocator.h new file mode 100644 index 0000000..d6e5769 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/AdjacentEdgeLocator.h @@ -0,0 +1,124 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + + +// Forward declarations +namespace geos { +namespace operation { +namespace relateng { + class NodeSections; + class NodeSection; +} +} +namespace geom { + class CoordinateXY; + class Geometry; + class LinearRing; + class Polygon; +} +} + + +using geos::geom::CoordinateXY; +using geos::geom::CoordinateSequence; +using geos::geom::Geometry; +using geos::geom::LinearRing; +using geos::geom::Polygon; +using geos::geom::Location; + + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + +/** + * Determines the location for a point which is known to lie + * on at least one edge of a set of polygons. + * This provides the union-semantics for determining + * point location in a GeometryCollection, which may + * have polygons with adjacent edges which are effectively + * in the interior of the geometry. + * Note that it is also possible to have adjacent edges which + * lie on the boundary of the geometry + * (e.g. a polygon contained within another polygon with adjacent edges). + * + * @author mdavis + * + */ +class GEOS_DLL AdjacentEdgeLocator { + +public: + + AdjacentEdgeLocator(const Geometry* geom) + { + init(geom); + } + + Location locate(const CoordinateXY* p); + + /** + * Disable copy construction and assignment. Apparently needed to make this + * class compile under MSVC. (See https://stackoverflow.com/q/29565299) + */ + AdjacentEdgeLocator(const AdjacentEdgeLocator&) = delete; + AdjacentEdgeLocator& operator=(const AdjacentEdgeLocator&) = delete; + + +private: + + // Members + + std::vector ringList; + + /* + * When we have to reorient rings, we end up allocating new + * rings, since we cannot reorient the rings of the input + * geometry, so this is where we store those "local" rings. + */ + std::vector> localRingList; + + + // Methods + + void addSections( + const CoordinateXY* p, + const CoordinateSequence* ring, + NodeSections& sections); + + NodeSection* createSection( + const CoordinateXY* p, + const CoordinateXY* prev, + const CoordinateXY* next); + + void init(const Geometry* geom); + + void addRings(const Geometry* geom); + + void addRing(const LinearRing* ring, bool requireCW); + + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/BasicPredicate.h b/Sources/geos/include/geos/operation/relateng/BasicPredicate.h new file mode 100644 index 0000000..2ed3f05 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/BasicPredicate.h @@ -0,0 +1,105 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + +#include +#include + +// Forward declarations +namespace geos { +namespace geom { + class Envelope; +} +} + + +using geos::geom::Envelope; +using geos::geom::Location; + + +namespace geos { // geos. +namespace operation { // geos.operation. +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL BasicPredicate : public TopologyPredicate { + +private: + + static constexpr int UNKNOWN = -1; + static constexpr int FALSE = 0; + static constexpr int TRUE = 1; + + int m_value = UNKNOWN; + + static bool isKnown(int val); + + static bool toBoolean(int val); + + static int toValue(bool val); + + +protected: + + /** + * Updates the predicate value to the given state + * if it is currently unknown. + * + * @param val the predicate value to update + */ + void setValue(bool val); + + void setValue(int val); + + void setValueIf(bool val, bool cond); + + void require(bool cond); + + using TopologyPredicate::requireCovers; + void requireCovers(const Envelope& a, const Envelope& b); + + +public: + + /** + * Tests if two geometries intersect + * based on an interaction at given locations. + * + * @param locA the location on geometry A + * @param locB the location on geometry B + * @return true if the geometries intersect + */ + static bool isIntersection(Location locA, Location locB); + + std::string name() const override = 0; + + void finish() override = 0; + + bool isKnown() const override; + + bool value() const override; + + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/DimensionLocation.h b/Sources/geos/include/geos/operation/relateng/DimensionLocation.h new file mode 100644 index 0000000..6d3d870 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/DimensionLocation.h @@ -0,0 +1,60 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + + +using geos::geom::Location; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL DimensionLocation { + +public: + + enum DimensionLocationType { + EXTERIOR = 2, // == Location.EXTERIOR + POINT_INTERIOR = 103, + LINE_INTERIOR = 110, + LINE_BOUNDARY = 111, + AREA_INTERIOR = 120, + AREA_BOUNDARY = 121 + }; + + static int locationArea(Location loc); + + static int locationLine(Location loc); + + static int locationPoint(Location loc); + + static Location location(int dimLoc); + + static int dimension(int dimLoc); + + static int dimension(int dimLoc, int exteriorDim); + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/EdgeSegmentIntersector.h b/Sources/geos/include/geos/operation/relateng/EdgeSegmentIntersector.h new file mode 100644 index 0000000..1b05b6f --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/EdgeSegmentIntersector.h @@ -0,0 +1,80 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + + +#include +#include +#include + + +// Forward declarations +namespace geos { +namespace noding { + class SegmentString; +} +namespace operation { +namespace relateng { + class RelateSegmentString; + class TopologyComputer; +} +} +} + + +using geos::noding::SegmentIntersector; +using geos::noding::SegmentString; +using geos::algorithm::LineIntersector; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + +class GEOS_DLL EdgeSegmentIntersector : public SegmentIntersector { + +private: + + // Members + LineIntersector li; + TopologyComputer& topoComputer; + + // Methods + + + void addIntersections( + RelateSegmentString* ssA, std::size_t segIndexA, + RelateSegmentString* ssB, std::size_t segIndexB); + + +public: + + EdgeSegmentIntersector(TopologyComputer& p_topoComputer) + : topoComputer(p_topoComputer) + {}; + + void processIntersections( + SegmentString* ss0, std::size_t segIndex0, + SegmentString* ss1, std::size_t segIndex1) override; + + bool isDone() const override; + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/EdgeSegmentOverlapAction.h b/Sources/geos/include/geos/operation/relateng/EdgeSegmentOverlapAction.h new file mode 100644 index 0000000..7f84ec7 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/EdgeSegmentOverlapAction.h @@ -0,0 +1,75 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + ********************************************************************** + * + * Last port: index/chain/MonotoneChainOverlapAction.java rev. 1.6 (JTS-1.10) + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + + +// Forward declarations +namespace geos { +namespace index { +namespace chain { + class MonotoneChain; +} +} +namespace noding { + class SegmentIntersector; +} +} + + +using geos::index::chain::MonotoneChain; +using geos::index::chain::MonotoneChainOverlapAction; +using geos::noding::SegmentIntersector; + + +namespace geos { +namespace operation { // geos::operation +namespace relateng { // geos::operation::relateng + +/** \brief + * The action for the internal iterator for performing + * overlap queries on a MonotoneChain. + */ +class GEOS_DLL EdgeSegmentOverlapAction : public MonotoneChainOverlapAction { + +private: + + SegmentIntersector& si; + + +public: + + EdgeSegmentOverlapAction(SegmentIntersector& p_si) + : si(p_si) + {} + + void overlap( + const MonotoneChain& mc1, std::size_t start1, + const MonotoneChain& mc2, std::size_t start2) override; + + +}; + +} // namespace geos::index::chain +} // namespace geos::index +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/EdgeSetIntersector.h b/Sources/geos/include/geos/operation/relateng/EdgeSetIntersector.h new file mode 100644 index 0000000..35028b5 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/EdgeSetIntersector.h @@ -0,0 +1,94 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + + +#include +#include +#include + + +// Forward declarations +namespace geos { +namespace geom { + class Geometry; + class Envelope; +} +namespace noding { + class SegmentString; +} +namespace operation { +namespace relateng { + class RelateSegmentString; + class EdgeSegmentIntersector; +} +} +} + + +using geos::geom::Envelope; +using geos::geom::Geometry; +using geos::index::strtree::TemplateSTRtree; +using geos::index::chain::MonotoneChain; +using geos::operation::relateng::EdgeSegmentIntersector; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + +class GEOS_DLL EdgeSetIntersector { + +private: + + // Members + TemplateSTRtree index; + // HPRtree index = new HPRtree(); + const Envelope* envelope = nullptr; + std::deque monoChains; + std::size_t overlapCounter = 0; + + + // Methods + + void addToIndex(const SegmentString* segStr); + + void addEdges(std::vector& segStrings); + + +public: + + EdgeSetIntersector( + std::vector& edgesA, + std::vector& edgesB, + const Envelope* env) + : envelope(env) + { + addEdges(edgesA); + addEdges(edgesB); + // build index to ensure thread-safety + // index.build(); + }; + + void process(EdgeSegmentIntersector& intersector); + + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/IMPatternMatcher.h b/Sources/geos/include/geos/operation/relateng/IMPatternMatcher.h new file mode 100644 index 0000000..be8486c --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/IMPatternMatcher.h @@ -0,0 +1,87 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +// Forward declarations +namespace geos { +namespace geom { + class Envelope; +} +} + + +using geos::geom::Envelope; +using geos::geom::Location; +using geos::geom::Dimension; +using geos::geom::IntersectionMatrix; + + +namespace geos { // geos. +namespace operation { // geos.operation. +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL IMPatternMatcher : public IMPredicate { + + +private: + + std::string imPattern; + IntersectionMatrix patternMatrix; + + static bool requireInteraction(const IntersectionMatrix& im); + + static bool isInteraction(int imDim); + + +public: + + IMPatternMatcher(std::string p_imPattern) + : imPattern(p_imPattern) + , patternMatrix(p_imPattern) + {}; + + std::string name() const override; + + using IMPredicate::init; + void init(const Envelope& envA, const Envelope& envB) override; + + bool requireInteraction() const override; + + bool isDetermined() const override; + + bool valueIM() override; + + std::string toString() const; + + friend std::ostream& operator<<(std::ostream& os, const IMPatternMatcher& imp); + +}; + + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/IMPredicate.h b/Sources/geos/include/geos/operation/relateng/IMPredicate.h new file mode 100644 index 0000000..6cf9f9b --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/IMPredicate.h @@ -0,0 +1,131 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +// Forward declarations +namespace geos { +namespace geom { + class Envelope; +} +} + + +using geos::geom::Envelope; +using geos::geom::Location; +using geos::geom::Dimension; +using geos::geom::IntersectionMatrix; + + +namespace geos { // geos. +namespace operation { // geos.operation. +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL IMPredicate : public BasicPredicate { + +private: + + + +protected: + + static constexpr int DIM_UNKNOWN = Dimension::DONTCARE; + + int dimA; + int dimB; + IntersectionMatrix intMatrix; + + /** + * Gets the value of the predicate according to the current + * intersection matrix state. + * + * @return the current predicate value + */ + virtual bool valueIM() = 0; + + /** + * Tests whether predicate evaluation can be short-circuited + * due to the current state of the matrix providing + * enough information to determine the predicate value. + * + * If this value is true then valueIM() + * must provide the correct result of the predicate. + * + * @return true if the predicate value is determined + */ + virtual bool isDetermined() const = 0; + + /** + * Tests whether the exterior of the specified input geometry + * is intersected by any part of the other input. + * + * @param isA the input geometry + * @return true if the input geometry exterior is intersected + */ + bool intersectsExteriorOf(bool isA) const; + + bool isIntersects(Location locA, Location locB) const; + + +public: + + IMPredicate() + { + // intMatrix = new IntersectionMatrix(); + //-- E/E is always dim = 2 + intMatrix.set(Location::EXTERIOR, Location::EXTERIOR, Dimension::A); + } + + static bool isDimsCompatibleWithCovers(int dim0, int dim1); + + void init(int dA, int dB) override; + + void updateDimension(Location locA, Location locB, int dimension) override; + + bool isDimChanged(Location locA, Location locB, int dimension) const; + + using TopologyPredicate::isKnown; + bool isKnown(Location locA, Location locB) const; + + bool isDimension(Location locA, Location locB, int dimension) const; + + int getDimension(Location locA, Location locB) const; + + /** + * Sets the final value based on the state of the IM. + */ + void finish() override; + + std::string toString() const; + + friend std::ostream& operator<<(std::ostream& os, const IMPredicate& imp); + + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/IntersectionMatrixPattern.h b/Sources/geos/include/geos/operation/relateng/IntersectionMatrixPattern.h new file mode 100644 index 0000000..9e7f2f5 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/IntersectionMatrixPattern.h @@ -0,0 +1,65 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +#include + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL IntersectionMatrixPattern { + +private: + + /** + * Cannot be instantiated. + */ + IntersectionMatrixPattern() {}; + + +public: + + /** + * A DE-9IM pattern to detect whether two polygonal geometries are adjacent along + * an edge, but do not overlap. + */ + static constexpr const char* ADJACENT = "F***1****"; + + /** + * A DE-9IM pattern to detect a geometry which properly contains another + * geometry (i.e. which lies entirely in the interior of the first geometry). + */ + static constexpr const char* CONTAINS_PROPERLY = "T**FF*FF*"; + + /** + * A DE-9IM pattern to detect if two geometries intersect in their interiors. + * This can be used to determine if a polygonal coverage contains any overlaps + * (although not whether they are correctly noded). + */ + static constexpr const char* INTERIOR_INTERSECTS = "T********"; + + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/LineStringExtracter.h b/Sources/geos/include/geos/operation/relateng/LineStringExtracter.h new file mode 100644 index 0000000..fc7298f --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/LineStringExtracter.h @@ -0,0 +1,77 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +#include +#include + +// Forward declarations +namespace geos { +namespace geom { + class LineString; + class Geometry; +} +} + + +using geos::geom::LineString; +using geos::geom::Geometry; +using geos::geom::GeometryFilter; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL LineStringExtracter : public GeometryFilter { + +private: + + std::vector& comps; + + +public: + + LineStringExtracter(std::vector& p_comps) + : comps(p_comps) + {} + + void filter_ro(const geom::Geometry* geom) override; + + static void getLines(const Geometry* geom, std::vector& lines); + + static std::vector getLines(const Geometry* geom); + + /** + * Extracts the {@link LineString} elements from a single {@link Geometry} + * and returns them as either a {@link LineString} or {@link MultiLineString}. + * + * @param geom the geometry from which to extract + * @return a linear geometry + */ + // static std::unique_ptr getGeometry(const Geometry* geom); + +}; + + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/LinearBoundary.h b/Sources/geos/include/geos/operation/relateng/LinearBoundary.h new file mode 100644 index 0000000..1803266 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/LinearBoundary.h @@ -0,0 +1,88 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +#include +#include + +// Forward declarations +namespace geos { +namespace algorithm { + class BoundaryNodeRule; +} +namespace geom { + class CoordinateXY; + class LineString; +} +} + + +using geos::algorithm::BoundaryNodeRule; +using geos::geom::Coordinate; +using geos::geom::CoordinateXY; +using geos::geom::LineString; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + +class GEOS_DLL LinearBoundary { + +private: + + // Members + + Coordinate::ConstIntMap m_vertexDegree; + bool m_hasBoundary; + const BoundaryNodeRule& m_boundaryNodeRule; + + +public: + + // Constructors + + LinearBoundary(std::vector& lines, const BoundaryNodeRule& bnRule); + + bool hasBoundary() const; + + bool isBoundary(const CoordinateXY* pt) const; + + +private: + + // Methods + + bool checkBoundary(Coordinate::ConstIntMap& vertexDegree) const; + + static void computeBoundaryPoints( + std::vector& lines, + Coordinate::ConstIntMap& vertexDegree); + + static void addEndpoint( + const CoordinateXY *p, + Coordinate::ConstIntMap& vertexDegree); + + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/NodeSection.h b/Sources/geos/include/geos/operation/relateng/NodeSection.h new file mode 100644 index 0000000..8fe0087 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/NodeSection.h @@ -0,0 +1,166 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +#include +#include + +// Forward declarations +namespace geos { +namespace geom { + class Geometry; +} +} + + +using geos::geom::CoordinateXY; +using geos::geom::Geometry; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + +/** + * Represents a computed node along with the incident edges on either side of + * it (if they exist). + * This captures the information about a node in a geometry component + * required to determine the component's contribution to the node topology. + * A node in an area geometry always has edges on both sides of the node. + * A node in a linear geometry may have one or other incident edge missing, if + * the node occurs at an endpoint of the line. + * The edges of an area node are assumed to be provided + * with CW-shell orientation (as per JTS norm). + * This must be enforced by the caller. + * + * @author Martin Davis + * + */ +class GEOS_DLL NodeSection { + +private: + + // Members + bool m_isA; + int m_dim; + int m_id; + int m_ringId; + const Geometry* m_poly; + bool m_isNodeAtVertex; + const CoordinateXY* m_v0; + const CoordinateXY m_nodePt; + const CoordinateXY* m_v1; + + // Methods + + static int compareWithNull(const CoordinateXY* v0, const CoordinateXY* v1); + + static int compare(int a, int b); + +public: + + NodeSection( + bool isA, + int dim, + int id, + int ringId, + const Geometry* poly, + bool isNodeAtVertex, + const CoordinateXY* v0, + const CoordinateXY nodePt, + const CoordinateXY* v1) + : m_isA(isA) + , m_dim(dim) + , m_id(id) + , m_ringId(ringId) + , m_poly(poly) + , m_isNodeAtVertex(isNodeAtVertex) + , m_v0(v0) + , m_nodePt(nodePt) + , m_v1(v1) + {}; + + NodeSection(const NodeSection* ns) + : m_isA(ns->isA()) + , m_dim(ns->dimension()) + , m_id(ns->id()) + , m_ringId(ns->ringId()) + , m_poly(ns->getPolygonal()) + , m_isNodeAtVertex(ns->isNodeAtVertex()) + , m_v0(ns->getVertex(0)) + , m_nodePt(ns->nodePt()) + , m_v1(ns->getVertex(1)) + {}; + + const CoordinateXY* getVertex(int i) const; + + const CoordinateXY& nodePt() const; + + int dimension() const; + + int id() const; + + int ringId() const; + + /** + * Gets the polygon this section is part of. + * Will be null if section is not on a polygon boundary. + * + * @return the associated polygon, or null + */ + const Geometry* getPolygonal() const; + + bool isShell() const; + + bool isArea() const; + + static bool isAreaArea(const NodeSection& a, const NodeSection& b); + + bool isA() const; + + bool isSameGeometry(const NodeSection& ns) const; + + bool isSamePolygon(const NodeSection& ns) const; + + bool isNodeAtVertex() const; + + bool isProper() const; + + static bool isProper(const NodeSection& a, const NodeSection& b); + + std::string toString() const; + + static std::string edgeRep(const CoordinateXY* p0, const CoordinateXY* p1); + + friend std::ostream& operator<<(std::ostream& os, const NodeSection& ns); + + /** + * Compare node sections by parent geometry, dimension, element id and ring id, + * and edge vertices. + * Sections are assumed to be at the same node point. + */ + int compareTo(const NodeSection& o) const; + + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/NodeSections.h b/Sources/geos/include/geos/operation/relateng/NodeSections.h new file mode 100644 index 0000000..d7d08b3 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/NodeSections.h @@ -0,0 +1,102 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include + + +// Forward declarations +namespace geos { +namespace operation { +namespace relateng { +class RelateNode; +// class NodeSection; +} +} +namespace geom { + class CoordinateXY; + class Geometry; +} +} + + +using geos::geom::CoordinateXY; +using geos::geom::Geometry; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL NodeSections { + +private: + + // Members + const CoordinateXY* nodePt; + std::vector> sections; + + // Methods + + /** + * Sorts the sections so that: + * * lines are before areas + * * edges from the same polygon are contiguous + */ + void prepareSections(); + + static bool hasMultiplePolygonSections( + std::vector>& sections, + std::size_t i); + + static std::vector collectPolygonSections( + std::vector>& sections, + std::size_t i); + + +public: + + NodeSections(const CoordinateXY* pt) + : nodePt(pt) + {}; + + const CoordinateXY* getCoordinate() const; + + void addNodeSection(NodeSection* e); + + bool hasInteractionAB() const; + + const Geometry* getPolygonal(bool isA) const; + + std::unique_ptr createNode(); + + /** + * Disable copy construction and assignment. Apparently needed to make this + * class compile under MSVC. (See https://stackoverflow.com/q/29565299) + */ + NodeSections(const NodeSections&) = delete; + NodeSections& operator=(const NodeSections&) = delete; + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/PolygonNodeConverter.h b/Sources/geos/include/geos/operation/relateng/PolygonNodeConverter.h new file mode 100644 index 0000000..0b1ddce --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/PolygonNodeConverter.h @@ -0,0 +1,112 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include + +// Forward declarations +namespace geos { +namespace operation { +namespace relateng { +// class NodeSection; +} +} +} + + +// using geos::geom::CoordinateXY; +// using geos::geom::Geometry; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + +/** + * Converts the node sections at a polygon node where + * a shell and one or more holes touch, or two or more holes touch. + * This converts the node topological structure from + * the OGC "touching-rings" (AKA "minimal-ring") model to the equivalent "self-touch" + * (AKA "inverted/exverted ring" or "maximal ring") model. + * In the "self-touch" model the converted NodeSection corners enclose areas + * which all lies inside the polygon + * (i.e. they does not enclose hole edges). + * This allows RelateNode to use simple area-additive semantics + * for adding edges and propagating edge locations. + * + * The input node sections are assumed to have canonical orientation + * (CW shells and CCW holes). + * The arrangement of shells and holes must be topologically valid. + * Specifically, the node sections must not cross or be collinear. + * + * This supports multiple shell-shell touches + * (including ones containing holes), and hole-hole touches, + * This generalizes the relate algorithm to support + * both the OGC model and the self-touch model. + * + * @author Martin Davis + * @see RelateNode + */ +class GEOS_DLL PolygonNodeConverter { + +public: + + /** + * Converts a list of sections of valid polygon rings + * to have "self-touching" structure. + * There are the same number of output sections as input ones. + * + * @param polySections the original sections + * @return the converted sections + */ + static std::vector> convert( + std::vector& polySections); + + +private: + + static std::size_t convertShellAndHoles( + std::vector& sections, + std::size_t shellIndex, + std::vector>& convertedSections); + + static std::vector> convertHoles( + std::vector& sections); + + static NodeSection* createSection( + const NodeSection* ns, + const CoordinateXY* v0, + const CoordinateXY* v1); + + static std::vector extractUnique( + std::vector& sections); + + static std::size_t next( + std::vector& ns, std::size_t i); + + static std::size_t findShell( + std::vector& polySections); + + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/RelateEdge.h b/Sources/geos/include/geos/operation/relateng/RelateEdge.h new file mode 100644 index 0000000..96afc24 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/RelateEdge.h @@ -0,0 +1,176 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + +#include +#include + +// Forward declarations +namespace geos { +namespace operation { +namespace relateng { +class RelateNode; +} +} +namespace geom { + class CoordinateXY; + class Geometry; +} +} + + +using geos::geom::CoordinateXY; +using geos::geom::Geometry; +using geos::geom::Location; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL RelateEdge { + +private: + + /** + * Indicates that the location is currently unknown + */ + static constexpr Location LOC_UNKNOWN = Location::NONE; + + // Members + const RelateNode* node; + const CoordinateXY* dirPt; + + int aDim = DIM_UNKNOWN; + Location aLocLeft = LOC_UNKNOWN; + Location aLocRight = LOC_UNKNOWN; + Location aLocLine = LOC_UNKNOWN; + + int bDim = DIM_UNKNOWN; + Location bLocLeft = LOC_UNKNOWN; + Location bLocRight = LOC_UNKNOWN; + Location bLocLine = LOC_UNKNOWN; + + +public: + + // Constants + static constexpr bool IS_FORWARD = true; + static constexpr bool IS_REVERSE = false; + static constexpr int DIM_UNKNOWN = -1; + + // Constructors + RelateEdge( + const RelateNode* node, const CoordinateXY* pt, + bool isA, bool isForward); + + RelateEdge( + const RelateNode* node, const CoordinateXY* pt, + bool isA); + + RelateEdge( + const RelateNode* node, const CoordinateXY* pt, + bool isA, Location locLeft, Location locRight, Location locLine); + + // Methods + static RelateEdge* create( + const RelateNode* node, + const CoordinateXY* dirPt, + bool isA, int dim, bool isForward); + + static std::size_t findKnownEdgeIndex( + std::vector>& edges, + bool isA); + + static void setAreaInterior( + std::vector>& edges, + bool isA); + + bool isInterior(bool isA, int position) const; + + Location location(bool isA, int position) const; + + int compareToEdge(const CoordinateXY* edgeDirPt) const; + + void setDimLocations(bool isA, int dim, Location loc); + + void setAreaInterior(bool isA); + + void setLocation(bool isA, int pos, Location loc); + + void setAllLocations(bool isA, Location loc); + + void setUnknownLocations(bool isA, Location loc); + + void merge(bool isA, int dim, bool isForward); + + std::string toString() const; + + friend std::ostream& operator<<(std::ostream& os, const RelateEdge& re); + + +private: + + // Methods + void mergeSideLocation(bool isA, int pos, Location loc); + + /** + * Area edges override Line edges. + * Merging edges of same dimension is a no-op for + * the dimension and on location. + * But merging an area edge into a line edge + * sets the dimension to A and the location to BOUNDARY. + * + * @param isA + * @param locEdge + */ + void mergeDimEdgeLoc(bool isA, Location locEdge); + + void setDimension(bool isA, int dimension); + + void setLeft(bool isA, Location loc); + + void setRight(bool isA, Location loc); + + void setOn(bool isA, Location loc); + + int dimension(bool isA) const; + + bool isKnown(bool isA) const; + + bool isKnown(bool isA, int pos) const; + + void setLocations(bool isA, Location locLeft, Location locRight, Location locLine); + + void setLocationsLine(bool isA); + + void setLocationsArea(bool isA, bool isForward); + + std::string labelString() const; + + std::string locationString(bool isA) const; + + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/RelateGeometry.h b/Sources/geos/include/geos/operation/relateng/RelateGeometry.h new file mode 100644 index 0000000..53adf01 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/RelateGeometry.h @@ -0,0 +1,272 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +// Forward declarations +namespace geos { +namespace geom { + class CoordinateSequence; + class Envelope; + class Geometry; + class LinearRing; + class LineString; + class MultiPolygon; + class Point; +} +namespace noding { + class SegmentString; +} +} + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + +using namespace geos::geom; +using geos::algorithm::BoundaryNodeRule; +using geos::noding::SegmentString; + + +class GEOS_DLL RelateGeometry { + +private: + + // Members + + const Geometry* geom; + bool m_isPrepared = false; + const Envelope* geomEnv; + const BoundaryNodeRule& boundaryNodeRule; + int geomDim = Dimension::False; + bool isLineZeroLen = false; + bool isGeomEmpty = false; + + Coordinate::ConstXYSet uniquePoints; + std::unique_ptr locator; + int elementId = 0; + bool hasPoints = false; + bool hasLines = false; + bool hasAreas = false; + + /* + * Memory contexts for lower level allocations + */ + std::vector> segStringTempStore; + std::vector> segStringPermStore; + std::vector> csStore; + + + // Methods + + void analyzeDimensions(); + + /** + * Tests if all geometry linear elements are zero-length. + * For efficiency the test avoids computing actual length. + * + * @param geom + * @return + */ + static bool isZeroLength(const Geometry* geom); + + static bool isZeroLength(const LineString* line); + + bool isZeroLengthLine(const Geometry* g) const { + // avoid expensive zero-length calculation if not linear + if (getDimension() != Dimension::L) + return false; + return isZeroLength(g); + }; + + RelatePointLocator* getLocator(); + + Coordinate::ConstXYSet createUniquePoints(); + + void extractSegmentStringsFromAtomic(bool isA, + const Geometry* geom, const MultiPolygon* parentPolygonal, + const Envelope* env, + std::vector& segStrings, + std::vector>& segStore); + + void extractRingToSegmentString(bool isA, + const LinearRing* ring, int ringId, const Envelope* env, + const Geometry* parentPoly, + std::vector& segStrings, + std::vector>& segStore); + + void extractSegmentStrings(bool isA, + const Envelope* env, const Geometry* geom, + std::vector& segStrings, + std::vector>& segStore); + + const CoordinateSequence* orientAndRemoveRepeated( + const CoordinateSequence* cs, bool orientCW); + + const CoordinateSequence* removeRepeated( + const CoordinateSequence* cs); + +public: + + static constexpr bool GEOM_A = true; + static constexpr bool GEOM_B = false; + + RelateGeometry(const Geometry* input) + : RelateGeometry(input, false, BoundaryNodeRule::getBoundaryRuleMod2()) + {}; + + RelateGeometry(const Geometry* input, const BoundaryNodeRule& bnRule) + : RelateGeometry(input, false, bnRule) + {}; + + RelateGeometry(const Geometry* input, bool p_isPrepared, const BoundaryNodeRule& bnRule); + + static std::string name(bool isA); + + const Geometry* getGeometry() const { + return geom; + } + + bool isPrepared() const { + return m_isPrepared; + } + + const Envelope* getEnvelope() const { + return geomEnv; + } + + inline int getDimension() const { + return geomDim; + } + + bool hasDimension(int dim) const { + switch (dim) { + case Dimension::P: return hasPoints; + case Dimension::L: return hasLines; + case Dimension::A: return hasAreas; + } + return false; + } + + /** + * Gets the actual non-empty dimension of the geometry. + * Zero-length LineStrings are treated as Points. + * + * @return the real (non-empty) dimension + */ + int getDimensionReal() const; + + bool hasEdges() const; + + bool isNodeInArea(const CoordinateXY* nodePt, const Geometry* parentPolygonal); + + int locateLineEndWithDim(const CoordinateXY* p); + + /** + * Locates a vertex of a polygon. + * A vertex of a Polygon or MultiPolygon is on + * the {@link Location#BOUNDARY}. + * But a vertex of an overlapped polygon in a GeometryCollection + * may be in the {@link Location#INTERIOR}. + * + * @param pt the polygon vertex + * @return the location of the vertex + */ + Location locateAreaVertex(const CoordinateXY* pt); + + Location locateNode(const CoordinateXY* pt, const Geometry* parentPolygonal); + + int locateWithDim(const CoordinateXY* pt); + + /** + * Indicates whether the geometry requires self-noding + * for correct evaluation of specific spatial predicates. + * Self-noding is required for geometries which may self-cross + * - i.e. lines, and overlapping elements in GeometryCollections. + * Self-noding is not required for polygonal geometries, + * since they can only touch at vertices. + * This ensures that the coordinates of nodes created by + * crossing segments are computed explicitly. + * This ensures that node locations match in situations + * where a self-crossing and mutual crossing occur at the same logical location. + * E.g. a self-crossing line tested against a single segment + * identical to one of the crossed segments. + * + * @return true if self-noding is required for this geometry + */ + bool isSelfNodingRequired() const; + + /** + * Tests whether the geometry has polygonal topology. + * This is not the case if it is a GeometryCollection + * containing more than one polygon (since they may overlap + * or be adjacent). + * The significance is that polygonal topology allows more assumptions + * about the location of boundary vertices. + * + * @return true if the geometry has polygonal topology + */ + bool isPolygonal() const; + + bool isEmpty() const; + + bool hasBoundary(); + + Coordinate::ConstXYSet& getUniquePoints(); + + std::vector getEffectivePoints(); + + /** + * Extract RSegmentStrings from the geometry which + * intersect a given envelope. + * If the envelope is null all edges are extracted. + * @param geomA + * + * @param env the envelope to extract around (may be null) + * @return a list of SegmentStrings + */ + std::vector extractSegmentStrings(bool isA, const Envelope* env); + + std::string toString() const; + + friend std::ostream& operator<<(std::ostream& os, const RelateGeometry& rg); + + /** + * Disable copy construction and assignment. Needed to make this + * class compile under MSVC. (See https://stackoverflow.com/q/29565299) + * Classes with members that are vector<> of unique_ptr<> need this. + */ + RelateGeometry(const RelateGeometry&) = delete; + RelateGeometry& operator=(const RelateGeometry&) = delete; + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/RelateMatrixPredicate.h b/Sources/geos/include/geos/operation/relateng/RelateMatrixPredicate.h new file mode 100644 index 0000000..b54ec2c --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/RelateMatrixPredicate.h @@ -0,0 +1,85 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +// Forward declarations +namespace geos { +namespace geom { + class Envelope; +} +} + + +using geos::geom::Envelope; +using geos::geom::Location; +using geos::geom::Dimension; +using geos::geom::IntersectionMatrix; + + +namespace geos { // geos. +namespace operation { // geos.operation. +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL RelateMatrixPredicate : public IMPredicate { + +public: + + RelateMatrixPredicate() {}; + + std::string name() const override { + return "relateMatrix"; + }; + + bool requireInteraction() const override { + //-- ensure entire matrix is computed + return false; + }; + + bool isDetermined() const override { + //-- ensure entire matrix is computed + return false; + }; + + bool valueIM() override { + //-- indicates full matrix is being evaluated + return false; + }; + + /** + * Gets the current state of the IM matrix (which may only be partially complete). + * + * @return the IM matrix + */ + std::unique_ptr getIM() { + return std::unique_ptr(new IntersectionMatrix(intMatrix)); + } + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/RelateNG.h b/Sources/geos/include/geos/operation/relateng/RelateNG.h new file mode 100644 index 0000000..b55f2dd --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/RelateNG.h @@ -0,0 +1,295 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + + +// Forward declarations +namespace geos { +namespace algorithm { + class BoundaryNodeRule; + +} +namespace geom { + class Geometry; +} +namespace noding { +} +namespace operation { +namespace relateng { + class TopologyPredicate; + class TopologyComputer; + class EdgeSegmentIntersector; +} +} +} + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + + +using geos::geom::CoordinateXY; +using geos::geom::Geometry; +using geos::algorithm::BoundaryNodeRule; +using geos::noding::MCIndexSegmentSetMutualIntersector; + + +/** + * Computes the value of topological predicates between two geometries based on the + * Dimensionally-Extended 9-Intersection Model (DE-9IM). + * Standard and custom topological predicates are provided by RelatePredicate. + * + * The RelateNG algorithm has the following capabilities: + * + * * Efficient short-circuited evaluation of topological predicates + * (including matching custom DE-9IM matrix patterns) + * * Optimized repeated evaluation of predicates against a single geometry + * via cached spatial indexes (AKA "prepared mode") + * * Robust computation (only point-local topology is required, + * so invalid geometry topology does not cause failures) + * * GeometryCollection inputs containing mixed types and overlapping polygons + * are supported, using union semantics. + * * Zero-length LineStrings are treated as being topologically identical to Points. + * * Support for BoundaryNodeRule. + * + * See IntersectionMatrixPattern for a description of DE-9IM patterns. + * + * If not specified, the standard BoundaryNodeRule::MOD2_BOUNDARY_RULE is used. + * + * RelateNG operates in 2D only; it ignores any Z ordinates. + * + * This implementation replaces RelateOp and PreparedGeometry. + * + * FUTURE WORK + * + * * Support for a distance tolerance to provide "approximate" predicate evaluation + * + * @author Martin Davis + * + * @see RelateOp + * @see PreparedGeometry + */ +class GEOS_DLL RelateNG { + +private: + + // Members + const BoundaryNodeRule& boundaryNodeRule; + RelateGeometry geomA; + std::unique_ptr edgeMutualInt = nullptr; + + // Methods + + RelateNG(const Geometry* inputA, bool isPrepared, const BoundaryNodeRule& bnRule) + : boundaryNodeRule(bnRule) + , geomA(inputA, isPrepared, bnRule) + {} + + RelateNG(const Geometry* inputA, bool isPrepared) + : RelateNG(inputA, isPrepared, BoundaryNodeRule::getBoundaryRuleMod2()) + {} + + bool hasRequiredEnvelopeInteraction(const Geometry* b, TopologyPredicate& predicate); + + bool finishValue(TopologyPredicate& predicate); + + void computePP(RelateGeometry& geomB, TopologyComputer& topoComputer); + + void computeAtPoints(RelateGeometry& geom, bool isA, RelateGeometry& geomTarget, TopologyComputer& topoComputer); + + bool computePoints(RelateGeometry& geom, bool isA, RelateGeometry& geomTarget, TopologyComputer& topoComputer); + + void computePoint(bool isA, const CoordinateXY* pt, RelateGeometry& geomTarget, TopologyComputer& topoComputer); + + bool computeLineEnds(RelateGeometry& geom, bool isA, RelateGeometry& geomTarget, TopologyComputer& topoComputer); + + + /** + * Compute the topology of a line endpoint. + * Also reports if the line end is in the exterior of the target geometry, + * to optimize testing multiple exterior endpoints. + * + * @param geom + * @param isA + * @param pt + * @param geomTarget + * @param topoComputer + * @return true if the line endpoint is in the exterior of the target + */ + bool computeLineEnd(RelateGeometry& geom, bool isA, const CoordinateXY* pt, RelateGeometry& geomTarget, TopologyComputer& topoComputer); + + bool computeAreaVertex(RelateGeometry& geom, bool isA, RelateGeometry& geomTarget, TopologyComputer& topoComputer); + + bool computeAreaVertex(RelateGeometry& geom, bool isA, const LinearRing* ring, RelateGeometry& geomTarget, TopologyComputer& topoComputer); + + void computeAtEdges(RelateGeometry& geomB, TopologyComputer& topoComputer); + + void computeEdgesAll(std::vector& edgesB, const Envelope* envInt, EdgeSegmentIntersector& intersector); + + void computeEdgesMutual(std::vector& edgesB, const Envelope* envInt, EdgeSegmentIntersector& intersector); + + + +public: + + /** + * Tests whether the topological relationship between two geometries + * satisfies a topological predicate. + * + * @param a the A input geometry + * @param b the B input geometry + * @param pred the topological predicate + * @return true if the topological relationship is satisfied + */ + static bool relate(const Geometry* a, const Geometry* b, TopologyPredicate& pred); + + /** + * Tests whether the topological relationship between two geometries + * satisfies a topological predicate, + * using a given BoundaryNodeRule. + * + * @param a the A input geometry + * @param b the B input geometry + * @param pred the topological predicate + * @param bnRule the Boundary Node Rule to use + * @return true if the topological relationship is satisfied + */ + static bool relate(const Geometry* a, const Geometry* b, TopologyPredicate& pred, const BoundaryNodeRule& bnRule); + + /** + * Tests whether the topological relationship to a geometry + * matches a DE-9IM matrix pattern. + * + * @param a the A input geometry + * @param b the B input geometry + * @param imPattern the DE-9IM pattern to match + * @return true if the geometries relationship matches the DE-9IM pattern + * + * @see IntersectionMatrixPattern + */ + static bool relate(const Geometry* a, const Geometry* b, const std::string& imPattern); + + /** + * Computes the DE-9IM matrix + * for the topological relationship between two geometries. + * + * @param a the A input geometry + * @param b the B input geometry + * @return the DE-9IM matrix for the topological relationship + */ + static std::unique_ptr relate(const Geometry* a, const Geometry* b); + + /** + * Computes the DE-9IM matrix + * for the topological relationship between two geometries. + * + * @param a the A input geometry + * @param b the B input geometry + * @param bnRule the Boundary Node Rule to use + * @return the DE-9IM matrix for the relationship + */ + static std::unique_ptr relate(const Geometry* a, const Geometry* b, const BoundaryNodeRule& bnRule); + + /** + * Creates a prepared RelateNG instance to optimize the + * evaluation of relationships against a single geometry. + * + * @param a the A input geometry + * @return a prepared instance + */ + static std::unique_ptr prepare(const Geometry* a); + + /** + * Creates a prepared RelateNG instance to optimize the + * computation of predicates against a single geometry, + * using a given BoundaryNodeRule. + * + * @param a the A input geometry + * @param bnRule the required BoundaryNodeRule + * @return a prepared instance + */ + static std::unique_ptr prepare(const Geometry* a, const BoundaryNodeRule& bnRule); + + + /** + * Computes the DE-9IM matrix for the topological relationship to a geometry. + * + * @param b the B geometry to test against + * @return the DE-9IM matrix + */ + std::unique_ptr evaluate(const Geometry* b); + + + /** + * Tests whether the topological relationship to a geometry + * matches a DE-9IM matrix pattern. + * + * @param b the B geometry to test against + * @param imPattern the DE-9IM pattern to match + * @return true if the geometries' topological relationship matches the DE-9IM pattern + * + * @see IntersectionMatrixPattern + */ + bool evaluate(const Geometry* b, const std::string& imPattern); + + /** + * Tests whether the topological relationship to a geometry + * satisfies a topology predicate. + * + * @param b the B geometry to test against + * @param predicate the topological predicate + * @return true if the predicate is satisfied + */ + bool evaluate(const Geometry* b, TopologyPredicate& predicate); + + static bool intersects(const Geometry* a, const Geometry* b); + static bool crosses(const Geometry* a, const Geometry* b); + static bool disjoint(const Geometry* a, const Geometry* b); + static bool touches(const Geometry* a, const Geometry* b); + static bool within(const Geometry* a, const Geometry* b); + static bool contains(const Geometry* a, const Geometry* b); + static bool overlaps(const Geometry* a, const Geometry* b); + static bool covers(const Geometry* a, const Geometry* b); + static bool coveredBy(const Geometry* a, const Geometry* b); + static bool equalsTopo(const Geometry* a, const Geometry* b); + + bool intersects(const Geometry* a); + bool crosses(const Geometry* a); + bool disjoint(const Geometry* a); + bool touches(const Geometry* a); + bool within(const Geometry* a); + bool contains(const Geometry* a); + bool overlaps(const Geometry* a); + bool covers(const Geometry* a); + bool coveredBy(const Geometry* a); + bool equalsTopo(const Geometry* a); + bool relate(const Geometry* a, const std::string& pat); + std::unique_ptr relate(const Geometry* a); + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/RelateNode.h b/Sources/geos/include/geos/operation/relateng/RelateNode.h new file mode 100644 index 0000000..581eec3 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/RelateNode.h @@ -0,0 +1,146 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include + +#include +#include +#include + +#include + + +// Forward declarations +namespace geos { +namespace operation { +namespace relateng { + class NodeSection; +} +} +namespace geom { + class CoordinateXY; + class Geometry; +} +} + + +using geos::geom::CoordinateXY; +using geos::geom::Geometry; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL RelateNode { + +private: + + // Members + + /** + * A list of the edges around the node in CCW order, + * ordered by their CCW angle with the positive X-axis. + */ + std::vector> edges; + + const CoordinateXY* nodePt; + + + // Methods + + void updateEdgesInArea(bool isA, std::size_t indexFrom, std::size_t indexTo); + + void updateIfAreaPrev(bool isA, std::size_t index); + + void updateIfAreaNext(bool isA, std::size_t index); + + const RelateEdge* addLineEdge(bool isA, const CoordinateXY* dirPt); + + const RelateEdge* addAreaEdge(bool isA, const CoordinateXY* dirPt, bool isForward); + + /** + * Adds or merges an edge to the node. + * + * @param isA + * @param dirPt + * @param dim dimension of the geometry element containing the edge + * @param isForward the direction of the edge + * + * @return the created or merged edge for this point + */ + const RelateEdge* addEdge(bool isA, const CoordinateXY* dirPt, int dim, bool isForward); + + void finishNode(bool isA, bool isAreaInterior); + + void propagateSideLocations(bool isA, std::size_t startIndex); + + static std::size_t prevIndex(std::vector>& list, std::size_t index); + + static std::size_t nextIndex(std::vector>& list, std::size_t i); + + std::size_t indexOf( + const std::vector>& edges, + const RelateEdge* edge) const; + + +public: + + RelateNode(const CoordinateXY* pt) + : nodePt(pt) + {}; + + const CoordinateXY* getCoordinate() const; + + const std::vector>& getEdges() const; + + void addEdges(std::vector& nss); + void addEdges(std::vector>& nss); + + void addEdges(const NodeSection* ns); + + /** + * Computes the final topology for the edges around this node. + * Although nodes lie on the boundary of areas or the interior of lines, + * in a mixed GC they may also lie in the interior of an area. + * This changes the locations of the sides and line to Interior. + * + * @param isAreaInteriorA true if the node is in the interior of A + * @param isAreaInteriorB true if the node is in the interior of B + */ + void finish(bool isAreaInteriorA, bool isAreaInteriorB); + + std::string toString() const; + + bool hasExteriorEdge(bool isA); + + friend std::ostream& operator<<(std::ostream& os, const RelateNode& ns); + + /** + * Disable copy construction and assignment. Apparently needed to make this + * class compile under MSVC. (See https://stackoverflow.com/q/29565299) + */ + RelateNode(const RelateNode&) = delete; + RelateNode& operator=(const RelateNode&) = delete; + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/RelatePointLocator.h b/Sources/geos/include/geos/operation/relateng/RelatePointLocator.h new file mode 100644 index 0000000..3ac03da --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/RelatePointLocator.h @@ -0,0 +1,213 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// Forward declarations +namespace geos { +namespace algorithm { + namespace locate { + // class PointOnGeometryLocator; + } +} +namespace operation { + namespace relateng { + // class LinearBoundary; + // class AdjacentEdgeLocator; + } +} +namespace geom { + class CoordinateXY; + class Geometry; + class LineString; + class Point; +} +} + + +using geos::algorithm::BoundaryNodeRule; +using geos::algorithm::locate::PointOnGeometryLocator; +using geos::geom::Coordinate; +using geos::geom::CoordinateXY; +using geos::geom::Geometry; +using geos::geom::LineString; +using geos::geom::Point; +using geos::geom::Location; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + + +/** + * Locates a point on a geometry, including mixed-type collections. + * The dimension of the containing geometry element is also determined. + * GeometryCollections are handled with union semantics; + * i.e. the location of a point is that location of that point + * on the union of the elements of the collection. + * + * Union semantics for GeometryCollections has the following behaviours: + * + * * For a mixed-dimension (heterogeneous) collection + * a point may lie on two geometry elements with different dimensions. + * In this case the location on the largest-dimension element is reported. + * * For a collection with overlapping or adjacent polygons, + * points on polygon element boundaries may lie in the effective interior + * of the collection geometry. + * + * Prepared mode is supported via cached spatial indexes. + * + * @author Martin Davis + */ +class GEOS_DLL RelatePointLocator { + +private: + + // Members + + const Geometry* geom; + bool isPrepared = false; + const BoundaryNodeRule& boundaryRule; + std::unique_ptr adjEdgeLocator; + Coordinate::ConstXYSet points; + std::vector lines; + std::vector polygons; + std::vector> polyLocator; + std::unique_ptr lineBoundary; + bool isEmpty; + + +public: + + // Constructors + + RelatePointLocator(const Geometry* p_geom) + : RelatePointLocator(p_geom, false, BoundaryNodeRule::getBoundaryRuleMod2()) + {}; + + RelatePointLocator(const Geometry* p_geom, bool p_isPrepared, const BoundaryNodeRule& p_bnRule) + : geom(p_geom) + , isPrepared(p_isPrepared) + , boundaryRule(p_bnRule) + { + init(geom); + }; + + void init(const Geometry* p_geom); + + bool hasBoundary() const; + + void extractElements(const Geometry* geom); + + void addPoint(const Point* pt); + + void addLine(const LineString* line); + + void addPolygonal(const Geometry* polygonal); + + Location locate(const CoordinateXY* p); + + int locateLineEndWithDim(const CoordinateXY* p); + + /* + * Locates a point which is known to be a node of the geometry + * (i.e. a vertex or on an edge). + * + * @param p the node point to locate + * @param parentPolygonal the polygon the point is a node of + * @return the location of the node point + */ + Location locateNode(const CoordinateXY* p, const Geometry* parentPolygonal); + + /** + * Locates a point which is known to be a node of the geometry, + * as a DimensionLocation. + * + * @param p the point to locate + * @param parentPolygonal the polygon the point is a node of + * @return the dimension and location of the point + */ + int locateNodeWithDim(const CoordinateXY* p, const Geometry* parentPolygonal); + + /** + * Computes the topological location ( Location) of a single point + * in a Geometry, as well as the dimension of the geometry element the point + * is located in (if not in the Exterior). + * It handles both single-element and multi-element Geometries. + * The algorithm for multi-part Geometries + * takes into account the SFS Boundary Determination Rule. + * + * @param p the point to locate + * @return the Location of the point relative to the input Geometry + */ + int locateWithDim(const CoordinateXY* p); + + +private: + + // Methods + + /** + * Computes the topological location (Location) of a single point + * in a Geometry, as well as the dimension of the geometry element the point + * is located in (if not in the Exterior). + * It handles both single-element and multi-element Geometries. + * The algorithm for multi-part Geometries + * takes into account the SFS Boundary Determination Rule. + * + * @param p the coordinate to locate + * @param isNode whether the coordinate is a node (on an edge) of the geometry + * @param polygon + * @return the Location of the point relative to the input Geometry + */ + int locateWithDim(const CoordinateXY* p, bool isNode, const Geometry* parentPolygonal); + + int computeDimLocation(const CoordinateXY* p, bool isNode, const Geometry* parentPolygonal); + + Location locateOnPoints(const CoordinateXY* p) const; + + Location locateOnLines(const CoordinateXY* p, bool isNode); + + Location locateOnLine(const CoordinateXY* p, /*bool isNode,*/ const LineString* l); + + Location locateOnPolygons(const CoordinateXY* p, bool isNode, const Geometry* parentPolygonal); + + Location locateOnPolygonal(const CoordinateXY* p, + bool isNode, + const Geometry* parentPolygonal, + std::size_t index); + + PointOnGeometryLocator * getLocator(std::size_t index); + + + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/RelatePredicate.h b/Sources/geos/include/geos/operation/relateng/RelatePredicate.h new file mode 100644 index 0000000..ca1db41 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/RelatePredicate.h @@ -0,0 +1,652 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +using geos::geom::Envelope; +using geos::geom::Location; + + +namespace geos { // geos. +namespace operation { // geos.operation. +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL RelatePredicate { + +public: + +/************************************************************************ + * + * Creates a predicate to determine whether two geometries intersect. + * + * The intersects predicate has the following equivalent definitions: + * + * * The two geometries have at least one point in common + * * The DE-9IM Intersection Matrix for the two geometries matches + * at least one of the patterns + * + * [T********] + * [*T*******] + * [***T*****] + * [****T****] + * + * disjoint() = false + * (intersects is the inverse of disjoint) + * + * @return the predicate instance + * + * @see disjoint() + */ +class IntersectsPredicate : public BasicPredicate { + +public: + + std::string name() const override { + return std::string("intersects"); + } + + bool requireSelfNoding() const override { + //-- self-noding is not required to check for a simple interaction + return false; + } + + bool requireExteriorCheck(bool isSourceA) const override { + (void)isSourceA; + //-- intersects only requires testing interaction + return false; + } + + void init(const Envelope& envA, const Envelope& envB) override { + require(envA.intersects(envB)); + } + + void updateDimension(Location locA, Location locB, int dimension) override { + (void)dimension; + setValueIf(true, isIntersection(locA, locB)); + } + + void finish() override { + //-- if no intersecting locations were found + setValue(false); + } + +}; + +static std::unique_ptr intersects(); + +/************************************************************************ + * + * Creates a predicate to determine whether two geometries are disjoint. + * + * The disjoint predicate has the following equivalent definitions: + * + * * The two geometries have no point in common + * * The DE-9IM Intersection Matrix for the two geometries matches + * [FF*FF****] + * * intersects() = false + * (disjoint is the inverse of intersects) + * + * @return the predicate instance + * + * @see intersects() + */ +class DisjointPredicate : public BasicPredicate { + + std::string name() const override { + return std::string("disjoint"); + } + + bool requireSelfNoding() const override { + //-- self-noding is not required to check for a simple interaction + return false; + } + + bool requireInteraction() const override { + //-- ensure entire matrix is computed + return false; + } + + bool requireExteriorCheck(bool isSourceA) const override { + (void)isSourceA; + //-- intersects only requires testing interaction + return false; + } + + void init(const Envelope& envA, const Envelope& envB) override { + setValueIf(true, envA.disjoint(envB)); + } + + void updateDimension(Location locA, Location locB, int dimension) override { + (void)dimension; + setValueIf(false, isIntersection(locA, locB)); + } + + void finish() override { + //-- if no intersecting locations were found + setValue(true); + } +}; + +static std::unique_ptr disjoint(); + +/************************************************************************ + * Creates a predicate to determine whether a geometry contains another geometry. + * + * The contains predicate has the following equivalent definitions: + * + * * Every point of the other geometry is a point of this geometry, + * and the interiors of the two geometries have at least one point in common. + * * The DE-9IM Intersection Matrix for the two geometries matches + * the pattern + * [T*****FF*] + * * within(B, A) = true + * (contains is the converse of within) + * + * An implication of the definition is that "Geometries do not + * contain their boundary". In other words, if a geometry A is a subset of + * the points in the boundary of a geometry B, B.contains(A) = false. + * (As a concrete example, take A to be a LineString which lies in the boundary of a Polygon B.) + * For a predicate with similar behavior but avoiding + * this subtle limitation, see covers(). + * + * @return the predicate instance + * + * @see within() + */ +class ContainsPredicate : public IMPredicate { + + std::string name() const override { + return std::string("contains"); + } + + bool requireCovers(bool isSourceA) override { + return isSourceA == RelateGeometry::GEOM_A; + } + + bool requireExteriorCheck(bool isSourceA) const override { + //-- only need to check B against Exterior of A + return isSourceA == RelateGeometry::GEOM_B; + } + + void init(int _dimA, int _dimB) override { + IMPredicate::init(_dimA, _dimB); + require(isDimsCompatibleWithCovers(dimA, dimB)); + } + + void init(const Envelope& envA, const Envelope& envB) override { + BasicPredicate::requireCovers(envA, envB); + } + + bool isDetermined() const override { + return intersectsExteriorOf(RelateGeometry::GEOM_A); + } + + bool valueIM() override { + return intMatrix.isContains(); + } +}; + +static std::unique_ptr contains(); + + + +/************************************************************************ + * Creates a predicate to determine whether a geometry is within another geometry. + * + * The within predicate has the following equivalent definitions: + * + * * Every point of this geometry is a point of the other geometry, + * and the interiors of the two geometries have at least one point in common. + * * The DE-9IM Intersection Matrix for the two geometries matches + * [T*F**F***] + * * contains(B, A) = true + * (within is the converse of contains()) + * + * An implication of the definition is that + * "The boundary of a Geometry is not within the Geometry". + * In other words, if a geometry A is a subset of + * the points in the boundary of a geometry B, within(B, A) = false + * (As a concrete example, take A to be a LineString which lies in the boundary of a Polygon B.) + * For a predicate with similar behavior but avoiding + * this subtle limitation, see coveredimBy(). + * + * @return the predicate instance + * + * @see #contains() + */ +class WithinPredicate : public IMPredicate { + + std::string name() const override { + return std::string("within"); + } + + bool requireCovers(bool isSourceA) override { + return isSourceA == RelateGeometry::GEOM_B; + } + + bool requireExteriorCheck(bool isSourceA) const override { + //-- only need to check B against Exterior of A + return isSourceA == RelateGeometry::GEOM_A; + } + + void init(int _dimA, int _dimB) override { + IMPredicate::init(_dimA, _dimB); + require(isDimsCompatibleWithCovers(dimB, dimA)); + } + + void init(const Envelope& envA, const Envelope& envB) override { + BasicPredicate::requireCovers(envB, envA); + } + + bool isDetermined() const override { + return intersectsExteriorOf(RelateGeometry::GEOM_B); + } + + bool valueIM() override { + return intMatrix.isWithin(); + } +}; + +static std::unique_ptr within(); + + + +/************************************************************************ + * Creates a predicate to determine whether a geometry covers another geometry. + * + * The covers predicate has the following equivalent definitions: + * + * Every point of the other geometry is a point of this geometry. + * The DE-9IM Intersection Matrix for the two geometries matches + * at least one of the following patterns: + * + * * [T*****FF*] + * * [*T****FF*] + * * [***T**FF*] + * * [****T*FF*] + * + * coveredimBy(b, a) = true + * (covers is the converse of coveredimBy()) + * + * If either geometry is empty, the value of this predicate is false. + * + * This predicate is similar to contains(), + * but is more inclusive (i.e. returns true for more cases). + * In particular, unlike contains it does not distinguish between + * points in the boundary and in the interior of geometries. + * For most cases, covers should be used in preference to contains. + * As an added benefit, covers is more amenable to optimization, + * and hence should be more performant. + * + * @return the predicate instance + * + * @see #coveredimBy() + */ +class CoversPredicate : public IMPredicate { + + std::string name() const override { + return std::string("covers"); + } + + bool requireCovers(bool isSourceA) override { + return isSourceA == RelateGeometry::GEOM_A; + } + + bool requireExteriorCheck(bool isSourceA) const override { + //-- only need to check B against Exterior of A + return isSourceA == RelateGeometry::GEOM_B; + } + + void init(int _dimA, int _dimB) override { + IMPredicate::init(_dimA, _dimB); + require(isDimsCompatibleWithCovers(dimA, dimB)); + } + + void init(const Envelope& envA, const Envelope& envB) override { + BasicPredicate::requireCovers(envA, envB); + + } + + bool isDetermined() const override { + return intersectsExteriorOf(RelateGeometry::GEOM_A); + } + + bool valueIM() override { + return intMatrix.isCovers(); + } +}; + +static std::unique_ptr covers(); + + +/************************************************************************ +* Creates a predicate to determine whether a geometry is covered +* by another geometry. +* +* The coveredimBy predicate has the following equivalent definitions: +* +* Every point of this geometry is a point of the other geometry. +* The DE-9IM Intersection Matrix for the two geometries matches +* at least one of the following patterns: +* +* [T*F**F***] +* [*TF**F***] +* [**FT*F***] +* [**F*TF***] +* +* covers(B, A) = true +* (coveredimBy is the converse of covers()) +* +* If either geometry is empty, the value of this predicate is false. +* +* This predicate is similar to within(), +* but is more inclusive (i.e. returns true for more cases). +* +* @return the predicate instance +* +* @see #covers() +*/ +class CoveredByPredicate : public IMPredicate { + + std::string name() const override { + return std::string("coveredBy"); + } + + bool requireCovers(bool isSourceA) override { + return isSourceA == RelateGeometry::GEOM_B; + } + + bool requireExteriorCheck(bool isSourceA) const override { + //-- only need to check B against Exterior of A + return isSourceA == RelateGeometry::GEOM_A; + } + + void init(int _dimA, int _dimB) override { + IMPredicate::init(_dimA, _dimB); + require(isDimsCompatibleWithCovers(dimB, dimA)); + } + + void init(const Envelope& envA, const Envelope& envB) override { + BasicPredicate::requireCovers(envB, envA); + } + + bool isDetermined() const override { + return intersectsExteriorOf(RelateGeometry::GEOM_B); + } + + bool valueIM() override { + return intMatrix.isCoveredBy(); + } + +}; + +static std::unique_ptr coveredBy(); + + +/************************************************************************ +* Creates a predicate to determine whether a geometry crosses another geometry. +* +* The crosses predicate has the following equivalent definitions: +* +* The geometries have some but not all interior points in common. +* The DE-9IM Intersection Matrix for the two geometries matches +* one of the following patterns: +* +* [T*T******] (for P/L, P/A, and L/A cases) +* [T*****T**] (for L/P, A/P, and A/L cases) +* [0********] (for L/L cases) +* +* +* For the A/A and P/P cases this predicate returns false. +* +* The SFS defined this predicate only for P/L, P/A, L/L, and L/A cases. +* To make the relation symmetric +* JTS extends the definition to apply to L/P, A/P and A/L cases as well. +* +* @return the predicate instance +*/ + +class CrossesPredicate : public IMPredicate { + + std::string name() const override { + return std::string("crosses"); + } + + void init(int _dimA, int _dimB) override { + IMPredicate::init(_dimA, _dimB); + bool isBothPointsOrAreas = + (dimA == Dimension::P && dimB == Dimension::P) || + (dimA == Dimension::A && dimB == Dimension::A); + require(!isBothPointsOrAreas); + } + + bool isDetermined() const override { + if (dimA == Dimension::L && dimB == Dimension::L) { + //-- L/L interaction can only be dim = P + if (getDimension(Location::INTERIOR, Location::INTERIOR) > Dimension::P) + return true; + } + else if (dimA < dimB) { + if (isIntersects(Location::INTERIOR, Location::INTERIOR) && + isIntersects(Location::INTERIOR, Location::EXTERIOR)) { + return true; + } + } + else if (dimA > dimB) { + if (isIntersects(Location::INTERIOR, Location::INTERIOR) && + isIntersects(Location::EXTERIOR, Location::INTERIOR)) { + return true; + } + } + return false; + } + + bool valueIM() override { + return intMatrix.isCrosses(dimA, dimB); + } +}; + +static std::unique_ptr crosses(); + + +/************************************************************************ +* Creates a predicate to determine whether two geometries are +* topologically equal. +* +* The equals predicate has the following equivalent definitions: +* +* The two geometries have at least one point in common, +* and no point of either geometry lies in the exterior of the other geometry. +* The DE-9IM Intersection Matrix for the two geometries matches +* the pattern T*F**FFF* +* +* @return the predicate instance +*/ +class EqualsTopoPredicate : public IMPredicate { + + std::string name() const override { + return std::string("equals"); + } + + bool requireInteraction() const override { + //-- allow EMPTY = EMPTY + return false; + }; + + void init(int _dimA, int _dimB) override { + IMPredicate::init(_dimA, _dimB); + //-- don't require equal dims, because EMPTY = EMPTY for all dims + } + + void init(const Envelope& envA, const Envelope& envB) override { + //-- handle EMPTY = EMPTY cases + setValueIf(true, envA.isNull() && envB.isNull()); + + require(envA.equals(&envB)); + } + + bool isDetermined() const override { + bool isEitherExteriorIntersects = + isIntersects(Location::INTERIOR, Location::EXTERIOR) || + isIntersects(Location::BOUNDARY, Location::EXTERIOR) || + isIntersects(Location::EXTERIOR, Location::INTERIOR) || + isIntersects(Location::EXTERIOR, Location::BOUNDARY); + + return isEitherExteriorIntersects; + } + + bool valueIM() override { + return intMatrix.isEquals(dimA, dimB); + } + +}; + +static std::unique_ptr equalsTopo(); + + +/************************************************************************ + * Creates a predicate to determine whether a geometry overlaps another geometry. + * + * The overlaps predicate has the following equivalent definitions: + * + * The geometries have at least one point each not shared by the other + * (or equivalently neither covers the other), + * they have the same dimension, + * and the intersection of the interiors of the two geometries has + * the same dimension as the geometries themselves. + * The DE-9IM Intersection Matrix for the two geometries matches + * [T*T***T**] (for P/P and A/A cases) + * or [1*T***T**] (for L/L cases) + * + * If the geometries are of different dimension this predicate returns false. + * This predicate is symmetric. + * + * @return the predicate instance + */ +class OverlapsPredicate : public IMPredicate { + + std::string name() const override { + return std::string("overlaps"); + } + + void init(int _dimA, int _dimB) override { + IMPredicate::init(_dimA, _dimB); + require(dimA == dimB); + } + + bool isDetermined() const override { + if (dimA == Dimension::A || dimA == Dimension::P) { + if (isIntersects(Location::INTERIOR, Location::INTERIOR) && + isIntersects(Location::INTERIOR, Location::EXTERIOR) && + isIntersects(Location::EXTERIOR, Location::INTERIOR)) + return true; + } + if (dimA == Dimension::L) { + if (isDimension(Location::INTERIOR, Location::INTERIOR, Dimension::L) && + isIntersects(Location::INTERIOR, Location::EXTERIOR) && + isIntersects(Location::EXTERIOR, Location::INTERIOR)) + return true; + } + return false; + } + + bool valueIM() override { + return intMatrix.isOverlaps(dimA, dimB); + } +}; + +static std::unique_ptr overlaps(); + + + + + +/************************************************************************ +* Creates a predicate to determine whether a geometry touches another geometry. +* +* The touches predicate has the following equivalent definitions: +* +* The geometries have at least one point in common, +* but their interiors do not intersect. +* The DE-9IM Intersection Matrix for the two geometries matches +* at least one of the following patterns +* +* [FT*******] +* [F**T*****] +* [F***T****] +* +* +* If both geometries have dimension 0, the predicate returns false, +* since points have only interiors. +* This predicate is symmetric. +* +* @return the predicate instance +*/ +class TouchesPredicate : public IMPredicate { + + std::string name() const override { + return std::string("touches"); + } + + void init(int _dimA, int _dimB) override { + IMPredicate::init(_dimA, _dimB); + bool isBothPoints = (dimA == 0 && dimB == 0); + require(! isBothPoints); + } + + bool isDetermined() const override { + bool isInteriorsIntersects = isIntersects(Location::INTERIOR, Location::INTERIOR); + return isInteriorsIntersects; + } + + bool valueIM() override { + return intMatrix.isTouches(dimA, dimB); + } +}; + +static std::unique_ptr touches(); + +/** + * Creates a predicate that matches a DE-9IM matrix pattern. + * + * @param imPattern the pattern to match + * @return a predicate that matches the pattern + * + * @see IntersectionMatrixPattern + */ +static std::unique_ptr matches(const std::string& imPattern) +{ + return std::unique_ptr(new IMPatternMatcher(imPattern)); +} + + + +}; // !RelatePredicate + + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/RelateSegmentString.h b/Sources/geos/include/geos/operation/relateng/RelateSegmentString.h new file mode 100644 index 0000000..f2e6caf --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/RelateSegmentString.h @@ -0,0 +1,161 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + + +#include +#include + + +// Forward declarations +namespace geos { +namespace geom { + class CoordinateXY; + class CoordinateSequence; + class Geometry; +} +namespace operation { +namespace relateng { + class RelateGeometry; + class NodeSection; +} +} +} + + +using geos::noding::BasicSegmentString; +using geos::geom::Geometry; +using geos::geom::CoordinateXY; +using geos::geom::CoordinateSequence; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + + + +class GEOS_DLL RelateSegmentString : public BasicSegmentString { + +private: + + // Members + bool m_isA; + int m_dimension; + int m_id; + int m_ringId; + const RelateGeometry* m_inputGeom; + const Geometry* m_parentPolygonal = nullptr; + + // Constructor + RelateSegmentString( + const CoordinateSequence* pts, + bool isA, + int dimension, + int id, + int ringId, + const Geometry* poly, + const RelateGeometry* inputGeom) + : BasicSegmentString(const_cast(pts), nullptr) + , m_isA(isA) + , m_dimension(dimension) + , m_id(id) + , m_ringId(ringId) + , m_inputGeom(inputGeom) + , m_parentPolygonal(poly) + {} + + + // Methods + + static const RelateSegmentString* createSegmentString( + const CoordinateSequence* pts, + bool isA, int dim, int elementId, int ringId, + const Geometry* poly, const RelateGeometry* parent); + + /** + * + * @param ss + * @param segIndex + * @param pt + * @return the previous vertex, or null if none exists + */ + const CoordinateXY* prevVertex( + std::size_t segIndex, + const CoordinateXY* pt) const; + + /** + * @param ss + * @param segIndex + * @param pt + * @return the next vertex, or null if none exists + */ + const CoordinateXY* nextVertex( + std::size_t segIndex, + const CoordinateXY* pt) const; + + +public: + + static const RelateSegmentString* createLine( + const CoordinateSequence* pts, + bool isA, int elementId, + const RelateGeometry* parent); + + static const RelateSegmentString* createRing( + const CoordinateSequence* pts, + bool isA, int elementId, int ringId, + const Geometry* poly, const RelateGeometry* parent); + + inline bool isA() const { + return m_isA; + } + + inline const RelateGeometry* getGeometry() const { + return m_inputGeom; + } + + inline const Geometry* getPolygonal() const { + return m_parentPolygonal; + } + + NodeSection* createNodeSection(std::size_t segIndex, const CoordinateXY intPt) const; + + /** + * Tests if a segment intersection point has that segment as its + * canonical containing segment. + * Segments are half-closed, and contain their start point but not the endpoint, + * except for the final segment in a non-closed segment string, which contains + * its endpoint as well. + * This test ensures that vertices are assigned to a unique segment in a segment string. + * In particular, this avoids double-counting intersections which lie exactly + * at segment endpoints. + * + * @param segIndex the segment the point may lie on + * @param pt the point + * @return true if the segment contains the point + */ + bool isContainingSegment(std::size_t segIndex, const CoordinateXY* pt) const; + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/TopologyComputer.h b/Sources/geos/include/geos/operation/relateng/TopologyComputer.h new file mode 100644 index 0000000..4b52598 --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/TopologyComputer.h @@ -0,0 +1,236 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include + +// Forward declarations +namespace geos { +namespace operation { +namespace relateng { + class NodeSection; + class RelateGeometry; + class RelateNode; + class TopologyPredicate; +} +} +} + + +using geos::geom::CoordinateXY; +using geos::geom::Location; +using geos::operation::relateng::NodeSection; +using geos::operation::relateng::NodeSections; +using geos::operation::relateng::RelateGeometry; +using geos::operation::relateng::RelateNode; +using geos::operation::relateng::TopologyPredicate; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL TopologyComputer { + +private: + + // Members + TopologyPredicate& predicate; + RelateGeometry& geomA; + RelateGeometry& geomB; + std::map nodeMap; + std::deque> nodeSectionsStore; + + // Methods + + /** + * Determine a priori partial EXTERIOR topology based on dimensions. + */ + void initExteriorDims(); + + void initExteriorEmpty(bool geomNonEmpty); + + inline RelateGeometry& getGeometry(bool isA) const { + return isA ? geomA : geomB; + }; + + void updateDim(Location locA, Location locB, int dimension); + + void updateDim(bool isAB, Location loc1, Location loc2, int dimension); + + /** + * Update topology for an intersection between A and B. + * + * @param a the section for geometry A + * @param b the section for geometry B + */ + void updateIntersectionAB(const NodeSection* a, const NodeSection* b); + + /** + * Updates topology for an AB Area-Area crossing node. + * Sections cross at a node if (a) the intersection is proper + * (i.e. in the interior of two segments) + * or (b) if non-proper then whether the linework crosses + * is determined by the geometry of the segments on either side of the node. + * In these situations the area geometry interiors intersect (in dimension 2). + * + * @param a the section for geometry A + * @param b the section for geometry B + */ + void updateAreaAreaCross(const NodeSection* a, const NodeSection* b); + + /** + * Updates topology for a node at an AB edge intersection. + * + * @param a the section for geometry A + * @param b the section for geometry B + */ + void updateNodeLocation(const NodeSection* a, const NodeSection* b); + + void addNodeSections(NodeSection* ns0, NodeSection* ns1); + + void addLineEndOnLine(bool isLineA, Location locLineEnd, Location locLine, const CoordinateXY* pt); + + void addLineEndOnArea(bool isLineA, Location locLineEnd, Location locArea, const CoordinateXY* pt); + + /** + * Updates topology for an area vertex (in Interior or on Boundary) + * intersecting a point. + * Note that because the largest dimension of intersecting target is determined, + * the intersecting point is not part of any other target geometry, + * and hence its neighbourhood is in the Exterior of the target. + * + * @param isAreaA whether the area is the A input + * @param locArea the location of the vertex in the area + * @param pt the point at which topology is being updated + */ + void addAreaVertexOnPoint(bool isAreaA, Location locArea, const CoordinateXY* pt); + + void addAreaVertexOnLine(bool isAreaA, Location locArea, Location locTarget, const CoordinateXY* pt); + + void evaluateNode(NodeSections* nodeSections); + + void evaluateNodeEdges(const RelateNode* node); + + NodeSections* getNodeSections(const CoordinateXY& nodePt); + + + +public: + + TopologyComputer( + TopologyPredicate& p_predicate, + RelateGeometry& p_geomA, + RelateGeometry& p_geomB) + : predicate(p_predicate) + , geomA(p_geomA) + , geomB(p_geomB) + { + initExteriorDims(); + }; + + int getDimension(bool isA) const; + + bool isAreaArea() const; + + /** + * Indicates whether the input geometries require self-noding + * for correct evaluation of specific spatial predicates. + * Self-noding is required for geometries which may + * have self-crossing linework. + * This causes the coordinates of nodes created by + * crossing segments to be computed explicitly. + * This ensures that node locations match in situations + * where a self-crossing and mutual crossing occur at the same logical location. + * The canonical example is a self-crossing line tested against a single segment * identical to one of the crossed segments. + * + * @return true if self-noding is required + */ + bool isSelfNodingRequired() const; + + bool isExteriorCheckRequired(bool isA) const; + + bool isResultKnown() const; + + bool getResult() const; + + /** + * Finalize the evaluation. + */ + void finish(); + + void addIntersection(NodeSection* a, NodeSection* b); + + void addPointOnPointInterior(const CoordinateXY* pt); + + void addPointOnPointExterior(bool isGeomA, const CoordinateXY* pt); + + void addPointOnGeometry(bool isA, Location locTarget, int dimTarget, const CoordinateXY* pt); + + /** + * Add topology for a line end. + * The line end point must be "significant"; + * i.e. not contained in an area if the source is a mixed-dimension GC. + * + * @param isLineA the input containing the line end + * @param locLineEnd the location of the line end (Interior or Boundary) + * @param locTarget the location on the target geometry + * @param dimTarget the dimension of the interacting target geometry element, + * (if any), or the dimension of the target + * @param pt the line end coordinate + */ + void addLineEndOnGeometry(bool isLineA, Location locLineEnd, Location locTarget, int dimTarget, const CoordinateXY* pt); + + /** + * Adds topology for an area vertex interaction with a target geometry element. + * Assumes the target geometry element has highest dimension + * (i.e. if the point lies on two elements of different dimension, + * the location on the higher dimension element is provided. + * This is the semantic provided by {@link RelatePointLocator}. + * + * Note that in a GeometryCollection containing overlapping or adjacent polygons, + * the area vertex location may be INTERIOR instead of BOUNDARY. + * + * @param isAreaA the input that is the area + * @param locArea the location on the area + * @param locTarget the location on the target geometry element + * @param dimTarget the dimension of the target geometry element + * @param pt the point of interaction + */ + void addAreaVertex(bool isAreaA, Location locArea, Location locTarget, int dimTarget, const CoordinateXY* pt); + + void addAreaVertexOnArea(bool isAreaA, Location locArea, Location locTarget, const CoordinateXY* pt); + + void evaluateNodes(); + + /** + * Disable copy construction and assignment. Apparently needed to make this + * class compile under MSVC. (See https://stackoverflow.com/q/29565299) + */ + TopologyComputer(const TopologyComputer&) = delete; + TopologyComputer& operator=(const TopologyComputer&) = delete; + + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/relateng/TopologyPredicate.h b/Sources/geos/include/geos/operation/relateng/TopologyPredicate.h new file mode 100644 index 0000000..6b594db --- /dev/null +++ b/Sources/geos/include/geos/operation/relateng/TopologyPredicate.h @@ -0,0 +1,206 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2024 Martin Davis + * Copyright (C) 2024 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include + + +// Forward declarations +namespace geos { +namespace geom { + class Envelope; +} +} + + +using geos::geom::Envelope; +using geos::geom::Location; + + +namespace geos { // geos. +namespace operation { // geos.operation +namespace relateng { // geos.operation.relateng + + +class GEOS_DLL TopologyPredicate { + +public: + + /* Virtual destructor to ensure proper cleanup of derived classes */ + virtual ~TopologyPredicate() {}; + + /** + * Gets the name of the predicate. + * + * @return the predicate name + */ + virtual std::string name() const = 0; + + /** + * Indicates that the value of the predicate can be finalized + * based on its current state. + */ + virtual void finish() = 0; + + /** + * Tests if the predicate value is known. + * + * @return true if the result is known + */ + virtual bool isKnown() const = 0; + + /** + * Gets the current value of the predicate result. + * The value is only valid if isKnown() is true. + * + * @return the predicate result value + */ + virtual bool value() const = 0; + + /** + * Reports whether this predicate requires self-noding for + * geometries which contain crossing edges + * (for example, LineString, or GeometryCollection + * containing lines or polygons which may self-intersect). + * Self-noding ensures that intersections are computed consistently + * in cases which contain self-crossings and mutual crossings. + * + * Most predicates require this, but it can + * be avoided for simple intersection detection + * (such as in RelatePredicate#intersects() + * and RelatePredicate#disjoint(). + * Avoiding self-noding improves performance for polygonal inputs. + * + * @return true if self-noding is required. + */ + virtual bool requireSelfNoding() const { + return true; + }; + + /** + * Reports whether this predicate requires interaction between + * the input geometries. + * This is the case if + * + * IM[I, I] >= 0 or IM[I, B] >= 0 or IM[B, I] >= 0 or IM[B, B] >= 0 + * + * This allows a fast result if + * the envelopes of the geometries are disjoint. + * + * @return true if the geometries must interact + */ + virtual bool requireInteraction() const { + return true; + }; + + /** + * Reports whether this predicate requires that the source + * cover the target. + * This is the case if + * + * IM[Ext(Src), Int(Tgt)] = F and IM[Ext(Src), Bdy(Tgt)] = F + * + * If true, this allows a fast result if + * the source envelope does not cover the target envelope. + * + * @param isSourceA indicates the source input geometry + * @return true if the predicate requires checking whether the source covers the target + */ + virtual bool requireCovers(bool isSourceA) { + (void)isSourceA; + return false; + } + + /** + * Reports whether this predicate requires checking if the source input intersects + * the Exterior of the target input. + * This is the case if: + * + * IM[Int(Src), Ext(Tgt)] >= 0 or IM[Bdy(Src), Ext(Tgt)] >= 0 + * + * If false, this may permit a faster result in some geometric situations. + * + * @param isSourceA indicates the source input geometry + * @return true if the predicate requires checking whether the source intersects the target exterior + */ + virtual bool requireExteriorCheck(bool isSourceA) const { + (void)isSourceA; + return true; + } + + /** + * Initializes the predicate for a specific geometric case. + * This may allow the predicate result to become known + * if it can be inferred from the dimensions. + * + * @param dimA the dimension of geometry A + * @param dimB the dimension of geometry B + * + * @see Dimension + */ + virtual void init(int dimA, int dimB) { + (void)dimA; + (void)dimB; + }; + + /** + * Initializes the predicate for a specific geometric case. + * This may allow the predicate result to become known + * if it can be inferred from the envelopes. + * + * @param envA the envelope of geometry A + * @param envB the envelope of geometry B + */ + virtual void init(const Envelope& envA, const Envelope& envB) + { + //-- default if envelopes provide no information + (void)envA; + (void)envB; + }; + + /** + * Updates the entry in the DE-9IM intersection matrix + * for given Location in the input geometries. + * + * If this method is called with a {@link Dimension} value + * which is less than the current value for the matrix entry, + * the implementing class should avoid changing the entry + * if this would cause information loss. + * + * @param locA the location on the A axis of the matrix + * @param locB the location on the B axis of the matrix + * @param dimension the dimension value for the entry + * + * @see Dimension + * @see Location + */ + virtual void updateDimension(Location locA, Location locB, int dimension) = 0; + + + friend std::ostream& + operator<<(std::ostream& os, const TopologyPredicate& ns) + { + os << ns.name(); + return os; + } + +}; + +} // namespace geos.operation.relateng +} // namespace geos.operation +} // namespace geos + diff --git a/Sources/geos/include/geos/operation/sharedpaths/SharedPathsOp.h b/Sources/geos/include/geos/operation/sharedpaths/SharedPathsOp.h index 62a4218..dfd75f6 100644 --- a/Sources/geos/include/geos/operation/sharedpaths/SharedPathsOp.h +++ b/Sources/geos/include/geos/operation/sharedpaths/SharedPathsOp.h @@ -79,12 +79,12 @@ class GEOS_DLL SharedPathsOp { /// @param sameDirection /// Shared edges having the same direction are pushed /// onto this vector. They'll be of type LineString. - /// Ownership of the edges is tranferred. + /// Ownership of the edges is transferred. /// /// @param oppositeDirection /// Shared edges having the opposite direction are pushed /// onto this vector. They'll be of type geom::LineString. - /// Ownership of the edges is tranferred. + /// Ownership of the edges is transferred. /// static void sharedPathsOp(const geom::Geometry& g1, const geom::Geometry& g2, @@ -106,12 +106,12 @@ class GEOS_DLL SharedPathsOp { /// @param sameDirection /// Shared edges having the same direction are pushed /// onto this vector. They'll be of type geom::LineString. - /// Ownership of the edges is tranferred. + /// Ownership of the edges is transferred. /// /// @param oppositeDirection /// Shared edges having the opposite direction are pushed /// onto this vector. They'll be of type geom::LineString. - /// Ownership of the edges is tranferred. + /// Ownership of the edges is transferred. /// void getSharedPaths(PathList& sameDirection, PathList& oppositeDirection); diff --git a/Sources/geos/include/geos/operation/union/CascadedPolygonUnion.h b/Sources/geos/include/geos/operation/union/CascadedPolygonUnion.h index 0bcf394..a89e6fa 100644 --- a/Sources/geos/include/geos/operation/union/CascadedPolygonUnion.h +++ b/Sources/geos/include/geos/operation/union/CascadedPolygonUnion.h @@ -73,7 +73,7 @@ class GEOS_DLL ClassicUnionStrategy : public UnionStrategy { /** * An alternative way of unioning polygonal geometries - * by using bufer(0). + * by using buffer(0). * Only worth using if regular overlay union fails. */ std::unique_ptr unionPolygonsByBuffer(const geom::Geometry* g0, const geom::Geometry* g1); diff --git a/Sources/geos/include/geos/operation/union/CoverageUnion.h b/Sources/geos/include/geos/operation/union/CoverageUnion.h index bceeca4..4b29b8e 100644 --- a/Sources/geos/include/geos/operation/union/CoverageUnion.h +++ b/Sources/geos/include/geos/operation/union/CoverageUnion.h @@ -18,33 +18,45 @@ #include #include -#include namespace geos { namespace geom { class Polygon; class LineString; + class LinearRing; class GeometryFactory; } } +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::Polygon; +using geos::geom::LineString; +using geos::geom::LinearRing; +using geos::geom::LineSegment; + + namespace geos { namespace operation { namespace geounion { class GEOS_DLL CoverageUnion { public: - static std::unique_ptr Union(const geom::Geometry* geom); + static std::unique_ptr Union(const Geometry* geom); private: CoverageUnion() = default; - void extractSegments(const geom::Polygon* geom); - void extractSegments(const geom::Geometry* geom); - void extractSegments(const geom::LineString* geom); + void extractRings(const Polygon* geom); + void extractRings(const Geometry* geom); + void extractSegments(const LineString* geom); + void sortRings(); + + std::unique_ptr polygonize(const GeometryFactory* gf); - std::unique_ptr polygonize(const geom::GeometryFactory* gf); - std::unordered_set segments; + std::vector rings; + // std::unordered_set segments; + LineSegment::UnorderedSet segments; static constexpr double AREA_PCT_DIFF_TOL = 1e-6; }; diff --git a/Sources/geos/include/geos/operation/union/DisjointSubsetUnion.h b/Sources/geos/include/geos/operation/union/DisjointSubsetUnion.h new file mode 100644 index 0000000..7a998f3 --- /dev/null +++ b/Sources/geos/include/geos/operation/union/DisjointSubsetUnion.h @@ -0,0 +1,49 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 ISciences LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace geos { +namespace operation { +namespace geounion { + +class GEOS_DLL DisjointSubsetUnion { +public: + /** Perform a unary union on a geometry by combining the results of + * unary unions performed on its disjoint subsets. + * + * @brief Union + * @param g the geometry to union + * @return result geometry + */ + static std::unique_ptr Union(const geom::Geometry* g) { + operation::cluster::GeometryIntersectsClusterFinder f; + operation::cluster::DisjointOperation op(f); + op.setSplitInputs(true); + + return op.processDisjointSubsets(*g, [](const geom::Geometry& subset) { + return subset.Union(); + }); + } +}; + +} +} +} diff --git a/Sources/geos/include/geos/operation/union/UnaryUnionOp.h b/Sources/geos/include/geos/operation/union/UnaryUnionOp.h index a5d1574..3fed7e3 100644 --- a/Sources/geos/include/geos/operation/union/UnaryUnionOp.h +++ b/Sources/geos/include/geos/operation/union/UnaryUnionOp.h @@ -27,9 +27,10 @@ #include #include #include -#include #include +#include + #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class @@ -170,6 +171,8 @@ class GEOS_DLL UnaryUnionOp { void extract(const geom::Geometry& geom) { + util::ensureNoCurvedComponents(geom); + using namespace geom::util; if(! geomFact) { @@ -196,8 +199,6 @@ class GEOS_DLL UnaryUnionOp { std::unique_ptr unionNoOpt(const geom::Geometry& g0) { - using geos::operation::overlay::OverlayOp; - if(! empty.get()) { empty = geomFact->createEmptyGeometry(); } diff --git a/Sources/geos/include/geos/operation/valid/IndexedNestedHoleTester.h b/Sources/geos/include/geos/operation/valid/IndexedNestedHoleTester.h index 9139c97..8786c56 100644 --- a/Sources/geos/include/geos/operation/valid/IndexedNestedHoleTester.h +++ b/Sources/geos/include/geos/operation/valid/IndexedNestedHoleTester.h @@ -36,7 +36,7 @@ namespace valid { // geos.operation.valid using geos::geom::Polygon; using geos::geom::LinearRing; -using geos::geom::Coordinate; +using geos::geom::CoordinateXY; class GEOS_DLL IndexedNestedHoleTester { @@ -45,7 +45,7 @@ class GEOS_DLL IndexedNestedHoleTester { const Polygon* polygon; index::strtree::TemplateSTRtree index; - Coordinate nestedPt; + CoordinateXY nestedPt; void loadIndex(); @@ -63,7 +63,7 @@ class GEOS_DLL IndexedNestedHoleTester { * * @return a point on a nested hole, or null if none are nested */ - const Coordinate& getNestedPoint() { return nestedPt; } + const CoordinateXY& getNestedPoint() { return nestedPt; } /** * Tests if any hole is nested (contained) within another hole. diff --git a/Sources/geos/include/geos/operation/valid/IndexedNestedPolygonTester.h b/Sources/geos/include/geos/operation/valid/IndexedNestedPolygonTester.h index 004d0bc..9ae9dd0 100644 --- a/Sources/geos/include/geos/operation/valid/IndexedNestedPolygonTester.h +++ b/Sources/geos/include/geos/operation/valid/IndexedNestedPolygonTester.h @@ -28,6 +28,7 @@ namespace geom { class Coordinate; class Polygon; class LinearRing; +class MultiPolygon; } } @@ -39,7 +40,7 @@ namespace valid { // geos.operation.valid using geos::geom::Polygon; using geos::geom::MultiPolygon; using geos::geom::LinearRing; -using geos::geom::Coordinate; +using geos::geom::CoordinateXY; using algorithm::locate::IndexedPointInAreaLocator; using index::strtree::TemplateSTRtree; @@ -51,7 +52,7 @@ class GEOS_DLL IndexedNestedPolygonTester { TemplateSTRtree index; // std::vector locators; std::map locators; - Coordinate nestedPt; + CoordinateXY nestedPt; void loadIndex(); @@ -60,7 +61,7 @@ class GEOS_DLL IndexedNestedPolygonTester { bool findNestedPoint(const LinearRing* shell, const Polygon* possibleOuterPoly, IndexedPointInAreaLocator& locator, - Coordinate& coordNested); + CoordinateXY& coordNested); /** * Finds a point of a shell segment which lies inside a polygon, if any. @@ -69,13 +70,13 @@ class GEOS_DLL IndexedNestedPolygonTester { * * @param the shell to test * @param the polygon to test against - * @param coordNested return parametr for found coordinate + * @param coordNested return parameter for found coordinate * @return an interior segment point, or null if the shell is nested correctly */ static bool findIncidentSegmentNestedPoint( const LinearRing* shell, const Polygon* poly, - Coordinate& coordNested); + CoordinateXY& coordNested); // Declare type as noncopyable IndexedNestedPolygonTester(const IndexedNestedPolygonTester& other) = delete; @@ -90,7 +91,7 @@ class GEOS_DLL IndexedNestedPolygonTester { * * @return a point on a nested polygon, or null if none are nested */ - const Coordinate& getNestedPoint() const { return nestedPt; } + const CoordinateXY& getNestedPoint() const { return nestedPt; } /** * Tests if any polygon is nested (contained) within another polygon. diff --git a/Sources/geos/include/geos/operation/valid/IsSimpleOp.h b/Sources/geos/include/geos/operation/valid/IsSimpleOp.h index d6971c9..1f2abfa 100644 --- a/Sources/geos/include/geos/operation/valid/IsSimpleOp.h +++ b/Sources/geos/include/geos/operation/valid/IsSimpleOp.h @@ -21,7 +21,6 @@ #include #include -#include #include // Forward declarations @@ -30,9 +29,9 @@ namespace noding { class SegmentString; class BasicSegmentString; } -namespace algorithm { -class BoundaryNodeRule; -} +// namespace algorithm { +// class BoundaryNodeRule; +// } namespace geom { class LineString; class LinearRing; @@ -100,7 +99,7 @@ class GEOS_DLL IsSimpleOp { bool isClosedEndpointsInInterior = true; bool isFindAllLocations = false; bool isSimpleResult = false; - std::vector nonSimplePts; + std::vector nonSimplePts; bool computed = false; void compute(); @@ -130,11 +129,11 @@ class GEOS_DLL IsSimpleOp { bool isSimpleLinearGeometry(const geom::Geometry& geom); - static std::vector> + static std::vector> removeRepeatedPts(const geom::Geometry& geom); static std::vector> - createSegmentStrings(std::vector>& seqs); + createSegmentStrings(std::vector>& seqs); class NonSimpleIntersectionFinder : public noding::SegmentIntersector { @@ -144,7 +143,7 @@ class GEOS_DLL IsSimpleOp { bool isClosedEndpointsInInterior; bool isFindAll = false; - std::vector& intersectionPts; + std::vector& intersectionPts; algorithm::LineIntersector li; // bool hasInteriorInt; @@ -155,8 +154,8 @@ class GEOS_DLL IsSimpleOp { bool findIntersection( noding::SegmentString* ss0, std::size_t segIndex0, noding::SegmentString* ss1, std::size_t segIndex1, - const geom::Coordinate& p00, const geom::Coordinate& p01, - const geom::Coordinate& p10, const geom::Coordinate& p11); + const geom::CoordinateXY& p00, const geom::CoordinateXY& p01, + const geom::CoordinateXY& p10, const geom::CoordinateXY& p11); /** * Tests whether an intersection vertex is an endpoint of a segment string. @@ -187,7 +186,7 @@ class GEOS_DLL IsSimpleOp { NonSimpleIntersectionFinder( bool p_isClosedEndpointsInInterior, bool p_isFindAll, - std::vector& p_intersectionPts) + std::vector& p_intersectionPts) : isClosedEndpointsInInterior(p_isClosedEndpointsInInterior) , isFindAll(p_isFindAll) , intersectionPts(p_intersectionPts) @@ -256,7 +255,7 @@ class GEOS_DLL IsSimpleOp { * @param geom the input geometry * @return a non-simple location, or null if the geometry is simple */ - geom::Coordinate getNonSimpleLocation(const geom::Geometry& geom); + geom::CoordinateXY getNonSimpleLocation(const geom::Geometry& geom); /** * Sets whether all non-simple intersection points @@ -281,14 +280,14 @@ class GEOS_DLL IsSimpleOp { * @return a coordinate for the location of the non-boundary self-intersection * or null if the geometry is simple */ - geom::Coordinate getNonSimpleLocation(); + geom::CoordinateXY getNonSimpleLocation(); /** * Gets all non-simple intersection locations. * * @return a list of the coordinates of non-simple locations */ - const std::vector& getNonSimpleLocations(); + const std::vector& getNonSimpleLocations(); diff --git a/Sources/geos/include/geos/operation/valid/IsValidOp.h b/Sources/geos/include/geos/operation/valid/IsValidOp.h index 703699e..d669ddf 100644 --- a/Sources/geos/include/geos/operation/valid/IsValidOp.h +++ b/Sources/geos/include/geos/operation/valid/IsValidOp.h @@ -19,12 +19,13 @@ #include #include +#include // Forward declarations namespace geos { namespace geom { -class Coordinate; +class CoordinateXY; class Geometry; class Point; class MultiPoint; @@ -76,8 +77,7 @@ class GEOS_DLL IsValidOp { return validErr != nullptr; } - - void logInvalid(int code, const geom::Coordinate* pt); + void logInvalid(int code, const geom::CoordinateXY& pt); bool isValidGeometry(const geom::Geometry* g); @@ -182,7 +182,7 @@ class GEOS_DLL IsValidOp { * @param shell the polygon shell to test against * @return a hole point outside the shell, or null if it is inside */ - const Coordinate * findHoleOutsideShellPoint( + const CoordinateXY* findHoleOutsideShellPoint( const geom::LinearRing* hole, const geom::LinearRing* shell); @@ -266,7 +266,7 @@ class GEOS_DLL IsValidOp { return ivo.isValid(); }; - static bool isValid(const geom::Coordinate& coord) + static bool isValid(const geom::CoordinateXY& coord) { return isValid(&coord); } @@ -286,7 +286,7 @@ class GEOS_DLL IsValidOp { * @param coord the coordinate to validate * @return true if the coordinate is valid */ - static bool isValid(const geom::Coordinate* coord); + static bool isValid(const geom::CoordinateXY* coord); /** * Computes the validity of the geometry, diff --git a/Sources/geos/include/geos/operation/valid/PolygonIntersectionAnalyzer.h b/Sources/geos/include/geos/operation/valid/PolygonIntersectionAnalyzer.h index 5566602..40542e1 100644 --- a/Sources/geos/include/geos/operation/valid/PolygonIntersectionAnalyzer.h +++ b/Sources/geos/include/geos/operation/valid/PolygonIntersectionAnalyzer.h @@ -15,6 +15,7 @@ #pragma once +#include #include #include #include @@ -26,9 +27,6 @@ // Forward declarations namespace geos { -namespace geom { -class Coordinate; -} namespace noding { class SegmentString; } @@ -38,7 +36,7 @@ namespace geos { // geos. namespace operation { // geos.operation namespace valid { // geos.operation.valid -using geos::geom::Coordinate; +using geos::geom::CoordinateXY; using geos::noding::SegmentString; class GEOS_DLL PolygonIntersectionAnalyzer : public noding::SegmentIntersector { @@ -49,8 +47,8 @@ class GEOS_DLL PolygonIntersectionAnalyzer : public noding::SegmentIntersector { bool m_hasDoubleTouch = false; bool isInvertedRingValid = false; int invalidCode = TopologyValidationError::oNoInvalidIntersection; - Coordinate invalidLocation; - Coordinate doubleTouchLocation; + CoordinateXY invalidLocation; + CoordinateXY doubleTouchLocation; int findInvalidIntersection( const SegmentString* ss0, std::size_t segIndex0, @@ -58,14 +56,14 @@ class GEOS_DLL PolygonIntersectionAnalyzer : public noding::SegmentIntersector { bool addDoubleTouch( const SegmentString* ss0, const SegmentString* ss1, - const Coordinate& intPt); + const CoordinateXY& intPt); void addSelfTouch( - const SegmentString* ss, const Coordinate& intPt, - const Coordinate* e00, const Coordinate* e01, - const Coordinate* e10, const Coordinate* e11); + const SegmentString* ss, const CoordinateXY& intPt, + const CoordinateXY* e00, const CoordinateXY* e01, + const CoordinateXY* e10, const CoordinateXY* e11); - const Coordinate& prevCoordinateInRing( + const CoordinateXY& prevCoordinateInRing( const SegmentString* ringSS, std::size_t segIndex) const; bool isAdjacentInRing(const SegmentString* ringSS, @@ -81,8 +79,8 @@ class GEOS_DLL PolygonIntersectionAnalyzer : public noding::SegmentIntersector { */ PolygonIntersectionAnalyzer(bool p_isInvertedRingValid) : isInvertedRingValid(p_isInvertedRingValid) - , invalidLocation(Coordinate::getNull()) - , doubleTouchLocation(Coordinate::getNull()) + , invalidLocation(CoordinateXY::getNull()) + , doubleTouchLocation(CoordinateXY::getNull()) {} void processIntersections( @@ -103,7 +101,7 @@ class GEOS_DLL PolygonIntersectionAnalyzer : public noding::SegmentIntersector { return invalidCode; }; - const Coordinate& getInvalidLocation() const + const CoordinateXY& getInvalidLocation() const { return invalidLocation; }; @@ -113,7 +111,7 @@ class GEOS_DLL PolygonIntersectionAnalyzer : public noding::SegmentIntersector { return m_hasDoubleTouch; }; - const Coordinate& getDoubleTouchLocation() const + const CoordinateXY& getDoubleTouchLocation() const { return doubleTouchLocation; }; diff --git a/Sources/geos/include/geos/operation/valid/PolygonNode.h b/Sources/geos/include/geos/operation/valid/PolygonNode.h deleted file mode 100644 index a689caf..0000000 --- a/Sources/geos/include/geos/operation/valid/PolygonNode.h +++ /dev/null @@ -1,113 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2021 Paul Ramsey - * Copyright (C) 2021 Martin Davis - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - **********************************************************************/ - -#pragma once - -#include - - -#include - -// Forward declarations -namespace geos { -namespace geom { -class Coordinate; -} -} -namespace geos { // geos. -namespace operation { // geos.operation -namespace valid { // geos.operation.valid - -using geos::geom::Coordinate; - -class GEOS_DLL PolygonNode { - -private: - - /** - * Tests if an edge p is between edges e0 and e1, - * where the edges all originate at a common origin. - * The "inside" of e0 and e1 is the arc which does not include the origin. - * The edges are assumed to be distinct (non-collinear). - * - * @param origin the origin - * @param p the destination point of edge p - * @param e0 the destination point of edge e0 - * @param e1 the destination point of edge e1 - * @return true if p is between e0 and e1 - */ - static bool - isBetween(const Coordinate* origin, const Coordinate* p, - const Coordinate* e0, const Coordinate* e1); - - /** - * Tests if the angle with the origin of a vector P is greater than that of the - * vector Q. - * - * @param origin the origin of the vectors - * @param p the endpoint of the vector P - * @param q the endpoint of the vector Q - * @return true if vector P has angle greater than Q - */ - static bool - isAngleGreater(const Coordinate* origin, - const Coordinate* p, const Coordinate* q); - - static int - quadrant(const Coordinate* origin, const Coordinate* p); - - -public: - - /** - * Check if the edges at a node between two rings (or one ring) cross. - * The node is topologically valid if the ring edges do not cross. - * This function assumes that the edges are not collinear. - * - * @param nodePt the node location - * @param a0 the previous edge endpoint in a ring - * @param a1 the next edge endpoint in a ring - * @param b0 the previous edge endpoint in the other ring - * @param b1 the next edge endpoint in the other ring - * @return true if the edges cross at the node - */ - static bool isCrossing(const Coordinate* nodePt, - const Coordinate* a0, const Coordinate* a1, - const Coordinate* b0, const Coordinate* b1); - - /** - * Tests whether an edge node-b lies in the interior or exterior - * of a corner of a ring given by a0-node-a1. - * The ring interior is assumed to be on the right of the corner (a CW ring). - * The edge must not be collinear with the corner segments. - * - * @param nodePt the node location - * @param a0 the first vertex of the corner - * @param a1 the second vertex of the corner - * @param b the destination vertex of the edge - * @return true if the edge is interior to the ring corner - */ - static bool isInteriorSegment(const Coordinate* nodePt, - const Coordinate* a0, const Coordinate* a1, - const Coordinate* b); - -}; - - - -} // namespace geos.operation.valid -} // namespace geos.operation -} // namespace geos - diff --git a/Sources/geos/include/geos/operation/valid/PolygonRing.h b/Sources/geos/include/geos/operation/valid/PolygonRing.h index 8670f07..fc32e9d 100644 --- a/Sources/geos/include/geos/operation/valid/PolygonRing.h +++ b/Sources/geos/include/geos/operation/valid/PolygonRing.h @@ -27,7 +27,6 @@ // Forward declarations namespace geos { namespace geom { -class Coordinate; class LinearRing; } } @@ -36,7 +35,7 @@ namespace geos { // geos. namespace operation { // geos.operation namespace valid { // geos.operation.valid -using geos::geom::Coordinate; +using geos::geom::CoordinateXY; using geos::geom::LinearRing; @@ -84,7 +83,7 @@ class GEOS_DLL PolygonRing { * @param pt the touch point * @return true if the rings touch only at the given point */ - bool isOnlyTouch(const PolygonRing* polyRing, const Coordinate& pt) const; + bool isOnlyTouch(const PolygonRing* polyRing, const CoordinateXY& pt) const; /** * Detects whether the subgraph of holes linked by touch to this ring @@ -94,7 +93,7 @@ class GEOS_DLL PolygonRing { * * @return a vertex in a hole cycle, or null if no cycle found */ - const Coordinate* findHoleCycleLocation(); + const CoordinateXY* findHoleCycleLocation(); void init(PolygonRing* root, std::stack& touchStack); @@ -106,7 +105,7 @@ class GEOS_DLL PolygonRing { * @param touchStack the stack of touches to scan * @return a vertex in a hole cycle if found, or null */ - const Coordinate* scanForHoleCycle(PolygonRingTouch* currentTouch, + const CoordinateXY* scanForHoleCycle(PolygonRingTouch* currentTouch, PolygonRing* root, std::stack& touchStack); @@ -133,7 +132,7 @@ class GEOS_DLL PolygonRing { std::vector getTouches() const; - void addTouch(PolygonRing* polyRing, const Coordinate& pt); + void addTouch(PolygonRing* polyRing, const CoordinateXY& pt); public: @@ -175,7 +174,7 @@ class GEOS_DLL PolygonRing { * @param pt the location where they touch * @return true if the polygons already touch */ - static bool addTouch(PolygonRing* ring0, PolygonRing* ring1, const Coordinate& pt); + static bool addTouch(PolygonRing* ring0, PolygonRing* ring1, const CoordinateXY& pt); /** * Finds a location (if any) where a chain of holes forms a cycle @@ -186,7 +185,7 @@ class GEOS_DLL PolygonRing { * @param polyRings the list of rings to check * @return a vertex contained in a ring cycle, or null if none is found */ - static const Coordinate* findHoleCycleLocation(std::vector polyRings); + static const CoordinateXY* findHoleCycleLocation(std::vector polyRings); /** * Finds a location of an interior self-touch in a list of rings, @@ -197,7 +196,7 @@ class GEOS_DLL PolygonRing { * @param polyRings the list of rings to check * @return the location of an interior self-touch node, or null if there are none */ - static const Coordinate* findInteriorSelfNode(std::vector polyRings); + static const CoordinateXY* findInteriorSelfNode(std::vector polyRings); bool isSamePolygon(const PolygonRing* polyRing) const { @@ -209,9 +208,9 @@ class GEOS_DLL PolygonRing { return shell == this; }; - void addSelfTouch(const Coordinate& origin, - const Coordinate* e00, const Coordinate* e01, - const Coordinate* e10, const Coordinate* e11); + void addSelfTouch(const CoordinateXY& origin, + const CoordinateXY* e00, const CoordinateXY* e01, + const CoordinateXY* e10, const CoordinateXY* e11); /** * Finds the location of an invalid interior self-touch in this ring, @@ -219,7 +218,7 @@ class GEOS_DLL PolygonRing { * * @return the location of an interior self-touch node, or null if there are none */ - const Coordinate* findInteriorSelfNode(); + const CoordinateXY* findInteriorSelfNode(); }; diff --git a/Sources/geos/include/geos/operation/valid/PolygonRingSelfNode.h b/Sources/geos/include/geos/operation/valid/PolygonRingSelfNode.h index c811b2f..d9cd8b7 100644 --- a/Sources/geos/include/geos/operation/valid/PolygonRingSelfNode.h +++ b/Sources/geos/include/geos/operation/valid/PolygonRingSelfNode.h @@ -17,41 +17,36 @@ #include +#include #include -// Forward declarations -namespace geos { -namespace geom { -class Coordinate; -} -} namespace geos { // geos. namespace operation { // geos.operation namespace valid { // geos.operation.valid -using geos::geom::Coordinate; +using geos::geom::CoordinateXY; class GEOS_DLL PolygonRingSelfNode { private: - Coordinate nodePt; - const Coordinate* e00; - const Coordinate* e01; - const Coordinate* e10; - const Coordinate* e11; + CoordinateXY nodePt; + const CoordinateXY* e00; + const CoordinateXY* e01; + const CoordinateXY* e10; + const CoordinateXY* e11; public: PolygonRingSelfNode( - const Coordinate& p_nodePt, - const Coordinate* p_e00, - const Coordinate* p_e01, - const Coordinate* p_e10, - const Coordinate* p_e11) + const CoordinateXY& p_nodePt, + const CoordinateXY* p_e00, + const CoordinateXY* p_e01, + const CoordinateXY* p_e10, + const CoordinateXY* p_e11) : nodePt(p_nodePt) , e00(p_e00) , e01(p_e01) @@ -64,7 +59,7 @@ class GEOS_DLL PolygonRingSelfNode { * * @return */ - const Coordinate* getCoordinate() const { + const CoordinateXY* getCoordinate() const { return &nodePt; } diff --git a/Sources/geos/include/geos/operation/valid/PolygonRingTouch.h b/Sources/geos/include/geos/operation/valid/PolygonRingTouch.h index 9b36516..c8aea3d 100644 --- a/Sources/geos/include/geos/operation/valid/PolygonRingTouch.h +++ b/Sources/geos/include/geos/operation/valid/PolygonRingTouch.h @@ -16,15 +16,13 @@ #pragma once #include +#include #include // Forward declarations namespace geos { -namespace geom { -class Coordinate; -} namespace operation { namespace valid { class PolygonRing; @@ -36,28 +34,28 @@ namespace geos { // geos. namespace operation { // geos.operation namespace valid { // geos.operation.valid -using geos::geom::Coordinate; +using geos::geom::CoordinateXY; class GEOS_DLL PolygonRingTouch { private: PolygonRing* ring; - Coordinate touchPt; + CoordinateXY touchPt; public: - PolygonRingTouch(PolygonRing* p_ring, const Coordinate& p_pt) + PolygonRingTouch(PolygonRing* p_ring, const CoordinateXY& p_pt) : ring(p_ring) , touchPt(p_pt) {}; - const Coordinate* getCoordinate() const; + const CoordinateXY* getCoordinate() const; PolygonRing* getRing() const; - bool isAtLocation(const Coordinate& pt) const; + bool isAtLocation(const CoordinateXY& pt) const; }; diff --git a/Sources/geos/include/geos/operation/valid/PolygonTopologyAnalyzer.h b/Sources/geos/include/geos/operation/valid/PolygonTopologyAnalyzer.h index b75e1dc..bfb2ed4 100644 --- a/Sources/geos/include/geos/operation/valid/PolygonTopologyAnalyzer.h +++ b/Sources/geos/include/geos/operation/valid/PolygonTopologyAnalyzer.h @@ -17,6 +17,7 @@ #include +#include #include #include #include @@ -27,7 +28,6 @@ namespace geos { namespace geom { class Geometry; -class Coordinate; } } @@ -35,7 +35,7 @@ namespace geos { // geos. namespace operation { // geos.operation namespace valid { // geos.operation.valid -using geos::geom::Coordinate; +using geos::geom::CoordinateXY; using geos::geom::CoordinateSequence; using geos::geom::Geometry; using geos::geom::LinearRing; @@ -48,7 +48,7 @@ class GEOS_DLL PolygonTopologyAnalyzer { bool isInvertedRingValid = false; PolygonIntersectionAnalyzer segInt; std::vector polyRings; - geom::Coordinate disconnectionPt; + geom::CoordinateXY disconnectionPt; // holding area for PolygonRings and SegmentStrings so we @@ -64,8 +64,8 @@ class GEOS_DLL PolygonTopologyAnalyzer { PolygonRing* createPolygonRing(const LinearRing* p_ring); PolygonRing* createPolygonRing(const LinearRing* p_ring, int p_index, PolygonRing* p_shell); - static const Coordinate& - findNonEqualVertex(const LinearRing* ring, const Coordinate& p); + static const CoordinateXY& + findNonEqualVertex(const LinearRing* ring, const CoordinateXY& p); /** * Tests whether a touching segment is interior to a ring. @@ -84,14 +84,14 @@ class GEOS_DLL PolygonTopologyAnalyzer { * @param ringPts the points of the ring * @return true if the segment is inside the ring. */ - static bool isIncidentSegmentInRing(const Coordinate* p0, const Coordinate* p1, + static bool isIncidentSegmentInRing(const CoordinateXY* p0, const CoordinateXY* p1, const CoordinateSequence* ringPts); - static const Coordinate& findRingVertexPrev(const CoordinateSequence* ringPts, - std::size_t index, const Coordinate& node); + static const CoordinateXY& findRingVertexPrev(const CoordinateSequence* ringPts, + std::size_t index, const CoordinateXY& node); - static const Coordinate& findRingVertexNext(const CoordinateSequence* ringPts, - std::size_t index, const Coordinate& node); + static const CoordinateXY& findRingVertexNext(const CoordinateSequence* ringPts, + std::size_t index, const CoordinateXY& node); static std::size_t ringIndexPrev(const CoordinateSequence* ringPts, std::size_t index); @@ -103,7 +103,7 @@ class GEOS_DLL PolygonTopologyAnalyzer { * @param pt the intersection point * @return the intersection segment index, or -1 if no intersection is found */ - static std::size_t intersectingSegIndex(const CoordinateSequence* ringPts, const Coordinate* pt); + static std::size_t intersectingSegIndex(const CoordinateSequence* ringPts, const CoordinateXY* pt); std::vector createSegmentStrings(const Geometry* geom, bool isInvertedRingValid); @@ -126,7 +126,7 @@ class GEOS_DLL PolygonTopologyAnalyzer { * @param ring the ring to analyze * @return a self-intersection point if one exists, or null */ - static Coordinate findSelfIntersection(const LinearRing* ring); + static CoordinateXY findSelfIntersection(const LinearRing* ring); /** * Tests whether a ring is nested inside another ring. @@ -158,7 +158,7 @@ class GEOS_DLL PolygonTopologyAnalyzer { return segInt.getInvalidCode(); } - const Coordinate& getInvalidLocation() { + const CoordinateXY& getInvalidLocation() { return segInt.getInvalidLocation(); } @@ -172,7 +172,7 @@ class GEOS_DLL PolygonTopologyAnalyzer { */ bool isInteriorDisconnected(); - const Coordinate& getDisconnectionLocation() const + const CoordinateXY& getDisconnectionLocation() const { return disconnectionPt; }; diff --git a/Sources/geos/include/geos/operation/valid/RepeatedPointRemover.h b/Sources/geos/include/geos/operation/valid/RepeatedPointRemover.h index 6b00c6d..e6e91c2 100644 --- a/Sources/geos/include/geos/operation/valid/RepeatedPointRemover.h +++ b/Sources/geos/include/geos/operation/valid/RepeatedPointRemover.h @@ -14,7 +14,7 @@ #pragma once -#include +#include #include @@ -38,12 +38,12 @@ namespace valid { * \param tolerance to apply * \return Geometr, ownership of returned object goes to the caller. */ - static std::unique_ptr + static std::unique_ptr removeRepeatedPoints( const geom::CoordinateSequence* seq, double tolerance = 0.0); - static std::unique_ptr + static std::unique_ptr removeRepeatedAndInvalidPoints( const geom::CoordinateSequence* seq, double tolerance = 0.0); diff --git a/Sources/geos/include/geos/operation/valid/RepeatedPointTester.h b/Sources/geos/include/geos/operation/valid/RepeatedPointTester.h index f61c85d..054977c 100644 --- a/Sources/geos/include/geos/operation/valid/RepeatedPointTester.h +++ b/Sources/geos/include/geos/operation/valid/RepeatedPointTester.h @@ -48,11 +48,11 @@ namespace valid { // geos::operation::valid class GEOS_DLL RepeatedPointTester { public: RepeatedPointTester() {} - geom::Coordinate& getCoordinate(); + geom::CoordinateXY& getCoordinate(); bool hasRepeatedPoint(const geom::Geometry* g); bool hasRepeatedPoint(const geom::CoordinateSequence* coord); private: - geom::Coordinate repeatedCoord; + geom::CoordinateXY repeatedCoord; bool hasRepeatedPoint(const geom::Polygon* p); bool hasRepeatedPoint(const geom::GeometryCollection* gc); bool hasRepeatedPoint(const geom::MultiPolygon* gc); diff --git a/Sources/geos/include/geos/operation/valid/TopologyValidationError.h b/Sources/geos/include/geos/operation/valid/TopologyValidationError.h index 736a5f2..c7ecc46 100644 --- a/Sources/geos/include/geos/operation/valid/TopologyValidationError.h +++ b/Sources/geos/include/geos/operation/valid/TopologyValidationError.h @@ -55,9 +55,9 @@ class GEOS_DLL TopologyValidationError { oNoInvalidIntersection = -1 }; - TopologyValidationError(int newErrorType, const geom::Coordinate& newPt); + TopologyValidationError(int newErrorType, const geom::CoordinateXY& newPt); TopologyValidationError(int newErrorType); - const geom::Coordinate& getCoordinate() const; + const geom::CoordinateXY& getCoordinate() const; std::string getMessage() const; int getErrorType() const; std::string toString() const; @@ -66,7 +66,7 @@ class GEOS_DLL TopologyValidationError { // Used const char* to reduce dynamic allocations static const char* errMsg[]; int errorType; - const geom::Coordinate pt; + const geom::CoordinateXY pt; }; diff --git a/Sources/geos/include/geos/planargraph/DirectedEdgeStar.h b/Sources/geos/include/geos/planargraph/DirectedEdgeStar.h index dae69f5..4ad9e9c 100644 --- a/Sources/geos/include/geos/planargraph/DirectedEdgeStar.h +++ b/Sources/geos/include/geos/planargraph/DirectedEdgeStar.h @@ -101,7 +101,7 @@ class GEOS_DLL DirectedEdgeStar { } /** - * \brief Returns the coordinate for the node at wich this + * \brief Returns the coordinate for the node at which this * star is based */ geom::Coordinate& getCoordinate() const; diff --git a/Sources/geos/include/geos/planargraph/NodeMap.h b/Sources/geos/include/geos/planargraph/NodeMap.h index e6e3de8..aa39db0 100644 --- a/Sources/geos/include/geos/planargraph/NodeMap.h +++ b/Sources/geos/include/geos/planargraph/NodeMap.h @@ -46,7 +46,7 @@ namespace planargraph { // geos.planargraph */ class GEOS_DLL NodeMap { public: - typedef std::map container; + typedef std::map container; private: container nodeMap; public: diff --git a/Sources/geos/include/geos/planargraph/Subgraph.h b/Sources/geos/include/geos/planargraph/Subgraph.h index c3fa2e6..18ee0d5 100644 --- a/Sources/geos/include/geos/planargraph/Subgraph.h +++ b/Sources/geos/include/geos/planargraph/Subgraph.h @@ -83,7 +83,7 @@ class GEOS_DLL Subgraph { * * @return a pair with first element being an iterator * to the Edge in set and second element - * being a boolean value indicating wheter + * being a boolean value indicating whether * the Edge has been inserted now or was * already in the set. */ diff --git a/Sources/geos/include/geos/precision/CommonBitsOp.h b/Sources/geos/include/geos/precision/CommonBitsOp.h index 69e565b..9216411 100644 --- a/Sources/geos/include/geos/precision/CommonBitsOp.h +++ b/Sources/geos/include/geos/precision/CommonBitsOp.h @@ -149,7 +149,7 @@ class GEOS_DLL CommonBitsOp { double distance); /** \brief - * If required, returning the result to the orginal precision + * If required, returning the result to the original precision * if required. * * In this current implementation, no rounding is performed on the diff --git a/Sources/geos/include/geos/precision/GeometryPrecisionReducer.h b/Sources/geos/include/geos/precision/GeometryPrecisionReducer.h index 5aca042..cb0cb9a 100644 --- a/Sources/geos/include/geos/precision/GeometryPrecisionReducer.h +++ b/Sources/geos/include/geos/precision/GeometryPrecisionReducer.h @@ -45,7 +45,7 @@ namespace precision { // geos.precision * It can be forced to be reduced pointwise by using setPointwise(boolean). * Note that in this case the result geometry may be invalid. * Linear and point geometry is always reduced pointwise (i.e. without further change to - * its topology or stucture), since this does not change validity. + * its topology or structure), since this does not change validity. * * By default the geometry precision model is not changed. * This can be overridden by usingsetChangePrecisionModel(boolean). @@ -193,6 +193,8 @@ class GEOS_DLL GeometryPrecisionReducer { std::unique_ptr reduce(const geom::Geometry& geom); + + }; } // namespace geos.precision diff --git a/Sources/geos/include/geos/precision/PointwisePrecisionReducerTransformer.h b/Sources/geos/include/geos/precision/PointwisePrecisionReducerTransformer.h index bafe048..9ea0671 100644 --- a/Sources/geos/include/geos/precision/PointwisePrecisionReducerTransformer.h +++ b/Sources/geos/include/geos/precision/PointwisePrecisionReducerTransformer.h @@ -43,7 +43,7 @@ class GEOS_DLL PointwisePrecisionReducerTransformer : public geom::util::Geometr const geom::PrecisionModel& targetPM; - std::vector reducePointwise( + std::unique_ptr reducePointwise( const geom::CoordinateSequence* coordinates); public: @@ -63,7 +63,7 @@ class GEOS_DLL PointwisePrecisionReducerTransformer : public geom::util::Geometr std::unique_ptr transformCoordinates( const geom::CoordinateSequence* coords, - const geom::Geometry* parent); + const geom::Geometry* parent) override; }; diff --git a/Sources/geos/include/geos/precision/PrecisionReducerTransformer.h b/Sources/geos/include/geos/precision/PrecisionReducerTransformer.h index f9ead1c..49d0949 100644 --- a/Sources/geos/include/geos/precision/PrecisionReducerTransformer.h +++ b/Sources/geos/include/geos/precision/PrecisionReducerTransformer.h @@ -53,7 +53,7 @@ class GEOS_DLL PrecisionReducerTransformer : public geom::util::GeometryTransfor std::unique_ptr reduceArea(const geom::Geometry* geom); void extend( - std::vector& coords, + geom::CoordinateSequence& coords, std::size_t minLength); diff --git a/Sources/geos/include/geos/shape/fractal/HilbertEncoder.h b/Sources/geos/include/geos/shape/fractal/HilbertEncoder.h index 61c0010..9d270f9 100644 --- a/Sources/geos/include/geos/shape/fractal/HilbertEncoder.h +++ b/Sources/geos/include/geos/shape/fractal/HilbertEncoder.h @@ -16,15 +16,15 @@ #pragma once #include +#include #include #include +#include // Forward declarations namespace geos { namespace geom { class Coordinate; -class Geometry; -class Envelope; } } @@ -41,6 +41,28 @@ class GEOS_DLL HilbertEncoder { uint32_t encode(const geom::Envelope* env); static void sort(std::vector& geoms); + template + static geom::Envelope getEnvelope(T begin, T end) { + geom::Envelope extent; + for (auto it = begin; it != end; ++it) { + const auto* g = *it; + if (extent.isNull()) + extent = *(g->getEnvelopeInternal()); + else + extent.expandToInclude(*(g->getEnvelopeInternal())); + } + + return extent; + } + + template + static void sort(T begin, T end) { + auto extent = getEnvelope(begin, end); + HilbertEncoder encoder(12, extent); + HilbertComparator hilbertCompare(encoder); + std::sort(begin, end, hilbertCompare); + } + private: uint32_t level; @@ -49,6 +71,20 @@ class GEOS_DLL HilbertEncoder { double strideX; double strideY; + struct HilbertComparator { + + HilbertEncoder& enc; + + HilbertComparator(HilbertEncoder& e) + : enc(e) {}; + + bool + operator()(const geom::Geometry* a, const geom::Geometry* b) + { + return enc.encode(a->getEnvelopeInternal()) > enc.encode(b->getEnvelopeInternal()); + } + }; + }; diff --git a/Sources/geos/include/geos/simplify/ComponentJumpChecker.h b/Sources/geos/include/geos/simplify/ComponentJumpChecker.h new file mode 100644 index 0000000..59fdd2e --- /dev/null +++ b/Sources/geos/include/geos/simplify/ComponentJumpChecker.h @@ -0,0 +1,120 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://libgeos.org + * + * Copyright (C) 2006 Refractions Research Inc. + * Copyright (C) 2023 Martin Davis + * Copyright (C) 2023 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +#include +#include +#include + + +// Forward declarations +namespace geos { +namespace geom { +class Coordinate; +class CoordinateSequence; +class Envelope; +class LineSegment; +} +namespace simplify { +class TaggedLineString; +} +} + +using geos::geom::Coordinate; +using geos::geom::Envelope; +using geos::geom::LineSegment; + +namespace geos { +namespace simplify { // geos::simplify + + +class GEOS_DLL ComponentJumpChecker { + +private: + + const std::vector& components; + + static bool hasJumpAtComponent( + const Coordinate& compPt, + const TaggedLineString* line, + std::size_t start, std::size_t end, + const LineSegment& seg); + + static bool hasJumpAtComponent( + const Coordinate& compPt, + const LineSegment* seg1, const LineSegment* seg2, + const LineSegment& seg); + + static std::size_t crossingCount( + const Coordinate& compPt, + const LineSegment& seg); + + static std::size_t crossingCount( + const Coordinate& compPt, + const LineSegment* seg1, const LineSegment* seg2); + + std::size_t static crossingCount( + const Coordinate& compPt, + const TaggedLineString* line, + std::size_t start, std::size_t end); + + static Envelope computeEnvelope( + const LineSegment* seg1, const LineSegment* seg2); + + static Envelope computeEnvelope( + const TaggedLineString* line, + std::size_t start, std::size_t end); + + +public: + + ComponentJumpChecker(const std::vector& taggedLines) + : components(taggedLines) + {} + + bool hasJump( + const TaggedLineString* line, + std::size_t start, std::size_t end, + const LineSegment& seg) const; + + /** + * Checks if two consecutive segments jumps a component if flattened. + * The segments are assumed to be consecutive. + * (so the seg1.p1 = seg2.p0). + * The flattening segment must be the segment between seg1.p0 and seg2.p1. + * + * @param line the line containing the section being flattened + * @param seg1 the first replaced segment + * @param seg2 the next replaced segment + * @param seg the flattening segment + * @return true if the flattened segment jumps a component + */ + bool hasJump( + const TaggedLineString* line, + const LineSegment* seg1, + const LineSegment* seg2, + const LineSegment& seg) const; + +}; + +} // namespace geos::simplify +} // namespace geos + + + + + diff --git a/Sources/geos/include/geos/simplify/DouglasPeuckerLineSimplifier.h b/Sources/geos/include/geos/simplify/DouglasPeuckerLineSimplifier.h index b3acf60..c45f16f 100644 --- a/Sources/geos/include/geos/simplify/DouglasPeuckerLineSimplifier.h +++ b/Sources/geos/include/geos/simplify/DouglasPeuckerLineSimplifier.h @@ -19,6 +19,7 @@ #pragma once #include +#include #include #include // for unique_ptr @@ -45,22 +46,16 @@ class GEOS_DLL DouglasPeuckerLineSimplifier { public: - typedef std::vector BoolVect; - typedef std::unique_ptr BoolVectAutoPtr; - - typedef std::vector CoordsVect; - typedef std::unique_ptr CoordsVectAutoPtr; - - /** \brief * Returns a newly allocated Coordinate vector, wrapped * into an unique_ptr */ - static CoordsVectAutoPtr simplify( - const CoordsVect& nPts, - double distanceTolerance); + static std::unique_ptr simplify( + const geom::CoordinateSequence& nPts, + double distanceTolerance, + bool preserveClosedEndpoint); - DouglasPeuckerLineSimplifier(const CoordsVect& nPts); + DouglasPeuckerLineSimplifier(const geom::CoordinateSequence& nPts); /** \brief * Sets the distance tolerance for the simplification. @@ -72,17 +67,25 @@ class GEOS_DLL DouglasPeuckerLineSimplifier { */ void setDistanceTolerance(double nDistanceTolerance); + /** \brief + * Sets whether the endpoint of a closed LineString should be preserved + * + * @param preserve `true` if the endpoint should be preserved + */ + void setPreserveClosedEndpoint(bool preserve); + /** \brief * Returns a newly allocated Coordinate vector, wrapped * into an unique_ptr */ - CoordsVectAutoPtr simplify(); + std::unique_ptr simplify(); private: - const CoordsVect& pts; - BoolVectAutoPtr usePt; + const geom::CoordinateSequence& pts; + std::vector usePt; double distanceTolerance; + bool preserveEndpoint; void simplifySection(std::size_t i, std::size_t j); diff --git a/Sources/geos/include/geos/simplify/LineSegmentIndex.h b/Sources/geos/include/geos/simplify/LineSegmentIndex.h index 136d7be..3a8de12 100644 --- a/Sources/geos/include/geos/simplify/LineSegmentIndex.h +++ b/Sources/geos/include/geos/simplify/LineSegmentIndex.h @@ -60,7 +60,7 @@ class GEOS_DLL LineSegmentIndex { void remove(const geom::LineSegment* seg); - std::unique_ptr< std::vector > + std::vector query(const geom::LineSegment* seg); diff --git a/Sources/geos/include/geos/simplify/LinkedLine.h b/Sources/geos/include/geos/simplify/LinkedLine.h new file mode 100644 index 0000000..7d78d59 --- /dev/null +++ b/Sources/geos/include/geos/simplify/LinkedLine.h @@ -0,0 +1,84 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2021 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#pragma once + +// #include + +#include + +#include +#include + + +namespace geos { +namespace geom { +class Coordinate; +class CoordinateSequence; +} +} + +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; + + +namespace geos { +namespace simplify { // geos::simplify + +class LinkedLine +{ + +public: + + LinkedLine(const CoordinateSequence& pts); + + bool isRing() const; + bool isCorner(std::size_t i) const; + + std::size_t size() const; + std::size_t next(std::size_t i) const; + std::size_t prev(std::size_t i) const; + + const Coordinate& getCoordinate(std::size_t index) const; + const Coordinate& prevCoordinate(std::size_t index) const; + const Coordinate& nextCoordinate(std::size_t index) const; + + bool hasCoordinate(std::size_t index) const; + + void remove(std::size_t index); + + std::unique_ptr getCoordinates() const; + + +private: + + // Members + const CoordinateSequence& m_coord; + bool m_isRing; + std::size_t m_size; + std::vector m_next; + std::vector m_prev; + + void createNextLinks(std::size_t size); + + void createPrevLinks(std::size_t size); + + +}; // LinkedLine + +GEOS_DLL std::ostream& operator<< (std::ostream& os, const LinkedLine& ll); + + +} // geos::simplify +} // geos diff --git a/Sources/geos/include/geos/simplify/LinkedRing.h b/Sources/geos/include/geos/simplify/LinkedRing.h index c7ebe9b..5df7eb9 100644 --- a/Sources/geos/include/geos/simplify/LinkedRing.h +++ b/Sources/geos/include/geos/simplify/LinkedRing.h @@ -17,20 +17,11 @@ #include #include - -namespace geos { -namespace geom { -class Coordinate; -class CoordinateArraySequence; -} -namespace triangulate { -namespace quadedge { -} -} -} +#include +#include using geos::geom::Coordinate; -using geos::geom::CoordinateArraySequence; +using geos::geom::CoordinateSequence; namespace geos { namespace simplify { // geos::simplify @@ -41,10 +32,7 @@ class LinkedRing { private: - static constexpr std::size_t - NO_COORD_INDEX = std::numeric_limits::max(); - - const std::vector& m_coord; + const CoordinateSequence& m_coord; std::size_t m_size; std::vector m_next; std::vector m_prev; @@ -55,7 +43,7 @@ class LinkedRing public: - LinkedRing(const std::vector& cs) + LinkedRing(const CoordinateSequence& cs) : m_coord(cs) , m_size(cs.size()-1) , m_next(createNextLinks(m_size)) @@ -70,7 +58,7 @@ class LinkedRing const Coordinate& nextCoordinate(std::size_t index) const; bool hasCoordinate(std::size_t index) const; void remove(std::size_t index); - std::unique_ptr getCoordinates() const; + std::unique_ptr getCoordinates() const; }; // LinkedRing diff --git a/Sources/geos/include/geos/simplify/RingHull.h b/Sources/geos/include/geos/simplify/RingHull.h index b674f87..fd8a044 100644 --- a/Sources/geos/include/geos/simplify/RingHull.h +++ b/Sources/geos/include/geos/simplify/RingHull.h @@ -97,26 +97,25 @@ class RingHull , area(p_area) {}; - /** - * Orders corners by increasing area - * Used in std::priority_queue which - * ordinarily returns largest element. - * We invert sense of <> here in order to get - * smallest element first. - */ - bool operator< (const Corner& rhs) const - { - return area > rhs.area; + inline int compareTo(const Corner& rhs) const { + if (area == rhs.getArea()) { + if (index == rhs.getIndex()) + return 0; + else return index < rhs.getIndex() ? -1 : 1; + } + else return area < rhs.getArea() ? -1 : 1; + } + + inline bool operator< (const Corner& rhs) const { + return compareTo(rhs) < 0; }; - bool operator> (const Corner& rhs) const - { - return area < rhs.area; + inline bool operator> (const Corner& rhs) const { + return compareTo(rhs) > 0; }; - bool operator==(const Corner& rhs) const - { - return area == rhs.area; + inline bool operator==(const Corner& rhs) const { + return compareTo(rhs) == 0; }; bool isVertex(std::size_t p_index) const; @@ -127,6 +126,14 @@ class RingHull bool isRemoved(const LinkedRing& ring) const; std::unique_ptr toLineString(const LinkedRing& ring); + struct Greater { + inline bool operator()(const Corner & a, const Corner & b) const { + return a > b; + } + }; + + using PriorityQueue = std::priority_queue, Corner::Greater>; + }; // class Corner @@ -140,7 +147,7 @@ class RingHull * Thus for convex interior angles * the vertices forming the angle are in CW orientation. */ - std::vector vertex; + std::unique_ptr vertex; std::unique_ptr vertexRing; double areaDelta = 0; @@ -151,11 +158,11 @@ class RingHull */ std::unique_ptr vertexIndex; - std::priority_queue cornerQueue; + Corner::PriorityQueue cornerQueue; - void init(std::vector& ring, bool isOuter); - void addCorner(std::size_t i, std::priority_queue& cornerQueue); + void init(CoordinateSequence& ring, bool isOuter); + void addCorner(std::size_t i, Corner::PriorityQueue& cornerQueue); bool isAtTarget(const Corner& corner); /** @@ -167,7 +174,7 @@ class RingHull * @param corner the corner to remove * @param cornerQueue the corner queue */ - void removeCorner(const Corner& corner, std::priority_queue& cornerQueue); + void removeCorner(const Corner& corner, Corner::PriorityQueue& cornerQueue); bool isRemovable(const Corner& corner, const RingHullIndex& hullIndex) const; /** diff --git a/Sources/geos/include/geos/simplify/TaggedLineSegment.h b/Sources/geos/include/geos/simplify/TaggedLineSegment.h index a6a5394..98b341c 100644 --- a/Sources/geos/include/geos/simplify/TaggedLineSegment.h +++ b/Sources/geos/include/geos/simplify/TaggedLineSegment.h @@ -20,7 +20,7 @@ * makes it useless for a TaggedLineSegment to store copies * of coordinates. Using pointers would be good enough here. * We don't do it to avoid having to break inheritance from - * LineSegment, which has copies intead. Wheter LineSegment + * LineSegment, which has copies instead. Whether LineSegment * itself should be refactored can be discussed. * --strk 2006-04-12 * diff --git a/Sources/geos/include/geos/simplify/TaggedLineString.h b/Sources/geos/include/geos/simplify/TaggedLineString.h index 70ffff8..b23f3a4 100644 --- a/Sources/geos/include/geos/simplify/TaggedLineString.h +++ b/Sources/geos/include/geos/simplify/TaggedLineString.h @@ -47,6 +47,9 @@ class TaggedLineSegment; } } +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; + namespace geos { namespace simplify { // geos::simplify @@ -58,27 +61,36 @@ class GEOS_DLL TaggedLineString { public: - typedef std::vector CoordVect; + typedef std::vector CoordVect; typedef std::unique_ptr CoordVectPtr; - typedef geom::CoordinateSequence CoordSeq; + typedef CoordinateSequence CoordSeq; - typedef std::unique_ptr CoordSeqPtr; + typedef std::unique_ptr CoordSeqPtr; TaggedLineString(const geom::LineString* nParentLine, - std::size_t minimumSize = 2); + std::size_t minimumSize, + bool bIsRing); ~TaggedLineString(); std::size_t getMinimumSize() const; + bool isRing() const; + const geom::LineString* getParent() const; const CoordSeq* getParentCoordinates() const; CoordSeqPtr getResultCoordinates() const; + const Coordinate& getCoordinate(std::size_t i) const; + + std::size_t size() const; + + const Coordinate& getComponentPoint() const; + std::size_t getResultSize() const; TaggedLineSegment* getSegment(std::size_t i); @@ -89,8 +101,12 @@ class GEOS_DLL TaggedLineString { const std::vector& getSegments() const; + const std::vector& getResultSegments() const; + void addToResult(std::unique_ptr seg); + const TaggedLineSegment* removeRingEndpoint(); + std::unique_ptr asLineString() const; std::unique_ptr asLinearRing() const; @@ -107,9 +123,11 @@ class GEOS_DLL TaggedLineString { std::size_t minimumSize; + bool m_isRing; + void init(); - static CoordVectPtr extractCoordinates( + static std::unique_ptr extractCoordinates( const std::vector& segs); // Copying is turned off diff --git a/Sources/geos/include/geos/simplify/TaggedLineStringSimplifier.h b/Sources/geos/include/geos/simplify/TaggedLineStringSimplifier.h index 79e9e12..3fba7e0 100644 --- a/Sources/geos/include/geos/simplify/TaggedLineStringSimplifier.h +++ b/Sources/geos/include/geos/simplify/TaggedLineStringSimplifier.h @@ -40,15 +40,22 @@ class LineIntersector; } namespace geom { class CoordinateSequence; +class Coordinate; class LineSegment; } namespace simplify { class TaggedLineSegment; class TaggedLineString; class LineSegmentIndex; +class ComponentJumpChecker; } } +using geos::geom::CoordinateSequence; +using geos::geom::Coordinate; +using geos::geom::LineSegment; + + namespace geos { namespace simplify { // geos::simplify @@ -64,25 +71,17 @@ class GEOS_DLL TaggedLineStringSimplifier { public: TaggedLineStringSimplifier(LineSegmentIndex* inputIndex, - LineSegmentIndex* outputIndex); - - /** \brief - * Sets the distance tolerance for the simplification. - * - * All vertices in the simplified geometry will be within this - * distance of the original geometry. - * - * @param d the approximation tolerance to use - */ - void setDistanceTolerance(double d); + LineSegmentIndex* outputIndex, + const ComponentJumpChecker* jumpChecker); /** * Simplifies the given {@link TaggedLineString} * using the distance tolerance specified. * * @param line the linestring to simplify + * @param distanceTolerance simplification tolerance */ - void simplify(TaggedLineString* line); + void simplify(TaggedLineString* line, double distanceTolerance); private: @@ -93,50 +92,68 @@ class GEOS_DLL TaggedLineStringSimplifier { // externally owned LineSegmentIndex* outputIndex; + const ComponentJumpChecker* jumpChecker; + std::unique_ptr li; /// non-const as segments are possibly added to it TaggedLineString* line; - const geom::CoordinateSequence* linePts; + const CoordinateSequence* linePts; - double distanceTolerance; + void simplifySection(std::size_t i, std::size_t j, std::size_t depth, double distanceTolerance); - void simplifySection(std::size_t i, std::size_t j, - std::size_t depth); + void simplifyRingEndpoint(double distanceTolerance); static std::size_t findFurthestPoint( - const geom::CoordinateSequence* pts, + const CoordinateSequence* pts, std::size_t i, std::size_t j, double& maxDistance); - bool hasBadIntersection(const TaggedLineString* parentLine, - const std::pair& sectionIndex, - const geom::LineSegment& candidateSeg); + bool isTopologyValid( + const TaggedLineString* lineIn, + std::size_t sectionStart, std::size_t sectionEnd, + const LineSegment& flatSeg); + + bool isTopologyValid( + const TaggedLineString* lineIn, + const LineSegment* seg1, const LineSegment* seg2, + const LineSegment& flatSeg); + + bool hasInputIntersection(const LineSegment& flatSeg); - bool hasBadInputIntersection(const TaggedLineString* parentLine, - const std::pair& sectionIndex, - const geom::LineSegment& candidateSeg); + bool hasInputIntersection( + const TaggedLineString* lineIn, + std::size_t excludeStart, std::size_t excludeEnd, + const LineSegment& flatSeg); - bool hasBadOutputIntersection(const geom::LineSegment& candidateSeg); + bool isCollinear(const Coordinate& pt, const LineSegment& seg) const; + + bool hasOutputIntersection(const LineSegment& flatSeg); + + bool hasInvalidIntersection( + const LineSegment& seg0, + const LineSegment& seg1) const; - bool hasInteriorIntersection(const geom::LineSegment& seg0, - const geom::LineSegment& seg1) const; std::unique_ptr flatten( std::size_t start, std::size_t end); /** \brief - * Tests whether a segment is in a section of a TaggedLineString + * Tests whether a segment is in a section of a TaggedLineString. + * Sections may wrap around the endpoint of the line, + * to support ring endpoint simplification. + * This is indicated by excludedStart > excludedEnd * - * @param parentLine - * @param sectionIndex - * @param seg - * @return + * @param line line to be checked for the presence of `seg` + * @param excludeStart the index of the first segment in the excluded section + * @param excludeEnd the index of the last segment in the excluded section + * @param seg segment to look for in `line` + * @return true if the test segment intersects some segment in the line not in the excluded section */ static bool isInLineSection( - const TaggedLineString* parentLine, - const std::pair& sectionIndex, + const TaggedLineString* line, + const std::size_t excludeStart, const std::size_t excludeEnd, const TaggedLineSegment* seg); /** \brief @@ -152,11 +169,6 @@ class GEOS_DLL TaggedLineStringSimplifier { }; -inline void -TaggedLineStringSimplifier::setDistanceTolerance(double d) -{ - distanceTolerance = d; -} } // namespace geos::simplify } // namespace geos diff --git a/Sources/geos/include/geos/simplify/TaggedLinesSimplifier.h b/Sources/geos/include/geos/simplify/TaggedLinesSimplifier.h index f1f1efd..12e8baa 100644 --- a/Sources/geos/include/geos/simplify/TaggedLinesSimplifier.h +++ b/Sources/geos/include/geos/simplify/TaggedLinesSimplifier.h @@ -68,44 +68,15 @@ class GEOS_DLL TaggedLinesSimplifier { */ void setDistanceTolerance(double tolerance); - /** \brief - * Simplify a set of {@link TaggedLineString}s - * - * @tparam iterator_type an iterator, must support assignment, increment, - * inequality and dereference operators. Dereference - * operator must return a `TaggedLineString*`. - * @param begin iterator to the first element to be simplified. - * @param end an iterator to one-past-last element to be simplified. - */ - template - void - simplify( - iterator_type begin, - iterator_type end) - { - // add lines to the index - for(iterator_type it = begin; it != end; ++it) { - assert(*it); - inputIndex->add(*(*it)); - } - - // Simplify lines - for(iterator_type it = begin; it != end; ++it) { - assert(*it); - simplify(*(*it)); - } - } - + void simplify(std::vector& tlsVector); private: - void simplify(TaggedLineString& line); - std::unique_ptr inputIndex; std::unique_ptr outputIndex; - std::unique_ptr taggedlineSimplifier; + double distanceTolerance; }; } // namespace geos::simplify diff --git a/Sources/geos/include/geos/triangulate/IncrementalDelaunayTriangulator.h b/Sources/geos/include/geos/triangulate/IncrementalDelaunayTriangulator.h index 95f14d2..0d22412 100644 --- a/Sources/geos/include/geos/triangulate/IncrementalDelaunayTriangulator.h +++ b/Sources/geos/include/geos/triangulate/IncrementalDelaunayTriangulator.h @@ -42,6 +42,7 @@ class GEOS_DLL IncrementalDelaunayTriangulator { private: quadedge::QuadEdgeSubdivision* subdiv; bool isUsingTolerance; + bool m_isForceConvex; public: /** @@ -55,6 +56,19 @@ class GEOS_DLL IncrementalDelaunayTriangulator { typedef std::vector VertexList; + /** + * Sets whether the triangulation is forced to have a convex boundary. Because + * of the use of a finite-size frame, this condition requires special logic to + * enforce. The default is true, since this is a requirement for some uses of + * Delaunay Triangulations (such as Concave Hull generation). However, forcing + * the triangulation boundary to be convex may cause the overall frame + * triangulation to be non-Delaunay. This can cause a problem for Voronoi + * generation, so the logic can be disabled via this method. + * + * @param isForceConvex true if the triangulation boundary is forced to be convex + */ + void forceConvex(bool isForceConvex); + /** * Inserts all sites in a collection. The inserted vertices MUST be * unique up to the provided tolerance value. (i.e. no two vertices should be @@ -77,6 +91,14 @@ class GEOS_DLL IncrementalDelaunayTriangulator { * @return a quadedge containing the inserted vertex */ quadedge::QuadEdge& insertSite(const quadedge::Vertex& v); + +private: + + bool isConcaveBoundary(const quadedge::QuadEdge& e) const; + + bool isConcaveAtOrigin(const quadedge::QuadEdge& e) const; + + bool isBetweenFrameAndInserted(const quadedge::QuadEdge& e, const quadedge::Vertex& vInsert) const; }; } //namespace geos.triangulate diff --git a/Sources/geos/include/geos/triangulate/VoronoiDiagramBuilder.h b/Sources/geos/include/geos/triangulate/VoronoiDiagramBuilder.h index bb1b1a0..ac6eb38 100644 --- a/Sources/geos/include/geos/triangulate/VoronoiDiagramBuilder.h +++ b/Sources/geos/include/geos/triangulate/VoronoiDiagramBuilder.h @@ -22,6 +22,7 @@ #include // for composition #include #include +#include namespace geos { namespace geom { @@ -68,6 +69,17 @@ class GEOS_DLL VoronoiDiagramBuilder { */ void setSites(const geom::CoordinateSequence& coords); + /** \brief + * Specify whether the geometries in the generated diagram should + * reflect the order of coordinates in the input. If the generated + * diagram cannot be consistent with the input coordinate order + * (e.g., for repeated input points that become a single cell) an + * exception will be thrown. + * + * @param isOrdered should the geometries reflect the input order? + */ + void setOrdered(bool isOrdered); + /** \brief * Sets the envelope to clip the diagram to. * @@ -98,7 +110,7 @@ class GEOS_DLL VoronoiDiagramBuilder { std::unique_ptr getSubdivision(); /** \brief - * Gets the faces of the computed diagram as a geom::GeometryCollection + * Gets the faces of the computed diagram as a {@link geom::GeometryCollection} * of {@link geom::Polygon}s, clipped as specified. * * @param geomFact the geometry factory to use to create the output @@ -107,29 +119,39 @@ class GEOS_DLL VoronoiDiagramBuilder { std::unique_ptr getDiagram(const geom::GeometryFactory& geomFact); /** \brief - * Gets the faces of the computed diagram as a geom::GeometryCollection - * of {@link geom::LineString}s, clipped as specified. + * Gets the edges of the computed diagram as a {@link geom::MultiLineString}, + * clipped as specified. * * @param geomFact the geometry factory to use to create the output - * @return the faces of the diagram + * @return the edges of the diagram */ - std::unique_ptr getDiagramEdges(const geom::GeometryFactory& geomFact); + std::unique_ptr getDiagramEdges(const geom::GeometryFactory& geomFact); + + void reorderCellsToInput(std::vector> & polys) const; private: + using CoordinateCellMap = std::unordered_map, geom::Coordinate::HashCode>; std::unique_ptr siteCoords; double tolerance; std::unique_ptr subdiv; const geom::Envelope* clipEnv; // externally owned + const geom::Geometry* inputGeom; + const geom::CoordinateSequence* inputSeq; geom::Envelope diagramEnv; + bool isOrdered; void create(); + std::size_t getNumInputPoints() const; + static std::unique_ptr clipGeometryCollection(std::vector> & geoms, const geom::Envelope& clipEnv); + + static void addCellsForCoordinates(CoordinateCellMap& cellMap, const geom::Geometry& g, std::vector> & polys); + static void addCellsForCoordinates(CoordinateCellMap& cellMap, const geom::CoordinateSequence& g, std::vector> & polys); }; } //namespace geos.triangulate } //namespace geos - diff --git a/Sources/geos/include/geos/triangulate/polygon/ConstrainedDelaunayTriangulator.h b/Sources/geos/include/geos/triangulate/polygon/ConstrainedDelaunayTriangulator.h index c8cc8ac..23c385d 100644 --- a/Sources/geos/include/geos/triangulate/polygon/ConstrainedDelaunayTriangulator.h +++ b/Sources/geos/include/geos/triangulate/polygon/ConstrainedDelaunayTriangulator.h @@ -55,7 +55,7 @@ class GEOS_DLL ConstrainedDelaunayTriangulator { const Geometry* inputGeom; const GeometryFactory* geomFact; - std::unique_ptr compute(); + std::unique_ptr compute() const; static std::unique_ptr toGeometry( const geom::GeometryFactory* geomFact, diff --git a/Sources/geos/include/geos/triangulate/polygon/PolygonEarClipper.h b/Sources/geos/include/geos/triangulate/polygon/PolygonEarClipper.h index 660d9da..5616216 100644 --- a/Sources/geos/include/geos/triangulate/polygon/PolygonEarClipper.h +++ b/Sources/geos/include/geos/triangulate/polygon/PolygonEarClipper.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -26,14 +27,12 @@ namespace geos { namespace geom { class Coordinate; -class CoordinateSequence; class Polygon; class Envelope; } } using geos::geom::Coordinate; -using geos::geom::CoordinateSequence; using geos::geom::Polygon; using geos::geom::Envelope; using geos::triangulate::tri::Tri; @@ -71,8 +70,6 @@ class GEOS_DLL PolygonEarClipper { // Members - static constexpr std::size_t NO_VERTEX_INDEX = std::numeric_limits::max(); - bool isFlatCornersSkipped = false; /** @@ -80,7 +77,7 @@ class GEOS_DLL PolygonEarClipper { * Thus for convex interior angles * the vertices forming the angle are in CW orientation. */ - std::vector vertex; + const CoordinateSequence& vertex; std::vector vertexNext; std::size_t vertexSize; @@ -112,7 +109,7 @@ class GEOS_DLL PolygonEarClipper { * * @param cornerIndex the index of the corner apex vertex * @param corner the corner vertices - * @return the index of an intersecting or duplicate vertex, or {@link #NO_VERTEX_INDEX} if none + * @return the index of an intersecting or duplicate vertex, or NO_COORD_INDEX if none */ std::size_t findIntersectingVertex(std::size_t cornerIndex, const std::array& corner) const; @@ -179,7 +176,7 @@ class GEOS_DLL PolygonEarClipper { * * @param polyShell the polygon vertices to process */ - PolygonEarClipper(const std::vector& polyShell); + PolygonEarClipper(const geom::CoordinateSequence& polyShell); /** * Triangulates a polygon via ear-clipping. @@ -187,8 +184,7 @@ class GEOS_DLL PolygonEarClipper { * @param polyShell the vertices of the polygon * @param triListResult vector to fill in with the resultant Tri s */ - static void triangulate(const std::vector& polyShell, TriList& triListResult); - static void triangulate(const CoordinateSequence& polyShell, TriList& triListResult); + static void triangulate(const geom::CoordinateSequence& polyShell, TriList& triListResult); /** * Sets whether flat corners formed by collinear adjacent line segments diff --git a/Sources/geos/include/geos/triangulate/polygon/PolygonHoleJoiner.h b/Sources/geos/include/geos/triangulate/polygon/PolygonHoleJoiner.h index 9e9ad0b..665b161 100644 --- a/Sources/geos/include/geos/triangulate/polygon/PolygonHoleJoiner.h +++ b/Sources/geos/include/geos/triangulate/polygon/PolygonHoleJoiner.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -71,8 +72,6 @@ class GEOS_DLL PolygonHoleJoiner { // Members - static constexpr std::size_t NO_INDEX = std::numeric_limits::max(); - const Polygon* inputPolygon; //-- normalized, sorted and noded polygon rings @@ -82,7 +81,7 @@ class GEOS_DLL PolygonHoleJoiner { //-- indicates whether a hole should be testing for touching std::vector isHoleTouchingHint; - std::vector joinedRing; + CoordinateSequence joinedRing; // a sorted and searchable version of the joinedRing std::set joinedPts; @@ -160,7 +159,7 @@ class GEOS_DLL PolygonHoleJoiner { * @return true if the line to the point is interior to the ring corner */ static bool isLineInterior( - const std::vector& ring, + const CoordinateSequence& ring, std::size_t ringIndex, const Coordinate& linePt); @@ -209,7 +208,7 @@ class GEOS_DLL PolygonHoleJoiner { const Polygon* poly); static std::size_t findLowestLeftVertexIndex( - const CoordinateSequence& coords); + const CoordinateSequence& holeCoords); /** * Tests whether the interior of a line segment intersects the polygon boundary. diff --git a/Sources/geos/include/geos/triangulate/quadedge/QuadEdgeSubdivision.h b/Sources/geos/include/geos/triangulate/quadedge/QuadEdgeSubdivision.h index edd2aa3..fd4a94f 100644 --- a/Sources/geos/include/geos/triangulate/quadedge/QuadEdgeSubdivision.h +++ b/Sources/geos/include/geos/triangulate/quadedge/QuadEdgeSubdivision.h @@ -50,11 +50,6 @@ namespace quadedge { //geos.triangulate.quadedge class TriangleVisitor; -const double EDGE_COINCIDENCE_TOL_FACTOR = 1000; - -//-- Frame size factor for initializing subdivision frame. Larger is more robust -const double FRAME_SIZE_FACTOR = 100; - /** \brief * A class that contains the [QuadEdges](@ref QuadEdge) representing a planar * subdivision that models a triangulation. @@ -115,7 +110,7 @@ class GEOS_DLL QuadEdgeSubdivision { * that encloses a supplied bounding box. * A new super-bounding box that contains the triangle is computed and stored. * - * @param env the bouding box to surround + * @param env the bounding box to surround * @param tolerance the tolerance value for determining if two sites are equal */ QuadEdgeSubdivision(const geom::Envelope& env, double tolerance); diff --git a/Sources/geos/include/geos/triangulate/quadedge/TrianglePredicate.h b/Sources/geos/include/geos/triangulate/quadedge/TrianglePredicate.h index f380159..c356928 100644 --- a/Sources/geos/include/geos/triangulate/quadedge/TrianglePredicate.h +++ b/Sources/geos/include/geos/triangulate/quadedge/TrianglePredicate.h @@ -19,15 +19,14 @@ #pragma once #include +#include namespace geos { namespace geom { -class Coordinate; +class CoordinateXY; } } -using geos::geom::Coordinate; - namespace geos { namespace triangulate { namespace quadedge { @@ -49,6 +48,8 @@ namespace quadedge { */ class GEOS_DLL TrianglePredicate { public: + using CoordinateXY = geos::geom::CoordinateXY; + /** * Tests if a point is inside the circle defined by * the triangle with vertices a, b, c (oriented counter-clockwise). @@ -61,9 +62,9 @@ class GEOS_DLL TrianglePredicate { * @param p the point to test * @return true if this point is inside the circle defined by the points a, b, c */ - static bool isInCircleNonRobust( - const Coordinate& a, const Coordinate& b, const Coordinate& c, - const Coordinate& p); + static geom::Location isInCircleNonRobust( + const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c, + const CoordinateXY& p); /** * Tests if a point is inside the circle defined by @@ -82,9 +83,9 @@ class GEOS_DLL TrianglePredicate { * @param p the point to test * @return true if this point is inside the circle defined by the points a, b, c */ - static bool isInCircleNormalized( - const Coordinate& a, const Coordinate& b, const Coordinate& c, - const Coordinate& p); + static geom::Location isInCircleNormalized( + const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c, + const CoordinateXY& p); private: /** @@ -95,8 +96,8 @@ class GEOS_DLL TrianglePredicate { * @param b a vertex of the triangle * @param c a vertex of the triangle */ - static double triArea(const Coordinate& a, - const Coordinate& b, const Coordinate& c); + static double triArea(const CoordinateXY& a, + const CoordinateXY& b, const CoordinateXY& c); public: /** @@ -110,9 +111,9 @@ class GEOS_DLL TrianglePredicate { * @param p the point to test * @return true if this point is inside the circle defined by the points a, b, c */ - static bool isInCircleRobust( - const Coordinate& a, const Coordinate& b, const Coordinate& c, - const Coordinate& p); + static geom::Location isInCircleRobust( + const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c, + const CoordinateXY& p); } ; diff --git a/Sources/geos/include/geos/triangulate/quadedge/Vertex.h b/Sources/geos/include/geos/triangulate/quadedge/Vertex.h index ce621ba..abb7bdc 100644 --- a/Sources/geos/include/geos/triangulate/quadedge/Vertex.h +++ b/Sources/geos/include/geos/triangulate/quadedge/Vertex.h @@ -205,7 +205,7 @@ class GEOS_DLL Vertex { * @return true if this vertex is in the circumcircle of (a,b,c) */ bool isInCircle(const Vertex& a, const Vertex& b, const Vertex& c) const { - return triangulate::quadedge::TrianglePredicate::isInCircleRobust(a.p, b.p, c.p, this->p); + return triangulate::quadedge::TrianglePredicate::isInCircleRobust(a.p, b.p, c.p, this->p) == geom::Location::INTERIOR; } /** diff --git a/Sources/geos/include/geos/util.h b/Sources/geos/include/geos/util.h index 4cbb5c7..fe0825b 100644 --- a/Sources/geos/include/geos/util.h +++ b/Sources/geos/include/geos/util.h @@ -24,6 +24,7 @@ #include #include #include +#include // // Private macros definition @@ -35,44 +36,7 @@ void ignore_unused_variable_warning(T const &) {} namespace detail { -#if __cplusplus >= 201402L using std::make_unique; -#else -// Backport of std::make_unique to C++11 -// Source: https://stackoverflow.com/a/19472607 -template -struct _Unique_if { - typedef std::unique_ptr _Single_object; -}; - -template -struct _Unique_if { - typedef std::unique_ptr _Unknown_bound; -}; - -template -struct _Unique_if { - typedef void _Known_bound; -}; - -template -typename _Unique_if::_Single_object -make_unique(Args &&... args) { - return std::unique_ptr(new T(std::forward(args)...)); -} - -template -typename _Unique_if::_Unknown_bound -make_unique(std::size_t n) { - typedef typename std::remove_extent::type U; - return std::unique_ptr(new U[n]()); -} - -template -typename _Unique_if::_Known_bound -make_unique(Args &&...) = delete; - -#endif /** Use detail::down_cast(pointer_to_base) as equivalent of * static_cast(pointer_to_base) with safe checking in debug @@ -95,18 +59,28 @@ template inline To down_cast(From* f) return static_cast(f); } -// Avoid "redundant move" warning when calling std::move() to return -// unique_ptr from a function with return type unique_ptr -// The std::move is required for the gcc 4.9 series, which has not addressed -// CWG defect 1579 ("return by converting move constructor") -// http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1579 -#if __GNUC__ > 0 && __GNUC__ < 5 -#define RETURN_UNIQUE_PTR(x) (std::move(x)) -#else -#define RETURN_UNIQUE_PTR(x) (x) -#endif - } // namespace detail + +namespace util { + +template +void ensureNoCurvedComponents(const T& geom) +{ + if (geom.hasCurvedComponents()) { + throw UnsupportedOperationException("Curved geometry types are not supported."); + } +} + +template +void ensureNoCurvedComponents(const T* geom) +{ + ensureNoCurvedComponents(*geom); +} + + +} + + } // namespace geos #endif // GEOS_UTIL_H diff --git a/Sources/geos/include/geos/util/Assert.h b/Sources/geos/include/geos/util/Assert.h index ee68a21..275de02 100644 --- a/Sources/geos/include/geos/util/Assert.h +++ b/Sources/geos/include/geos/util/Assert.h @@ -21,7 +21,7 @@ // Forward declarations namespace geos { namespace geom { -class Coordinate; +class CoordinateXY; } } @@ -40,13 +40,13 @@ class GEOS_DLL Assert { } - static void equals(const geom::Coordinate& expectedValue, - const geom::Coordinate& actualValue, + static void equals(const geom::CoordinateXY& expectedValue, + const geom::CoordinateXY& actualValue, const std::string& message); static void - equals(const geom::Coordinate& expectedValue, - const geom::Coordinate& actualValue) + equals(const geom::CoordinateXY& expectedValue, + const geom::CoordinateXY& actualValue) { equals(expectedValue, actualValue, std::string()); } diff --git a/Sources/geos/include/geos/util/CoordinateArrayFilter.h b/Sources/geos/include/geos/util/CoordinateArrayFilter.h index 9ef7e42..0bed5c4 100644 --- a/Sources/geos/include/geos/util/CoordinateArrayFilter.h +++ b/Sources/geos/include/geos/util/CoordinateArrayFilter.h @@ -17,46 +17,69 @@ #include #include +#include +#include #include #include #include +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class +#endif + namespace geos { namespace util { // geos::util -/** \brief - * A CoordinateFilter that adds read-only pointers - * to every Coordinate in a Geometry to a given - * vector. - * - * Last port: util/CoordinateArrayFilter.java rev. 1.15 +/* + * A CoordinateFilter that fills a vector of Coordinate const pointers. */ -class GEOS_DLL CoordinateArrayFilter: public geom::CoordinateFilter { -private: - geom::Coordinate::ConstVect& pts; // target vector reference +class GEOS_DLL CoordinateArrayFilter : public geom::CoordinateInspector { public: - /** \brief - * Constructs a CoordinateArrayFilter. - * - * @param target The destination vector. - */ - CoordinateArrayFilter(geom::Coordinate::ConstVect& target) - : - pts(target) + + CoordinateArrayFilter(std::vector& target) + : pts(target) {} - virtual - ~CoordinateArrayFilter() {} + /** + * Destructor. + * Virtual dctor promises appropriate behaviour when someone will + * delete a derived-class object via a base-class pointer. + * http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.7 + */ + ~CoordinateArrayFilter() override {} - virtual void - filter_ro(const geom::Coordinate* coord) + template + void filter(const CoordType* coord) { pts.push_back(coord); } + + void filter(const geom::CoordinateXY*) { + assert(0); // not supported + } + + void filter(const geom::CoordinateXYM*) { + assert(0); // not supported + } + +private: + std::vector& pts; // target set reference + + // Declare type as noncopyable + CoordinateArrayFilter(const CoordinateArrayFilter& other) = delete; + CoordinateArrayFilter& operator=(const CoordinateArrayFilter& rhs) = delete; }; -} // namespace geos.util + + + +} // namespace geos::util } // namespace geos +#ifdef _MSC_VER +#pragma warning(pop) +#endif + diff --git a/Sources/geos/include/geos/util/GeometricShapeFactory.h b/Sources/geos/include/geos/util/GeometricShapeFactory.h index 2cc5470..99146e1 100644 --- a/Sources/geos/include/geos/util/GeometricShapeFactory.h +++ b/Sources/geos/include/geos/util/GeometricShapeFactory.h @@ -68,12 +68,12 @@ class GEOS_DLL GeometricShapeFactory { class Dimensions { public: Dimensions(); - geom::Coordinate base; - geom::Coordinate centre; + geom::CoordinateXY base; + geom::CoordinateXY centre; double width; double height; - void setBase(const geom::Coordinate& newBase); - void setCentre(const geom::Coordinate& newCentre); + void setBase(const geom::CoordinateXY& newBase); + void setCentre(const geom::CoordinateXY& newCentre); void setSize(double size); void setWidth(double nWidth); void setHeight(double nHeight); @@ -86,7 +86,7 @@ class GEOS_DLL GeometricShapeFactory { Dimensions dim; uint32_t nPts; - geom::Coordinate coord(double x, double y) const; + geom::CoordinateXY coord(double x, double y) const; public: @@ -149,7 +149,7 @@ class GEOS_DLL GeometricShapeFactory { * * @param base the base coordinate of the shape */ - void setBase(const geom::Coordinate& base); + void setBase(const geom::CoordinateXY& base); /** * \brief @@ -158,7 +158,7 @@ class GEOS_DLL GeometricShapeFactory { * * @param centre the centre coordinate of the shape */ - void setCentre(const geom::Coordinate& centre); + void setCentre(const geom::CoordinateXY& centre); /** * \brief Sets the height of the shape. diff --git a/Sources/geos/include/geos/util/UniqueCoordinateArrayFilter.h b/Sources/geos/include/geos/util/UniqueCoordinateArrayFilter.h index 915d958..5b0afb0 100644 --- a/Sources/geos/include/geos/util/UniqueCoordinateArrayFilter.h +++ b/Sources/geos/include/geos/util/UniqueCoordinateArrayFilter.h @@ -38,15 +38,21 @@ namespace util { // geos::util * * Last port: util/UniqueCoordinateArrayFilter.java rev. 1.17 */ -class GEOS_DLL UniqueCoordinateArrayFilter: public geom::CoordinateFilter { +class GEOS_DLL UniqueCoordinateArrayFilter : public geom::CoordinateInspector { public: /** * Constructs a CoordinateArrayFilter. * * @param target The destination set. */ - UniqueCoordinateArrayFilter(geom::Coordinate::ConstVect& target) + UniqueCoordinateArrayFilter(std::vector& target) : pts(target) + , maxUnique(NO_COORD_INDEX) + {} + + UniqueCoordinateArrayFilter(std::vector& target, std::size_t p_maxUnique) + : pts(target) + , maxUnique(p_maxUnique) {} /** @@ -62,23 +68,43 @@ class GEOS_DLL UniqueCoordinateArrayFilter: public geom::CoordinateFilter { * @param coord The "read-only" Coordinate to which * the filter is applied. */ - void - filter_ro(const geom::Coordinate* coord) override + template + void filter(const CoordType* coord) { if(uniqPts.insert(coord).second) { + // TODO make `pts` a CoordinateSequence rather than coercing the type pts.push_back(coord); } + if(maxUnique != NO_COORD_INDEX && uniqPts.size() > maxUnique) { + done = true; + } + } + + void filter(const geom::CoordinateXY*) { + assert(0); // not supported + } + + + void filter(const geom::CoordinateXYM*) { + assert(0); // not supported + } + + bool isDone() const override { + return done; } private: - geom::Coordinate::ConstVect& pts; // target set reference - geom::Coordinate::ConstSet uniqPts; // unique points set + std::vector& pts; // target set reference + std::set uniqPts; // unique points set + std::size_t maxUnique; // stop visiting when we have this many unique coordinates + bool done = false; // Declare type as noncopyable UniqueCoordinateArrayFilter(const UniqueCoordinateArrayFilter& other) = delete; UniqueCoordinateArrayFilter& operator=(const UniqueCoordinateArrayFilter& rhs) = delete; }; + } // namespace geos::util } // namespace geos diff --git a/Sources/geos/src/geom/DefaultCoordinateSequenceFactory.cpp b/Sources/geos/include/geos/util/string.h similarity index 55% rename from Sources/geos/src/geom/DefaultCoordinateSequenceFactory.cpp rename to Sources/geos/include/geos/util/string.h index 8d958d5..374dfa0 100644 --- a/Sources/geos/src/geom/DefaultCoordinateSequenceFactory.cpp +++ b/Sources/geos/include/geos/util/string.h @@ -3,7 +3,7 @@ * GEOS - Geometry Engine Open Source * http://geos.osgeo.org * - * Copyright (C) 2019 Daniel Baston + * Copyright (C) 2022 ISciences LLC * * This is free software; you can redistribute and/or modify it under * the terms of the GNU Lesser General Public Licence as published @@ -12,19 +12,20 @@ * **********************************************************************/ -#include +#pragma once + +#include namespace geos { -namespace geom { // geos::geom +namespace util { -static DefaultCoordinateSequenceFactory defaultCoordinateSequenceFactory; +bool endsWith(const std::string & s, const std::string & suffix); +bool endsWith(const std::string & s, char suffix); -const CoordinateSequenceFactory* -DefaultCoordinateSequenceFactory::instance() -{ - return &defaultCoordinateSequenceFactory; -} +bool startsWith(const std::string & s, const std::string & prefix); +bool startsWith(const std::string & s, char prefix); -} // namespace geos::geom -} // namespace geos +void toUpper(std::string& s); +} +} diff --git a/Sources/geos/include/geos/vend/json.hpp b/Sources/geos/include/geos/vend/json.hpp index 492118a..c81c5c5 100644 --- a/Sources/geos/include/geos/vend/json.hpp +++ b/Sources/geos/include/geos/vend/json.hpp @@ -5255,7 +5255,7 @@ auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) } // This class only handles inputs of input_buffer_adapter type. -// It's required so that expressions like {ptr, len} can be implicitely casted +// It's required so that expressions like {ptr, len} can be implicitly casted // to the correct adapter. class span_input_adapter { @@ -10030,7 +10030,7 @@ class binary_reader @return whether conversion completed - @note This function needs to respect the system's endianess, because + @note This function needs to respect the system's endianness, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. */ @@ -10202,7 +10202,7 @@ class binary_reader /// the number of characters read std::size_t chars_read = 0; - /// whether we can assume little endianess + /// whether we can assume little endianness const bool is_little_endian = little_endianess(); /// the SAX parser @@ -14345,7 +14345,7 @@ class binary_writer @tparam OutputIsLittleEndian Set to true if output data is required to be little endian - @note This function needs to respect the system's endianess, because bytes + @note This function needs to respect the system's endianness, because bytes in CBOR, MessagePack, and UBJSON are stored in network order (big endian) and therefore need reordering on little endian systems. */ @@ -14428,7 +14428,7 @@ class binary_writer } private: - /// whether we can assume little endianess + /// whether we can assume little endianness const bool is_little_endian = little_endianess(); /// the output @@ -17528,7 +17528,7 @@ class basic_json - If a subtype is given and the binary array contains exactly 1, 2, 4, 8, or 16 elements, the fixext family (fixext1, fixext2, fixext4, fixext8) is used. For other sizes, the ext family (ext8, ext16, ext32) is used. - The subtype is then added as singed 8-bit integer. + The subtype is then added as signed 8-bit integer. - If no subtype is given, the bin family (bin8, bin16, bin32) is used. - BSON - If a subtype is given, it is used and added as unsigned 8-bit integer. @@ -21482,7 +21482,7 @@ class basic_json `key()` returns an empty string. @warning Using `items()` on temporary objects is dangerous. Make sure the - object's lifetime exeeds the iteration. See + object's lifetime exceeds the iteration. See for more information. diff --git a/Sources/geos/include/geos/version.h b/Sources/geos/include/geos/version.h index ce27c71..af54917 100644 --- a/Sources/geos/include/geos/version.h +++ b/Sources/geos/include/geos/version.h @@ -19,15 +19,15 @@ #endif #ifndef GEOS_VERSION_MINOR -#define GEOS_VERSION_MINOR 11 +#define GEOS_VERSION_MINOR 13 #endif #ifndef GEOS_VERSION_PATCH -#define GEOS_VERSION_PATCH 2 +#define GEOS_VERSION_PATCH 0 #endif #ifndef GEOS_VERSION -#define GEOS_VERSION "3.11.2" +#define GEOS_VERSION "3.13.0" #endif #ifndef GEOS_JTS_PORT diff --git a/Sources/geos/public/geos/export.h b/Sources/geos/public/geos/export.h index 1a26f8c..d1baff3 100644 --- a/Sources/geos/public/geos/export.h +++ b/Sources/geos/public/geos/export.h @@ -19,7 +19,7 @@ # if defined(GEOS_DLL_EXPORT) # define GEOS_DLL __declspec(dllexport) # elif defined(GEOS_DLL_IMPORT) -# define GEOS_DLL __declspec(dllimport) +# define GEOS_DLL extern __declspec(dllimport) # else # define GEOS_DLL # endif @@ -31,3 +31,21 @@ #if defined(_MSC_VER) # pragma warning(disable: 4251) // identifier : class type needs to have dll-interface to be used by clients of class type2 #endif + + +/********************************************************************** + * Portability macros + **********************************************************************/ + +#ifdef _MSC_VER +#include +#define GEOS_PRINTF_FORMAT _Printf_format_string_ +#define GEOS_PRINTF_FORMAT_ATTR(format_param, dots_param) /**/ +#elif __GNUC__ +#define GEOS_PRINTF_FORMAT /**/ +#define GEOS_PRINTF_FORMAT_ATTR(format_param, dots_param) \ + __attribute__((format(printf, format_param, dots_param))) +#else +#define GEOS_PRINTF_FORMAT /**/ +#define GEOS_PRINTF_FORMAT_ATTR(format_param, dots_param) /**/ +#endif diff --git a/Sources/geos/public/geos_c.h b/Sources/geos/public/geos_c.h index af9629b..f3d1433 100644 --- a/Sources/geos/public/geos_c.h +++ b/Sources/geos/public/geos_c.h @@ -16,24 +16,24 @@ * \brief C API for the GEOS geometry algorithms library. * \tableofcontents * -* The C API is the preferred API to use when integration GEOS into -* you program/language/etc. While the C++ API is available, the ABI -* will not be stable between versions, and the API may also change. -* The GEOS team makes an effort to keep the C API stable, and to -* deprecate function signatures only over a long time period to allow -* transition time. +* The C API is the supported API for using GEOS in +* your application/library/etc. +* The C API is kept stable, and deprecated function signatures +* are kept available over a long time period to allow transition time. +* While the C++ API is available, it may change at any time, and the ABI +* may not be stable between versions. * * Important programming notes: * -* - Remember to call initGEOS() before any use of this library's -* functions, and call finishGEOS() when done. -* - Currently you have to explicitly GEOSGeom_destroy() all -* GEOSGeom objects to avoid memory leaks, and GEOSFree() -* all returned char * (unless const). -* - Functions ending with _r are thread safe (reentrant); +* - Call initGEOS() before using the library functions. +* - Call finishGEOS() when finished using the library. +* - To avoid memory leaks, call GEOSGeom_destroy() on +* \ref GEOSGeometry objects, and call GEOSFree() on +* returned `char *` strings and byte buffers (unless marked `const`). +* - Functions ending with `_r` are thread safe (reentrant); * see details in https://libgeos.org/development/rfcs/rfc03. * To avoid accidental use of non-reentrant functions, -* define GEOS_USE_ONLY_R_API before including geos_c.h. +* define `GEOS_USE_ONLY_R_API` before including geos_c.h. * */ @@ -61,22 +61,22 @@ extern "C" { #define GEOS_VERSION_MAJOR 3 #endif #ifndef GEOS_VERSION_MINOR -#define GEOS_VERSION_MINOR 11 +#define GEOS_VERSION_MINOR 13 #endif #ifndef GEOS_VERSION_PATCH -#define GEOS_VERSION_PATCH 2 +#define GEOS_VERSION_PATCH 0 #endif #ifndef GEOS_VERSION -#define GEOS_VERSION "3.11.2" +#define GEOS_VERSION "3.13.0" #endif #ifndef GEOS_JTS_PORT #define GEOS_JTS_PORT "1.18.0" #endif #define GEOS_CAPI_VERSION_MAJOR 1 -#define GEOS_CAPI_VERSION_MINOR 17 -#define GEOS_CAPI_VERSION_PATCH 2 -#define GEOS_CAPI_VERSION "3.11.2-CAPI-1.17.2" +#define GEOS_CAPI_VERSION_MINOR 19 +#define GEOS_CAPI_VERSION_PATCH 0 +#define GEOS_CAPI_VERSION "3.13.0-CAPI-1.19.0" #define GEOS_CAPI_FIRST_INTERFACE GEOS_CAPI_VERSION_MAJOR #define GEOS_CAPI_LAST_INTERFACE (GEOS_CAPI_VERSION_MAJOR+GEOS_CAPI_VERSION_MINOR) @@ -87,23 +87,23 @@ extern "C" { /** -* Type returned by GEOS_init_r(), for use in multi-threaded -* applications. +* Type returned by GEOS_init_r(), for use with the functions ending in `_r` +* (the reentrant API). * -* There should be only one GEOSContextHandle_t per thread. +* Contexts must only be used from a single thread at a time. */ typedef struct GEOSContextHandle_HS *GEOSContextHandle_t; /** * Callback function for passing GEOS error messages to parent process. * -* Set the GEOSMessageHandler for error and notice messages in \ref initGEOS -* for single-threaded programs, or using \ref initGEOS_r for threaded -* programs +* Set the \ref GEOSMessageHandler function for error and notice messages using +* \ref initGEOS or \ref initGEOS_r. * * \param fmt the message format template */ -typedef void (*GEOSMessageHandler)(const char *fmt, ...); +typedef void (*GEOSMessageHandler)(GEOS_PRINTF_FORMAT const char *fmt, ...) + GEOS_PRINTF_FORMAT_ATTR(1, 2); /** * A GEOS message handler function. @@ -127,10 +127,10 @@ typedef void (*GEOSMessageHandler_r)(const char *message, void *userdata); #ifndef GEOSGeometry /** -* Geometry generic type. Geometry can be a point, linestring, polygon, -* multipoint, multilinestring, multipolygon, or geometrycollection. -* Geometry type can be read with \ref GEOSGeomTypeId. Most functions -* in GEOS either have GEOSGeometry* as a parameter or a return type. +* The generic type for a geometry. A geometry can be a Point, LineString, Polygon, +* MultiPoint, MultiLineString, MultiPolygon, or GeometryCollection. +* The geometry type can be read with \ref GEOSGeomTypeId. Most functions +* in GEOS have `GEOSGeometry *` as either a parameter or a return type. * \see GEOSGeom_createPoint * \see GEOSGeom_createLineString * \see GEOSGeom_createPolygon @@ -147,7 +147,8 @@ typedef struct GEOSGeom_t GEOSGeometry; typedef struct GEOSPrepGeom_t GEOSPreparedGeometry; /** -* Coordinate sequence. +* Coordinate sequence type representing fixed-size lists of coordinates. +* Contains the list of vertices defining the location of a \ref GEOSGeometry. * \see GEOSCoordSeq_create() * \see GEOSCoordSeq_destroy() */ @@ -210,7 +211,12 @@ enum GEOSGeomTypes { /** Multipolygon, a homogeneous collection of polygons */ GEOS_MULTIPOLYGON, /** Geometry collection, a heterogeneous collection of geometry */ - GEOS_GEOMETRYCOLLECTION + GEOS_GEOMETRYCOLLECTION, + GEOS_CIRCULARSTRING, + GEOS_COMPOUNDCURVE, + GEOS_CURVEPOLYGON, + GEOS_MULTICURVE, + GEOS_MULTISURFACE, }; /** @@ -263,7 +269,7 @@ typedef void (*GEOSQueryCallback)(void *item, void *userdata); * \param distance the distance between the items here * \param userdata extra data for the calculation * -* \return zero if distance calculation succeeded, non-zero otherwise +* \return 1 if distance calculation succeeds, 0 otherwise * * \see GEOSSTRtree_nearest_generic * \see GEOSSTRtree_iterate @@ -310,26 +316,32 @@ typedef void (GEOSInterruptCallback)(void); * \param cb Callback function to invoke * \return the previously configured callback * \see GEOSInterruptCallback +* \since 3.4 */ extern GEOSInterruptCallback GEOS_DLL *GEOS_interruptRegisterCallback( GEOSInterruptCallback* cb); /** * Request safe interruption of operations +* \since 3.4 */ extern void GEOS_DLL GEOS_interruptRequest(void); /** * Cancel a pending interruption request +* \since 3.4 */ extern void GEOS_DLL GEOS_interruptCancel(void); /* ========== Initialization and Cleanup ========== */ /** -* Initialize a context for this thread. Pass this context into -* your other calls of `*_r` functions. -* \return a GEOS context for this thread +* Allocate and initialize a context. Pass this context as the first argument +* when calling other `*_r` functions. Contexts must only be used from a single +* thread at a time. +* \return a new GEOS context. +* +* \since 3.5 */ extern GEOSContextHandle_t GEOS_DLL GEOS_init_r(void); @@ -337,6 +349,8 @@ extern GEOSContextHandle_t GEOS_DLL GEOS_init_r(void); * Free the memory associated with a \ref GEOSContextHandle_t * when you are finished calling GEOS functions. * \param handle to be freed +* +* \since 3.5 */ extern void GEOS_DLL GEOS_finish_r(GEOSContextHandle_t handle); @@ -345,6 +359,8 @@ extern void GEOS_DLL GEOS_finish_r(GEOSContextHandle_t handle); * \param extHandle the context returned by \ref GEOS_init_r. * \param nf the handler callback * \return the previously configured message handler or NULL if no message handler was configured +* +* \since 3.3 */ extern GEOSMessageHandler GEOS_DLL GEOSContext_setNoticeHandler_r( GEOSContextHandle_t extHandle, @@ -357,6 +373,8 @@ extern GEOSMessageHandler GEOS_DLL GEOSContext_setNoticeHandler_r( * \param extHandle the GEOS context from \ref GEOS_init_r * \param ef the handler callback * \return the previously configured message handler or NULL if no message handler was configured +* +* \since 3.3 */ extern GEOSMessageHandler GEOS_DLL GEOSContext_setErrorHandler_r( GEOSContextHandle_t extHandle, @@ -368,6 +386,8 @@ extern GEOSMessageHandler GEOS_DLL GEOSContext_setErrorHandler_r( * \param nf the message handler * \param userData optional user data pointer that will be passed to the message handler * \return the previously configured message handler or NULL if no message handler was configured +* +* \since 3.5 */ extern GEOSMessageHandler_r GEOS_DLL GEOSContext_setNoticeMessageHandler_r( GEOSContextHandle_t extHandle, @@ -382,6 +402,8 @@ extern GEOSMessageHandler_r GEOS_DLL GEOSContext_setNoticeMessageHandler_r( * \param userData optional user data pointer that will be passed to the message handler * * \return the previously configured message handler or NULL if no message handler was configured +* +* \since 3.5 */ extern GEOSMessageHandler_r GEOS_DLL GEOSContext_setErrorMessageHandler_r( GEOSContextHandle_t extHandle, @@ -720,6 +742,12 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_createCollection_r( GEOSGeometry* *geoms, unsigned int ngeoms); +/** \see GEOSGeom_releaseCollection */ +extern GEOSGeometry GEOS_DLL ** GEOSGeom_releaseCollection_r( + GEOSContextHandle_t handle, + GEOSGeometry * collection, + unsigned int * ngeoms); + /** \see GEOSGeom_createEmptyCollection */ extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyCollection_r( GEOSContextHandle_t handle, int type); @@ -735,6 +763,36 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_clone_r( GEOSContextHandle_t handle, const GEOSGeometry* g); +/** \see GEOSGeom_createCircularString */ +extern GEOSGeometry GEOS_DLL *GEOSGeom_createCircularString_r( + GEOSContextHandle_t handle, + GEOSCoordSequence* s); + +/** \see GEOSGeom_createEmptyCircularString */ +extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyCircularString_r( + GEOSContextHandle_t handle); + +/** \see GEOSGeom_createCompoundCurve */ +extern GEOSGeometry GEOS_DLL *GEOSGeom_createCompoundCurve_r( + GEOSContextHandle_t handle, + GEOSGeometry** curves, + unsigned int ncurves); + +/** \see GEOSGeom_createEmptyCompoundCurve */ +extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyCompoundCurve_r( + GEOSContextHandle_t handle); + +/** \see GEOSGeom_createCurvePolygon */ +extern GEOSGeometry GEOS_DLL *GEOSGeom_createCurvePolygon_r( + GEOSContextHandle_t handle, + GEOSGeometry* shell, + GEOSGeometry** holes, + unsigned int nholes); + +/** \see GEOSGeom_createEmptyCurvePolygon */ +extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyCurvePolygon_r( + GEOSContextHandle_t handle); + /* ========= Memory management ========= */ /** \see GEOSGeom_destroy */ @@ -742,6 +800,30 @@ extern void GEOS_DLL GEOSGeom_destroy_r( GEOSContextHandle_t handle, GEOSGeometry* g); +/* ========= Coverages ========= */ + +/** \see GEOSCoverageUnion */ +extern GEOSGeometry GEOS_DLL * +GEOSCoverageUnion_r( + GEOSContextHandle_t handle, + const GEOSGeometry* g); + +/** \see GEOSCoverageIsValid */ +extern int GEOS_DLL +GEOSCoverageIsValid_r( + GEOSContextHandle_t extHandle, + const GEOSGeometry* input, + double gapWidth, + GEOSGeometry** output); + +/** \see GEOSCoverageSimplifyVW */ +extern GEOSGeometry GEOS_DLL * +GEOSCoverageSimplifyVW_r( + GEOSContextHandle_t extHandle, + const GEOSGeometry* input, + double tolerance, + int preserveBoundary); + /* ========= Topology Operations ========= */ /** \see GEOSEnvelope */ @@ -774,6 +856,13 @@ extern GEOSGeometry GEOS_DLL *GEOSConcaveHull_r( double ratio, unsigned int allowHoles); +/** \see GEOSConcaveHullByLength */ +extern GEOSGeometry GEOS_DLL *GEOSConcaveHullByLength_r( + GEOSContextHandle_t handle, + const GEOSGeometry* g, + double ratio, + unsigned int allowHoles); + /** \see GEOSPolygonHullSimplify */ extern GEOSGeometry GEOS_DLL *GEOSPolygonHullSimplify_r( GEOSContextHandle_t handle, @@ -781,7 +870,7 @@ extern GEOSGeometry GEOS_DLL *GEOSPolygonHullSimplify_r( unsigned int isOuter, double vertexNumFraction); -/** \see GEOSPolygonHullSimplifyByArea */ +/** \see GEOSPolygonHullSimplifyMode */ extern GEOSGeometry GEOS_DLL *GEOSPolygonHullSimplifyMode_r( GEOSContextHandle_t handle, const GEOSGeometry* g, @@ -886,8 +975,8 @@ extern GEOSGeometry GEOS_DLL *GEOSUnaryUnionPrec_r( const GEOSGeometry* g, double gridSize); -/** \see GEOSCoverageUnion */ -extern GEOSGeometry GEOS_DLL *GEOSCoverageUnion_r( +/** \see GEOSDisjointSubsetUnion */ +extern GEOSGeometry GEOS_DLL *GEOSDisjointSubsetUnion_r( GEOSContextHandle_t handle, const GEOSGeometry* g); @@ -961,6 +1050,13 @@ extern GEOSGeometry GEOS_DLL *GEOSLineMergeDirected_r( GEOSContextHandle_t handle, const GEOSGeometry* g); +/** \see GEOSLineSubstring */ +extern GEOSGeometry GEOS_DLL *GEOSLineSubstring_r( + GEOSContextHandle_t handle, + const GEOSGeometry* g, + double start_fraction, + double end_fdraction); + /** \see GEOSReverse */ extern GEOSGeometry GEOS_DLL *GEOSReverse_r( GEOSContextHandle_t handle, @@ -1013,7 +1109,7 @@ extern GEOSGeometry GEOS_DLL * GEOSVoronoiDiagram_r( const GEOSGeometry *g, const GEOSGeometry *env, double tolerance, - int onlyEdges); + int flags); /** \see GEOSSegmentIntersection */ extern int GEOS_DLL GEOSSegmentIntersection_r( @@ -1081,6 +1177,12 @@ extern char GEOS_DLL GEOSEqualsExact_r( const GEOSGeometry* g2, double tolerance); +/** \see GEOSEqualsIdentical */ +extern char GEOS_DLL GEOSEqualsIdentical_r( + GEOSContextHandle_t handle, + const GEOSGeometry* g1, + const GEOSGeometry* g2); + /** \see GEOSCovers */ extern char GEOS_DLL GEOSCovers_r( GEOSContextHandle_t handle, @@ -1111,6 +1213,13 @@ extern char GEOS_DLL GEOSPreparedContains_r( const GEOSPreparedGeometry* pg1, const GEOSGeometry* g2); +/** \see GEOSPreparedContainsXY */ +extern char GEOS_DLL GEOSPreparedContainsXY_r( + GEOSContextHandle_t handle, + const GEOSPreparedGeometry* pg1, + double x, + double y); + /** \see GEOSPreparedContainsProperly */ extern char GEOS_DLL GEOSPreparedContainsProperly_r( GEOSContextHandle_t handle, @@ -1147,6 +1256,13 @@ extern char GEOS_DLL GEOSPreparedIntersects_r( const GEOSPreparedGeometry* pg1, const GEOSGeometry* g2); +/** \see GEOSPreparedIntersectsXY */ +extern char GEOS_DLL GEOSPreparedIntersectsXY_r( + GEOSContextHandle_t handle, + const GEOSPreparedGeometry* pg1, + double x, + double y); + /** \see GEOSPreparedOverlaps */ extern char GEOS_DLL GEOSPreparedOverlaps_r( GEOSContextHandle_t handle, @@ -1165,6 +1281,19 @@ extern char GEOS_DLL GEOSPreparedWithin_r( const GEOSPreparedGeometry* pg1, const GEOSGeometry* g2); +/** \see GEOSPreparedRelate */ +extern char GEOS_DLL * GEOSPreparedRelate_r( + GEOSContextHandle_t handle, + const GEOSPreparedGeometry* pg1, + const GEOSGeometry* g2); + +/** \see GEOSPreparedRelatePattern */ +extern char GEOS_DLL GEOSPreparedRelatePattern_r( + GEOSContextHandle_t handle, + const GEOSPreparedGeometry* pg1, + const GEOSGeometry* g2, + const char* im); + /** \see GEOSPreparedNearestPoints */ extern GEOSCoordSequence GEOS_DLL *GEOSPreparedNearestPoints_r( GEOSContextHandle_t handle, @@ -1190,6 +1319,11 @@ extern GEOSSTRtree GEOS_DLL *GEOSSTRtree_create_r( GEOSContextHandle_t handle, size_t nodeCapacity); +/** \see GEOSSTRtree_build */ +extern int GEOS_DLL GEOSSTRtree_build_r( + GEOSContextHandle_t handle, + GEOSSTRtree *tree); + /** \see GEOSSTRtree_insert */ extern void GEOS_DLL GEOSSTRtree_insert_r( GEOSContextHandle_t handle, @@ -1262,6 +1396,11 @@ extern char GEOS_DLL GEOSHasZ_r( GEOSContextHandle_t handle, const GEOSGeometry* g); +/** \see GEOSHasM */ +extern char GEOS_DLL GEOSHasM_r( + GEOSContextHandle_t handle, + const GEOSGeometry* g); + /** \see GEOSisClosed */ extern char GEOS_DLL GEOSisClosed_r( GEOSContextHandle_t handle, @@ -1291,7 +1430,7 @@ extern char GEOS_DLL GEOSRelatePattern_r( GEOSContextHandle_t handle, const GEOSGeometry* g1, const GEOSGeometry* g2, - const char *pat); + const char *imPattern); /** \see GEOSRelate */ extern char GEOS_DLL *GEOSRelate_r( @@ -1302,8 +1441,8 @@ extern char GEOS_DLL *GEOSRelate_r( /** \see GEOSRelatePatternMatch */ extern char GEOS_DLL GEOSRelatePatternMatch_r( GEOSContextHandle_t handle, - const char *mat, - const char *pat); + const char *intMatrix, + const char *imPattern); /** \see GEOSRelateBoundaryNodeRule */ extern char GEOS_DLL *GEOSRelateBoundaryNodeRule_r( @@ -1445,6 +1584,12 @@ extern int GEOS_DLL GEOSNormalize_r( GEOSContextHandle_t handle, GEOSGeometry* g); +/** \see GEOSOrientPolygons */ +extern int GEOS_DLL GEOSOrientPolygons_r( + GEOSContextHandle_t handle, + GEOSGeometry* g, + int exteriorCW); + /** * Controls the behavior of GEOSGeom_setPrecision() * when altering the precision of a geometry. @@ -1498,6 +1643,12 @@ extern int GEOS_DLL GEOSGeomGetZ_r( const GEOSGeometry *g, double *z); +/** \see GEOSGeomGetM */ +extern int GEOS_DLL GEOSGeomGetM_r( + GEOSContextHandle_t handle, + const GEOSGeometry *g, + double *m); + /** \see GEOSGetInteriorRingN */ extern const GEOSGeometry GEOS_DLL *GEOSGetInteriorRingN_r( GEOSContextHandle_t handle, @@ -1796,6 +1947,21 @@ extern void GEOS_DLL GEOSWKTWriter_setOld3D_r( GEOSWKTWriter *writer, int useOld3D); +/** Print the shortest representation of a double. Non-zero absolute values + * that are <1e-4 and >=1e+17 are formatted using scientific notation, and + * other values are formatted with positional notation with precision used for + * the max digits after decimal point. + * \param d The number to format. + * \param precision The desired precision. + * \param result The buffer to write the result to, with a suggested size 28. + * \return the length of the written string. + */ +extern int GEOS_DLL GEOS_printDouble( + double d, + unsigned int precision, + char *result +); + /* ========== WKB Reader ========== */ /** \see GEOSWKBReader_create */ @@ -1938,6 +2104,7 @@ extern void GEOS_DLL GEOSFree_r( * This function does not have a reentrant variant and is * available if `GEOS_USE_ONLY_R_API` is defined. * \return version string +* \since 2.2 */ extern const char GEOS_DLL *GEOSversion(void); @@ -1965,6 +2132,7 @@ extern const char GEOS_DLL *GEOSversion(void); * * \param notice_function Handle notice messages * \param error_function Handle error messages +* \since 2.2 */ extern void GEOS_DLL initGEOS( GEOSMessageHandler notice_function, @@ -1973,6 +2141,7 @@ extern void GEOS_DLL initGEOS( /** * For non-reentrant code, call when all GEOS operations are complete, * cleans up global resources. +* \since 3.1 */ extern void GEOS_DLL finishGEOS(void); @@ -1980,7 +2149,10 @@ extern void GEOS_DLL finishGEOS(void); * Free strings and byte buffers returned by functions such * as GEOSWKBWriter_write(), * GEOSWKBWriter_writeHEX() and GEOSWKTWriter_write(), etc. -* \param buffer The memory to free +* If passed a null pointer the function does nothing. + +* \param buffer The memory to free (may be null) +* \since 3.1 */ extern void GEOS_DLL GEOSFree(void *buffer); @@ -1989,25 +2161,28 @@ extern void GEOS_DLL GEOSFree(void *buffer); /* ========= Coordinate Sequence functions ========= */ /** @name Coordinate Sequences * A GEOSCoordSequence is an ordered list of coordinates. -* Coordinates are 2 (XY) or 3 (XYZ) dimensional. +* Coordinates are 2 (XY), 3 (XYZ or XYM), or 4 (XYZM) dimensional. */ ///@{ /** * Create a coordinate sequence. * \param size number of coordinates in the sequence -* \param dims dimensionality of the coordinates (2 or 3) +* \param dims dimensionality of the coordinates (2, 3 or 4) * \return the sequence or NULL on exception +* \since 2.2 */ extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_create(unsigned int size, unsigned int dims); /** -* Create a coordinate sequence by copying from a buffer of doubles (XYXY or XYZXYZ) +* Create a coordinate sequence by copying from an interleaved buffer of doubles (e.g., XYXY or XYZXYZ) * \param buf pointer to buffer * \param size number of coordinates in the sequence * \param hasZ does buffer have Z values? -* \param hasM does buffer have M values? (they will be ignored) +* \param hasM does buffer have M values? * \return the sequence or NULL on exception +* +* \since 3.10 */ extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_copyFromBuffer(const double* buf, unsigned int size, int hasZ, int hasM); @@ -2016,30 +2191,36 @@ extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_copyFromBuffer(const double* buf * \param x array of x coordinates * \param y array of y coordinates * \param z array of z coordinates, or NULL -* \param m array of m coordinates, (must be NULL) +* \param m array of m coordinates, or NULL * \param size length of each array * \return the sequence or NULL on exception +* +* \since 3.10 */ extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_copyFromArrays(const double* x, const double* y, const double* z, const double* m, unsigned int size); /** -* Copy the contents of a coordinate sequence to a buffer of doubles (XYXY or XYZXYZ) +* Copy the contents of a coordinate sequence to an interleaved buffer of doubles (e.g., XYXY or XYZXYZ) * \param s sequence to copy * \param buf buffer to which coordinates should be copied * \param hasZ copy Z values to buffer? -* \param hasM copy M values to buffer? (will be NaN) +* \param hasM copy M values to buffer? * \return 1 on success, 0 on error +* +* \since 3.10 */ extern int GEOS_DLL GEOSCoordSeq_copyToBuffer(const GEOSCoordSequence* s, double* buf, int hasZ, int hasM); /** -* Copy the contents of a coordinate sequence to a buffer of doubles (XYZY or XYZXYZ) +* Copy the contents of a coordinate sequence to arrays of doubles * \param s sequence to copy * \param x array to which x values should be copied * \param y array to which y values should be copied * \param z array to which z values should be copied, or NULL -* \param m array to which m values should be copied (will all be NAN) +* \param m array to which m values should be copied, or NULL * \return 1 on success, 0 on error +* +* \since 3.10 */ extern int GEOS_DLL GEOSCoordSeq_copyToArrays(const GEOSCoordSequence* s, double* x, double* y, double* z, double* m); @@ -2047,12 +2228,14 @@ extern int GEOS_DLL GEOSCoordSeq_copyToArrays(const GEOSCoordSequence* s, double * Clone a coordinate sequence. * \param s the coordinate sequence to clone * \return a copy of the coordinate sequence or NULL on exception +* \since 2.2 */ extern GEOSCoordSequence GEOS_DLL *GEOSCoordSeq_clone(const GEOSCoordSequence* s); /** * Destroy a coordinate sequence, freeing all memory. * \param s the coordinate sequence to destroy +* \since 2.2 */ extern void GEOS_DLL GEOSCoordSeq_destroy(GEOSCoordSequence* s); @@ -2062,6 +2245,7 @@ extern void GEOS_DLL GEOSCoordSeq_destroy(GEOSCoordSequence* s); * \param idx the index of the coordinate to alter, zero based * \param val the value to set the ordinate to * \return 0 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSCoordSeq_setX(GEOSCoordSequence* s, unsigned int idx, double val); @@ -2071,6 +2255,7 @@ extern int GEOS_DLL GEOSCoordSeq_setX(GEOSCoordSequence* s, * \param idx the index of the coordinate to alter, zero based * \param val the value to set the ordinate to * \return 0 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSCoordSeq_setY(GEOSCoordSequence* s, unsigned int idx, double val); @@ -2080,6 +2265,7 @@ extern int GEOS_DLL GEOSCoordSeq_setY(GEOSCoordSequence* s, * \param idx the index of the coordinate to alter, zero based * \param val the value to set the ordinate to * \return 0 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSCoordSeq_setZ(GEOSCoordSequence* s, unsigned int idx, double val); @@ -2090,6 +2276,8 @@ extern int GEOS_DLL GEOSCoordSeq_setZ(GEOSCoordSequence* s, * \param x the value to set the X ordinate to * \param y the value to set the Y ordinate to * \return 0 on exception +* +* \since 3.8 */ extern int GEOS_DLL GEOSCoordSeq_setXY(GEOSCoordSequence* s, unsigned int idx, double x, double y); @@ -2101,6 +2289,8 @@ extern int GEOS_DLL GEOSCoordSeq_setXY(GEOSCoordSequence* s, * \param y the value to set the Y ordinate to * \param z the value to set the Z ordinate to * \return 0 on exception +* +* \since 3.8 */ extern int GEOS_DLL GEOSCoordSeq_setXYZ(GEOSCoordSequence* s, unsigned int idx, double x, double y, double z); @@ -2111,6 +2301,7 @@ extern int GEOS_DLL GEOSCoordSeq_setXYZ(GEOSCoordSequence* s, * \param dim the dimension number of the ordinate to alter, zero based * \param val the value to set the ordinate to * \return 0 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSCoordSeq_setOrdinate(GEOSCoordSequence* s, unsigned int idx, unsigned int dim, double val); @@ -2121,6 +2312,7 @@ extern int GEOS_DLL GEOSCoordSeq_setOrdinate(GEOSCoordSequence* s, * \param idx the index of the coordinate to alter, zero based * \param val pointer where ordinate value will be placed * \return 0 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSCoordSeq_getX(const GEOSCoordSequence* s, unsigned int idx, double *val); @@ -2131,6 +2323,7 @@ extern int GEOS_DLL GEOSCoordSeq_getX(const GEOSCoordSequence* s, * \param idx the index of the coordinate to alter, zero based * \param val pointer where ordinate value will be placed * \return 0 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSCoordSeq_getY(const GEOSCoordSequence* s, unsigned int idx, double *val); @@ -2140,6 +2333,7 @@ extern int GEOS_DLL GEOSCoordSeq_getY(const GEOSCoordSequence* s, * \param idx the index of the coordinate to alter, zero based * \param val pointer where ordinate value will be placed * \return 0 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSCoordSeq_getZ(const GEOSCoordSequence* s, unsigned int idx, double *val); @@ -2150,6 +2344,8 @@ extern int GEOS_DLL GEOSCoordSeq_getZ(const GEOSCoordSequence* s, * \param x pointer where ordinate X value will be placed * \param y pointer where ordinate Y value will be placed * \return 0 on exception +* +* \since 3.8 */ extern int GEOS_DLL GEOSCoordSeq_getXY(const GEOSCoordSequence* s, unsigned int idx, double *x, double *y); @@ -2161,6 +2357,8 @@ extern int GEOS_DLL GEOSCoordSeq_getXY(const GEOSCoordSequence* s, * \param y pointer where ordinate Y value will be placed * \param z pointer where ordinate Z value will be placed * \return 0 on exception +* +* \since 3.8 */ extern int GEOS_DLL GEOSCoordSeq_getXYZ(const GEOSCoordSequence* s, unsigned int idx, double *x, double *y, double *z); @@ -2171,6 +2369,7 @@ extern int GEOS_DLL GEOSCoordSeq_getXYZ(const GEOSCoordSequence* s, * \param[in] dim the dimension number of the ordinate to read, zero based * \param[out] val pointer where ordinate value will be placed * \return 0 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSCoordSeq_getOrdinate(const GEOSCoordSequence* s, unsigned int idx, unsigned int dim, double *val); @@ -2180,6 +2379,7 @@ extern int GEOS_DLL GEOSCoordSeq_getOrdinate(const GEOSCoordSequence* s, * \param[in] s the coordinate sequence * \param[out] size pointer where size value will be placed * \return 0 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSCoordSeq_getSize( const GEOSCoordSequence* s, @@ -2190,6 +2390,7 @@ extern int GEOS_DLL GEOSCoordSeq_getSize( * \param[in] s the coordinate sequence * \param[out] dims pointer where dimension value will be placed * \return 0 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSCoordSeq_getDimensions( const GEOSCoordSequence* s, @@ -2197,11 +2398,12 @@ extern int GEOS_DLL GEOSCoordSeq_getDimensions( /** * Check orientation of a coordinate sequence. Closure of the sequence is -* assumed. Invalid (collapsed) sequences will return false. Short (less -* than 4 points) sequences will return exception. +* assumed. Invalid (collapsed) or short (fewer than 4 points) sequences return false. * \param s the coordinate sequence * \param is_ccw pointer for ccw value, 1 if counter-clockwise orientation, 0 otherwise * \return 0 on exception, 1 on success +* +* \since 3.7 */ extern int GEOS_DLL GEOSCoordSeq_isCCW( const GEOSCoordSequence* s, @@ -2221,6 +2423,7 @@ extern int GEOS_DLL GEOSCoordSeq_isCCW( * \param s Input coordinate sequence, ownership passes to the geometry * \return A newly allocated point geometry. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_createPoint(GEOSCoordSequence* s); @@ -2230,6 +2433,8 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_createPoint(GEOSCoordSequence* s); * \param y The Y coordinate * \return A newly allocated point geometry. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.8 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_createPointFromXY(double x, double y); @@ -2237,6 +2442,8 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_createPointFromXY(double x, double y); * Creates an empty point. * \return A newly allocated empty point geometry. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyPoint(void); @@ -2245,6 +2452,7 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyPoint(void); * \param s Input coordinate sequence, ownership passes to the geometry * \return A newly allocated linear ring geometry. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_createLinearRing(GEOSCoordSequence* s); @@ -2253,6 +2461,7 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_createLinearRing(GEOSCoordSequence* s); * \param s Input coordinate sequence, ownership passes to the geometry * \return A newly allocated linestring geometry. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_createLineString(GEOSCoordSequence* s); @@ -2260,6 +2469,8 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_createLineString(GEOSCoordSequence* s); * Creates an emptylinestring geometry. * \return A newly allocated linestring geometry. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyLineString(void); @@ -2267,11 +2478,13 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyLineString(void); * Creates an empty polygon geometry. * \return A newly allocated empty polygon geometry. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyPolygon(void); /** -* Creates a polygon geometry from line ring geometries. +* Creates a polygon geometry from linear ring geometries. * \param shell A linear ring that is the exterior ring of the polygon. * \param holes An array of linear rings that are the holes. * \param nholes The number of rings in the holes array. @@ -2281,12 +2494,75 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyPolygon(void); * The caller **retains ownership** of the containing array, * but the ownership of the pointed-to objects is transferred * to the returned \ref GEOSGeometry. +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_createPolygon( GEOSGeometry* shell, GEOSGeometry** holes, unsigned int nholes); +/** +* Creates a CircularString geometry. +* \param s Input coordinate sequence, ownership passes to the geometry +* \return A newly allocated CircularString geometry. NULL on exception. +* Caller is responsible for freeing with GEOSGeom_destroy(). +* \since 3.13 +*/ +extern GEOSGeometry GEOS_DLL *GEOSGeom_createCircularString(GEOSCoordSequence* s); + +/** +* Creates an empty CircularString geometry. +* \return A newly allocated CircularString geometry. NULL on exception. +* Caller is responsible for freeing with GEOSGeom_destroy(). +* \since 3.13 +*/ +extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyCircularString(); + +/** +* Creates a CompoundCurve geometry. +* \param curves A list of geometries that will form the CompoundCurve +* \param ncurves The number of geometries in the curves list +* \return A newly allocated CompoundCurve geometry. NULL on exception. +* Caller is responsible for freeing with GEOSGeom_destroy(). +* \since 3.13 +*/ +extern GEOSGeometry GEOS_DLL *GEOSGeom_createCompoundCurve(GEOSGeometry** curves, + unsigned int ncurves); + +/** +* Creates an empty CompoundCurve geometry. +* \return A newly allocated CompoundCurve geometry. NULL on exception. +* Caller is responsible for freeing with GEOSGeom_destroy(). +* \since 3.13 +*/ +extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyCompoundCurve(); + +/** +* Creates a CurvePolygon geometry from ring geometries. +* \param shell A ring that is the exterior ring of the polygon. +* \param holes An array of rings that are the holes. +* \param nholes The number of rings in the holes array. +* \return A newly allocated geometry. NULL on exception. +* Caller is responsible for freeing with GEOSGeom_destroy(). +* \note The holes argument is an array of GEOSGeometry* objects. +* The caller **retains ownership** of the containing array, +* but the ownership of the pointed-to objects is transferred +* to the returned \ref GEOSGeometry. +* \since 3.13 +*/ +extern GEOSGeometry GEOS_DLL *GEOSGeom_createCurvePolygon( + GEOSGeometry* shell, + GEOSGeometry** holes, + unsigned int nholes); + +/** +* Creates an empty CurvePolygon geometry. +* \return A newly allocated CurvePolygon geometry. NULL on exception. +* Caller is responsible for freeing with GEOSGeom_destroy(). +* \since 3.13 +*/ +extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyCurvePolygon(); + /** * Create a geometry collection. * \param type The geometry type, enumerated by \ref GEOSGeomTypes @@ -2294,21 +2570,44 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_createPolygon( * \param ngeoms The number of geometries in the geoms list * \return A newly allocated geometry collection. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). -* \note The holes argument is an array of GEOSGeometry* objects. +* \note The geoms argument is an array of GEOSGeometry* objects. * The caller **retains ownership** of the containing array, * but the ownership of the pointed-to objects is transferred * to the returned \ref GEOSGeometry. +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_createCollection( int type, GEOSGeometry** geoms, unsigned int ngeoms); +/** +* Release the sub-geometries of a collection for management. +* by the caller. The input collection remains as an empty collection, +* that the caller is responsible for destroying. The output geometries +* are also the responsibility of the caller, as is the containing array, +* which must be freed with GEOSFree(). +* \param collection The collection that will have its components released. +* \param ngeoms A pointer to a variable that will be filled with the +* size of the output array. +* \return A newly allocated array of GEOSGeometry pointers. +* \note The caller is responsible for freeing the returned array +* with GEOSFree() and all the elements with GEOSGeom_destroy(). +* If called with an empty collection, null will be returned +* and ngeoms set to zero. +* \since 3.12 +*/ +extern GEOSGeometry GEOS_DLL ** GEOSGeom_releaseCollection( + GEOSGeometry * collection, + unsigned int * ngeoms); + /** * Create an empty geometry collection. * \param type The geometry type, enumerated by \ref GEOSGeomTypes * \return A newly allocated empty geometry collection. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyCollection(int type); @@ -2319,6 +2618,8 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_createEmptyCollection(int type); * \param ymin Lower bound of envelope * \param xmax Right bound of envelope * \param ymax Upper bound of envelope +* +* \since 3.11 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_createRectangle( double xmin, double ymin, @@ -2329,12 +2630,14 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_createRectangle( * \param g The geometry to copy * \return A newly allocated geometry. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_clone(const GEOSGeometry* g); /** * Release the memory associated with a geometry. * \param g The geometry to be destroyed. +* \since 2.2 */ extern void GEOS_DLL GEOSGeom_destroy(GEOSGeometry* g); @@ -2353,6 +2656,7 @@ extern void GEOS_DLL GEOSGeom_destroy(GEOSGeometry* g); * \return A string with the geometry type. * Caller must free with GEOSFree(). * NULL on exception. +* \since 2.2 */ extern char GEOS_DLL *GEOSGeomType(const GEOSGeometry* g); @@ -2360,6 +2664,7 @@ extern char GEOS_DLL *GEOSGeomType(const GEOSGeometry* g); * Returns the \ref GEOSGeomTypeId number for this geometry. * \param g Input geometry * \return The geometry type number, or -1 on exception. +* \since 2.2 */ extern int GEOS_DLL GEOSGeomTypeId(const GEOSGeometry* g); @@ -2367,16 +2672,19 @@ extern int GEOS_DLL GEOSGeomTypeId(const GEOSGeometry* g); * Returns the "spatial reference id" (SRID) for this geometry. * \param g Input geometry * \return SRID number or 0 if unknown / not set. +* \since 2.2 */ extern int GEOS_DLL GEOSGetSRID(const GEOSGeometry* g); /** * Return the anonymous "user data" for this geometry. -* User data must be managed by the caller, and freed before -* the geometry is freed. +* User data must be managed by the caller, and is not freed when +* the geometry is destroyed. * \param g Input geometry * \return A void* to the user data, caller is responsible for -* casting to the appropriate type and freeing. +* casting to the appropriate type. +* +* \since 3.6 */ extern void GEOS_DLL *GEOSGeom_getUserData(const GEOSGeometry* g); @@ -2385,9 +2693,12 @@ extern void GEOS_DLL *GEOSGeom_getUserData(const GEOSGeometry* g); * multi-geometry or collection or 1 for a simple geometry. * For nested collections, remember to check if returned * sub-geometries are **themselves** also collections. +* Empty collection or multi-geometry types return 0, +* and empty simple geometry types return 1. * \param g Input geometry * \return Number of direct children in this collection * \warning For GEOS < 3.2 this function may crash when fed simple geometries +* \since 2.2 */ extern int GEOS_DLL GEOSGetNumGeometries(const GEOSGeometry* g); @@ -2397,13 +2708,14 @@ extern int GEOS_DLL GEOSGetNumGeometries(const GEOSGeometry* g); * Returned object is a pointer to internal storage: * it must NOT be destroyed directly. * \param g Input geometry -* \param n Sub-geometry index, zero-base +* \param n Sub-geometry index, zero-based * \return A const \ref GEOSGeometry, do not free! It will be freed when the parent is freed. Returns NULL on exception. * \note Up to GEOS 3.2.0 the input geometry must be a Collection, in * later versions it doesn't matter (getGeometryN(0) for a single will * return the input). +* \since 2.2 */ extern const GEOSGeometry GEOS_DLL *GEOSGetGeometryN( const GEOSGeometry* g, @@ -2415,6 +2727,8 @@ extern const GEOSGeometry GEOS_DLL *GEOSGetGeometryN( * precision or 0.0 if it is full floating point precision. * \param g Input geometry * \return The grid size, or -1 on exception +* +* \since 3.6 */ extern double GEOS_DLL GEOSGeom_getPrecision(const GEOSGeometry *g); @@ -2423,6 +2737,7 @@ extern double GEOS_DLL GEOSGeom_getPrecision(const GEOSGeometry *g); * an exception otherwise. * \param g Input Polygon geometry * \return Number of interior rings, -1 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSGetNumInteriorRings(const GEOSGeometry* g); @@ -2431,6 +2746,7 @@ extern int GEOS_DLL GEOSGetNumInteriorRings(const GEOSGeometry* g); * an exception otherwise. * \param g Input LineString geometry * \return Number of points, -1 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSGeomGetNumPoints(const GEOSGeometry* g); @@ -2440,6 +2756,7 @@ extern int GEOS_DLL GEOSGeomGetNumPoints(const GEOSGeometry* g); * \param[in] g Input Point geometry * \param[out] x Pointer to hold return value * \returns 1 on success, 0 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSGeomGetX(const GEOSGeometry *g, double *x); @@ -2449,6 +2766,7 @@ extern int GEOS_DLL GEOSGeomGetX(const GEOSGeometry *g, double *x); * \param[in] g Input Point geometry * \param[out] y Pointer to hold return value * \returns 1 on success, 0 on exception +* \since 2.2 */ extern int GEOS_DLL GEOSGeomGetY(const GEOSGeometry *g, double *y); @@ -2458,9 +2776,22 @@ extern int GEOS_DLL GEOSGeomGetY(const GEOSGeometry *g, double *y); * \param[in] g Input Point geometry * \param[out] z Pointer to hold return value * \returns 1 on success, 0 on exception +* +* \since 3.7 */ extern int GEOS_DLL GEOSGeomGetZ(const GEOSGeometry *g, double *z); +/** +* Returns the M coordinate, for a Point input, or an +* exception otherwise. +* \param[in] g Input Point geometry +* \param[out] m Pointer to hold return value +* \returns 1 on success, 0 on exception +* +* \since 3.12 +*/ +extern int GEOS_DLL GEOSGeomGetM(const GEOSGeometry *g, double *m); + /** * Returns the N'th ring for a Polygon input. * \note Returned object is a pointer to internal storage: @@ -2468,6 +2799,7 @@ extern int GEOS_DLL GEOSGeomGetZ(const GEOSGeometry *g, double *z); * \param g Input Polygon geometry * \param n Index of the desired ring * \return LinearRing geometry. Owned by parent geometry, do not free. NULL on exception. +* \since 2.2 */ extern const GEOSGeometry GEOS_DLL *GEOSGetInteriorRingN( const GEOSGeometry* g, @@ -2479,6 +2811,7 @@ extern const GEOSGeometry GEOS_DLL *GEOSGetInteriorRingN( * it must NOT be destroyed directly. * \param g Input Polygon geometry * \return LinearRing geometry. Owned by parent geometry, do not free. NULL on exception. +* \since 2.2 */ extern const GEOSGeometry GEOS_DLL *GEOSGetExteriorRing( const GEOSGeometry* g); @@ -2488,6 +2821,7 @@ extern const GEOSGeometry GEOS_DLL *GEOSGetExteriorRing( * of any type. * \param g Input geometry * \return Number of points in the geometry. -1 on exception. +* \since 2.2 */ extern int GEOS_DLL GEOSGetNumCoordinates( const GEOSGeometry* g); @@ -2499,6 +2833,7 @@ extern int GEOS_DLL GEOSGetNumCoordinates( * the parent geometry. * \param g Input geometry * \return Coordinate sequence or NULL on exception. +* \since 2.2 */ extern const GEOSCoordSequence GEOS_DLL *GEOSGeom_getCoordSeq( const GEOSGeometry* g); @@ -2513,6 +2848,7 @@ extern const GEOSCoordSequence GEOS_DLL *GEOSGeom_getCoordSeq( * \see geos::geom::Dimension::DimensionType * \param g Input geometry * \return The dimensionality +* \since 2.2 */ extern int GEOS_DLL GEOSGeom_getDimensions( const GEOSGeometry* g); @@ -2525,6 +2861,8 @@ extern int GEOS_DLL GEOSGeom_getDimensions( * * \param g Input geometry * \return The dimension +* +* \since 3.3 */ extern int GEOS_DLL GEOSGeom_getCoordinateDimension( const GEOSGeometry* g); @@ -2534,6 +2872,8 @@ extern int GEOS_DLL GEOSGeom_getCoordinateDimension( * \param[in] g Input geometry * \param[out] value Pointer to place result * \return 0 on exception +* +* \since 3.7 */ extern int GEOS_DLL GEOSGeom_getXMin(const GEOSGeometry* g, double* value); @@ -2542,6 +2882,8 @@ extern int GEOS_DLL GEOSGeom_getXMin(const GEOSGeometry* g, double* value); * \param[in] g Input geometry * \param[out] value Pointer to place result * \return 0 on exception +* +* \since 3.7 */ extern int GEOS_DLL GEOSGeom_getYMin(const GEOSGeometry* g, double* value); @@ -2550,6 +2892,8 @@ extern int GEOS_DLL GEOSGeom_getYMin(const GEOSGeometry* g, double* value); * \param[in] g Input geometry * \param[out] value Pointer to place result * \return 0 on exception +* +* \since 3.7 */ extern int GEOS_DLL GEOSGeom_getXMax(const GEOSGeometry* g, double* value); @@ -2558,6 +2902,8 @@ extern int GEOS_DLL GEOSGeom_getXMax(const GEOSGeometry* g, double* value); * \param[in] g Input geometry * \param[out] value Pointer to place result * \return 0 on exception +* +* \since 3.7 */ extern int GEOS_DLL GEOSGeom_getYMax(const GEOSGeometry* g, double* value); @@ -2571,6 +2917,8 @@ extern int GEOS_DLL GEOSGeom_getYMax(const GEOSGeometry* g, double* value); * \param[out] xmax Pointer to place result for maximum X value * \param[out] ymax Pointer to place result for maximum Y value * \return 1 on success, 0 on exception +* +* \since 3.11 */ extern int GEOS_DLL GEOSGeom_getExtent( const GEOSGeometry* g, @@ -2586,6 +2934,7 @@ extern int GEOS_DLL GEOSGeom_getExtent( * \return A Point geometry. * Caller must free with GEOSGeom_destroy() * NULL on exception. +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSGeomGetPointN(const GEOSGeometry *g, int n); @@ -2595,6 +2944,7 @@ extern GEOSGeometry GEOS_DLL *GEOSGeomGetPointN(const GEOSGeometry *g, int n); * \return A Point geometry. * Caller must free with GEOSGeom_destroy() * NULL on exception. +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSGeomGetStartPoint(const GEOSGeometry *g); @@ -2604,6 +2954,7 @@ extern GEOSGeometry GEOS_DLL *GEOSGeomGetStartPoint(const GEOSGeometry *g); * \return A Point geometry. * Caller must free with GEOSGeom_destroy() * NULL on exception. +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSGeomGetEndPoint(const GEOSGeometry *g); @@ -2614,6 +2965,7 @@ extern GEOSGeometry GEOS_DLL *GEOSGeomGetEndPoint(const GEOSGeometry *g); * has no boundary or interior. * \param g The geometry to test * \return 1 on true, 0 on false, 2 on exception +* \since 2.2 */ extern char GEOS_DLL GEOSisEmpty(const GEOSGeometry* g); @@ -2623,22 +2975,34 @@ extern char GEOS_DLL GEOSisEmpty(const GEOSGeometry* g); * with start and end point being identical. * \param g The geometry to test * \return 1 on true, 0 on false, 2 on exception +* \since 2.2 */ extern char GEOS_DLL GEOSisRing(const GEOSGeometry* g); /** -* Tests whether the input geometry has z coordinates. +* Tests whether the input geometry has Z coordinates. * \param g The geometry to test * \return 1 on true, 0 on false, 2 on exception +* \since 2.2 */ extern char GEOS_DLL GEOSHasZ(const GEOSGeometry* g); +/** +* Tests whether the input geometry has M coordinates. +* \param g The geometry to test +* \return 1 on true, 0 on false, 2 on exception +* +* \since 3.12 +*/ +extern char GEOS_DLL GEOSHasM(const GEOSGeometry* g); + /** * Tests whether the input geometry is closed. * A closed geometry is a linestring or multilinestring * with the start and end points being the same. * \param g The geometry to test * \return 1 on true, 0 on false, 2 on exception +* \since 3.3 */ extern char GEOS_DLL GEOSisClosed(const GEOSGeometry *g); @@ -2654,14 +3018,16 @@ extern char GEOS_DLL GEOSisClosed(const GEOSGeometry *g); * Set the "spatial reference id" (SRID) for this geometry. * \param g Input geometry * \param SRID SRID number or 0 for unknown SRID. +* \since 2.2 */ extern void GEOS_DLL GEOSSetSRID(GEOSGeometry* g, int SRID); /** * Set the anonymous "user data" for this geometry. -* Don't forget to free the user data before freeing the geometry. * \param g Input geometry * \param userData Void pointer to user data +* +* \since 3.6 */ extern void GEOS_DLL GEOSGeom_setUserData(GEOSGeometry* g, void* userData); @@ -2682,9 +3048,23 @@ extern void GEOS_DLL GEOSGeom_setUserData(GEOSGeometry* g, void* userData); * Use before calling \ref GEOSEqualsExact to avoid false "not equal" results. * \param g Input geometry * \return 0 on success or -1 on exception +* \since 3.0 */ extern int GEOS_DLL GEOSNormalize(GEOSGeometry* g); +/** +* Enforce a ring orientation on all polygonal elements in the input geometry. +* Non-polygonal geometries will not be modified. +* +* \param g Input geometry +* \param exteriorCW if 1, exterior rings will be clockwise and interior rings +* will be counter-clockwise +* \return 0 on success or -1 on exception +* \since 3.12 +*/ +extern int GEOS_DLL GEOSOrientPolygons(GEOSGeometry* g, + int exteriorCW); + ///@} /* ========== Validity checking ============================================================ */ @@ -2699,6 +3079,7 @@ and geometric quality. * linestrings. A "simple" linestring has no self-intersections. * \param g The geometry to test * \return 1 on true, 0 on false, 2 on exception +* \since 2.2 */ extern char GEOS_DLL GEOSisSimple(const GEOSGeometry* g); @@ -2713,6 +3094,7 @@ extern char GEOS_DLL GEOSisSimple(const GEOSGeometry* g); * \param g The geometry to test * \return 1 on true, 0 on false, 2 on exception * \see geos::operation::valid::isValidOp +* \since 2.2 */ extern char GEOS_DLL GEOSisValid(const GEOSGeometry* g); @@ -2721,7 +3103,9 @@ extern char GEOS_DLL GEOSisValid(const GEOSGeometry* g); * "Valid Geometry" string otherwise, or NULL on exception. * \param g The geometry to test * \return A string with the reason, NULL on exception. - Caller must GEOSFree() their result. +* Caller must GEOSFree() their result. +* +* \since 3.1 */ extern char GEOS_DLL *GEOSisValidReason(const GEOSGeometry *g); @@ -2736,6 +3120,8 @@ extern char GEOS_DLL *GEOSisValidReason(const GEOSGeometry *g); * \param reason A pointer in which the reason string will be places * \param location A pointer in which the location GEOSGeometry will be placed * \return 1 when valid, 0 when invalid, 2 on exception +* +* \since 3.3 */ extern char GEOS_DLL GEOSisValidDetail( const GEOSGeometry* g, @@ -2747,6 +3133,8 @@ extern char GEOS_DLL GEOSisValidDetail( * Repair an invalid geometry, returning a valid output. * \param g The geometry to repair * \return The repaired geometry. Caller must free with GEOSGeom_destroy(). +* +* \since 3.8 */ extern GEOSGeometry GEOS_DLL *GEOSMakeValid( const GEOSGeometry* g); @@ -2761,6 +3149,8 @@ extern GEOSGeometry GEOS_DLL *GEOSMakeValid( * \see GEOSMakeValidParams_destroy * \see GEOSMakeValidParams_setMethod * \see GEOSMakeValidParams_setKeepCollapsed +* +* \since 3.10 */ extern GEOSGeometry GEOS_DLL *GEOSMakeValidWithParams( const GEOSGeometry* g, @@ -2771,6 +3161,8 @@ extern GEOSGeometry GEOS_DLL *GEOSMakeValidWithParams( * to control the algorithm and behavior of the validation process. * \return a parameter object * \see GEOSMakeValidWithParams +* +* \since 3.10 */ extern GEOSMakeValidParams GEOS_DLL *GEOSMakeValidParams_create(void); @@ -2778,6 +3170,8 @@ extern GEOSMakeValidParams GEOS_DLL *GEOSMakeValidParams_create(void); * Destroy a GEOSMakeValidParams. * \param parms the object to destroy * \see GEOSMakeValidWithParams +* +* \since 3.10 */ extern void GEOS_DLL GEOSMakeValidParams_destroy(GEOSMakeValidParams* parms); @@ -2786,6 +3180,8 @@ extern void GEOS_DLL GEOSMakeValidParams_destroy(GEOSMakeValidParams* parms); * valid. * \return 0 on exception, 1 on success. * \see GEOSMakeValidWithParams +* +* \since 3.10 */ extern int GEOS_DLL GEOSMakeValidParams_setMethod( GEOSMakeValidParams* p, @@ -2798,6 +3194,8 @@ extern int GEOS_DLL GEOSMakeValidParams_setMethod( * to a point. * \return 0 on exception, 1 on success. * \see GEOSMakeValidWithParams +* +* \since 3.10 */ extern int GEOS_DLL GEOSMakeValidParams_setKeepCollapsed( GEOSMakeValidParams* p, @@ -2819,6 +3217,8 @@ extern int GEOS_DLL GEOSMakeValidParams_setKeepCollapsed( * \return 0 if no exception occurred. * 2 if an exception occurred. * \see geos::precision::MinimumClearance +* +* \since 3.6 */ extern int GEOS_DLL GEOSMinimumClearance(const GEOSGeometry* g, double* d); @@ -2830,6 +3230,8 @@ extern int GEOS_DLL GEOSMinimumClearance(const GEOSGeometry* g, double* d); * \return a linestring geometry, or NULL if an exception occurred. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::precision::MinimumClearance +* +* \since 3.6 */ extern GEOSGeometry GEOS_DLL *GEOSMinimumClearanceLine(const GEOSGeometry* g); @@ -2847,6 +3249,8 @@ extern GEOSGeometry GEOS_DLL *GEOSMinimumClearanceLine(const GEOSGeometry* g); * \param tolerance Remove all points within this distance of each other. Use 0.0 to remove only exactly repeated points. * * \see GEOSMakeValidWithParams +* +* \since 3.11 */ extern GEOSGeometry GEOS_DLL *GEOSRemoveRepeatedPoints( const GEOSGeometry* g, @@ -2865,6 +3269,7 @@ extern GEOSGeometry GEOS_DLL *GEOSRemoveRepeatedPoints( * \param[in] g Input geometry * \param[out] area Pointer to be filled in with area result * \return 1 on success, 0 on exception. +* \since 2.2 */ extern int GEOS_DLL GEOSArea( const GEOSGeometry* g, @@ -2875,6 +3280,7 @@ extern int GEOS_DLL GEOSArea( * \param[in] g Input geometry * \param[out] length Pointer to be filled in with length result * \return 1 on success, 0 on exception. +* \since 2.2 */ extern int GEOS_DLL GEOSLength( const GEOSGeometry* g, @@ -2887,6 +3293,7 @@ extern int GEOS_DLL GEOSLength( * \param[in] g Input geometry * \param[out] length Pointer to be filled in with length result * \return 1 on success, 0 on exception. +* \since 3.3 */ extern int GEOS_DLL GEOSGeomGetLength( const GEOSGeometry *g, @@ -2907,6 +3314,7 @@ extern int GEOS_DLL GEOSGeomGetLength( * \param[in] g2 Input geometry * \param[out] dist Pointer to be filled in with distance result * \return 1 on success, 0 on exception. +* \since 2.2 */ extern int GEOS_DLL GEOSDistance( const GEOSGeometry* g1, @@ -2920,6 +3328,8 @@ extern int GEOS_DLL GEOSDistance( * \param g2 Input geometry * \param dist The max distance * \returns 1 on true, 0 on false, 2 on exception +* +* \since 3.10 */ extern char GEOS_DLL GEOSDistanceWithin( const GEOSGeometry* g1, @@ -2936,6 +3346,8 @@ extern char GEOS_DLL GEOSDistanceWithin( * \param[out] dist Pointer to be filled in with distance result * \return 1 on success, 0 on exception. * \see geos::operation::distance:;IndexedFacetDistance +* +* \since 3.7 */ extern int GEOS_DLL GEOSDistanceIndexed( const GEOSGeometry* g1, @@ -2950,6 +3362,8 @@ extern int GEOS_DLL GEOSDistanceIndexed( * \param[in] g2 Input geometry * \return A coordinate sequence with the two points, or NULL on exception. * Caller must free with GEOSCoordSeq_destroy(). +* +* \since 3.5 */ extern GEOSCoordSequence GEOS_DLL *GEOSNearestPoints( const GEOSGeometry* g1, @@ -2964,6 +3378,7 @@ extern GEOSCoordSequence GEOS_DLL *GEOSNearestPoints( * \param[out] dist Pointer to be filled in with distance result * \return 1 on success, 0 on exception. * \see geos::algorithm::distance::DiscreteHausdorffDistance +* \since 3.2 */ extern int GEOS_DLL GEOSHausdorffDistance( const GEOSGeometry *g1, @@ -2982,6 +3397,7 @@ extern int GEOS_DLL GEOSHausdorffDistance( * \param[out] dist Pointer to be filled in with distance result * \return 1 on success, 0 on exception. * \see geos::algorithm::distance::DiscreteHausdorffDistance +* \since 3.2 */ extern int GEOS_DLL GEOSHausdorffDistanceDensify( const GEOSGeometry *g1, @@ -2999,6 +3415,8 @@ extern int GEOS_DLL GEOSHausdorffDistanceDensify( * \param[out] dist Pointer to be filled in with distance result * \return 1 on success, 0 on exception. * \see geos::algorithm::distance::DiscreteFrechetDistance +* +* \since 3.7 */ extern int GEOS_DLL GEOSFrechetDistance( const GEOSGeometry *g1, @@ -3009,8 +3427,8 @@ extern int GEOS_DLL GEOSFrechetDistance( * Calculate the * [Frechet distance](https://en.wikipedia.org/wiki/Fr%C3%A9chet_distance) * between two geometries, -* a similarity measure for linear features. For more precision, first -* densify the inputs. +* a similarity measure for linear features. +* The inputs can be densified to provide a more accurate result. * \param[in] g1 Input geometry * \param[in] g2 Input geometry * \param[in] densifyFrac The largest % of the overall line length that @@ -3018,6 +3436,8 @@ extern int GEOS_DLL GEOSFrechetDistance( * \param[out] dist Pointer to be filled in with distance result * \return 1 on success, 0 on exception. * \see geos::algorithm::distance::DiscreteFrechetDistance +* +* \since 3.7 */ extern int GEOS_DLL GEOSFrechetDistanceDensify( const GEOSGeometry *g1, @@ -3041,6 +3461,7 @@ extern int GEOS_DLL GEOSFrechetDistanceDensify( * \return distance along line that point projects to, -1 on exception * * \note Line parameter must be a LineString. +* \since 3.2 */ extern double GEOS_DLL GEOSProject(const GEOSGeometry* line, const GEOSGeometry* point); @@ -3054,6 +3475,7 @@ extern double GEOS_DLL GEOSProject(const GEOSGeometry* line, * \param d distance from start of line to created point * \return The point \ref GEOSGeometry that is distance from the start of line. * Caller takes ownership of returned geometry. +* \since 3.2 */ extern GEOSGeometry GEOS_DLL *GEOSInterpolate(const GEOSGeometry* line, double d); @@ -3066,6 +3488,7 @@ extern GEOSGeometry GEOS_DLL *GEOSInterpolate(const GEOSGeometry* line, * \param point the point to project * \return The proportion of the overall line length that the projected * point falls at. +* \since 3.2 */ extern double GEOS_DLL GEOSProjectNormalized(const GEOSGeometry* line, const GEOSGeometry* point); @@ -3077,6 +3500,7 @@ extern double GEOS_DLL GEOSProjectNormalized(const GEOSGeometry* line, * \param proportion The proportion from the start of line to created point * \return The point \ref GEOSGeometry that is distance from the start of line. * Caller takes ownership of returned geometry. +* \since 3.2 */ extern GEOSGeometry GEOS_DLL *GEOSInterpolateNormalized( const GEOSGeometry *line, @@ -3099,6 +3523,7 @@ extern GEOSGeometry GEOS_DLL *GEOSInterpolateNormalized( * \return A newly allocated geometry of the intersection. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::overlayng::OverlayNG +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSIntersection(const GEOSGeometry* g1, const GEOSGeometry* g2); @@ -3113,6 +3538,8 @@ extern GEOSGeometry GEOS_DLL *GEOSIntersection(const GEOSGeometry* g1, const GEO * \return A newly allocated geometry of the intersection. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::overlayng::OverlayNG +* +* \since 3.9 */ extern GEOSGeometry GEOS_DLL *GEOSIntersectionPrec(const GEOSGeometry* g1, const GEOSGeometry* g2, double gridSize); @@ -3124,6 +3551,7 @@ extern GEOSGeometry GEOS_DLL *GEOSIntersectionPrec(const GEOSGeometry* g1, const * \return A newly allocated geometry of the difference. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::overlayng::OverlayNG +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSDifference( const GEOSGeometry* ga, @@ -3141,6 +3569,8 @@ extern GEOSGeometry GEOS_DLL *GEOSDifference( * \return A newly allocated geometry of the difference. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::overlayng::OverlayNG +* +* \since 3.9 */ extern GEOSGeometry GEOS_DLL *GEOSDifferencePrec( const GEOSGeometry* ga, @@ -3156,6 +3586,7 @@ extern GEOSGeometry GEOS_DLL *GEOSDifferencePrec( * \return A newly allocated geometry of the symmetric difference. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::overlayng::OverlayNG +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSSymDifference( const GEOSGeometry* ga, @@ -3174,6 +3605,8 @@ extern GEOSGeometry GEOS_DLL *GEOSSymDifference( * \return A newly allocated geometry of the symmetric difference. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::overlayng::OverlayNG +* +* \since 3.9 */ extern GEOSGeometry GEOS_DLL *GEOSSymDifferencePrec( const GEOSGeometry* ga, @@ -3188,6 +3621,7 @@ extern GEOSGeometry GEOS_DLL *GEOSSymDifferencePrec( * \return A newly allocated geometry of the union. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::overlayng::OverlayNG +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSUnion( const GEOSGeometry* ga, @@ -3205,6 +3639,8 @@ extern GEOSGeometry GEOS_DLL *GEOSUnion( * \return A newly allocated geometry of the union. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::overlayng::OverlayNG +* +* \since 3.9 */ extern GEOSGeometry GEOS_DLL *GEOSUnionPrec( const GEOSGeometry* ga, @@ -3219,6 +3655,8 @@ extern GEOSGeometry GEOS_DLL *GEOSUnionPrec( * \return A newly allocated geometry of the union. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::overlayng::OverlayNG +* +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSUnaryUnion(const GEOSGeometry* g); @@ -3234,20 +3672,24 @@ extern GEOSGeometry GEOS_DLL *GEOSUnaryUnion(const GEOSGeometry* g); * \return A newly allocated geometry of the union. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::overlayng::OverlayNG +* +* \since 3.9 */ extern GEOSGeometry GEOS_DLL *GEOSUnaryUnionPrec( const GEOSGeometry* g, double gridSize); /** -* Optimized union algorithm for polygonal inputs that are correctly -* noded and do not overlap. It will generate an error (return NULL) -* for inputs that do not satisfy this constraint. +* Optimized union algorithm for inputs that can be divided into subsets +* that do not intersect. If there is only one such subset, performance +* can be expected to be worse than GEOSUnionaryUnion. * \param g The input geometry -* \return A geometry that covers all the points of the input geometry. +* \return A newly allocated geometry of the union, or NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.12 */ -extern GEOSGeometry GEOS_DLL *GEOSCoverageUnion(const GEOSGeometry *g); +extern GEOSGeometry GEOS_DLL *GEOSDisjointSubsetUnion(const GEOSGeometry *g); /** * Intersection optimized for a rectangular clipping polygon. @@ -3261,6 +3703,8 @@ extern GEOSGeometry GEOS_DLL *GEOSCoverageUnion(const GEOSGeometry *g); * \return The clipped geometry or NULL on exception * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::intersection::RectangleIntersection +* +* \since 3.5 */ extern GEOSGeometry GEOS_DLL *GEOSClipByRect( const GEOSGeometry* g, @@ -3282,6 +3726,8 @@ extern GEOSGeometry GEOS_DLL *GEOSClipByRect( * \return The shared paths * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::sharedpaths::SharedPathsOp +* +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSSharedPaths( const GEOSGeometry* g1, @@ -3304,6 +3750,7 @@ extern GEOSGeometry GEOS_DLL *GEOSSharedPaths( * segments provides a more "precise" buffer at the expense of size. * \return A \ref GEOSGeometry of the buffered result. * NULL on exception. Caller is responsible for freeing with GEOSGeom_destroy(). +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSBuffer(const GEOSGeometry* g, double width, int quadsegs); @@ -3313,12 +3760,16 @@ extern GEOSGeometry GEOS_DLL *GEOSBuffer(const GEOSGeometry* g, * of buffered generated by \ref GEOSBuffer. * \return A newly allocated GEOSBufferParams. NULL on exception. * Caller is responsible for freeing with GEOSBufferParams_destroy(). +* +* \since 3.3 */ extern GEOSBufferParams GEOS_DLL *GEOSBufferParams_create(void); /** * Destroy a GEOSBufferParams and free all associated memory. * \param parms The object to destroy. +* +* \since 3.3 */ extern void GEOS_DLL GEOSBufferParams_destroy(GEOSBufferParams* parms); @@ -3326,6 +3777,8 @@ extern void GEOS_DLL GEOSBufferParams_destroy(GEOSBufferParams* parms); * Set the end cap type of a GEOSBufferParams to the desired style, * which must be one enumerated in \ref GEOSBufCapStyles. * \return 0 on exception, 1 on success. +* +* \since 3.3 */ extern int GEOS_DLL GEOSBufferParams_setEndCapStyle( GEOSBufferParams* p, @@ -3335,6 +3788,8 @@ extern int GEOS_DLL GEOSBufferParams_setEndCapStyle( * Set the join type of a GEOSBufferParams to the desired style, * which must be one enumerated in \ref GEOSBufJoinStyles. * \return 0 on exception, 1 on success. +* +* \since 3.3 */ extern int GEOS_DLL GEOSBufferParams_setJoinStyle( GEOSBufferParams* p, @@ -3348,6 +3803,8 @@ extern int GEOS_DLL GEOSBufferParams_setJoinStyle( * \param p The GEOSBufferParams to operate on * \param mitreLimit The limit to set * \return 0 on exception, 1 on success. +* +* \since 3.3 */ extern int GEOS_DLL GEOSBufferParams_setMitreLimit( GEOSBufferParams* p, @@ -3360,6 +3817,8 @@ extern int GEOS_DLL GEOSBufferParams_setMitreLimit( * \param p The GEOSBufferParams to operate on * \param quadSegs Number of segments per quadrant * \return 0 on exception, 1 on success. +* +* \since 3.3 */ extern int GEOS_DLL GEOSBufferParams_setQuadrantSegments( GEOSBufferParams* p, @@ -3372,6 +3831,8 @@ extern int GEOS_DLL GEOSBufferParams_setQuadrantSegments( * \param p The GEOSBufferParams to operate on * \param singleSided Set to 1 for single-sided output 0 otherwise * \return 0 on exception, 1 on success. +* +* \since 3.3 */ extern int GEOS_DLL GEOSBufferParams_setSingleSided( GEOSBufferParams* p, @@ -3384,6 +3845,8 @@ extern int GEOS_DLL GEOSBufferParams_setSingleSided( * \param width The buffer distance * \return The buffered geometry, or NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSBufferWithParams( const GEOSGeometry* g, @@ -3400,6 +3863,8 @@ extern GEOSGeometry GEOS_DLL *GEOSBufferWithParams( * \param mitreLimit See GEOSBufferParams_setMitreLimit * \return The buffered geometry, or NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.2 */ extern GEOSGeometry GEOS_DLL *GEOSBufferWithStyle( const GEOSGeometry* g, @@ -3428,12 +3893,96 @@ extern GEOSGeometry GEOS_DLL *GEOSBufferWithStyle( * \return The offset geometry. Returns NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::buffer::BufferBuilder::bufferLineSingleSided +* +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSOffsetCurve(const GEOSGeometry* g, double width, int quadsegs, int joinStyle, double mitreLimit); ///@} + +/* ====================================================================== */ +/** @name Coverages +* Functions to work with coverages represented by lists of polygons +* that exactly share edge geometry. +*/ +///@{ + +/** +* Optimized union algorithm for polygonal inputs that are correctly +* noded and do not overlap. It may generate an error (return NULL) +* for inputs that do not satisfy this constraint, however this is not +* guaranteed. +* \param g The input geometry +* \return A geometry that covers all the points of the input geometry. +* Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.8 +*/ +extern GEOSGeometry GEOS_DLL *GEOSCoverageUnion(const GEOSGeometry *g); + + +/** +* Analyze a coverage (represented as a collection of polygonal geometry +* with exactly matching edge geometry) to find places where the +* assumption of exactly matching edges is not met. +* +* \param input The polygonal coverage to access, +* stored in a geometry collection. All members must be POLYGON +* or MULTIPOLYGON. +* \param gapWidth The maximum width of gaps to detect. +* \param invalidEdges When there are invalidities in the coverage, +* this pointer +* will be set with a geometry collection of the same length as +* the input, with a MULTILINESTRING of the error edges for each +* invalid polygon, or an EMPTY where the polygon is a valid +* participant in the coverage. Pass NULL if you do not want +* the invalid edges returned. +* \return A value of 1 for a valid coverage, 0 for invalid and 2 for +* an exception or error. Invalidity includes polygons that overlap, +* that have gaps smaller than the gapWidth, or non-polygonal +* entries in the input collection. +* +* \since 3.12 +*/ +extern int GEOS_DLL GEOSCoverageIsValid( + const GEOSGeometry* input, + double gapWidth, + GEOSGeometry** invalidEdges); + +/** +* Operates on a coverage (represented as a list of polygonal geometry +* with exactly matching edge geometry) to apply a Visvalingam–Whyatt +* simplification to the edges, reducing complexity in proportion with +* the provided tolerance, while retaining a valid coverage (no edges +* will cross or touch after the simplification). +* Geometries never disappear, but they may be simplified down to just +* a triangle. Also, some invalid geoms (such as Polygons which have too +* few non-repeated points) will be returned unchanged. +* If the input dataset is not a valid coverage due to overlaps, +* it will still be simplified, but invalid topology such as crossing +* edges will still be invalid. +* +* \param input The polygonal coverage to access, +* stored in a geometry collection. All members must be POLYGON +* or MULTIPOLYGON. +* \param tolerance A tolerance parameter in linear units. +* \param preserveBoundary Use 1 to preserve the outside edges +* of the coverage without simplification, +* 0 to allow them to be simplified. +* \return A collection containing the simplified geometries, or null +* on error. +* +* \since 3.12 +*/ +extern GEOSGeometry GEOS_DLL * GEOSCoverageSimplifyVW( + const GEOSGeometry* input, + double tolerance, + int preserveBoundary); + +///@} + /* ========== Construction Operations ========== */ /** @name Geometric Constructions * Functions for computing geometric constructions. @@ -3441,10 +3990,12 @@ extern GEOSGeometry GEOS_DLL *GEOSOffsetCurve(const GEOSGeometry* g, ///@{ /** -* Returns minimum rectangular polygon that contains the geometry. +* Returns minimum rectangular polygon or point that contains the geometry, +* or an empty point for empty inputs. * \param g The geometry to calculate an envelope for -* \return A newly allocated polygonal envelope. NULL on exception. +* \return A newly allocated polygonal envelope or point. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSEnvelope(const GEOSGeometry* g); @@ -3459,6 +4010,7 @@ extern GEOSGeometry GEOS_DLL *GEOSEnvelope(const GEOSGeometry* g); * \param g The input geometry * \return A newly allocated geometry of the boundary. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSBoundary(const GEOSGeometry* g); @@ -3469,34 +4021,122 @@ extern GEOSGeometry GEOS_DLL *GEOSBoundary(const GEOSGeometry* g); * \return A newly allocated geometry of the convex hull. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::overlayng::OverlayNG +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSConvexHull(const GEOSGeometry* g); /** -* Returns "concave hull" of a geometry. The concave hull is fully -* contained within the convex hull and also contains all the -* points of the input, but in a smaller area. -* The area ratio is the ratio -* of the area of the convex hull and the concave hull. Frequently -* used to convert a multi-point into a polygonal area. -* that contains all the points in the input Geometry +* Returns a "concave hull" of a geometry. A concave hull is +* a polygon which contains all the +* points of the input, but is a better approximation than the convex hull +* to the area occupied by the input. +* Frequently used to convert a multi-point into a polygonal area. +* that contains all the points in the input Geometry. +* +* A set of points has a sequence of hulls of increasing concaveness, +* determined by a numeric target parameter. +* The concave hull is constructed by removing the longest outer edges +* of the Delaunay Triangulation of the space between the polygons, +* until the target criterion parameter is reached. +* This can be expressed as a ratio between the lengths of the longest and shortest edges. +* 1 produces the convex hull; 0 produces a hull with maximum concaveness +* * \param g The input geometry -* \param ratio The ratio value, between 0 and 1. +* \param ratio The edge length ratio value, between 0 and 1. * \param allowHoles When non-zero, the polygonal output may contain holes. * \return A newly allocated geometry of the concave hull. NULL on exception. -* The area ratio is the ratio of the concave hull area to the convex hull area. -* 1 produces the convex hull; 0 produces maximum concaveness. -* The Length Ratio is a fraction determining the length of the longest -* edge in the computed hull. 1 produces the convex hull; -* 0 produces a hull with maximum concaveness +* * Caller is responsible for freeing with GEOSGeom_destroy(). +* * \see geos::algorithm::hull::ConcaveHull +* \see GEOSConcaveHullByLength +* \see GEOSConcaveHullOfPolygons +* \see GEOSConvexHull +* +* \since 3.11 */ extern GEOSGeometry GEOS_DLL *GEOSConcaveHull( const GEOSGeometry* g, double ratio, unsigned int allowHoles); +/** +* Returns a "concave hull" of a geometry. A concave hull is +* a polygon which contains all the +* points of the input, but is a better approximation than the convex hull +* to the area occupied by the input. +* Frequently used to convert a multi-point into a polygonal area. +* that contains all the points in the input Geometry. +* +* A set of points has a sequence of hulls of increasing concaveness, +* determined by a numeric target parameter. +* The concave hull is constructed by removing the longest outer edges +* of the Delaunay Triangulation of the space between the polygons, +* until the specified maximm edge length is reached. +* A large value produces the convex hull, 0 produces the hull of maximum concaveness. +* +* \param g The input geometry +* \param length The maximum edge length (0 or greater) +* \param allowHoles When non-zero, the polygonal output may contain holes. +* \return A newly allocated geometry of the concave hull. NULL on exception. +* +* Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \see geos::algorithm::hull::ConcaveHull +* \see GEOSConcaveHull +* \see GEOSConcaveHullOfPolygons +* \see GEOSConvexHull +* +* \since 3.12 +*/ +extern GEOSGeometry GEOS_DLL *GEOSConcaveHullByLength( + const GEOSGeometry* g, + double length, + unsigned int allowHoles); + +/** +* Constructs a concave hull of a set of polygons, respecting +* the polygons as constraints. +* +* A concave hull is a (possibly) non-convex polygon containing all the input polygons. +* The computed hull "fills the gap" between the polygons, +* and does not intersect their interior. +* A set of polygons has a sequence of hulls of increasing concaveness, +* determined by a numeric target parameter. +* +* The concave hull is constructed by removing the longest outer edges +* of the Delaunay Triangulation of the space between the polygons, +* until the target criterion parameter is reached. +* The "Maximum Edge Length" parameter limits the length of the longest edge between polygons to be no larger than this value. +* This can be expressed as a ratio between the lengths of the longest and shortest edges. +* +* The input polygons *must* be a *valid* MultiPolygon +* (i.e. they must be non-overlapping). +* +* \param g the valid MultiPolygon geometry to process +* \param lengthRatio specifies the Maximum Edge Length as a +* fraction of the difference between the longest and +* shortest edge lengths between the polygons. +* This normalizes the Maximum Edge Length to be scale-free. +* A value of 1 produces the convex hull; a value of 0 produces +* the original polygons. +* \param isHolesAllowed is the concave hull allowed to contain holes? +* \param isTight does the hull follow the outer boundaries of the input polygons. +* \return A newly allocated geometry of the concave hull. NULL on exception. +* +* Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \see geos::algorithm::hull::ConcaveHullOfPolygons +* \see GEOSConcaveHull +* \see GEOSConvexHull +* +* \since 3.11 +*/ +extern GEOSGeometry GEOS_DLL *GEOSConcaveHullOfPolygons( + const GEOSGeometry* g, + double lengthRatio, + unsigned int isTight, + unsigned int isHolesAllowed); /** * Computes a boundary-respecting hull of a polygonal geometry, @@ -3514,6 +4154,8 @@ extern GEOSGeometry GEOS_DLL *GEOSConcaveHull( * * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::simplify::PolygonHullSimplifier +* +* \since 3.11 */ extern GEOSGeometry GEOS_DLL *GEOSPolygonHullSimplify( const GEOSGeometry* g, @@ -3525,28 +4167,32 @@ extern GEOSGeometry GEOS_DLL *GEOSPolygonHullSimplify( * Controls the behavior of the GEOSPolygonHullSimplify parameter. */ enum GEOSPolygonHullParameterModes { - /** See geos::simplify::PolygonHullSimplifier::hull() */ + /** Fraction of input vertices retained */ GEOSHULL_PARAM_VERTEX_RATIO = 1, - /** See geos::simplify::PolygonHullSimplifier::hullByAreaDelta() */ + /** Ratio of simplified hull area to input area */ GEOSHULL_PARAM_AREA_RATIO = 2 }; /** * Computes a topology-preserving simplified hull of a polygonal geometry, * with hull shape determined by the parameter, controlled by a parameter -* mode, which is one defined in GEOSPolygonHullParameterModes. In general, -* larger values compute less concave results and value of 0 +* mode, which is one defined in \ref GEOSPolygonHullParameterModes. +* Larger values compute less concave results and a value of 0 * produces the original geometry. * Either outer or inner hulls can be computed. * * \param g the polygonal geometry to process * \param isOuter indicates whether to compute an outer or inner hull (1 for outer hull, 0 for inner) -* \param parameterMode the interpretation to apply to the parameter argument +* \param parameterMode the interpretation to apply to the parameter argument; see \ref GEOSPolygonHullParameterModes * \param parameter the target ratio of area difference to original area * \return A newly allocated geometry of the concave hull. NULL on exception. * * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::simplify::PolygonHullSimplifier +* \see GEOSPolygonHullParameterModes +* \see GEOSPolygonHullSimplify +* +* \since 3.11 */ extern GEOSGeometry GEOS_DLL *GEOSPolygonHullSimplifyMode( const GEOSGeometry* g, @@ -3554,45 +4200,6 @@ extern GEOSGeometry GEOS_DLL *GEOSPolygonHullSimplifyMode( unsigned int parameterMode, double parameter); -/** -* Constructs a concave hull of a set of polygons, respecting -* the polygons as constraints. -* -* A concave hull is a possibly non-convex polygon containing all the input polygons. -* A given set of polygons has a sequence of hulls of increasing concaveness, -* determined by a numeric target parameter. -* The computed hull "fills the gap" between the polygons, -* and does not intersect their interior. -* -* The concave hull is constructed by removing the longest outer edges -* of the Delaunay Triangulation of the space between the polygons, -* until the target criterion parameter is reached. -* -* "Maximum Edge Length" constrains the length of the longest edge between the polygons to be no larger than this value. -* -* \param g the valid MultiPolygon geometry to process -* \param lengthRatio determine the Maximum Edge Length as a -* fraction of the difference between the longest and -* shortest edge lengths between the polygons. -* This normalizes the Maximum Edge Length to be scale-free. -* A value of 1 produces the convex hull; a value of 0 produces -* the original polygons. -* \param isHolesAllowed is the concave hull allowed to contain holes? -* \param isTight does the hull follow the outer boundaries of the input polygons. -* \return A newly allocated geometry of the concave hull. NULL on exception. -* -* Caller is responsible for freeing with GEOSGeom_destroy(). -* \see geos::algorithm::hull::ConcaveHullOfPolygons -* -* The input polygons *must* form a *valid* MultiPolygon -* (i.e. they must be non-overlapping). -*/ -extern GEOSGeometry GEOS_DLL *GEOSConcaveHullOfPolygons( - const GEOSGeometry* g, - double lengthRatio, - unsigned int isTight, - unsigned int isHolesAllowed); - /** * Returns the minimum rotated rectangular POLYGON which encloses * the input geometry. The rectangle has width equal to the @@ -3603,6 +4210,8 @@ extern GEOSGeometry GEOS_DLL *GEOSConcaveHullOfPolygons( * \param g The input geometry * \return A newly allocated geometry of the rotated envelope. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.6 */ extern GEOSGeometry GEOS_DLL *GEOSMinimumRotatedRectangle(const GEOSGeometry* g); @@ -3627,32 +4236,45 @@ extern GEOSGeometry GEOS_DLL *GEOSMinimumRotatedRectangle(const GEOSGeometry* g) * \return A newly allocated geometry of the MIC. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::algorithm::construct::MaximumInscribedCircle +* +* \since 3.9 */ extern GEOSGeometry GEOS_DLL *GEOSMaximumInscribedCircle( const GEOSGeometry* g, double tolerance); /** -* Constructs the "largest empty circle" (LEC) for a set of obstacle geometries, up to a -* specified tolerance. The obstacles are point and line geometries. Polygonal obstacles willl -* be treated as linear features. -* The LEC is the largest circle which has its **center** inside the boundary, -* and whose interior does not intersect with any obstacle. If no boundary is provided, the -* convex hull of the obstacles is used as the boundary. +* Constructs the "largest empty circle" (LEC) for a set of obstacle geometries +* and within a polygonal boundary, +* with accuracy to to a specified distance tolerance. +* The obstacles may be any collection of points, lines and polygons. +* The LEC is the largest circle whose interior does not intersect with any obstacle. +* and which has its **center** inside the given boundary. +* If no boundary is provided, the +* convex hull of the obstacles is used. * The LEC center is the point in the interior of the boundary which has the farthest distance from -* the obstacles (up to tolerance). The LEC is determined by the center point and a point lying on an -* obstacle indicating the circle radius. +* the obstacles (up to the given distance tolerance). +* The LEC is determined by the center point and a point indicating the circle radius +* (which will lie on an obstacle). +* \n +* To compute an LEC which lies **wholly** within a polygonal boundary, +* include the boundary of the polygon(s) as a linear obstacle. +* \n * The implementation uses a successive-approximation technique over a grid of square cells covering the obstacles and boundary. * The grid is refined using a branch-and-bound algorithm. Point containment and distance are computed in a performant * way by using spatial indexes. -* Returns a two-point linestring, with the start point at the center of the inscribed circle and the end -* on the boundary of the inscribed circle. -* \param obstacles The geometries that the LEC must fit within without covering -* \param boundary The area within which the LEC must reside +* \n +* Returns the LEC radius as a two-point linestring, with the start point at the center of the inscribed circle and the end +* on the boundary of the circle. +* +* \param obstacles The geometries that the LEC must not cross +* \param boundary The area to contain the LEC center (may be null or empty) * \param tolerance Stop the algorithm when the search area is smaller than this tolerance -* \return A newly allocated geometry of the LEC. NULL on exception. +* \return A newly allocated geometry of the LEC radius line. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::algorithm::construct::LargestEmptyCircle +* +* \since 3.9 */ extern GEOSGeometry GEOS_DLL *GEOSLargestEmptyCircle( const GEOSGeometry* obstacles, @@ -3669,6 +4291,8 @@ extern GEOSGeometry GEOS_DLL *GEOSLargestEmptyCircle( * \return A newly allocated geometry of the LEC. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::algorithm::MinimumDiameter +* +* \since 3.6 */ extern GEOSGeometry GEOS_DLL *GEOSMinimumWidth(const GEOSGeometry* g); @@ -3679,6 +4303,8 @@ extern GEOSGeometry GEOS_DLL *GEOSMinimumWidth(const GEOSGeometry* g); * \return A point that is inside the input * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::algorithm::InteriorPointArea +* +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSPointOnSurface(const GEOSGeometry* g); @@ -3688,6 +4314,8 @@ extern GEOSGeometry GEOS_DLL *GEOSPointOnSurface(const GEOSGeometry* g); * \return A point at the center of mass of the input * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::algorithm::Centroid +* +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSGetCentroid(const GEOSGeometry* g); @@ -3700,6 +4328,8 @@ extern GEOSGeometry GEOS_DLL *GEOSGetCentroid(const GEOSGeometry* g); * \return The circle geometry or NULL on exception * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::algorithm::MinimumBoundingCircle::getCircle +* +* \since 3.8 */ extern GEOSGeometry GEOS_DLL *GEOSMinimumBoundingCircle( const GEOSGeometry* g, @@ -3716,6 +4346,8 @@ extern GEOSGeometry GEOS_DLL *GEOSMinimumBoundingCircle( * * \return A newly allocated geometry. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.4 */ extern GEOSGeometry GEOS_DLL * GEOSDelaunayTriangulation( const GEOSGeometry *g, @@ -3730,16 +4362,33 @@ extern GEOSGeometry GEOS_DLL * GEOSDelaunayTriangulation( * \param g the input geometry whose rings will be used as input * \return A newly allocated geometry. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.10 */ extern GEOSGeometry GEOS_DLL * GEOSConstrainedDelaunayTriangulation( const GEOSGeometry *g); +/** Change behaviour of \ref GEOSVoronoiDiagram */ +enum GEOSVoronoiFlags +{ + /** Return only edges of the Voronoi cells, as a MultiLineString **/ + GEOS_VORONOI_ONLY_EDGES = 1, + /** Preserve order of inputs, such that the nth cell in the result corresponds + * to the nth vertex in the input. If this cannot be done, such as for inputs + * that contain repeated points, \ref GEOSVoronoiDiagram will return NULL. **/ + GEOS_VORONOI_PRESERVE_ORDER = 2 +}; + /** -* Returns the Voronoi polygons of the vertices of the given geometry. +* Returns the Voronoi polygons or edges of the vertices of the given geometry. * * \param g the input geometry whose vertices will be used as sites. -* \param tolerance snapping tolerance to use for improved robustness -* \param onlyEdges whether to return only edges of the voronoi cells +* \param tolerance snapping tolerance to use for improved robustness. A + tolerance of 0.0 specifies that no snapping will take + place. This argument can be finicky and is known to cause + the algorithm to fail in several cases. If you're using + tolerance and getting a failure, try setting it to 0.0. +* \param flags A value from the \ref GEOSVoronoiFlags enum * \param env clipping envelope for the returned diagram, automatically * determined if env is NULL. * The diagram will be clipped to the larger @@ -3747,12 +4396,14 @@ extern GEOSGeometry GEOS_DLL * GEOSConstrainedDelaunayTriangulation( * * \return A newly allocated geometry. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.5 */ extern GEOSGeometry GEOS_DLL * GEOSVoronoiDiagram( const GEOSGeometry *g, const GEOSGeometry *env, double tolerance, - int onlyEdges); + int flags); ///@} @@ -3769,6 +4420,8 @@ extern GEOSGeometry GEOS_DLL * GEOSVoronoiDiagram( * \return The noded geometry or NULL on exception * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::noding::GeometryNoder::node +* +* \since 3.4 */ extern GEOSGeometry GEOS_DLL *GEOSNode(const GEOSGeometry* g); @@ -3811,20 +4464,26 @@ extern GEOSGeometry GEOS_DLL *GEOSNode(const GEOSGeometry* g); * \return The polygonal output geometry. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::polygonize::Polygonizer +* +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSPolygonize( const GEOSGeometry * const geoms[], unsigned int ngeoms); /** -* Same polygonizing behavior as GEOSPolygonize(), but only returning results -* that are valid. +* Has the same polygonizing behavior as GEOSPolygonize(), +* but returns a result which is a valid polygonal geometry. +* The result will not contain any edge-adjacent elements. * * \param geoms Array of linear geometries to polygons. Caller retains ownersihp of both array container and objects. * \param ngeoms Size of the geoms array. * \return The polygonal output geometry. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::polygonize::Polygonizer +* \see GEOSBuildArea() +* +* \since 3.8 */ extern GEOSGeometry GEOS_DLL *GEOSPolygonize_valid( const GEOSGeometry * const geoms[], @@ -3840,6 +4499,8 @@ extern GEOSGeometry GEOS_DLL *GEOSPolygonize_valid( * \return The "cut edges" * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::polygonize::Polygonizer +* +* \since 3.1 */ extern GEOSGeometry GEOS_DLL *GEOSPolygonizer_getCutEdges( const GEOSGeometry * const geoms[], @@ -3847,7 +4508,7 @@ extern GEOSGeometry GEOS_DLL *GEOSPolygonizer_getCutEdges( /** * Perform the polygonization as GEOSPolygonize() and return the -* polygonal result as well as all extra ouputs. +* polygonal result as well as all extra outputs. * * \param[in] input A single geometry with all the input lines to polygonize. * \param[out] cuts Pointer to hold "cut edges", connected on both ends but not part of output. Caller must free. @@ -3856,6 +4517,8 @@ extern GEOSGeometry GEOS_DLL *GEOSPolygonizer_getCutEdges( * \return The polygonal valid output * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::polygonize::Polygonizer +* +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSPolygonize_full( const GEOSGeometry* input, @@ -3871,6 +4534,8 @@ extern GEOSGeometry GEOS_DLL *GEOSPolygonize_full( * \return The polygonal output * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::polygonize::BuildArea +* +* \since 3.8 */ extern GEOSGeometry GEOS_DLL *GEOSBuildArea(const GEOSGeometry* g); @@ -3892,32 +4557,57 @@ extern GEOSGeometry GEOS_DLL *GEOSBuildArea(const GEOSGeometry* g); * \param tolerance the distance tolerance to densify * \return The densified geometry, or NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.10 */ extern GEOSGeometry GEOS_DLL *GEOSDensify( const GEOSGeometry* g, double tolerance); /** -* Sews together a set of fully noded LineStrings -* removing any cardinality 2 nodes in the linework. +* Merges a set of LineStrings, +* joining them at nodes which have cardinality 2. +* Lines may have their direction reversed. + * \param g The input linework -* \return The merged linework +* \return The merged linework. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::linemerge::LineMerger +* \since 2.2 */ extern GEOSGeometry GEOS_DLL *GEOSLineMerge(const GEOSGeometry* g); /** -* Sews together a set of fully noded LineStrings -* removing any cardinality 2 nodes in the linework -* only if possible without changing order of points. +* Merges a set of LineStrings, +* joining them at nodes which have cardinality 2. +* and where the lines have the same direction. +* This means that lines do not have their direction reversed. + * \param g The input linework -* \return The merged linework +* \return The merged linework. * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::operation::linemerge::LineMerger +* +* \since 3.11 */ extern GEOSGeometry GEOS_DLL *GEOSLineMergeDirected(const GEOSGeometry* g); +/** + * Computes the line which is the section of the input LineString starting and + * ending at the given length fractions. + * \param g The input LineString + * \param start_fraction start fraction (0-1) along the length of g + * \param end_fraction end fraction (0-1) along the length of g + * \return selected substring. + * Caller is responsible for freeing with GEOSGeom_destroy() + * + * \since 3.12 + */ +extern GEOSGeometry GEOS_DLL *GEOSLineSubstring( + const GEOSGeometry* g, + double start_fraction, + double end_fraction); + /** * For geometries with coordinate sequences, reverses the order * of the sequences. Converts CCW rings to CW. Reverses direction @@ -3925,6 +4615,8 @@ extern GEOSGeometry GEOS_DLL *GEOSLineMergeDirected(const GEOSGeometry* g); * \param g The input geometry * \return The reversed geometry * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.7 */ extern GEOSGeometry GEOS_DLL *GEOSReverse(const GEOSGeometry* g); @@ -3939,6 +4631,8 @@ extern GEOSGeometry GEOS_DLL *GEOSReverse(const GEOSGeometry* g); * \return The simplified geometry * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::simplify::DouglasPeuckerSimplifier +* +* \since 3.0 */ extern GEOSGeometry GEOS_DLL *GEOSSimplify( const GEOSGeometry* g, @@ -3957,6 +4651,8 @@ extern GEOSGeometry GEOS_DLL *GEOSSimplify( * \return The simplified geometry * Caller is responsible for freeing with GEOSGeom_destroy(). * \see geos::simplify::DouglasPeuckerSimplifier +* +* \since 3.0 */ extern GEOSGeometry GEOS_DLL *GEOSTopologyPreserveSimplify( const GEOSGeometry* g, @@ -3969,6 +4665,8 @@ extern GEOSGeometry GEOS_DLL *GEOSTopologyPreserveSimplify( * \param g The input geometry * \return The distinct points * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_extractUniquePoints( const GEOSGeometry* g); @@ -3985,6 +4683,8 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_extractUniquePoints( * \param[in] level The level of precision of the Hilbert curve, up to 16 * \param[out] code Pointer to be filled in with Hilbert code result * \return 1 on success, 0 on exception. +* +* \since 3.11 */ extern int GEOS_DLL GEOSHilbertCode( const GEOSGeometry *geom, @@ -4004,6 +4704,8 @@ extern int GEOS_DLL GEOSHilbertCode( * \param userdata an optional pointer to pe passed to 'callback' as an argument * \return a copy of the input geometry with transformed coordinates. * Caller must free with GEOSGeom_destroy(). +* +* \since 3.11 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_transformXY( const GEOSGeometry* g, @@ -4011,12 +4713,25 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_transformXY( void* userdata); /** -* Snap first geometry onto second within the given tolerance. +* Snaps the vertices and segments of the first geometry to vertices of the +* second geometry within the given tolerance. +* +* Where possible, this operation tries to avoid creating invalid geometries; +* however, it does not guarantee that output geometries will be valid. It is +* the responsibility of the caller to check for and handle invalid geometries. +* +* Because too much snapping can result in invalid +* topology being created, heuristics are used to determine the number and +* location of snapped vertices that are likely safe to snap. These heuristics +* may omit some potential snaps that are otherwise within the tolerance. +* * \param input An input geometry * \param snap_target A geometry to snap the input to * \param tolerance Snapping tolerance -* \return The snapped verion of the input. NULL on exception. +* \return The snapped version of the input. NULL on exception. * Caller is responsible for freeing with GEOSGeom_destroy(). +* +* \since 3.3 */ extern GEOSGeometry GEOS_DLL *GEOSSnap( const GEOSGeometry* input, @@ -4049,6 +4764,8 @@ extern GEOSGeometry GEOS_DLL *GEOSSnap( * \return The precision reduced result. * Caller must free with GEOSGeom_destroy() * NULL on exception. +* +* \since 3.6 */ extern GEOSGeometry GEOS_DLL *GEOSGeom_setPrecision( const GEOSGeometry *g, @@ -4069,6 +4786,7 @@ extern GEOSGeometry GEOS_DLL *GEOSGeom_setPrecision( * \param g2 Input geometry * \returns 1 on true, 0 on false, 2 on exception * \see geos::geom::Geometry::disjoint +* \since 2.2 */ extern char GEOS_DLL GEOSDisjoint(const GEOSGeometry* g1, const GEOSGeometry* g2); @@ -4079,6 +4797,7 @@ extern char GEOS_DLL GEOSDisjoint(const GEOSGeometry* g1, const GEOSGeometry* g2 * \param g2 Input geometry * \returns 1 on true, 0 on false, 2 on exception * \see geos::geom::Geometry::touches +* \since 2.2 */ extern char GEOS_DLL GEOSTouches(const GEOSGeometry* g1, const GEOSGeometry* g2); @@ -4088,16 +4807,18 @@ extern char GEOS_DLL GEOSTouches(const GEOSGeometry* g1, const GEOSGeometry* g2) * \param g2 Input geometry * \returns 1 on true, 0 on false, 2 on exception * \see geos::geom::Geometry::intersects +* \since 2.2 */ extern char GEOS_DLL GEOSIntersects(const GEOSGeometry* g1, const GEOSGeometry* g2); /** -* True if geometries interiors interact but their boundares do not. +* True if geometries interiors interact but their boundaries do not. * Most useful for finding line crosses cases. * \param g1 Input geometry * \param g2 Input geometry * \returns 1 on true, 0 on false, 2 on exception * \see geos::geom::Geometry::crosses +* \since 2.2 */ extern char GEOS_DLL GEOSCrosses(const GEOSGeometry* g1, const GEOSGeometry* g2); @@ -4108,6 +4829,7 @@ extern char GEOS_DLL GEOSCrosses(const GEOSGeometry* g1, const GEOSGeometry* g2) * \param g2 Input geometry * \returns 1 on true, 0 on false, 2 on exception * \see geos::geom::Geometry::within +* \since 2.2 */ extern char GEOS_DLL GEOSWithin(const GEOSGeometry* g1, const GEOSGeometry* g2); @@ -4117,6 +4839,7 @@ extern char GEOS_DLL GEOSWithin(const GEOSGeometry* g1, const GEOSGeometry* g2); * \param g2 Input geometry * \returns 1 on true, 0 on false, 2 on exception * \see geos::geom::Geometry::contains +* \since 2.2 */ extern char GEOS_DLL GEOSContains(const GEOSGeometry* g1, const GEOSGeometry* g2); @@ -4127,15 +4850,17 @@ extern char GEOS_DLL GEOSContains(const GEOSGeometry* g1, const GEOSGeometry* g2 * \param g2 Input geometry * \returns 1 on true, 0 on false, 2 on exception * \see geos::geom::Geometry::overlaps +* \since 2.2 */ extern char GEOS_DLL GEOSOverlaps(const GEOSGeometry* g1, const GEOSGeometry* g2); /** -* True if geometries cover the same space on the place. +* True if geometries cover the same space on the plane. * \param g1 Input geometry * \param g2 Input geometry * \returns 1 on true, 0 on false, 2 on exception * \see geos::geom::Geometry::equals +* \since 2.2 */ extern char GEOS_DLL GEOSEquals(const GEOSGeometry* g1, const GEOSGeometry* g2); @@ -4146,6 +4871,8 @@ extern char GEOS_DLL GEOSEquals(const GEOSGeometry* g1, const GEOSGeometry* g2); * \param g2 Input geometry * \returns 1 on true, 0 on false, 2 on exception * \see geos::geom::Geometry::covers +* +* \since 3.3 */ extern char GEOS_DLL GEOSCovers(const GEOSGeometry* g1, const GEOSGeometry* g2); @@ -4155,7 +4882,9 @@ extern char GEOS_DLL GEOSCovers(const GEOSGeometry* g1, const GEOSGeometry* g2); * \param g1 Input geometry * \param g2 Input geometry * \returns 1 on true, 0 on false, 2 on exception -* \see geos::geom::Geometry::coveredby +* \see geos::geom::Geometry::coveredBy +* +* \since 3.3 */ extern char GEOS_DLL GEOSCoveredBy(const GEOSGeometry* g1, const GEOSGeometry* g2); @@ -4164,6 +4893,8 @@ extern char GEOS_DLL GEOSCoveredBy(const GEOSGeometry* g1, const GEOSGeometry* g * checking that they have identical structure * and that each vertex of g2 is * within the distance tolerance of the corresponding vertex in g1. +* Z and M values are ignored by GEOSEqualsExact, and this function may return true +* for inputs with different dimensionality. * Unlike GEOSEquals(), geometries that are topologically equivalent but have different * representations (e.g., LINESTRING (0 0, 1 1) and MULTILINESTRING ((0 0, 1 1)) ) are not * considered equal by GEOSEqualsExact(). @@ -4172,6 +4903,7 @@ extern char GEOS_DLL GEOSCoveredBy(const GEOSGeometry* g1, const GEOSGeometry* g * \param tolerance Tolerance to determine vertex equality * \returns 1 on true, 0 on false, 2 on exception * \see GEOSNormalize() +* \since 3.0 */ extern char GEOS_DLL GEOSEqualsExact( const GEOSGeometry* g1, @@ -4179,48 +4911,72 @@ extern char GEOS_DLL GEOSEqualsExact( double tolerance); /** -* Calculate the DE9IM pattern for this geometry pair -* and compare against the provided pattern to check for -* consistency. If the result and pattern are consistent -* return true. The pattern may include glob "*" characters -* for portions that are allowed to match any value. + * Determine pointwise equivalence of two geometries by checking + * that the structure, ordering, and values of all vertices are + * identical in all dimensions. NaN values are considered to be + * equal to other NaN values. + * +* \param g1 Input geometry +* \param g2 Input geometry +* \returns 1 on true, 0 on false, 2 on exception +* +* \since 3.12 +*/ +extern char GEOS_DLL GEOSEqualsIdentical( + const GEOSGeometry* g1, + const GEOSGeometry* g2); + +/** +* Calculate the [DE9IM](https://en.wikipedia.org/wiki/DE-9IM) string for a geometry pair +* and compare against a DE9IM pattern to check for +* consistency. +* If the result matches the pattern return true. +* The pattern is a 9-character string +* containing symbols in the set "012TF*". +* "012F" match the corresponding dimension symbol; +* "T" matches any non-empty dimension; "*" matches any dimension. * \see geos::geom::Geometry::relate * \param g1 First geometry in pair * \param g2 Second geometry in pair -* \param pat DE9IM pattern to check +* \param imPattern DE9IM pattern to match * \return 1 on true, 0 on false, 2 on exception +* \since 2.2 */ extern char GEOS_DLL GEOSRelatePattern( const GEOSGeometry* g1, const GEOSGeometry* g2, - const char *pat); + const char *imPattern); /** -* Calculate and return the DE9IM pattern for this geometry pair. +* Calculate the [DE9IM](https://en.wikipedia.org/wiki/DE-9IM) string for this geometry pair. +* The result is a 9-character string containing dimension symbols in the set "012F". * \see geos::geom::Geometry::relate * \param g1 First geometry in pair * \param g2 Second geometry in pair * \return DE9IM string. Caller is responsible for freeing with GEOSFree(). * NULL on exception +* \since 2.2 */ extern char GEOS_DLL *GEOSRelate( const GEOSGeometry* g1, const GEOSGeometry* g2); /** -* Compare two DE9IM patterns and return true if they +* Compare a [DE9IM](https://en.wikipedia.org/wiki/DE-9IM) string to a pattern and return true if they * are consistent. -* \param mat Complete DE9IM string (does not have "*") -* \param pat Pattern to match to (may contain "*") +* \param intMatrix DE9IM string (contains symbols "012F") +* \param imPattern Pattern to match to (may also contain symbols "T" and "*") * \return 1 on true, 0 on false, 2 on exception +* +* \since 3.3 */ extern char GEOS_DLL GEOSRelatePatternMatch( - const char *mat, - const char *pat); + const char *intMatrix, + const char *imPattern); /** -* Calculate and return the DE9IM pattern for this geometry pair. -* Apply the supplied \ref GEOSRelateBoundaryNodeRules. +* Calculate the [DE9IM](https://en.wikipedia.org/wiki/DE-9IM) string for this geometry pair, +* using the supplied \ref GEOSRelateBoundaryNodeRules. * \see geos::geom::Geometry::relate * \see geos::algorithm::BoundaryNodeRule * \param g1 First geometry in pair @@ -4228,6 +4984,8 @@ extern char GEOS_DLL GEOSRelatePatternMatch( * \param bnr A member of the \ref GEOSRelateBoundaryNodeRules enum * \return DE9IM string. Caller is responsible for freeing with GEOSFree(). * NULL on exception +* +* \since 3.3 */ extern char GEOS_DLL *GEOSRelateBoundaryNodeRule( const GEOSGeometry* g1, @@ -4260,6 +5018,8 @@ extern char GEOS_DLL *GEOSRelateBoundaryNodeRule( * \param g The base geometry to wrap in a prepared geometry. * \return A prepared geometry. Caller is responsible for freeing with * GEOSPreparedGeom_destroy() +* +* \since 3.1 */ extern const GEOSPreparedGeometry GEOS_DLL *GEOSPrepare(const GEOSGeometry* g); @@ -4268,28 +5028,50 @@ extern const GEOSPreparedGeometry GEOS_DLL *GEOSPrepare(const GEOSGeometry* g); * Caller must separately free the base \ref GEOSGeometry used * to create the prepared geometry. * \param g Prepared geometry to destroy. +* +* \since 3.1 */ extern void GEOS_DLL GEOSPreparedGeom_destroy(const GEOSPreparedGeometry* g); /** -* Using a \ref GEOSPreparedGeometry do a high performance +* Use a \ref GEOSPreparedGeometry do a high performance * calculation of whether the provided geometry is contained. * \param pg1 The prepared geometry * \param g2 The geometry to test * \returns 1 on true, 0 on false, 2 on exception * \see GEOSContains +* +* \since 3.1 */ extern char GEOS_DLL GEOSPreparedContains( const GEOSPreparedGeometry* pg1, const GEOSGeometry* g2); /** -* Using a \ref GEOSPreparedGeometry do a high performance +* Use a \ref GEOSPreparedGeometry do a high performance +* calculation of whether the provided point is contained. +* \param pg1 The prepared geometry +* \param x x coordinate of point to test +* \param y y coordinate of point to test +* \returns 1 on true, 0 on false, 2 on exception +* \see GEOSContains +* +* \since 3.12 +*/ +extern char GEOS_DLL GEOSPreparedContainsXY( + const GEOSPreparedGeometry* pg1, + double x, + double y); + +/** +* Use a \ref GEOSPreparedGeometry do a high performance * calculation of whether the provided geometry is contained properly. * \param pg1 The prepared geometry * \param g2 The geometry to test * \returns 1 on true, 0 on false, 2 on exception * \see GEOSContainsProperly +* +* \since 3.1 */ extern char GEOS_DLL GEOSPreparedContainsProperly( const GEOSPreparedGeometry* pg1, @@ -4302,6 +5084,8 @@ extern char GEOS_DLL GEOSPreparedContainsProperly( * \param g2 The geometry to test * \returns 1 on true, 0 on false, 2 on exception * \see GEOSCoveredBy +* +* \since 3.3 */ extern char GEOS_DLL GEOSPreparedCoveredBy( const GEOSPreparedGeometry* pg1, @@ -4314,6 +5098,8 @@ extern char GEOS_DLL GEOSPreparedCoveredBy( * \param g2 The geometry to test * \returns 1 on true, 0 on false, 2 on exception * \see GEOSCovers +* +* \since 3.1 */ extern char GEOS_DLL GEOSPreparedCovers( const GEOSPreparedGeometry* pg1, @@ -4326,73 +5112,139 @@ extern char GEOS_DLL GEOSPreparedCovers( * \param g2 The geometry to test * \returns 1 on true, 0 on false, 2 on exception * \see GEOSCrosses +* +* \since 3.3 */ extern char GEOS_DLL GEOSPreparedCrosses( const GEOSPreparedGeometry* pg1, const GEOSGeometry* g2); /** -* Using a \ref GEOSPreparedDisjoint do a high performance +* Use a \ref GEOSPreparedGeometry do a high performance * calculation of whether the provided geometry is disjoint. * \param pg1 The prepared geometry * \param g2 The geometry to test * \returns 1 on true, 0 on false, 2 on exception -* \see GEOSDisjoin +* \see GEOSDisjoint +* +* \since 3.3 */ extern char GEOS_DLL GEOSPreparedDisjoint( const GEOSPreparedGeometry* pg1, const GEOSGeometry* g2); /** -* Using a \ref GEOSPreparedDisjoint do a high performance -* calculation of whether the provided geometry is disjoint. +* Use a \ref GEOSPreparedGeometry do a high performance +* calculation of whether the provided geometry intersects. * \param pg1 The prepared geometry * \param g2 The geometry to test * \returns 1 on true, 0 on false, 2 on exception -* \see GEOSDisjoin +* \see GEOSIntersects +* +* \since 3.1 */ extern char GEOS_DLL GEOSPreparedIntersects( const GEOSPreparedGeometry* pg1, const GEOSGeometry* g2); /** -* Using a \ref GEOSPreparedDisjoint do a high performance +* Use a \ref GEOSPreparedGeometry do a high performance +* calculation of whether the provided point intersects. +* \param pg1 The prepared geometry +* \param x x coordinate of point to test +* \param y y coordinate of point to test +* \returns 1 on true, 0 on false, 2 on exception +* \see GEOSIntersects +* +* \since 3.12 +*/ +extern char GEOS_DLL GEOSPreparedIntersectsXY( + const GEOSPreparedGeometry* pg1, + double x, + double y); + +/** +* Use a \ref GEOSPreparedGeometry do a high performance * calculation of whether the provided geometry overlaps. * \param pg1 The prepared geometry * \param g2 The geometry to test * \returns 1 on true, 0 on false, 2 on exception * \see GEOSOverlaps +* +* \since 3.3 */ extern char GEOS_DLL GEOSPreparedOverlaps( const GEOSPreparedGeometry* pg1, const GEOSGeometry* g2); /** -* Using a \ref GEOSPreparedDisjoint do a high performance +* Use a \ref GEOSPreparedGeometry do a high performance * calculation of whether the provided geometry touches. * \param pg1 The prepared geometry * \param g2 The geometry to test * \returns 1 on true, 0 on false, 2 on exception * \see GEOSTouches +* +* \since 3.3 */ extern char GEOS_DLL GEOSPreparedTouches( const GEOSPreparedGeometry* pg1, const GEOSGeometry* g2); /** -* Using a \ref GEOSPreparedDisjoint do a high performance +* Use a \ref GEOSPreparedGeometry do a high performance * calculation of whether the provided geometry is within. * \param pg1 The prepared geometry * \param g2 The geometry to test * \returns 1 on true, 0 on false, 2 on exception * \see GEOSWithin +* +* \since 3.3 */ extern char GEOS_DLL GEOSPreparedWithin( const GEOSPreparedGeometry* pg1, const GEOSGeometry* g2); /** -* Using a \ref GEOSPreparedDisjoint do a high performance +* Use a \ref GEOSPreparedGeometry do a high-performance +* calculation of the [DE9IM](https://en.wikipedia.org/wiki/DE-9IM) relationship between the +* prepared and provided geometry. +* \param pg1 The prepared geometry +* \param g2 The geometry to test +* \returns The DE9IM string +* \see GEOSPrepare +* \see GEOSRelate +* \see GEOSPreparedRelatePattern +* +* \since 3.13 +*/ +extern char GEOS_DLL * GEOSPreparedRelate( + const GEOSPreparedGeometry* pg1, + const GEOSGeometry* g2); + +/** +* Use a \ref GEOSPreparedGeometry do a high-performance +* calculation of the [DE9IM](https://en.wikipedia.org/wiki/DE-9IM) relationship between the +* prepared and provided geometry, and compare the +* relationship to the provided DE9IM pattern. +* Returns true if the patterns are consistent and false otherwise. +* \param pg1 The prepared geometry +* \param g2 The geometry to test +* \param imPattern The DE9IM pattern to test +* \returns 1 on true, 0 on false, 2 on exception +* \see GEOSPrepare +* \see GEOSRelatePattern +* \see GEOSPreparedRelate +* +* \since 3.13 +*/ +extern char GEOS_DLL GEOSPreparedRelatePattern( + const GEOSPreparedGeometry* pg1, + const GEOSGeometry* g2, + const char* imPattern); + +/** +* Use a \ref GEOSPreparedGeometry do a high performance * calculation to find the nearest points between the * prepared and provided geometry. * \param pg1 The prepared geometry @@ -4400,14 +5252,16 @@ extern char GEOS_DLL GEOSPreparedWithin( * \returns A coordinate sequence containing the nearest points, or NULL on exception. * The first point in the sequence is from the prepared geometry, and the * seconds is from the other argument. +* +* \since 3.9 */ extern GEOSCoordSequence GEOS_DLL *GEOSPreparedNearestPoints( const GEOSPreparedGeometry* pg1, const GEOSGeometry* g2); /** -* Using a \ref GEOSPreparedDistance do a high performance -* calculation to find the distance points between the +* Use a \ref GEOSPreparedGeometry do a high performance +* calculation to find the distance between the * prepared and provided geometry. Useful for situations where * one geometry is large and static and needs to be tested * against a large number of other geometries. @@ -4415,6 +5269,8 @@ extern GEOSCoordSequence GEOS_DLL *GEOSPreparedNearestPoints( * \param[in] g2 The geometry to test * \param[out] dist Pointer to store the result in * \return 1 on success +* +* \since 3.9 */ extern int GEOS_DLL GEOSPreparedDistance( const GEOSPreparedGeometry* pg1, @@ -4422,7 +5278,7 @@ extern int GEOS_DLL GEOSPreparedDistance( double *dist); /** -* Using a \ref GEOSPreparedDistanceWithin do a high performance +* Use a \ref GEOSPreparedGeometry do a high performance * calculation to find whether the prepared and provided geometry * are within the given max distance. * Useful for situations where @@ -4432,6 +5288,8 @@ extern int GEOS_DLL GEOSPreparedDistance( * \param g2 The geometry to test * \param dist The max distance * \return 1 on success +* +* \since 3.10 */ extern char GEOS_DLL GEOSPreparedDistanceWithin( const GEOSPreparedGeometry* pg1, @@ -4456,9 +5314,25 @@ extern char GEOS_DLL GEOSPreparedDistanceWithin( * The minimum recommended capacity value is 4. * If unsure, use a default node capacity of 10. * \return a pointer to the created tree +* +* \since 3.2 */ extern GEOSSTRtree GEOS_DLL *GEOSSTRtree_create(size_t nodeCapacity); +/** +* Construct an STRtree from items that have been inserted. Once constructed, +* no more items may be inserted into the tree. Functions that require a +* constructed tree will build it automatically, so there is no need to call +* `GEOSSTRtree_build` unless it is desired to explicitly construct the tree +* in a certain section of code or using a certain thread. +* +* \param tree the \ref GEOSSTRtree to apply the build to +* \return 1 on success, 0 on error +* +* \since 3.12 +*/ +extern int GEOS_DLL GEOSSTRtree_build(GEOSSTRtree *tree); + /** * Insert an item into an \ref GEOSSTRtree * @@ -4468,6 +5342,8 @@ extern GEOSSTRtree GEOS_DLL *GEOSSTRtree_create(size_t nodeCapacity); * must be retained until the tree is destroyed. * \param item the item to insert into the tree * \note The tree does **not** take ownership of the geometry or the item. +* +* \since 3.2 */ extern void GEOS_DLL GEOSSTRtree_insert( GEOSSTRtree *tree, @@ -4475,14 +5351,18 @@ extern void GEOS_DLL GEOSSTRtree_insert( void *item); /** -* Query an \ref GEOSSTRtree for items intersecting a specified envelope +* Query a \ref GEOSSTRtree for items intersecting a specified envelope. +* The tree will automatically be constructed if necessary, after which +* no more items may be added. * * \param tree the \ref GEOSSTRtree to search * \param g a GEOSGeomety from which a query envelope will be extracted * \param callback a function to be executed for each item in the tree whose envelope intersects * the envelope of 'g'. The callback function should take two parameters: a void * pointer representing the located item in the tree, and a void userdata pointer. -* \param userdata an optional pointer to pe passed to 'callback' as an argument +* \param userdata an optional pointer to pe passed to `callback` as an argument +* +* \since 3.2 */ extern void GEOS_DLL GEOSSTRtree_query( GEOSSTRtree *tree, @@ -4494,11 +5374,15 @@ extern void GEOS_DLL GEOSSTRtree_query( * Returns the nearest item in the \ref GEOSSTRtree to the supplied geometry. * All items in the tree MUST be of type \ref GEOSGeometry. * If this is not the case, use GEOSSTRtree_nearest_generic() instead. +* The tree will automatically be constructed if necessary, after which +* no more items may be added. * * \param tree the \ref GEOSSTRtree to search * \param geom the geometry with which the tree should be queried * \return a const pointer to the nearest \ref GEOSGeometry in the tree to 'geom', or NULL in * case of exception +* +* \since 3.6 */ extern const GEOSGeometry GEOS_DLL *GEOSSTRtree_nearest( GEOSSTRtree *tree, @@ -4506,6 +5390,8 @@ extern const GEOSGeometry GEOS_DLL *GEOSSTRtree_nearest( /** * Returns the nearest item in the \ref GEOSSTRtree to the supplied item +* The tree will automatically be constructed if necessary, after which +* no more items may be added. * * \param tree the STRtree to search * \param item the item with which the tree should be queried @@ -4513,12 +5399,14 @@ extern const GEOSGeometry GEOS_DLL *GEOSSTRtree_nearest( * \param distancefn a function that can compute the distance between two items * in the STRtree. The function should return zero in case of error, * and should store the computed distance to the location pointed to by -* the 'distance' argument. The computed distance between two items +* the `distance` argument. The computed distance between two items * must not exceed the Cartesian distance between their envelopes. -* \param userdata optional pointer to arbitrary data; will be passed to distancefn +* \param userdata optional pointer to arbitrary data; will be passed to `distancefn` * each time it is called. -* \return a const pointer to the nearest item in the tree to 'item', or NULL in +* \return a const pointer to the nearest item in the tree to `item`, or NULL in * case of exception +* +* \since 3.6 */ extern const void GEOS_DLL *GEOSSTRtree_nearest_generic( GEOSSTRtree *tree, @@ -4529,10 +5417,13 @@ extern const void GEOS_DLL *GEOSSTRtree_nearest_generic( /** * Iterate over all items in the \ref GEOSSTRtree. +* This will not cause the tree to be constructed. * * \param tree the STRtree over which to iterate * \param callback a function to be executed for each item in the tree. * \param userdata payload to pass the callback function. +* +* \since 3.2 */ extern void GEOS_DLL GEOSSTRtree_iterate( GEOSSTRtree *tree, @@ -4541,6 +5432,8 @@ extern void GEOS_DLL GEOSSTRtree_iterate( /** * Removes an item from the \ref GEOSSTRtree + * The tree will automatically be constructed if necessary, after which + * no more items may be added. * * \param tree the STRtree from which to remove an item * \param g the envelope of the item to remove @@ -4548,6 +5441,8 @@ extern void GEOS_DLL GEOSSTRtree_iterate( * \return 0 if the item was not removed; * 1 if the item was removed; * 2 if an exception occurred + * + * \since 3.2 */ extern char GEOS_DLL GEOSSTRtree_remove( GEOSSTRtree *tree, @@ -4559,6 +5454,10 @@ extern char GEOS_DLL GEOSSTRtree_remove( * Only the tree is freed. The geometries and items fed into * GEOSSTRtree_insert() are not owned by the tree, and are * still left to the caller to manage. +* +* \param tree the \ref GEOSSTRtree to destroy +* +* \since 3.2 */ extern void GEOS_DLL GEOSSTRtree_destroy(GEOSSTRtree *tree); @@ -4585,6 +5484,8 @@ extern void GEOS_DLL GEOSSTRtree_destroy(GEOSSTRtree *tree); * \param[out] cy y-coordinate of intersection point * * \return 0 on error, 1 on success, -1 if segments do not intersect +* +* \since 3.7 */ extern int GEOS_DLL GEOSSegmentIntersection( double ax0, double ay0, @@ -4605,6 +5506,8 @@ extern int GEOS_DLL GEOSSegmentIntersection( * \return -1 if reaching P takes a counter-clockwise (left) turn, * 1 if reaching P takes a clockwise (right) turn, * 0 if P is collinear with A-B +* +* \since 3.3 */ extern int GEOS_DLL GEOSOrientationIndex( double Ax, double Ay, @@ -4624,12 +5527,14 @@ extern int GEOS_DLL GEOSOrientationIndex( /** * Allocate a new \ref GEOSWKTReader. * \returns a new reader. Caller must free with GEOSWKTReader_destroy() +* \since 3.0 */ extern GEOSWKTReader GEOS_DLL *GEOSWKTReader_create(void); /** * Free the memory associated with a \ref GEOSWKTReader. * \param reader The reader to destroy. +* \since 3.0 */ extern void GEOS_DLL GEOSWKTReader_destroy(GEOSWKTReader* reader); @@ -4639,6 +5544,7 @@ extern void GEOS_DLL GEOSWKTReader_destroy(GEOSWKTReader* reader); * \param reader A WKT reader object, caller retains ownership * \param wkt The WKT string to parse, caller retains ownership * \return A \ref GEOSGeometry, caller to free with GEOSGeom_destroy()) +* \since 3.0 */ extern GEOSGeometry GEOS_DLL *GEOSWKTReader_read( GEOSWKTReader* reader, @@ -4649,6 +5555,8 @@ extern GEOSGeometry GEOS_DLL *GEOSWKTReader_read( * in the input (currently just unclosed rings) while reading. * \param reader A WKT reader object, caller retains ownership * \param doFix Set to 1 to repair, 0 for no repair (default). +* +* \since 3.11 */ extern void GEOS_DLL GEOSWKTReader_setFixStructure( GEOSWKTReader *reader, @@ -4659,12 +5567,14 @@ extern void GEOS_DLL GEOSWKTReader_setFixStructure( /** * Allocate a new \ref GEOSWKTWriter. * \returns a new writer. Caller must free with GEOSWKTWriter_destroy() +* \since 3.0 */ extern GEOSWKTWriter GEOS_DLL *GEOSWKTWriter_create(void); /** * Free the memory associated with a \ref GEOSWKTWriter. * \param writer The writer to destroy. +* \since 3.0 */ extern void GEOS_DLL GEOSWKTWriter_destroy( GEOSWKTWriter* writer); @@ -4676,6 +5586,7 @@ extern void GEOS_DLL GEOSWKTWriter_destroy( * \param g Input geometry * \return A newly allocated string containing the WKT output or NULL on exception. * Caller must free with GEOSFree() +* \since 3.0 */ extern char GEOS_DLL *GEOSWKTWriter_write( GEOSWKTWriter* writer, @@ -4684,10 +5595,16 @@ extern char GEOS_DLL *GEOSWKTWriter_write( /** * Sets the number trimming option on a \ref GEOSWKTWriter. * With trim set to 1, the writer will strip trailing 0's from -* the output coordinates. With 0, all coordinates will be +* the output coordinates. With 1 (trimming enabled), big and small +* absolute coordinates will use scientific notation, otherwise +* positional notation is used; see \ref GEOS_printDouble for details. +* With 0 (trimming disabled), all coordinates will be * padded with 0's out to the rounding precision. +* Default since GEOS 3.12 is with trim set to 1 for 'on'. * \param writer A \ref GEOSWKTWriter. -* \param trim The trimming behaviour to set, 1 for 'on', 0 for 'off', default 'off' +* \param trim The trimming behaviour to set, 1 for 'on', 0 for 'off' +* +* \since 3.3 */ extern void GEOS_DLL GEOSWKTWriter_setTrim( GEOSWKTWriter *writer, @@ -4698,16 +5615,20 @@ extern void GEOS_DLL GEOSWKTWriter_setTrim( * WKT. * \param writer A \ref GEOSWKTWriter. * \param precision The desired precision, default 16. +* +* \since 3.3 */ extern void GEOS_DLL GEOSWKTWriter_setRoundingPrecision( GEOSWKTWriter *writer, int precision); /** -* Sets whether or not to write out XY or XYZ coordinates. -* Legal values are 2 or 3. +* Set the output dimensionality of the writer. Either +* 2, 3, or 4 dimensions. Default since GEOS 3.12 is 4. * \param writer A \ref GEOSWKTWriter. -* \param dim The desired dimension, default 2. +* \param dim The dimensionality desired. +* +* \since 3.3 */ extern void GEOS_DLL GEOSWKTWriter_setOutputDimension( GEOSWKTWriter *writer, @@ -4717,15 +5638,20 @@ extern void GEOS_DLL GEOSWKTWriter_setOutputDimension( * Reads the current output dimension from a \ref GEOSWKTWriter. * \param writer A \ref GEOSWKTWriter. * \return The current dimension. +* +* \since 3.3 */ extern int GEOS_DLL GEOSWKTWriter_getOutputDimension(GEOSWKTWriter *writer); /** * Sets the format for 3D outputs. The "old 3D" format does not -* include a dimensionality tag, eg. "POINT(1 2 3)" while the new (ISO) -* format does includes a tag, eg "POINT Z (1 2 3)". +* include a Z dimension tag, e.g. "POINT (1 2 3)", except for XYM, +* e.g. "POINT M (1 2 3)". Geometries with XYZM coordinates do not add +* any dimensionality tags, e.g. "POINT (1 2 3 4)". * \param writer A \ref GEOSWKTWriter. * \param useOld3D True to use the old format, false is the default. +* +* \since 3.3 */ extern void GEOS_DLL GEOSWKTWriter_setOld3D( GEOSWKTWriter *writer, @@ -4742,12 +5668,14 @@ extern void GEOS_DLL GEOSWKTWriter_setOld3D( /** * Allocate a new \ref GEOSWKBReader. * \returns a new reader. Caller must free with GEOSWKBReader_destroy() +* \since 3.0 */ extern GEOSWKBReader GEOS_DLL *GEOSWKBReader_create(void); /** * Free the memory associated with a \ref GEOSWKBReader. * \param reader The reader to destroy. +* \since 3.0 */ extern void GEOS_DLL GEOSWKBReader_destroy( GEOSWKBReader* reader); @@ -4757,6 +5685,8 @@ extern void GEOS_DLL GEOSWKBReader_destroy( * in the input (currently just unclosed rings) while reading. * \param reader A WKB reader object, caller retains ownership * \param doFix Set to 1 to repair, 0 for no repair (default). +* +* \since 3.11 */ extern void GEOS_DLL GEOSWKBReader_setFixStructure( GEOSWKBReader *reader, @@ -4768,6 +5698,7 @@ extern void GEOS_DLL GEOSWKBReader_setFixStructure( * \param wkb A pointer to the buffer to read from * \param size The number of bytes of data in the buffer * \return A \ref GEOSGeometry built from the WKB, or NULL on exception. +* \since 3.0 */ extern GEOSGeometry GEOS_DLL *GEOSWKBReader_read( GEOSWKBReader* reader, @@ -4780,6 +5711,7 @@ extern GEOSGeometry GEOS_DLL *GEOSWKBReader_read( * \param hex A pointer to the buffer to read from * \param size The number of bytes of data in the buffer * \return A \ref GEOSGeometry built from the HEX WKB, or NULL on exception. +* \since 3.0 */ extern GEOSGeometry GEOS_DLL *GEOSWKBReader_readHEX( GEOSWKBReader* reader, @@ -4791,12 +5723,14 @@ extern GEOSGeometry GEOS_DLL *GEOSWKBReader_readHEX( /** * Allocate a new \ref GEOSWKBWriter. * \returns a new writer. Caller must free with GEOSWKBWriter_destroy() +* \since 3.0 */ extern GEOSWKBWriter GEOS_DLL *GEOSWKBWriter_create(void); /** * Free the memory associated with a \ref GEOSWKBWriter. * \param writer The writer to destroy. +* \since 3.0 */ extern void GEOS_DLL GEOSWKBWriter_destroy(GEOSWKBWriter* writer); @@ -4807,6 +5741,7 @@ extern void GEOS_DLL GEOSWKBWriter_destroy(GEOSWKBWriter* writer); * \param g Geometry to convert to WKB * \param size Pointer to write the size of the final output WKB to * \return The WKB representation. Caller must free with GEOSFree() +* \since 3.0 */ extern unsigned char GEOS_DLL *GEOSWKBWriter_write( GEOSWKBWriter* writer, @@ -4820,6 +5755,7 @@ extern unsigned char GEOS_DLL *GEOSWKBWriter_write( * \param g Geometry to convert to WKB * \param size Pointer to write the size of the final output WKB to * \return The HEX WKB representation. Caller must free with GEOSFree() +* \since 3.0 */ extern unsigned char GEOS_DLL *GEOSWKBWriter_writeHEX( GEOSWKBWriter* writer, @@ -4828,19 +5764,21 @@ extern unsigned char GEOS_DLL *GEOSWKBWriter_writeHEX( /** * Read the current output dimension of the writer. -* Either 2 or 3 dimensions. +* Either 2, 3, or 4 dimensions. * Return current number of dimensions. * \param writer The writer to read from. -* \return Number of dimensions (2 or 3) +* \return Number of dimensions (2, 3, or 4) +* \since 3.0 */ extern int GEOS_DLL GEOSWKBWriter_getOutputDimension( const GEOSWKBWriter* writer); /** * Set the output dimensionality of the writer. Either -* 2 or 3 dimensions. +* 2, 3, or 4 dimensions. Default since GEOS 3.12 is 4. * \param writer The writer to read from. * \param newDimension The dimensionality desired +* \since 3.0 */ extern void GEOS_DLL GEOSWKBWriter_setOutputDimension( GEOSWKBWriter* writer, @@ -4853,6 +5791,7 @@ extern void GEOS_DLL GEOSWKBWriter_setOutputDimension( * The return value is a member of \ref GEOSWKBByteOrders. * \param writer The writer to read byte order from * \return The current byte order +* \since 3.0 */ extern int GEOS_DLL GEOSWKBWriter_getByteOrder( const GEOSWKBWriter* writer); @@ -4862,6 +5801,7 @@ extern int GEOS_DLL GEOSWKBWriter_getByteOrder( * a value from \ref GEOSWKBByteOrders enum. * \param writer The writer to set byte order on * \param byteOrder Desired byte order +* \since 3.0 */ extern void GEOS_DLL GEOSWKBWriter_setByteOrder( GEOSWKBWriter* writer, @@ -4878,6 +5818,8 @@ extern void GEOS_DLL GEOSWKBWriter_setByteOrder( * The return value is a member of \ref GEOSWKBFlavors. * \param writer The writer to read flavor from * \return The current flavor +* +* \since 3.10 */ extern int GEOS_DLL GEOSWKBWriter_getFlavor( const GEOSWKBWriter* writer); @@ -4887,6 +5829,8 @@ extern int GEOS_DLL GEOSWKBWriter_getFlavor( * a value from \ref GEOSWKBFlavors enum. * \param writer The writer to set flavor on * \param flavor Desired flavor +* +* \since 3.10 */ extern void GEOS_DLL GEOSWKBWriter_setFlavor( GEOSWKBWriter* writer, @@ -4895,6 +5839,7 @@ extern void GEOS_DLL GEOSWKBWriter_setFlavor( /** * Read the current SRID embedding value from the writer. * \param writer The writer to check SRID value on +* \since 3.0 */ extern char GEOS_DLL GEOSWKBWriter_getIncludeSRID( const GEOSWKBWriter* writer); @@ -4904,6 +5849,7 @@ extern char GEOS_DLL GEOSWKBWriter_getIncludeSRID( * Many WKB readers do not support SRID values, use with caution. * \param writer The writer to set SRID output on * \param writeSRID Set to 1 to include SRID, 0 otherwise +* \since 3.0 */ extern void GEOS_DLL GEOSWKBWriter_setIncludeSRID( GEOSWKBWriter* writer, @@ -4921,22 +5867,28 @@ extern void GEOS_DLL GEOSWKBWriter_setIncludeSRID( /** * Allocate a new \ref GEOSGeoJSONReader. * \returns a new reader. Caller must free with GEOSGeoJSONReader_destroy() +* +* \since 3.10 */ extern GEOSGeoJSONReader GEOS_DLL *GEOSGeoJSONReader_create(void); /** * Free the memory associated with a \ref GEOSGeoJSONReader. * \param reader The reader to destroy. +* +* \since 3.10 */ extern void GEOS_DLL GEOSGeoJSONReader_destroy(GEOSGeoJSONReader* reader); /** -* Use a reader to parse a GeoJSON. A single geometry or feature is -* converted into a geometry. A featurecollection is converted into a -* geometrycollection. Feature properties are not read. +* Use a reader to parse a GeoJSON string. A single geometry or `Feature` is +* parsed as a geometry. A `FeatureCollection` is parsed as a +* `GeometryCollection`. Feature properties are not read. * \param reader A GeoJSON reader object, caller retains ownership * \param geojson The json string to parse, caller retains ownership * \return A \ref GEOSGeometry, caller to free with GEOSGeom_destroy()) +* +* \since 3.10 */ extern GEOSGeometry GEOS_DLL *GEOSGeoJSONReader_readGeometry( GEOSGeoJSONReader* reader, @@ -4947,12 +5899,16 @@ extern GEOSGeometry GEOS_DLL *GEOSGeoJSONReader_readGeometry( /** * Allocate a new \ref GEOSGeoJSONWriter. * \returns a new writer. Caller must free with GEOSGeoJSONWriter_destroy() +* +* \since 3.10 */ extern GEOSGeoJSONWriter GEOS_DLL *GEOSGeoJSONWriter_create(void); /** * Free the memory associated with a \ref GEOSGeoJSONWriter. * \param writer The writer to destroy. +* +* \since 3.10 */ extern void GEOS_DLL GEOSGeoJSONWriter_destroy(GEOSGeoJSONWriter* writer); @@ -4963,6 +5919,8 @@ extern void GEOS_DLL GEOSGeoJSONWriter_destroy(GEOSGeoJSONWriter* writer); * \param g The geometry to convert, caller retains ownership. * \param indent The indentation used. Use -1 for no formatting. * \return A char pointer, caller to free with GEOSFree()) +* +* \since 3.10 */ extern char GEOS_DLL *GEOSGeoJSONWriter_writeGeometry( GEOSGeoJSONWriter* writer, diff --git a/Sources/geos/src/algorithm/Angle.cpp b/Sources/geos/src/algorithm/Angle.cpp index a7a0394..7069607 100644 --- a/Sources/geos/src/algorithm/Angle.cpp +++ b/Sources/geos/src/algorithm/Angle.cpp @@ -41,8 +41,8 @@ Angle::toRadians(double angleDegrees) /* public static */ double -Angle::angle(const geom::Coordinate& p0, - const geom::Coordinate& p1) +Angle::angle(const geom::CoordinateXY& p0, + const geom::CoordinateXY& p1) { double dx = p1.x - p0.x; double dy = p1.y - p0.y; @@ -51,16 +51,16 @@ Angle::angle(const geom::Coordinate& p0, /* public static */ double -Angle::angle(const geom::Coordinate& p) +Angle::angle(const geom::CoordinateXY& p) { return atan2(p.y, p.x); } /* public static */ bool -Angle::isAcute(const geom::Coordinate& p0, - const geom::Coordinate& p1, - const geom::Coordinate& p2) +Angle::isAcute(const geom::CoordinateXY& p0, + const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2) { // relies on fact that A dot B is positive iff A ang B is acute double dx0 = p0.x - p1.x; @@ -73,9 +73,9 @@ Angle::isAcute(const geom::Coordinate& p0, /* public static */ bool -Angle::isObtuse(const geom::Coordinate& p0, - const geom::Coordinate& p1, - const geom::Coordinate& p2) +Angle::isObtuse(const geom::CoordinateXY& p0, + const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2) { // relies on fact that A dot B is negative iff A ang B is obtuse double dx0 = p0.x - p1.x; @@ -88,9 +88,9 @@ Angle::isObtuse(const geom::Coordinate& p0, /* public static */ double -Angle::angleBetween(const geom::Coordinate& tip1, - const geom::Coordinate& tail, - const geom::Coordinate& tip2) +Angle::angleBetween(const geom::CoordinateXY& tip1, + const geom::CoordinateXY& tail, + const geom::CoordinateXY& tip2) { double a1 = angle(tail, tip1); double a2 = angle(tail, tip2); @@ -100,9 +100,9 @@ Angle::angleBetween(const geom::Coordinate& tip1, /* public static */ double -Angle::angleBetweenOriented(const geom::Coordinate& tip1, - const geom::Coordinate& tail, - const geom::Coordinate& tip2) +Angle::angleBetweenOriented(const geom::CoordinateXY& tip1, + const geom::CoordinateXY& tail, + const geom::CoordinateXY& tip2) { double a1 = angle(tail, tip1); double a2 = angle(tail, tip2); @@ -120,8 +120,8 @@ Angle::angleBetweenOriented(const geom::Coordinate& tip1, /* public static */ double -Angle::interiorAngle(const geom::Coordinate& p0, const geom::Coordinate& p1, - const geom::Coordinate& p2) +Angle::interiorAngle(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2) { double anglePrev = angle(p1, p0); double angleNext = angle(p1, p2); @@ -195,7 +195,7 @@ Angle::diff(double ang1, double ang2) } if(delAngle > MATH_PI) { - delAngle = (2 * MATH_PI) - delAngle; + delAngle = PI_TIMES_2 - delAngle; } return delAngle; diff --git a/Sources/geos/src/algorithm/Area.cpp b/Sources/geos/src/algorithm/Area.cpp index 39ec5db..3d2375c 100644 --- a/Sources/geos/src/algorithm/Area.cpp +++ b/Sources/geos/src/algorithm/Area.cpp @@ -20,6 +20,12 @@ #include #include +#include +#include +#include +#include + +using geos::geom::CoordinateXY; namespace geos { namespace algorithm { // geos.algorithm @@ -74,9 +80,9 @@ Area::ofRingSigned(const geom::CoordinateSequence* ring) * Based on the Shoelace formula. * http://en.wikipedia.org/wiki/Shoelace_formula */ - geom::Coordinate p0, p1, p2; - p1 = ring->getAt(0); - p2 = ring->getAt(1); + CoordinateXY p0, p1, p2; + p1 = ring->getAt(0); + p2 = ring->getAt(1); double x0 = p1.x; p2.x -= x0; double sum = 0.0; @@ -84,14 +90,65 @@ Area::ofRingSigned(const geom::CoordinateSequence* ring) p0.y = p1.y; p1.x = p2.x; p1.y = p2.y; - p2 = ring->getAt(i + 1); + p2 = ring->getAt(i + 1); p2.x -= x0; sum += p1.x * (p0.y - p2.y); } return sum / 2.0; } +double +Area::ofClosedCurve(const geom::Curve& ring) { + if (!ring.isClosed()) { + throw util::IllegalArgumentException("Argument is not closed"); + } + + double sum = 0; + + for (std::size_t i = 0; i < ring.getNumCurves(); i++) { + const geom::SimpleCurve& section = *ring.getCurveN(i); + + if (section.isEmpty()) { + continue; + } + + const geom::CoordinateSequence& coords = *section.getCoordinatesRO(); + + if (section.isCurved()) { + for (std::size_t j = 2; j < coords.size(); j += 2) { + const CoordinateXY& p0 = coords.getAt(j-2); + const CoordinateXY& p1 = coords.getAt(j-1); + const CoordinateXY& p2 = coords.getAt(j); + double triangleArea = 0.5*(p0.x*p2.y - p2.x*p0.y); + sum += triangleArea; + + geom::CircularArc arc(p0, p1, p2); + if (arc.isLinear()) { + continue; + } + + double circularSegmentArea = arc.getArea(); + + if (algorithm::Orientation::index(p0, p2, p1) == algorithm::Orientation::CLOCKWISE) { + sum += circularSegmentArea; + } else { + sum -= circularSegmentArea; + } + } + } else { + for (std::size_t j = 1; j < coords.size(); j++) { + const CoordinateXY& p0 = coords.getAt(j-1); + const CoordinateXY& p1 = coords.getAt(j); + + double triangleArea = 0.5*(p0.x*p1.y - p1.x*p0.y); + sum += triangleArea; + } + } + } + + return std::abs(sum); +} } // namespace geos.algorithm } //namespace geos diff --git a/Sources/geos/src/algorithm/BoundaryNodeRule.cpp b/Sources/geos/src/algorithm/BoundaryNodeRule.cpp index c7417b9..393ca5e 100644 --- a/Sources/geos/src/algorithm/BoundaryNodeRule.cpp +++ b/Sources/geos/src/algorithm/BoundaryNodeRule.cpp @@ -48,7 +48,11 @@ class Mod2BoundaryNodeRule : public BoundaryNodeRule { { // the "Mod-2 Rule" return boundaryCount % 2 == 1; - } + }; + std::string + toString() const override { + return "Mod2BoundaryNodeRule"; + }; }; @@ -84,7 +88,11 @@ class EndPointBoundaryNodeRule : public BoundaryNodeRule { isInBoundary(int boundaryCount) const override { return boundaryCount > 0; - } + }; + std::string + toString() const override { + return "EndPointBoundaryNodeRule"; + }; }; /** @@ -103,6 +111,10 @@ class MultiValentEndPointBoundaryNodeRule : public BoundaryNodeRule { { return boundaryCount > 1; } + std::string + toString() const override { + return "MultiValentEndPointBoundaryNodeRule"; + }; }; /** @@ -120,6 +132,10 @@ class MonoValentEndPointBoundaryNodeRule : public BoundaryNodeRule { { return boundaryCount == 1; } + std::string + toString() const override { + return "MonoValentEndPointBoundaryNodeRule"; + }; }; Mod2BoundaryNodeRule mod2Rule; diff --git a/Sources/geos/src/algorithm/CGAlgorithmsDD.cpp b/Sources/geos/src/algorithm/CGAlgorithmsDD.cpp index 623bd17..ccfef60 100644 --- a/Sources/geos/src/algorithm/CGAlgorithmsDD.cpp +++ b/Sources/geos/src/algorithm/CGAlgorithmsDD.cpp @@ -80,11 +80,11 @@ CGAlgorithmsDD::orientationIndex(double p1x, double p1y, } -// inlining this method worsened performance slighly +// inlining this method worsened performance slightly int -CGAlgorithmsDD::orientationIndex(const Coordinate& p1, - const Coordinate& p2, - const Coordinate& q) +CGAlgorithmsDD::orientationIndex(const CoordinateXY& p1, + const CoordinateXY& p2, + const CoordinateXY& q) { return orientationIndex(p1.x, p1.y, p2.x, p2.y, q.x, q.y); @@ -113,9 +113,9 @@ CGAlgorithmsDD::signOfDet2x2(double dx1, double dy1, double dx2, double dy2) return CGAlgorithmsDD::signOfDet2x2(x1, y1, x2, y2); } -Coordinate -CGAlgorithmsDD::intersection(const Coordinate& p1, const Coordinate& p2, - const Coordinate& q1, const Coordinate& q2) +CoordinateXY +CGAlgorithmsDD::intersection(const CoordinateXY& p1, const CoordinateXY& p2, + const CoordinateXY& q1, const CoordinateXY& q2) { DD q1x(q1.x); DD q1y(q1.y); @@ -155,9 +155,12 @@ CGAlgorithmsDD::intersection(const Coordinate& p1, const Coordinate& p2, } /* public static */ -Coordinate -CGAlgorithmsDD::circumcentreDD(const Coordinate& a, const Coordinate& b, const Coordinate& c) +CoordinateXY +CGAlgorithmsDD::circumcentreDD(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c) { + if((a == b) && (a == c)){ + return a; + } DD ax = DD(a.x) - DD(c.x); DD ay = DD(a.y) - DD(c.y); DD bx = DD(b.x) - DD(c.x); diff --git a/Sources/geos/src/algorithm/Centroid.cpp b/Sources/geos/src/algorithm/Centroid.cpp index 6dec14c..727bb28 100644 --- a/Sources/geos/src/algorithm/Centroid.cpp +++ b/Sources/geos/src/algorithm/Centroid.cpp @@ -28,6 +28,8 @@ #include // for std::abs +#include "geos/util.h" + using namespace geos::geom; namespace geos { @@ -35,7 +37,7 @@ namespace algorithm { // geos.algorithm /* static public */ bool -Centroid::getCentroid(const Geometry& geom, Coordinate& pt) +Centroid::getCentroid(const Geometry& geom, CoordinateXY& pt) { Centroid cent(geom); return cent.getCentroid(pt); @@ -43,7 +45,7 @@ Centroid::getCentroid(const Geometry& geom, Coordinate& pt) /* public */ bool -Centroid::getCentroid(Coordinate& cent) const +Centroid::getCentroid(CoordinateXY& cent) const { if(std::abs(areasum2) > 0.0) { cent.x = cg3.x / 3 / areasum2; @@ -68,6 +70,8 @@ Centroid::getCentroid(Coordinate& cent) const void Centroid::add(const Geometry& geom) { + util::ensureNoCurvedComponents(geom); + if(geom.isEmpty()) { return; } @@ -90,9 +94,9 @@ Centroid::add(const Geometry& geom) /* private */ void -Centroid::setAreaBasePoint(const Coordinate& basePt) +Centroid::setAreaBasePoint(const CoordinateXY& basePt) { - areaBasePt.reset(new Coordinate(basePt)); + areaBasePt.reset(new CoordinateXY(basePt)); } /* private */ @@ -111,11 +115,11 @@ Centroid::addShell(const CoordinateSequence& pts) { std::size_t len = pts.size(); if(len > 0) { - setAreaBasePoint(pts[0]); + setAreaBasePoint(pts.getAt(0)); } bool isPositiveArea = ! Orientation::isCCW(&pts); for(std::size_t i = 0; i < len - 1; ++i) { - addTriangle(*areaBasePt, pts[i], pts[i + 1], isPositiveArea); + addTriangle(*areaBasePt, pts.getAt(i), pts.getAt(i + 1), isPositiveArea); } addLineSegments(pts); } @@ -124,16 +128,20 @@ Centroid::addShell(const CoordinateSequence& pts) void Centroid::addHole(const CoordinateSequence& pts) { + if (pts.isEmpty()) { + return; + } + bool isPositiveArea = Orientation::isCCW(&pts); for(std::size_t i = 0, e = pts.size() - 1; i < e; ++i) { - addTriangle(*areaBasePt, pts[i], pts[i + 1], isPositiveArea); + addTriangle(*areaBasePt, pts.getAt(i), pts.getAt(i + 1), isPositiveArea); } addLineSegments(pts); } /* private */ void -Centroid::addTriangle(const Coordinate& p0, const Coordinate& p1, const Coordinate& p2, bool isPositiveArea) +Centroid::addTriangle(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2, bool isPositiveArea) { double sign = (isPositiveArea) ? 1.0 : -1.0; centroid3(p0, p1, p2, triangleCent3); @@ -145,7 +153,7 @@ Centroid::addTriangle(const Coordinate& p0, const Coordinate& p1, const Coordina /* static private */ void -Centroid::centroid3(const Coordinate& p1, const Coordinate& p2, const Coordinate& p3, Coordinate& c) +Centroid::centroid3(const CoordinateXY& p1, const CoordinateXY& p2, const CoordinateXY& p3, CoordinateXY& c) { c.x = p1.x + p2.x + p3.x; c.y = p1.y + p2.y + p3.y; @@ -154,7 +162,7 @@ Centroid::centroid3(const Coordinate& p1, const Coordinate& p2, const Coordinate /* static private */ double -Centroid::area2(const Coordinate& p1, const Coordinate& p2, const Coordinate& p3) +Centroid::area2(const CoordinateXY& p1, const CoordinateXY& p2, const CoordinateXY& p3) { return (p2.x - p1.x) * (p3.y - p1.y) - @@ -168,16 +176,16 @@ Centroid::addLineSegments(const CoordinateSequence& pts) std::size_t npts = pts.size(); double lineLen = 0.0; for(std::size_t i = 0; i < npts - 1; i++) { - double segmentLen = pts[i].distance(pts[i + 1]); + double segmentLen = pts.getAt(i).distance(pts.getAt(i + 1)); if(segmentLen == 0.0) { continue; } lineLen += segmentLen; - double midx = (pts[i].x + pts[i + 1].x) / 2; + double midx = (pts.getAt(i).x + pts.getAt(i + 1).x) / 2; lineCentSum.x += segmentLen * midx; - double midy = (pts[i].y + pts[i + 1].y) / 2; + double midy = (pts.getAt(i).y + pts.getAt(i + 1).y) / 2; lineCentSum.y += segmentLen * midy; } totalLength += lineLen; @@ -188,7 +196,7 @@ Centroid::addLineSegments(const CoordinateSequence& pts) /* private */ void -Centroid::addPoint(const Coordinate& pt) +Centroid::addPoint(const CoordinateXY& pt) { ptCount += 1; ptCentSum.x += pt.x; diff --git a/Sources/geos/src/algorithm/CircularArcs.cpp b/Sources/geos/src/algorithm/CircularArcs.cpp new file mode 100644 index 0000000..949f185 --- /dev/null +++ b/Sources/geos/src/algorithm/CircularArcs.cpp @@ -0,0 +1,124 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include +#include + +using geos::geom::CoordinateXY; + +namespace geos { +namespace algorithm { + + +CoordinateXY +CircularArcs::getCenter(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2) +{ + if (p0.equals2D(p2)) { + // Closed circle + return { 0.5*(p0.x + p1.x), 0.5*(p0.y + p1.y) }; + } + + // Circumcenter formulas from Graphics Gems III + CoordinateXY a{p1.x - p2.x, p1.y - p2.y}; + CoordinateXY b{p2.x - p0.x, p2.y - p0.y}; + CoordinateXY c{p0.x - p1.x, p0.y - p1.y}; + + double d1 = -(b.x*c.x + b.y*c.y); + double d2 = -(c.x*a.x + c.y*a.y); + double d3 = -(a.x*b.x + a.y*b.y); + + double e1 = d2*d3; + double e2 = d3*d1; + double e3 = d1*d2; + double e = e1 + e2 + e3; + + CoordinateXY G3{p0.x + p1.x + p2.x, p0.y + p1.y + p2.y}; + CoordinateXY H {(e1*p0.x + e2*p1.x + e3*p2.x) / e, (e1*p0.y + e2*p1.y + e3*p2.y) / e}; + + CoordinateXY center = {0.5*(G3.x - H.x), 0.5*(G3.y - H.y)}; + + return center; +} + +void +CircularArcs::expandEnvelope(geom::Envelope& e, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2) +{ + e.expandToInclude(p0); + e.expandToInclude(p1); + e.expandToInclude(p2); + + CoordinateXY center = getCenter(p0, p1, p2); + + // zero-length arc + if (center.equals2D(p0) || center.equals2D(p1)) { + return; + } + + // collinear + if (std::isnan(center.x)) { + return; + } + + auto orientation = Orientation::index(center, p0, p1); + + //* 1 | 0 + //* --+-- + //* 2 | 3 + + using geom::Quadrant; + + auto q0 = geom::Quadrant::quadrant(center, p0); + auto q2 = geom::Quadrant::quadrant(center, p2); + double R = center.distance(p1); + + if (q0 == q2) { + // Start and end quadrants are the same. Either the arc crosses all of + // the axes, or none of the axes. + if (Orientation::index(center, p1, p2) != orientation) { + e.expandToInclude({center.x, center.y + R}); + e.expandToInclude({center.x - R, center.y}); + e.expandToInclude({center.x, center.y - R}); + e.expandToInclude({center.x + R, center.y}); + } + + return; + } + + if (orientation == Orientation::CLOCKWISE) { + std::swap(q0, q2); + } + + for (auto q = q0 + 1; (q % 4) != ((q2+1) % 4); q++) { + switch (q % 4) { + case Quadrant::NW: + e.expandToInclude({center.x, center.y + R}); + break; + case Quadrant::SW: + e.expandToInclude({center.x - R, center.y}); + break; + case Quadrant::SE: + e.expandToInclude({center.x, center.y - R}); + break; + case Quadrant::NE: + e.expandToInclude({center.x + R, center.y}); + break; + } + } +} + +} +} diff --git a/Sources/geos/src/algorithm/ConvexHull.cpp b/Sources/geos/src/algorithm/ConvexHull.cpp index 5ff1a8a..7d8e343 100644 --- a/Sources/geos/src/algorithm/ConvexHull.cpp +++ b/Sources/geos/src/algorithm/ConvexHull.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include @@ -42,7 +42,6 @@ using geos::geom::Polygon; using geos::geom::LineString; using geos::geom::LinearRing; using geos::geom::CoordinateSequence; -using geos::geom::CoordinateSequenceFactory; namespace geos { @@ -116,21 +115,18 @@ class RadiallyLessThen { std::unique_ptr ConvexHull::toCoordinateSequence(Coordinate::ConstVect& cv) { - const CoordinateSequenceFactory* csf = - geomFactory->getCoordinateSequenceFactory(); - - std::vector vect(cv.size()); + auto cs = detail::make_unique(cv.size()); for(std::size_t i = 0; i < cv.size(); ++i) { - vect[i] = *(cv[i]); // Coordinate copy + cs->setAt(*(cv[i]), i); // Coordinate copy } - return csf->create(std::move(vect)); // takes ownership of the vector + return cs; } /* private */ void -ConvexHull::computeOctPts(const Coordinate::ConstVect& p_inputPts, +ConvexHull::computeInnerOctolateralPts(const Coordinate::ConstVect& p_inputPts, Coordinate::ConstVect& pts) { // Initialize all slots with first input coordinate @@ -168,10 +164,10 @@ ConvexHull::computeOctPts(const Coordinate::ConstVect& p_inputPts, /* private */ bool -ConvexHull::computeOctRing(const Coordinate::ConstVect& p_inputPts, +ConvexHull::computeInnerOctolateralRing(const Coordinate::ConstVect& p_inputPts, Coordinate::ConstVect& dest) { - computeOctPts(p_inputPts, dest); + computeInnerOctolateralPts(p_inputPts, dest); // Remove consecutive equal Coordinates // unique() returns an iterator to the end of the resulting @@ -195,7 +191,7 @@ ConvexHull::reduce(Coordinate::ConstVect& pts) { Coordinate::ConstVect polyPts; - if(! computeOctRing(pts, polyPts)) { + if(! computeInnerOctolateralRing(pts, polyPts)) { // unable to compute interior polygon for some reason return; } @@ -238,27 +234,23 @@ ConvexHull::padArray3(geom::Coordinate::ConstVect& pts) std::unique_ptr ConvexHull::getConvexHull() { - std::size_t nInputPts = inputPts.size(); - - if(nInputPts == 0) { // Return an empty geometry - return geomFactory->createEmptyGeometry(); - } + std::unique_ptr fewPointsGeom = createFewPointsResult(); + if (fewPointsGeom != nullptr) + return fewPointsGeom; - if(nInputPts == 1) { // Return a Point - // Copy the Coordinate from the ConstVect - return std::unique_ptr(geomFactory->createPoint(*(inputPts[0]))); - } + util::CoordinateArrayFilter filter(inputPts); + inputGeom->apply_ro(&filter); - if(nInputPts == 2) { // Return a LineString - // Copy all Coordinates from the ConstVect - auto cs = toCoordinateSequence(inputPts); - return geomFactory->createLineString(std::move(cs)); - } + std::size_t nInputPts = inputPts.size(); // use heuristic to reduce points, if large - if(nInputPts > 50) { + if(nInputPts > TUNING_REDUCE_SIZE) { reduce(inputPts); } + else { + inputPts.clear(); + extractUnique(inputPts, NO_COORD_INDEX); + } GEOS_CHECK_FOR_INTERRUPTS(); @@ -403,5 +395,45 @@ ConvexHull::cleanRing(const Coordinate::ConstVect& original, } +/* +* Returns true if the extraction is stopped +* by the maxPts restriction. That is, if there +* are yet more points to be processed. +*/ +/* private */ +bool +ConvexHull::extractUnique(Coordinate::ConstVect& pts, std::size_t maxPts) +{ + util::UniqueCoordinateArrayFilter filter(pts, maxPts); + inputGeom->apply_ro(&filter); + return filter.isDone(); +} + + +/* private */ +std::unique_ptr +ConvexHull::createFewPointsResult() +{ + Coordinate::ConstVect uniquePts; + bool ok = extractUnique(uniquePts, 2); + + if (ok) { + return nullptr; + } + + if(uniquePts.size() == 0) { // Return an empty geometry + return geomFactory->createEmptyGeometry(); + } + else if(uniquePts.size() == 1) { // Return a Point + // Copy the Coordinate from the ConstVect + return std::unique_ptr(geomFactory->createPoint(*(uniquePts[0]))); + } + else { // Return a LineString + auto cs = toCoordinateSequence(uniquePts); + return geomFactory->createLineString(std::move(cs)); + } +} + + } // namespace geos.algorithm } // namespace geos diff --git a/Sources/geos/src/algorithm/Distance.cpp b/Sources/geos/src/algorithm/Distance.cpp index 2c85036..ebbba35 100644 --- a/Sources/geos/src/algorithm/Distance.cpp +++ b/Sources/geos/src/algorithm/Distance.cpp @@ -29,9 +29,9 @@ namespace algorithm { // geos.algorithm /*public static*/ double -Distance::pointToSegment(const geom::Coordinate& p, - const geom::Coordinate& A, - const geom::Coordinate& B) +Distance::pointToSegment(const geom::CoordinateXY& p, + const geom::CoordinateXY& A, + const geom::CoordinateXY& B) { /* if start==end, then use pt distance */ if(A == B) { @@ -80,8 +80,8 @@ Distance::pointToSegment(const geom::Coordinate& p, /*public static*/ double -Distance::pointToLinePerpendicular(const geom::Coordinate& p, - const geom::Coordinate& A, const geom::Coordinate& B) +Distance::pointToLinePerpendicular(const geom::CoordinateXY& p, + const geom::CoordinateXY& A, const geom::CoordinateXY& B) { /* use comp.graphics.algorithms method @@ -102,9 +102,9 @@ Distance::pointToLinePerpendicular(const geom::Coordinate& p, /*public static*/ double -Distance::segmentToSegment(const geom::Coordinate& A, - const geom::Coordinate& B, const geom::Coordinate& C, - const geom::Coordinate& D) +Distance::segmentToSegment(const geom::CoordinateXY& A, + const geom::CoordinateXY& B, const geom::CoordinateXY& C, + const geom::CoordinateXY& D) { /* Check for zero-length segments */ if(A == B) { @@ -177,7 +177,7 @@ Distance::segmentToSegment(const geom::Coordinate& A, /*public static*/ double -Distance::pointToSegmentString(const geom::Coordinate& p, +Distance::pointToSegmentString(const geom::CoordinateXY& p, const geom::CoordinateSequence* seq) { if(seq->isEmpty()) { @@ -201,6 +201,29 @@ Distance::pointToSegmentString(const geom::Coordinate& p, } +/*public static*/ +double +Distance::pointToLinePerpendicularSigned( + const geom::CoordinateXY& p, + const geom::CoordinateXY& A, + const geom::CoordinateXY& B) +{ + // use comp.graphics.algorithms Frequently Asked Questions method + /* + * (2) s = (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) + * ----------------------------- + * L^2 + * + * Then the distance from C to P = |s|*L. + */ + double len2 = (B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y); + double s = ((A.y - p.y) * (B.x - A.x) - (A.x - p.x) * (B.y - A.y)) + / len2; + + return s * std::sqrt(len2); +} + + } // namespace geos.algorithm } //namespace geos diff --git a/Sources/geos/src/algorithm/InteriorPointArea.cpp b/Sources/geos/src/algorithm/InteriorPointArea.cpp index 699417d..55fbfe6 100644 --- a/Sources/geos/src/algorithm/InteriorPointArea.cpp +++ b/Sources/geos/src/algorithm/InteriorPointArea.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include @@ -134,13 +133,13 @@ class InteriorPointPolygon { } bool - getInteriorPoint(Coordinate& ret) const + getInteriorPoint(CoordinateXY& ret) const { ret = interiorPoint; return true; } - double getWidth() + double getWidth() const { return interiorSectionWidth; } @@ -172,7 +171,7 @@ class InteriorPointPolygon { const Polygon& polygon; double interiorPointY; double interiorSectionWidth = 0.0; - Coordinate interiorPoint; + CoordinateXY interiorPoint; void scanRing(const LinearRing& ring, std::vector& crossings) { @@ -182,13 +181,13 @@ class InteriorPointPolygon { const CoordinateSequence* seq = ring.getCoordinatesRO(); for (std::size_t i = 1; i < seq->size(); i++) { - const Coordinate& ptPrev = seq->getAt(i - 1); - const Coordinate& pt = seq->getAt(i); + const CoordinateXY& ptPrev = seq->getAt(i - 1); + const CoordinateXY& pt = seq->getAt(i); addEdgeCrossing(ptPrev, pt, interiorPointY, crossings); } } - static void addEdgeCrossing(const Coordinate& p0, const Coordinate& p1, double scanY, std::vector& crossings) + static void addEdgeCrossing(const CoordinateXY& p0, const CoordinateXY& p1, double scanY, std::vector& crossings) { // skip non-crossing segments if (!intersectsHorizontalLine(p0, p1, scanY)) @@ -228,7 +227,7 @@ class InteriorPointPolygon { } static bool - isEdgeCrossingCounted(const Coordinate& p0, const Coordinate& p1, double scanY) + isEdgeCrossingCounted(const CoordinateXY& p0, const CoordinateXY& p1, double scanY) { // skip horizontal lines if (p0.y == p1.y) @@ -244,7 +243,7 @@ class InteriorPointPolygon { } static double - intersection(const Coordinate& p0, const Coordinate& p1, double Y) + intersection(const CoordinateXY& p0, const CoordinateXY& p1, double Y) { double x0 = p0.x; double x1 = p1.x; @@ -271,7 +270,7 @@ class InteriorPointPolygon { } static bool - intersectsHorizontalLine(const Coordinate& p0, const Coordinate& p1, double y) + intersectsHorizontalLine(const CoordinateXY& p0, const CoordinateXY& p1, double y) { // both ends above? if (p0.y > y && p1.y > y) diff --git a/Sources/geos/src/algorithm/InteriorPointLine.cpp b/Sources/geos/src/algorithm/InteriorPointLine.cpp index 85d6bd4..5d791d8 100644 --- a/Sources/geos/src/algorithm/InteriorPointLine.cpp +++ b/Sources/geos/src/algorithm/InteriorPointLine.cpp @@ -65,6 +65,7 @@ InteriorPointLine::addInterior(const Geometry* geom) { const LineString* ls = dynamic_cast(geom); if (ls) { + if (ls->isEmpty()) return; addInterior(ls->getCoordinatesRO()); return; } diff --git a/Sources/geos/src/algorithm/InteriorPointPoint.cpp b/Sources/geos/src/algorithm/InteriorPointPoint.cpp index 6e52fb5..9070aa6 100644 --- a/Sources/geos/src/algorithm/InteriorPointPoint.cpp +++ b/Sources/geos/src/algorithm/InteriorPointPoint.cpp @@ -47,6 +47,9 @@ InteriorPointPoint::InteriorPointPoint(const Geometry* g) void InteriorPointPoint::add(const Geometry* geom) { + if (geom->isEmpty()) + return; + const Point* po = dynamic_cast(geom); if (po) { add(po->getCoordinate()); @@ -63,7 +66,7 @@ InteriorPointPoint::add(const Geometry* geom) /*private*/ void -InteriorPointPoint::add(const Coordinate* point) +InteriorPointPoint::add(const CoordinateXY* point) { assert(point); // we wouldn't been called if this was an empty geom double dist = point->distance(centroid); @@ -75,7 +78,7 @@ InteriorPointPoint::add(const Coordinate* point) /*public*/ bool -InteriorPointPoint::getInteriorPoint(Coordinate& ret) const +InteriorPointPoint::getInteriorPoint(CoordinateXY& ret) const { if (! hasInterior) { return false; diff --git a/Sources/geos/src/algorithm/Intersection.cpp b/Sources/geos/src/algorithm/Intersection.cpp index 455bd60..7970f6f 100644 --- a/Sources/geos/src/algorithm/Intersection.cpp +++ b/Sources/geos/src/algorithm/Intersection.cpp @@ -15,70 +15,80 @@ #include #include +#include +#include #include +#include namespace geos { namespace algorithm { // geos.algorithm /* public static */ -geom::Coordinate -Intersection::intersection(const geom::Coordinate& p1, const geom::Coordinate& p2, - const geom::Coordinate& q1, const geom::Coordinate& q2) +geom::CoordinateXY +Intersection::intersection(const geom::CoordinateXY& p1, const geom::CoordinateXY& p2, + const geom::CoordinateXY& q1, const geom::CoordinateXY& q2) { - double minX0 = p1.x < p2.x ? p1.x : p2.x; - double minY0 = p1.y < p2.y ? p1.y : p2.y; - double maxX0 = p1.x > p2.x ? p1.x : p2.x; - double maxY0 = p1.y > p2.y ? p1.y : p2.y; - - double minX1 = q1.x < q2.x ? q1.x : q2.x; - double minY1 = q1.y < q2.y ? q1.y : q2.y; - double maxX1 = q1.x > q2.x ? q1.x : q2.x; - double maxY1 = q1.y > q2.y ? q1.y : q2.y; - - double intMinX = minX0 > minX1 ? minX0 : minX1; - double intMaxX = maxX0 < maxX1 ? maxX0 : maxX1; - double intMinY = minY0 > minY1 ? minY0 : minY1; - double intMaxY = maxY0 < maxY1 ? maxY0 : maxY1; - - double midx = (intMinX + intMaxX) / 2.0; - double midy = (intMinY + intMaxY) / 2.0; - - // condition ordinate values by subtracting midpoint - double p1x = p1.x - midx; - double p1y = p1.y - midy; - double p2x = p2.x - midx; - double p2y = p2.y - midy; - double q1x = q1.x - midx; - double q1y = q1.y - midy; - double q2x = q2.x - midx; - double q2y = q2.y - midy; - - // unrolled computation using homogeneous coordinates eqn - double px = p1y - p2y; - double py = p2x - p1x; - double pw = p1x * p2y - p2x * p1y; - - double qx = q1y - q2y; - double qy = q2x - q1x; - double qw = q1x * q2y - q2x * q1y; + return CGAlgorithmsDD::intersection(p1, p2, q1, q2); +} - double x = py * qw - qy * pw; - double y = qx * pw - px * qw; - double w = px * qy - qx * py; - double xInt = x/w; - double yInt = y/w; - geom::Coordinate rv; - // check for parallel lines - if (!std::isfinite(xInt) || !std::isfinite(yInt)) { - rv.setNull(); - return rv; +/** +* Computes the intersection point of a line and a line segment (if any). +* There will be no intersection point if: +* +* * the segment does not intersect the line +* * the line or the segment are degenerate (have zero length) +* +* If the segment is collinear with the line the first segment endpoint is returned. +* +* @param line1 a point on the line +* @param line2 a point on the line +* @param seg1 an endpoint of the line segment +* @param seg2 an endpoint of the line segment +* @return the intersection point, or null if it is not possible to find an intersection +*/ +/* public static */ +geom::CoordinateXY +Intersection::intersectionLineSegment( + const geom::CoordinateXY& line1, + const geom::CoordinateXY& line2, + const geom::CoordinateXY& seg1, + const geom::CoordinateXY& seg2) +{ + int orientS1 = Orientation::index(line1, line2, seg1); + if (orientS1 == 0) + return seg1; + + int orientS2 = Orientation::index(line1, line2, seg2); + if (orientS2 == 0) + return seg2; + + /** + * If segment lies completely on one side of the line, it does not intersect + */ + if ((orientS1 > 0 && orientS2 > 0) || (orientS1 < 0 && orientS2 < 0)) { + return geom::CoordinateXY::getNull(); } - // de-condition intersection point - rv.x = xInt + midx; - rv.y = yInt + midy; - return rv; + + /** + * The segment intersects the line. + * The full line-line intersection is used to compute the intersection point. + */ + geom::CoordinateXY intPt = intersection(line1, line2, seg1, seg2); + if (!intPt.isNull()) + return intPt; + + /** + * Due to robustness failure it is possible the intersection computation will return null. + * In this case choose the closest point + */ + double dist1 = Distance::pointToLinePerpendicular(seg1, line1, line2); + double dist2 = Distance::pointToLinePerpendicular(seg2, line1, line2); + if (dist1 < dist2) + return seg1; + else + return seg2; } diff --git a/Sources/geos/src/algorithm/Length.cpp b/Sources/geos/src/algorithm/Length.cpp index 7a8f5ff..1466510 100644 --- a/Sources/geos/src/algorithm/Length.cpp +++ b/Sources/geos/src/algorithm/Length.cpp @@ -36,12 +36,12 @@ Length::ofLine(const geom::CoordinateSequence* pts) double len = 0.0; - const geom::Coordinate& p = pts->getAt(0); + const geom::CoordinateXY& p = pts->getAt(0); double x0 = p.x; double y0 = p.y; for(std::size_t i = 1; i < n; i++) { - const geom::Coordinate& pi = pts->getAt(i); + const geom::CoordinateXY& pi = pts->getAt(i); double x1 = pi.x; double y1 = pi.y; double dx = x1 - x0; diff --git a/Sources/geos/src/algorithm/LineIntersector.cpp b/Sources/geos/src/algorithm/LineIntersector.cpp index b26bc54..b3901b9 100644 --- a/Sources/geos/src/algorithm/LineIntersector.cpp +++ b/Sources/geos/src/algorithm/LineIntersector.cpp @@ -18,12 +18,14 @@ **********************************************************************/ #include +#include #include #include #include #include #include #include +#include #include #include @@ -49,7 +51,7 @@ namespace algorithm { // geos.algorithm /*public static*/ double -LineIntersector::computeEdgeDistance(const Coordinate& p, const Coordinate& p0, const Coordinate& p1) +LineIntersector::computeEdgeDistance(const CoordinateXY& p, const CoordinateXY& p0, const CoordinateXY& p1) { double dx = fabs(p1.x - p0.x); double dy = fabs(p1.y - p0.y); @@ -84,24 +86,11 @@ LineIntersector::computeEdgeDistance(const Coordinate& p, const Coordinate& p0, return dist; } -/*public*/ -void -LineIntersector::computeIntersection(const Coordinate& p1, const Coordinate& p2, const Coordinate& p3, - const Coordinate& p4) -{ - inputLines[0][0] = &p1; - inputLines[0][1] = &p2; - inputLines[1][0] = &p3; - inputLines[1][1] = &p4; - result = computeIntersect(p1, p2, p3, p4); - //numIntersects++; -} - /*public*/ std::string LineIntersector::toString() const { - auto getCoordString = [](const Coordinate* coord) -> std::string { + auto getCoordString = [](const CoordinateXY* coord) -> std::string { return coord ? coord->toString() : ""; }; std::ostringstream ss; @@ -183,477 +172,52 @@ LineIntersector::getEdgeDistance(std::size_t segmentIndex, std::size_t intIndex) return dist; } -/*public static*/ -double -LineIntersector::interpolateZ(const Coordinate& p, - const Coordinate& p1, const Coordinate& p2) -{ -#if GEOS_DEBUG - std::cerr << "LineIntersector::interpolateZ(" << p.toString() << ", " << p1.toString() << ", " << p2.toString() << ")" << - std::endl; -#endif - - if(std::isnan(p1.z)) { -#if GEOS_DEBUG - std::cerr << " p1 do not have a Z" << std::endl; -#endif - return p2.z; // might be DoubleNotANumber again - } - - if(std::isnan(p2.z)) { -#if GEOS_DEBUG - std::cerr << " p2 do not have a Z" << std::endl; -#endif - return p1.z; // might be DoubleNotANumber again - } - - if(p == p1) { -#if GEOS_DEBUG - std::cerr << " p==p1, returning " << p1.z << std::endl; -#endif - return p1.z; - } - if(p == p2) { -#if GEOS_DEBUG - std::cerr << " p==p2, returning " << p2.z << std::endl; -#endif - return p2.z; - } - - //double zgap = fabs(p2.z - p1.z); - double zgap = p2.z - p1.z; - if(zgap == 0.0) { -#if GEOS_DEBUG - std::cerr << " no zgap, returning " << p2.z << std::endl; -#endif - return p2.z; - } - double xoff = (p2.x - p1.x); - double yoff = (p2.y - p1.y); - double seglen = (xoff * xoff + yoff * yoff); - xoff = (p.x - p1.x); - yoff = (p.y - p1.y); - double pdist = (xoff * xoff + yoff * yoff); - double fract = std::sqrt(pdist / seglen); - double zoff = zgap * fract; - //double interpolated = p1.z < p2.z ? p1.z+zoff : p1.z-zoff; - double interpolated = p1.z + zoff; -#if GEOS_DEBUG - std::cerr << " zgap:" << zgap << " seglen:" << seglen << " pdist:" << pdist - << " fract:" << fract << " z:" << interpolated << std::endl; -#endif - return interpolated; - -} - +class DoIntersect { +public: + DoIntersect(algorithm::LineIntersector& li, + const CoordinateSequence& seq0, + std::size_t i0, + const CoordinateSequence& seq1, + std::size_t i1) : + m_li(li), + m_seq0(seq0), + m_i0(i0), + m_seq1(seq1), + m_i1(i1) {} + + template + void operator()() { + const T1& p00 = m_seq0.getAt(m_i0); + const T1& p01 = m_seq0.getAt(m_i0 + 1); + const T2& p10 = m_seq1.getAt(m_i1); + const T2& p11 = m_seq1.getAt(m_i1 + 1); + + m_li.computeIntersection(p00, p01, p10, p11); + } + +private: + algorithm::LineIntersector& m_li; + const CoordinateSequence& m_seq0; + std::size_t m_i0; + const CoordinateSequence& m_seq1; + std::size_t m_i1; +}; /*public*/ void -LineIntersector::computeIntersection(const Coordinate& p, const Coordinate& p1, const Coordinate& p2) -{ - isProperVar = false; - - // do between check first, since it is faster than the orientation test - if(Envelope::intersects(p1, p2, p)) { - if((Orientation::index(p1, p2, p) == 0) && - (Orientation::index(p2, p1, p) == 0)) { - isProperVar = true; - if((p == p1) || (p == p2)) { // 2d only test - isProperVar = false; - } - result = POINT_INTERSECTION; - return; - } - } - result = NO_INTERSECTION; -} - -/* public static */ -bool -LineIntersector::hasIntersection(const Coordinate& p, const Coordinate& p1, const Coordinate& p2) +LineIntersector::computeIntersection(const CoordinateSequence& p, std::size_t p0, + const CoordinateSequence& q, std::size_t q0) { - if(Envelope::intersects(p1, p2, p)) { - if((Orientation::index(p1, p2, p) == 0) && - (Orientation::index(p2, p1, p) == 0)) { - return true; - } - } - return false; + DoIntersect dis(*this, p, p0, q, q0); + CoordinateSequences::binaryDispatch(p, q, dis); } -/*private*/ -uint8_t -LineIntersector::computeIntersect(const Coordinate& p1, const Coordinate& p2, - const Coordinate& q1, const Coordinate& q2) -{ -#if GEOS_DEBUG - std::cerr << "LineIntersector::computeIntersect called" << std::endl; - std::cerr << " p1:" << p1.toString() << " p2:" << p2.toString() << " q1:" << q1.toString() << " q2:" << q2.toString() << - std::endl; -#endif // GEOS_DEBUG - - isProperVar = false; - - // first try a fast test to see if the envelopes of the lines intersect - if(!Envelope::intersects(p1, p2, q1, q2)) { -#if GEOS_DEBUG - std::cerr << " NO_INTERSECTION" << std::endl; -#endif - return NO_INTERSECTION; - } - - // for each endpoint, compute which side of the other segment it lies - // if both endpoints lie on the same side of the other segment, - // the segments do not intersect - int Pq1 = Orientation::index(p1, p2, q1); - int Pq2 = Orientation::index(p1, p2, q2); - - if((Pq1 > 0 && Pq2 > 0) || (Pq1 < 0 && Pq2 < 0)) { -#if GEOS_DEBUG - std::cerr << " NO_INTERSECTION" << std::endl; -#endif - return NO_INTERSECTION; - } - - int Qp1 = Orientation::index(q1, q2, p1); - int Qp2 = Orientation::index(q1, q2, p2); - - if((Qp1 > 0 && Qp2 > 0) || (Qp1 < 0 && Qp2 < 0)) { -#if GEOS_DEBUG - std::cerr << " NO_INTERSECTION" << std::endl; -#endif - return NO_INTERSECTION; - } - - /** - * Intersection is collinear if each endpoint lies on the other line. - */ - bool collinear = Pq1 == 0 && Pq2 == 0 && Qp1 == 0 && Qp2 == 0; - if(collinear) { -#if GEOS_DEBUG - std::cerr << " computingCollinearIntersection" << std::endl; -#endif - return computeCollinearIntersection(p1, p2, q1, q2); - } - - /* - * At this point we know that there is a single intersection point - * (since the lines are not collinear). - */ - - /* - * Check if the intersection is an endpoint. - * If it is, copy the endpoint as - * the intersection point. Copying the point rather than - * computing it ensures the point has the exact value, - * which is important for robustness. It is sufficient to - * simply check for an endpoint which is on the other line, - * since at this point we know that the inputLines must - * intersect. - */ - Coordinate p; - double z = DoubleNotANumber; - - if(Pq1 == 0 || Pq2 == 0 || Qp1 == 0 || Qp2 == 0) { - - isProperVar = false; - -#if GEOS_DEBUG - std::cerr << " intersection is NOT proper" << std::endl; -#endif - - /* Check for two equal endpoints. - * This is done explicitly rather than by the orientation tests - * below in order to improve robustness. - * - * (A example where the orientation tests fail - * to be consistent is: - * - * LINESTRING ( 19.850257749638203 46.29709338043669, - * 20.31970698357233 46.76654261437082 ) - * and - * LINESTRING ( -48.51001596420236 -22.063180333403878, - * 19.850257749638203 46.29709338043669 ) - * - * which used to produce the INCORRECT result: - * (20.31970698357233, 46.76654261437082, NaN) - */ - - if (p1.equals2D(q1)) { - p = p1; - z = zGet(p1, q1); - } - else if (p1.equals2D(q2)) { - p = p1; - z = zGet(p1, q2); - } - else if (p2.equals2D(q1)) { - p = p2; - z = zGet(p2, q1); - } - else if (p2.equals2D(q2)) { - p = p2; - z = zGet(p2, q2); - } - /* - * Now check to see if any endpoint lies on the interior of the other segment. - */ - else if(Pq1 == 0) { - p = q1; - z = zGetOrInterpolate(q1, p1, p2); - } - else if(Pq2 == 0) { - p = q2; - z = zGetOrInterpolate(q2, p1, p2); - } - else if(Qp1 == 0) { - p = p1; - z = zGetOrInterpolate(p1, q1, q2); - } - else if(Qp2 == 0) { - p = p2; - z = zGetOrInterpolate(p2, q1, q2); - } - } - else { -#if GEOS_DEBUG - std::cerr << " intersection is proper" << std::endl; -#endif // GEOS_DEBUG - isProperVar = true; - p = intersection(p1, p2, q1, q2); -#if GEOS_DEBUG - std::cerr << " computed intersection point: " << p << std::endl; -#endif // GEOS_DEBUG - z = zInterpolate(p, p1, p2, q1, q2); -#if GEOS_DEBUG - std::cerr << " computed proper intersection Z: " << z << std::endl; -#endif // GEOS_DEBUG - } - intPt[0] = Coordinate(p.x, p.y, z); -#if GEOS_DEBUG - std::cerr << " POINT_INTERSECTION; intPt[0]:" << intPt[0].toString() << std::endl; -#endif // GEOS_DEBUG - return POINT_INTERSECTION; -} - -/*private*/ -uint8_t -LineIntersector::computeCollinearIntersection(const Coordinate& p1, const Coordinate& p2, - const Coordinate& q1, const Coordinate& q2) -{ - -#if GEOS_DEBUG - std::cerr << "LineIntersector::computeCollinearIntersection called" << std::endl; - std::cerr << " p1:" << p1.toString() << " p2:" << p2.toString() << " q1:" << q1.toString() << " q2:" << q2.toString() << - std::endl; -#endif // GEOS_DEBUG - - bool q1inP = Envelope::intersects(p1, p2, q1); - bool q2inP = Envelope::intersects(p1, p2, q2); - bool p1inQ = Envelope::intersects(q1, q2, p1); - bool p2inQ = Envelope::intersects(q1, q2, p2); - - if(q1inP && q2inP) { -#if GEOS_DEBUG - std::cerr << " q1inP && q2inP" << std::endl; -#endif - intPt[0] = zGetOrInterpolateCopy(q1, p1, p2); - intPt[1] = zGetOrInterpolateCopy(q2, p1, p2); -#if GEOS_DEBUG - std::cerr << " intPt[0]: " << intPt[0].toString() << std::endl; - std::cerr << " intPt[1]: " << intPt[1].toString() << std::endl; -#endif - return COLLINEAR_INTERSECTION; - } - if(p1inQ && p2inQ) { -#if GEOS_DEBUG - std::cerr << " p1inQ && p2inQ" << std::endl; -#endif - intPt[0] = zGetOrInterpolateCopy(p1, q1, q2); - intPt[1] = zGetOrInterpolateCopy(p2, q1, q2); - return COLLINEAR_INTERSECTION; - } - if(q1inP && p1inQ) { -#if GEOS_DEBUG - std::cerr << " q1inP && p1inQ" << std::endl; -#endif - // if pts are equal Z is chosen arbitrarily - intPt[0] = zGetOrInterpolateCopy(q1, p1, p2); - intPt[1] = zGetOrInterpolateCopy(p1, q1, q2); - -#if GEOS_DEBUG - std::cerr << " intPt[0]: " << intPt[0].toString() << std::endl; - std::cerr << " intPt[1]: " << intPt[1].toString() << std::endl; -#endif - return (q1 == p1) && !q2inP && !p2inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION; - } - if(q1inP && p2inQ) { -#if GEOS_DEBUG - std::cerr << " q1inP && p2inQ" << std::endl; -#endif - // if pts are equal Z is chosen arbitrarily - intPt[0] = zGetOrInterpolateCopy(q1, p1, p2); - intPt[1] = zGetOrInterpolateCopy(p2, q1, q2); - -#if GEOS_DEBUG - std::cerr << " intPt[0]: " << intPt[0].toString() << std::endl; - std::cerr << " intPt[1]: " << intPt[1].toString() << std::endl; -#endif - return (q1 == p2) && !q2inP && !p1inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION; - } - if(q2inP && p1inQ) { -#if GEOS_DEBUG - std::cerr << " q2inP && p1inQ" << std::endl; -#endif - // if pts are equal Z is chosen arbitrarily - intPt[0] = zGetOrInterpolateCopy(q2, p1, p2); - intPt[1] = zGetOrInterpolateCopy(p1, q1, q2); -#if GEOS_DEBUG - std::cerr << " intPt[0]: " << intPt[0].toString() << std::endl; - std::cerr << " intPt[1]: " << intPt[1].toString() << std::endl; -#endif - return (q2 == p1) && !q1inP && !p2inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION; - } - if(q2inP && p2inQ) { -#if GEOS_DEBUG - std::cerr << " q2inP && p2inQ" << std::endl; -#endif - // if pts are equal Z is chosen arbitrarily - intPt[0] = zGetOrInterpolateCopy(q2, p1, p2); - intPt[1] = zGetOrInterpolateCopy(p2, q1, q2); -#if GEOS_DEBUG - std::cerr << " intPt[0]: " << intPt[0].toString() << std::endl; - std::cerr << " intPt[1]: " << intPt[1].toString() << std::endl; -#endif - return (q2 == p2) && !q1inP && !p1inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION; - } - return NO_INTERSECTION; -} - -/*private*/ -Coordinate -LineIntersector::intersection(const Coordinate& p1, const Coordinate& p2, - const Coordinate& q1, const Coordinate& q2) const -{ - - Coordinate intPtOut = intersectionSafe(p1, p2, q1, q2); - - /* - * Due to rounding it can happen that the computed intersection is - * outside the envelopes of the input segments. Clearly this - * is inconsistent. - * This code checks this condition and forces a more reasonable answer - * - * MD - May 4 2005 - This is still a problem. Here is a failure case: - * - * LINESTRING (2089426.5233462777 1180182.3877339689, - * 2085646.6891757075 1195618.7333999649) - * LINESTRING (1889281.8148903656 1997547.0560044837, - * 2259977.3672235999 483675.17050843034) - * int point = (2097408.2633752143,1144595.8008114607) - */ - - if(! isInSegmentEnvelopes(intPtOut)) { - //intPt = CentralEndpointIntersector::getIntersection(p1, p2, q1, q2); - intPtOut = nearestEndpoint(p1, p2, q1, q2); -#if GEOS_DEBUG - std::cerr << "Intersection outside segment envelopes, snapped to " - << intPtOut.toString() << std::endl; -#endif - } - - if(precisionModel != nullptr) { - precisionModel->makePrecise(intPtOut); - } - - return intPtOut; -} - -/* private static */ -double -LineIntersector::zInterpolate(const Coordinate& p, const Coordinate& p1, const Coordinate& p2) -{ -#if GEOS_DEBUG - std::cerr << " zInterpolate " << p << ", " << p1 << ", " << p2 << std::endl; -#endif - double p1z = p1.z; - double p2z = p2.z; - if (std::isnan(p1z)) { - return p2z; // may be NaN - } - if (std::isnan(p2z)) { - return p1z; // may be NaN - } - if (p.equals2D(p1)) { - return p1z; // not NaN - } - if (p.equals2D(p2)) { - return p2z; // not NaN - } - double dz = p2z - p1z; - if (dz == 0.0) { - return p1z; - } -#if GEOS_DEBUG - std::cerr << " Interpolating Z from distance of p along p1-p2" << std::endl; -#endif - // interpolate Z from distance of p along p1-p2 - double dx = (p2.x - p1.x); - double dy = (p2.y - p1.y); - // seg has non-zero length since p1 < p < p2 - double seglen = (dx * dx + dy * dy); - double xoff = (p.x - p1.x); - double yoff = (p.y - p1.y); - double plen = (xoff * xoff + yoff * yoff); - double frac = std::sqrt(plen / seglen); - double zoff = dz * frac; - double zInterpolated = p1z + zoff; -#if GEOS_DEBUG - std::cerr << " interpolated Z: " << zInterpolated << std::endl; -#endif - return zInterpolated; -} - - - -double -LineIntersector::zInterpolate(const Coordinate& p, - const Coordinate& p1, - const Coordinate& p2, - const Coordinate& q1, - const Coordinate& q2) -{ -#if GEOS_DEBUG - std::cerr << " zInterpolate(5 coords) called" << std::endl; -#endif // GEOS_DEBUG - double zp = zInterpolate(p, p1, p2); -#if GEOS_DEBUG - std::cerr << " zp: " << zp << std::endl; -#endif // GEOS_DEBUG - double zq = zInterpolate(p, q1, q2); -#if GEOS_DEBUG - std::cerr << " zq: " << zq << std::endl; -#endif // GEOS_DEBUG - if (std::isnan(zp)) { - return zq; // may be NaN - } - if (std::isnan(zq)) { - return zp; // may be NaN - } -#if GEOS_DEBUG - std::cerr << " averaging Z" << std::endl; -#endif // GEOS_DEBUG - // both Zs have values, so average them - return (zp + zq) / 2.0; -} - - /* private static */ -Coordinate -LineIntersector::nearestEndpoint(const Coordinate& p1, const Coordinate& p2, - const Coordinate& q1, const Coordinate& q2) +const CoordinateXY& +LineIntersector::nearestEndpoint(const CoordinateXY& p1, const CoordinateXY& p2, + const CoordinateXY& q1, const CoordinateXY& q2) { - const Coordinate* nearestPt = &p1; + const CoordinateXY* nearestPt = &p1; double minDist = Distance::pointToSegment(p1, q1, q2); double dist = Distance::pointToSegment(p2, q1, q2); diff --git a/Sources/geos/src/algorithm/MinimumAreaRectangle.cpp b/Sources/geos/src/algorithm/MinimumAreaRectangle.cpp new file mode 100644 index 0000000..b3f655f --- /dev/null +++ b/Sources/geos/src/algorithm/MinimumAreaRectangle.cpp @@ -0,0 +1,269 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace geos::geom; + + +namespace geos { +namespace algorithm { // geos.algorithm + + +/* public static */ +std::unique_ptr +MinimumAreaRectangle::getMinimumRectangle(const Geometry* geom) +{ + MinimumAreaRectangle mar(geom); + return mar.getMinimumRectangle(); +} + + +/* private */ +std::unique_ptr +MinimumAreaRectangle::getMinimumRectangle() +{ + if (m_inputGeom->isEmpty()) { + return m_inputGeom->getFactory()->createPolygon(); + } + if (m_isConvex) { + return computeConvex(m_inputGeom); + } + ConvexHull cvh(m_inputGeom); + std::unique_ptr convexGeom = cvh.getConvexHull(); + + return computeConvex(convexGeom.get()); +} + + +/* private */ +std::unique_ptr +MinimumAreaRectangle::computeConvex(const Geometry* convexGeom) +{ + const CoordinateSequence* convexHullPts; + switch (convexGeom->getGeometryTypeId()) { + case GEOS_POLYGON: + { + auto poly = static_cast(convexGeom); + convexHullPts = poly->getExteriorRing()->getCoordinatesRO(); + break; + } + case GEOS_LINESTRING: + { + auto line = static_cast(convexGeom); + convexHullPts = line->getCoordinatesRO(); + break; + } + case GEOS_POINT: + { + auto pt = static_cast(convexGeom); + convexHullPts = pt->getCoordinatesRO(); + break; + } + default: + throw util::IllegalArgumentException("computeConvex called with unsupported geometry type"); + } + + // special cases for lines or points or degenerate rings + switch (convexHullPts->size()) + { + case 1: + return m_inputGeom->getFactory()->createPoint(convexHullPts->getAt(0)); + case 2: + case 3: + return computeMaximumLine(convexHullPts, m_inputGeom->getFactory()); + default: + // TODO: ensure ring is CW + return computeConvexRing(convexHullPts); + } +} + + +/* private */ +std::unique_ptr +MinimumAreaRectangle::computeConvexRing(const CoordinateSequence* ring) +{ + // Assert: ring is oriented CW + + double minRectangleArea = DoubleMax; + std::size_t minRectangleBaseIndex = NO_COORD_INDEX; + std::size_t minRectangleDiamIndex = NO_COORD_INDEX; + std::size_t minRectangleLeftIndex = NO_COORD_INDEX; + std::size_t minRectangleRightIndex = NO_COORD_INDEX; + + //-- start at vertex after first one + std::size_t diameterIndex = 1; + std::size_t leftSideIndex = 1; + std::size_t rightSideIndex = NO_COORD_INDEX; // initialized once first diameter is found + + LineSegment segBase; + LineSegment segDiam; + // for each segment, find the next vertex which is at maximum distance + for (std::size_t i = 0; i < ring->size() - 1; i++) { + segBase.p0 = ring->getAt(i); + segBase.p1 = ring->getAt(i + 1); + diameterIndex = findFurthestVertex(ring, segBase, diameterIndex, 0); + + const CoordinateXY& diamPt = ring->getAt(diameterIndex); + CoordinateXY diamBasePt = segBase.project(diamPt); + segDiam.p0 = diamBasePt; + segDiam.p1 = diamPt; + + leftSideIndex = findFurthestVertex(ring, segDiam, leftSideIndex, 1); + + //-- init the max right index + if (i == 0) { + rightSideIndex = diameterIndex; + } + rightSideIndex = findFurthestVertex(ring, segDiam, rightSideIndex, -1); + + double rectWidth = segDiam.distancePerpendicular(ring->getAt(leftSideIndex)) + + segDiam.distancePerpendicular(ring->getAt(rightSideIndex)); + double rectArea = segDiam.getLength() * rectWidth; + + if (rectArea < minRectangleArea) { + minRectangleArea = rectArea; + minRectangleBaseIndex = i; + minRectangleDiamIndex = diameterIndex; + minRectangleLeftIndex = leftSideIndex; + minRectangleRightIndex = rightSideIndex; + } + } + return Rectangle::createFromSidePts( + ring->getAt(minRectangleBaseIndex), + ring->getAt(minRectangleBaseIndex + 1), + ring->getAt(minRectangleDiamIndex), + ring->getAt(minRectangleLeftIndex), + ring->getAt(minRectangleRightIndex), + m_inputGeom->getFactory()); +} + + +/* private */ +std::size_t +MinimumAreaRectangle::findFurthestVertex( + const CoordinateSequence* pts, + const LineSegment& baseSeg, + std::size_t startIndex, int orient) +{ + double maxDistance = orientedDistance(baseSeg, pts->getAt(startIndex), orient); + double nextDistance = maxDistance; + std::size_t maxIndex = startIndex; + std::size_t nextIndex = maxIndex; + //-- rotate "caliper" while distance from base segment is non-decreasing + while (isFurtherOrEqual(nextDistance, maxDistance, orient)) { + maxDistance = nextDistance; + maxIndex = nextIndex; + + nextIndex = getNextIndex(pts, maxIndex); + if (nextIndex == startIndex) + break; + nextDistance = orientedDistance(baseSeg, pts->getAt(nextIndex), orient); + } + return maxIndex; +} + + +/* private */ +bool +MinimumAreaRectangle::isFurtherOrEqual( + double d1, double d2, + int orient) +{ + switch (orient) { + case 0: return std::abs(d1) >= std::abs(d2); + case 1: return d1 >= d2; + case -1: return d1 <= d2; + } + throw util::IllegalArgumentException("Invalid orientation index"); +} + + +/* private static */ +double +MinimumAreaRectangle::orientedDistance( + const LineSegment& seg, + const CoordinateXY& p, + int orient) +{ + double dist = seg.distancePerpendicularOriented(p); + if (orient == 0) { + return std::abs(dist); + } + return dist; +} + + +/* private static */ +std::size_t +MinimumAreaRectangle::getNextIndex( + const CoordinateSequence* ring, + std::size_t index) +{ + if (index == NO_COORD_INDEX) index = 0; + else index = index + 1; + + if (index >= ring->size() - 1) + index = 0; + return index; +} + + +/* private static */ +std::unique_ptr +MinimumAreaRectangle::computeMaximumLine( + const CoordinateSequence* pts, + const GeometryFactory* factory) +{ + //-- find max and min pts for X and Y + CoordinateXY ptMinX; ptMinX.setNull(); + CoordinateXY ptMaxX; ptMaxX.setNull(); + CoordinateXY ptMinY; ptMinY.setNull(); + CoordinateXY ptMaxY; ptMaxY.setNull(); + for (const CoordinateXY& p : pts->items()) { + if (ptMinX.isNull() || p.x < ptMinX.x) ptMinX = p; + if (ptMaxX.isNull() || p.x > ptMaxX.x) ptMaxX = p; + if (ptMinY.isNull() || p.y < ptMinY.y) ptMinY = p; + if (ptMaxY.isNull() || p.y > ptMaxY.y) ptMaxY = p; + } + + CoordinateXY p0 = ptMinX; + CoordinateXY p1 = ptMaxX; + + //-- line is vertical - use Y pts + if (p0.x == p1.x) { + p0 = ptMinY; + p1 = ptMaxY; + } + CoordinateSequence cs({ p0, p1 }); + return factory->createLineString(std::move(cs)); +} + + + +} // namespace geos.algorithm +} // namespace geos diff --git a/Sources/geos/src/algorithm/MinimumBoundingCircle.cpp b/Sources/geos/src/algorithm/MinimumBoundingCircle.cpp index 87b7acc..f454a7a 100644 --- a/Sources/geos/src/algorithm/MinimumBoundingCircle.cpp +++ b/Sources/geos/src/algorithm/MinimumBoundingCircle.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -30,6 +29,7 @@ #include #include #include +#include #include // sqrt #include // for unique_ptr @@ -64,7 +64,6 @@ std::unique_ptr MinimumBoundingCircle::getMaximumDiameter() { compute(); - uint8_t dims = input->getCoordinateDimension(); std::size_t len = 2; switch(extremalPts.size()) { case 0: @@ -72,14 +71,14 @@ MinimumBoundingCircle::getMaximumDiameter() case 1: return std::unique_ptr(input->getFactory()->createPoint(centre)); case 2: { - auto cs = input->getFactory()->getCoordinateSequenceFactory()->create(len, dims); + auto cs = detail::make_unique(len, input->hasZ(), input->hasM(), false); cs->setAt(extremalPts.front(), 0); cs->setAt(extremalPts.back(), 1); return input->getFactory()->createLineString(std::move(cs)); } default: { - std::vector fp = farthestPoints(extremalPts); - auto cs = input->getFactory()->getCoordinateSequenceFactory()->create(len, dims); + const auto& fp = farthestPoints(extremalPts); + auto cs = detail::make_unique(len, input->hasZ(), input->hasM(), false); cs->setAt(fp.front(), 0); cs->setAt(fp.back(), 1); return input->getFactory()->createLineString(std::move(cs)); @@ -89,10 +88,10 @@ MinimumBoundingCircle::getMaximumDiameter() } /* private */ -std::vector -MinimumBoundingCircle::farthestPoints(std::vector& pts) +std::vector +MinimumBoundingCircle::farthestPoints(std::vector& pts) { - std::vector fp; + std::vector fp; double dist01 = pts[0].distance(pts[1]); double dist12 = pts[1].distance(pts[2]); double dist20 = pts[2].distance(pts[0]); @@ -122,9 +121,8 @@ MinimumBoundingCircle::getDiameter() case 1: return std::unique_ptr(input->getFactory()->createPoint(centre)); } - uint8_t dims = input->getCoordinateDimension(); std::size_t len = 2; - auto cs = input->getFactory()->getCoordinateSequenceFactory()->create(len, dims); + auto cs = detail::make_unique(len, input->hasZ(), input->hasM(), false); // TODO: handle case of 3 extremal points, by computing a line from one of // them through the centre point with len = 2*radius cs->setAt(extremalPts[0], 0); @@ -134,7 +132,7 @@ MinimumBoundingCircle::getDiameter() /*public*/ -std::vector +std::vector MinimumBoundingCircle::getExtremalPoints() { compute(); @@ -142,7 +140,7 @@ MinimumBoundingCircle::getExtremalPoints() } /*public*/ -Coordinate +CoordinateXY MinimumBoundingCircle::getCentre() { compute(); @@ -222,7 +220,7 @@ MinimumBoundingCircle::computeCirclePoints() std::unique_ptr convexHull(input->convexHull()); std::unique_ptr cs(convexHull->getCoordinates()); - std::vector pts; + std::vector pts; cs->toVector(pts); // strip duplicate final point, if any @@ -239,10 +237,10 @@ MinimumBoundingCircle::computeCirclePoints() } // find a point P with minimum Y ordinate - Coordinate P = lowestPoint(pts); + CoordinateXY P = lowestPoint(pts); // find a point Q such that the angle that PQ makes with the x-axis is minimal - Coordinate Q = pointWitMinAngleWithX(pts, P); + CoordinateXY Q = pointWitMinAngleWithX(pts, P); /* * Iterate over the remaining points to find @@ -253,7 +251,7 @@ MinimumBoundingCircle::computeCirclePoints() */ std::size_t i = 0, n = pts.size(); while(i++ < n) { - Coordinate R = pointWithMinAngleWithSegment(pts, P, Q); + CoordinateXY R = pointWithMinAngleWithSegment(pts, P, Q); // if PRQ is obtuse, then MBC is determined by P and Q if(algorithm::Angle::isObtuse(P, R, Q)) { @@ -282,10 +280,10 @@ MinimumBoundingCircle::computeCirclePoints() } /*private*/ -Coordinate -MinimumBoundingCircle::lowestPoint(std::vector& pts) +CoordinateXY +MinimumBoundingCircle::lowestPoint(std::vector& pts) { - const Coordinate* min = &(pts[0]); + const CoordinateXY* min = &(pts[0]); for(const auto& pt : pts) { if(pt.y < min->y) { min = &pt; @@ -296,11 +294,11 @@ MinimumBoundingCircle::lowestPoint(std::vector& pts) /*private*/ -Coordinate -MinimumBoundingCircle::pointWitMinAngleWithX(std::vector& pts, Coordinate& P) +CoordinateXY +MinimumBoundingCircle::pointWitMinAngleWithX(std::vector& pts, CoordinateXY& P) { double minSin = DoubleInfinity; - Coordinate minAngPt; + CoordinateXY minAngPt; minAngPt.setNull(); for(const auto& p : pts) { @@ -329,12 +327,12 @@ MinimumBoundingCircle::pointWitMinAngleWithX(std::vector& pts, Coord /*private*/ -Coordinate -MinimumBoundingCircle::pointWithMinAngleWithSegment(std::vector& pts, Coordinate& P, Coordinate& Q) +CoordinateXY +MinimumBoundingCircle::pointWithMinAngleWithSegment(std::vector& pts, CoordinateXY& P, CoordinateXY& Q) { assert(!pts.empty()); double minAng = DoubleInfinity; - const Coordinate* minAngPt = &pts[0]; + const CoordinateXY* minAngPt = &pts[0]; for(const auto& p : pts) { if(p == P) { diff --git a/Sources/geos/src/algorithm/MinimumDiameter.cpp b/Sources/geos/src/algorithm/MinimumDiameter.cpp index 7c92e60..813b161 100644 --- a/Sources/geos/src/algorithm/MinimumDiameter.cpp +++ b/Sources/geos/src/algorithm/MinimumDiameter.cpp @@ -3,6 +3,7 @@ * GEOS - Geometry Engine Open Source * http://geos.osgeo.org * + * Copyright (C) 2023 Paul Ramsey * Copyright (C) 2001-2002 Vivid Solutions Inc. * Copyright (C) 2005 Refractions Research Inc. * @@ -11,10 +12,6 @@ * by the Free Software Foundation. * See the COPYING file for more information. * - ********************************************************************** - * - * Last port: algorithm/MinimumDiameter.java r966 - * **********************************************************************/ #include @@ -25,9 +22,9 @@ #include #include #include -#include #include #include +#include #include #include // for fabs() @@ -59,7 +56,7 @@ namespace algorithm { // geos.algorithm /** * Compute a minimum diameter for a giver {@link Geometry}. * - * @param geom a Geometry + * @param newInputGeom a Geometry */ MinimumDiameter::MinimumDiameter(const Geometry* newInputGeom) { @@ -78,8 +75,8 @@ MinimumDiameter::MinimumDiameter(const Geometry* newInputGeom) * (e.g. a convex Polygon or LinearRing, * or a two-point LineString, or a Point). * - * @param geom a Geometry which is convex - * @param isConvex true if the input geometry is convex + * @param newInputGeom a Geometry which is convex + * @param newIsConvex true if the input geometry is convex */ MinimumDiameter::MinimumDiameter(const Geometry* newInputGeom, const bool newIsConvex) { @@ -144,7 +141,7 @@ MinimumDiameter::getDiameter() Coordinate basePt; minBaseSeg.project(minWidthPt, basePt); - auto cl = inputGeom->getFactory()->getCoordinateSequenceFactory()->create(2); + auto cl = detail::make_unique(2u); cl->setAt(basePt, 0); cl->setAt(minWidthPt, 1); return inputGeom->getFactory()->createLineString(std::move(cl)); @@ -219,7 +216,7 @@ MinimumDiameter::computeConvexRingMinDiameter(const CoordinateSequence* pts) unsigned int currMaxIndex = 1; LineSegment seg; - // compute the max distance for all segments in the ring, and pick the minimum + // for each segment, find a vertex at max distance, and pick the minimum const std::size_t npts = pts->getSize(); for(std::size_t i = 1; i < npts; ++i) { seg.p0 = pts->getAt(i - 1); @@ -326,10 +323,7 @@ MinimumDiameter::getMinimumRectangle() Coordinate p2 = minParaLine.lineIntersection(minPerpLine); Coordinate p3 = maxParaLine.lineIntersection(minPerpLine); - const CoordinateSequenceFactory* csf = - inputGeom->getFactory()->getCoordinateSequenceFactory(); - - auto seq = csf->create(5, 2); + auto seq = detail::make_unique(5u, 2u); seq->setAt(p0, 0); seq->setAt(p1, 1); seq->setAt(p2, 2); @@ -367,9 +361,7 @@ MinimumDiameter::computeMaximumLine(const geom::CoordinateSequence* pts, p1 = ptMaxY; } - const CoordinateSequenceFactory* csf = - factory->getCoordinateSequenceFactory(); - auto seq = csf->create(2, 2); + auto seq = detail::make_unique(2u, 2u); seq->setAt(p0, 0); seq->setAt(p1, 1); diff --git a/Sources/geos/src/algorithm/Orientation.cpp b/Sources/geos/src/algorithm/Orientation.cpp index 17dfe6e..55ff3fe 100644 --- a/Sources/geos/src/algorithm/Orientation.cpp +++ b/Sources/geos/src/algorithm/Orientation.cpp @@ -27,14 +27,16 @@ #include #include +using geos::geom::CoordinateXY; + namespace geos { namespace algorithm { // geos.algorithm /* public static */ // inlining this method worsened performance slightly int -Orientation::index(const geom::Coordinate& p1, const geom::Coordinate& p2, - const geom::Coordinate& q) +Orientation::index(const geom::CoordinateXY& p1, const geom::CoordinateXY& p2, + const geom::CoordinateXY& q) { return CGAlgorithmsDD::orientationIndex(p1, p2, q); } @@ -48,8 +50,7 @@ Orientation::isCCW(const geom::CoordinateSequence* ring) int inPts = static_cast(ring->size()) - 1; // sanity check if (inPts < 3) - throw util::IllegalArgumentException( - "Ring has fewer than 4 points, so orientation cannot be determined"); + return false; uint32_t nPts = static_cast(inPts); /** @@ -60,20 +61,20 @@ Orientation::isCCW(const geom::CoordinateSequence* ring) * Note this relies on the convention that * rings have the same start and end point. */ - geom::Coordinate upHiPt(ring->getAt(0)); - geom::Coordinate upLowPt(geom::Coordinate::getNull()); + const CoordinateXY* upHiPt = &ring->getAt(0); + const CoordinateXY* upLowPt = &CoordinateXY::getNull(); - double prevY = upHiPt.y; + double prevY = upHiPt->y; uint32_t iUpHi = 0; for (uint32_t i = 1; i <= nPts; i++) { double py = ring->getY(i); /** * If segment is upwards and endpoint is higher, record it */ - if (py > prevY && py >= upHiPt.y) { + if (py > prevY && py >= upHiPt->y) { iUpHi = i; - upHiPt = ring->getAt(i); - upLowPt = ring->getAt(i-1); + upHiPt = &ring->getAt(i); + upLowPt = &ring->getAt(i-1); } prevY = py; } @@ -90,11 +91,11 @@ Orientation::isCCW(const geom::CoordinateSequence* ring) uint32_t iDownLow = iUpHi; do { iDownLow = (iDownLow + 1) % nPts; - } while (iDownLow != iUpHi && ring->getY(iDownLow) == upHiPt.y ); + } while (iDownLow != iUpHi && ring->getY(iDownLow) == upHiPt->y ); - const geom::Coordinate& downLowPt = ring->getAt(iDownLow); + const CoordinateXY& downLowPt = ring->getAt(iDownLow); uint32_t iDownHi = iDownLow > 0 ? iDownLow - 1 : nPts - 1; - const geom::Coordinate& downHiPt = ring->getAt(iDownHi); + const CoordinateXY& downHiPt = ring->getAt(iDownHi); /** * Two cases can occur: @@ -105,14 +106,14 @@ Orientation::isCCW(const geom::CoordinateSequence* ring) * In this case the top of the cap is flat. * The ring orientation is given by the direction of the flat segment */ - if (upHiPt.equals2D(downHiPt)) { + if (upHiPt->equals2D(downHiPt)) { /** * Check for the case where the cap has configuration A-B-A. * This can happen if the ring does not contain 3 distinct points * (including the case where the input array has fewer than 4 elements), or * it contains coincident line segments. */ - if (upLowPt.equals2D(upHiPt) || downLowPt.equals2D(upHiPt) || upLowPt.equals2D(downLowPt)) + if (upLowPt->equals2D(*upHiPt) || downLowPt.equals2D(*upHiPt) || upLowPt->equals2D(downLowPt)) return false; /** @@ -120,14 +121,14 @@ Orientation::isCCW(const geom::CoordinateSequence* ring) * This is an invalid ring, which cannot be computed correctly. * In this case the orientation is 0, and the result is false. */ - int orientationIndex = index(upLowPt, upHiPt, downLowPt); + int orientationIndex = index(*upLowPt, *upHiPt, downLowPt); return orientationIndex == COUNTERCLOCKWISE; } else { /** * Flat cap - direction of flat top determines orientation */ - double delX = downHiPt.x - upHiPt.x; + double delX = downHiPt.x - upHiPt->x; return delX < 0; } } @@ -144,4 +145,3 @@ Orientation::isCCWArea(const geom::CoordinateSequence* ring) } // namespace geos.algorithm } //namespace geos - diff --git a/Sources/geos/src/algorithm/PointLocation.cpp b/Sources/geos/src/algorithm/PointLocation.cpp index f9ab6d7..054ea33 100644 --- a/Sources/geos/src/algorithm/PointLocation.cpp +++ b/Sources/geos/src/algorithm/PointLocation.cpp @@ -20,10 +20,12 @@ #include #include +#include #include #include #include #include +#include #include #include @@ -32,27 +34,40 @@ namespace algorithm { // geos.algorithm /* public static */ bool -PointLocation::isOnLine(const geom::Coordinate& p, const geom::CoordinateSequence* pt) +PointLocation::isOnSegment(const geom::CoordinateXY& p, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1) +{ + //-- test envelope first since it's faster + if (! geom::Envelope::intersects(p0, p1, p)) + return false; + //-- handle zero-length segments + if (p.equals2D(p0)) + return true; + bool isOnLine = Orientation::COLLINEAR == Orientation::index(p0, p1, p); + return isOnLine; +} + +/* public static */ +bool +PointLocation::isOnLine(const geom::CoordinateXY& p, const geom::CoordinateSequence* pt) { std::size_t ptsize = pt->getSize(); if(ptsize == 0) { return false; } - const geom::Coordinate* pp = &(pt->getAt(0)); for(std::size_t i = 1; i < ptsize; ++i) { - const geom::Coordinate& p1 = pt->getAt(i); - if(LineIntersector::hasIntersection(p, *pp, p1)) { + if(isOnSegment(p, + pt->getAt(i - 1), + pt->getAt(i))) { return true; } - pp = &p1; } return false; } /* public static */ bool -PointLocation::isInRing(const geom::Coordinate& p, +PointLocation::isInRing(const geom::CoordinateXY& p, const std::vector& ring) { return PointLocation::locateInRing(p, ring) != geom::Location::EXTERIOR; @@ -60,7 +75,7 @@ PointLocation::isInRing(const geom::Coordinate& p, /* public static */ bool -PointLocation::isInRing(const geom::Coordinate& p, +PointLocation::isInRing(const geom::CoordinateXY& p, const geom::CoordinateSequence* ring) { return PointLocation::locateInRing(p, *ring) != geom::Location::EXTERIOR; @@ -68,7 +83,7 @@ PointLocation::isInRing(const geom::Coordinate& p, /* public static */ geom::Location -PointLocation::locateInRing(const geom::Coordinate& p, +PointLocation::locateInRing(const geom::CoordinateXY& p, const std::vector& ring) { return RayCrossingCounter::locatePointInRing(p, ring); @@ -76,12 +91,18 @@ PointLocation::locateInRing(const geom::Coordinate& p, /* public static */ geom::Location -PointLocation::locateInRing(const geom::Coordinate& p, +PointLocation::locateInRing(const geom::CoordinateXY& p, const geom::CoordinateSequence& ring) { return RayCrossingCounter::locatePointInRing(p, ring); } +geom::Location +PointLocation::locateInRing(const geom::CoordinateXY& p, + const geom::Curve& ring) { + return RayCrossingCounter::locatePointInRing(p, ring); +} + } // namespace geos.algorithm } // namespace geos diff --git a/Sources/geos/src/algorithm/PointLocator.cpp b/Sources/geos/src/algorithm/PointLocator.cpp index 1425440..7fa7dd7 100644 --- a/Sources/geos/src/algorithm/PointLocator.cpp +++ b/Sources/geos/src/algorithm/PointLocator.cpp @@ -40,7 +40,7 @@ namespace algorithm { // geos.algorithm Location -PointLocator::locate(const Coordinate& p, const Geometry* geom) +PointLocator::locate(const CoordinateXY& p, const Geometry* geom) { if (geom->isEmpty()) { return Location::EXTERIOR; @@ -72,9 +72,13 @@ PointLocator::locate(const Coordinate& p, const Geometry* geom) /* private */ void -PointLocator::computeLocation(const Coordinate& p, const Geometry* geom) +PointLocator::computeLocation(const CoordinateXY& p, const Geometry* geom) { - + //-- handle empty elements + if (geom->isEmpty()) { + return; + } + GeometryTypeId geomTypeId = geom->getGeometryTypeId(); switch (geomTypeId) { @@ -130,7 +134,6 @@ PointLocator::computeLocation(const Coordinate& p, const Geometry* geom) } default: { throw util::UnsupportedOperationException("unknown GeometryTypeId"); - break; } } @@ -150,10 +153,10 @@ PointLocator::updateLocationInfo(geom::Location loc) /* private */ Location -PointLocator::locate(const Coordinate& p, const Point* pt) +PointLocator::locate(const CoordinateXY& p, const Point* pt) { // no point in doing envelope test, since equality test is just as fast - const Coordinate* ptCoord = pt->getCoordinate(); + const CoordinateXY* ptCoord = pt->getCoordinate(); if(ptCoord != nullptr && ptCoord->equals2D(p)) { return Location::INTERIOR; } @@ -162,7 +165,7 @@ PointLocator::locate(const Coordinate& p, const Point* pt) /* private */ Location -PointLocator::locate(const Coordinate& p, const LineString* l) +PointLocator::locate(const CoordinateXY& p, const LineString* l) { if(!l->getEnvelopeInternal()->intersects(p)) { return Location::EXTERIOR; @@ -170,7 +173,7 @@ PointLocator::locate(const Coordinate& p, const LineString* l) const CoordinateSequence* seq = l->getCoordinatesRO(); if(! l->isClosed()) { - if((p == seq->getAt(0)) || (p == seq->getAt(seq->getSize() - 1))) { + if((p == seq->getAt(0)) || (p == seq->getAt(seq->getSize() - 1))) { return Location::BOUNDARY; } } @@ -182,7 +185,7 @@ PointLocator::locate(const Coordinate& p, const LineString* l) /* private */ Location -PointLocator::locateInPolygonRing(const Coordinate& p, const LinearRing* ring) +PointLocator::locateInPolygonRing(const CoordinateXY& p, const LinearRing* ring) { if(!ring->getEnvelopeInternal()->intersects(p)) { return Location::EXTERIOR; @@ -201,7 +204,7 @@ PointLocator::locateInPolygonRing(const Coordinate& p, const LinearRing* ring) /* private */ Location -PointLocator::locate(const Coordinate& p, const Polygon* poly) +PointLocator::locate(const CoordinateXY& p, const Polygon* poly) { if(poly->isEmpty()) { return Location::EXTERIOR; diff --git a/Sources/geos/src/algorithm/PolygonNodeTopology.cpp b/Sources/geos/src/algorithm/PolygonNodeTopology.cpp index f76cc49..8449944 100644 --- a/Sources/geos/src/algorithm/PolygonNodeTopology.cpp +++ b/Sources/geos/src/algorithm/PolygonNodeTopology.cpp @@ -24,35 +24,44 @@ using geos::geom::Quadrant; namespace geos { namespace algorithm { // geos.algorithm + /* public static */ bool -PolygonNodeTopology::isCrossing(const Coordinate* nodePt, - const Coordinate* a0, const Coordinate* a1, - const Coordinate* b0, const Coordinate* b1) +PolygonNodeTopology::isCrossing(const CoordinateXY* nodePt, + const CoordinateXY* a0, const CoordinateXY* a1, + const CoordinateXY* b0, const CoordinateXY* b1) { - const Coordinate* aLo = a0; - const Coordinate* aHi = a1; + const CoordinateXY* aLo = a0; + const CoordinateXY* aHi = a1; if (isAngleGreater(nodePt, aLo, aHi)) { aLo = a1; aHi = a0; } + + // bool bBetween0 = isBetween(nodePt, b0, aLo, aHi); + // bool bBetween1 = isBetween(nodePt, b1, aLo, aHi); + // return bBetween0 != bBetween1; + /** * Find positions of b0 and b1. * If they are the same they do not cross the other edge */ - bool bBetween0 = isBetween(nodePt, b0, aLo, aHi); - bool bBetween1 = isBetween(nodePt, b1, aLo, aHi); + int compBetween0 = compareBetween(nodePt, b0, aLo, aHi); + if (compBetween0 == 0) return false; + int compBetween1 = compareBetween(nodePt, b1, aLo, aHi); + if (compBetween1 == 0) return false; - return bBetween0 != bBetween1; + return compBetween0 != compBetween1; } + /* public static */ bool -PolygonNodeTopology::isInteriorSegment(const Coordinate* nodePt, - const Coordinate* a0, const Coordinate* a1, const Coordinate* b) +PolygonNodeTopology::isInteriorSegment(const CoordinateXY* nodePt, + const CoordinateXY* a0, const CoordinateXY* a1, const CoordinateXY* b) { - const Coordinate* aLo = a0; - const Coordinate* aHi = a1; + const CoordinateXY* aLo = a0; + const CoordinateXY* aHi = a1; bool isInteriorBetween = true; if (isAngleGreater(nodePt, aLo, aHi)) { aLo = a1; @@ -67,9 +76,9 @@ PolygonNodeTopology::isInteriorSegment(const Coordinate* nodePt, /* private static */ bool -PolygonNodeTopology::isBetween(const Coordinate* origin, - const Coordinate* p, - const Coordinate* e0, const Coordinate* e1) +PolygonNodeTopology::isBetween(const CoordinateXY* origin, + const CoordinateXY* p, + const CoordinateXY* e0, const CoordinateXY* e1) { bool isGreater0 = isAngleGreater(origin, p, e0); if (! isGreater0) return false; @@ -78,10 +87,56 @@ PolygonNodeTopology::isBetween(const Coordinate* origin, } +/* private static */ +int +PolygonNodeTopology::compareBetween( + const CoordinateXY* origin, const CoordinateXY* p, + const CoordinateXY* e0, const CoordinateXY* e1) +{ + int comp0 = compareAngle(origin, p, e0); + if (comp0 == 0) return 0; + int comp1 = compareAngle(origin, p, e1); + if (comp1 == 0) return 0; + if (comp0 > 0 && comp1 < 0) return 1; + return -1; +} + + +/* public static */ +int +PolygonNodeTopology::compareAngle( + const CoordinateXY* origin, + const CoordinateXY* p, + const CoordinateXY* q) +{ + int quadrantP = quadrant(origin, p); + int quadrantQ = quadrant(origin, q); + + /** + * If the vectors are in different quadrants, + * that determines the ordering + */ + if (quadrantP > quadrantQ) return 1; + if (quadrantP < quadrantQ) return -1; + + //--- vectors are in the same quadrant + // Check relative orientation of vectors + // P > Q if it is CCW of Q + int orient = Orientation::index(*origin, *q, *p); + switch (orient) { + case Orientation::COUNTERCLOCKWISE: + return 1; + case Orientation::CLOCKWISE: + return -1; + default: + return 0; + } +} + /* private static */ bool -PolygonNodeTopology::isAngleGreater(const Coordinate* origin, - const Coordinate* p, const Coordinate* q) +PolygonNodeTopology::isAngleGreater(const CoordinateXY* origin, + const CoordinateXY* p, const CoordinateXY* q) { int quadrantP = quadrant(origin, p); int quadrantQ = quadrant(origin, q); @@ -103,7 +158,7 @@ PolygonNodeTopology::isAngleGreater(const Coordinate* origin, /* private static */ int -PolygonNodeTopology::quadrant(const Coordinate* origin, const Coordinate* p) +PolygonNodeTopology::quadrant(const CoordinateXY* origin, const CoordinateXY* p) { double dx = p->x - origin->x; double dy = p->y - origin->y; diff --git a/Sources/geos/src/algorithm/RayCrossingCounter.cpp b/Sources/geos/src/algorithm/RayCrossingCounter.cpp index 96e4769..9e25deb 100644 --- a/Sources/geos/src/algorithm/RayCrossingCounter.cpp +++ b/Sources/geos/src/algorithm/RayCrossingCounter.cpp @@ -18,11 +18,15 @@ #include #include -#include -#include +#include #include #include +#include +#include +#include +#include +using geos::geom::CoordinateXY; namespace geos { namespace algorithm { @@ -38,14 +42,14 @@ namespace algorithm { // public: // /*static*/ geom::Location -RayCrossingCounter::locatePointInRing(const geom::Coordinate& point, +RayCrossingCounter::locatePointInRing(const geom::CoordinateXY& point, const geom::CoordinateSequence& ring) { RayCrossingCounter rcc(point); for(std::size_t i = 1, ni = ring.size(); i < ni; i++) { - const geom::Coordinate& p1 = ring[ i - 1 ]; - const geom::Coordinate& p2 = ring[ i ]; + const geom::CoordinateXY& p1 = ring.getAt(i-1);; + const geom::CoordinateXY& p2 = ring.getAt(i); rcc.countSegment(p1, p2); @@ -57,7 +61,7 @@ RayCrossingCounter::locatePointInRing(const geom::Coordinate& point, } /*static*/ geom::Location -RayCrossingCounter::locatePointInRing(const geom::Coordinate& point, +RayCrossingCounter::locatePointInRing(const geom::CoordinateXY& point, const std::vector& ring) { RayCrossingCounter rcc(point); @@ -75,9 +79,56 @@ RayCrossingCounter::locatePointInRing(const geom::Coordinate& point, return rcc.getLocation(); } +geom::Location +RayCrossingCounter::locatePointInRing(const geom::CoordinateXY& point, + const geom::Curve& ring) +{ + RayCrossingCounter rcc(point); + + for (std::size_t i = 0; i < ring.getNumCurves(); i++) { + const geom::SimpleCurve* curve = ring.getCurveN(i); + rcc.processSequence(*curve->getCoordinatesRO(), !curve->hasCurvedComponents()); + } + + return rcc.getLocation(); +} + +void +RayCrossingCounter::processSequence(const geom::CoordinateSequence& seq, bool isLinear) +{ + if (isOnSegment()) { + return; + } + + if (isLinear) { + for(std::size_t i = 1; i < seq.size(); i++) { + const geom::CoordinateXY& p1 = seq.getAt(i-1);; + const geom::CoordinateXY& p2 = seq.getAt(i); + + countSegment(p1, p2); + + if (isOnSegment()) { + return; + } + } + } else { + for (std::size_t i = 2; i < seq.size(); i += 2) { + const geom::CoordinateXY& p1 = seq.getAt(i-2); + const geom::CoordinateXY& p2 = seq.getAt(i-1); + const geom::CoordinateXY& p3 = seq.getAt(i); + + countArc(p1, p2, p3); + + if (isOnSegment()) { + return; + } + } + } +} + void -RayCrossingCounter::countSegment(const geom::Coordinate& p1, - const geom::Coordinate& p2) +RayCrossingCounter::countSegment(const geom::CoordinateXY& p1, + const geom::CoordinateXY& p2) { // For each segment, check if it crosses // a horizontal ray running from the test point in @@ -119,8 +170,8 @@ RayCrossingCounter::countSegment(const geom::Coordinate& p1, // final endpoint // - a downward edge excludes its starting endpoint, and includes its // final endpoint - if(((p1.y > point.y) && (p2.y <= point.y)) || - ((p2.y > point.y) && (p1.y <= point.y))) { + + if((p1.y > point.y && p2.y <= point.y) || (p2.y > point.y && p1.y <= point.y)) { // For an upward edge, orientationIndex will be positive when p1->p2 // crosses ray. Conversely, downward edges should have negative sign. int sign = CGAlgorithmsDD::orientationIndex(p1, p2, point); @@ -140,6 +191,107 @@ RayCrossingCounter::countSegment(const geom::Coordinate& p1, } } +bool +RayCrossingCounter::shouldCountCrossing(const geom::CircularArc& arc, const geom::CoordinateXY& q) { + // To avoid double-counting shared vertices, we count an intersection point if + // a) is in the interior of the arc + // b) is at the starting point of the arc, and the arc is directed upward at that point + // c) is at the ending point of the arc is directed downward at that point + if (q.equals2D(arc.p0)) { + return arc.isUpwardAtPoint(q); + } else if (q.equals2D(arc.p2)) { + return !arc.isUpwardAtPoint(q); + } else { + return true; + } +} + +/// Return an array of 0-2 intersection points between an arc and a horizontal +/// ray extending righward from a point. If fewer than 2 intersection points exist, +/// some Coordinates in the returned array will be equal to CoordinateXY::getNull(). +std::array +RayCrossingCounter::pointsIntersectingHorizontalRay(const geom::CircularArc& arc, const geom::CoordinateXY& origin) { + const auto& c = arc.getCenter(); + const auto& R = arc.getRadius(); + + auto dx = std::sqrt(R*R - std::pow(origin.y - c.y, 2) ); + + // Find two points where the horizontal line intersects the circle + // that is coincident with this arc. + // Problem: because of floating-point errors, these + // constructed points may not actually like on the circle. + CoordinateXY intPt1{c.x - dx, origin.y}; + CoordinateXY intPt2{c.x + dx, origin.y}; + + // Solution (best we have for now) + // Snap computed points to points that define the arc + double eps = 1e-14; + + for (const CoordinateXY& pt : arc ) { + if (origin.y == pt.y) { + if (intPt1.distance(pt) < eps) { + intPt1 = pt; + } + if (intPt2.distance(pt) < eps) { + intPt2 = pt; + } + } + } + + std::array ret { CoordinateXY::getNull(), CoordinateXY::getNull() }; + + std::size_t pos = 0; + if (intPt1.x >= origin.x && arc.containsPointOnCircle(intPt1)) { + ret[pos++] = intPt1; + } + if (intPt2.x >= origin.x && arc.containsPointOnCircle(intPt2)) { + ret[pos++] = intPt2; + } + + return ret; +} + +void +RayCrossingCounter::countArc(const CoordinateXY& p1, + const CoordinateXY& p2, + const CoordinateXY& p3) +{ + // For each arc, check if it crosses + // a horizontal ray running from the test point in + // the positive x direction. + geom::CircularArc arc(p1, p2, p3); + + // If the arc is degenerate, process it is two line segments + if (arc.isLinear()) { + countSegment(p1, p2); + countSegment(p2, p3); + return; + } + + // Check if the arc is strictly to the left of the test point + geom::Envelope arcEnvelope; + CircularArcs::expandEnvelope(arcEnvelope, p1, p2, p3); + + if (arcEnvelope.getMaxX() < point.x) { + return; + } + + // Evaluate all arcs whose enveleope is to the right of the test point. + if (arcEnvelope.getMaxY() >= point.y && arcEnvelope.getMinY() <= point.y) { + if (arc.containsPoint(point)) { + isPointOnSegment = true; + return; + } + + auto intPts = pointsIntersectingHorizontalRay(arc, point); + if (!intPts[0].isNull() && shouldCountCrossing(arc, intPts[0])) { + crossingCount++; + } + if (!intPts[1].isNull() && shouldCountCrossing(arc, intPts[1])) { + crossingCount++; + } + } +} geom::Location RayCrossingCounter::getLocation() const diff --git a/Sources/geos/src/algorithm/RayCrossingCounterDD.cpp b/Sources/geos/src/algorithm/RayCrossingCounterDD.cpp deleted file mode 100644 index eb8d8fb..0000000 --- a/Sources/geos/src/algorithm/RayCrossingCounterDD.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2018 Paul Ramsey - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - ********************************************************************** - * - * Last port: algorithm/RayCrossingCounterDD.java rev. 1.2 (JTS-1.9) - * - **********************************************************************/ - -#include -#include -#include -#include -#include -#include - - -namespace geos { -namespace algorithm { -// -// private: -// - -// -// protected: -// - -// -// public: -// -/*static*/ -geom::Location -RayCrossingCounterDD::locatePointInRing(const geom::Coordinate& point, - const geom::CoordinateSequence& ring) -{ - RayCrossingCounterDD rcc(point); - - for(std::size_t i = 1, ni = ring.size(); i < ni; i++) { - const geom::Coordinate& p1 = ring[ i - 1 ]; - const geom::Coordinate& p2 = ring[ i ]; - - rcc.countSegment(p1, p2); - - if(rcc.isOnSegment()) { - return rcc.getLocation(); - } - } - return rcc.getLocation(); -} - -/*static*/ -geom::Location -RayCrossingCounterDD::locatePointInRing(const geom::Coordinate& point, - const std::vector& ring) -{ - RayCrossingCounterDD rcc(point); - - for(std::size_t i = 1, ni = ring.size(); i < ni; i++) { - const geom::Coordinate& p1 = *ring[ i - 1 ]; - const geom::Coordinate& p2 = *ring[ i ]; - - rcc.countSegment(p1, p2); - - if(rcc.isOnSegment()) { - return rcc.getLocation(); - } - } - return rcc.getLocation(); -} - -/*public static*/ -int -RayCrossingCounterDD::orientationIndex(const geom::Coordinate& p1, - const geom::Coordinate& p2, const geom::Coordinate& q) -{ - return CGAlgorithmsDD::orientationIndex(p1, p2, q); -} - -void -RayCrossingCounterDD::countSegment(const geom::Coordinate& p1, - const geom::Coordinate& p2) -{ - // For each segment, check if it crosses - // a horizontal ray running from the test point in - // the positive x direction. - - // check if the segment is strictly to the left of the test point - if(p1.x < point.x && p2.x < point.x) { - return; - } - - // check if the point is equal to the current ring vertex - if(point.x == p2.x && point.y == p2.y) { - isPointOnSegment = true; - return; - } - - // For horizontal segments, check if the point is on the segment. - // Otherwise, horizontal segments are not counted. - if(p1.y == point.y && p2.y == point.y) { - double minx = p1.x; - double maxx = p2.x; - - if(minx > maxx) { - minx = p2.x; - maxx = p1.x; - } - - if(point.x >= minx && point.x <= maxx) { - isPointOnSegment = true; - } - - return; - } - - // Evaluate all non-horizontal segments which cross a horizontal ray - // to the right of the test pt. - // To avoid double-counting shared vertices, we use the convention that - // - an upward edge includes its starting endpoint, and excludes its - // final endpoint - // - a downward edge excludes its starting endpoint, and includes its - // final endpoint - if(((p1.y > point.y) && (p2.y <= point.y)) || - ((p2.y > point.y) && (p1.y <= point.y))) { - // For an upward edge, orientationIndex will be positive when p1->p2 - // crosses ray. Conversely, downward edges should have negative sign. - int sign = CGAlgorithmsDD::orientationIndex(p1, p2, point); - if(sign == CGAlgorithmsDD::STRAIGHT) { - isPointOnSegment = true; - return; - } - - if(p2.y < p1.y) { - sign = -sign; - } - - // The segment crosses the ray if the sign is strictly positive. - if(sign == CGAlgorithmsDD::LEFT) { - crossingCount++; - } - } -} - - -geom::Location -RayCrossingCounterDD::getLocation() -{ - if(isPointOnSegment) { - return geom::Location::BOUNDARY; - } - - // The point is in the interior of the ring if the number - // of X-crossings is odd. - if((crossingCount % 2) == 1) { - return geom::Location::INTERIOR; - } - - return geom::Location::EXTERIOR; -} - - -bool -RayCrossingCounterDD::isPointInPolygon() -{ - return getLocation() != geom::Location::EXTERIOR; -} - - -} // geos::algorithm -} // geos diff --git a/Sources/geos/src/algorithm/Rectangle.cpp b/Sources/geos/src/algorithm/Rectangle.cpp new file mode 100644 index 0000000..7be35a4 --- /dev/null +++ b/Sources/geos/src/algorithm/Rectangle.cpp @@ -0,0 +1,126 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include +#include +#include +#include + +using geos::geom::CoordinateXY; +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; +using geos::geom::GeometryFactory; +using geos::geom::LineSegment; +using geos::geom::LinearRing; +using geos::geom::Polygon; + + +namespace geos { +namespace algorithm { // geos.algorithm + + +/* public static */ +std::unique_ptr +Rectangle::createFromSidePts( + const CoordinateXY& baseRightPt, + const CoordinateXY& baseLeftPt, + const CoordinateXY& oppositePt, + const CoordinateXY& leftSidePt, + const CoordinateXY& rightSidePt, + const GeometryFactory* factory) +{ + //-- deltas for the base segment provide slope + double dx = baseLeftPt.x - baseRightPt.x; + double dy = baseLeftPt.y - baseRightPt.y; + // Assert: dx and dy are not both zero + + double baseC = computeLineEquationC(dx, dy, baseRightPt); + double oppC = computeLineEquationC(dx, dy, oppositePt); + double leftC = computeLineEquationC(-dy, dx, leftSidePt); + double rightC = computeLineEquationC(-dy, dx, rightSidePt); + + //-- compute lines along edges of rectangle + LineSegment baseLine = createLineForStandardEquation(-dy, dx, baseC); + LineSegment oppLine = createLineForStandardEquation(-dy, dx, oppC); + LineSegment leftLine = createLineForStandardEquation(-dx, -dy, leftC); + LineSegment rightLine = createLineForStandardEquation(-dx, -dy, rightC); + + /** + * Corners of rectangle are the intersections of the + * base and opposite, and left and right lines. + * The rectangle is constructed with CW orientation. + * The first side of the constructed rectangle contains the base segment. + * + * If a corner coincides with a input point + * the exact value is used to avoid numerical inaccuracy. + */ + CoordinateXY p0 = rightSidePt.equals2D(baseRightPt) ? baseRightPt + : baseLine.lineIntersection(rightLine); + CoordinateXY p1 = leftSidePt.equals2D(baseLeftPt) ? baseLeftPt + : baseLine.lineIntersection(leftLine); + CoordinateXY p2 = leftSidePt.equals2D(oppositePt) ? oppositePt + : oppLine.lineIntersection(leftLine); + CoordinateXY p3 = rightSidePt.equals2D(oppositePt) ? oppositePt + : oppLine.lineIntersection(rightLine); + + CoordinateSequence cs({ p0, p1, p2, p3, p0 }); + return factory->createPolygon(std::move(cs)); +} + + + +/* private static */ +double +Rectangle::computeLineEquationC(double a, double b, const CoordinateXY& p) +{ + return a * p.y - b * p.x; +} + + +/* private static */ +LineSegment +Rectangle::createLineForStandardEquation(double a, double b, double c) +{ + Coordinate p0; + Coordinate p1; + /* + * Line equation is ax + by = c + * Slope m = -a/b. + * Y-intercept = c/b + * X-intercept = c/a + * + * If slope is low, use constant X values; if high use Y values. + * This handles lines that are vertical (b = 0, m = Inf ) + * and horizontal (a = 0, m = 0). + */ + if (std::abs(b) > std::abs(a)) { + //-- abs(m) < 1 + p0 = Coordinate(0.0, c/b); + p1 = Coordinate(1.0, c/b - a/b); + } + else { + //-- abs(m) >= 1 + p0 = Coordinate(c/a, 0.0); + p1 = Coordinate(c/a - b/a, 1.0); + } + return LineSegment(p0, p1); +} + + + +} // namespace geos.algorithm +} // namespace geos + diff --git a/Sources/geos/src/algorithm/RobustDeterminant.cpp b/Sources/geos/src/algorithm/RobustDeterminant.cpp index 0b5cf66..33b9371 100644 --- a/Sources/geos/src/algorithm/RobustDeterminant.cpp +++ b/Sources/geos/src/algorithm/RobustDeterminant.cpp @@ -52,7 +52,7 @@ RobustDeterminant::signOfDet2x2(double x1, double y1, double x2, double y2) { // returns -1 if the determinant is negative, // returns 1 if the determinant is positive, - // retunrs 0 if the determinant is null. + // returns 0 if the determinant is null. int sign = 1; double swap; diff --git a/Sources/geos/src/algorithm/construct/IndexedDistanceToPoint.cpp b/Sources/geos/src/algorithm/construct/IndexedDistanceToPoint.cpp new file mode 100644 index 0000000..e1fd585 --- /dev/null +++ b/Sources/geos/src/algorithm/construct/IndexedDistanceToPoint.cpp @@ -0,0 +1,69 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + ********************************************************************** + * + * Last port: algorithm/construct/IndexedDistanceToPoint.java + * https://github.com/locationtech/jts/commit/d92f783163d9440fcc10c729143787bf7b9fe8f9 + * + **********************************************************************/ + +#include +#include + +//using namespace geos::geom; +using geos::geom::Location; + +namespace geos { +namespace algorithm { // geos.algorithm +namespace construct { // geos.algorithm.construct + +IndexedDistanceToPoint::IndexedDistanceToPoint(const Geometry& geom) + : targetGeometry(geom) +{ +} + +/* private */ +void IndexedDistanceToPoint::init() +{ + if (facetDistance != nullptr) + return; + ptLocator.reset(new IndexedPointInPolygonsLocator(targetGeometry)); + facetDistance.reset(new IndexedFacetDistance(&targetGeometry)); +} + +/* public */ +double IndexedDistanceToPoint::distance(const Point& pt) +{ + init(); + //-- distance is 0 if point is inside a target polygon + if (isInArea(pt)) { + return 0; + } + return facetDistance->distance(&pt); +} + +/* private */ +bool IndexedDistanceToPoint::isInArea(const Point& pt) +{ + return Location::EXTERIOR != ptLocator->locate(pt.getCoordinate()); +} + +/* public */ +std::unique_ptr +IndexedDistanceToPoint::nearestPoints(const Point& pt) +{ + init(); + return facetDistance->nearestPoints(&pt); +} + +}}} diff --git a/Sources/geos/src/algorithm/construct/IndexedPointInPolygonsLocator.cpp b/Sources/geos/src/algorithm/construct/IndexedPointInPolygonsLocator.cpp new file mode 100644 index 0000000..bb9e51a --- /dev/null +++ b/Sources/geos/src/algorithm/construct/IndexedPointInPolygonsLocator.cpp @@ -0,0 +1,70 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + ********************************************************************** + * + * Last port: algorithm/construct/IndexedDistanceToPoint.java + * https://github.com/locationtech/jts/commit/d92f783163d9440fcc10c729143787bf7b9fe8f9 + * + **********************************************************************/ + +#include +#include +#include +#include + +using geos::algorithm::locate::IndexedPointInAreaLocator; +using geos::geom::Envelope; +using geos::geom::util::PolygonalExtracter; + +namespace geos { +namespace algorithm { // geos.algorithm +namespace construct { // geos.algorithm.construct + +/* public */ +IndexedPointInPolygonsLocator::IndexedPointInPolygonsLocator(const Geometry& g) + : geom(g), isInitialized(false) +{ +} + +/* private */ +void IndexedPointInPolygonsLocator::init() +{ + if (isInitialized) { + return; + } + isInitialized = true; + std::vector polys; + PolygonalExtracter::getPolygonals(geom, polys); + for (const Geometry* poly : polys) { + IndexedPointInAreaLocator* ptLocator = new IndexedPointInAreaLocator(*poly); + locators.emplace_back(ptLocator); + index.insert(poly->getEnvelopeInternal(), ptLocator); + } +} + +/* public */ +Location IndexedPointInPolygonsLocator::locate(const CoordinateXY* /*const*/ pt) +{ + init(); + Envelope queryEnv(*pt); + std::vector result; + index.query(queryEnv, result); + for (IndexedPointInAreaLocator* ptLocater : result) { + Location loc = ptLocater->locate(pt); + if (loc != Location::EXTERIOR) + return loc; + } + return Location::EXTERIOR; +} + +}}} \ No newline at end of file diff --git a/Sources/geos/src/algorithm/construct/LargestEmptyCircle.cpp b/Sources/geos/src/algorithm/construct/LargestEmptyCircle.cpp index 2cdd47f..c4fc40f 100644 --- a/Sources/geos/src/algorithm/construct/LargestEmptyCircle.cpp +++ b/Sources/geos/src/algorithm/construct/LargestEmptyCircle.cpp @@ -18,9 +18,9 @@ **********************************************************************/ #include +#include #include #include -#include #include #include #include @@ -29,19 +29,17 @@ #include #include #include +#include #include // for dynamic_cast #include using namespace geos::geom; - namespace geos { namespace algorithm { // geos.algorithm namespace construct { // geos.algorithm.construct - - LargestEmptyCircle::LargestEmptyCircle(const Geometry* p_obstacles, double p_tolerance) : LargestEmptyCircle(p_obstacles, nullptr, p_tolerance) { @@ -51,32 +49,17 @@ LargestEmptyCircle::LargestEmptyCircle(const Geometry* p_obstacles, const Geomet : tolerance(p_tolerance) , obstacles(p_obstacles) , factory(p_obstacles->getFactory()) - , obstacleDistance(p_obstacles) , done(false) + , obstacleDistance(*p_obstacles) { - if (!p_boundary) - { - boundary = p_obstacles->convexHull(); - } - else - { - boundary = p_boundary->clone(); - } - if (obstacles->isEmpty()) { throw util::IllegalArgumentException("Empty obstacles geometry is not supported"); } - if (boundary->isEmpty()) { - throw util::IllegalArgumentException("Empty obstacles geometry is not supported"); + if (! p_boundary || p_boundary->isEmpty()) { + boundary = obstacles->convexHull(); } - if (!boundary->covers(obstacles)) { - throw util::IllegalArgumentException("Boundary geometry does not cover obstacles"); - } - - // if boundary does not enclose an area cannot create a ptLocator - if (boundary->getDimension() >= 2) { - ptLocator.reset(new algorithm::locate::IndexedPointInAreaLocator(*(boundary.get()))); - boundaryDistance.reset(new operation::distance::IndexedFacetDistance(boundary.get())); + else { + boundary = p_boundary->clone(); } } @@ -118,7 +101,7 @@ LargestEmptyCircle::getRadiusLine() { compute(); - auto cl = factory->getCoordinateSequenceFactory()->create(2); + auto cl = detail::make_unique(2u); cl->setAt(centerPt, 0); cl->setAt(radiusPt, 1); return factory->createLineString(std::move(cl)); @@ -129,21 +112,20 @@ LargestEmptyCircle::getRadiusLine() void LargestEmptyCircle::createInitialGrid(const Envelope* env, std::priority_queue& cellQueue) { - double minX = env->getMinX(); - double maxX = env->getMaxX(); - double minY = env->getMinY(); - double maxY = env->getMaxY(); - double width = env->getWidth(); - double height = env->getHeight(); - double cellSize = std::min(width, height); - double hSize = cellSize / 2.0; - - // compute initial grid of cells to cover area - for (double x = minX; x < maxX; x += cellSize) { - for (double y = minY; y < maxY; y += cellSize) { - cellQueue.emplace(x+hSize, y+hSize, hSize, distanceToConstraints(x+hSize, y+hSize)); - } + if (!env->isFinite()) { + throw util::GEOSException("Non-finite envelope encountered."); } + + double cellSize = std::max(env->getWidth(), env->getHeight()); + double hSide = cellSize / 2.0; + + // Collapsed geometries just end up using the centroid + // as the answer and skip all the other machinery + if (cellSize == 0) return; + + CoordinateXY c; + env->centre(c); + cellQueue.emplace(c.x, c.y, hSide, distanceToConstraints(c.x, c.y)); } /* private */ @@ -182,14 +164,14 @@ LargestEmptyCircle::mayContainCircleCenter(const Cell& cell, const Cell& farthes double LargestEmptyCircle::distanceToConstraints(const Coordinate& c) { - bool isOutside = ptLocator && (Location::EXTERIOR == ptLocator->locate(&c)); + bool isOutside = boundaryPtLocater && (Location::EXTERIOR == boundaryPtLocater->locate(&c)); std::unique_ptr pt(factory->createPoint(c)); if (isOutside) { double boundaryDist = boundaryDistance->distance(pt.get()); return -boundaryDist; } - double dist = obstacleDistance.distance(pt.get()); + double dist = obstacleDistance.distance(*(pt.get())); return dist; } @@ -211,18 +193,29 @@ LargestEmptyCircle::createCentroidCell(const Geometry* geom) return cell; } +/* private */ +void +LargestEmptyCircle::initBoundary() +{ + gridEnv = *(boundary->getEnvelopeInternal()); + // if boundary does not enclose an area cannot create a ptLocator + if (boundary->getDimension() >= 2) { + boundaryPtLocater = detail::make_unique(*(boundary.get())); + boundaryDistance = detail::make_unique(boundary.get()); + } +} /* private */ void LargestEmptyCircle::compute() { - // check if already computed if (done) return; + initBoundary(); // if ptLocator is not present then result is degenerate (represented as zero-radius circle) - if (!ptLocator) { - const Coordinate* pt = obstacles->getCoordinate(); + if (!boundaryPtLocater) { + const CoordinateXY* pt = obstacles->getCoordinate(); centerPt = *pt; radiusPt = *pt; done = true; @@ -231,7 +224,7 @@ LargestEmptyCircle::compute() // Priority queue of cells, ordered by decreasing distance from constraints std::priority_queue cellQueue; - createInitialGrid(obstacles->getEnvelopeInternal(), cellQueue); + createInitialGrid(&gridEnv, cellQueue); Cell farthestCell = createCentroidCell(obstacles); @@ -239,12 +232,18 @@ LargestEmptyCircle::compute() * Carry out the branch-and-bound search * of the cell space */ - while (!cellQueue.empty()) { + std::size_t maxIter = MaximumInscribedCircle::computeMaximumIterations(boundary.get(), tolerance); + std::size_t iterationCount = 0; + while (!cellQueue.empty() && iterationCount < maxIter) { // pick the most promising cell from the queue Cell cell = cellQueue.top(); cellQueue.pop(); + if ((iterationCount++ % 1000) == 0) { + GEOS_CHECK_FOR_INTERRUPTS(); + } + // update the center cell if the candidate is further from the constraints if (cell.getDistance() > farthestCell.getDistance()) { farthestCell = cell; @@ -274,8 +273,8 @@ LargestEmptyCircle::compute() // compute radius point std::unique_ptr centerPoint(factory->createPoint(centerPt)); - std::vector nearestPts = obstacleDistance.nearestPoints(centerPoint.get()); - radiusPt = nearestPts[0]; + const auto& nearestPts = obstacleDistance.nearestPoints(*(centerPoint.get())); + radiusPt = nearestPts->getAt(0); // flag computation done = true; @@ -286,5 +285,3 @@ LargestEmptyCircle::compute() } // namespace geos.algorithm.construct } // namespace geos.algorithm } // namespace geos - - diff --git a/Sources/geos/src/algorithm/construct/MaximumInscribedCircle.cpp b/Sources/geos/src/algorithm/construct/MaximumInscribedCircle.cpp index 1394131..c3a4710 100644 --- a/Sources/geos/src/algorithm/construct/MaximumInscribedCircle.cpp +++ b/Sources/geos/src/algorithm/construct/MaximumInscribedCircle.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -29,6 +28,7 @@ #include #include #include +#include #include // for dynamic_cast #include @@ -78,13 +78,24 @@ MaximumInscribedCircle::getRadiusLine(const Geometry* polygonal, double toleranc return mic.getRadiusLine(); } +/* public static */ +std::size_t +MaximumInscribedCircle::computeMaximumIterations(const Geometry* geom, double toleranceDist) +{ + double diam = geom->getEnvelopeInternal()->getDiameter(); + double ncells = diam / toleranceDist; + //-- Using log of ncells allows control over number of iterations + int factor = (int) std::log(ncells); + if (factor < 1) factor = 1; + return (std::size_t) (2000 + 2000 * factor); +} + /* public */ std::unique_ptr MaximumInscribedCircle::getCenter() { compute(); - auto pt = factory->createPoint(centerPt); - return std::unique_ptr(pt); + return factory->createPoint(centerPt); } /* public */ @@ -92,8 +103,7 @@ std::unique_ptr MaximumInscribedCircle::getRadiusPoint() { compute(); - auto pt = factory->createPoint(radiusPt); - return std::unique_ptr(pt); + return factory->createPoint(radiusPt); } /* public */ @@ -102,39 +112,32 @@ MaximumInscribedCircle::getRadiusLine() { compute(); - auto cl = factory->getCoordinateSequenceFactory()->create(2); + auto cl = detail::make_unique(2u); cl->setAt(centerPt, 0); cl->setAt(radiusPt, 1); return factory->createLineString(std::move(cl)); } +int INITIAL_GRID_SIDE = 25; + /* private */ void -MaximumInscribedCircle::createInitialGrid(const Envelope* env, std::priority_queue& cellQueue) +MaximumInscribedCircle::createInitialGrid(const Envelope* env, Cell::CellQueue& cellQueue) { - if (!std::isfinite(env->getArea())) { + if (!env->isFinite()) { throw util::GEOSException("Non-finite envelope encountered."); } - double minX = env->getMinX(); - double maxX = env->getMaxX(); - double minY = env->getMinY(); - double maxY = env->getMaxY(); - double width = env->getWidth(); - double height = env->getHeight(); - double cellSize = std::min(width, height); - double hSize = cellSize / 2.0; + double cellSize = std::max(env->getWidth(), env->getHeight()); + double hSide = cellSize / 2.0; // Collapsed geometries just end up using the centroid // as the answer and skip all the other machinery if (cellSize == 0) return; - // compute initial grid of cells to cover area - for (double x = minX; x < maxX; x += cellSize) { - for (double y = minY; y < maxY; y += cellSize) { - cellQueue.emplace(x+hSize, y+hSize, hSize, distanceToBoundary(x+hSize, y+hSize)); - } - } + CoordinateXY c; + env->centre(c); + cellQueue.emplace(c.x, c.y, hSide, distanceToBoundary(c.x, c.y)); } /* private */ @@ -159,11 +162,11 @@ MaximumInscribedCircle::distanceToBoundary(double x, double y) /* private */ MaximumInscribedCircle::Cell -MaximumInscribedCircle::createCentroidCell(const Geometry* geom) +MaximumInscribedCircle::createInteriorPointCell(const Geometry* geom) { Coordinate c; - geom->getCentroid(c); - Cell cell(c.x, c.y, 0, distanceToBoundary(c)); + std::unique_ptr p = geom->getInteriorPoint(); + Cell cell(p->getX(), p->getY(), 0, distanceToBoundary(c)); return cell; } @@ -176,23 +179,32 @@ MaximumInscribedCircle::compute() if (done) return; // Priority queue of cells, ordered by maximum distance from boundary - std::priority_queue cellQueue; + Cell::CellQueue cellQueue; createInitialGrid(inputGeom->getEnvelopeInternal(), cellQueue); // use the area centroid as the initial candidate center point - Cell farthestCell = createCentroidCell(inputGeom); + Cell farthestCell = createInteriorPointCell(inputGeom); /** * Carry out the branch-and-bound search * of the cell space */ - while (!cellQueue.empty()) { + std::size_t maxIter = computeMaximumIterations(inputGeom, tolerance); + std::size_t iterationCount = 0; + while (!cellQueue.empty() && iterationCount < maxIter) { // pick the most promising cell from the queue Cell cell = cellQueue.top(); cellQueue.pop(); +// std::cout << iterationCount << "] Dist: " << cell.getDistance() << " size: " << cell.getHSize() << std::endl; - // std::cout << i << ": (" << cell.getX() << ", " << cell.getY() << ") " << cell.getHSize() << " dist = " << cell.getDistance() << std::endl; + if ((iterationCount++ % 1000) == 0) { + GEOS_CHECK_FOR_INTERRUPTS(); + } + + //-- if cell must be closer than furthest, terminate since all remaining cells in queue are even closer. + if (cell.getMaxDistance() < farthestCell.getDistance()) + break; // update the center cell if the candidate is further from the boundary if (cell.getDistance() > farthestCell.getDistance()) { @@ -215,7 +227,6 @@ MaximumInscribedCircle::compute() cellQueue.emplace(cell.getX()+h2, cell.getY()+h2, h2, distanceToBoundary(cell.getX()+h2, cell.getY()+h2)); } } - // std::cout << "number of iterations: " << i << std::endl; // the farthest cell is the best approximation to the MIC center Cell centerCell = farthestCell; @@ -224,8 +235,8 @@ MaximumInscribedCircle::compute() // compute radius point std::unique_ptr centerPoint(factory->createPoint(centerPt)); - std::vector nearestPts = indexedDistance.nearestPoints(centerPoint.get()); - radiusPt = nearestPts[0]; + const auto& nearestPts = indexedDistance.nearestPoints(centerPoint.get()); + radiusPt = nearestPts->getAt(0); // flag computation done = true; @@ -237,4 +248,3 @@ MaximumInscribedCircle::compute() } // namespace geos.algorithm.construct } // namespace geos.algorithm } // namespace geos - diff --git a/Sources/geos/src/algorithm/distance/DiscreteFrechetDistance.cpp b/Sources/geos/src/algorithm/distance/DiscreteFrechetDistance.cpp index 923f2af..99a650f 100644 --- a/Sources/geos/src/algorithm/distance/DiscreteFrechetDistance.cpp +++ b/Sources/geos/src/algorithm/distance/DiscreteFrechetDistance.cpp @@ -28,6 +28,8 @@ #include #include #include + +#include "geos/util.h" using namespace geos::geom; namespace geos { @@ -100,7 +102,7 @@ DiscreteFrechetDistance::getSegmentAt(const CoordinateSequence& seq, std::size_t } PointPairDistance& -DiscreteFrechetDistance::getFrecheDistance(std::vector< std::vector >& ca, std::size_t i, std::size_t j, +DiscreteFrechetDistance::getFrechetDistance(std::vector< std::vector >& ca, std::size_t i, std::size_t j, const CoordinateSequence& p, const CoordinateSequence& q) { PointPairDistance p_ptDist; @@ -112,17 +114,17 @@ DiscreteFrechetDistance::getFrecheDistance(std::vector< std::vector 0 && j == 0) { - PointPairDistance nextDist = getFrecheDistance(ca, i - 1, 0, p, q); + PointPairDistance nextDist = getFrechetDistance(ca, i - 1, 0, p, q); ca[i][j] = (nextDist.getDistance() > p_ptDist.getDistance()) ? nextDist : p_ptDist; } else if(i == 0 && j > 0) { - PointPairDistance nextDist = getFrecheDistance(ca, 0, j - 1, p, q); + PointPairDistance nextDist = getFrechetDistance(ca, 0, j - 1, p, q); ca[i][j] = (nextDist.getDistance() > p_ptDist.getDistance()) ? nextDist : p_ptDist; } else { - PointPairDistance d1 = getFrecheDistance(ca, i - 1, j, p, q), - d2 = getFrecheDistance(ca, i - 1, j - 1, p, q), - d3 = getFrecheDistance(ca, i, j - 1, p, q); + PointPairDistance d1 = getFrechetDistance(ca, i - 1, j, p, q), + d2 = getFrechetDistance(ca, i - 1, j - 1, p, q), + d3 = getFrechetDistance(ca, i, j - 1, p, q); PointPairDistance& minDist = (d1.getDistance() < d2.getDistance()) ? d1 : d2; if(d3.getDistance() < minDist.getDistance()) { minDist = d3; @@ -138,6 +140,13 @@ DiscreteFrechetDistance::compute( const geom::Geometry& discreteGeom, const geom::Geometry& geom) { + if (discreteGeom.isEmpty() || geom.isEmpty()) { + throw util::IllegalArgumentException("DiscreteFrechetDistance called with empty inputs."); + } + + util::ensureNoCurvedComponents(discreteGeom); + util::ensureNoCurvedComponents(geom); + auto lp = discreteGeom.getCoordinates(); auto lq = geom.getCoordinates(); std::size_t pSize, qSize; @@ -156,7 +165,7 @@ DiscreteFrechetDistance::compute( ca[i][j].initialize(); } } - ptDist = getFrecheDistance(ca, pSize - 1, qSize - 1, *lp, *lq); + ptDist = getFrechetDistance(ca, pSize - 1, qSize - 1, *lp, *lq); } } // namespace geos.algorithm.distance diff --git a/Sources/geos/src/algorithm/distance/DiscreteHausdorffDistance.cpp b/Sources/geos/src/algorithm/distance/DiscreteHausdorffDistance.cpp index cffc7b6..3712b0a 100644 --- a/Sources/geos/src/algorithm/distance/DiscreteHausdorffDistance.cpp +++ b/Sources/geos/src/algorithm/distance/DiscreteHausdorffDistance.cpp @@ -23,6 +23,8 @@ #include #include +#include "geos/util.h" + using namespace geos::geom; namespace geos { @@ -101,6 +103,9 @@ DiscreteHausdorffDistance::computeOrientedDistance( const geom::Geometry& geom, PointPairDistance& p_ptDist) { + util::ensureNoCurvedComponents(discreteGeom); + util::ensureNoCurvedComponents(geom); + // can't calculate distance with empty if (discreteGeom.isEmpty() || geom.isEmpty()) return; diff --git a/Sources/geos/src/algorithm/distance/DistanceToPoint.cpp b/Sources/geos/src/algorithm/distance/DistanceToPoint.cpp index 36a1c05..b983432 100644 --- a/Sources/geos/src/algorithm/distance/DistanceToPoint.cpp +++ b/Sources/geos/src/algorithm/distance/DistanceToPoint.cpp @@ -37,7 +37,7 @@ namespace distance { // geos.algorithm.distance /* public static */ void DistanceToPoint::computeDistance(const geom::Geometry& geom, - const geom::Coordinate& pt, + const geom::CoordinateXY& pt, PointPairDistance& ptDist) { if (geom.isEmpty()) { @@ -45,7 +45,7 @@ DistanceToPoint::computeDistance(const geom::Geometry& geom, return; } - if(geom.getGeometryTypeId() == GEOS_LINESTRING) { + if(geom.getGeometryTypeId() == GEOS_LINESTRING || geom.getGeometryTypeId() == GEOS_LINEARRING) { const LineString* ls = static_cast(&geom); computeDistance(*ls, pt, ptDist); } @@ -69,7 +69,7 @@ DistanceToPoint::computeDistance(const geom::Geometry& geom, /* public static */ void DistanceToPoint::computeDistance(const geom::LineString& line, - const geom::Coordinate& pt, + const geom::CoordinateXY& pt, PointPairDistance& ptDist) { const CoordinateSequence* coordsRO = line.getCoordinatesRO(); @@ -97,7 +97,7 @@ DistanceToPoint::computeDistance(const geom::LineString& line, /* public static */ void DistanceToPoint::computeDistance(const geom::LineSegment& segment, - const geom::Coordinate& pt, + const geom::CoordinateXY& pt, PointPairDistance& ptDist) { Coordinate closestPt; @@ -108,7 +108,7 @@ DistanceToPoint::computeDistance(const geom::LineSegment& segment, /* public static */ void DistanceToPoint::computeDistance(const geom::Polygon& poly, - const geom::Coordinate& pt, + const geom::CoordinateXY& pt, PointPairDistance& ptDist) { computeDistance(*(poly.getExteriorRing()), pt, ptDist); diff --git a/Sources/geos/src/algorithm/hull/ConcaveHull.cpp b/Sources/geos/src/algorithm/hull/ConcaveHull.cpp index 94b2eb4..d70a80d 100644 --- a/Sources/geos/src/algorithm/hull/ConcaveHull.cpp +++ b/Sources/geos/src/algorithm/hull/ConcaveHull.cpp @@ -28,6 +28,7 @@ #include #include #include +#include using geos::geom::Coordinate; @@ -48,6 +49,17 @@ namespace geos { namespace algorithm { // geos.algorithm namespace hull { // geos.algorithm.hulll +ConcaveHull::ConcaveHull(const Geometry* geom) + : inputGeometry(geom) + , maxEdgeLengthRatio(-1.0) + , alpha(-1) + , isHolesAllowed(false) + , criteriaType(PARAM_EDGE_LENGTH) + , maxSizeInHull(0.0) + , geomFactory(geom->getFactory()) +{ + util::ensureNoCurvedComponents(geom); +} /* public static */ double @@ -103,6 +115,18 @@ ConcaveHull::concaveHullByLengthRatio( return hull.getHull(); } +/* public static */ +std::unique_ptr +ConcaveHull::alphaShape( + const Geometry* geom, + double alpha, + bool holesAllowed) +{ + ConcaveHull hull(geom); + hull.setAlpha(alpha); + hull.setHolesAllowed(holesAllowed); + return hull.getHull(); +} /* public */ void @@ -110,8 +134,9 @@ ConcaveHull::setMaximumEdgeLength(double edgeLength) { if (edgeLength < 0) throw util::IllegalArgumentException("Edge length must be non-negative"); - maxEdgeLength = edgeLength; + maxSizeInHull = edgeLength; maxEdgeLengthRatio = -1.0; + criteriaType = PARAM_EDGE_LENGTH; } @@ -122,6 +147,7 @@ ConcaveHull::setMaximumEdgeLengthRatio(double edgeLengthRatio) if (edgeLengthRatio < 0 || edgeLengthRatio > 1) throw util::IllegalArgumentException("Edge length ratio must be in range [0,1]"); maxEdgeLengthRatio = edgeLengthRatio; + criteriaType = PARAM_EDGE_LENGTH; } @@ -132,6 +158,14 @@ ConcaveHull::setHolesAllowed(bool holesAllowed) isHolesAllowed = holesAllowed; } +/* public */ +void +ConcaveHull::setAlpha(double newAlpha) +{ + alpha = newAlpha; + maxSizeInHull = newAlpha; + criteriaType = PARAM_ALPHA; +} /* public */ std::unique_ptr @@ -142,8 +176,10 @@ ConcaveHull::getHull() } TriList triList; HullTriangulation::createDelaunayTriangulation(inputGeometry, triList); + setSize(triList); + if (maxEdgeLengthRatio >= 0) { - maxEdgeLength = computeTargetEdgeLength(triList, maxEdgeLengthRatio); + maxSizeInHull = computeTargetEdgeLength(triList, maxEdgeLengthRatio); } if (triList.empty()) { return inputGeometry->convexHull(); @@ -153,6 +189,26 @@ ConcaveHull::getHull() return hull; } +/* private */ +void +ConcaveHull::setSize(TriList& triList) +{ + for (auto* tri : triList) { + if (criteriaType == PARAM_EDGE_LENGTH) { + tri->setSizeToLongestEdge(); + } + else { + tri->setSizeToCircumradius(); + } + } +} + +/* private */ +bool +ConcaveHull::isInHull(const HullTri* tri) const +{ + return tri->getSize() < maxSizeInHull; +} /* private static */ double @@ -198,13 +254,13 @@ ConcaveHull::computeHullBorder(TriList& triList) HullTriQueue queue; createBorderQueue(queue, triList); - // remove tris in order of decreasing size (edge length) + // process tris in order of decreasing size (edge length or circumradius) while (! queue.empty()) { HullTri* tri = queue.top(); queue.pop(); - if (isBelowLengthThreshold(tri)) + if (isInHull(tri)) break; if (isRemovableBorder(tri)) { @@ -229,13 +285,7 @@ void ConcaveHull::createBorderQueue(HullTriQueue& queue, TriList& triList) { for (auto* tri : triList) { - //-- add only border triangles which could be eroded - // (if tri has only 1 adjacent it can't be removed because that would isolate a vertex) - if (tri->numAdjacent() != 2){ - continue; - } - tri->setSizeToBoundary(); - queue.push(tri); + addBorderTri(tri, queue); } return; } @@ -247,23 +297,27 @@ ConcaveHull::addBorderTri(HullTri* tri, HullTriQueue& queue) { if (tri == nullptr) return; if (tri->numAdjacent() != 2) return; - tri->setSizeToBoundary(); + setSize(tri); queue.push(tri); } /* private */ -bool -ConcaveHull::isBelowLengthThreshold(const HullTri* tri) const +void +ConcaveHull::setSize(HullTri* tri) { - return tri->lengthOfBoundary() < maxEdgeLength; + if (criteriaType == PARAM_EDGE_LENGTH) + tri->setSizeToBoundary(); + else + tri->setSizeToCircumradius(); } + /* private */ void ConcaveHull::computeHullHoles(TriList& triList) { - std::vector candidateHoles = findCandidateHoles(triList, maxEdgeLength); + std::vector candidateHoles = findCandidateHoles(triList, maxSizeInHull); // remove tris in order of decreasing size (edge length) for (auto* tri : candidateHoles) { if (tri->isRemoved() || @@ -277,17 +331,19 @@ ConcaveHull::computeHullHoles(TriList& triList) /* private static */ std::vector -ConcaveHull::findCandidateHoles(TriList& triList, double minEdgeLen) +ConcaveHull::findCandidateHoles(TriList& triList, double maxSizeInHull) { std::vector candidates; for (auto* tri : triList) { - if (tri->getSize() < minEdgeLen) continue; + //-- tris below the size threshold are in the hull, so NOT in a hole + if (tri->getSize() < maxSizeInHull) continue; + bool isTouchingBoundary = tri->isBorder() || tri->hasBoundaryTouch(); if (! isTouchingBoundary) { candidates.push_back(tri); } } - // sort by HullTri comparator - longest edge length first + // sort by HullTri comparator - larger sizes first std::sort(candidates.begin(), candidates.end(), HullTri::HullTriCompare()); return candidates; } @@ -310,7 +366,7 @@ ConcaveHull::removeHole(TriList& triList, HullTri* triHole) HullTri* tri = queue.top(); queue.pop(); - if (tri != triHole && isBelowLengthThreshold(tri)) { + if (tri != triHole && isInHull(tri)) { break; } diff --git a/Sources/geos/src/algorithm/hull/ConcaveHullOfPolygons.cpp b/Sources/geos/src/algorithm/hull/ConcaveHullOfPolygons.cpp index a774cf6..6ed54fb 100644 --- a/Sources/geos/src/algorithm/hull/ConcaveHullOfPolygons.cpp +++ b/Sources/geos/src/algorithm/hull/ConcaveHullOfPolygons.cpp @@ -26,6 +26,8 @@ #include #include +#include "geos/util.h" + using geos::geom::Coordinate; using geos::geom::Geometry; @@ -111,6 +113,7 @@ ConcaveHullOfPolygons::ConcaveHullOfPolygons(const Geometry* geom) , isHolesAllowed(false) , isTight(false) { + util::ensureNoCurvedComponents(geom); if (! geom->isPolygonal()) { throw util::IllegalArgumentException("Input must be polygonal"); } @@ -153,7 +156,7 @@ ConcaveHullOfPolygons::setTight(bool p_isTight) std::unique_ptr ConcaveHullOfPolygons::getHull() { - if (inputPolygons->isEmpty()) { + if (inputPolygons->isEmpty() || inputPolygons->getArea() == 0) { return createEmptyHull(); } buildHullTris(); diff --git a/Sources/geos/src/algorithm/hull/HullTri.cpp b/Sources/geos/src/algorithm/hull/HullTri.cpp index ef166ab..ca8cfc3 100644 --- a/Sources/geos/src/algorithm/hull/HullTri.cpp +++ b/Sources/geos/src/algorithm/hull/HullTri.cpp @@ -15,9 +15,10 @@ #include #include - +#include using geos::geom::Coordinate; +using geos::geom::Triangle; namespace geos { @@ -38,6 +39,20 @@ HullTri::setSizeToBoundary() m_size = lengthOfBoundary(); } +/* public */ +void +HullTri::setSizeToLongestEdge() +{ + m_size = lengthOfLongestEdge(); +} + +/* public */ +void +HullTri::setSizeToCircumradius() +{ + m_size = Triangle::circumradius(p2, p1, p0); +} + /* public */ bool HullTri::isMarked() const diff --git a/Sources/geos/src/algorithm/hull/HullTriangulation.cpp b/Sources/geos/src/algorithm/hull/HullTriangulation.cpp index bbc1c42..ea8e0d4 100644 --- a/Sources/geos/src/algorithm/hull/HullTriangulation.cpp +++ b/Sources/geos/src/algorithm/hull/HullTriangulation.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,7 @@ using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; using geos::geom::CoordinateList; using geos::geom::Geometry; using geos::geom::GeometryFactory; @@ -102,18 +104,18 @@ HullTriangulation::traceBoundaryPolygon( HullTri* tri = triList[0]; return tri->toPolygon(factory); } - std::vector pts = traceBoundary(triList); + auto&& pts = traceBoundary(triList); return factory->createPolygon(std::move(pts)); } /* private static */ -std::vector +CoordinateSequence HullTriangulation::traceBoundary(TriList& triList) { HullTri* triStart = findBorderTri(triList); - CoordinateList coordList; + CoordinateSequence coordList; HullTri* tri = triStart; do { TriIndex borderIndex = tri->boundaryIndexCCW(); @@ -129,7 +131,7 @@ HullTriangulation::traceBoundary(TriList& triList) tri = nextBorderTri(tri); } while (tri != triStart); coordList.closeRing(); - return *(coordList.toCoordinateArray()); + return coordList; } diff --git a/Sources/geos/src/algorithm/locate/IndexedPointInAreaLocator.cpp b/Sources/geos/src/algorithm/locate/IndexedPointInAreaLocator.cpp index 405c232..ce9ac78 100644 --- a/Sources/geos/src/algorithm/locate/IndexedPointInAreaLocator.cpp +++ b/Sources/geos/src/algorithm/locate/IndexedPointInAreaLocator.cpp @@ -30,7 +30,8 @@ #include #include -#include + +using geos::geom::CoordinateXY; namespace geos { namespace algorithm { @@ -73,7 +74,7 @@ void IndexedPointInAreaLocator::IntervalIndexedGeometry::addLine(const geom::CoordinateSequence* pts) { for(std::size_t i = 1, ni = pts->size(); i < ni; i++) { - SegmentView seg(&pts->getAt(i-1), &pts->getAt(i)); + SegmentView seg(&pts->getAt(i-1), &pts->getAt(i)); auto r = std::minmax(seg.p0().y, seg.p1().y); index.insert(index::strtree::Interval(r.first, r.second), seg); @@ -101,7 +102,7 @@ IndexedPointInAreaLocator::IndexedPointInAreaLocator(const geom::Geometry& g) } geom::Location -IndexedPointInAreaLocator::locate(const geom::Coordinate* /*const*/ p) +IndexedPointInAreaLocator::locate(const geom::CoordinateXY* /*const*/ p) { if (index == nullptr) { buildIndex(areaGeom); diff --git a/Sources/geos/src/algorithm/locate/SimplePointInAreaLocator.cpp b/Sources/geos/src/algorithm/locate/SimplePointInAreaLocator.cpp index ddd1f3f..0a7b8e3 100644 --- a/Sources/geos/src/algorithm/locate/SimplePointInAreaLocator.cpp +++ b/Sources/geos/src/algorithm/locate/SimplePointInAreaLocator.cpp @@ -23,9 +23,6 @@ #include #include -#include -#include - using namespace geos::geom; namespace geos { @@ -38,40 +35,36 @@ namespace locate { // geos.algorithm * is more complex, since it has to take into account the boundaryDetermination rule */ geom::Location -SimplePointInAreaLocator::locate(const Coordinate& p, const Geometry* geom) +SimplePointInAreaLocator::locate(const CoordinateXY& p, const Geometry* geom) { - if(geom->isEmpty()) { - return Location::EXTERIOR; - } - - /* - * Do a fast check against the geometry envelope first - */ - if (! geom->getEnvelopeInternal()->intersects(p)) - return Location::EXTERIOR; - return locateInGeometry(p, geom); } bool -SimplePointInAreaLocator::isContained(const Coordinate& p, const Geometry* geom) +SimplePointInAreaLocator::isContained(const CoordinateXY& p, const Geometry* geom) { return Location::EXTERIOR != locate(p, geom); } geom::Location -SimplePointInAreaLocator::locateInGeometry(const Coordinate& p, const Geometry* geom) +SimplePointInAreaLocator::locateInGeometry(const CoordinateXY& p, const Geometry* geom) { + /* + * Do a fast check against the geometry envelope first + */ + if (! geom->getEnvelopeInternal()->intersects(p)) + return Location::EXTERIOR; + if (geom->getDimension() < 2) { return Location::EXTERIOR; } if (geom->getNumGeometries() == 1) { - auto poly = dynamic_cast(geom->getGeometryN(0)); - if (poly) { - return locatePointInPolygon(p, poly); + auto typ = geom->getGeometryTypeId(); + if (typ == GEOS_POLYGON || typ == GEOS_CURVEPOLYGON) { + auto surface = static_cast(geom); + return locatePointInSurface(p, *surface); } - // Else it is a collection with a single element. Will be handled below. } for (std::size_t i = 0; i < geom->getNumGeometries(); i++) { const Geometry* gi = geom->getGeometryN(i); @@ -85,28 +78,25 @@ SimplePointInAreaLocator::locateInGeometry(const Coordinate& p, const Geometry* } geom::Location -SimplePointInAreaLocator::locatePointInPolygon(const Coordinate& p, const Polygon* poly) +SimplePointInAreaLocator::locatePointInSurface(const CoordinateXY& p, const Surface& surface) { - if(poly->isEmpty()) { + if(surface.isEmpty()) { return Location::EXTERIOR; } - if(!poly->getEnvelopeInternal()->contains(p)) { + if(!surface.getEnvelopeInternal()->contains(p)) { return Location::EXTERIOR; } - const LineString* shell = poly->getExteriorRing(); - const CoordinateSequence* cl; - cl = shell->getCoordinatesRO(); - Location shellLoc = PointLocation::locateInRing(p, *cl); + const Curve& shell = *surface.getExteriorRing(); + Location shellLoc = PointLocation::locateInRing(p, shell); if(shellLoc != Location::INTERIOR) { return shellLoc; } // now test if the point lies in or on the holes - for(std::size_t i = 0, n = poly->getNumInteriorRing(); i < n; i++) { - const LineString* hole = poly->getInteriorRingN(i); - if(hole->getEnvelopeInternal()->contains(p)) { - cl = hole->getCoordinatesRO(); - Location holeLoc = RayCrossingCounter::locatePointInRing(p, *cl); + for(std::size_t i = 0; i < surface.getNumInteriorRing(); i++) { + const Curve& hole = *surface.getInteriorRingN(i); + if(hole.getEnvelopeInternal()->contains(p)) { + Location holeLoc = RayCrossingCounter::locatePointInRing(p, hole); if(holeLoc == Location::BOUNDARY) { return Location::BOUNDARY; } diff --git a/Sources/geos/src/coverage/Corner.cpp b/Sources/geos/src/coverage/Corner.cpp new file mode 100644 index 0000000..743681e --- /dev/null +++ b/Sources/geos/src/coverage/Corner.cpp @@ -0,0 +1,164 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * Copyright (c) 2022 Martin Davis. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + + +#include +#include +#include +#include +#include +#include +#include +#include + +using geos::coverage::Corner; +using geos::geom::Coordinate; +using geos::geom::Envelope; +using geos::geom::GeometryFactory; +using geos::geom::Triangle; +using geos::io::WKTWriter; + +namespace geos { +namespace coverage { // geos.coverage + + +Corner::Corner(const LinkedLine* edge, std::size_t i) + : m_edge(edge) + , m_index(i) + , m_prev(edge->prev(i)) + , m_next(edge->next(i)) + , m_area(area(*edge, i)) + {} + +/* public */ +bool +Corner::isVertex(std::size_t index) const +{ + return index == m_index + || index == m_prev + || index == m_next; +} + +/* public */ +const Coordinate& +Corner::prev() const +{ + return m_edge->getCoordinate(m_prev); +} + +/* public */ +const Coordinate& +Corner::next() const +{ + return m_edge->getCoordinate(m_next); +} + +/* private static */ +double +Corner::area(const LinkedLine& edge, std::size_t index) +{ + const Coordinate& pp = edge.prevCoordinate(index); + const Coordinate& p = edge.getCoordinate(index); + const Coordinate& pn = edge.nextCoordinate(index); + return Triangle::area(pp, p, pn); +} + + +/* public */ +Envelope +Corner::envelope() const +{ + const Coordinate& pp = m_edge->getCoordinate(m_prev); + const Coordinate& p = m_edge->getCoordinate(m_index); + const Coordinate& pn = m_edge->getCoordinate(m_next); + Envelope env(pp, pn); + env.expandToInclude(p); + return env; +} + +/* public */ +bool +Corner::isVertex(const Coordinate& v) const +{ + if (v.equals2D(m_edge->getCoordinate(m_prev))) return true; + if (v.equals2D(m_edge->getCoordinate(m_index))) return true; + if (v.equals2D(m_edge->getCoordinate(m_next))) return true; + return false; +} + +/* public */ +bool +Corner::isBaseline(const Coordinate& p0, const Coordinate& p1) const +{ + const Coordinate& l_prev = prev(); + const Coordinate& l_next = next(); + if (l_prev.equals2D( p0 ) && l_next.equals2D( p1 )) + return true; + if (l_prev.equals2D( p1 ) && l_next.equals2D( p0 )) + return true; + return false; +} + +/* public */ +bool +Corner::intersects(const Coordinate& v) const +{ + const Coordinate& pp = m_edge->getCoordinate(m_prev); + const Coordinate& p = m_edge->getCoordinate(m_index); + const Coordinate& pn = m_edge->getCoordinate(m_next); + return Triangle::intersects(pp, p, pn, v); +} + +/* public */ +bool +Corner::isRemoved() const +{ + return m_edge->prev(m_index) != m_prev + || m_edge->next(m_index) != m_next; +} + +/* public */ +std::unique_ptr +Corner::toLineString() const +{ + Coordinate pp = m_edge->getCoordinate(m_prev); + Coordinate p = m_edge->getCoordinate(m_index); + Coordinate pn = m_edge->getCoordinate(m_next); + + /* safeCoord replacement */ + if (pp.isNull()) pp.x = pp.y = DoubleNotANumber; + if (p.isNull()) p.x = p.y = DoubleNotANumber; + if (pn.isNull()) pn.x = pn.y = DoubleNotANumber; + + CoordinateSequence cs; + cs.add(pp); cs.add(p); cs.add(pn); + + auto gf = GeometryFactory::create(); + return gf->createLineString(std::move(cs)); +} + + +std::ostream& +operator<< (std::ostream& os, const Corner& corner) +{ + WKTWriter writer; + auto ls = corner.toLineString(); + os << writer.write(*ls); + return os; +} + + +} // namespace geos.coverage +} // namespace geos diff --git a/Sources/geos/src/coverage/CoverageBoundarySegmentFinder.cpp b/Sources/geos/src/coverage/CoverageBoundarySegmentFinder.cpp new file mode 100644 index 0000000..22740ac --- /dev/null +++ b/Sources/geos/src/coverage/CoverageBoundarySegmentFinder.cpp @@ -0,0 +1,89 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2022 Martin Davis. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include + + +using geos::geom::CoordinateSequence; +using geos::geom::Geometry; + + +namespace geos { // geos +namespace coverage { // geos.coverage + +// public static +LineSegment::UnorderedSet +CoverageBoundarySegmentFinder::findBoundarySegments( + const std::vector& geoms) +{ + LineSegment::UnorderedSet segs; + CoverageBoundarySegmentFinder finder(segs); + for (const Geometry* geom : geoms) { + geom->apply_ro(finder); + } + return segs; +} + +// private static +LineSegment +CoverageBoundarySegmentFinder::createSegment( + const CoordinateSequence& seq, std::size_t i) +{ + LineSegment seg(seq.getAt(i), seq.getAt(i + 1)); + seg.normalize(); + return seg; +} + + +// public +void +CoverageBoundarySegmentFinder::filter_ro( + const CoordinateSequence& seq, std::size_t i) +{ + //-- final point does not start a segment + if (i >= seq.size() - 1) + return; + + LineSegment seg = createSegment(seq, i); + + + + if (m_boundarySegs.find(seg) != m_boundarySegs.end()) { + m_boundarySegs.erase(seg); + } + else { + m_boundarySegs.insert(seg); + } +} + + +/* public static */ +bool +CoverageBoundarySegmentFinder::isBoundarySegment( + const LineSegment::UnorderedSet& boundarySegs, + const CoordinateSequence* seq, + std::size_t i) +{ + LineSegment seg = CoverageBoundarySegmentFinder::createSegment(*seq, i); + return boundarySegs.find(seg) != boundarySegs.end(); +} + + + + + +} // namespace geos.coverage +} // namespace geos diff --git a/Sources/geos/src/coverage/CoverageEdge.cpp b/Sources/geos/src/coverage/CoverageEdge.cpp new file mode 100644 index 0000000..a2114b9 --- /dev/null +++ b/Sources/geos/src/coverage/CoverageEdge.cpp @@ -0,0 +1,172 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * Copyright (c) 2022 Martin Davis. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include +#include + + +using geos::geom::Coordinate; +using geos::geom::GeometryFactory; +using geos::geom::LinearRing; +using geos::geom::LineSegment; + + +namespace geos { // geos +namespace coverage { // geos.coverage + +/* public static */ +std::unique_ptr +CoverageEdge::createEdge(const CoordinateSequence& ring) +{ + auto pts = extractEdgePoints(ring, 0, ring.getSize() - 1); + return detail::make_unique(std::move(pts), true); +} + +/* public static */ +std::unique_ptr +CoverageEdge::createEdge(const CoordinateSequence& ring, + std::size_t start, std::size_t end) +{ + auto pts = extractEdgePoints(ring, start, end); + return detail::make_unique(std::move(pts), false); +} + +/* public static */ +std::unique_ptr +CoverageEdge::createLines( + const std::vector& edges, + const GeometryFactory* geomFactory) +{ + std::vector> lines; + for (const CoverageEdge* edge : edges) { + auto cs = edge->getCoordinates()->clone(); + auto ls = geomFactory->createLineString(std::move(cs)); + lines.push_back(std::move(ls)); + } + return geomFactory->createMultiLineString(std::move(lines)); +} + +/* public */ +std::unique_ptr +CoverageEdge::toLineString(const GeometryFactory* geomFactory) +{ + const CoordinateSequence* cs = getCoordinates(); + return geomFactory->createLineString(cs->clone()); +} + +/* private static */ +std::unique_ptr +CoverageEdge::extractEdgePoints(const CoordinateSequence& ring, + std::size_t start, std::size_t end) +{ + auto pts = detail::make_unique(); + std::size_t size = start < end + ? end - start + 1 + : ring.getSize() - start + end; + std::size_t iring = start; + for (std::size_t i = 0; i < size; i++) { + pts->add(ring.getAt(iring)); + iring += 1; + if (iring >= ring.getSize()) + iring = 1; + } + return pts; +} + + +/* public static */ +LineSegment +CoverageEdge::key(const CoordinateSequence& ring) +{ + // find lowest vertex index + std::size_t indexLow = 0; + for (std::size_t i = 1; i < ring.size() - 1; i++) { + if (ring.getAt(indexLow).compareTo(ring.getAt(i)) < 0) + indexLow = i; + } + const Coordinate& key0 = ring.getAt(indexLow); + // find distinct adjacent vertices + const Coordinate& adj0 = findDistinctPoint(ring, indexLow, true, key0); + const Coordinate& adj1 = findDistinctPoint(ring, indexLow, false, key0); + const Coordinate& key1 = adj0.compareTo(adj1) < 0 ? adj0 : adj1; + return LineSegment(key0, key1); +} + + +/* public static */ +LineSegment +CoverageEdge::key(const CoordinateSequence& ring, + std::size_t start, std::size_t end) +{ + //-- endpoints are distinct in a line edge + const Coordinate& end0 = ring.getAt(start); + const Coordinate& end1 = ring.getAt(end); + bool isForward = 0 > end0.compareTo(end1); + const Coordinate* key0; + const Coordinate* key1; + if (isForward) { + key0 = &end0; + key1 = &findDistinctPoint(ring, start, true, *key0); + } + else { + key0 = &end1; + key1 = &findDistinctPoint(ring, end, false, *key0); + } + return LineSegment(*key0, *key1); +} + +/* private static */ +const Coordinate& +CoverageEdge::findDistinctPoint( + const CoordinateSequence& pts, + std::size_t index, + bool isForward, + const Coordinate& pt) +{ + std::size_t i = index; + std::size_t endIndex = pts.size()-1; + do { + if (! pts.getAt(i).equals2D(pt)) { + return pts.getAt(i); + } + // increment index with wrapping + if (isForward) { + i = (i == endIndex) ? 0 : (i+1); + } + else { + i = (i == 0) ? endIndex : (i-1); + } + + } while (i != index); + throw util::IllegalStateException("Edge does not contain distinct points"); +} + + + +// /* public */ +// std::string toString() +// { +// return WKTWriter::toLineString(pts); +// } + + + +} // namespace geos.coverage +} // namespace geos diff --git a/Sources/geos/src/coverage/CoverageGapFinder.cpp b/Sources/geos/src/coverage/CoverageGapFinder.cpp new file mode 100644 index 0000000..b4de0fc --- /dev/null +++ b/Sources/geos/src/coverage/CoverageGapFinder.cpp @@ -0,0 +1,98 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include + + +#include +#include +#include +#include +#include +#include +#include + + +using geos::geom::Envelope; +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::LinearRing; +using geos::geom::LineString; +using geos::geom::Polygon; +using geos::geom::util::PolygonExtracter; +using geos::algorithm::construct::MaximumInscribedCircle; + + +namespace geos { // geos +namespace coverage { // geos.coverage + + +/* public static */ +std::unique_ptr +CoverageGapFinder::findGaps(std::vector& coverage, double gapWidth) +{ + CoverageGapFinder finder(coverage); + return finder.findGaps(gapWidth); +} + + +/* public */ +std::unique_ptr +CoverageGapFinder::findGaps(double gapWidth) +{ + std::unique_ptr geomUnion = CoverageUnion::Union(m_coverage); + std::vector polygons; + PolygonExtracter::getPolygons(*geomUnion, polygons); + + std::vector> gapLines; + for (const Polygon* poly : polygons) { + for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) { + const LinearRing* hole = poly->getInteriorRingN(i); + if (isGap(hole, gapWidth)) { + auto pts = hole->getCoordinatesRO()->clone(); + auto ls = geomUnion->getFactory()->createLineString(std::move(pts)); + gapLines.emplace_back(ls.release()); + } + } + } + return geomUnion->getFactory()->buildGeometry(std::move(gapLines)); +} + + +/* private */ +bool +CoverageGapFinder::isGap(const LinearRing* hole, double gapWidth) +{ + std::vector noOtherHoles; + + //-- guard against bad input + if (gapWidth <= 0.0) + return false; + + std::unique_ptr holePoly(hole->getFactory()->createPolygon(*hole, noOtherHoles)); + + double tolerance = gapWidth / 100; + //TODO: improve MIC class to allow short-circuiting when radius is larger than a value + std::unique_ptr line = MaximumInscribedCircle::getRadiusLine(holePoly.get(), tolerance); + double width = line->getLength() * 2; + return width <= gapWidth; +} + + + +} // namespace geos.coverage +} // namespace geos + + diff --git a/Sources/geos/src/coverage/CoveragePolygon.cpp b/Sources/geos/src/coverage/CoveragePolygon.cpp new file mode 100644 index 0000000..70ba468 --- /dev/null +++ b/Sources/geos/src/coverage/CoveragePolygon.cpp @@ -0,0 +1,76 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include + +using geos::algorithm::locate::IndexedPointInAreaLocator; +using geos::geom::CoordinateXY; +using geos::geom::Envelope; +using geos::geom::Location; +using geos::geom::Polygon; + +namespace geos { // geos +namespace coverage { // geos.coverage + +/* public */ +CoveragePolygon::CoveragePolygon(const Polygon* poly) + : m_polygon(poly) +{ + //-- cache polygon envelope for maximum performance + polyEnv = *(poly->getEnvelopeInternal()); +} + +/* public */ +bool +CoveragePolygon::intersectsEnv(const Envelope& env) const +{ + return polyEnv.intersects(env); +} + +/* public */ +bool +CoveragePolygon::intersectsEnv(const CoordinateXY& p) const +{ + return polyEnv.intersects(p); +} + +/* public */ +bool +CoveragePolygon::contains(const CoordinateXY& p) const +{ + if (! intersectsEnv(p)) + return false; + auto& pia = getLocator(); + return Location::INTERIOR == pia.locate(&p); +} + +/* private */ +IndexedPointInAreaLocator& +CoveragePolygon::getLocator() const +{ + if (m_locator == nullptr) { + m_locator = std::make_unique(*m_polygon); + } + return *m_locator; +} + +} // namespace geos.coverage +} // namespace geos + + diff --git a/Sources/geos/src/coverage/CoveragePolygonValidator.cpp b/Sources/geos/src/coverage/CoveragePolygonValidator.cpp new file mode 100644 index 0000000..d0161c0 --- /dev/null +++ b/Sources/geos/src/coverage/CoveragePolygonValidator.cpp @@ -0,0 +1,405 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using geos::algorithm::locate::IndexedPointInAreaLocator; +using geos::algorithm::Orientation; +using geos::geom::CoordinateXY; +using geos::geom::Envelope; +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::LineString; +using geos::geom::LineSegment; +using geos::geom::Location; +using geos::geom::Polygon; +using geos::geom::util::PolygonExtracter; +using geos::noding::MCIndexSegmentSetMutualIntersector; +using geos::operation::valid::RepeatedPointRemover; + +namespace geos { // geos +namespace coverage { // geos.coverage + + +/* public static */ +std::unique_ptr +CoveragePolygonValidator::validate(const Geometry* targetPolygon, std::vector& adjPolygons) +{ + CoveragePolygonValidator v(targetPolygon, adjPolygons); + return v.validate(); +} + + +/* public static */ +std::unique_ptr +CoveragePolygonValidator::validate(const Geometry* targetPolygon, std::vector& adjPolygons, double gapWidth) +{ + CoveragePolygonValidator v(targetPolygon, adjPolygons); + v.setGapWidth(gapWidth); + return v.validate(); +} + + +/* public */ +CoveragePolygonValidator::CoveragePolygonValidator( + const Geometry* geom, + std::vector& p_adjGeoms) + : targetGeom(geom) + , adjGeoms(p_adjGeoms) + , geomFactory(geom->getFactory()) +{} + + +/* public */ +void +CoveragePolygonValidator::setGapWidth(double p_gapWidth) +{ + gapWidth = p_gapWidth; +} + + +/* public */ +std::unique_ptr +CoveragePolygonValidator::validate() +{ + std::vector adjPolygons = extractPolygons(adjGeoms); + m_adjCovPolygons = toCoveragePolygons(adjPolygons); + std::vector targetRings = createRings(targetGeom); + std::vector adjRings = createRings(adjPolygons); + + /** + * Mark matching segments first. + * Matched segments are not considered for further checks. + * This improves performance substantially for mostly-valid coverages. + */ + Envelope targetEnv = *(targetGeom->getEnvelopeInternal()); + targetEnv.expandBy(gapWidth); + + checkTargetRings(targetRings, adjRings, targetEnv); + + return createInvalidLines(targetRings); +} + +/* private static */ +std::vector> +CoveragePolygonValidator::toCoveragePolygons(const std::vector polygons) { + std::vector> covPolys; + for (const Polygon* poly : polygons) { + covPolys.push_back( std::make_unique(poly) ); + } + return covPolys; +} + +/* private */ +void +CoveragePolygonValidator::checkTargetRings( + std::vector& targetRings, + std::vector& adjRings, + const Envelope& targetEnv) +{ + markMatchedSegments(targetRings, adjRings, targetEnv); + + /** + * Short-circuit if target is fully known (matched or invalid). + * This often happens in clean coverages, + * when the target is surrounded by matching polygons. + * It can also happen in invalid coverages + * which have polygons which are duplicates, + * or perfectly overlap other polygons. + */ + if (CoverageRing::isKnown(targetRings)) + return; + + /** + * Here target has at least one unmatched segment. + * Do further checks to see if any of them are are invalid. + */ + markInvalidInteractingSegments(targetRings, adjRings, gapWidth); + markInvalidInteriorSegments(targetRings, m_adjCovPolygons); +} + +/* private static */ +std::vector +CoveragePolygonValidator::extractPolygons(std::vector& geoms) +{ + std::vector polygons; + for (const Geometry* geom : geoms) { + PolygonExtracter::getPolygons(*geom, polygons); + } + return polygons; +} + + +/* private */ +std::unique_ptr +CoveragePolygonValidator::createEmptyResult() +{ + return geomFactory->createLineString(); +} + + +/* private */ +void +CoveragePolygonValidator::markMatchedSegments( + std::vector& targetRings, + std::vector& adjRngs, + const Envelope& targetEnv) +{ + CoverageRingSegmentMap segmentMap; + markMatchedSegments(targetRings, targetEnv, segmentMap); + markMatchedSegments(adjRngs, targetEnv, segmentMap); +} + + +/* private */ +void +CoveragePolygonValidator::markMatchedSegments( + std::vector& rings, + const Envelope& envLimit, + CoverageRingSegmentMap& segmentMap) +{ + for (CoverageRing* ring : rings) { + for (std::size_t i = 0; i < ring->size() - 1; i++) { + const Coordinate& p0 = ring->getCoordinate(i); + const Coordinate& p1 = ring->getCoordinate(i + 1); + + //-- skip segments which lie outside the limit envelope + if (! envLimit.intersects(p0, p1)) { + continue; + } + //-- if segment keys match, mark them as matched (or invalid) + CoverageRingSegment* seg = createCoverageRingSegment(ring, i); + auto search = segmentMap.find(seg); + if (search != segmentMap.end()) { + CoverageRingSegment* segMatch = search->second; + seg->match(segMatch); + } + else { + segmentMap[seg] = seg; + } + } + } +} + + +/* private */ +CoveragePolygonValidator::CoverageRingSegment* +CoveragePolygonValidator::createCoverageRingSegment(CoverageRing* ring, std::size_t index) +{ + const Coordinate& p0 = ring->getCoordinate(index); + const Coordinate& p1 = ring->getCoordinate(index + 1); + + if(ring->isInteriorOnRight()) { + coverageRingSegmentStore.emplace_back(p0, p1, ring, index); + } + else { + coverageRingSegmentStore.emplace_back(p1, p0, ring, index); + } + CoverageRingSegment& seg = coverageRingSegmentStore.back(); + return &seg; +} + + +/* private */ +void +CoveragePolygonValidator::markInvalidInteractingSegments( + std::vector& targetRings, + std::vector& adjRings, + double distanceTolerance) +{ + std::vector targetSS; + for (auto cr: targetRings) { + targetSS.push_back(static_cast(cr)); + } + std::vector adjSS; + for (auto cr: adjRings) { + adjSS.push_back(static_cast(cr)); + } + + InvalidSegmentDetector detector(distanceTolerance); + MCIndexSegmentSetMutualIntersector segSetMutInt(distanceTolerance); + segSetMutInt.setBaseSegments(&targetSS); + segSetMutInt.setSegmentIntersector(&detector); + segSetMutInt.process(&adjSS); +} + + +/* private */ +void +CoveragePolygonValidator::markInvalidInteriorSegments( + std::vector& targetRings, + std::vector>& adjCovPolygons ) +{ + for (CoverageRing* ring : targetRings) { + std::size_t stride = 1000; //-- RING_SECTION_STRIDE; + for (std::size_t i = 0; i < ring->size() - 1; i += stride) { + std::size_t iEnd = i + stride; + if (iEnd >= ring->size()) + iEnd = ring->size() - 1; + markInvalidInteriorSection(*ring, i, iEnd, adjCovPolygons); + } + } +} + +/* private */ +void +CoveragePolygonValidator::markInvalidInteriorSection( + CoverageRing& ring, + std::size_t iStart, + std::size_t iEnd, + std::vector>& adjCovPolygons ) +{ + Envelope sectionEnv = ring.getEnvelope(iStart, iEnd); + //TODO: is it worth indexing polygons? + for (auto& adjPoly : adjCovPolygons) { + if (adjPoly->intersectsEnv(sectionEnv)) { + //-- test vertices in section + for (auto i = iStart; i < iEnd; i++) { + markInvalidInteriorSegment(ring, i, adjPoly.get()); + } + } + } +} + +/* private */ +void +CoveragePolygonValidator::markInvalidInteriorSegment( + CoverageRing& ring, std::size_t i, CoveragePolygon* adjPoly) +{ + //-- skip check for segments with known state. + if (ring.isKnown(i)) + return; + + /** + * Check if vertex is in interior of an adjacent polygon. + * If so, the segments on either side are in the interior. + * Mark them invalid, unless they are already matched. + */ + const CoordinateXY& p = ring.getCoordinate(i); + if (adjPoly->contains(p)) { + ring.markInvalid(i); + //-- previous segment may be interior (but may also be matched) + auto iPrev = i == 0 ? ring.size()-2 : i-1; + if (! ring.isKnown(iPrev)) + ring.markInvalid(iPrev); + } +} + +/* private */ +std::unique_ptr +CoveragePolygonValidator::createInvalidLines(std::vector& rings) +{ + std::vector> lines; + for (CoverageRing* ring : rings) { + ring->createInvalidLines(geomFactory, lines); + } + + if (lines.size() == 0) { + return createEmptyResult(); + } + else if (lines.size() == 1) { + return lines[0]->clone(); + } + + return geomFactory->createMultiLineString(std::move(lines)); +} + +/**********************************************************************************/ + +/* private */ +std::vector +CoveragePolygonValidator::createRings(const Geometry* geom) +{ + std::vector polygons; + geom::util::PolygonExtracter::getPolygons(*geom, polygons); + return createRings(polygons); +} + +/* private */ +std::vector +CoveragePolygonValidator::createRings(std::vector& polygons) +{ + std::vector rings; + for (const Polygon* poly : polygons) { + createRings(poly, rings); + } + return rings; +} + +/* private */ +void +CoveragePolygonValidator::createRings( + const Polygon* poly, + std::vector& rings) +{ + // Create exterior shell ring + addRing( poly->getExteriorRing(), true, rings); + + // Create hole rings + for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) { + addRing( poly->getInteriorRingN(i), false, rings); + } +} + +/* private */ +void +CoveragePolygonValidator::addRing( + const LinearRing* ring, + bool isShell, + std::vector& rings) +{ + if (ring->isEmpty()) + return; + rings.push_back(createRing(ring, isShell)); +} + +/* private */ +CoverageRing* +CoveragePolygonValidator::createRing(const LinearRing* ring, bool isShell) +{ + CoordinateSequence* pts = const_cast(ring->getCoordinatesRO()); + if (pts->hasRepeatedOrInvalidPoints()) { + CoordinateSequence* cleanPts = RepeatedPointRemover::removeRepeatedAndInvalidPoints(pts).release(); + localCoordinateSequences.emplace_back(cleanPts); + pts = cleanPts; + } + bool isCCW = Orientation::isCCW(pts); + bool isInteriorOnRight = isShell ? ! isCCW : isCCW; + coverageRingStore.emplace_back(pts, isInteriorOnRight); + CoverageRing& cRing = coverageRingStore.back(); + return &cRing; +} + + + + +} // namespace geos.coverage +} // namespace geos diff --git a/Sources/geos/src/coverage/CoverageRing.cpp b/Sources/geos/src/coverage/CoverageRing.cpp new file mode 100644 index 0000000..9a33129 --- /dev/null +++ b/Sources/geos/src/coverage/CoverageRing.cpp @@ -0,0 +1,323 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::LineString; +using geos::geom::LinearRing; +using geos::geom::Polygon; + + +namespace geos { // geos +namespace coverage { // geos.coverage + + +/* public static */ +bool +CoverageRing::isKnown(std::vector& rings) +{ + for (auto* ring : rings) { + if (! ring->isKnown()) + return false; + } + return true; +} + + +/* public */ +CoverageRing::CoverageRing(CoordinateSequence* inPts, bool interiorOnRight) + : noding::BasicSegmentString(inPts, nullptr) + , m_isInteriorOnRight(interiorOnRight) +{ + m_isInvalid.resize(size() - 1, false); + m_isMatched.resize(size() - 1, false); +} + + +/* public */ +CoverageRing::CoverageRing(const LinearRing* ring, bool isShell) + : CoverageRing( + // This is bad. The ownership rules of SegmentStrings need + // to be carefully considered. Most noders don't even touch + // them so a const CoordinateSequence makes sense. Some add + // things, like the NodedSegmentString, but do so out-of-line. + // Some noders (just ScalingNoder?) completely transform the + // inputs. Could maybe do bulk copying for that case? + const_cast(ring->getCoordinatesRO()), + algorithm::Orientation::isCCW(ring->getCoordinatesRO()) != isShell) +{} + +/* public */ +geom::Envelope CoverageRing::getEnvelope(std::size_t start, std::size_t end) +{ + geom::Envelope env; + for (std::size_t i = start; i < end; i++) { + env.expandToInclude(getCoordinate(i)); + } + return env; +} + + +/* public */ +bool +CoverageRing::isInteriorOnRight() const +{ + return m_isInteriorOnRight; +} + + +/* public */ +void +CoverageRing::markInvalid(std::size_t index) +{ + m_isInvalid[index] = true; +} + + +/* public */ +void +CoverageRing::markMatched(std::size_t index) +{ + m_isMatched[index] = true; +} + + +/* public */ +bool +CoverageRing::isKnown() const +{ + for (size_t i = 0; i < m_isMatched.size(); i++ ) { + if (!(m_isMatched[i] && m_isInvalid[i])) + return false; + } + return true; +} + +/* public */ +bool +CoverageRing::isInvalid(std::size_t i) const +{ + return m_isInvalid[i]; +} + +/* public */ +bool +CoverageRing::isInvalid() const +{ + for (bool b: m_isInvalid) { + if (!b) + return false; + } + return true; +} + + +/* public */ +bool +CoverageRing::hasInvalid() const +{ + for (bool b: m_isInvalid) { + if (b) + return true; + } + return false; +} + + +/* public */ +bool +CoverageRing::isKnown(std::size_t i) const +{ + return m_isMatched[i] || m_isInvalid[i]; +} + + +/* public */ +const Coordinate& +CoverageRing::findVertexPrev(std::size_t index, const Coordinate& pt) const +{ + std::size_t iPrev = index; + const Coordinate* cPrev = &getCoordinate(iPrev); + while (pt.equals2D(*cPrev)) { + iPrev = prev(iPrev); + cPrev = &getCoordinate(iPrev); + } + return *cPrev; +} + + +/* public */ +const Coordinate& +CoverageRing::findVertexNext(std::size_t index, const Coordinate& pt) const +{ + //-- safe, since index is always the start of a segment + std::size_t iNext = index + 1; + const Coordinate* cNext = &getCoordinate(iNext); + while (pt.equals2D(*cNext)) { + iNext = next(iNext); + cNext = &getCoordinate(iNext); + } + return *cNext; +} + + +/* public */ +std::size_t +CoverageRing::prev(std::size_t index) const +{ + if (index == 0) + return size() - 2; + return index - 1; +} + + +/* public */ +std::size_t +CoverageRing::next(std::size_t index) const +{ + if (index < size() - 2) + return index + 1; + return 0; +} + + +/* public */ +void +CoverageRing::createInvalidLines( + const GeometryFactory* geomFactory, + std::vector>& lines) +{ + //-- empty case + if (! hasInvalid()) { + return; + } + //-- entire ring case + if (isInvalid()) { + std::unique_ptr line = createLine(0, size() - 1, geomFactory); + lines.push_back(std::move(line)); + return; + } + + //-- find first end after index 0, to allow wrap-around + std::size_t startIndex = findInvalidStart(0); + std::size_t firstEndIndex = findInvalidEnd(startIndex); + std::size_t endIndex = firstEndIndex; + while (true) { + startIndex = findInvalidStart(endIndex); + endIndex = findInvalidEnd(startIndex); + std::unique_ptr line = createLine(startIndex, endIndex, geomFactory); + lines.push_back(std::move(line)); + if (endIndex == firstEndIndex) + break; + } +} + + +/* private */ +std::size_t +CoverageRing::findInvalidStart(std::size_t index) +{ + while (! isInvalid(index)) { + index = nextMarkIndex(index); + } + return index; +} + + +/* private */ +std::size_t +CoverageRing::findInvalidEnd(std::size_t index) +{ + index = nextMarkIndex(index); + while (isInvalid(index)) { + index = nextMarkIndex(index); + } + return index; +} + + +/* private */ +std::size_t +CoverageRing::nextMarkIndex(std::size_t index) +{ + if (index >= m_isInvalid.size() - 1) { + return 0; + } + return index + 1; +} + + +/* private */ +std::unique_ptr +CoverageRing::createLine( + std::size_t startIndex, + std::size_t endIndex, + const GeometryFactory* geomFactory) +{ + std::unique_ptr linePts = endIndex < startIndex + ? extractSectionWrap(startIndex, endIndex) + : extractSection(startIndex, endIndex); + return geomFactory->createLineString(std::move(linePts)); +} + + +/* private */ +std::unique_ptr +CoverageRing::extractSection(std::size_t startIndex, std::size_t endIndex) +{ + // std::size_t sz = endIndex - startIndex + 1; + std::unique_ptr linePts(new CoordinateSequence()); + for (std::size_t i = startIndex; i <= endIndex; i++) { + linePts->add(getCoordinate(i)); + } + + return linePts; +} + + +/* private */ +std::unique_ptr +CoverageRing::extractSectionWrap(std::size_t startIndex, std::size_t endIndex) +{ + std::size_t sz = endIndex + (size() - startIndex); + std::unique_ptr linePts(new CoordinateSequence); + std::size_t index = startIndex; + for (std::size_t i = 0; i < sz; i++) { + linePts->add(getCoordinate(index)); + index = nextMarkIndex(index); + } + + return linePts; +} + + +} // namespace geos.coverage +} // namespace geos + + diff --git a/Sources/geos/src/coverage/CoverageRingEdges.cpp b/Sources/geos/src/coverage/CoverageRingEdges.cpp new file mode 100644 index 0000000..e6ec619 --- /dev/null +++ b/Sources/geos/src/coverage/CoverageRingEdges.cpp @@ -0,0 +1,424 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 Paul Ramsey + * Copyright (c) 2023 Martin Davis. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::LineSegment; +using geos::geom::LinearRing; +using geos::geom::Polygon; +using geos::geom::MultiPolygon; +using geos::operation::valid::RepeatedPointRemover; + +namespace geos { // geos +namespace coverage { // geos.coverage + + +/* public */ +std::vector +CoverageRingEdges::selectEdges(std::size_t ringCount) const +{ + std::vector result; + for (CoverageEdge* edge : m_edges) { + if (edge->getRingCount() == ringCount) { + result.push_back(edge); + } + } + return result; +} + + +/* private */ +void +CoverageRingEdges::build() +{ + Coordinate::UnorderedSet nodes = findMultiRingNodes(m_coverage); + LineSegment::UnorderedSet boundarySegs = CoverageBoundarySegmentFinder::findBoundarySegments(m_coverage); + Coordinate::UnorderedSet boundaryNodes = findBoundaryNodes(boundarySegs); + nodes.insert(boundaryNodes.begin(), boundaryNodes.end()); + + std::map uniqueEdgeMap; + for (const Geometry* geom : m_coverage) { + for (std::size_t ipoly = 0; ipoly < geom->getNumGeometries(); ipoly++) { + util::ensureNoCurvedComponents(geom->getGeometryN(ipoly)); + + const Polygon* poly = static_cast(geom->getGeometryN(ipoly)); + + //-- skip empty elements. Missing elements are copied in result + if (poly->isEmpty()) + continue; + + //-- extract shell + const LinearRing* shell = poly->getExteriorRing(); + addRingEdges(shell, nodes, boundarySegs, uniqueEdgeMap); + //-- extract holes + for (std::size_t ihole = 0; ihole < poly->getNumInteriorRing(); ihole++) { + const LinearRing* hole = poly->getInteriorRingN(ihole); + //-- skip empty rings. Missing rings are copied in result + if (hole->isEmpty()) + continue; + addRingEdges(hole, nodes, boundarySegs, uniqueEdgeMap); + } + } + } +} + +/* private */ +void +CoverageRingEdges::addRingEdges( + const LinearRing* ring, + Coordinate::UnorderedSet& nodes, + LineSegment::UnorderedSet& boundarySegs, + std::map& uniqueEdgeMap) +{ + addBoundaryInnerNodes(ring, boundarySegs, nodes); + std::vector ringEdges = extractRingEdges(ring, uniqueEdgeMap, nodes); + if (ringEdges.size() > 0) { + m_ringEdgesMap[ring] = ringEdges; + } +} + +/* private */ +void +CoverageRingEdges::addBoundaryInnerNodes( + const LinearRing* ring, + LineSegment::UnorderedSet& boundarySegs, + Coordinate::UnorderedSet& nodes) +{ + const CoordinateSequence* seq = ring->getCoordinatesRO(); + bool isBdyLast = CoverageBoundarySegmentFinder::isBoundarySegment(boundarySegs, seq, seq->size() - 2); + bool isBdyPrev = isBdyLast; + for (std::size_t i = 0; i < seq->size() - 1; i++) { + bool isBdy = CoverageBoundarySegmentFinder::isBoundarySegment(boundarySegs, seq, i); + if (isBdy != isBdyPrev) { + const Coordinate& nodePt = seq->getAt(i); + nodes.insert(nodePt); + } + isBdyPrev = isBdy; + } +} + +/* private */ +std::vector +CoverageRingEdges::extractRingEdges( + const LinearRing* ring, + std::map& uniqueEdgeMap, + Coordinate::UnorderedSet& nodes) +{ + std::unique_ptr pts + = RepeatedPointRemover::removeRepeatedPoints( ring->getCoordinatesRO() ); + std::vector ringEdges; + //-- if compacted ring is too short, don't process it + if (pts->getSize() < 3) + return ringEdges; + + std::size_t first = findNextNodeIndex(*pts, NO_COORD_INDEX, nodes); + if (first == NO_COORD_INDEX) { + //-- ring does not contain a node, so edge is entire ring + CoverageEdge* edge = createEdge(*pts, uniqueEdgeMap); + ringEdges.push_back(edge); + } + else { + std::size_t start = first; + std::size_t end = start; + do { + end = findNextNodeIndex(*pts, start, nodes); + CoverageEdge* edge = createEdge(*pts, start, end, uniqueEdgeMap); + ringEdges.push_back(edge); + start = end; + } while (end != first); + } + return ringEdges; +} + +/* private */ +CoverageEdge* +CoverageRingEdges::createEdge( + const CoordinateSequence& ring, + std::map& uniqueEdgeMap) +{ + CoverageEdge* edge; + LineSegment edgeKey = CoverageEdge::key(ring); + auto result = uniqueEdgeMap.find(edgeKey); + if (result != uniqueEdgeMap.end()) { + edge = result->second; + } + // if (uniqueEdgeMap.containsKey(edgeKey)) { + // edge = uniqueEdgeMap.get(edgeKey); + // } + else { + std::unique_ptr edge_ptr = CoverageEdge::createEdge(ring); + edge = edge_ptr.release(); + m_edgeStore.emplace_back(edge); + m_edges.push_back(edge); + uniqueEdgeMap[edgeKey] = edge; + } + edge->incRingCount(); + return edge; +} + +/* private */ +CoverageEdge* +CoverageRingEdges::createEdge( + const CoordinateSequence& ring, + std::size_t start, std::size_t end, + std::map& uniqueEdgeMap) +{ + CoverageEdge* edge; + LineSegment edgeKey = (end == start) ? CoverageEdge::key(ring) : CoverageEdge::key(ring, start, end); + // if (uniqueEdgeMap.containsKey(edgeKey)) { + // edge = uniqueEdgeMap.get(edgeKey); + // } + auto result = uniqueEdgeMap.find(edgeKey); + if (result != uniqueEdgeMap.end()) { + edge = result->second; + } + else { + std::unique_ptr edge_ptr = CoverageEdge::createEdge(ring, start, end); + edge = edge_ptr.release(); + m_edgeStore.emplace_back(edge); + m_edges.push_back(edge); + uniqueEdgeMap[edgeKey] = edge; + } + edge->incRingCount(); + return edge; +} + +/* private */ +std::size_t +CoverageRingEdges::findNextNodeIndex( + const CoordinateSequence& ring, + std::size_t start, + Coordinate::UnorderedSet& nodes) const +{ + std::size_t index = start; + bool isScanned0 = false; + do { + index = next(index, ring); + if (index == 0) { + if (start == NO_COORD_INDEX && isScanned0) { + return NO_COORD_INDEX; + } + isScanned0 = true; + } + const Coordinate& pt = ring.getAt(index); + if (nodes.find(pt) != nodes.end()) { + return index; + } + } while (index != start); + return NO_COORD_INDEX; +} + +/* private static */ +std::size_t +CoverageRingEdges::next(std::size_t index, const CoordinateSequence& ring) +{ + if (index == NO_COORD_INDEX) return 0; + index = index + 1; + if (index >= ring.getSize() - 1) + index = 0; + return index; +} + + +/* private */ +Coordinate::UnorderedSet +CoverageRingEdges::findMultiRingNodes(const std::vector& coverage) +{ + std::map vertexRingCount; + VertexRingCounter::count(coverage, vertexRingCount); + Coordinate::UnorderedSet nodes; + // for (Coordinate v : vertexCount.keySet()) { + // if (vertexCount.get(v) > 2) { + // nodes.add(v); + // } + // } + for (const auto &mapPair : vertexRingCount) { + const Coordinate& v = mapPair.first; + std::size_t count = mapPair.second; + if (count > 2) + nodes.insert(v); + } + return nodes; +} + + +/* private */ +Coordinate::UnorderedSet +CoverageRingEdges::findBoundaryNodes(LineSegment::UnorderedSet& boundarySegments) +{ + std::map counter; + for (const LineSegment& seg : boundarySegments) { + // counter.put(line.p0, counter.getOrDefault(line.p0, 0) + 1); + // counter.put(line.p1, counter.getOrDefault(line.p1, 0) + 1); + auto search0 = counter.find(seg.p0); + if (search0 != counter.end()) { + counter[seg.p0] = search0->second + 1; + } + else { + counter[seg.p0] = 0; + } + + auto search1 = counter.find(seg.p1); + if (search1 != counter.end()) { + counter[seg.p1] = search1->second + 1; + } + else { + counter[seg.p1] = 0; + } + } + + Coordinate::UnorderedSet nodes; + for (const auto& c : counter) { + const Coordinate& v = c.first; + std::size_t count = c.second; + if (count > 2) + nodes.insert(v); + } + return nodes; + + // return counter.entrySet().stream() + // .filter(e->e.getValue()>2) + // .map(Map.Entry::getKey).collect(Collectors.toSet()); +} + + +/* public */ +std::vector> +CoverageRingEdges::buildCoverage() const +{ + std::vector> result; + for (const Geometry* geom : m_coverage) { + result.push_back(buildPolygonal(geom)); + } + return result; +} + +/* private */ +std::unique_ptr +CoverageRingEdges::buildPolygonal(const Geometry* geom) const +{ + if (geom->getGeometryTypeId() == geom::GEOS_MULTIPOLYGON) { + return buildMultiPolygon(static_cast(geom)); + } + else { + return buildPolygon(static_cast(geom)); + } +} + +/* private */ +std::unique_ptr +CoverageRingEdges::buildMultiPolygon(const MultiPolygon* geom) const +{ + // Polygon[] polys = new Polygon[geom.getNumGeometries()]; + std::vector> polys; + for (std::size_t i = 0; i < geom->getNumGeometries(); i++) { + const Polygon* poly = static_cast(geom->getGeometryN(i)); + polys.push_back(buildPolygon(poly)); + } + return geom->getFactory()->createMultiPolygon(std::move(polys)); +} + +/* private */ +std::unique_ptr +CoverageRingEdges::buildPolygon(const Polygon* polygon) const +{ + std::size_t numRings = polygon->getNumInteriorRing(); + std::unique_ptr shell = buildRing(polygon->getExteriorRing()); + if (numRings == 0) { + return polygon->getFactory()->createPolygon(std::move(shell)); + } + std::vector> holes; + for (std::size_t i = 0; i < numRings; i++) { + const LinearRing* hole = polygon->getInteriorRingN(i); + auto newHole = buildRing(hole); + holes.emplace_back(newHole.release()); + } + return polygon->getFactory()->createPolygon(std::move(shell), std::move(holes)); +} + + +/* private */ +std::unique_ptr +CoverageRingEdges::buildRing(const LinearRing* ring) const +{ + const std::vector* ringEdges; + + // List ringEdges = m_ringEdgesMap.get(ring); + auto result = m_ringEdgesMap.find(ring); + if (result == m_ringEdgesMap.end()) { + //-- if ring is not in map, must have been invalid. Just copy original + return ring->clone(); + } + else { + ringEdges = &(result->second); + } + + // CoordinateList ptsList = new CoordinateList(); + std::unique_ptr pts(new CoordinateSequence()); + Coordinate nullPt = Coordinate::getNull(); + for (std::size_t i = 0; i < ringEdges->size(); i++) { + Coordinate& lastPt = pts->isEmpty() ? nullPt : pts->back(); + bool dir = isEdgeDirForward(*ringEdges, i, lastPt); + const CoordinateSequence* ringCs = ringEdges->at(i)->getCoordinates(); + pts->add(*ringCs, false, dir); + } + return ring->getFactory()->createLinearRing(std::move(pts)); +} + +/* private */ +bool +CoverageRingEdges::isEdgeDirForward( + const std::vector& ringEdges, + std::size_t index, + const Coordinate& prevPt) const +{ + std::size_t size = ringEdges.size(); + if (size <= 1) return true; + if (index == 0) { + //-- if only 2 edges, first one can keep orientation + if (size == 2) + return true; + const Coordinate& endPt0 = ringEdges[0]->getEndCoordinate(); + return endPt0.equals2D(ringEdges[1]->getStartCoordinate()) + || endPt0.equals2D(ringEdges[1]->getEndCoordinate()); + } + //-- previous point determines required orientation + return prevPt.equals2D(ringEdges[index]->getStartCoordinate()); +} + + + +} // namespace geos.coverage +} // namespace geos diff --git a/Sources/geos/src/coverage/CoverageSimplifier.cpp b/Sources/geos/src/coverage/CoverageSimplifier.cpp new file mode 100644 index 0000000..dbbd902 --- /dev/null +++ b/Sources/geos/src/coverage/CoverageSimplifier.cpp @@ -0,0 +1,156 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2023 Paul Ramsey + * Copyright (c) 2023 Martin Davis. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + + +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::MultiLineString; + + +namespace geos { // geos +namespace coverage { // geos.coverage + +/* public static */ +std::vector> +CoverageSimplifier::simplify( + std::vector& coverage, + double tolerance) +{ + CoverageSimplifier simplifier(coverage); + return simplifier.simplify(tolerance); +} + +/* public static */ +std::vector> +CoverageSimplifier::simplify( + const std::vector>& coverage, + double tolerance) +{ + std::vector geoms; + for (auto& geom : coverage) { + geoms.push_back(geom.get()); + } + return simplify(geoms, tolerance); +} + + +/* public static */ +std::vector> +CoverageSimplifier::simplifyInner( + std::vector& coverage, + double tolerance) +{ + CoverageSimplifier simplifier(coverage); + return simplifier.simplifyInner(tolerance); +} + + +/* public static */ +std::vector> +CoverageSimplifier::simplifyInner( + const std::vector>& coverage, + double tolerance) +{ + std::vector geoms; + for (auto& geom : coverage) { + geoms.push_back(geom.get()); + } + return simplifyInner(geoms, tolerance); +} + + +/* public */ +CoverageSimplifier::CoverageSimplifier(const std::vector& coverage) + : m_input(coverage) + , m_geomFactory(coverage.empty() ? nullptr : coverage[0]->getFactory()) + { + for (const Geometry* g: m_input) { + if (!g->isPolygonal()) + throw util::IllegalArgumentException("Argument is non-polygonal"); + } + } + +/* public */ +std::vector> +CoverageSimplifier::simplify(double tolerance) +{ + CoverageRingEdges cov(m_input); + simplifyEdges(cov.getEdges(), nullptr, tolerance); + return cov.buildCoverage(); +} + +/* public */ +std::vector> +CoverageSimplifier::simplifyInner(double tolerance) +{ + CoverageRingEdges cov(m_input); + std::vector innerEdges = cov.selectEdges(2); + std::vector outerEdges = cov.selectEdges(1); + std::unique_ptr constraintEdges = CoverageEdge::createLines(outerEdges, m_geomFactory); + + simplifyEdges(innerEdges, constraintEdges.get(), tolerance); + return cov.buildCoverage(); +} + +/* private */ +void +CoverageSimplifier::simplifyEdges( + std::vector edges, + const MultiLineString* constraints, + double tolerance) +{ + std::unique_ptr lines = CoverageEdge::createLines(edges, m_geomFactory); + std::vector freeRings = getFreeRings(edges); + std::unique_ptr linesSimp = TPVWSimplifier::simplify(lines.get(), freeRings, constraints, tolerance); + //Assert: mlsSimp.getNumGeometries = edges.length + + setCoordinates(edges, linesSimp.get()); +} + + +/* private */ +void +CoverageSimplifier::setCoordinates(std::vector& edges, const MultiLineString* lines) +{ + for (std::size_t i = 0; i < edges.size(); i++) { + CoverageEdge* edge = edges[i]; + edge->setCoordinates(lines->getGeometryN(i)->getCoordinatesRO()); + } +} + + +/* private */ +std::vector +CoverageSimplifier::getFreeRings(const std::vector& edges) const +{ + std::vector freeRings; + for (auto edge: edges) { + freeRings.push_back(edge->isFreeRing()); + } + return freeRings; +} + + +} // geos.coverage +} // geos diff --git a/Sources/geos/src/coverage/CoverageUnion.cpp b/Sources/geos/src/coverage/CoverageUnion.cpp new file mode 100644 index 0000000..4e2d547 --- /dev/null +++ b/Sources/geos/src/coverage/CoverageUnion.cpp @@ -0,0 +1,62 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include +//#include + +using geos::geom::Geometry; +using geos::geom::GeometryCollection; +using geos::geom::GeometryFactory; + + +namespace geos { // geos +namespace coverage { // geos.coverage + + +/* public static */ +std::unique_ptr +CoverageUnion::Union(std::vector& coverage) +{ + if (coverage.size() == 0) + return nullptr; + + // TODO? spatial sort polygons to improve performance + // Test results are somewhat inconclusive + //shape::fractal::HilbertEncoder::sort(coverage.begin(), coverage.end()); + + const GeometryFactory* geomFact = coverage[0]->getFactory(); + std::unique_ptr geoms(geomFact->createGeometryCollection(coverage)); + return operation::overlayng::CoverageUnion::geomunion(geoms.get()); +} + + +/* public static */ +std::unique_ptr +CoverageUnion::Union(const Geometry* coverage) +{ + const GeometryCollection* col = dynamic_cast(coverage); + + if (col == nullptr) return nullptr; + + return operation::overlayng::CoverageUnion::geomunion(col); +} + + +} // namespace geos.coverage +} // namespace geos diff --git a/Sources/geos/src/coverage/CoverageValidator.cpp b/Sources/geos/src/coverage/CoverageValidator.cpp new file mode 100644 index 0000000..6445583 --- /dev/null +++ b/Sources/geos/src/coverage/CoverageValidator.cpp @@ -0,0 +1,120 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include + +#include +#include +#include + + +using geos::geom::Envelope; +using geos::geom::Geometry; +using geos::geom::GeometryFactory; + + +namespace geos { // geos +namespace coverage { // geos.coverage + +/* public static */ +bool +CoverageValidator::isValid(std::vector& coverage) +{ + CoverageValidator v(coverage); + return ! hasInvalidResult(v.validate()); +} + +/* public static */ +bool +CoverageValidator::hasInvalidResult(const std::vector>& validateResult) +{ + for (const auto& geom : validateResult) { + if (geom != nullptr) + return true; + } + return false; +} + + +/* public static */ +std::vector> +CoverageValidator::validate(std::vector& coverage) +{ + CoverageValidator v(coverage); + return v.validate(); +} + + +/* public static */ +std::vector> +CoverageValidator::validate(std::vector& coverage, double gapWidth) +{ + CoverageValidator v(coverage); + v.setGapWidth(gapWidth); + return v.validate(); +} + + +/* public */ +std::vector> +CoverageValidator::validate() +{ + TemplateSTRtree index; + std::vector> invalidLines; + for (auto* geom : m_coverage) { + util::ensureNoCurvedComponents(geom); + + index.insert(geom); + invalidLines.emplace_back(nullptr); + } + + for (std::size_t i = 0; i < m_coverage.size(); i++) { + const Geometry* geom = m_coverage[i]; + std::unique_ptr result = validate(geom, index); + invalidLines[i].reset(result.release()); + } + return invalidLines; +} + +/* private */ +std::unique_ptr +CoverageValidator::validate(const Geometry* targetGeom, TemplateSTRtree& index) +{ + Envelope queryEnv = *(targetGeom->getEnvelopeInternal()); + queryEnv.expandBy(m_gapWidth); + + // Query the index for nearby geometry and add to the list + std::vector nearGeoms; + auto visitor = [&nearGeoms](const Geometry* geom) { + nearGeoms.push_back(geom); + }; + index.query(queryEnv, visitor); + //-- the target geometry is returned in the query, so must be removed from the set + auto it = std::find(nearGeoms.begin(), nearGeoms.end(), targetGeom); + if (it != nearGeoms.end()) { + nearGeoms.erase(it); + } + + // Geometry[] nearGeoms = GeometryFactory.toGeometryArray(nearGeoms); + std::unique_ptr result = CoveragePolygonValidator::validate(targetGeom, nearGeoms, m_gapWidth); + if (result->isEmpty()) + return nullptr; + else + return result; +} + + +} // namespace geos.coverage +} // namespace geos diff --git a/Sources/geos/src/coverage/InvalidSegmentDetector.cpp b/Sources/geos/src/coverage/InvalidSegmentDetector.cpp new file mode 100644 index 0000000..bc2f878 --- /dev/null +++ b/Sources/geos/src/coverage/InvalidSegmentDetector.cpp @@ -0,0 +1,202 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + + +using geos::algorithm::PolygonNodeTopology; +using geos::algorithm::LineIntersector; +using geos::geom::Coordinate; +using geos::geom::LineSegment; +using geos::noding::SegmentString; + + +namespace geos { // geos +namespace coverage { // geos.coverage + + +/* public */ +void +InvalidSegmentDetector::processIntersections( + SegmentString* ssAdj, std::size_t iAdj, + SegmentString* ssTarget, std::size_t iTarget) +{ + // note the source of the edges is important + CoverageRing* adj = static_cast(ssAdj); + CoverageRing* target = static_cast(ssTarget); + + //-- skip target segments with known status + if (target->isKnown(iTarget)) return; + + const Coordinate& t0 = target->getCoordinate(iTarget); + const Coordinate& t1 = target->getCoordinate(iTarget + 1); + const Coordinate& adj0 = adj->getCoordinate(iAdj); + const Coordinate& adj1 = adj->getCoordinate(iAdj + 1); + + //-- skip zero-length segments + if (t0.equals2D(t1) || adj0.equals2D(adj1)) + return; + + if (isEqual(t0, t1, adj0, adj1)) + return; + + bool bInvalid = isInvalid(t0, t1, adj0, adj1, adj, iAdj); + if (bInvalid) { + target->markInvalid(iTarget); + } +} + + +/* private */ +bool +InvalidSegmentDetector::isEqual( + const Coordinate& t0, const Coordinate& t1, + const Coordinate& adj0, const Coordinate& adj1) +{ + if (t0.equals2D(adj0) && t1.equals2D(adj1)) + return true; + if (t0.equals2D(adj1) && t1.equals2D(adj0)) + return true; + return false; +} + + +/* private */ +bool +InvalidSegmentDetector::isInvalid(const Coordinate& tgt0, const Coordinate& tgt1, + const Coordinate& adj0, const Coordinate& adj1, + CoverageRing* adj, std::size_t indexAdj) +{ + //-- segments that are collinear (but not matching) or are interior are invalid + if (isCollinearOrInterior(tgt0, tgt1, adj0, adj1, adj, indexAdj)) + return true; + + //-- segments which are nearly parallel for a significant length are invalid + if (distanceTol > 0 && isNearlyParallel(tgt0, tgt1, adj0, adj1, distanceTol)) + return true; + + return false; +} + + +/* private */ +bool +InvalidSegmentDetector::isCollinearOrInterior( + const Coordinate& tgt0, const Coordinate& tgt1, + const Coordinate& adj0, const Coordinate& adj1, + CoverageRing* adj, std::size_t indexAdj) +{ + LineIntersector li; + li.computeIntersection(tgt0, tgt1, adj0, adj1); + + //-- segments do not interact + if (! li.hasIntersection()) + return false; + + //-- If the segments are collinear, they do not match, so are invalid. + if (li.getIntersectionNum() == 2) { + //TODO: assert segments are not equal? + return true; + } + + //-- target segment crosses, or segments touch at non-endpoint + if (li.isProper() || li.isInteriorIntersection()) { + return true; + } + + /** + * At this point the segments have a single intersection point + * which is an endpoint of both segments. + * + * Check if the target segment lies in the interior of the adj ring. + */ + const Coordinate& intVertex = li.getIntersection(0); + bool isInterior = isInteriorSegment(intVertex, tgt0, tgt1, adj, indexAdj); + return isInterior; +} + + +/* private */ +bool +InvalidSegmentDetector::isInteriorSegment( + const Coordinate& intVertex, + const Coordinate& tgt0, const Coordinate& tgt1, + CoverageRing* adj, std::size_t indexAdj) +{ + //-- find target segment endpoint which is not the intersection point + const Coordinate* tgtEnd = intVertex.equals2D(tgt0) ? &tgt1 : &tgt0; + + //-- find adjacent-ring vertices on either side of intersection vertex + const Coordinate* adjPrev = &adj->findVertexPrev(indexAdj, intVertex); + const Coordinate* adjNext = &adj->findVertexNext(indexAdj, intVertex); + + //-- don't check if test segment is equal to either corner segment + if (tgtEnd->equals2D(*adjPrev) || tgtEnd->equals2D(*adjNext)) { + return false; + } + + //-- if needed, re-orient corner to have interior on right + if (! adj->isInteriorOnRight()) { + const Coordinate* temp = adjPrev; + adjPrev = adjNext; + adjNext = temp; + } + + bool isInterior = PolygonNodeTopology::isInteriorSegment(&intVertex, adjPrev, adjNext, tgtEnd); + return isInterior; +} + + +/* private static */ +bool +InvalidSegmentDetector::isNearlyParallel( + const Coordinate& p00, const Coordinate& p01, + const Coordinate& p10, const Coordinate& p11, + double distanceTol) +{ + LineSegment line0(p00, p01); + LineSegment line1(p10, p11); + LineSegment proj0; + LineSegment proj1; + + if (!line0.project(line1, proj0)) + return false; + + if (!line1.project(line0, proj1)) + return false; + + if (proj0.getLength() <= distanceTol || proj1.getLength() <= distanceTol) + return false; + + if (proj0.p0.distance(proj1.p1) < proj0.p0.distance(proj1.p0)) { + proj1.reverse(); + } + return proj0.p0.distance(proj1.p0) <= distanceTol + && proj0.p1.distance(proj1.p1) <= distanceTol; +} + + + +} // namespace geos.coverage +} // namespace geos + + diff --git a/Sources/geos/src/coverage/TPVWSimplifier.cpp b/Sources/geos/src/coverage/TPVWSimplifier.cpp new file mode 100644 index 0000000..7d395cd --- /dev/null +++ b/Sources/geos/src/coverage/TPVWSimplifier.cpp @@ -0,0 +1,332 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * Copyright (c) 2022 Martin Davis. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using geos::geom::Coordinate; +using geos::geom::Envelope; +using geos::geom::Geometry; +using geos::geom::GeometryFactory; +using geos::geom::LineString; +using geos::geom::MultiLineString; +using geos::simplify::LinkedLine; + + +namespace geos { +namespace coverage { // geos.coverage + + +typedef TPVWSimplifier::Edge Edge; +typedef TPVWSimplifier::EdgeIndex EdgeIndex; + + +/* public static */ +std::unique_ptr +TPVWSimplifier::simplify( + const MultiLineString* lines, + double distanceTolerance) +{ + TPVWSimplifier simp(lines, distanceTolerance); + std::unique_ptr result = simp.simplify(); + return result; +} + + +/* public static */ +std::unique_ptr +TPVWSimplifier::simplify( + const MultiLineString* p_lines, + std::vector& p_freeRings, + const MultiLineString* p_constraintLines, + double distanceTolerance) +{ + TPVWSimplifier simp(p_lines, distanceTolerance); + simp.setFreeRingIndices(p_freeRings); + simp.setConstraints(p_constraintLines); + std::unique_ptr result = simp.simplify(); + return result; +} + + +/* public */ +TPVWSimplifier::TPVWSimplifier( + const MultiLineString* lines, + double distanceTolerance) + : inputLines(lines) + , areaTolerance(distanceTolerance*distanceTolerance) + , geomFactory(inputLines->getFactory()) + , constraintLines(nullptr) + {} + + +/* private */ +void +TPVWSimplifier::setConstraints(const MultiLineString* constraints) +{ + constraintLines = constraints; +} + +/* public */ +void +TPVWSimplifier::setFreeRingIndices(std::vector& freeRing) +{ + //XXX Assert: bit set has same size as number of lines. + isFreeRing = freeRing; +} + +/* private */ +std::unique_ptr +TPVWSimplifier::simplify() +{ + std::vector emptyList; + std::vector edges = createEdges(inputLines, isFreeRing); + std::vector constraintEdges = createEdges(constraintLines, emptyList); + + EdgeIndex edgeIndex; + edgeIndex.add(edges); + edgeIndex.add(constraintEdges); + + std::vector> result; + for (auto& edge : edges) { + std::unique_ptr ptsSimp = edge.simplify(edgeIndex); + auto ls = geomFactory->createLineString(std::move(ptsSimp)); + result.emplace_back(ls.release()); + } + return geomFactory->createMultiLineString(std::move(result)); +} + +/* private */ +std::vector +TPVWSimplifier::createEdges( + const MultiLineString* lines, + std::vector& freeRing) +{ + std::vector edges; + + if (lines == nullptr) + return edges; + + for (std::size_t i = 0; i < lines->getNumGeometries(); i++) { + const LineString* line = lines->getGeometryN(i); + bool isFree = freeRing.empty() ? false : freeRing[i]; + edges.emplace_back(line, isFree, areaTolerance); + } + return edges; +} + +/************************************************************************/ + +TPVWSimplifier::Edge::Edge(const LineString* p_inputLine, bool p_isFreeRing, double p_areaTolerance) + : areaTolerance(p_areaTolerance) + , isFreeRing(p_isFreeRing) + , envelope(p_inputLine->getEnvelopeInternal()) + , nbPts(p_inputLine->getNumPoints()) + , linkedLine(*(p_inputLine->getCoordinatesRO())) + , vertexIndex(*(p_inputLine->getCoordinatesRO())) + , minEdgeSize(p_inputLine->getCoordinatesRO()->isRing() ? 3 : 2) +{ + // linkedLine = new LinkedLine(pts); + // minEdgeSize = linkedLine.isRing() ? 3 : 2; + // vertexIndex = new VertexSequencePackedRtree(pts); + //-- remove ring duplicate final vertex + if (linkedLine.isRing()) { + vertexIndex.remove(nbPts-1); + } +} + +/* private */ +const Coordinate& +TPVWSimplifier::Edge::getCoordinate(std::size_t index) const +{ + return linkedLine.getCoordinate(index); +} + +/* public */ +const Envelope* +TPVWSimplifier::Edge::getEnvelopeInternal() const +{ + return envelope; +} + +/* public */ +std::size_t +TPVWSimplifier::Edge::size() const +{ + return linkedLine.size(); +} + +/* private */ +std::unique_ptr +TPVWSimplifier::Edge::simplify(EdgeIndex& edgeIndex) +{ + Corner::PriorityQueue cornerQueue; + createQueue(cornerQueue); + while (! cornerQueue.empty() && size() > minEdgeSize) { + //Corner corner = cornerQueue.poll(); + Corner corner = cornerQueue.top(); + cornerQueue.pop(); + + //-- a corner may no longer be valid due to removal of adjacent corners + if (corner.isRemoved()) + continue; + //System.out.println(corner.toLineString(edge)); + //-- done when all small corners are removed + if (corner.getArea() > areaTolerance) + break; + if (isRemovable(corner, edgeIndex) ) { + removeCorner(corner, cornerQueue); + } + } + return linkedLine.getCoordinates(); +} + +/* private */ +void +TPVWSimplifier::Edge::createQueue(Corner::PriorityQueue& cornerQueue) +{ + std::size_t minIndex = (linkedLine.isRing() && isFreeRing) ? 0 : 1; + std::size_t maxIndex = nbPts - 1; + for (std::size_t i = minIndex; i < maxIndex; i++) { + addCorner(i, cornerQueue); + } + return; +} + +/* private */ +void +TPVWSimplifier::Edge::addCorner( + std::size_t i, + Corner::PriorityQueue& cornerQueue) +{ + if (isFreeRing || (i != 0 && i != nbPts-1)) { + Corner corner(&linkedLine, i); + if (corner.getArea() <= areaTolerance) { + cornerQueue.push(corner); + } + } +} + +/* private */ +bool +TPVWSimplifier::Edge::isRemovable( + Corner& corner, + EdgeIndex& edgeIndex) const +{ + Envelope cornerEnv = corner.envelope(); + //-- check nearby lines for violating intersections + //-- the query also returns this line for checking + std::vector edgeHits = edgeIndex.query(cornerEnv); + for (const Edge* edge : edgeHits) { + if (hasIntersectingVertex(corner, cornerEnv, *edge)) + return false; + //-- check if corner base equals line (2-pts) + //-- if so, don't remove corner, since that would collapse to the line + if (edge != this && edge->size() == 2) { + // TODO xxxxxx make linkedLine coordinates local + // to linkedline and return a reference, update + // simplify to clone reference at final step + auto linePts = edge->linkedLine.getCoordinates(); + if (corner.isBaseline(linePts->getAt(0), linePts->getAt(1))) + return false; + } + } + return true; +} + + +/* private */ +bool +TPVWSimplifier::Edge::hasIntersectingVertex( + const Corner& corner, + const Envelope& cornerEnv, + const Edge& edge) const +{ + std::vector result = edge.query(cornerEnv); + for (std::size_t index : result) { + + const Coordinate& v = edge.getCoordinate(index); + // ok if corner touches another line - should only happen at endpoints + if (corner.isVertex(v)) + continue; + + //--- does corner triangle contain vertex? + if (corner.intersects(v)) + return true; + } + return false; +} + +/* private */ +std::vector +TPVWSimplifier::Edge::query(const Envelope& cornerEnv) const +{ + std::vector result; + vertexIndex.query(cornerEnv, result); + return result; +} + + +/* private */ +void +TPVWSimplifier::Edge::removeCorner( + Corner& corner, + Corner::PriorityQueue& cornerQueue) +{ + std::size_t index = corner.getIndex(); + std::size_t prev = linkedLine.prev(index); + std::size_t next = linkedLine.next(index); + linkedLine.remove(index); + vertexIndex.remove(index); + + //-- potentially add the new corners created + addCorner(prev, cornerQueue); + addCorner(next, cornerQueue); +} + +/************************************************************************/ + + +/* public */ +void +TPVWSimplifier::EdgeIndex::add(std::vector& edges) +{ + for (Edge& edge : edges) { + index.insert(&edge); + } +} + +/* public */ +std::vector +TPVWSimplifier::EdgeIndex::query(const Envelope& queryEnv) +{ + std::vector hits; + index.query(queryEnv, hits); + return hits; +} + + + +} // geos.coverage +} // geos diff --git a/Sources/geos/src/coverage/VertexRingCounter.cpp b/Sources/geos/src/coverage/VertexRingCounter.cpp new file mode 100644 index 0000000..fe74288 --- /dev/null +++ b/Sources/geos/src/coverage/VertexRingCounter.cpp @@ -0,0 +1,66 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Paul Ramsey + * Copyright (c) 2022 Martin Davis. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include + + +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; +using geos::geom::CoordinateSequenceFilter; +using geos::geom::Geometry; + + +namespace geos { +namespace coverage { // geos.coverage + +/* public static */ +void +VertexRingCounter::count( + const std::vector& geoms, + std::map& counts) +{ + VertexRingCounter vertextCounter(counts); + for (const Geometry* geom : geoms) { + geom->apply_ro(vertextCounter); + } +} + + +/* public */ +void +VertexRingCounter::filter_ro(const CoordinateSequence& seq, std::size_t i) +{ + //-- for rings don't double-count duplicate endpoint + if (seq.isRing() && i == 0) + return; + + const Coordinate& v = seq.getAt(i); + auto search = vertexCounts.find(v); + std::size_t count = 0; + if (search != vertexCounts.end()) { + count = search->second; + } + count++; + vertexCounts[v] = count; +} + + +} // geos.coverage +} // geos diff --git a/Sources/geos/src/edgegraph/EdgeGraph.cpp b/Sources/geos/src/edgegraph/EdgeGraph.cpp index 8ca8f97..0b33ebd 100644 --- a/Sources/geos/src/edgegraph/EdgeGraph.cpp +++ b/Sources/geos/src/edgegraph/EdgeGraph.cpp @@ -31,7 +31,7 @@ namespace edgegraph { // geos.edgegraph /*protected*/ HalfEdge* -EdgeGraph::createEdge(const Coordinate& orig) +EdgeGraph::createEdge(const CoordinateXYZM& orig) { edges.emplace_back(orig); return &(edges.back()); @@ -39,7 +39,7 @@ EdgeGraph::createEdge(const Coordinate& orig) /*private*/ HalfEdge* -EdgeGraph::create(const Coordinate& p0, const Coordinate& p1) +EdgeGraph::create(const CoordinateXYZM& p0, const CoordinateXYZM& p1) { HalfEdge* e0 = createEdge(p0); HalfEdge* e1 = createEdge(p1); @@ -49,7 +49,7 @@ EdgeGraph::create(const Coordinate& p0, const Coordinate& p1) /*public*/ HalfEdge* -EdgeGraph::addEdge(const Coordinate& orig, const Coordinate& dest) +EdgeGraph::addEdge(const CoordinateXYZM& orig, const CoordinateXYZM& dest) { if (! isValidEdge(orig, dest)) { return nullptr; @@ -80,14 +80,14 @@ EdgeGraph::addEdge(const Coordinate& orig, const Coordinate& dest) /*public static*/ bool -EdgeGraph::isValidEdge(const Coordinate& orig, const Coordinate& dest) +EdgeGraph::isValidEdge(const CoordinateXY& orig, const CoordinateXY& dest) { return dest.compareTo(orig) != 0; } /*private*/ HalfEdge* -EdgeGraph::insert(const Coordinate& orig, const Coordinate& dest, HalfEdge* eAdj) +EdgeGraph::insert(const CoordinateXYZM& orig, const CoordinateXYZM& dest, HalfEdge* eAdj) { // edge does not exist, so create it and insert in graph HalfEdge* e = create(orig, dest); @@ -125,7 +125,7 @@ EdgeGraph::getVertexEdges(std::vector& edgesOut) /*public*/ HalfEdge* -EdgeGraph::findEdge(const Coordinate& orig, const Coordinate& dest) +EdgeGraph::findEdge(const CoordinateXY& orig, const CoordinateXY& dest) { HalfEdge* e = nullptr; auto it = vertexMap.find(orig); diff --git a/Sources/geos/src/edgegraph/EdgeGraphBuilder.cpp b/Sources/geos/src/edgegraph/EdgeGraphBuilder.cpp index 9ad3ef8..d806bfd 100644 --- a/Sources/geos/src/edgegraph/EdgeGraphBuilder.cpp +++ b/Sources/geos/src/edgegraph/EdgeGraphBuilder.cpp @@ -86,7 +86,8 @@ EdgeGraphBuilder::add(const LineString* line) { const CoordinateSequence* seq = line->getCoordinatesRO(); for (std::size_t i = 1, sz = seq->getSize(); i < sz; i++) { - graph->addEdge(seq->getAt(i-1), seq->getAt(i)); + // FIXME handle types properly + graph->addEdge(CoordinateXYZM(seq->getAt(i-1)), CoordinateXYZM(seq->getAt(i))); } } diff --git a/Sources/geos/src/edgegraph/HalfEdge.cpp b/Sources/geos/src/edgegraph/HalfEdge.cpp index a454567..98f4b67 100644 --- a/Sources/geos/src/edgegraph/HalfEdge.cpp +++ b/Sources/geos/src/edgegraph/HalfEdge.cpp @@ -33,7 +33,7 @@ namespace edgegraph { // geos.edgegraph /*public static*/ HalfEdge* -HalfEdge::create(const Coordinate& p0, const Coordinate& p1) +HalfEdge::create(const CoordinateXYZM& p0, const CoordinateXYZM& p1) { HalfEdge* e0 = new HalfEdge(p0); HalfEdge* e1 = new HalfEdge(p1); @@ -54,7 +54,7 @@ HalfEdge::link(HalfEdge* p_sym) /*public*/ HalfEdge* -HalfEdge::find(const Coordinate& p_dest) +HalfEdge::find(const CoordinateXY& p_dest) { HalfEdge* oNxt = this; do { @@ -71,7 +71,7 @@ HalfEdge::find(const Coordinate& p_dest) /*public*/ bool -HalfEdge::equals(const Coordinate& p0, const Coordinate& p1) const +HalfEdge::equals(const CoordinateXY& p0, const CoordinateXY& p1) const { return m_orig.equals2D(p0) && m_sym->m_orig.equals2D(p1); } diff --git a/Sources/geos/src/geom/CircularString.cpp b/Sources/geos/src/geom/CircularString.cpp new file mode 100644 index 0000000..b666c8e --- /dev/null +++ b/Sources/geos/src/geom/CircularString.cpp @@ -0,0 +1,98 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include +#include +#include + +namespace geos { +namespace geom { + +/*public*/ +CircularString::CircularString(std::unique_ptr&& newCoords, + const GeometryFactory& factory) + : + SimpleCurve(std::move(newCoords), false, factory) +{ + validateConstruction(); +} + +CircularString::~CircularString() = default; + +std::unique_ptr +CircularString::clone() const +{ + return std::unique_ptr(cloneImpl()); +} + +std::string +CircularString::getGeometryType() const +{ + return "CircularString"; +} + +GeometryTypeId +CircularString::getGeometryTypeId() const +{ + return GEOS_CIRCULARSTRING; +} + +double +CircularString::getLength() const +{ + if (isEmpty()) { + return 0; + } + + const CoordinateSequence& coords = *getCoordinatesRO(); + + double tot = 0; + for (std::size_t i = 2; i < coords.size(); i += 2) { + auto len = CircularArc(coords[i-2], coords[i-1], coords[i]).getLength(); + tot += len; + } + return tot; +} + +CircularString* +CircularString::reverseImpl() const +{ + if (isEmpty()) { + return clone().release(); + } + + assert(points.get()); + auto seq = points->clone(); + seq->reverse(); + assert(getFactory()); + return getFactory()->createCircularString(std::move(seq)).release(); +} + +void +CircularString::validateConstruction() +{ + if (points.get() == nullptr) { + points = std::make_unique(); + return; + } + + if (points->size() == 2) { + throw util::IllegalArgumentException("point array must contain 0 or >2 elements\n"); + } +} + +} +} diff --git a/Sources/geos/src/geom/CompoundCurve.cpp b/Sources/geos/src/geom/CompoundCurve.cpp new file mode 100644 index 0000000..2175c7e --- /dev/null +++ b/Sources/geos/src/geom/CompoundCurve.cpp @@ -0,0 +1,311 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include +#include +#include + +namespace geos { +namespace geom { + +CompoundCurve::CompoundCurve(std::vector>&& p_curves, + const GeometryFactory& gf) + : Curve(gf), + curves(std::move(p_curves)), + envelope(computeEnvelopeInternal()) {} + +CompoundCurve::CompoundCurve(const CompoundCurve& other) + : Curve(other), + curves(other.curves.size()), + envelope(other.envelope) +{ + for (std::size_t i = 0; i < curves.size(); i++) { + curves[i].reset(static_cast(other.curves[i]->clone().release())); + } +} + +CompoundCurve& +CompoundCurve::operator=(const CompoundCurve& other) +{ + curves.resize(other.curves.size()); + envelope = other.envelope; + + for (std::size_t i =0; i < curves.size(); i++) { + curves[i].reset(static_cast(other.curves[i]->clone().release())); + } + + return *this; +} + +void +CompoundCurve::apply_ro(CoordinateFilter* cf) const +{ + for (const auto& curve : curves) { + curve->apply_ro(cf); + } +} + +void +CompoundCurve::apply_ro(CoordinateSequenceFilter& csf) const +{ + for (const auto& curve : curves) { + const auto& seq = *curve->getCoordinatesRO(); + for (std::size_t i = 0; i < seq.size(); i++) { + if (csf.isDone()) { + return; + } + csf.filter_ro(seq, i); + } + } +} + +void +CompoundCurve::apply_rw(const CoordinateFilter* cf) +{ + for (auto& curve : curves) { + curve->apply_rw(cf); + } +} + +void +CompoundCurve::apply_rw(CoordinateSequenceFilter&) +{ + throw util::UnsupportedOperationException(); +} + +std::unique_ptr +CompoundCurve::clone() const +{ + return std::unique_ptr(cloneImpl()); +} + +CompoundCurve* +CompoundCurve::cloneImpl() const +{ + return new CompoundCurve(*this); +} + +int +CompoundCurve::compareToSameClass(const Geometry* g) const +{ + const CompoundCurve* curve = detail::down_cast(g); + return compare(curves, curve->curves); +} + +Envelope +CompoundCurve::computeEnvelopeInternal() const +{ + Envelope e; + for (const auto& curve : curves) { + e.expandToInclude(curve->getEnvelopeInternal()); + } + return e; +} + +bool +CompoundCurve::equalsExact(const Geometry* other, double tolerance) const +{ + if (!isEquivalentClass(other)) { + return false; + } + + const CompoundCurve* otherCurve = static_cast(other); + if (curves.size() != otherCurve->curves.size()) { + return false; + } + + for (std::size_t i = 0; i < otherCurve->curves.size(); i++) { + if (!curves[i]->equalsExact(otherCurve->curves[i].get(), tolerance)) { + return false; + } + } + + return true; +} + +bool +CompoundCurve::equalsIdentical(const Geometry* other) const +{ + if (!isEquivalentClass(other)) { + return false; + } + + const CompoundCurve* otherCurve = static_cast(other); + if (curves.size() != otherCurve->curves.size()) { + return false; + } + + for (std::size_t i = 0; i < otherCurve->curves.size(); i++) { + if (!curves[i]->equalsIdentical(otherCurve->curves[i].get())) { + return false; + } + } + + return true; +} + +std::unique_ptr +CompoundCurve::getBoundary() const +{ + operation::BoundaryOp bop(*this); + return bop.getBoundary(); +} + +const CoordinateXY* +CompoundCurve::getCoordinate() const +{ + for (const auto& curve : curves) { + if (!curve->isEmpty()) { + return curve->getCoordinate(); + } + } + + return nullptr; +} + +uint8_t +CompoundCurve::getCoordinateDimension() const +{ + return static_cast(2 + hasZ() + hasM()); +} + +std::unique_ptr +CompoundCurve::getCoordinates() const +{ + auto ret = std::make_unique(0, hasZ(), hasM()); + for (const auto& curve : curves) { + ret->add(*curve->getCoordinatesRO()); + } + return ret; +} + +const SimpleCurve* +CompoundCurve::getCurveN(std::size_t i) const +{ + return curves[i].get(); +} + +std::string +CompoundCurve::getGeometryType() const +{ + return "CompoundCurve"; +} + +GeometryTypeId +CompoundCurve::getGeometryTypeId() const +{ + return GEOS_COMPOUNDCURVE; +} + +double +CompoundCurve::getLength() const +{ + double sum = 0; + for (const auto& curve : curves) { + sum += curve->getLength(); + } + return sum; +} + +std::size_t +CompoundCurve::getNumCurves() const +{ + return curves.size(); +} + +std::size_t +CompoundCurve::getNumPoints() const +{ + std::size_t n =0; + for (const auto& curve : curves) { + n += curve->getNumPoints(); + } + return n; +} + +bool +CompoundCurve::hasZ() const +{ + return std::any_of(curves.begin(), curves.end(), [](const auto& curve) { + return curve->hasZ(); + }); +} + +bool +CompoundCurve::hasM() const +{ + return std::any_of(curves.begin(), curves.end(), [](const auto& curve) { + return curve->hasM(); + }); +} + +bool +CompoundCurve::hasCurvedComponents() const +{ + for (const auto& curve : curves) { + if (curve->hasCurvedComponents()) { + return true; + } + } + return false; +} + +bool +CompoundCurve::isClosed() const +{ + if (isEmpty()) { + return false; + } + + const SimpleCurve& first = *curves.front(); + const SimpleCurve& last = *curves.back(); + + return first.getCoordinateN(0) == last.getCoordinateN(last.getNumPoints() - 1); +} + +bool +CompoundCurve::isEmpty() const +{ + return !std::any_of(curves.begin(), curves.end(), [](const auto& curve) { + return !curve->isEmpty(); + }); +} + +void +CompoundCurve::normalize() +{ + throw util::UnsupportedOperationException(); +} + +std::unique_ptr +CompoundCurve::reverse() const +{ + return std::unique_ptr(reverseImpl()); +} + +CompoundCurve* +CompoundCurve::reverseImpl() const +{ + std::vector> reversed(curves.size()); + std::transform(curves.rbegin(), curves.rend(), reversed.begin(), [](const auto& curve) { + return std::unique_ptr(static_cast(curve->reverse().release())); + }); + + return getFactory()->createCompoundCurve(std::move(reversed)).release(); +} + +} +} diff --git a/Sources/geos/src/geom/Coordinate.cpp b/Sources/geos/src/geom/Coordinate.cpp index 9899cc7..3ab0c5f 100644 --- a/Sources/geos/src/geom/Coordinate.cpp +++ b/Sources/geos/src/geom/Coordinate.cpp @@ -24,14 +24,35 @@ namespace geos { namespace geom { // geos::geom -Coordinate Coordinate::_nullCoord = Coordinate(DoubleNotANumber, DoubleNotANumber, DoubleNotANumber); +const CoordinateXY CoordinateXY::_nullCoord = CoordinateXY(DoubleNotANumber, DoubleNotANumber); +const Coordinate Coordinate::_nullCoord = Coordinate(DoubleNotANumber, DoubleNotANumber, DoubleNotANumber); +const CoordinateXYM CoordinateXYM::_nullCoord = CoordinateXYM(DoubleNotANumber, DoubleNotANumber, DoubleNotANumber); +const CoordinateXYZM CoordinateXYZM::_nullCoord = CoordinateXYZM(DoubleNotANumber, DoubleNotANumber, DoubleNotANumber, DoubleNotANumber); -Coordinate& +const CoordinateXY& +CoordinateXY::getNull() +{ + return _nullCoord; +} + +const Coordinate& Coordinate::getNull() { return _nullCoord; } +const CoordinateXYM& +CoordinateXYM::getNull() +{ + return _nullCoord; +} + +const CoordinateXYZM& +CoordinateXYZM::getNull() +{ + return _nullCoord; +} + std::string Coordinate::toString() const { @@ -40,14 +61,79 @@ Coordinate::toString() const return s.str(); } +std::string +CoordinateXY::toString() const +{ + std::ostringstream s; + s << std::setprecision(17) << *this; + return s.str(); +} + +std::string +CoordinateXYM::toString() const +{ + std::ostringstream s; + s << std::setprecision(17) << *this; + return s.str(); +} + +std::string +CoordinateXYZM::toString() const +{ + std::ostringstream s; + s << std::setprecision(17) << *this; + return s.str(); +} + +std::ostream& +operator<< (std::ostream& os, const CoordinateType typ) +{ + switch(typ) { + case CoordinateType::XY: os << "XY"; break; + case CoordinateType::XYZ: os << "XYZ"; break; + case CoordinateType::XYM: os << "XYM"; break; + case CoordinateType::XYZM: os << "XYZM"; break; + } + + return os; +} + +std::ostream& +operator<< (std::ostream& os, const CoordinateXY& c) +{ + os << c.x << " " << c.y; + return os; +} + std::ostream& operator<< (std::ostream& os, const Coordinate& c) { - if(std::isnan(c.z)) { - os << c.x << " " << c.y; + os << c.x << " " << c.y; + if(!std::isnan(c.z)) { + os << " " << c.z; + } + return os; +} + +std::ostream& +operator<< (std::ostream& os, const CoordinateXYM& c) +{ + os << c.x << " " << c.y; + if(!std::isnan(c.m)) { + os << " " << c.m; + } + return os; +} + +std::ostream& +operator<< (std::ostream& os, const CoordinateXYZM& c) +{ + os << c.x << " " << c.y; + if(!std::isnan(c.z) || !std::isnan(c.m)) { + os << " " << c.z; } - else { - os << c.x << " " << c.y << " " << c.z; + if(!std::isnan(c.m)) { + os << " " << c.m; } return os; } diff --git a/Sources/geos/src/geom/CoordinateArraySequence.cpp b/Sources/geos/src/geom/CoordinateArraySequence.cpp deleted file mode 100644 index cfeb724..0000000 --- a/Sources/geos/src/geom/CoordinateArraySequence.cpp +++ /dev/null @@ -1,257 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2001-2002 Vivid Solutions Inc. - * Copyright (C) 2005 Refractions Research Inc. - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - **********************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace geos { -namespace geom { // geos::geom - -CoordinateArraySequence::CoordinateArraySequence(): - dimension(0) -{ -} - -CoordinateArraySequence::CoordinateArraySequence(std::size_t n, - std::size_t dimension_in): - vect(n), - dimension(dimension_in) -{ -} - -CoordinateArraySequence::CoordinateArraySequence(std::vector && coords, std::size_t dimension_in): - vect(std::move(coords)), - dimension(dimension_in) -{ -} - -CoordinateArraySequence::CoordinateArraySequence( - std::vector* coords, std::size_t dimension_in) - : dimension(dimension_in) -{ - std::unique_ptr> coordp(coords); - - if(coordp) { - vect = std::move(*coords); - } -} - -CoordinateArraySequence::CoordinateArraySequence( - const CoordinateArraySequence& c) - : - CoordinateSequence(c), - vect(c.vect), - dimension(c.getDimension()) -{ -} - -CoordinateArraySequence::CoordinateArraySequence( - const CoordinateSequence& c) - : - CoordinateSequence(c), - vect(c.size()), - dimension(c.getDimension()) -{ - for(std::size_t i = 0, n = vect.size(); i < n; ++i) { - vect[i] = c.getAt(i); - } -} - -std::unique_ptr -CoordinateArraySequence::clone() const -{ - return detail::make_unique(*this); -} - -void -CoordinateArraySequence::setPoints(const std::vector& v) -{ - vect.assign(v.begin(), v.end()); -} - -std::size_t -CoordinateArraySequence::getDimension() const -{ - if(dimension != 0) { - return dimension; - } - - if(vect.empty()) { - return 3; - } - - if(std::isnan(vect[0].z)) { - dimension = 2; - } - else { - dimension = 3; - } - - return dimension; -} - -void -CoordinateArraySequence::toVector(std::vector& out) const -{ - out.insert(out.end(), vect.begin(), vect.end()); -} - -void -CoordinateArraySequence::add(const Coordinate& c) -{ - vect.push_back(c); -} - -void -CoordinateArraySequence::add(const Coordinate& c, bool allowRepeated) -{ - if(!allowRepeated && ! vect.empty()) { - const Coordinate& last = vect.back(); - if(last.equals2D(c)) { - return; - } - } - vect.push_back(c); -} - -void -CoordinateArraySequence::add(const CoordinateSequence* cl, bool allowRepeated, bool direction) -{ - // FIXME: don't rely on negative values for 'j' (the reverse case) - - const auto npts = cl->size(); - if(direction) { - for(std::size_t i = 0; i < npts; ++i) { - add(cl->getAt(i), allowRepeated); - } - } - else { - for(auto j = npts; j > 0; --j) { - add(cl->getAt(j - 1), allowRepeated); - } - } -} - -/*public*/ -void -CoordinateArraySequence::add(std::size_t i, const Coordinate& coord, - bool allowRepeated) -{ - // don't add duplicate coordinates - if(! allowRepeated) { - std::size_t sz = size(); - if(sz > 0) { - if(i > 0) { - const Coordinate& prev = getAt(i - 1); - if(prev.equals2D(coord)) { - return; - } - } - if(i < sz) { - const Coordinate& next = getAt(i); - if(next.equals2D(coord)) { - return; - } - } - } - } - - vect.insert(std::next(vect.begin(), static_cast(i)), coord); -} - -size_t -CoordinateArraySequence::getSize() const -{ - return vect.size(); -} - -const Coordinate& -CoordinateArraySequence::getAt(std::size_t pos) const -{ - return vect[pos]; -} - -void -CoordinateArraySequence::getAt(std::size_t pos, Coordinate& c) const -{ - c = vect[pos]; -} - -void -CoordinateArraySequence::setAt(const Coordinate& c, std::size_t pos) -{ - vect[pos] = c; -} - -void -CoordinateArraySequence::expandEnvelope(Envelope& env) const -{ - for(const auto& coord : vect) { - env.expandToInclude(coord); - } -} - -void -CoordinateArraySequence::setOrdinate(std::size_t index, std::size_t ordinateIndex, - double value) -{ - switch(ordinateIndex) { - case CoordinateSequence::X: - vect[index].x = value; - break; - case CoordinateSequence::Y: - vect[index].y = value; - break; - case CoordinateSequence::Z: - vect[index].z = value; - break; - default: { - std::stringstream ss; - ss << "Unknown ordinate index " << ordinateIndex; - throw util::IllegalArgumentException(ss.str()); - break; - } - } -} - -void -CoordinateArraySequence::closeRing() -{ - if(!isEmpty() && front() != back()) { - add(front()); - } -} - -void -CoordinateArraySequence::apply_rw(const CoordinateFilter* filter) -{ - for(auto& coord : vect) { - filter->filter_rw(&coord); - } - dimension = 0; // re-check (see http://trac.osgeo.org/geos/ticket/435) -} - -} // namespace geos::geom -} //namespace geos diff --git a/Sources/geos/src/geom/CoordinateArraySequenceFactory.cpp b/Sources/geos/src/geom/CoordinateArraySequenceFactory.cpp deleted file mode 100644 index 42de7c2..0000000 --- a/Sources/geos/src/geom/CoordinateArraySequenceFactory.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/********************************************************************** - * - * GEOS - Geometry Engine Open Source - * http://geos.osgeo.org - * - * Copyright (C) 2001-2002 Vivid Solutions Inc. - * Copyright (C) 2005 Refractions Research Inc. - * - * This is free software; you can redistribute and/or modify it under - * the terms of the GNU Lesser General Public Licence as published - * by the Free Software Foundation. - * See the COPYING file for more information. - * - **********************************************************************/ - -#include - - -namespace geos { -namespace geom { // geos::geom - -static CoordinateArraySequenceFactory defaultCoordinateSequenceFactory; - -std::unique_ptr -CoordinateArraySequenceFactory::create() const -{ - return std::unique_ptr( - new CoordinateArraySequence( - reinterpret_cast*>(0), 0)); -} - -const CoordinateSequenceFactory* -CoordinateArraySequenceFactory::instance() -{ - return &defaultCoordinateSequenceFactory; -} - -} // namespace geos::geom -} // namespace geos - diff --git a/Sources/geos/src/geom/CoordinateSequence.cpp b/Sources/geos/src/geom/CoordinateSequence.cpp index 75cb947..599ca6f 100644 --- a/Sources/geos/src/geom/CoordinateSequence.cpp +++ b/Sources/geos/src/geom/CoordinateSequence.cpp @@ -3,6 +3,7 @@ * GEOS - Geometry Engine Open Source * http://geos.osgeo.org * + * Copyright (C) 2022 ISciences LLC * Copyright (C) 2006 Refractions Research Inc. * Copyright (C) 2001-2002 Vivid Solutions Inc. * @@ -13,15 +14,17 @@ * **********************************************************************/ +#include #include +#include #include -// FIXME: we should probably not be using CoordinateArraySequenceFactory -#include #include #include #include +#include #include +#include #include #include #include @@ -35,16 +38,265 @@ namespace geom { // geos::geom static Profiler* profiler = Profiler::instance(); #endif +// If GEOS_COORDSEQ_PADZ is defined: +// - XY sequences will be stored as XYZ +// - XYM sequences will be stored as XYZM +// This prevents incorrect results when an XYZ Coordinate is read from +// a sequence storing XY or XYM. When GEOS is changed to check +// coordinate types throughout the library, this can be undefined to +// store coordinates efficiently. +#define GEOS_COORDSEQ_PADZ + +CoordinateSequence::CoordinateSequence() : + CoordinateSequence(0, 0) {} + +CoordinateSequence::CoordinateSequence(std::size_t sz, bool hasz, bool hasm, bool init) : +#ifdef GEOS_COORDSEQ_PADZ + m_vect(sz * (3u + hasm)), + m_stride(static_cast(3u + hasm)), +#else + m_vect(sz * (2u + hasm + hasz)), + m_stride(static_cast(2u + hasm + hasz)), +#endif + m_hasdim(true), + m_hasz(hasz), + m_hasm(hasm) +{ + if (init) { + initialize(); + } +} + +CoordinateSequence::CoordinateSequence(std::size_t sz, std::size_t dim) : + m_vect(sz * std::max(static_cast(dim), static_cast(3))), + m_stride(std::max(static_cast(dim), static_cast(3))), + m_hasdim(dim > 0), + m_hasz(dim >= 3), + m_hasm(dim == 4) +{ + if (dim == 1 || dim > 4) { + throw util::IllegalArgumentException("Declared dimension must be 2, 3, or 4"); + } + initialize(); +} + +CoordinateSequence::CoordinateSequence(const std::initializer_list& list) : + m_stride(3), + m_hasdim(false), + m_hasz(false), + m_hasm(false) +{ + reserve(list.size()); + add(list.begin(), list.end()); +} + +CoordinateSequence::CoordinateSequence(const std::initializer_list& list) : +#ifdef GEOS_COORDSEQ_PADZ + m_stride(3), +#else + m_stride(2), +#endif + m_hasdim(true), + m_hasz(false), + m_hasm(false) +{ + reserve(list.size()); + add(list.begin(), list.end()); +} + +CoordinateSequence::CoordinateSequence(const std::initializer_list& list) : +#ifdef GEOS_COORDSEQ_PADZ + m_stride(4), +#else + m_stride(3), +#endif + m_hasdim(true), + m_hasz(false), + m_hasm(true) +{ + reserve(list.size()); + add(list.begin(), list.end()); +} + +CoordinateSequence::CoordinateSequence(const std::initializer_list& list) : + m_stride(4), + m_hasdim(true), + m_hasz(true), + m_hasm(true) +{ + reserve(list.size()); + add(list.begin(), list.end()); +} + +template +void fillVector(std::vector & v) +{ + const T c; + T* from = reinterpret_cast(v.data()); + T* to = reinterpret_cast(v.data() + v.size()); + std::fill(from, to, c); +} + +void +CoordinateSequence::initialize() +{ + switch(getCoordinateType()) { + case CoordinateType::XYZ: fillVector(m_vect); break; + case CoordinateType::XYZM: fillVector(m_vect); break; + case CoordinateType::XY: fillVector(m_vect); break; + case CoordinateType::XYM: fillVector(m_vect); break; + } +} + +void +CoordinateSequence::add(const CoordinateSequence& cs, std::size_t from, std::size_t to) +{ + if (cs.stride() == stride() && cs.hasM() == hasM()) { + m_vect.insert(m_vect.end(), + std::next(cs.m_vect.cbegin(), static_cast(from * stride())), + std::next(cs.m_vect.cbegin(), static_cast((to + 1u)*stride()))); + } else { + std::size_t pos = size(); + make_space(pos, to - from + 1); + + switch(cs.getCoordinateType()) { + case CoordinateType::XY: cs.forEach(from, to, [this, &pos](const CoordinateXY& c) { setAt(c, pos++); }); break; + case CoordinateType::XYZ: cs.forEach(from, to, [this, &pos](const Coordinate& c) { setAt(c, pos++); }); break; + case CoordinateType::XYZM: cs.forEach(from, to, [this, &pos](const CoordinateXYZM& c) { setAt(c, pos++); }); break; + case CoordinateType::XYM: cs.forEach(from, to, [this, &pos](const CoordinateXYM& c) { setAt(c, pos++); }); break; + } + } +} + +void +CoordinateSequence::add(const CoordinateSequence& cs) +{ + add(cs, 0, cs.size() - 1); +} + +void +CoordinateSequence::add(const CoordinateSequence& cs, std::size_t from, std::size_t to, bool allowRepeated) +{ + if (allowRepeated) { + add(cs, from, to); + return; + } + + std::size_t first = from; + + // Check for case where first point(s) of `cs` duplicate last points of `this` + if (!isEmpty()) { + while(first <= to && cs.getAt(first).equals2D(back())) { + first++; + } + } + + if (first > to) { + // No unique points to add. + return; + } + + std::size_t last = first + 1; + const CoordinateXY* last_unique = &cs.getAt(first); + while(last <= to) { + const CoordinateXY* curr = &cs.getAt(last); + if (curr->equals2D(*last_unique)) { + // End of block + add(cs, first, last - 1); + do { + last++; + } while (last <= to && cs.getAt(last).equals2D(*last_unique)); + + if (last != (to + 1) ) { + first = last; + last_unique = &cs.getAt(first); + } + last++; + } else { + last_unique = curr; + last++; + } + } + + if (last == (to + 1)) { + add(cs, first, to); + } +} + +void +CoordinateSequence::add(const CoordinateSequence& cs, bool allowRepeated) { + if (!cs.isEmpty()) { + add(cs, 0, cs.size() - 1, allowRepeated); + } +} + +void +CoordinateSequence::add(const CoordinateSequence& cl, bool allowRepeated, bool forwardDirection) +{ + if (forwardDirection) { + add(cl, allowRepeated); + } else { + CoordinateSequence rev(cl); + rev.reverse(); + add(rev, allowRepeated); + return; + } + +} + +/*public*/ + +std::unique_ptr +CoordinateSequence::clone() const +{ + return detail::make_unique(*this); +} + +void +CoordinateSequence::closeRing(bool allowRepeated) +{ + if(!isEmpty() && (allowRepeated || front() != back())) { + m_vect.insert(m_vect.end(), + m_vect.begin(), + std::next(m_vect.begin(), stride())); + } +} + +std::size_t +CoordinateSequence::getDimension() const +{ + if (m_hasdim) { + return static_cast(2 + hasM() + hasZ()); + } + + if (m_vect.empty()) { + return 3; + } + + assert(stride() >= 3); + m_hasdim = true; + if (!std::isnan(getAt(0).z)) { + m_hasz = true; + } + + return getDimension(); +} + + double CoordinateSequence::getOrdinate(std::size_t index, std::size_t ordinateIndex) const { switch(ordinateIndex) { case CoordinateSequence::X: - return getAt(index).x; + return getAt(index).x; case CoordinateSequence::Y: - return getAt(index).y; + return getAt(index).y; case CoordinateSequence::Z: - return getAt(index).z; + return hasZ() ? getAt(index).z : DoubleNotANumber; + case CoordinateSequence::M: + return getCoordinateType() == CoordinateType::XYZM ? getAt(index).m : + getCoordinateType() == CoordinateType::XYM ? getAt(index).m : + DoubleNotANumber; default: return DoubleNotANumber; } @@ -53,15 +305,37 @@ CoordinateSequence::getOrdinate(std::size_t index, std::size_t ordinateIndex) co bool CoordinateSequence::hasRepeatedPoints() const { - const std::size_t p_size = getSize(); - for(std::size_t i = 1; i < p_size; i++) { - if(getAt(i - 1) == getAt(i)) { + // Iterate over the array of doubles and check x/y values directly. + // This is about 30% faster than retrieving/comparing CoordinateXY&. + for (std::size_t i = stride(); i < m_vect.size(); i += stride()) { + if (m_vect[i - stride()] == m_vect[i] && m_vect[i + 1 - stride()] == m_vect[i+1]) { + return true; + } + } + return false; +} + +bool +CoordinateSequence::hasRepeatedOrInvalidPoints() const +{ + // Check first points + if (! (std::isfinite(m_vect[0]) && std::isfinite(m_vect[1]) )) { + return true; + } + // Iterate over the array of doubles and check x/y values directly. + // This is about 30% faster than retrieving/comparing CoordinateXY&. + for (std::size_t i = stride(); i < m_vect.size(); i += stride()) { + if (! (std::isfinite(m_vect[i]) && std::isfinite(m_vect[i+1]) )) { + return true; + } + if (m_vect[i - stride()] == m_vect[i] && m_vect[i + 1 - stride()] == m_vect[i+1]) { return true; } } return false; } + /* * Returns either the given coordinate array if its length is greater than the * given amount, or an empty coordinate array. @@ -75,70 +349,48 @@ CoordinateSequence::atLeastNCoordinatesOrNothing(std::size_t n, } else { // FIXME: return NULL rather then empty coordinate array - return CoordinateArraySequenceFactory::instance()->create().release(); - } -} - - -bool -CoordinateSequence::hasRepeatedPoints(const CoordinateSequence* cl) -{ - const std::size_t size = cl->getSize(); - for(std::size_t i = 1; i < size; i++) { - if(cl->getAt(i - 1) == cl->getAt(i)) { - return true; - } + return new CoordinateSequence(0, c->getDimension()); } - return false; } - -const Coordinate* +const CoordinateXY* CoordinateSequence::minCoordinate() const { - const Coordinate* minCoord = nullptr; + const CoordinateXY* minCoord = nullptr; const std::size_t p_size = getSize(); for(std::size_t i = 0; i < p_size; i++) { - if(minCoord == nullptr || minCoord->compareTo(getAt(i)) > 0) { - minCoord = &getAt(i); + if(minCoord == nullptr || minCoord->compareTo(getAt(i)) > 0) { + minCoord = &getAt(i); } } return minCoord; } size_t -CoordinateSequence::indexOf(const Coordinate* coordinate, +CoordinateSequence::indexOf(const CoordinateXY* coordinate, const CoordinateSequence* cl) { std::size_t p_size = cl->size(); for(std::size_t i = 0; i < p_size; ++i) { - if((*coordinate) == cl->getAt(i)) { + if((*coordinate) == cl->getAt(i)) { return i; } } - return std::numeric_limits::max(); + return NO_COORD_INDEX; } void CoordinateSequence::scroll(CoordinateSequence* cl, - const Coordinate* firstCoordinate) + const CoordinateXY* firstCoordinate) { - // FIXME: use a standard algorithm instead - std::size_t i, j = 0; std::size_t ind = indexOf(firstCoordinate, cl); - if(ind < 1) { + if(ind == 0 || ind == std::numeric_limits::max()) { return; // not found or already first } - const std::size_t length = cl->getSize(); - std::vector v(length); - for(i = ind; i < length; i++) { - v[j++] = cl->getAt(i); - } - for(i = 0; i < ind; i++) { - v[j++] = cl->getAt(i); - } - cl->setPoints(v); + std::rotate(cl->m_vect.begin(), + std::next(cl->m_vect.begin(), static_cast(ind * cl->stride())), + cl->m_vect.end()); } int @@ -164,22 +416,36 @@ CoordinateSequence::isRing() const if (size() < 4) return false; - if (getAt(0) != getAt(size()-1)) + if (front() != back()) return false; return true; } void -CoordinateSequence::reverse(CoordinateSequence* cl) +CoordinateSequence::reverse() +{ + auto mid = m_vect.size() / 2; + auto last = m_vect.size() - stride(); + for (std::size_t i = 0; i < mid; i += stride()) { + switch(stride()) { + case 4: std::swap(m_vect[i + 3], m_vect[last - i + 3]); // fall-through + case 3: std::swap(m_vect[i + 2], m_vect[last - i + 2]); // fall-through + case 2: std::swap(m_vect[i + 1], m_vect[last - i + 1]); + std::swap(m_vect[i], m_vect[last - i]); + + } + } +} + +void +CoordinateSequence::sort() { - // FIXME: use a standard algorithm - auto last = cl->size() - 1; - auto mid = last / 2; - for(std::size_t i = 0; i <= mid; i++) { - const Coordinate tmp = cl->getAt(i); - cl->setAt(cl->getAt(last - i), i); - cl->setAt(tmp, last - i); + switch(getCoordinateType()) { + case CoordinateType::XY: std::sort(items().begin(), items().end()); return; + case CoordinateType::XYZ: std::sort(items().begin(), items().end()); return; + case CoordinateType::XYZM: std::sort(items().begin(), items().end()); return; + case CoordinateType::XYM: std::sort(items().begin(), items().end()); return; } } @@ -187,23 +453,55 @@ bool CoordinateSequence::equals(const CoordinateSequence* cl1, const CoordinateSequence* cl2) { - // FIXME: use std::equals() - if(cl1 == cl2) { return true; } + if(cl1 == nullptr || cl2 == nullptr) { return false; } + std::size_t npts1 = cl1->getSize(); if(npts1 != cl2->getSize()) { return false; } for(std::size_t i = 0; i < npts1; i++) { - if(!(cl1->getAt(i) == cl2->getAt(i))) { + if(!(cl1->getAt(i) == cl2->getAt(i))) { + return false; + } + } + return true; +} + +bool +CoordinateSequence::equalsIdentical(const CoordinateSequence& other) const +{ + if (this == &other) { + return true; + } + + if (size() != other.size()) { + return false; + } + + if (hasZ() != other.hasZ()) { + return false; + } + + if (hasM() != other.hasM()) { + return false; + } + + assert(getCoordinateType() == other.getCoordinateType()); + + for (std::size_t i = 0; i < m_vect.size(); i++) { + const double& a = m_vect[i]; + const double& b = other.m_vect[i]; + if (a != b && !(std::isnan(a) && std::isnan(b))) { return false; } } + return true; } @@ -212,28 +510,137 @@ CoordinateSequence::expandEnvelope(Envelope& env) const { const std::size_t p_size = getSize(); for(std::size_t i = 0; i < p_size; i++) { - env.expandToInclude(getAt(i)); + env.expandToInclude(getAt(i)); } } Envelope CoordinateSequence::getEnvelope() const { - Envelope e; - expandEnvelope(e); - return e; + if (isEmpty()) { + return {}; + } + + double xmin = std::numeric_limits::infinity(); + double ymin = std::numeric_limits::infinity(); + double xmax = -std::numeric_limits::infinity(); + double ymax = -std::numeric_limits::infinity(); + + for (std::size_t i = 0; i < m_vect.size(); i += stride()) { + xmin = std::min(xmin, m_vect[i]); + xmax = std::max(xmax, m_vect[i]); + ymin = std::min(ymin, m_vect[i+1]); + ymax = std::max(ymax, m_vect[i+1]); + } + + return {xmin, xmax, ymin, ymax}; +} + + +void +CoordinateSequence::setOrdinate(std::size_t index, std::size_t ordinateIndex, double value) +{ + switch(ordinateIndex) { + case CoordinateSequence::X: + getAt(index).x = value; + break; + case CoordinateSequence::Y: + getAt(index).y = value; + break; + case CoordinateSequence::Z: + getAt(index).z = value; + break; + case CoordinateSequence::M: + { + if (getCoordinateType() == CoordinateType::XYZM) { + getAt(index).m = value; + } else { + getAt(index).m = value; + } + break; + } + default: { + std::stringstream ss; + ss << "Unknown ordinate index " << ordinateIndex; + throw util::IllegalArgumentException(ss.str()); + break; + } + } +} + +void +CoordinateSequence::setPoints(const std::vector& v) +{ + m_stride = 3; + m_hasdim = false; + m_hasz = false; + m_hasm = false; + + m_vect.resize(3 * v.size()); + const double* cbuf = reinterpret_cast(v.data()); + m_vect.assign(cbuf, cbuf + m_vect.size()); +} + +void +CoordinateSequence::toVector(std::vector& out) const +{ + if (getCoordinateType() == CoordinateType::XYZ) { + const Coordinate* cbuf = reinterpret_cast(m_vect.data()); + out.insert(out.end(), cbuf, cbuf + size()); + } else if (hasZ()) { + for (const auto& c : items()) { + out.emplace_back(c.x, c.y, c.z); + } + } else { + for (const auto& c : items()) { + out.emplace_back(c.x, c.y); + } + } +} + +void +CoordinateSequence::toVector(std::vector& out) const +{ + if (stride() == 2) { + const CoordinateXY* cbuf = reinterpret_cast(m_vect.data()); + out.insert(out.end(), cbuf, cbuf + size()); + } else { + for (const CoordinateXY& c : items()) { + out.emplace_back(c.x, c.y); + } + } +} + +void +CoordinateSequence::pop_back() +{ + switch (stride()) { + case 4: m_vect.pop_back(); // fall through + case 3: m_vect.pop_back(); // fall through + case 2: m_vect.pop_back(); + m_vect.pop_back(); + break; + default: + assert(0); + } } std::ostream& operator<< (std::ostream& os, const CoordinateSequence& cs) { os << "("; - for(std::size_t i = 0, n = cs.size(); i < n; ++i) { - const Coordinate& c = cs[i]; - if(i) { + + bool writeComma = false; + auto write = [&os, &writeComma](const auto& coord) { + if (writeComma) { os << ", "; + } else { + writeComma = true; } - os << c; - } + + os << coord; + }; + + cs.forEach(write); os << ")"; return os; diff --git a/Sources/geos/src/geom/Curve.cpp b/Sources/geos/src/geom/Curve.cpp new file mode 100644 index 0000000..91fd60d --- /dev/null +++ b/Sources/geos/src/geom/Curve.cpp @@ -0,0 +1,59 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2011 Sandro Santilli + * Copyright (C) 2005-2006 Refractions Research Inc. + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include + +namespace geos { +namespace geom { + +void +Curve::apply_ro(GeometryComponentFilter* filter) const +{ + assert(filter); + filter->filter_ro(this); +} + +void +Curve::apply_ro(GeometryFilter* filter) const +{ + assert(filter); + filter->filter_ro(this); +} + +void +Curve::apply_rw(GeometryComponentFilter* filter) +{ + assert(filter); + filter->filter_rw(this); +} + +void +Curve::apply_rw(GeometryFilter* filter) +{ + assert(filter); + filter->filter_rw(this); +} + +bool +Curve::isRing() const +{ + return isClosed() && isSimple(); +} + + +} +} diff --git a/Sources/geos/src/geom/CurvePolygon.cpp b/Sources/geos/src/geom/CurvePolygon.cpp new file mode 100644 index 0000000..c521360 --- /dev/null +++ b/Sources/geos/src/geom/CurvePolygon.cpp @@ -0,0 +1,92 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include +#include +#include + +namespace geos { +namespace geom { + + std::unique_ptr + CurvePolygon::getCoordinates() const + { + auto coordinates = shell->getCoordinates(); + for (const auto& hole : holes) { + if (auto simpleHole = dynamic_cast(hole.get())) { + coordinates->add(*simpleHole->getCoordinatesRO()); + } else { + coordinates->add(*hole->getCoordinates()); + } + } + return coordinates; + } + + std::string CurvePolygon::getGeometryType() const { + return "CurvePolygon"; + } + + GeometryTypeId CurvePolygon::getGeometryTypeId() const { + return GEOS_CURVEPOLYGON; + } + + std::unique_ptr + CurvePolygon::getBoundary() const { + throw util::UnsupportedOperationException(); + } + + void + CurvePolygon::normalize() { + throw util::UnsupportedOperationException(); + } + + double CurvePolygon::getArea() const { + double sum = algorithm::Area::ofClosedCurve(*shell); + for (const auto& hole : holes) { + sum -= algorithm::Area::ofClosedCurve(*hole); + } + return sum; + } + + bool CurvePolygon::hasCurvedComponents() const { + if (shell->hasCurvedComponents()) { + return true; + } + for (const auto& hole : holes) { + if (hole->hasCurvedComponents()) { + return true; + } + } + return false; + } + + Geometry* + CurvePolygon::cloneImpl() const { + return new CurvePolygon(*this); + } + + Geometry* + CurvePolygon::reverseImpl() const { + std::unique_ptr revShell(static_cast(shell->reverse().release())); + std::vector> revHoles(holes.size()); + for (std::size_t i = 0; i < revHoles.size(); i++) { + revHoles[i].reset(static_cast(holes[i]->reverse().release())); + } + return new CurvePolygon(std::move(revShell), std::move(revHoles), *getFactory()); + } + +} +} diff --git a/Sources/geos/src/geom/Envelope.cpp b/Sources/geos/src/geom/Envelope.cpp index 7871a2f..74899e7 100644 --- a/Sources/geos/src/geom/Envelope.cpp +++ b/Sources/geos/src/geom/Envelope.cpp @@ -38,8 +38,8 @@ namespace geom { // geos::geom /*public*/ bool -Envelope::intersects(const Coordinate& p1, const Coordinate& p2, - const Coordinate& q) +Envelope::intersects(const CoordinateXY& p1, const CoordinateXY& p2, + const CoordinateXY& q) { //OptimizeIt shows that Math#min and Math#max here are a bottleneck. //Replace with direct comparisons. [Jon Aquino] @@ -52,7 +52,7 @@ Envelope::intersects(const Coordinate& p1, const Coordinate& p2, /*public*/ bool -Envelope::intersects(const Coordinate& a, const Coordinate& b) const +Envelope::intersects(const CoordinateXY& a, const CoordinateXY& b) const { // These comparisons look redundant, but an alternative using // std::minmax performs no better and compiles down to more @@ -100,16 +100,6 @@ Envelope::Envelope(const std::string& str) strtod(values[3].c_str(), nullptr)); } -/*public*/ -bool -Envelope::covers(double x, double y) const -{ - return std::isgreaterequal(x, minx) && - std::islessequal(x, maxx) && - std::isgreaterequal(y, miny) && - std::islessequal(y, maxy); -} - /*public*/ bool Envelope::covers(const Envelope& other) const @@ -160,22 +150,6 @@ Envelope::toString() const return s.str(); } - -/*public*/ -size_t -Envelope::hashCode() const -{ - auto hash = std::hash{}; - - //Algorithm from Effective Java by Joshua Bloch [Jon Aquino] - std::size_t result = 17; - result = 37 * result + hash(minx); - result = 37 * result + hash(maxx); - result = 37 * result + hash(miny); - result = 37 * result + hash(maxy); - return result; -} - /*public static*/ std::vector Envelope::split(const std::string& str, const std::string& delimiters) @@ -202,7 +176,7 @@ Envelope::split(const std::string& str, const std::string& delimiters) /*public*/ bool -Envelope::centre(Coordinate& p_centre) const +Envelope::centre(CoordinateXY& p_centre) const { if(isNull()) { return false; diff --git a/Sources/geos/src/geom/Geometry.cpp b/Sources/geos/src/geom/Geometry.cpp index bc20fe0..4339335 100644 --- a/Sources/geos/src/geom/Geometry.cpp +++ b/Sources/geos/src/geom/Geometry.cpp @@ -39,18 +39,22 @@ #include #include #include +#include +#include #include #include #include #include #include +#include +#include #include -#include #include #include #include #include #include +#include #include #include #include @@ -70,14 +74,16 @@ #define SHORTCIRCUIT_PREDICATES 1 //#define USE_RECTANGLE_INTERSECTION 1 +#define USE_RELATENG 1 using namespace geos::algorithm; using namespace geos::operation::valid; using namespace geos::operation::relate; using namespace geos::operation::buffer; -using namespace geos::operation::overlay; using namespace geos::operation::distance; using namespace geos::operation; +using geos::operation::overlayng::OverlayNG; + namespace geos { namespace geom { // geos::geom @@ -106,7 +112,6 @@ Geometry::GeometryChangedFilter Geometry::geometryChangedFilter; Geometry::Geometry(const GeometryFactory* newFactory) : - envelope(nullptr), _factory(newFactory), _userData(nullptr) { @@ -123,13 +128,6 @@ Geometry::Geometry(const Geometry& geom) _factory(geom._factory), _userData(nullptr) { - if(geom.envelope.get()) { - envelope.reset(new Envelope(*(geom.envelope))); - } - //factory=geom.factory; - //envelope(new Envelope(*(geom.envelope.get()))); - //SRID=geom.getSRID(); - //_userData=NULL; _factory->addRef(); } @@ -166,9 +164,37 @@ Geometry::getCentroid() const return std::unique_ptr(getFactory()->createPoint(centPt)); } +/* public */ +bool +Geometry::isMixedDimension() const +{ + Dimension::DimensionType baseDim = Dimension::DONTCARE; + return isMixedDimension(&baseDim); +} + +/* public */ +bool +Geometry::isMixedDimension(Dimension::DimensionType* baseDim) const +{ + if (isCollection()) { + for (std::size_t i = 0; i < getNumGeometries(); i++) { + if (getGeometryN(i)->isMixedDimension(baseDim)) + return true; + } + return false; + } + else { + if (*baseDim == Dimension::DONTCARE) { + *baseDim = getDimension(); + return false; + } + return *baseDim != getDimension(); + } +} + /*public*/ bool -Geometry::getCentroid(Coordinate& ret) const +Geometry::getCentroid(CoordinateXY& ret) const { if(isEmpty()) { return false; @@ -183,6 +209,8 @@ Geometry::getCentroid(Coordinate& ret) const std::unique_ptr Geometry::getInteriorPoint() const { + geos::util::ensureNoCurvedComponents(this); + Coordinate interiorPt; int dim = getDimension(); if(dim == 0) { @@ -218,18 +246,6 @@ Geometry::geometryChanged() apply_rw(&geometryChangedFilter); } -/** - * Notifies this Geometry that its Coordinates have been changed by an external - * party. When geometryChanged is called, this method will be called for - * this Geometry and its component Geometries. - * @see apply(GeometryComponentFilter *) - */ -void -Geometry::geometryChangedAction() -{ - envelope.reset(nullptr); -} - bool Geometry::isValid() const { @@ -242,27 +258,14 @@ Geometry::getEnvelope() const return std::unique_ptr(getFactory()->toGeometry(getEnvelopeInternal())); } -const Envelope* -Geometry::getEnvelopeInternal() const -{ - if(!envelope.get()) { - envelope = computeEnvelopeInternal(); - } - return envelope.get(); -} - bool Geometry::disjoint(const Geometry* g) const { -#ifdef SHORTCIRCUIT_PREDICATES - // short-circuit test - if(! getEnvelopeInternal()->intersects(g->getEnvelopeInternal())) { - return true; - } +#if USE_RELATENG + return operation::relateng::RelateNG::disjoint(this, g); +#else + return !intersects(g); #endif - std::unique_ptr im(relate(g)); - bool res = im->isDisjoint(); - return res; } bool @@ -274,9 +277,14 @@ Geometry::touches(const Geometry* g) const return false; } #endif + +#if USE_RELATENG + return operation::relateng::RelateNG::touches(this, g); +#else std::unique_ptr im(relate(g)); bool res = im->isTouches(getDimension(), g->getDimension()); return res; +#endif } bool @@ -315,15 +323,35 @@ Geometry::intersects(const Geometry* g) const return predicate::RectangleIntersects::intersects(*p, *this); } - std::unique_ptr im(relate(g)); - bool res = im->isIntersects(); - return res; + auto typ = getGeometryTypeId(); + if (typ == GEOS_CURVEPOLYGON && g->getGeometryTypeId() == GEOS_POINT) { + auto loc = locate::SimplePointInAreaLocator::locatePointInSurface(*g->getCoordinate(), *detail::down_cast(this)); + return loc != Location::EXTERIOR; + } else if (typ == GEOS_POINT && g->getGeometryTypeId() == GEOS_CURVEPOLYGON) { + auto loc = locate::SimplePointInAreaLocator::locatePointInSurface(*getCoordinate(), *detail::down_cast(g)); + return loc != Location::EXTERIOR; + } + +#if USE_RELATENG + return operation::relateng::RelateNG::intersects(this, g); +#else + if (typ == GEOS_GEOMETRYCOLLECTION) { + auto im = relate(g); + bool res = im->isIntersects(); + return res; + } else { + return prep::PreparedGeometryFactory::prepare(this)->intersects(g); + } +#endif } /*public*/ bool Geometry::covers(const Geometry* g) const { +#if USE_RELATENG + return operation::relateng::RelateNG::covers(this, g); +#else // optimization - lower dimension cannot cover areas if(g->getDimension() == 2 && getDimension() < 2) { return false; @@ -351,12 +379,27 @@ Geometry::covers(const Geometry* g) const std::unique_ptr im(relate(g)); return im->isCovers(); +#endif } +/*public*/ +bool +Geometry::coveredBy(const Geometry* g) const +{ +#if USE_RELATENG + return operation::relateng::RelateNG::coveredBy(this, g); +#else + return covers(g, this); +#endif +} bool Geometry::crosses(const Geometry* g) const { +#if USE_RELATENG + return operation::relateng::RelateNG::crosses(this, g); +#else + #ifdef SHORTCIRCUIT_PREDICATES // short-circuit test if(! getEnvelopeInternal()->intersects(g->getEnvelopeInternal())) { @@ -366,17 +409,27 @@ Geometry::crosses(const Geometry* g) const std::unique_ptr im(relate(g)); bool res = im->isCrosses(getDimension(), g->getDimension()); return res; + +#endif } bool Geometry::within(const Geometry* g) const { +#if USE_RELATENG + return operation::relateng::RelateNG::within(this, g); +#else return g->contains(this); +#endif } bool Geometry::contains(const Geometry* g) const { +#if USE_RELATENG + return operation::relateng::RelateNG::contains(this, g); +#else + // optimization - lower dimension cannot contain areas if(g->getDimension() == 2 && getDimension() < 2) { return false; @@ -409,11 +462,16 @@ Geometry::contains(const Geometry* g) const std::unique_ptr im(relate(g)); bool res = im->isContains(); return res; +#endif } bool Geometry::overlaps(const Geometry* g) const { +#if USE_RELATENG + return operation::relateng::RelateNG::overlaps(this, g); +#else + #ifdef SHORTCIRCUIT_PREDICATES // short-circuit test if(! getEnvelopeInternal()->intersects(g->getEnvelopeInternal())) { @@ -423,19 +481,29 @@ Geometry::overlaps(const Geometry* g) const std::unique_ptr im(relate(g)); bool res = im->isOverlaps(getDimension(), g->getDimension()); return res; + +#endif } bool Geometry::relate(const Geometry* g, const std::string& intersectionPattern) const { +#if USE_RELATENG + return operation::relateng::RelateNG::relate(this, g, intersectionPattern); +#else std::unique_ptr im(relate(g)); bool res = im->matches(intersectionPattern); return res; +#endif } bool Geometry::equals(const Geometry* g) const { +#if USE_RELATENG + return operation::relateng::RelateNG::equalsTopo(this, g); +#else + #ifdef SHORTCIRCUIT_PREDICATES // short-circuit test if(! getEnvelopeInternal()->equals(g->getEnvelopeInternal())) { @@ -453,12 +521,17 @@ Geometry::equals(const Geometry* g) const std::unique_ptr im(relate(g)); bool res = im->isEquals(getDimension(), g->getDimension()); return res; +#endif } std::unique_ptr Geometry::relate(const Geometry* other) const { +#if USE_RELATENG + return operation::relateng::RelateNG::relate(this, other); +#else return RelateOp::relate(this, other); +#endif } std::unique_ptr @@ -485,6 +558,9 @@ std::string Geometry::toText() const { io::WKTWriter writer; + // To enable "GEOS<3.12" outputs, uncomment the following: + // writer.setTrim(false); + // writer.setOutputDimension(2); return writer.write(this); } @@ -519,11 +595,6 @@ Geometry::intersection(const Geometry* other) const * TODO: MD - add optimization for P-A case using Point-In-Polygon */ - // special case: if one input is empty ==> empty - if(isEmpty() || other->isEmpty()) { - return OverlayOp::createEmptyResult(OverlayOp::opINTERSECTION, this, other, getFactory()); - } - #ifdef USE_RECTANGLE_INTERSECTION // optimization for rectangle arguments using operation::intersection::Rectangle; @@ -542,22 +613,12 @@ Geometry::intersection(const Geometry* other) const } #endif - return HeuristicOverlay(this, other, OverlayOp::opINTERSECTION); + return HeuristicOverlay(this, other, OverlayNG::INTERSECTION); } std::unique_ptr Geometry::Union(const Geometry* other) const { - // handle empty geometry cases - if(isEmpty() || other->isEmpty() ) { - if(isEmpty() && other->isEmpty() ) { - return OverlayOp::createEmptyResult(OverlayOp::opUNION, this, other, getFactory()); - } - // special case: if one input is empty ==> other input - if(isEmpty()) return other->clone(); - if(other->isEmpty()) return clone(); - } - #ifdef SHORTCIRCUIT_PREDICATES // if envelopes are disjoint return a MULTI geom or // a geometrycollection @@ -569,34 +630,33 @@ Geometry::Union(const Geometry* other) const std::size_t ngeomsOther = other->getNumGeometries(); // Allocated for ownership transfer - std::vector* v = new std::vector(); - v->reserve(ngeomsThis + ngeomsOther); + std::vector> v; + v.reserve(ngeomsThis + ngeomsOther); if(nullptr != (coll = dynamic_cast(this))) { for(std::size_t i = 0; i < ngeomsThis; ++i) { - v->push_back(coll->getGeometryN(i)->clone().release()); + v.push_back(coll->getGeometryN(i)->clone()); } } else { - v->push_back(this->clone().release()); + v.push_back(this->clone()); } if(nullptr != (coll = dynamic_cast(other))) { for(std::size_t i = 0; i < ngeomsOther; ++i) { - v->push_back(coll->getGeometryN(i)->clone().release()); + v.push_back(coll->getGeometryN(i)->clone()); } } else { - v->push_back(other->clone().release()); + v.push_back(other->clone()); } - std::unique_ptrout(_factory->buildGeometry(v)); - return out; + return _factory->buildGeometry(std::move(v)); } #endif - return HeuristicOverlay(this, other, OverlayOp::opUNION); + return HeuristicOverlay(this, other, OverlayNG::UNION); } /* public */ @@ -604,77 +664,53 @@ Geometry::Ptr Geometry::Union() const { using geos::operation::geounion::UnaryUnionOp; -#ifdef DISABLE_OVERLAYNG return UnaryUnionOp::Union(*this); -#else - return operation::overlayng::OverlayNGRobust::Union(this); -#endif } std::unique_ptr Geometry::difference(const Geometry* other) const -//throw(IllegalArgumentException *) { - // special case: if A.isEmpty ==> empty; if B.isEmpty ==> A - if(isEmpty()) { - return OverlayOp::createEmptyResult(OverlayOp::opDIFFERENCE, this, other, getFactory()); - } - if(other->isEmpty()) { - return clone(); - } - - return HeuristicOverlay(this, other, OverlayOp::opDIFFERENCE); + return HeuristicOverlay(this, other, OverlayNG::DIFFERENCE); } std::unique_ptr Geometry::symDifference(const Geometry* other) const { - // handle empty geometry cases - if(isEmpty() || other->isEmpty() ) { - if(isEmpty() && other->isEmpty() ) { - return OverlayOp::createEmptyResult(OverlayOp::opSYMDIFFERENCE, this, other, getFactory()); - } - // special case: if either input is empty ==> other input - if(isEmpty()) return other->clone(); - if(other->isEmpty()) return clone(); - } - // if envelopes are disjoint return a MULTI geom or // a geometrycollection - if(! getEnvelopeInternal()->intersects(other->getEnvelopeInternal())) { + if(! getEnvelopeInternal()->intersects(other->getEnvelopeInternal()) && !(isEmpty() && other->isEmpty())) { const GeometryCollection* coll; std::size_t ngeomsThis = getNumGeometries(); std::size_t ngeomsOther = other->getNumGeometries(); // Allocated for ownership transfer - std::vector* v = new std::vector(); - v->reserve(ngeomsThis + ngeomsOther); + std::vector> v; + v.reserve(ngeomsThis + ngeomsOther); if(nullptr != (coll = dynamic_cast(this))) { for(std::size_t i = 0; i < ngeomsThis; ++i) { - v->push_back(coll->getGeometryN(i)->clone().release()); + v.push_back(coll->getGeometryN(i)->clone()); } } else { - v->push_back(this->clone().release()); + v.push_back(this->clone()); } if(nullptr != (coll = dynamic_cast(other))) { for(std::size_t i = 0; i < ngeomsOther; ++i) { - v->push_back(coll->getGeometryN(i)->clone().release()); + v.push_back(coll->getGeometryN(i)->clone()); } } else { - v->push_back(other->clone().release()); + v.push_back(other->clone()); } - return std::unique_ptr(_factory->buildGeometry(v)); + return _factory->buildGeometry(std::move(v)); } - return HeuristicOverlay(this, other, OverlayOp::opSYMDIFFERENCE); - + return HeuristicOverlay(this, other, OverlayNG::SYMDIFFERENCE); } int @@ -729,78 +765,6 @@ Geometry::GeometryChangedFilter::filter_rw(Geometry* geom) geom->geometryChangedAction(); } -int -Geometry::compare(std::vector a, std::vector b) const -{ - std::size_t i = 0; - std::size_t j = 0; - while(i < a.size() && j < b.size()) { - Coordinate& aCoord = a[i]; - Coordinate& bCoord = b[j]; - int comparison = aCoord.compareTo(bCoord); - if(comparison != 0) { - return comparison; - } - i++; - j++; - } - if(i < a.size()) { - return 1; - } - if(j < b.size()) { - return -1; - } - return 0; -} - -int -Geometry::compare(std::vector a, std::vector b) const -{ - std::size_t i = 0; - std::size_t j = 0; - while(i < a.size() && j < b.size()) { - Geometry* aGeom = a[i]; - Geometry* bGeom = b[j]; - int comparison = aGeom->compareTo(bGeom); - if(comparison != 0) { - return comparison; - } - i++; - j++; - } - if(i < a.size()) { - return 1; - } - if(j < b.size()) { - return -1; - } - return 0; -} - -int -Geometry::compare(const std::vector> & a, - const std::vector> & b) const -{ - std::size_t i = 0; - std::size_t j = 0; - while(i < a.size() && j < b.size()) { - Geometry* aGeom = a[i].get(); - Geometry* bGeom = b[j].get(); - int comparison = aGeom->compareTo(bGeom); - if(comparison != 0) { - return comparison; - } - i++; - j++; - } - if(i < a.size()) { - return 1; - } - if(j < b.size()) { - return -1; - } - return 0; -} /** * Returns the minimum distance between this Geometry @@ -860,7 +824,7 @@ GeometryGreaterThen::operator()(const Geometry* first, const Geometry* second) } bool -Geometry::equal(const Coordinate& a, const Coordinate& b, +Geometry::equal(const CoordinateXY& a, const CoordinateXY& b, double tolerance) const { if(tolerance == 0) { @@ -908,6 +872,11 @@ Geometry::getPrecisionModel() const return _factory->getPrecisionModel(); } +bool +Geometry::hasCurvedComponents() const { + return false; +} + } // namespace geos::geom } // namespace geos diff --git a/Sources/geos/src/geom/GeometryCollection.cpp b/Sources/geos/src/geom/GeometryCollection.cpp index c278eea..80ecbcc 100644 --- a/Sources/geos/src/geom/GeometryCollection.cpp +++ b/Sources/geos/src/geom/GeometryCollection.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -39,35 +38,31 @@ namespace geom { // geos::geom GeometryCollection::GeometryCollection(const GeometryCollection& gc) : Geometry(gc), - geometries(gc.geometries.size()) + geometries(gc.geometries.size()), + envelope(gc.envelope) { for(std::size_t i = 0; i < geometries.size(); ++i) { geometries[i] = gc.geometries[i]->clone(); } } -/*protected*/ -GeometryCollection::GeometryCollection(std::vector* newGeoms, const GeometryFactory* factory): - Geometry(factory) +GeometryCollection& +GeometryCollection::operator=(const GeometryCollection& gc) { - if(newGeoms == nullptr) { - return; - } - if(hasNullElements(newGeoms)) { - throw util::IllegalArgumentException("geometries must not contain null elements\n"); - } - for (const auto& geom : *newGeoms) { - geometries.emplace_back(geom); + geometries.resize(gc.geometries.size()); + envelope = gc.envelope; + + for (std::size_t i = 0; i < geometries.size(); i++) { + geometries[i] = gc.geometries[i]->clone(); } - delete newGeoms; - // Set SRID for inner geoms - setSRID(getSRID()); + return *this; } GeometryCollection::GeometryCollection(std::vector> && newGeoms, const GeometryFactory& factory) : Geometry(&factory), - geometries(std::move(newGeoms)) { + geometries(std::move(newGeoms)), + envelope(computeEnvelopeInternal()) { if (hasNullElements(&geometries)) { throw util::IllegalArgumentException("geometries must not contain null elements\n"); @@ -94,18 +89,18 @@ GeometryCollection::setSRID(int newSRID) std::unique_ptr GeometryCollection::getCoordinates() const { - std::vector coordinates(getNumPoints()); + auto coordinates = detail::make_unique(getNumPoints()); std::size_t k = 0; for(const auto& g : geometries) { auto childCoordinates = g->getCoordinates(); // TODO avoid this copy where getCoordinateRO() exists std::size_t npts = childCoordinates->getSize(); for(std::size_t j = 0; j < npts; ++j) { - coordinates[k] = childCoordinates->getAt(j); + coordinates->setAt(childCoordinates->getAt(j), k); k++; } } - return CoordinateArraySequenceFactory::instance()->create(std::move(coordinates)); + return coordinates; } bool @@ -137,6 +132,15 @@ GeometryCollection::isDimensionStrict(Dimension::DimensionType d) const { }); } +bool +GeometryCollection::hasDimension(Dimension::DimensionType d) const { + return std::any_of(geometries.begin(), + geometries.end(), + [&d](const std::unique_ptr& g) { + return g->hasDimension(d); + }); +} + int GeometryCollection::getBoundaryDimension() const { @@ -158,6 +162,28 @@ GeometryCollection::getCoordinateDimension() const return dimension; } +bool +GeometryCollection::hasM() const +{ + for (const auto& g : geometries) { + if (g->hasM()) { + return true; + } + } + return false; +} + +bool +GeometryCollection::hasZ() const +{ + for (const auto& g : geometries) { + if (g->hasZ()) { + return true; + } + } + return false; +} + size_t GeometryCollection::getNumGeometries() const { @@ -219,6 +245,31 @@ GeometryCollection::equalsExact(const Geometry* other, double tolerance) const return true; } +bool +GeometryCollection::equalsIdentical(const Geometry* other_g) const +{ + if(!isEquivalentClass(other_g)) { + return false; + } + + const auto& other = static_cast(*other_g); + if(getNumGeometries() != other.getNumGeometries()) { + return false; + } + + if (envelope != other.envelope) { + return false; + } + + for(std::size_t i = 0; i < getNumGeometries(); i++) { + if(!(getGeometryN(i)->equalsIdentical(other.getGeometryN(i)))) { + return false; + } + } + + return true; +} + void GeometryCollection::apply_rw(const CoordinateFilter* filter) { @@ -264,13 +315,13 @@ GeometryCollection::normalize() }); } -Envelope::Ptr +Envelope GeometryCollection::computeEnvelopeInternal() const { - Envelope::Ptr p_envelope(new Envelope()); + Envelope p_envelope; for(const auto& g : geometries) { const Envelope* env = g->getEnvelopeInternal(); - p_envelope->expandToInclude(env); + p_envelope.expandToInclude(env); } return p_envelope; } @@ -282,7 +333,16 @@ GeometryCollection::compareToSameClass(const Geometry* g) const return compare(geometries, gc->geometries); } -const Coordinate* +bool GeometryCollection::hasCurvedComponents() const { + for (const auto& g : geometries) { + if (g->hasCurvedComponents()) { + return true; + } + } + return false; +} + +const CoordinateXY* GeometryCollection::getCoordinate() const { for(const auto& g : geometries) { @@ -396,5 +456,7 @@ GeometryCollection::reverseImpl() const return getFactory()->createGeometryCollection(std::move(reversed)).release(); } + + } // namespace geos::geom } // namespace geos diff --git a/Sources/geos/src/geom/GeometryFactory.cpp b/Sources/geos/src/geom/GeometryFactory.cpp index 4012ab8..4e97f85 100644 --- a/Sources/geos/src/geom/GeometryFactory.cpp +++ b/Sources/geos/src/geom/GeometryFactory.cpp @@ -20,15 +20,19 @@ #include #include -#include +#include +#include +#include #include #include #include #include #include +#include #include #include #include +#include #include #include #include @@ -57,16 +61,12 @@ namespace { class gfCoordinateOperation: public util::CoordinateOperation { using CoordinateOperation::edit; - const CoordinateSequenceFactory* _gsf; public: - gfCoordinateOperation(const CoordinateSequenceFactory* gsf) - : _gsf(gsf) - {} std::unique_ptr edit(const CoordinateSequence* coordSeq, const Geometry*) override { - return _gsf->create(*coordSeq); + return detail::make_unique(*coordSeq); } }; @@ -77,8 +77,7 @@ class gfCoordinateOperation: public util::CoordinateOperation { /*protected*/ GeometryFactory::GeometryFactory() : - SRID(0), - coordinateListFactory(DefaultCoordinateSequenceFactory::instance()) + SRID(0) , _refCount(0), _autoDestroy(false) { #if GEOS_DEBUG @@ -94,73 +93,10 @@ GeometryFactory::create() return GeometryFactory::Ptr(new GeometryFactory()); } -/*protected*/ -GeometryFactory::GeometryFactory(const PrecisionModel* pm, int newSRID, - CoordinateSequenceFactory* nCoordinateSequenceFactory) - : - SRID(newSRID) - , _refCount(0), _autoDestroy(false) -{ -#if GEOS_DEBUG - std::cerr << "GEOS_DEBUG: GeometryFactory[" << this << "]::GeometryFactory(PrecisionModel[" << pm << "], SRID)" << - std::endl; -#endif - if(pm) { - precisionModel = *pm; - } - - if(! nCoordinateSequenceFactory) { - coordinateListFactory = DefaultCoordinateSequenceFactory::instance(); - } - else { - coordinateListFactory = nCoordinateSequenceFactory; - } -} - -/*public static*/ -GeometryFactory::Ptr -GeometryFactory::create(const PrecisionModel* pm, int newSRID, - CoordinateSequenceFactory* nCoordinateSequenceFactory) -{ - return GeometryFactory::Ptr( - new GeometryFactory(pm, newSRID, nCoordinateSequenceFactory) - ); -} - -/*protected*/ -GeometryFactory::GeometryFactory( - CoordinateSequenceFactory* nCoordinateSequenceFactory) - : - SRID(0) - , _refCount(0), _autoDestroy(false) -{ -#if GEOS_DEBUG - std::cerr << "GEOS_DEBUG: GeometryFactory[" << this << "]::GeometryFactory(CoordinateSequenceFactory[" << - nCoordinateSequenceFactory << "])" << std::endl; -#endif - if(! nCoordinateSequenceFactory) { - coordinateListFactory = DefaultCoordinateSequenceFactory::instance(); - } - else { - coordinateListFactory = nCoordinateSequenceFactory; - } -} - -/*public static*/ -GeometryFactory::Ptr -GeometryFactory::create( - CoordinateSequenceFactory* nCoordinateSequenceFactory) -{ - return GeometryFactory::Ptr( - new GeometryFactory(nCoordinateSequenceFactory) - ); -} - /*protected*/ GeometryFactory::GeometryFactory(const PrecisionModel* pm) : - SRID(0), - coordinateListFactory(DefaultCoordinateSequenceFactory::instance()) + SRID(0) , _refCount(0), _autoDestroy(false) { #if GEOS_DEBUG @@ -183,7 +119,6 @@ GeometryFactory::create(const PrecisionModel* pm) /*protected*/ GeometryFactory::GeometryFactory(const PrecisionModel* pm, int newSRID) : SRID(newSRID) - , coordinateListFactory(DefaultCoordinateSequenceFactory::instance()) , _refCount(0) , _autoDestroy(false) { @@ -209,7 +144,6 @@ GeometryFactory::create(const PrecisionModel* pm, int newSRID) GeometryFactory::GeometryFactory(const GeometryFactory& gf) : precisionModel(gf.precisionModel) , SRID(gf.SRID) - , coordinateListFactory(gf.coordinateListFactory) , _refCount(0) , _autoDestroy(false) {} @@ -232,9 +166,9 @@ GeometryFactory::~GeometryFactory() } /*public*/ -Point* +std::unique_ptr GeometryFactory::createPointFromInternalCoord(const Coordinate* coord, - const Geometry* exemplar) const + const Geometry* exemplar) { assert(coord); Coordinate newcoord = *coord; @@ -247,7 +181,7 @@ GeometryFactory::createPointFromInternalCoord(const Coordinate* coord, std::unique_ptr GeometryFactory::toGeometry(const Envelope* envelope) const { - Coordinate coord; + CoordinateXY coord; if(envelope->isNull()) { return createPoint(); @@ -258,7 +192,7 @@ GeometryFactory::toGeometry(const Envelope* envelope) const return std::unique_ptr(createPoint(coord)); } - auto cl = coordinateListFactory->create(5, 2); + auto cl = detail::make_unique(5u, false, false, false); coord.x = envelope->getMinX(); coord.y = envelope->getMinY(); @@ -287,58 +221,71 @@ GeometryFactory::toGeometry(const Envelope* envelope) const std::unique_ptr GeometryFactory::createPoint(std::size_t coordinateDimension) const { - if (coordinateDimension == 3) { - geos::geom::FixedSizeCoordinateSequence<0> seq(coordinateDimension); - return std::unique_ptr(createPoint(seq)); - } - return std::unique_ptr(new Point(nullptr, this)); + CoordinateSequence seq(0u, coordinateDimension); + return std::unique_ptr(new Point(std::move(seq), this)); } /*public*/ -Point* -GeometryFactory::createPoint(const Coordinate& coordinate) const +std::unique_ptr +GeometryFactory::createPoint(bool hasZ, bool hasM) const { - if(coordinate.isNull()) { - return createPoint().release(); - } - else { - return new Point(coordinate, this); - } + CoordinateSequence seq(0u, hasZ, hasM); + return std::unique_ptr(new Point(std::move(seq), this)); } /*public*/ -Point* -GeometryFactory::createPoint(CoordinateSequence* newCoords) const +std::unique_ptr +GeometryFactory::createPoint(std::unique_ptr&& coords) const { - return new Point(newCoords, this); + if (!coords) { + return createPoint(); + } + return std::unique_ptr(new Point(std::move(*coords), this)); +} + +std::unique_ptr +GeometryFactory::createPoint(const CoordinateXY& coordinate) const +{ + return std::unique_ptr(new Point(coordinate, this)); } /*public*/ -Point* -GeometryFactory::createPoint(const CoordinateSequence& fromCoords) const +std::unique_ptr +GeometryFactory::createPoint(const Coordinate& coordinate) const +{ + return std::unique_ptr(new Point(coordinate, this)); +} + +std::unique_ptr +GeometryFactory::createPoint(const CoordinateXYM& coordinate) const { - auto newCoords = fromCoords.clone(); - return new Point(newCoords.release(), this); + return std::unique_ptr(new Point(coordinate, this)); +} +std::unique_ptr +GeometryFactory::createPoint(const CoordinateXYZM& coordinate) const +{ + return std::unique_ptr(new Point(coordinate, this)); } /*public*/ -std::unique_ptr -GeometryFactory::createMultiLineString() const +std::unique_ptr +GeometryFactory::createPoint(const CoordinateSequence& fromCoords) const { - return std::unique_ptr(new MultiLineString(nullptr, this)); + CoordinateSequence newCoords(fromCoords); + return std::unique_ptr(new Point(std::move(newCoords), this)); + } /*public*/ -MultiLineString* -GeometryFactory::createMultiLineString(std::vector* newLines) -const +std::unique_ptr +GeometryFactory::createMultiLineString() const { - return new MultiLineString(newLines, this); + return createMultiLineString(std::vector>()); } /*public*/ -MultiLineString* +std::unique_ptr GeometryFactory::createMultiLineString(const std::vector& fromLines) const { @@ -354,7 +301,7 @@ const newGeoms[i].reset(new LineString(*line)); } - return new MultiLineString(std::move(newGeoms), *this); + return createMultiLineString(std::move(newGeoms)); } std::unique_ptr @@ -369,29 +316,56 @@ GeometryFactory::createMultiLineString(std::vector> && return std::unique_ptr(new MultiLineString(std::move(fromLines), *this)); } +std::unique_ptr +GeometryFactory::createMultiCurve() const { + return createMultiCurve(std::vector>()); +} + +std::unique_ptr +GeometryFactory::createMultiCurve(std::vector> && fromCurves) const { + // Can't use make_unique because constructor is protected + return std::unique_ptr(new MultiCurve(std::move(fromCurves), *this)); +} + +std::unique_ptr +GeometryFactory::createMultiCurve(std::vector> && fromCurves) const { + // Can't use make_unique because constructor is protected + return std::unique_ptr(new MultiCurve(std::move(fromCurves), *this)); +} + /*public*/ std::unique_ptr GeometryFactory::createGeometryCollection() const { - return std::unique_ptr(new GeometryCollection(nullptr, this)); + return createGeometryCollection(std::vector>()); } /*public*/ std::unique_ptr -GeometryFactory::createEmptyGeometry() const -{ - return createGeometryCollection(); -} +GeometryFactory::createEmptyGeometry(GeometryTypeId type, bool hasZ, bool hasM) const +{ + switch (type) { + case GEOS_POINT: return createPoint(hasZ, hasM); + case GEOS_LINESTRING: return createLineString(hasZ, hasM); + case GEOS_LINEARRING: return createLinearRing(hasZ, hasM); + case GEOS_POLYGON: return createPolygon(hasZ, hasM); + case GEOS_MULTIPOINT: return createMultiPoint(); + case GEOS_MULTILINESTRING: return createMultiLineString(); + case GEOS_MULTIPOLYGON: return createMultiPolygon(); + case GEOS_GEOMETRYCOLLECTION: return createGeometryCollection(); + case GEOS_CIRCULARSTRING: return createCircularString(hasZ, hasM); + case GEOS_COMPOUNDCURVE: return createCompoundCurve(); + case GEOS_CURVEPOLYGON: return createCurvePolygon(hasZ, hasM); + case GEOS_MULTICURVE: return createMultiCurve(); + case GEOS_MULTISURFACE: return createMultiSurface(); + default: + throw geos::util::IllegalArgumentException("Unexpected GeometryTypeId"); -/*public*/ -GeometryCollection* -GeometryFactory::createGeometryCollection(std::vector* newGeoms) const -{ - return new GeometryCollection(newGeoms, this); + } } /*public*/ -GeometryCollection* +std::unique_ptr GeometryFactory::createGeometryCollection(const std::vector& fromGeoms) const { std::vector> newGeoms(fromGeoms.size()); @@ -400,23 +374,17 @@ GeometryFactory::createGeometryCollection(const std::vector& fr newGeoms[i] = fromGeoms[i]->clone(); } - return new GeometryCollection(std::move(newGeoms), *this); + return createGeometryCollection(std::move(newGeoms)); } /*public*/ std::unique_ptr GeometryFactory::createMultiPolygon() const { - return std::unique_ptr(new MultiPolygon(nullptr, this)); + return createMultiPolygon(std::vector>()); } /*public*/ -MultiPolygon* -GeometryFactory::createMultiPolygon(std::vector* newPolys) const -{ - return new MultiPolygon(newPolys, this); -} - std::unique_ptr GeometryFactory::createMultiPolygon(std::vector> && newPolys) const { @@ -432,7 +400,7 @@ GeometryFactory::createMultiPolygon(std::vector> && ne } /*public*/ -MultiPolygon* +std::unique_ptr GeometryFactory::createMultiPolygon(const std::vector& fromPolys) const { std::vector> newGeoms(fromPolys.size()); @@ -441,68 +409,63 @@ GeometryFactory::createMultiPolygon(const std::vector& fromPoly newGeoms[i] = fromPolys[i]->clone(); } - return new MultiPolygon(std::move(newGeoms), *this); + return createMultiPolygon(std::move(newGeoms)); } -/*public*/ -std::unique_ptr -GeometryFactory::createLinearRing() const +std::unique_ptr +GeometryFactory::createMultiSurface() const { - return std::unique_ptr(new LinearRing(nullptr, this)); + // Can't use make_unique because constructor is protected + return std::unique_ptr(new MultiSurface(std::vector>(), *this)); } -/*public*/ -LinearRing* -GeometryFactory::createLinearRing(CoordinateSequence* newCoords) const +std::unique_ptr +GeometryFactory::createMultiSurface(std::vector> && newSurfaces) const { - return new LinearRing(newCoords, this); + // Can't use make_unique because constructor is protected + return std::unique_ptr(new MultiSurface(std::move(newSurfaces), *this)); } -std::unique_ptr -GeometryFactory::createLinearRing(CoordinateSequence::Ptr && newCoords) const +std::unique_ptr +GeometryFactory::createMultiSurface(std::vector> && newSurfaces) const { - // Can't use make_unique with protected constructor - return std::unique_ptr(new LinearRing(std::move(newCoords), *this)); + // Can't use make_unique because constructor is protected + return std::unique_ptr(new MultiSurface(std::move(newSurfaces), *this)); } /*public*/ std::unique_ptr -GeometryFactory::createLinearRing(std::vector && newCoords) -const +GeometryFactory::createLinearRing(std::size_t coordinateDimension) const { // Can't use make_unique with protected constructor - return std::unique_ptr(new LinearRing(std::move(newCoords), *this)); + auto cs = detail::make_unique(0u, coordinateDimension); + return std::unique_ptr(new LinearRing(std::move(cs), *this)); } /*public*/ -LinearRing* -GeometryFactory::createLinearRing(const CoordinateSequence& fromCoords) const +std::unique_ptr +GeometryFactory::createLinearRing(bool hasZ, bool hasM) const { - auto newCoords = fromCoords.clone(); - LinearRing* g = nullptr; - // construction failure will delete newCoords - g = new LinearRing(newCoords.release(), this); - return g; + // Can't use make_unique with protected constructor + auto cs = detail::make_unique(0u, hasZ, hasM); + return std::unique_ptr(new LinearRing(std::move(cs), *this)); } -/*public*/ -MultiPoint* -GeometryFactory::createMultiPoint(std::vector* newPoints) const +std::unique_ptr +GeometryFactory::createLinearRing(CoordinateSequence::Ptr && newCoords) const { - return new MultiPoint(newPoints, this); + // Can't use make_unique with protected constructor + return std::unique_ptr(new LinearRing(std::move(newCoords), *this)); } -std::unique_ptr -GeometryFactory::createMultiPoint(std::vector && newPoints) const { - std::vector> pts(newPoints.size()); - - for(std::size_t i = 0; i < newPoints.size(); ++i) { - pts[i].reset(createPoint(newPoints[i])); - } - - return std::unique_ptr(new MultiPoint(std::move(pts), *this)); +/*public*/ +std::unique_ptr +GeometryFactory::createLinearRing(const CoordinateSequence& fromCoords) const +{ + return createLinearRing(fromCoords.clone()); } +/*public*/ std::unique_ptr GeometryFactory::createMultiPoint(std::vector> && newPoints) const { @@ -516,7 +479,7 @@ GeometryFactory::createMultiPoint(std::vector> && newP } /*public*/ -MultiPoint* +std::unique_ptr GeometryFactory::createMultiPoint(const std::vector& fromPoints) const { std::vector> newGeoms(fromPoints.size()); @@ -524,59 +487,47 @@ GeometryFactory::createMultiPoint(const std::vector& fromPoints newGeoms[i] = fromPoints[i]->clone(); } - return new MultiPoint(std::move(newGeoms), *this); + return createMultiPoint(std::move(newGeoms)); } /*public*/ std::unique_ptr GeometryFactory::createMultiPoint() const { - return std::unique_ptr(new MultiPoint(nullptr, this)); + return std::unique_ptr(new MultiPoint(std::vector>(), *this)); } /*public*/ -MultiPoint* +std::unique_ptr GeometryFactory::createMultiPoint(const CoordinateSequence& fromCoords) const { std::size_t npts = fromCoords.getSize(); - std::vector> pts(npts); + std::vector> pts; + pts.reserve(npts); - for(std::size_t i = 0; i < npts; ++i) { - pts[i].reset(createPoint(fromCoords.getAt(i))); - } + fromCoords.forEach([&pts, this](const auto& coord) -> void { + pts.push_back(this->createPoint(coord)); + }); - return new MultiPoint(std::move(pts), *this); -} - -/*public*/ -MultiPoint* -GeometryFactory::createMultiPoint(const std::vector& fromCoords) const -{ - std::size_t npts = fromCoords.size(); - std::vector> pts(npts); - - for(std::size_t i = 0; i < npts; ++i) { - pts[i].reset(createPoint(fromCoords[i])); - } - - return new MultiPoint(std::move(pts), *this); + return createMultiPoint(std::move(pts)); } /*public*/ std::unique_ptr GeometryFactory::createPolygon(std::size_t coordinateDimension) const { - auto cs = coordinateListFactory->create(std::size_t(0), coordinateDimension); - auto lr = createLinearRing(cs.release()); - return std::unique_ptr(createPolygon(lr, nullptr)); + auto cs = detail::make_unique(0u, coordinateDimension); + auto lr = createLinearRing(std::move(cs)); + return createPolygon(std::move(lr)); } /*public*/ -Polygon* -GeometryFactory::createPolygon(LinearRing* shell, std::vector* holes) -const +std::unique_ptr +GeometryFactory::createPolygon(bool hasZ, bool hasM) const { - return new Polygon(shell, holes, this); + auto cs = detail::make_unique(0u, hasZ, hasM); + auto lr = createLinearRing(std::move(cs)); + return createPolygon(std::move(lr)); } std::unique_ptr @@ -589,11 +540,10 @@ const /*public*/ std::unique_ptr -GeometryFactory::createPolygon(std::vector && coords) +GeometryFactory::createPolygon(CoordinateSequence && coords) const { - const geom::CoordinateSequenceFactory* csf = getCoordinateSequenceFactory(); - std::unique_ptr cs = csf->create(std::move(coords)); + auto cs = detail::make_unique(std::move(coords)); std::unique_ptr lr = createLinearRing(std::move(cs)); std::unique_ptr ply = createPolygon(std::move(lr)); return ply; @@ -624,14 +574,55 @@ const return new Polygon(std::move(newRing), std::move(newHoles), *this); } +/* public */ +std::unique_ptr +GeometryFactory::createCurvePolygon(bool hasZ, bool hasM) +const +{ + // Can't use make_unique with protected constructor + return std::unique_ptr(new CurvePolygon(createLinearRing(hasZ, hasM), *this)); +} + +/* public */ +std::unique_ptr +GeometryFactory::createCurvePolygon(std::unique_ptr && shell) +const +{ + // Can't use make_unique with protected constructor + return std::unique_ptr(new CurvePolygon(std::move(shell), *this)); +} + +/* public */ +std::unique_ptr +GeometryFactory::createCurvePolygon(std::unique_ptr && shell, std::vector> && holes) +const +{ + // Can't use make_unique with protected constructor + return std::unique_ptr(new CurvePolygon(std::move(shell), std::move(holes), *this)); +} + /*public*/ std::unique_ptr GeometryFactory::createLineString(std::size_t coordinateDimension) const { - if (coordinateDimension == 3) { - return createLineString(coordinateListFactory->create(std::size_t(0), coordinateDimension)); - } - return std::unique_ptr(new LineString(nullptr, this)); + auto cs = detail::make_unique(0u, coordinateDimension); + return createLineString(std::move(cs)); +} + +/*public*/ +std::unique_ptr +GeometryFactory::createLineString(bool hasZ, bool hasM) const +{ + auto cs = detail::make_unique(0u, hasZ, hasM); + return createLineString(std::move(cs)); +} + +/*public*/ +std::unique_ptr +GeometryFactory::createCircularString(bool hasZ, bool hasM) const +{ + auto cs = detail::make_unique(0u, hasZ, hasM); + return createCircularString(std::move(cs)); } /*public*/ @@ -643,11 +634,11 @@ GeometryFactory::createLineString(const LineString& ls) const } /*public*/ -LineString* -GeometryFactory::createLineString(CoordinateSequence* newCoords) -const +std::unique_ptr +GeometryFactory::createCircularString(const CircularString& ls) const { - return new LineString(newCoords, this); + // Can't use make_unique with protected constructor + return std::unique_ptr(new CircularString(ls)); } /*public*/ @@ -655,29 +646,56 @@ std::unique_ptr GeometryFactory::createLineString(CoordinateSequence::Ptr && newCoords) const { + if (!newCoords) + return createLineString(); // Can't use make_unique with protected constructor return std::unique_ptr(new LineString(std::move(newCoords), *this)); } /*public*/ -std::unique_ptr -GeometryFactory::createLineString(std::vector && newCoords) +std::unique_ptr +GeometryFactory::createCircularString(CoordinateSequence::Ptr && newCoords) const { + if (!newCoords) + return createCircularString(false, false); // Can't use make_unique with protected constructor - return std::unique_ptr(new LineString(std::move(newCoords), *this)); + return std::unique_ptr(new CircularString(std::move(newCoords), *this)); +} + +/*public*/ +std::unique_ptr +GeometryFactory::createCompoundCurve() +const +{ + std::vector> curves; + return createCompoundCurve(std::move(curves)); } /*public*/ -LineString* +std::unique_ptr +GeometryFactory::createCompoundCurve(std::vector>&& curves) +const +{ + return std::unique_ptr(new CompoundCurve(std::move(curves), *this)); +} + +/*public*/ +std::unique_ptr GeometryFactory::createLineString(const CoordinateSequence& fromCoords) const { - auto newCoords = fromCoords.clone(); - LineString* g = nullptr; - // construction failure will delete newCoords - g = new LineString(newCoords.release(), this); - return g; + // Can't use make_unique with protected constructor + return std::unique_ptr(new LineString(fromCoords.clone(), *this)); +} + +/*public*/ +std::unique_ptr +GeometryFactory::createCircularString(const CoordinateSequence& fromCoords) +const +{ + // Can't use make_unique with protected constructor + return std::unique_ptr(new CircularString(fromCoords.clone(), *this)); } /*public*/ @@ -694,6 +712,51 @@ GeometryFactory::createEmpty(int dimension) const } } +/*public*/ +std::unique_ptr +GeometryFactory::createEmpty(GeometryTypeId typeId) const +{ + switch (typeId) { + case GEOS_POINT: return createPoint(); + case GEOS_LINESTRING: return createLineString(); + case GEOS_POLYGON: return createPolygon(); + case GEOS_MULTIPOINT: return createMultiPoint(); + case GEOS_MULTILINESTRING: return createMultiLineString(); + case GEOS_MULTIPOLYGON: return createMultiPolygon(); + case GEOS_GEOMETRYCOLLECTION: return createGeometryCollection(); + default: + throw geos::util::IllegalArgumentException("Invalid GeometryTypeId"); + } +} + +/*public*/ +std::unique_ptr +GeometryFactory::createMulti(std::unique_ptr && geom) const +{ + GeometryTypeId typeId = geom->getGeometryTypeId(); + + // Already a collection? Done! + if (geom->isCollection()) + return std::move(geom); + + if (geom->isEmpty()) { + return geom->getFactory()->createEmpty(Geometry::multiTypeId(typeId)); + } + + std::vector> subgeoms; + const GeometryFactory* gf = geom->getFactory(); + subgeoms.push_back(std::move(geom)); + switch (typeId) { + case GEOS_POINT: + return gf->createMultiPoint(std::move(subgeoms)); + case GEOS_LINESTRING: + return gf->createMultiLineString(std::move(subgeoms)); + case GEOS_POLYGON: + return gf->createMultiPolygon(std::move(subgeoms)); + default: + throw geos::util::IllegalArgumentException("Unsupported GeometryTypeId"); + } +} template GeometryTypeId commonType(const T& geoms) { @@ -721,32 +784,6 @@ GeometryTypeId commonType(const T& geoms) { } } -/*public*/ -Geometry* -GeometryFactory::buildGeometry(std::vector* newGeoms) const -{ - if(newGeoms->empty()) { - // we do not need the vector anymore - delete newGeoms; - return createGeometryCollection().release(); - } - - if (newGeoms->size() == 1) { - Geometry* ret = (*newGeoms)[0]; - delete newGeoms; - return ret; - } - - auto resultType = commonType(*newGeoms); - - switch(resultType) { - case GEOS_MULTIPOINT: return createMultiPoint(newGeoms); - case GEOS_MULTILINESTRING: return createMultiLineString(newGeoms); - case GEOS_MULTIPOLYGON: return createMultiPolygon(newGeoms); - default: return createGeometryCollection(newGeoms); - } -} - std::unique_ptr GeometryFactory::buildGeometry(std::vector> && geoms) const { @@ -811,15 +848,15 @@ GeometryFactory::buildGeometry(std::vector> && geoms) c } /*public*/ -Geometry* +std::unique_ptr GeometryFactory::buildGeometry(const std::vector& fromGeoms) const { if(fromGeoms.empty()) { - return createGeometryCollection().release(); + return createGeometryCollection(); } if(fromGeoms.size() == 1) { - return fromGeoms[0]->clone().release(); + return fromGeoms[0]->clone(); } auto resultType = commonType(fromGeoms); @@ -833,14 +870,14 @@ GeometryFactory::buildGeometry(const std::vector& fromGeoms) co } /*public*/ -Geometry* +std::unique_ptr GeometryFactory::createGeometry(const Geometry* g) const { // could this be cached to make this more efficient? Or maybe it isn't enough overhead to bother - //return g->clone(); + //return g->clone(); <-- a simple clone() wouldn't change the factory to `this` util::GeometryEditor editor(this); - gfCoordinateOperation coordOp(coordinateListFactory); - return editor.edit(g, &coordOp).release(); + gfCoordinateOperation coordOp; + return editor.edit(g, &coordOp); } /*public*/ diff --git a/Sources/geos/src/geom/HeuristicOverlay.cpp b/Sources/geos/src/geom/HeuristicOverlay.cpp index 8a20600..2d3d513 100644 --- a/Sources/geos/src/geom/HeuristicOverlay.cpp +++ b/Sources/geos/src/geom/HeuristicOverlay.cpp @@ -17,773 +17,381 @@ * ********************************************************************** * - * This file provides a single templated function, taking two + * This file provides a single function, taking two * const Geometry pointers, applying a binary operator to them * and returning a result Geometry in an unique_ptr<>. * - * The binary operator is expected to take two const Geometry pointers - * and return a newly allocated Geometry pointer, possibly throwing - * a TopologyException to signal it couldn't succeed due to robustness - * issues. - * - * This function will catch TopologyExceptions and try again with - * slightly modified versions of the input. The following heuristic - * is used: - * - * - Try with original input. - * - Try removing common bits from input coordinate values - * - Try snaping input geometries to each other - * - Try snaping input coordinates to a increasing grid (size from 1/25 to 1) - * - Try simplifiying input with increasing tolerance (from 0.01 to 0.04) - * - * If none of the step succeeds the original exception is thrown. - * - * Note that you can skip Grid snapping, Geometry snapping and Simplify policies - * by a compile-time define when building geos. - * See USE_TP_SIMPLIFY_POLICY, USE_PRECISION_REDUCTION_POLICY and - * USE_SNAPPING_POLICY macros below. - * **********************************************************************/ #include -#include -#include #include - -#include -#include -#include -#include -#include -#include - - -#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include - -#include - -#define GEOS_DEBUG_HEURISTICOVERLAY 0 -#define GEOS_DEBUG_HEURISTICOVERLAY_PRINT_INVALID 0 - - -#if GEOS_DEBUG_HEURISTICOVERLAY -# include -# include -# include -#endif - - -/* - * Define this to use OverlayNG policy with whatever precision - */ -#if ! defined(DISABLE_OVERLAYNG) && ! defined(USE_OVERLAYNG_SNAPIFNEEDED) -# define USE_OVERLAYNG_SNAPIFNEEDED -#endif - -/* - * Always try original input first - */ -#ifndef USE_ORIGINAL_INPUT -# define USE_ORIGINAL_INPUT 1 -#endif - -/* - * Check validity of operation between original geometries - */ -#define GEOS_CHECK_ORIGINAL_RESULT_VALIDITY 0 - -/* - * Define this to use OverlayNG policy with fixed precision - */ -#ifndef USE_FIXED_PRECISION_OVERLAYNG -# define USE_FIXED_PRECISION_OVERLAYNG 0 -#endif - -/* - * Define this to use PrecisionReduction policy - * in an attempt at by-passing binary operation - * robustness problems (handles TopologyExceptions) - */ -#ifndef USE_PRECISION_REDUCTION_POLICY -# define USE_PRECISION_REDUCTION_POLICY 1 -#endif - -/* - * Check validity of operation performed - * by precision reduction policy. - * - * Precision reduction policy reduces precision of inputs - * and restores it in the result. The restore phase may - * introduce invalidities. - * - */ -#define GEOS_CHECK_PRECISION_REDUCTION_VALIDITY 0 - -/* - * Define this to use TopologyPreserving simplification policy - * in an attempt at by-passing binary operation - * robustness problems (handles TopologyExceptions) - */ -#ifndef USE_TP_SIMPLIFY_POLICY -//# define USE_TP_SIMPLIFY_POLICY 1 -#endif - -/* - * Use common bits removal policy. - * If enabled, this would be tried /before/ - * Geometry snapping. - */ -#ifndef USE_COMMONBITS_POLICY -# define USE_COMMONBITS_POLICY 1 -#endif - -/* - * Check validity of operation performed - * by common bits removal policy. - * - * This matches what EnhancedPrecisionOp does in JTS - * and fixes 5 tests of invalid outputs in our testsuite - * (stmlf-cases-20061020-invalid-output.xml) - * and breaks 1 test (robustness-invalid-output.xml) so much - * to prevent a result. - * - */ -#define GEOS_CHECK_COMMONBITS_VALIDITY 1 - -/* - * Use snapping policy - */ -#ifndef USE_SNAPPING_POLICY -# define USE_SNAPPING_POLICY 1 -#endif - -/* Remove common bits before snapping */ -#ifndef CBR_BEFORE_SNAPPING -# define CBR_BEFORE_SNAPPING 1 -#endif - - -/* - * Check validity of result from SnapOp - */ -#define GEOS_CHECK_SNAPPINGOP_VALIDITY 0 - -using geos::operation::overlay::OverlayOp; -using geos::operation::overlayng::OverlayNG; +#include namespace geos { namespace geom { // geos::geom -inline bool -check_valid(const Geometry& g, const std::string& label, bool doThrow = false, bool validOnly = false) -{ - if(g.isLineal()) { - if(! validOnly) { - operation::valid::IsSimpleOp sop(g, algorithm::BoundaryNodeRule::getBoundaryEndPoint()); - if(! sop.isSimple()) { - if(doThrow) { - throw geos::util::TopologyException( - label + " is not simple"); - } - return false; - } - } - } - else { - operation::valid::IsValidOp ivo(&g); - if(! ivo.isValid()) { - using operation::valid::TopologyValidationError; - const TopologyValidationError* err = ivo.getValidationError(); -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << label << " is INVALID: " - << err->toString() - << " (" << std::setprecision(20) - << err->getCoordinate() << ")" - << std::endl -#if GEOS_DEBUG_HEURISTICOVERLAY_PRINT_INVALID - << "" << std::endl - << g.toString() - << std::endl - << "" << std::endl -#endif - ; -#endif - if(doThrow) { - throw geos::util::TopologyException( - label + " is invalid: " + err->getMessage(), - err->getCoordinate()); - } - return false; - } - } - return true; -} - -/* - * Attempt to fix noding of multilines and - * self-intersection of multipolygons - * - * May return the input untouched. - */ -inline std::unique_ptr -fix_self_intersections(std::unique_ptr g, const std::string& label) -{ - ::geos::ignore_unused_variable_warning(label); -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << label << " fix_self_intersection (UnaryUnion)" << std::endl; -#endif - - // Only multi-components can be fixed by UnaryUnion - if(! dynamic_cast(g.get())) { - return g; - } - - using operation::valid::IsValidOp; - - IsValidOp ivo(g.get()); - - // Polygon is valid, nothing to do - if(ivo.isValid()) { - return g; - } - - // Not all invalidities can be fixed by this code - - using operation::valid::TopologyValidationError; - const TopologyValidationError* err = ivo.getValidationError(); - switch(err->getErrorType()) { - case TopologyValidationError::eRingSelfIntersection: - case TopologyValidationError::eTooFewPoints: // collapsed lines -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << label << " ATTEMPT_TO_FIX: " << err->getErrorType() << ": " << *g << std::endl; -#endif - g = g->Union(); -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << label << " ATTEMPT_TO_FIX succeeded.. " << std::endl; -#endif - return g; - case TopologyValidationError::eSelfIntersection: - // this one is within a single component, won't be fixed - default: -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << label << " invalidity is: " << err->getErrorType() << std::endl; -#endif - return g; - } -} - - -/// \brief -/// Apply an overlay operation to the given geometries -/// after snapping them to each other after common-bits -/// removal. -/// -std::unique_ptr -SnapOp(const Geometry* g0, const Geometry* g1, int opCode) -{ - typedef std::unique_ptr GeomPtr; - - //using geos::precision::GeometrySnapper; - using geos::operation::overlay::snap::GeometrySnapper; - - // Snap tolerance must be computed on the original - // (not commonbits-removed) geoms - double snapTolerance = GeometrySnapper::computeOverlaySnapTolerance(*g0, *g1); -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << std::setprecision(20) << "Computed snap tolerance: " << snapTolerance << std::endl; -#endif - - -#if CBR_BEFORE_SNAPPING - // Compute common bits - geos::precision::CommonBitsRemover cbr; - cbr.add(g0); - cbr.add(g1); -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Computed common bits: " << cbr.getCommonCoordinate() << std::endl; -#endif - - // Now remove common bits - GeomPtr rG0 = g0->clone(); - cbr.removeCommonBits(rG0.get()); - GeomPtr rG1 = g1->clone(); - cbr.removeCommonBits(rG1.get()); - -#if GEOS_DEBUG_HEURISTICOVERLAY - check_valid(*rG0, "CBR: removed-bits geom 0"); - check_valid(*rG1, "CBR: removed-bits geom 1"); -#endif - - const Geometry& operand0 = *rG0; - const Geometry& operand1 = *rG1; -#else // don't CBR before snapping - const Geometry& operand0 = *g0; - const Geometry& operand1 = *g1; -#endif - - - GeometrySnapper snapper0(operand0); - GeomPtr snapG0(snapper0.snapTo(operand1, snapTolerance)); - //snapG0 = fix_self_intersections(snapG0, "SNAP: snapped geom 0"); - - // NOTE: second geom is snapped on the snapped first one - GeometrySnapper snapper1(operand1); - GeomPtr snapG1(snapper1.snapTo(*snapG0, snapTolerance)); - //snapG1 = fix_self_intersections(snapG1, "SNAP: snapped geom 1"); - - // Run the overlay op - GeomPtr result(OverlayOp::overlayOp(snapG0.get(), snapG1.get(), OverlayOp::OpCode(opCode))); - -#if GEOS_DEBUG_HEURISTICOVERLAY - check_valid(*result, "SNAP: result (before common-bits addition"); -#endif - -#if CBR_BEFORE_SNAPPING - // Add common bits back in - cbr.addCommonBits(result.get()); - //result = fix_self_intersections(result, "SNAP: result (after common-bits addition)"); - - check_valid(*result, "CBR: result (after common-bits addition)", true); - -#endif - - return result; -} - +using operation::overlayng::OverlayNG; +using operation::overlayng::OverlayNGRobust; std::unique_ptr HeuristicOverlay(const Geometry* g0, const Geometry* g1, int opCode) { - typedef std::unique_ptr GeomPtr; - - GeomPtr ret; - geos::util::TopologyException origException; - - -/**************************************************************************/ - -/* -* overlayng::OverlayNGRobust carries out the following steps -* -* 1. Perform overlay operation using PrecisionModel(float). -* If no exception return result. -* 2. Perform overlay operation using SnappingNoder(tolerance), starting -* with a very very small tolerance and increasing it for 5 iterations. -* The SnappingNoder moves only nodes that are within tolerance of -* other nodes and lines, leaving all the rest undisturbed, for a very -* clean result, if it manages to create one. -* If a result is found with no exception, return. -* 3. Perform overlay operation using a PrecisionModel(scale), which -* uses a SnapRoundingNoder. Every vertex will be noded to the snapping -* grid, resulting in a modified geometry. The SnapRoundingNoder approach -* reliably produces results, assuming valid inputs. -* -* Running overlayng::OverlayNGRobust at this stage should guarantee -* that none of the other heuristics are ever needed. -*/ -#ifdef USE_OVERLAYNG_SNAPIFNEEDED - -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Trying with OverlayNGRobust" << std::endl; -#endif - - try { - if (g0 == nullptr && g1 == nullptr) { - return std::unique_ptr(nullptr); - } - else if (g0 == nullptr) { - // Use a uniary union for the one-parameter case, as the pairwise - // union with one parameter is very intolerant to invalid - // collections and multi-polygons. - ret = operation::overlayng::OverlayNGRobust::Union(g1); - } - else if (g1 == nullptr) { - // Use a uniary union for the one-parameter case, as the pairwise - // union with one parameter is very intolerant to invalid - // collections and multi-polygons. - ret = operation::overlayng::OverlayNGRobust::Union(g0); + std::unique_ptr ret; + + /* + * overlayng::OverlayNGRobust does not currently handle + * GeometryCollection (collections of mixed dimension) + * so we handle that case here. + */ + if ((g0->isMixedDimension() && !g0->isEmpty()) || + (g1->isMixedDimension() && !g1->isEmpty())) + { + StructuredCollection s0(g0); + StructuredCollection s1(g1); + switch (opCode) { + case OverlayNG::UNION: + return s0.doUnion(s1); + case OverlayNG::DIFFERENCE: + return s0.doDifference(s1); + case OverlayNG::SYMDIFFERENCE: + return s0.doSymDifference(s1); + case OverlayNG::INTERSECTION: + return s0.doIntersection(s1); } - else { - ret = operation::overlayng::OverlayNGRobust::Overlay(g0, g1, opCode); - } - -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Attempt with OverlayNGRobust succeeded" << std::endl; -#endif - - return ret; - } - catch(const std::exception& ex) { - ::geos::ignore_unused_variable_warning(ex); - -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "OverlayNGRobust: " << ex.what() << std::endl; -#endif - } - - check_valid(*g0, "Input geom 0", true, true); - check_valid(*g1, "Input geom 1", true, true); - -#endif // USE_OVERLAYNG_SNAPIFNEEDED } - - -/**************************************************************************/ - -#ifdef USE_ORIGINAL_INPUT - // Try with original input - try { -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Trying with original input." << std::endl; -#endif - ret.reset(OverlayOp::overlayOp(g0, g1, OverlayOp::OpCode(opCode))); - -#if GEOS_CHECK_ORIGINAL_RESULT_VALIDITY - check_valid(*ret, "Overlay result between original inputs", true, true); -#endif - -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Attempt with original input succeeded" << std::endl; -#endif - return ret; } - catch(const geos::util::TopologyException& ex) { - origException = ex; -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Original exception: " << ex.what() << std::endl; -#endif - } -#endif // USE_ORIGINAL_INPUT - -/**************************************************************************/ - - check_valid(*g0, "Input geom 0", true, true); - check_valid(*g1, "Input geom 1", true, true); - -#if USE_COMMONBITS_POLICY - // Try removing common bits (possibly obsoleted by snapping below) - // - // NOTE: this policy was _later_ implemented - // in JTS as EnhancedPrecisionOp - // TODO: consider using the now-ported EnhancedPrecisionOp - // here too - // - try { - GeomPtr rG0; - GeomPtr rG1; - precision::CommonBitsRemover cbr; - -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Trying with Common Bits Remover (CBR)" << std::endl; -#endif - - cbr.add(g0); - cbr.add(g1); - - rG0 = g0->clone(); - cbr.removeCommonBits(rG0.get()); - - rG1 = g1->clone(); - cbr.removeCommonBits(rG1.get()); - -#if GEOS_DEBUG_HEURISTICOVERLAY - check_valid(*rG0, "CBR: geom 0 (after common-bits removal)"); - check_valid(*rG1, "CBR: geom 1 (after common-bits removal)"); -#endif - ret.reset(OverlayOp::overlayOp(rG0.get(), rG1.get(), OverlayOp::OpCode(opCode))); - -#if GEOS_DEBUG_HEURISTICOVERLAY - check_valid(*ret, "CBR: result (before common-bits addition)"); -#endif - - cbr.addCommonBits(ret.get()); - -#if GEOS_CHECK_COMMONBITS_VALIDITY - check_valid(*ret, "CBR: result (after common-bits addition)", true); -#endif - -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Attempt with CBR succeeded" << std::endl; -#endif - - return ret; + /* + * overlayng::OverlayNGRobust carries out the following steps + * + * 1. Perform overlay operation using PrecisionModel(float). + * If no exception return result. + * 2. Perform overlay operation using SnappingNoder(tolerance), starting + * with a very very small tolerance and increasing it for 5 iterations. + * The SnappingNoder moves only nodes that are within tolerance of + * other nodes and lines, leaving all the rest undisturbed, for a very + * clean result, if it manages to create one. + * If a result is found with no exception, return. + * 3. Perform overlay operation using a PrecisionModel(scale), which + * uses a SnapRoundingNoder. Every vertex will be noded to the snapping + * grid, resulting in a modified geometry. The SnapRoundingNoder approach + * reliably produces results, assuming valid inputs. + * + * Running overlayng::OverlayNGRobust at this stage should guarantee + * that none of the other heuristics are ever needed. + */ + if (g0 == nullptr && g1 == nullptr) { + return std::unique_ptr(nullptr); } - catch(const geos::util::TopologyException& ex) { - ::geos::ignore_unused_variable_warning(ex); -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "CBR: " << ex.what() << std::endl; -#endif + else if (g0 == nullptr) { + // Use a unary union for the one-parameter case, as the pairwise + // union with one parameter is very intolerant to invalid + // collections and multi-polygons. + ret = OverlayNGRobust::Union(g1); } -#endif - -/**************************************************************************/ - -// Try with snapping -// -// TODO: possible optimization would be reusing the -// already common-bit-removed inputs and just -// apply geometry snapping, whereas the current -// SnapOp function does both. -// { -#if USE_SNAPPING_POLICY - -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Trying with snapping " << std::endl; -#endif - - try { - ret = SnapOp(g0, g1, opCode); -#if GEOS_CHECK_SNAPPINGOP_VALIDITY - check_valid(*ret, "SNAP: result", true, true); -#endif -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "SnapOp succeeded" << std::endl; -#endif - return ret; - + else if (g1 == nullptr) { + // Use a unary union for the one-parameter case, as the pairwise + // union with one parameter is very intolerant to invalid + // collections and multi-polygons. + ret = OverlayNGRobust::Union(g0); } - catch(const geos::util::TopologyException& ex) { - ::geos::ignore_unused_variable_warning(ex); -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "SNAP: " << ex.what() << std::endl; -#endif + else { + ret = OverlayNGRobust::Overlay(g0, g1, opCode); } -#endif // USE_SNAPPING_POLICY } - -/**************************************************************************/ - -// { -#if USE_PRECISION_REDUCTION_POLICY - - - // Try reducing precision - try { - long unsigned int g0scale = - static_cast(g0->getFactory()->getPrecisionModel()->getScale()); - long unsigned int g1scale = - static_cast(g1->getFactory()->getPrecisionModel()->getScale()); - -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Original input scales are: " - << g0scale - << " and " - << g1scale - << std::endl; -#endif - - double maxScale = 1e16; // TODO: compute from input - double minScale = 1; // TODO: compute from input - - // Don't use a scale bigger than the input one - if(g0scale && static_cast(g0scale) < maxScale) { - maxScale = static_cast(g0scale); - } - if(g1scale && static_cast(g1scale) < maxScale) { - maxScale = static_cast(g1scale); - } - - - for(double scale = maxScale; scale >= minScale; scale /= 10) { - PrecisionModel pm(scale); - GeometryFactory::Ptr gf = GeometryFactory::create(&pm); -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Trying with scale " << scale << std::endl; -#endif - - precision::GeometryPrecisionReducer reducer(*gf); - reducer.setUseAreaReducer(false); - reducer.setChangePrecisionModel(true); - GeomPtr rG0(reducer.reduce(*g0)); - GeomPtr rG1(reducer.reduce(*g1)); - -#if GEOS_DEBUG_HEURISTICOVERLAY - check_valid(*rG0, "PR: geom 0 (after precision reduction)"); - check_valid(*rG1, "PR: geom 1 (after precision reduction)"); -#endif - - try { - ret.reset(OverlayOp::overlayOp(rG0.get(), rG1.get(), OverlayOp::OpCode(opCode))); - // restore original precision (least precision between inputs) - if(g0->getFactory()->getPrecisionModel()->compareTo(g1->getFactory()->getPrecisionModel()) < 0) { - ret.reset(g0->getFactory()->createGeometry(ret.get())); - } - else { - ret.reset(g1->getFactory()->createGeometry(ret.get())); - } - -#if GEOS_CHECK_PRECISION_REDUCTION_VALIDITY - check_valid(*ret, "PR: result (after restore of original precision)", true); -#endif - -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Attempt with scale " << scale << " succeeded" << std::endl; -#endif - return ret; - } - catch(const geos::util::TopologyException& ex) { -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Reduced with scale (" << scale << "): " - << ex.what() << std::endl; -#endif - (void)ex; // quiet compiler warning about unused variable - if(scale == 1) { - throw; - } - } + return ret; +} +/* public */ +void +StructuredCollection::readCollection(const Geometry* g) +{ + if (!factory) factory = g->getFactory(); + if (g->isCollection()) { + for (std::size_t i = 0; i < g->getNumGeometries(); i++) { + readCollection(g->getGeometryN(i)); } - } - catch(const geos::util::TopologyException& ex) { -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Reduced: " << ex.what() << std::endl; -#endif - ::geos::ignore_unused_variable_warning(ex); - } - -#endif -// USE_PRECISION_REDUCTION_POLICY } - -/**************************************************************************/ - -// { -#if USE_FIXED_PRECISION_OVERLAYNG - - // Try OverlayNG with fixed precision - try { - long unsigned int g0scale = - static_cast(g0->getFactory()->getPrecisionModel()->getScale()); - long unsigned int g1scale = - static_cast(g1->getFactory()->getPrecisionModel()->getScale()); - -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Original input scales are: " - << g0scale - << " and " - << g1scale - << std::endl; -#endif - - double maxScale = 1e16; // TODO: compute from input - double minScale = 1e10; // TODO: compute from input - - // Don't use a scale bigger than the input one - if(g0scale && static_cast(g0scale) < maxScale) { - maxScale = static_cast(g0scale); - } - if(g1scale && static_cast(g1scale) < maxScale) { - maxScale = static_cast(g1scale); + else { + if (g->isEmpty()) return; + switch (g->getGeometryTypeId()) { + case GEOS_POINT: + pts.push_back(g); + break; + case GEOS_LINESTRING: + lines.push_back(g); + break; + case GEOS_POLYGON: + polys.push_back(g); + break; + default: + throw util::IllegalArgumentException("cannot process unexpected collection"); } + } +} - - for(double scale = maxScale; scale >= minScale; scale /= 10) { - PrecisionModel pm(scale); -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Trying with precision scale " << scale << std::endl; -#endif - - try { - ret = OverlayNG::overlay(g0, g1, opCode, &pm); - -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Attempt with fixedNG scale " << scale << " succeeded" << std::endl; -#endif - return ret; - } - catch(const geos::util::TopologyException& ex) { -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "fixedNG with scale (" << scale << "): " - << ex.what() << std::endl; -#endif - if(scale == 1) { - throw ex; - } - } - +/* public static */ +void +StructuredCollection::toVector(const Geometry* g, std::vector& v) +{ + if (!g || g->isEmpty()) return; + if (g->isCollection()) { + for (std::size_t i = 0; i < g->getNumGeometries(); i++) { + toVector(g->getGeometryN(i), v); } - } - catch(const geos::util::TopologyException& ex) { -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Reduced: " << ex.what() << std::endl; -#endif - ::geos::ignore_unused_variable_warning(ex); + else { + switch (g->getGeometryTypeId()) { + case GEOS_POINT: + case GEOS_LINESTRING: + case GEOS_POLYGON: + v.push_back(g); + break; + default: + return; + } } +} -#endif -// USE_FIXED_PRECISION_OVERLAYNG } - -/**************************************************************************/ - -// { -#if USE_TP_SIMPLIFY_POLICY - - // Try simplifying - try { - - double maxTolerance = 0.04; - double minTolerance = 0.01; - double tolStep = 0.01; - for(double tol = minTolerance; tol <= maxTolerance; tol += tolStep) { -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Trying simplifying with tolerance " << tol << std::endl; -#endif +/* public */ +void +StructuredCollection::unionByDimension(void) +{ + /* + * Remove duplication within each dimension, so that there + * is only one object covering any particular space within + * that dimension. + * This makes reasoning about the collection-on-collection + * operations a little easier later on. + */ + std::unique_ptr pt_col = factory->createMultiPoint(pts); + std::unique_ptr line_col = factory->createMultiLineString(lines); + std::unique_ptr poly_col = factory->createMultiPolygon(polys); + + pt_union = OverlayNGRobust::Union(static_cast(pt_col.get())); + line_union = OverlayNGRobust::Union(static_cast(line_col.get())); + poly_union = OverlayNGRobust::Union(static_cast(poly_col.get())); + + // io::WKTWriter w; + // std::cout << "line_col " << w.write(*line_col) << std::endl; + // std::cout << "line_union " << w.write(*line_union) << std::endl; + + if (! pt_union->isPuntal()) + throw util::IllegalArgumentException("union of points not puntal"); + if (! line_union->isLineal()) + throw util::IllegalArgumentException("union of lines not lineal"); + if (! poly_union->isPolygonal()) + throw util::IllegalArgumentException("union of polygons not polygonal"); +} - GeomPtr rG0(simplify::TopologyPreservingSimplifier::simplify(g0, tol)); - GeomPtr rG1(simplify::TopologyPreservingSimplifier::simplify(g1, tol)); +/* public */ +std::unique_ptr +StructuredCollection::doUnaryUnion() const +{ + /* + * Before output, we clean up the components to remove spatial + * duplication. Points that lines pass through. Lines that are covered + * by polygonal areas already. Provides a "neater" output that still + * covers all the area it should. + */ + std::unique_ptr pts_less_lines = OverlayNGRobust::Overlay( + pt_union.get(), + line_union.get(), + OverlayNG::DIFFERENCE); + + std::unique_ptr pts_less_polys_lines = OverlayNGRobust::Overlay( + pts_less_lines.get(), + poly_union.get(), + OverlayNG::DIFFERENCE); + + std::unique_ptr lines_less_polys = OverlayNGRobust::Overlay( + line_union.get(), + poly_union.get(), + OverlayNG::DIFFERENCE); + + std::vector geoms; + toVector(pts_less_polys_lines.get(), geoms); + toVector(lines_less_polys.get(), geoms); + toVector(poly_union.get(), geoms); + + return factory->buildGeometry(geoms.begin(), geoms.end()); +} - try { - ret.reset(OverlayOp::overlayOp(rG0.get(), rG1.get(), geos::operation::overlay::OverlayOp::OpCode(opCode))); - return ret; - } - catch(const geos::util::TopologyException& ex) { - if(tol >= maxTolerance) { - throw; - } -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Simplified with tolerance (" << tol << "): " - << ex.what() << std::endl; -#endif - } - } +/* public */ +std::unique_ptr +StructuredCollection::doUnion(const StructuredCollection& a) const +{ - return ret; + auto poly_union_poly = OverlayNGRobust::Overlay( + a.getPolyUnion(), + poly_union.get(), + OverlayNG::UNION); + + auto line_union_line = OverlayNGRobust::Overlay( + a.getLineUnion(), + line_union.get(), + OverlayNG::UNION); + + auto pt_union_pt = OverlayNGRobust::Overlay( + a.getPointUnion(), + pt_union.get(), + OverlayNG::UNION); + + StructuredCollection c; + c.readCollection(poly_union_poly.get()); + c.readCollection(line_union_line.get()); + c.readCollection(pt_union_pt.get()); + c.unionByDimension(); + return c.doUnaryUnion(); +} - } - catch(const geos::util::TopologyException& ex) { -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "Simplified: " << ex.what() << std::endl; -#endif - } -#endif -// USE_TP_SIMPLIFY_POLICY } +std::unique_ptr +StructuredCollection::doIntersection(const StructuredCollection& a) const +{ + std::unique_ptr poly_inter_poly = OverlayNGRobust::Overlay( + poly_union.get(), + a.getPolyUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr poly_inter_line = OverlayNGRobust::Overlay( + poly_union.get(), + a.getLineUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr poly_inter_pt = OverlayNGRobust::Overlay( + poly_union.get(), + a.getPointUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr line_inter_poly = OverlayNGRobust::Overlay( + line_union.get(), + a.getPolyUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr line_inter_line = OverlayNGRobust::Overlay( + line_union.get(), + a.getLineUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr line_inter_pt = OverlayNGRobust::Overlay( + line_union.get(), + a.getPointUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr pt_inter_pt = OverlayNGRobust::Overlay( + pt_union.get(), + a.getPointUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr pt_inter_line = OverlayNGRobust::Overlay( + pt_union.get(), + a.getLineUnion(), + OverlayNG::INTERSECTION); + + std::unique_ptr pt_inter_poly = OverlayNGRobust::Overlay( + pt_union.get(), + a.getPolyUnion(), + OverlayNG::INTERSECTION); + + // io::WKTWriter w; + // std::cout << "poly_inter_poly " << w.write(*poly_inter_poly) << std::endl; + // std::cout << "poly_union.get() " << w.write(poly_union.get()) << std::endl; + // std::cout << "a.getLineUnion() " << w.write(a.getLineUnion()) << std::endl; + // std::cout << "poly_inter_line " << w.write(*poly_inter_line) << std::endl; + // std::cout << "poly_inter_pt " << w.write(*poly_inter_pt) << std::endl; + // std::cout << "line_inter_line " << w.write(*line_inter_line) << std::endl; + // std::cout << "line_inter_pt " << w.write(*line_inter_pt) << std::endl; + // std::cout << "pt_inter_pt " << w.write(*pt_inter_pt) << std::endl; + + StructuredCollection c; + c.readCollection(poly_inter_poly.get()); + c.readCollection(poly_inter_line.get()); + c.readCollection(poly_inter_pt.get()); + c.readCollection(line_inter_poly.get()); + c.readCollection(line_inter_line.get()); + c.readCollection(line_inter_pt.get()); + c.readCollection(pt_inter_poly.get()); + c.readCollection(pt_inter_line.get()); + c.readCollection(pt_inter_pt.get()); + c.unionByDimension(); + return c.doUnaryUnion(); +} -/**************************************************************************/ -#if GEOS_DEBUG_HEURISTICOVERLAY - std::cerr << "No attempts worked to union " << std::endl; - std::cerr << "Input geometries:" << std::endl - << "" << std::endl - << g0->toString() << std::endl - << "" << std::endl - << "" << std::endl - << g1->toString() << std::endl - << "" << std::endl; -#endif +std::unique_ptr +StructuredCollection::doDifference(const StructuredCollection& a) const +{ + std::unique_ptr poly_diff_poly = OverlayNGRobust::Overlay( + poly_union.get(), + a.getPolyUnion(), + OverlayNG::DIFFERENCE); + + std::unique_ptr line_diff_poly = OverlayNGRobust::Overlay( + line_union.get(), + a.getPolyUnion(), + OverlayNG::DIFFERENCE); + + std::unique_ptr pt_diff_poly = OverlayNGRobust::Overlay( + pt_union.get(), + a.getPolyUnion(), + OverlayNG::DIFFERENCE); + + std::unique_ptr line_diff_poly_line = OverlayNGRobust::Overlay( + line_diff_poly.get(), + a.getLineUnion(), + OverlayNG::DIFFERENCE); + + std::unique_ptr pt_diff_poly_line = OverlayNGRobust::Overlay( + pt_diff_poly.get(), + line_diff_poly_line.get(), + OverlayNG::DIFFERENCE); + + std::unique_ptr pt_diff_poly_line_pt = OverlayNGRobust::Overlay( + pt_diff_poly_line.get(), + a.getPointUnion(), + OverlayNG::DIFFERENCE); + + StructuredCollection c; + c.readCollection(poly_diff_poly.get()); + c.readCollection(line_diff_poly_line.get()); + c.readCollection(pt_diff_poly_line_pt.get()); + c.unionByDimension(); + return c.doUnaryUnion(); +} - throw origException; +std::unique_ptr +StructuredCollection::doSymDifference(const StructuredCollection& a) const +{ + std::unique_ptr poly_symdiff_poly = OverlayNGRobust::Overlay( + poly_union.get(), + a.getPolyUnion(), + OverlayNG::SYMDIFFERENCE); + + std::unique_ptr line_symdiff_line = OverlayNGRobust::Overlay( + line_union.get(), + a.getLineUnion(), + OverlayNG::DIFFERENCE); + + std::unique_ptr pt_symdiff_pt = OverlayNGRobust::Overlay( + pt_union.get(), + a.getPointUnion(), + OverlayNG::DIFFERENCE); + + StructuredCollection c; + c.readCollection(poly_symdiff_poly.get()); + c.readCollection(line_symdiff_line.get()); + c.readCollection(pt_symdiff_pt.get()); + c.unionByDimension(); + return c.doUnaryUnion(); } diff --git a/Sources/geos/src/geom/IntersectionMatrix.cpp b/Sources/geos/src/geom/IntersectionMatrix.cpp index 82acfcf..f7ace8a 100644 --- a/Sources/geos/src/geom/IntersectionMatrix.cpp +++ b/Sources/geos/src/geom/IntersectionMatrix.cpp @@ -144,7 +144,7 @@ IntersectionMatrix::set(Location row, Location col, int dimensionValue) void IntersectionMatrix::set(const std::string& dimensionSymbols) { - auto limit = dimensionSymbols.length(); + auto limit = std::min(dimensionSymbols.length(), static_cast(9)); for(std::size_t i = 0; i < limit; i++) { auto row = i / firstDim; diff --git a/Sources/geos/src/geom/LineSegment.cpp b/Sources/geos/src/geom/LineSegment.cpp index a090b16..c352fc2 100644 --- a/Sources/geos/src/geom/LineSegment.cpp +++ b/Sources/geos/src/geom/LineSegment.cpp @@ -22,12 +22,12 @@ #include #include // for toGeometry #include -#include #include #include #include #include #include +#include #include // for max #include @@ -47,7 +47,7 @@ LineSegment::reverse() /*public*/ double -LineSegment::projectionFactor(const Coordinate& p) const +LineSegment::projectionFactor(const CoordinateXY& p) const { if(p == p0) { return 0.0; @@ -55,6 +55,9 @@ LineSegment::projectionFactor(const Coordinate& p) const if(p == p1) { return 1.0; } + if(p0 == p1) { + return 0.0; + } // Otherwise, use comp.graphics.algorithms Frequently Asked Questions method /*(1) AC dot AB r = --------- @@ -75,7 +78,7 @@ LineSegment::projectionFactor(const Coordinate& p) const /*public*/ double -LineSegment::segmentFraction(const Coordinate& inputPt) const +LineSegment::segmentFraction(const CoordinateXY& inputPt) const { double segFrac = projectionFactor(inputPt); if(segFrac < 0.0) { @@ -93,19 +96,34 @@ LineSegment::project(const Coordinate& p, Coordinate& ret) const { if(p == p0 || p == p1) { ret = p; + return; } double r = projectionFactor(p); ret = Coordinate(p0.x + r * (p1.x - p0.x), p0.y + r * (p1.y - p0.y)); } +CoordinateXY +LineSegment::project(const CoordinateXY& p) const +{ + if(p == p0 || p == p1) { + return p; + } + double r = projectionFactor(p); + double x = p0.x + r * (p1.x - p0.x); + double y = p0.y + r * (p1.y - p0.y); + return CoordinateXY(x, y); +} + + + /*private*/ void -LineSegment::project(double factor, Coordinate& ret) const +LineSegment::project(double factor, CoordinateXY& ret) const { if( factor == 1.0 ) ret = p1; else - ret = Coordinate(p0.x + factor * (p1.x - p0.x), p0.y + factor * (p1.y - p0.y)); + ret = CoordinateXY(p0.x + factor * (p1.x - p0.x), p0.y + factor * (p1.y - p0.y)); } bool @@ -125,8 +143,13 @@ LineSegment::project(const LineSegment& seg, LineSegment& ret) const Coordinate newp0; project(pf0, newp0); + if (pf0 < 0.0) newp0 = p0; + if (pf0 > 1.0) newp0 = p1; + Coordinate newp1; project(pf1, newp1); + if (pf1 < 0.0) newp1 = p0; + if (pf1 > 1.0) newp1 = p1; ret.setCoordinates(newp0, newp1); @@ -135,7 +158,7 @@ LineSegment::project(const LineSegment& seg, LineSegment& ret) const //Coordinate* void -LineSegment::closestPoint(const Coordinate& p, Coordinate& ret) const +LineSegment::closestPoint(const CoordinateXY& p, CoordinateXY& ret) const { double factor = projectionFactor(p); if(factor > 0 && factor < 1) { @@ -151,17 +174,6 @@ LineSegment::closestPoint(const Coordinate& p, Coordinate& ret) const ret = p1; } -/*public*/ -int -LineSegment::compareTo(const LineSegment& other) const -{ - int comp0 = p0.compareTo(other.p0); - if(comp0 != 0) { - return comp0; - } - return p1.compareTo(other.p1); -} - /*public*/ bool LineSegment::equalsTopo(const LineSegment& other) const @@ -256,7 +268,8 @@ LineSegment::intersection(const LineSegment& line) const Coordinate LineSegment::lineIntersection(const LineSegment& line) const { - return algorithm::Intersection::intersection(p0, p1, line.p0, line.p1); + // TODO return a CoordinateXY here. + return Coordinate(algorithm::Intersection::intersection(p0, p1, line.p0, line.p1)); } @@ -306,23 +319,40 @@ LineSegment::offset(double offsetDistance) return ls; } +/* public */ +double +LineSegment::distancePerpendicularOriented(const CoordinateXY& p) const +{ + if (p0.equals2D(p1)) + return p0.distance(p); + double dist = distancePerpendicular(p); + if (orientationIndex(p) < 0) + return -dist; + return dist; +} /* public */ std::unique_ptr LineSegment::toGeometry(const GeometryFactory& gf) const { - auto cl = gf.getCoordinateSequenceFactory()->create(2, 0); + auto cl = detail::make_unique(2u); cl->setAt(p0, 0); cl->setAt(p1, 1); return gf.createLineString(std::move(cl)); } +/* public */ std::ostream& -operator<< (std::ostream& o, const LineSegment& l) +LineSegment::operator<< (std::ostream& os) { - return o << "LINESEGMENT(" << l.p0.x << " " << l.p0.y << "," << l.p1.x << " " << l.p1.y << ")"; + return os << "LINESEGMENT(" + << p0.x << " " << p0.y << "," + << p1.x << " " << p1.y << ")"; } + + + } // namespace geos::geom } // namespace geos diff --git a/Sources/geos/src/geom/LineString.cpp b/Sources/geos/src/geom/LineString.cpp index 39a25ca..90e3747 100644 --- a/Sources/geos/src/geom/LineString.cpp +++ b/Sources/geos/src/geom/LineString.cpp @@ -22,8 +22,6 @@ #include #include #include -#include -#include #include #include #include @@ -36,6 +34,7 @@ #include // for getBoundary #include #include +#include #include #include @@ -51,11 +50,17 @@ LineString::~LineString(){} /*protected*/ LineString::LineString(const LineString& ls) + : SimpleCurve(ls) +{ +} + +/*public*/ +LineString::LineString(CoordinateSequence::Ptr && newCoords, + const GeometryFactory& factory) : - Geometry(ls), - points(ls.points->clone()) + SimpleCurve(std::move(newCoords), true, factory) { - //points=ls.points->clone(); + validateConstruction(); } LineString* @@ -67,9 +72,9 @@ LineString::reverseImpl() const assert(points.get()); auto seq = points->clone(); - CoordinateSequence::reverse(seq.get()); + seq->reverse(); assert(getFactory()); - return getFactory()->createLineString(seq.release()); + return getFactory()->createLineString(std::move(seq)).release(); } @@ -78,7 +83,7 @@ void LineString::validateConstruction() { if(points.get() == nullptr) { - points = getFactory()->getCoordinateSequenceFactory()->create(); + points = std::make_unique(); return; } @@ -87,143 +92,6 @@ LineString::validateConstruction() } } -/*protected*/ -LineString::LineString(CoordinateSequence* newCoords, - const GeometryFactory* factory) - : - Geometry(factory), - points(newCoords) -{ - validateConstruction(); -} - -/*public*/ -LineString::LineString(CoordinateSequence::Ptr && newCoords, - const GeometryFactory& factory) - : - Geometry(&factory), - points(std::move(newCoords)) -{ - validateConstruction(); -} - -/*public*/ -LineString::LineString(std::vector && newCoords, - const GeometryFactory& factory) - : - Geometry(&factory), - points(new CoordinateArraySequence(std::move(newCoords))) -{ - validateConstruction(); -} - -std::unique_ptr -LineString::getCoordinates() const -{ - assert(points.get()); - return points->clone(); - //return points; -} - -const CoordinateSequence* -LineString::getCoordinatesRO() const -{ - assert(nullptr != points.get()); - return points.get(); -} - -std::unique_ptr -LineString::releaseCoordinates() -{ - auto ret = std::move(points); - geometryChanged(); - return ret; -} - -const Coordinate& -LineString::getCoordinateN(std::size_t n) const -{ - assert(points.get()); - return points->getAt(n); -} - -Dimension::DimensionType -LineString::getDimension() const -{ - return Dimension::L; // line -} - -uint8_t -LineString::getCoordinateDimension() const -{ - return (uint8_t) points->getDimension(); -} - -int -LineString::getBoundaryDimension() const -{ - if(isClosed()) { - return Dimension::False; - } - return 0; -} - -bool -LineString::isEmpty() const -{ - assert(points.get()); - return points->isEmpty(); -} - -std::size_t -LineString::getNumPoints() const -{ - assert(points.get()); - return points->getSize(); -} - -std::unique_ptr -LineString::getPointN(std::size_t n) const -{ - assert(getFactory()); - assert(points.get()); - return std::unique_ptr(getFactory()->createPoint(points->getAt(n))); -} - -std::unique_ptr -LineString::getStartPoint() const -{ - if(isEmpty()) { - return nullptr; - //return new Point(NULL,NULL); - } - return getPointN(0); -} - -std::unique_ptr -LineString::getEndPoint() const -{ - if(isEmpty()) { - return nullptr; - //return new Point(NULL,NULL); - } - return getPointN(getNumPoints() - 1); -} - -bool -LineString::isClosed() const -{ - if(isEmpty()) { - return false; - } - return getCoordinateN(0).equals2D(getCoordinateN(getNumPoints() - 1)); -} - -bool -LineString::isRing() const -{ - return isClosed() && isSimple(); -} std::string LineString::getGeometryType() const @@ -231,165 +99,6 @@ LineString::getGeometryType() const return "LineString"; } -std::unique_ptr -LineString::getBoundary() const -{ - operation::BoundaryOp bop(*this); - return bop.getBoundary(); -} - -bool -LineString::isCoordinate(Coordinate& pt) const -{ - assert(points.get()); - std::size_t npts = points->getSize(); - for(std::size_t i = 0; i < npts; i++) { - if(points->getAt(i) == pt) { - return true; - } - } - return false; -} - -/*protected*/ -Envelope::Ptr -LineString::computeEnvelopeInternal() const -{ - if(isEmpty()) { - // We don't return NULL here - // as it would indicate "unknown" - // envelope. In this case we - // *know* the envelope is EMPTY. - return Envelope::Ptr(new Envelope()); - } - - return detail::make_unique(points->getEnvelope()); -} - -bool -LineString::equalsExact(const Geometry* other, double tolerance) const -{ - if(!isEquivalentClass(other)) { - return false; - } - - const LineString* otherLineString = detail::down_cast(other); - std::size_t npts = points->getSize(); - if(npts != otherLineString->points->getSize()) { - return false; - } - for(std::size_t i = 0; i < npts; ++i) { - if(!equal(points->getAt(i), otherLineString->points->getAt(i), tolerance)) { - return false; - } - } - return true; -} - -void -LineString::apply_rw(const CoordinateFilter* filter) -{ - assert(points.get()); - points->apply_rw(filter); -} - -void -LineString::apply_ro(CoordinateFilter* filter) const -{ - assert(points.get()); - points->apply_ro(filter); -} - -void -LineString::apply_rw(GeometryFilter* filter) -{ - assert(filter); - filter->filter_rw(this); -} - -void -LineString::apply_ro(GeometryFilter* filter) const -{ - assert(filter); - filter->filter_ro(this); -} - -/*private*/ -void -LineString::normalizeClosed() -{ - auto coords = detail::make_unique>(); - getCoordinatesRO()->toVector(*coords); - - coords->erase(coords->end() - 1); // remove last point (repeated) - - auto uniqueCoordinates = detail::make_unique(coords.release()); - - const Coordinate* minCoordinate = uniqueCoordinates->minCoordinate(); - - CoordinateSequence::scroll(uniqueCoordinates.get(), minCoordinate); - uniqueCoordinates->add(uniqueCoordinates->getAt(0)); - - if(uniqueCoordinates->size() >= 4 && algorithm::Orientation::isCCW(uniqueCoordinates.get())) { - CoordinateSequence::reverse(uniqueCoordinates.get()); - } - points = uniqueCoordinates.get()->clone(); -} - -/*public*/ -void -LineString::normalize() -{ - if (isEmpty()) return; - assert(points.get()); - if (isClosed()) { - normalizeClosed(); - return; - } - std::size_t npts = points->getSize(); - std::size_t n = npts / 2; - for(std::size_t i = 0; i < n; i++) { - std::size_t j = npts - 1 - i; - if(!(points->getAt(i) == points->getAt(j))) { - if(points->getAt(i).compareTo(points->getAt(j)) > 0) { - CoordinateSequence::reverse(points.get()); - } - return; - } - } -} - -int -LineString::compareToSameClass(const Geometry* ls) const -{ - const LineString* line = detail::down_cast(ls); - - // MD - optimized implementation - std::size_t mynpts = points->getSize(); - std::size_t othnpts = line->points->getSize(); - if(mynpts > othnpts) { - return 1; - } - if(mynpts < othnpts) { - return -1; - } - for(std::size_t i = 0; i < mynpts; i++) { - int cmp = points->getAt(i).compareTo(line->points->getAt(i)); - if(cmp) { - return cmp; - } - } - return 0; -} - -const Coordinate* -LineString::getCoordinate() const -{ - if(isEmpty()) { - return nullptr; - } - return &(points->getAt(0)); -} double LineString::getLength() const @@ -397,53 +106,6 @@ LineString::getLength() const return Length::ofLine(points.get()); } -void -LineString::apply_rw(GeometryComponentFilter* filter) -{ - assert(filter); - filter->filter_rw(this); -} - -void -LineString::apply_ro(GeometryComponentFilter* filter) const -{ - assert(filter); - filter->filter_ro(this); -} - -void -LineString::apply_rw(CoordinateSequenceFilter& filter) -{ - std::size_t npts = points->size(); - if(!npts) { - return; - } - for(std::size_t i = 0; i < npts; ++i) { - filter.filter_rw(*points, i); - if(filter.isDone()) { - break; - } - } - if(filter.isGeometryChanged()) { - geometryChanged(); - } -} - -void -LineString::apply_ro(CoordinateSequenceFilter& filter) const -{ - std::size_t npts = points->size(); - if(!npts) { - return; - } - for(std::size_t i = 0; i < npts; ++i) { - filter.filter_ro(*points, i); - if(filter.isDone()) { - break; - } - } - //if (filter.isGeometryChanged()) geometryChanged(); -} GeometryTypeId LineString::getGeometryTypeId() const diff --git a/Sources/geos/src/geom/LinearRing.cpp b/Sources/geos/src/geom/LinearRing.cpp index 3d48053..6afc506 100644 --- a/Sources/geos/src/geom/LinearRing.cpp +++ b/Sources/geos/src/geom/LinearRing.cpp @@ -17,10 +17,11 @@ * **********************************************************************/ +#include + #include #include #include -#include #include #include @@ -35,15 +36,6 @@ namespace geom { // geos::geom /*public*/ LinearRing::LinearRing(const LinearRing& lr): LineString(lr) {} -/*public*/ -LinearRing::LinearRing(CoordinateSequence* newCoords, - const GeometryFactory* newFactory) - : - LineString(newCoords, newFactory) -{ - validateConstruction(); -} - /*public*/ LinearRing::LinearRing(CoordinateSequence::Ptr && newCoords, const GeometryFactory& newFactory) @@ -52,15 +44,6 @@ LinearRing::LinearRing(CoordinateSequence::Ptr && newCoords, validateConstruction(); } -LinearRing::LinearRing(std::vector && newCoords, - const GeometryFactory& factory) - : - LineString(std::move(newCoords), factory) -{ - validateConstruction(); -} - - void LinearRing::validateConstruction() { @@ -78,7 +61,7 @@ LinearRing::validateConstruction() if(points->getSize() < MINIMUM_VALID_SIZE) { std::ostringstream os; os << "Invalid number of points in LinearRing found " - << points->getSize() << " - must be 0 or >= 4"; + << points->getSize() << " - must be 0 or >= " << MINIMUM_VALID_SIZE; throw util::IllegalArgumentException(os.str()); } } @@ -117,6 +100,19 @@ LinearRing::getGeometryTypeId() const return GEOS_LINEARRING; } +void +LinearRing::orient(bool isCW) +{ + if (isEmpty()) { + return; + } + + if (algorithm::Orientation::isCCW(points.get()) == isCW) { + points->reverse(); + } + +} + LinearRing* LinearRing::reverseImpl() const { @@ -126,7 +122,7 @@ LinearRing::reverseImpl() const assert(points.get()); auto seq = points->clone(); - CoordinateSequence::reverse(seq.get()); + seq->reverse(); assert(getFactory()); return getFactory()->createLinearRing(std::move(seq)).release(); } diff --git a/Sources/geos/src/geom/MultiCurve.cpp b/Sources/geos/src/geom/MultiCurve.cpp new file mode 100644 index 0000000..31e13eb --- /dev/null +++ b/Sources/geos/src/geom/MultiCurve.cpp @@ -0,0 +1,116 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * Copyright (C) 2005 2006 Refractions Research Inc. + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include +#include + +namespace geos { +namespace geom { + +MultiCurve::MultiCurve(std::vector>&& newLines, + const GeometryFactory& factory) + : GeometryCollection(std::move(newLines), factory) +{ + for (const auto& geom : geometries) { + if (!dynamic_cast(geom.get())) { + throw util::IllegalArgumentException("All elements of MultiCurve must be a Curve"); + } + } +} + +MultiCurve::MultiCurve(std::vector>&& newLines, + const GeometryFactory& factory) + : GeometryCollection(std::move(newLines), factory) +{} + +std::unique_ptr +MultiCurve::getBoundary() const +{ + operation::BoundaryOp bop(*this); + return bop.getBoundary(); +} + +int +MultiCurve::getBoundaryDimension() const +{ + if (isClosed()) { + return Dimension::False; + } + return 0; +} + +Dimension::DimensionType +MultiCurve::getDimension() const +{ + return Dimension::L; // line +} + +const Curve* +MultiCurve::getGeometryN(std::size_t i) const +{ + return static_cast(geometries[i].get()); +} + +std::string +MultiCurve::getGeometryType() const +{ + return "MultiCurve"; +} + +GeometryTypeId +MultiCurve::getGeometryTypeId() const +{ + return GEOS_MULTICURVE; +} + +bool +MultiCurve::isClosed() const +{ + if (isEmpty()) { + return false; + } + for (const auto& g : geometries) { + const Curve* ls = detail::down_cast(g.get()); + if (! ls->isClosed()) { + return false; + } + } + return true; +} + +MultiCurve* +MultiCurve::reverseImpl() const +{ + if (isEmpty()) { + return clone().release(); + } + + std::vector> reversed(geometries.size()); + + std::transform(geometries.begin(), + geometries.end(), + reversed.begin(), + [](const std::unique_ptr& g) { + return g->reverse(); + }); + + return getFactory()->createMultiCurve(std::move(reversed)).release(); +} + +} +} diff --git a/Sources/geos/src/geom/MultiLineString.cpp b/Sources/geos/src/geom/MultiLineString.cpp index 6f50705..00b52df 100644 --- a/Sources/geos/src/geom/MultiLineString.cpp +++ b/Sources/geos/src/geom/MultiLineString.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -34,13 +35,6 @@ namespace geos { namespace geom { // geos::geom /*protected*/ -MultiLineString::MultiLineString(std::vector* newLines, - const GeometryFactory* factory) - : - GeometryCollection(newLines, factory) -{ -} - MultiLineString::MultiLineString(std::vector> && newLines, const GeometryFactory& factory) : GeometryCollection(std::move(newLines), factory) diff --git a/Sources/geos/src/geom/MultiPoint.cpp b/Sources/geos/src/geom/MultiPoint.cpp index a6ab0a6..aa615bf 100644 --- a/Sources/geos/src/geom/MultiPoint.cpp +++ b/Sources/geos/src/geom/MultiPoint.cpp @@ -29,12 +29,6 @@ namespace geos { namespace geom { // geos::geom /*protected*/ -MultiPoint::MultiPoint(std::vector* newPoints, const GeometryFactory* factory) - : - GeometryCollection(newPoints, factory) -{ -} - MultiPoint::MultiPoint(std::vector> && newPoints, const GeometryFactory& factory) : GeometryCollection(std::move(newPoints), factory) @@ -73,7 +67,7 @@ MultiPoint::getBoundary() const return std::unique_ptr(getFactory()->createGeometryCollection()); } -const Coordinate* +const CoordinateXY* MultiPoint::getCoordinateN(std::size_t n) const { return geometries[n]->getCoordinate(); diff --git a/Sources/geos/src/geom/MultiPolygon.cpp b/Sources/geos/src/geom/MultiPolygon.cpp index 183db1d..fa7cbaf 100644 --- a/Sources/geos/src/geom/MultiPolygon.cpp +++ b/Sources/geos/src/geom/MultiPolygon.cpp @@ -35,10 +35,6 @@ namespace geos { namespace geom { // geos::geom /*protected*/ -MultiPolygon::MultiPolygon(std::vector* newPolys, const GeometryFactory* factory) - : GeometryCollection(newPolys, factory) -{} - MultiPolygon::MultiPolygon(std::vector> && newPolys, const GeometryFactory& factory) : GeometryCollection(std::move(newPolys), factory) {} diff --git a/Sources/geos/src/geom/MultiSurface.cpp b/Sources/geos/src/geom/MultiSurface.cpp new file mode 100644 index 0000000..b8e299f --- /dev/null +++ b/Sources/geos/src/geom/MultiSurface.cpp @@ -0,0 +1,109 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * Copyright (C) 2005 2006 Refractions Research Inc. + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include + +namespace geos { +namespace geom { + +MultiSurface::MultiSurface(std::vector>&& newPolys, const GeometryFactory& factory) + : GeometryCollection(std::move(newPolys), factory) +{ + for (const auto& geom : geometries) { + if (!dynamic_cast(geom.get())) { + throw util::IllegalArgumentException("All elements of MultiSurface must be a Surface"); + } + } +} + +MultiSurface::MultiSurface(std::vector>&& newPolys, const GeometryFactory& factory) + : GeometryCollection(std::move(newPolys), factory) +{ +} + +MultiSurface::~MultiSurface() {} + +std::unique_ptr +MultiSurface::getBoundary() const +{ + if (isEmpty()) { + return std::unique_ptr(getFactory()->createMultiCurve()); + } + + std::vector> allRings; + for (const auto& pg : geometries) { + auto g = pg->getBoundary(); + + if (g->getNumGeometries() == 1) { + allRings.push_back(std::move(g)); + } + else { + for (auto& gi : (static_cast(*g)).releaseGeometries()) { + allRings.push_back(std::move(gi)); + } + } + } + + return getFactory()->createMultiCurve(std::move(allRings)); +} + +int +MultiSurface::getBoundaryDimension() const +{ + return 1; +} + +Dimension::DimensionType +MultiSurface::getDimension() const +{ + return Dimension::A; // area +} + +std::string +MultiSurface::getGeometryType() const +{ + return "MultiSurface"; +} + +GeometryTypeId +MultiSurface::getGeometryTypeId() const +{ + return GEOS_MULTISURFACE; +} + +MultiSurface* +MultiSurface::reverseImpl() const +{ + if (isEmpty()) { + return clone().release(); + } + + std::vector> reversed(geometries.size()); + + std::transform(geometries.begin(), + geometries.end(), + reversed.begin(), + [](const std::unique_ptr& g) { + return g->reverse(); + }); + + return getFactory()->createMultiSurface(std::move(reversed)).release(); +} + +} +} diff --git a/Sources/geos/src/geom/Point.cpp b/Sources/geos/src/geom/Point.cpp index 3cadee1..b5c7757 100644 --- a/Sources/geos/src/geom/Point.cpp +++ b/Sources/geos/src/geom/Point.cpp @@ -27,11 +27,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include @@ -39,38 +39,46 @@ namespace geos { namespace geom { // geos::geom -const static FixedSizeCoordinateSequence<0> emptyCoords2d(2); -const static FixedSizeCoordinateSequence<0> emptyCoords3d(3); /*protected*/ -Point::Point(CoordinateSequence* newCoords, const GeometryFactory* factory) +Point::Point(CoordinateSequence&& newCoords, const GeometryFactory* factory) : Geometry(factory) - , empty2d(false) - , empty3d(false) + , coordinates(newCoords) + , envelope(computeEnvelopeInternal()) { - std::unique_ptr coords(newCoords); - - if(coords == nullptr) { - empty2d = true; - return; - } - - if (coords->getSize() == 1) { - coordinates.setAt(coords->getAt(0), 0); - } else if (coords->getSize() > 1) { + if (coordinates.getSize() > 1) { throw util::IllegalArgumentException("Point coordinate list must contain a single element"); - } else if (coords->getDimension() == 3) { - empty3d = true; - } else { - empty2d = true; } } Point::Point(const Coordinate & c, const GeometryFactory* factory) : Geometry(factory) - , empty2d(false) - , empty3d(false) + , coordinates{c} + , envelope(c) +{ +} + +Point::Point(const CoordinateXY & c, const GeometryFactory* factory) + : Geometry(factory) + , coordinates{c} + , envelope(c) +{ +} + +Point::Point(const CoordinateXYM & c, const GeometryFactory* factory) + : Geometry(factory) + , coordinates{c} + , envelope(c) +{ +} + +Point::Point(const CoordinateXYZM & c, const GeometryFactory* factory) + : Geometry(factory) + // check Z and M values because we may be constructing this from + // an XYM coordinate that was stored as XYZM + , coordinates{1u, !std::isnan(c.z), !std::isnan(c.m), false} + , envelope(c) { coordinates.setAt(c, 0); } @@ -79,8 +87,7 @@ Point::Point(const Coordinate & c, const GeometryFactory* factory) Point::Point(const Point& p) : Geometry(p) , coordinates(p.coordinates) - , empty2d(p.empty2d) - , empty3d(p.empty3d) + , envelope(p.envelope) {} std::unique_ptr @@ -92,18 +99,13 @@ Point::getCoordinates() const std::size_t Point::getNumPoints() const { - return isEmpty() ? 0 : 1; + return coordinates.size(); } bool Point::isEmpty() const { - if (empty2d || empty3d) return true; - const Coordinate& c = coordinates.getAt(0); - if (std::isnan(c.x) && std::isnan(c.y)) - return true; - else - return false; + return coordinates.isEmpty(); } bool @@ -124,6 +126,18 @@ Point::getCoordinateDimension() const return (uint8_t) getCoordinatesRO()->getDimension(); } +bool +Point::hasM() const +{ + return getCoordinatesRO()->hasM(); +} + +bool +Point::hasZ() const +{ + return getCoordinatesRO()->hasZ(); +} + int Point::getBoundaryDimension() const { @@ -154,13 +168,16 @@ Point::getZ() const if(isEmpty()) { throw util::UnsupportedOperationException("getZ called on empty Point\n"); } - return getCoordinate()->z; + return coordinates.getOrdinate(0, CoordinateSequence::Z); } -const Coordinate* -Point::getCoordinate() const +double +Point::getM() const { - return isEmpty() ? nullptr : &coordinates[0]; + if(isEmpty()) { + throw util::UnsupportedOperationException("getM called on empty Point\n"); + } + return coordinates.getOrdinate(0, CoordinateSequence::M); } std::string @@ -175,33 +192,25 @@ Point::getBoundary() const return getFactory()->createGeometryCollection(); } -Envelope::Ptr +Envelope Point::computeEnvelopeInternal() const { if(isEmpty()) { - return Envelope::Ptr(new Envelope()); + return Envelope(); } - return Envelope::Ptr(new Envelope(getCoordinate()->x, - getCoordinate()->x, getCoordinate()->y, - getCoordinate()->y)); + return Envelope(*getCoordinate()); } void Point::apply_ro(CoordinateFilter* filter) const { - if(isEmpty()) { - return; - } - filter->filter_ro(getCoordinate()); + coordinates.apply_ro(filter); } void Point::apply_rw(const CoordinateFilter* filter) { - if (isEmpty()) { - return; - } coordinates.apply_rw(filter); } @@ -248,7 +257,6 @@ Point::apply_ro(CoordinateSequenceFilter& filter) const return; } filter.filter_ro(coordinates, 0); - //if (filter.isGeometryChanged()) geometryChanged(); } bool @@ -269,8 +277,8 @@ Point::equalsExact(const Geometry* other, double tolerance) const return false; } - const Coordinate* this_coord = getCoordinate(); - const Coordinate* other_coord = other->getCoordinate(); + const CoordinateXY* this_coord = getCoordinate(); + const CoordinateXY* other_coord = other->getCoordinate(); // assume the isEmpty checks above worked :) assert(this_coord && other_coord); @@ -278,6 +286,17 @@ Point::equalsExact(const Geometry* other, double tolerance) const return equal(*this_coord, *other_coord, tolerance); } +bool +Point::equalsIdentical(const Geometry* other) const +{ + if(!isEquivalentClass(other)) { + return false; + } + + return getCoordinatesRO()->equalsIdentical( + *static_cast(other)->getCoordinatesRO()); +} + int Point::compareToSameClass(const Geometry* g) const { @@ -295,11 +314,6 @@ Point::getGeometryTypeId() const const CoordinateSequence* Point::getCoordinatesRO() const { - if (empty2d) { - return &emptyCoords2d; - } else if (empty3d) { - return &emptyCoords3d; - } return &coordinates; } diff --git a/Sources/geos/src/geom/Polygon.cpp b/Sources/geos/src/geom/Polygon.cpp index 184d841..3163e85 100644 --- a/Sources/geos/src/geom/Polygon.cpp +++ b/Sources/geos/src/geom/Polygon.cpp @@ -28,11 +28,7 @@ #include #include #include -#include -#include -#include -#include -#include +#include #include #include // for fabs @@ -49,171 +45,25 @@ namespace geos { namespace geom { // geos::geom -/*protected*/ -Polygon::Polygon(const Polygon& p) - : - Geometry(p), - shell(detail::make_unique(*p.shell)), - holes(p.holes.size()) -{ - for(std::size_t i = 0; i < holes.size(); ++i) { - holes[i] = detail::make_unique(*p.holes[i]); - } -} - -/*protected*/ -Polygon::Polygon(LinearRing* newShell, std::vector* newHoles, - const GeometryFactory* newFactory): - Geometry(newFactory) -{ - if(newShell == nullptr) { - shell = getFactory()->createLinearRing(); - } - else { - if(newHoles != nullptr && newShell->isEmpty() && hasNonEmptyElements(newHoles)) { - throw util::IllegalArgumentException("shell is empty but holes are not"); - } - shell.reset(newShell); - } - - if(newHoles != nullptr) { - if(hasNullElements(newHoles)) { - throw util::IllegalArgumentException("holes must not contain null elements"); - } - for (const auto& hole : *newHoles) { - holes.emplace_back(hole); - } - delete newHoles; - } -} - -Polygon::Polygon(std::unique_ptr && newShell, - const GeometryFactory& newFactory) : - Geometry(&newFactory), - shell(std::move(newShell)) -{ - if(shell == nullptr) { - shell = getFactory()->createLinearRing(); - } -} - -Polygon::Polygon(std::unique_ptr && newShell, - std::vector> && newHoles, - const GeometryFactory& newFactory) : - Geometry(&newFactory), - shell(std::move(newShell)), - holes(std::move(newHoles)) -{ - if(shell == nullptr) { - shell = getFactory()->createLinearRing(); - } - - // TODO move into validateConstruction() method - if(shell->isEmpty() && hasNonEmptyElements(&holes)) { - throw util::IllegalArgumentException("shell is empty but holes are not"); - } - if (hasNullElements(&holes)) { - throw util::IllegalArgumentException("holes must not contain null elements"); - } -} - - std::unique_ptr Polygon::getCoordinates() const { if(isEmpty()) { - return getFactory()->getCoordinateSequenceFactory()->create(); + return std::make_unique(0u, hasZ(), hasM()); } - std::vector cl; - cl.reserve(getNumPoints()); + auto cl = std::make_unique(0u, hasZ(), hasM()); + cl->reserve(getNumPoints()); // Add shell points - const CoordinateSequence* shellCoords = shell->getCoordinatesRO(); - shellCoords->toVector(cl); + cl->add(*shell->getCoordinatesRO()); // Add holes points for(const auto& hole : holes) { - const CoordinateSequence* childCoords = hole->getCoordinatesRO(); - childCoords->toVector(cl); - } - - return getFactory()->getCoordinateSequenceFactory()->create(std::move(cl)); -} - -size_t -Polygon::getNumPoints() const -{ - std::size_t numPoints = shell->getNumPoints(); - for(const auto& lr : holes) { - numPoints += lr->getNumPoints(); - } - return numPoints; -} - -Dimension::DimensionType -Polygon::getDimension() const -{ - return Dimension::A; // area -} - -uint8_t -Polygon::getCoordinateDimension() const -{ - uint8_t dimension = 2; - - if(shell != nullptr) { - dimension = std::max(dimension, shell->getCoordinateDimension()); - } - - for(const auto& hole : holes) { - dimension = std::max(dimension, hole->getCoordinateDimension()); + cl->add(*hole->getCoordinatesRO()); } - return dimension; -} - -int -Polygon::getBoundaryDimension() const -{ - return 1; -} - -bool -Polygon::isEmpty() const -{ - return shell->isEmpty(); -} - -const LinearRing* -Polygon::getExteriorRing() const -{ - return shell.get(); -} - -std::unique_ptr -Polygon::releaseExteriorRing() -{ - envelope.reset(); - return std::move(shell); -} - -size_t -Polygon::getNumInteriorRing() const -{ - return holes.size(); -} - -const LinearRing* -Polygon::getInteriorRingN(std::size_t n) const -{ - return holes[n].get(); -} - -std::vector> -Polygon::releaseInteriorRings() -{ - return std::move(holes); + return cl; } std::string @@ -255,122 +105,6 @@ Polygon::getBoundary() const return getFactory()->createMultiLineString(std::move(rings)); } -Envelope::Ptr -Polygon::computeEnvelopeInternal() const -{ - return detail::make_unique(*(shell->getEnvelopeInternal())); -} - -bool -Polygon::equalsExact(const Geometry* other, double tolerance) const -{ - if(!isEquivalentClass(other)) { - return false; - } - - const Polygon* otherPolygon = detail::down_cast(other); - if(! otherPolygon) { - return false; - } - - if(!shell->equalsExact(otherPolygon->shell.get(), tolerance)) { - return false; - } - - std::size_t nholes = holes.size(); - - if(nholes != otherPolygon->holes.size()) { - return false; - } - - for(std::size_t i = 0; i < nholes; i++) { - const LinearRing* hole = holes[i].get(); - const LinearRing* otherhole = otherPolygon->holes[i].get(); - if(!hole->equalsExact(otherhole, tolerance)) { - return false; - } - } - - return true; -} - -void -Polygon::apply_ro(CoordinateFilter* filter) const -{ - shell->apply_ro(filter); - for(const auto& lr : holes) { - lr->apply_ro(filter); - } -} - -void -Polygon::apply_rw(const CoordinateFilter* filter) -{ - shell->apply_rw(filter); - for(auto& lr : holes) { - lr->apply_rw(filter); - } -} - -void -Polygon::apply_rw(GeometryFilter* filter) -{ - filter->filter_rw(this); -} - -void -Polygon::apply_ro(GeometryFilter* filter) const -{ - filter->filter_ro(this); -} - -std::unique_ptr -Polygon::convexHull() const -{ - return getExteriorRing()->convexHull(); -} - - -void -Polygon::normalize() -{ - normalize(shell.get(), true); - for(auto& lr : holes) { - normalize(lr.get(), false); - } - std::sort(holes.begin(), holes.end(), [](const std::unique_ptr & a, const std::unique_ptr & b) { - return a->compareTo(b.get()) > 0; - }); -} - -int -Polygon::compareToSameClass(const Geometry* g) const -{ - const Polygon* p = detail::down_cast(g); - int shellComp = shell->compareToSameClass(p->shell.get()); - if (shellComp != 0) { - return shellComp; - } - - size_t nHole1 = getNumInteriorRing(); - size_t nHole2 = p->getNumInteriorRing(); - if (nHole1 < nHole2) { - return -1; - } - if (nHole1 > nHole2) { - return 1; - } - - for (size_t i=0; i < nHole1; i++) { - const LinearRing *lr = p->getInteriorRingN(i); - const int holeComp = getInteriorRingN(i)->compareToSameClass(lr); - if (holeComp != 0) { - return holeComp; - } - } - - return 0; -} /* * TODO: check this function, there should be CoordinateSequence copy @@ -383,27 +117,24 @@ Polygon::normalize(LinearRing* ring, bool clockwise) return; } - auto coords = detail::make_unique>(); - ring->getCoordinatesRO()->toVector(*coords); - coords->erase(coords->end() - 1); // remove last point (repeated) + const auto& ringCoords = ring->getCoordinatesRO(); + CoordinateSequence coords(0u, ringCoords->hasZ(), ringCoords->hasM()); + coords.reserve(ringCoords->size()); + + // exclude last point (repeated) + coords.add(*ringCoords, 0, ringCoords->size() - 2); - auto uniqueCoordinates = detail::make_unique(coords.release()); + const CoordinateXY* minCoordinate = coords.minCoordinate(); - const Coordinate* minCoordinate = uniqueCoordinates->minCoordinate(); + CoordinateSequence::scroll(&coords, minCoordinate); + coords.closeRing(); - CoordinateSequence::scroll(uniqueCoordinates.get(), minCoordinate); - uniqueCoordinates->add(uniqueCoordinates->getAt(0)); - if(algorithm::Orientation::isCCW(uniqueCoordinates.get()) == clockwise) { - CoordinateSequence::reverse(uniqueCoordinates.get()); + if(algorithm::Orientation::isCCW(&coords) == clockwise) { + coords.reverse(); } - ring->setPoints(uniqueCoordinates.get()); + ring->setPoints(&coords); } -const Coordinate* -Polygon::getCoordinate() const -{ - return shell->getCoordinate(); -} /* * Returns the area of this Polygon @@ -422,76 +153,6 @@ Polygon::getArea() const return area; } -/** - * Returns the perimeter of this Polygon - * - * @return the perimeter of the polygon - */ -double -Polygon::getLength() const -{ - double len = 0.0; - len += shell->getLength(); - for(const auto& hole : holes) { - len += hole->getLength(); - } - return len; -} - -void -Polygon::apply_ro(GeometryComponentFilter* filter) const -{ - filter->filter_ro(this); - shell->apply_ro(filter); - for(std::size_t i = 0, n = holes.size(); i < n && !filter->isDone(); ++i) { - holes[i]->apply_ro(filter); - } -} - -void -Polygon::apply_rw(GeometryComponentFilter* filter) -{ - filter->filter_rw(this); - shell->apply_rw(filter); - for(std::size_t i = 0, n = holes.size(); i < n && !filter->isDone(); ++i) { - holes[i]->apply_rw(filter); - } -} - -void -Polygon::apply_rw(CoordinateSequenceFilter& filter) -{ - shell->apply_rw(filter); - - if(! filter.isDone()) { - for(std::size_t i = 0, n = holes.size(); i < n; ++i) { - holes[i]->apply_rw(filter); - if(filter.isDone()) { - break; - } - } - } - if(filter.isGeometryChanged()) { - geometryChanged(); - } -} - -void -Polygon::apply_ro(CoordinateSequenceFilter& filter) const -{ - shell->apply_ro(filter); - - if(! filter.isDone()) { - for(std::size_t i = 0, n = holes.size(); i < n; ++i) { - holes[i]->apply_ro(filter); - if(filter.isDone()) { - break; - } - } - } - //if (filter.isGeometryChanged()) geometryChanged(); -} - GeometryTypeId Polygon::getGeometryTypeId() const { @@ -541,6 +202,16 @@ Polygon::isRectangle() const return true; } +void +Polygon::orientRings(bool exteriorCW) +{ + shell->orient(exteriorCW); + for (auto& hole : holes) { + hole->orient(!exteriorCW); + } +} + + Polygon* Polygon::reverseImpl() const { @@ -560,5 +231,17 @@ Polygon::reverseImpl() const return getFactory()->createPolygon(shell->reverse(), std::move(interiorRingsReversed)).release(); } +void +Polygon::normalize() +{ + normalize(shell.get(), true); + for(auto& lr : holes) { + normalize(lr.get(), false); + } + std::sort(holes.begin(), holes.end(), [](const auto& a, const auto& b) { + return a->compareTo(b.get()) > 0; + }); +} + } // namespace geos::geom } // namespace geos diff --git a/Sources/geos/src/geom/PrecisionModel.cpp b/Sources/geos/src/geom/PrecisionModel.cpp index 71af03a..25d4085 100644 --- a/Sources/geos/src/geom/PrecisionModel.cpp +++ b/Sources/geos/src/geom/PrecisionModel.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #ifndef GEOS_DEBUG #define GEOS_DEBUG 0 @@ -55,10 +56,17 @@ PrecisionModel::makePrecise(double val) const return static_cast(floatSingleVal); } if(modelType == FIXED) { - if (gridSize > 0) { + //-- make arithmetic robust by using integral value if available + if (gridSize > 1) { +//double v2 = util::round(val / gridSize) * gridSize; +//std::cout << std::setprecision(16) << "GS[" << gridSize << "] " << val << " -> " << v2 << std::endl; return util::round(val / gridSize) * gridSize; } - else { + //-- since grid size is <= 1, scale must be >= 1 OR 0 + //-- if scale == 0, this is a no-op (should never happen) + else if (scale != 0.0) { +//double v2 = util::round(val * scale) / scale; +//std::cout << std::setprecision(16) << "SC[" << scale << "] " << val << " -> " << "SC " << v2 << std::endl; return util::round(val * scale) / scale; } } @@ -85,7 +93,7 @@ PrecisionModel::PrecisionModel(Type nModelType) : modelType(nModelType), scale(1.0), - gridSize(0.0) + gridSize(1.0) { #if GEOS_DEBUG std::cerr << "PrecisionModel[" << this << "] ctor(Type)" << std::endl; @@ -153,25 +161,48 @@ PrecisionModel::getMaximumSignificantDigits() const return maxSigDigits; } +//-- this value is not critical, since most common usage should be VERY close to integral +const double GRIDSIZE_INTEGER_TOLERANCE = 1e-5; + /*private*/ void PrecisionModel::setScale(double newScale) { + //-- should never happen, but make this a no-op in case + if (newScale == 0) { + scale = 0.0; + gridSize = 0.0; + } /** * A negative scale indicates the grid size is being set. * The scale is set as well, as the reciprocal. + * NOTE: may not need to support negative grid size now due to robust arithmetic */ if (newScale < 0) { - gridSize = std::fabs(newScale); - scale = 1.0 / gridSize; + scale = 1.0 / std::fabs(newScale); } else { - scale = std::fabs(newScale); - /** - * Leave gridSize as 0, to ensure it is computed using scale - */ - gridSize = 0.0; + scale = newScale; + } + //-- snap nearly integral scale or gridsize to exact integer + //-- this handles the most common case of fractional powers of ten + if (scale < 1) { + gridSize = snapToInt(1.0 / scale, GRIDSIZE_INTEGER_TOLERANCE); } + else { + scale = snapToInt( scale, GRIDSIZE_INTEGER_TOLERANCE); + gridSize = 1.0 / scale; + } +} + +/*private*/ +double +PrecisionModel::snapToInt(double val, double tolerance) { + double valInt = std::round(val); + if (std::abs(val - valInt) < tolerance) { + return valInt; + } + return val; } /*public*/ diff --git a/Sources/geos/src/geom/SimpleCurve.cpp b/Sources/geos/src/geom/SimpleCurve.cpp new file mode 100644 index 0000000..be9b50b --- /dev/null +++ b/Sources/geos/src/geom/SimpleCurve.cpp @@ -0,0 +1,371 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * Copyright (C) 2005 2006 Refractions Research Inc. + * Copyright (C) 2011 Sandro Santilli + * Copyright (C) 2024 ISciences, LLC + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace geos { +namespace geom { + +SimpleCurve::SimpleCurve(const SimpleCurve& other) + : Curve(other), + points(other.points->clone()), + envelope(other.envelope) +{ +} + +SimpleCurve::SimpleCurve(std::unique_ptr&& newCoords, + bool isLinear, + const GeometryFactory& factory) + : Curve(factory), + points(newCoords ? std::move(newCoords) : std::make_unique()), + envelope(computeEnvelopeInternal(isLinear)) +{ +} + +void +SimpleCurve::apply_ro(CoordinateFilter* filter) const +{ + assert(points.get()); + points->apply_ro(filter); +} + +void +SimpleCurve::apply_ro(CoordinateSequenceFilter& filter) const +{ + std::size_t npts = points->size(); + if (!npts) { + return; + } + for (std::size_t i = 0; i < npts; ++i) { + filter.filter_ro(*points, i); + if (filter.isDone()) { + break; + } + } +} + +void +SimpleCurve::apply_rw(const CoordinateFilter* filter) +{ + assert(points.get()); + points->apply_rw(filter); +} + +void +SimpleCurve::apply_rw(CoordinateSequenceFilter& filter) +{ + std::size_t npts = points->size(); + if (!npts) { + return; + } + for (std::size_t i = 0; i < npts; ++i) { + filter.filter_rw(*points, i); + if (filter.isDone()) { + break; + } + } + if (filter.isGeometryChanged()) { + geometryChanged(); + } +} + +int +SimpleCurve::compareToSameClass(const Geometry* ls) const +{ + const SimpleCurve* line = detail::down_cast(ls); + + // MD - optimized implementation + std::size_t mynpts = points->getSize(); + std::size_t othnpts = line->points->getSize(); + if (mynpts > othnpts) { + return 1; + } + if (mynpts < othnpts) { + return -1; + } + for (std::size_t i = 0; i < mynpts; i++) { + int cmp = points->getAt(i).compareTo(line->points->getAt(i)); + if (cmp) { + return cmp; + } + } + return 0; +} + +Envelope +SimpleCurve::computeEnvelopeInternal(bool isLinear) const +{ + if (isEmpty()) { + return Envelope(); + } + + if (isLinear) { + return points->getEnvelope(); + } + else { + Envelope e; + for (std::size_t i = 2; i < points->size(); i++) { + algorithm::CircularArcs::expandEnvelope(e, + points->getAt(i-2), + points->getAt(i-1), + points->getAt(i)); + } + return e; + } +} + +bool +SimpleCurve::equalsExact(const Geometry* other, double tolerance) const +{ + if (!isEquivalentClass(other)) { + return false; + } + + const SimpleCurve* otherCurve = detail::down_cast(other); + std::size_t npts = points->getSize(); + if (npts != otherCurve->points->getSize()) { + return false; + } + for (std::size_t i = 0; i < npts; ++i) { + if (!equal(points->getAt(i), otherCurve->points->getAt(i), tolerance)) { + return false; + } + } + return true; +} + +bool +SimpleCurve::equalsIdentical(const Geometry* other_g) const +{ + if (!isEquivalentClass(other_g)) { + return false; + } + + const auto& other = static_cast(*other_g); + + if (envelope != other.envelope) { + return false; + } + + return getCoordinatesRO()->equalsIdentical(*other.getCoordinatesRO()); +} + +std::unique_ptr +SimpleCurve::getBoundary() const +{ + operation::BoundaryOp bop(*this); + return bop.getBoundary(); +} + +const CoordinateXY* +SimpleCurve::getCoordinate() const +{ + if (isEmpty()) { + return nullptr; + } + return &(points->getAt(0)); +} + +uint8_t +SimpleCurve::getCoordinateDimension() const +{ + return (uint8_t) points->getDimension(); +} + +const Coordinate& +SimpleCurve::getCoordinateN(std::size_t n) const +{ + assert(points.get()); + return points->getAt(n); +} + +std::unique_ptr +SimpleCurve::getCoordinates() const +{ + assert(points.get()); + return points->clone(); +} + +const CoordinateSequence* +SimpleCurve::getCoordinatesRO() const +{ + assert(nullptr != points.get()); + return points.get(); +} + +const SimpleCurve* +SimpleCurve::getCurveN(std::size_t) const +{ + return this; +} + +std::unique_ptr +SimpleCurve::getEndPoint() const +{ + if (isEmpty()) { + return nullptr; + } + return getPointN(getNumPoints() - 1); +} + +std::size_t +SimpleCurve::getNumCurves() const +{ + return 1; +} + +std::size_t +SimpleCurve::getNumPoints() const +{ + assert(points.get()); + return points->getSize(); +} + +std::unique_ptr +SimpleCurve::getPointN(std::size_t n) const +{ + assert(getFactory()); + assert(points.get()); + return std::unique_ptr(getFactory()->createPoint(points->getAt(n))); +} + +std::unique_ptr +SimpleCurve::getStartPoint() const +{ + if (isEmpty()) { + return nullptr; + } + return getPointN(0); +} + +bool +SimpleCurve::hasM() const +{ + return points->hasM(); +} + +bool +SimpleCurve::hasZ() const +{ + return points->hasZ(); +} + +bool +SimpleCurve::isClosed() const +{ + if (isEmpty()) { + return false; + } + + return points->front().equals2D(points->back()); +} + +bool +SimpleCurve::isCoordinate(CoordinateXY& pt) const +{ + assert(points.get()); + std::size_t npts = points->getSize(); + for (std::size_t i = 0; i < npts; i++) { + if (points->getAt(i) == pt) { + return true; + } + } + return false; +} + +bool +SimpleCurve::isEmpty() const +{ + assert(points.get()); + return points->isEmpty(); +} + +/*public*/ +void +SimpleCurve::normalize() +{ + util::ensureNoCurvedComponents(*this); + + if (isEmpty()) return; + assert(points.get()); + if (isClosed()) { + normalizeClosed(); + return; + } + std::size_t npts = points->getSize(); + std::size_t n = npts / 2; + for (std::size_t i = 0; i < n; i++) { + std::size_t j = npts - 1 - i; + if (!(points->getAt(i) == points->getAt(j))) { + if (points->getAt(i).compareTo(points->getAt(j)) > 0) { + points->reverse(); + } + return; + } + } +} + +/*private*/ +void +SimpleCurve::normalizeClosed() +{ + if (isEmpty()) { + return; + } + + const auto& ringCoords = getCoordinatesRO(); + + auto coords = detail::make_unique(0u, ringCoords->hasZ(), ringCoords->hasM()); + coords->reserve(ringCoords->size()); + // exclude last point (repeated) + coords->add(*ringCoords, 0, ringCoords->size() - 2); + + const CoordinateXY* minCoordinate = coords->minCoordinate(); + + CoordinateSequence::scroll(coords.get(), minCoordinate); + coords->closeRing(true); + + if (coords->size() >= 4 && algorithm::Orientation::isCCW(coords.get())) { + coords->reverse(); + } + + points = std::move(coords); +} + +std::unique_ptr +SimpleCurve::releaseCoordinates() +{ + auto newPts = std::make_unique(0u, points->hasZ(), points->hasM()); + auto ret = std::move(points); + points = std::move(newPts); + geometryChanged(); + return ret; +} + +} // namespace geos::geom +} // namespace geos diff --git a/Sources/geos/src/geom/Surface.cpp b/Sources/geos/src/geom/Surface.cpp new file mode 100644 index 0000000..34bb5c6 --- /dev/null +++ b/Sources/geos/src/geom/Surface.cpp @@ -0,0 +1,286 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2024 ISciences LLC + * Copyright (C) 2011 Sandro Santilli + * Copyright (C) 2005-2006 Refractions Research Inc. + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + ********************************************************************** + * + * Last port: geom/Polygon.java r320 (JTS-1.12) + * + **********************************************************************/ + +#include +#include +#include +#include + +namespace geos { +namespace geom { + +void +Surface::apply_ro(CoordinateFilter* filter) const +{ + getExteriorRing()->apply_ro(filter); + for (std::size_t i = 0; i < getNumInteriorRing(); i++) { + getInteriorRingN(i)->apply_ro(filter); + } +} + +void +Surface::apply_ro(CoordinateSequenceFilter& filter) const +{ + getExteriorRing()->apply_ro(filter); + + for (std::size_t i = 0; !filter.isDone() && i < getNumInteriorRing(); i++) { + getInteriorRingN(i)->apply_ro(filter); + } +} + +void +Surface::apply_ro(GeometryComponentFilter* filter) const +{ + filter->filter_ro(this); + getExteriorRing()->apply_ro(filter); + for (std::size_t i = 0; !filter->isDone() && i < getNumInteriorRing(); i++) { + getInteriorRingN(i)->apply_ro(filter); + } +} + +void +Surface::apply_ro(GeometryFilter* filter) const +{ + filter->filter_ro(this); +} + +void +Surface::apply_rw(const CoordinateFilter* filter) +{ + getExteriorRing()->apply_rw(filter); + for (std::size_t i = 0; i < getNumInteriorRing(); i++) { + getInteriorRingN(i)->apply_rw(filter); + } +} + +void +Surface::apply_rw(CoordinateSequenceFilter& filter) +{ + getExteriorRing()->apply_rw(filter); + + for (std::size_t i = 0; !filter.isDone() && i < getNumInteriorRing(); i++) { + getInteriorRingN(i)->apply_rw(filter); + } + + if (filter.isGeometryChanged()) { + geometryChanged(); + } +} + +void +Surface::apply_rw(GeometryComponentFilter* filter) +{ + filter->filter_rw(this); + getExteriorRing()->apply_rw(filter); + for (std::size_t i = 0; !filter->isDone() && i < getNumInteriorRing(); i++) { + getInteriorRingN(i)->apply_rw(filter); + } +} + +void +Surface::apply_rw(GeometryFilter* filter) +{ + filter->filter_rw(this); +} + +int +Surface::compareToSameClass(const Geometry* g) const +{ + const Surface* p = detail::down_cast(g); + int shellComp = getExteriorRing()->compareTo(p->getExteriorRing()); + if (shellComp != 0) { + return shellComp; + } + + size_t nHole1 = getNumInteriorRing(); + size_t nHole2 = p->getNumInteriorRing(); + if (nHole1 < nHole2) { + return -1; + } + if (nHole1 > nHole2) { + return 1; + } + + for (size_t i=0; i < nHole1; i++) { + const Curve* lr = p->getInteriorRingN(i); + const int holeComp = getInteriorRingN(i)->compareTo(lr); + if (holeComp != 0) { + return holeComp; + } + } + + return 0; +} + +std::unique_ptr +Surface::convexHull() const +{ + return getExteriorRing()->convexHull(); +} + +std::unique_ptr +Surface::createEmptyRing(const GeometryFactory& factory) +{ + return factory.createLinearRing(); +} + +bool +Surface::equalsExact(const Geometry* other, double tolerance) const +{ + if (!isEquivalentClass(other)) { + return false; + } + + const Surface* otherPolygon = detail::down_cast(other); + if (! otherPolygon) { + return false; + } + + if (!getExteriorRing()->equalsExact(otherPolygon->getExteriorRing(), tolerance)) { + return false; + } + + if (getNumInteriorRing() != otherPolygon->getNumInteriorRing()) { + return false; + } + + for (std::size_t i = 0; i < getNumInteriorRing(); i++) { + const Curve* hole = getInteriorRingN(i); + const Curve* otherhole = otherPolygon->getInteriorRingN(i); + if (!hole->equalsExact(otherhole, tolerance)) { + return false; + } + } + + return true; +} + +bool +Surface::equalsIdentical(const Geometry* other_g) const +{ + if (!isEquivalentClass(other_g)) { + return false; + } + + const auto& other = static_cast(*other_g); + + if (getNumInteriorRing() != other.getNumInteriorRing()) { + return false; + } + + if (!getExteriorRing()->equalsIdentical(other.getExteriorRing())) { + return false; + } + + for (std::size_t i = 0; i < getNumInteriorRing(); i++) { + if (!getInteriorRingN(i)->equalsIdentical(other.getInteriorRingN(i))) { + return false; + } + } + + return true; +} + +const CoordinateXY* +Surface::getCoordinate() const +{ + return getExteriorRing()->getCoordinate(); +} + +uint8_t +Surface::getCoordinateDimension() const +{ + uint8_t dimension = 2; + + if (getExteriorRing() != nullptr) { + dimension = std::max(dimension, getExteriorRing()->getCoordinateDimension()); + } + + for (std::size_t i = 0; i < getNumInteriorRing(); i++) { + dimension = std::max(dimension, getInteriorRingN(i)->getCoordinateDimension()); + } + + return dimension; +} + +const Envelope* +Surface::getEnvelopeInternal() const +{ + return getExteriorRing()->getEnvelopeInternal(); +} + +double +Surface::getLength() const +{ + double len = 0.0; + len += getExteriorRing()->getLength(); + for (std::size_t i = 0; i < getNumInteriorRing(); i++) { + len += getInteriorRingN(i)->getLength(); + } + return len; +} + +size_t +Surface::getNumPoints() const +{ + std::size_t numPoints = getExteriorRing()->getNumPoints(); + for (std::size_t i = 0; i < getNumInteriorRing(); i++) { + numPoints += getInteriorRingN(i)->getNumPoints(); + } + return numPoints; +} + +bool +Surface::hasM() const +{ + if (getExteriorRing()->hasM()) { + return true; + } + for (std::size_t i = 0 ; i < getNumInteriorRing(); i++) { + if (getInteriorRingN(i)->hasM()) { + return true; + } + } + return false; +} + +bool +Surface::hasZ() const +{ + if (getExteriorRing()->hasZ()) { + return true; + } + for (std::size_t i = 0 ; i < getNumInteriorRing(); i++) { + if (getInteriorRingN(i)->hasZ()) { + return true; + } + } + return false; +} + +bool +Surface::isEmpty() const +{ + return getExteriorRing()->isEmpty(); +} + +} +} diff --git a/Sources/geos/src/geom/Triangle.cpp b/Sources/geos/src/geom/Triangle.cpp index f33f97b..90dad34 100644 --- a/Sources/geos/src/geom/Triangle.cpp +++ b/Sources/geos/src/geom/Triangle.cpp @@ -38,7 +38,7 @@ Triangle::isIsoceles() } void -Triangle::inCentre(Coordinate& result) +Triangle::inCentre(CoordinateXY& result) { // the lengths of the sides, labelled by their opposite vertex double len0 = p1.distance(p2); @@ -48,11 +48,11 @@ Triangle::inCentre(Coordinate& result) double inCentreX = (len0 * p0.x + len1 * p1.x + len2 * p2.x) / circum; double inCentreY = (len0 * p0.y + len1 * p1.y + len2 * p2.y) / circum; - result = Coordinate(inCentreX, inCentreY); + result = CoordinateXY(inCentreX, inCentreY); } void -Triangle::circumcentre(Coordinate& result) +Triangle::circumcentre(CoordinateXY& result) { double cx = p2.x; double cy = p2.y; @@ -68,21 +68,39 @@ Triangle::circumcentre(Coordinate& result) double ccx = cx - numx / denom; double ccy = cy + numy / denom; - result = Coordinate(ccx, ccy); + result = CoordinateXY(ccx, ccy); } + +double +Triangle::circumradius( + const CoordinateXY& a, + const CoordinateXY& b, + const CoordinateXY& c) +{ + double A = a.distance(b); + double B = b.distance(c); + double C = c.distance(a); + double triArea = area(a, b, c); + if (triArea == 0.0) + return std::numeric_limits::infinity(); + + return (A * B * C) / (4 * triArea); +} + + void -Triangle::circumcentreDD(Coordinate& result) +Triangle::circumcentreDD(CoordinateXY& result) { result = algorithm::CGAlgorithmsDD::circumcentreDD(p0, p1, p2); } /* public static */ -const Coordinate -Triangle::circumcentre(const Coordinate& p0, const Coordinate& p1, const Coordinate& p2) +const CoordinateXY +Triangle::circumcentre(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2) { Triangle t(p0, p1, p2); - Coordinate c; + CoordinateXY c; t.circumcentre(c); return c; } @@ -97,7 +115,7 @@ Triangle::det(double m00, double m01, double m10, double m11) const /* public static */ bool -Triangle::isAcute(const Coordinate& a, const Coordinate& b, const Coordinate& c) +Triangle::isAcute(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c) { if (!Angle::isAcute(a, b, c)) return false; @@ -111,7 +129,7 @@ Triangle::isAcute(const Coordinate& a, const Coordinate& b, const Coordinate& c) /* public static */ bool -Triangle::isCCW(const Coordinate& a, const Coordinate& b, const Coordinate& c) +Triangle::isCCW(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c) { return Orientation::COUNTERCLOCKWISE == Orientation::index(a, b, c); } @@ -119,7 +137,7 @@ Triangle::isCCW(const Coordinate& a, const Coordinate& b, const Coordinate& c) /* public static */ bool -Triangle::intersects(const Coordinate& a, const Coordinate& b, const Coordinate& c, const Coordinate& p) +Triangle::intersects(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c, const CoordinateXY& p) { int exteriorIndex = isCCW(a, b, c) ? Orientation::CLOCKWISE : Orientation::COUNTERCLOCKWISE; @@ -135,7 +153,7 @@ Triangle::intersects(const Coordinate& a, const Coordinate& b, const Coordinate& /* public static */ double -Triangle::length(const Coordinate& a, const Coordinate& b, const Coordinate& c) +Triangle::length(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c) { return a.distance(b) + b.distance(c) + c.distance(a); } @@ -149,7 +167,7 @@ Triangle::length() const /* public static */ double -Triangle::area(const Coordinate& a, const Coordinate& b, const Coordinate& c) +Triangle::area(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c) { return std::abs(((c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y)) / 2); } @@ -163,7 +181,7 @@ Triangle::area() const /* public static */ double -Triangle::longestSideLength(const Coordinate& a, const Coordinate& b, const Coordinate& c) +Triangle::longestSideLength(const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c) { double lenAB = a.distance(b); double lenBC = b.distance(c); diff --git a/Sources/geos/src/geom/prep/AbstractPreparedPolygonContains.cpp b/Sources/geos/src/geom/prep/AbstractPreparedPolygonContains.cpp index cfc8743..f0dbdea 100644 --- a/Sources/geos/src/geom/prep/AbstractPreparedPolygonContains.cpp +++ b/Sources/geos/src/geom/prep/AbstractPreparedPolygonContains.cpp @@ -186,7 +186,6 @@ bool AbstractPreparedPolygonContains::evalPointTestGeom(const Geometry *geom, Lo if (outermostLoc == Location::EXTERIOR) { return false; } - // For the Covers predicate, we can return true // since no Points are on the exterior of the target // geometry. @@ -195,19 +194,18 @@ bool AbstractPreparedPolygonContains::evalPointTestGeom(const Geometry *geom, Lo } // For the Contains predicate, we need to test if any - // of those points lie in the interior of the target + // of the test points lie in the interior of the target // geometry. if (outermostLoc == Location::INTERIOR) { return true; } - if (geom->getNumGeometries() > 1) { - // for MultiPoint, try to find at least one point - // in interior - return isAnyTestComponentInTargetInterior(geom); + // a single point must not be in interior + if (geom->getNumPoints() <= 1) { + return false; } - - return false; + // for multiple points have to check all + return isAnyTestComponentInTargetInterior(geom); } // diff --git a/Sources/geos/src/geom/prep/BasicPreparedGeometry.cpp b/Sources/geos/src/geom/prep/BasicPreparedGeometry.cpp index 218d4ae..0718a37 100644 --- a/Sources/geos/src/geom/prep/BasicPreparedGeometry.cpp +++ b/Sources/geos/src/geom/prep/BasicPreparedGeometry.cpp @@ -19,10 +19,13 @@ #include #include +#include #include #include #include +#include "geos/util.h" + namespace geos { namespace geom { // geos.geom namespace prep { // geos.geom.prep @@ -79,83 +82,87 @@ BasicPreparedGeometry::isAnyTargetComponentInTest(const geom::Geometry* testGeom { algorithm::PointLocator locator; - for(std::size_t i = 0, n = representativePts.size(); i < n; i++) { - const geom::Coordinate& c = *(representativePts[i]); - if(locator.intersects(c, testGeom)) { + for(const auto& c : representativePts) { + if(locator.intersects(*c, testGeom)) { return true; } } return false; } +bool +BasicPreparedGeometry::within(const geom::Geometry* g) const +{ + return getRelateNG().within(g); +} + bool BasicPreparedGeometry::contains(const geom::Geometry* g) const { - return baseGeom->contains(g); + return getRelateNG().contains(g); } bool BasicPreparedGeometry::containsProperly(const geom::Geometry* g) const { - // since raw relate is used, provide some optimizations - - // short-circuit test - if(! baseGeom->getEnvelopeInternal()->contains(g->getEnvelopeInternal())) { - return false; - } - - // otherwise, compute using relate mask - return baseGeom->relate(g, "T**FF*FF*"); + return getRelateNG().relate(g, "T**FF*FF*"); } bool BasicPreparedGeometry::coveredBy(const geom::Geometry* g) const { - return baseGeom->coveredBy(g); + return getRelateNG().coveredBy(g); } bool BasicPreparedGeometry::covers(const geom::Geometry* g) const { - return baseGeom->covers(g); + return getRelateNG().covers(g); } bool BasicPreparedGeometry::crosses(const geom::Geometry* g) const { - return baseGeom->crosses(g); + return getRelateNG().crosses(g); } bool BasicPreparedGeometry::disjoint(const geom::Geometry* g) const { - return ! intersects(g); + return getRelateNG().disjoint(g); } bool BasicPreparedGeometry::intersects(const geom::Geometry* g) const { - return baseGeom->intersects(g); + return getRelateNG().intersects(g); } bool BasicPreparedGeometry::overlaps(const geom::Geometry* g) const { - return baseGeom->overlaps(g); + return getRelateNG().overlaps(g); } bool BasicPreparedGeometry::touches(const geom::Geometry* g) const { - return baseGeom->touches(g); + return getRelateNG().touches(g); } bool -BasicPreparedGeometry::within(const geom::Geometry* g) const +BasicPreparedGeometry::relate(const geom::Geometry* g, const std::string& pat) const { - return baseGeom->within(g); + return getRelateNG().relate(g, pat); } +std::unique_ptr +BasicPreparedGeometry::relate(const geom::Geometry* g) const +{ + return getRelateNG().relate(g); +} + + std::unique_ptr BasicPreparedGeometry::nearestPoints(const geom::Geometry* g) const { diff --git a/Sources/geos/src/geom/prep/PreparedGeometryFactory.cpp b/Sources/geos/src/geom/prep/PreparedGeometryFactory.cpp index 899eff9..8445969 100644 --- a/Sources/geos/src/geom/prep/PreparedGeometryFactory.cpp +++ b/Sources/geos/src/geom/prep/PreparedGeometryFactory.cpp @@ -45,6 +45,8 @@ PreparedGeometryFactory::create(const geom::Geometry* g) const throw util::IllegalArgumentException("PreparedGeometry constructed with null Geometry object"); } + util::ensureNoCurvedComponents(g); + std::unique_ptr pg; switch(g->getGeometryTypeId()) { diff --git a/Sources/geos/src/geom/prep/PreparedLineString.cpp b/Sources/geos/src/geom/prep/PreparedLineString.cpp index 0a936d6..4ae7f0a 100644 --- a/Sources/geos/src/geom/prep/PreparedLineString.cpp +++ b/Sources/geos/src/geom/prep/PreparedLineString.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -58,6 +57,8 @@ PreparedLineString::getIntersectionFinder() bool PreparedLineString::intersects(const geom::Geometry* g) const { + geos::util::ensureNoCurvedComponents(g); + if(! envelopesIntersect(g)) { return false; } @@ -91,6 +92,12 @@ PreparedLineString::distance(const geom::Geometry* g) const return PreparedLineStringDistance::distance(*this, g); } +bool +PreparedLineString::isWithinDistance(const geom::Geometry* g, double d) const +{ + return PreparedLineStringDistance(*this).isWithinDistance(g, d); +} + } // namespace geos.geom.prep } // namespace geos.geom diff --git a/Sources/geos/src/geom/prep/PreparedLineStringDistance.cpp b/Sources/geos/src/geom/prep/PreparedLineStringDistance.cpp index 9d907a6..5bb2b45 100644 --- a/Sources/geos/src/geom/prep/PreparedLineStringDistance.cpp +++ b/Sources/geos/src/geom/prep/PreparedLineStringDistance.cpp @@ -32,12 +32,39 @@ PreparedLineStringDistance::distance(const geom::Geometry* g) const return DoubleInfinity; } - // TODO: test if this shortcut be any useful - //if ( prepLine.intersects(g) ) return 0.0; + /* Compute potential distance from facets */ + operation::distance::IndexedFacetDistance *idf = prepLine.getIndexedFacetDistance(); + double dist = idf->distance(g); + if (dist == 0.0) + return 0.0; + + // If any point from prepLine is contained by g, the distance is zero + // Do this last because this PIP test is not indexed. + if ( g->getDimension() == 2 + && prepLine.isAnyTargetComponentInTest(g)) { + return 0.0; + } + return dist; +} + +bool +PreparedLineStringDistance::isWithinDistance(const geom::Geometry* g, double d) const +{ + if ( prepLine.getGeometry().isEmpty() || g->isEmpty() ) + { + return false; + } - /* Not intersecting, compute distance from facets */ operation::distance::IndexedFacetDistance *idf = prepLine.getIndexedFacetDistance(); - return idf->distance(g); + if (idf->isWithinDistance(g, d)) + return true; + + // If any point from prepLine is contained by g, the distance is zero + // Do this last because this PIP test is not indexed. + if ( g->getDimension() == 2 ) { + return prepLine.isAnyTargetComponentInTest(g); + } + return false; } diff --git a/Sources/geos/src/geom/prep/PreparedLineStringIntersects.cpp b/Sources/geos/src/geom/prep/PreparedLineStringIntersects.cpp index 810bb5f..c4fc2b5 100644 --- a/Sources/geos/src/geom/prep/PreparedLineStringIntersects.cpp +++ b/Sources/geos/src/geom/prep/PreparedLineStringIntersects.cpp @@ -41,12 +41,11 @@ PreparedLineStringIntersects::isAnyTestPointInTarget(const geom::Geometry* testG */ PointLocator locator; - geom::Coordinate::ConstVect coords; + std::vector coords; ComponentCoordinateExtracter::getCoordinates(*testGeom, coords); - for(std::size_t i = 0, n = coords.size(); i < n; i++) { - const geom::Coordinate& c = *(coords[i]); - if(locator.intersects(c, &(prepLine.getGeometry()))) { + for(const auto& c : coords) { + if(locator.intersects(*c, &(prepLine.getGeometry()))) { return true; } } @@ -70,11 +69,6 @@ PreparedLineStringIntersects::intersects(const geom::Geometry* g) const return true; } - // For L/L case we are done - if(g->getDimension() == 1) { - return false; - } - // For L/A case, need to check for proper inclusion of the target in the test if(g->getDimension() == 2 && prepLine.isAnyTargetComponentInTest(g)) { @@ -82,7 +76,7 @@ PreparedLineStringIntersects::intersects(const geom::Geometry* g) const } // For L/P case, need to check if any points lie on line(s) - if(g->getDimension() == 0) { + if(g->hasDimension(Dimension::P)) { return isAnyTestPointInTarget(g); } diff --git a/Sources/geos/src/geom/prep/PreparedLineStringNearestPoints.cpp b/Sources/geos/src/geom/prep/PreparedLineStringNearestPoints.cpp index 227ae8d..566b98d 100644 --- a/Sources/geos/src/geom/prep/PreparedLineStringNearestPoints.cpp +++ b/Sources/geos/src/geom/prep/PreparedLineStringNearestPoints.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include namespace geos { @@ -30,10 +29,8 @@ namespace prep { // geos.geom.prep std::unique_ptr PreparedLineStringNearestPoints::nearestPoints(const geom::Geometry* g) const { - const GeometryFactory *gf = prepLine.getGeometry().getFactory(); - const CoordinateSequenceFactory *cf = gf->getCoordinateSequenceFactory(); operation::distance::IndexedFacetDistance *idf = prepLine.getIndexedFacetDistance(); - return cf->create(idf->nearestPoints(g)); + return idf->nearestPoints(g); } } // namespace geos.geom.prep diff --git a/Sources/geos/src/geom/prep/PreparedPoint.cpp b/Sources/geos/src/geom/prep/PreparedPoint.cpp index 8fa3f94..5ddef66 100644 --- a/Sources/geos/src/geom/prep/PreparedPoint.cpp +++ b/Sources/geos/src/geom/prep/PreparedPoint.cpp @@ -17,8 +17,11 @@ **********************************************************************/ +#include #include +#include "geos/util.h" + namespace geos { namespace geom { // geos.geom namespace prep { // geos.geom.prep @@ -26,6 +29,8 @@ namespace prep { // geos.geom.prep bool PreparedPoint::intersects(const geom::Geometry* g) const { + util::ensureNoCurvedComponents(g); + if(! envelopesIntersect(g)) { return false; } diff --git a/Sources/geos/src/geom/prep/PreparedPolygon.cpp b/Sources/geos/src/geom/prep/PreparedPolygon.cpp index 9cb749b..cf22432 100644 --- a/Sources/geos/src/geom/prep/PreparedPolygon.cpp +++ b/Sources/geos/src/geom/prep/PreparedPolygon.cpp @@ -30,6 +30,7 @@ #include #include #include +#include // std #include @@ -68,11 +69,18 @@ algorithm::locate::PointOnGeometryLocator* PreparedPolygon:: getPointLocator() const { + // If we are only going to locate a single point, it's faster to do a brute-force SimplePointInAreaLocator + // instead of an IndexedPointInAreaLocator. There's a reasonable chance we will only use this locator + // once (for example, if we get here through Geometry::intersects). So we create a simple locator for the + // first usage and switch to an indexed locator when it is clear we're in a multiple-use scenario. if(! ptOnGeomLoc) { - ptOnGeomLoc.reset(new algorithm::locate::IndexedPointInAreaLocator(getGeometry())); + ptOnGeomLoc = detail::make_unique(&getGeometry()); + return ptOnGeomLoc.get(); + } else if (!indexedPtOnGeomLoc) { + indexedPtOnGeomLoc = detail::make_unique(getGeometry()); } - return ptOnGeomLoc.get(); + return indexedPtOnGeomLoc.get(); } bool @@ -128,6 +136,8 @@ bool PreparedPolygon:: intersects(const geom::Geometry* g) const { + geos::util::ensureNoCurvedComponents(g); + // envelope test if(!envelopesIntersect(g)) { return false; @@ -161,6 +171,12 @@ PreparedPolygon::distance(const geom::Geometry* g) const return PreparedPolygonDistance::distance(*this, g); } +bool +PreparedPolygon::isWithinDistance(const geom::Geometry* g, double d) const +{ + return PreparedPolygonDistance(*this).isWithinDistance(g, d); +} + } // namespace geos.geom.prep } // namespace geos.geom } // namespace geos diff --git a/Sources/geos/src/geom/prep/PreparedPolygonDistance.cpp b/Sources/geos/src/geom/prep/PreparedPolygonDistance.cpp index 09fb762..012064c 100644 --- a/Sources/geos/src/geom/prep/PreparedPolygonDistance.cpp +++ b/Sources/geos/src/geom/prep/PreparedPolygonDistance.cpp @@ -17,6 +17,7 @@ **********************************************************************/ #include +#include #include #include #include @@ -31,16 +32,53 @@ namespace prep { // geos.geom.prep double PreparedPolygonDistance::distance(const geom::Geometry* g) const { - if ( prepPoly.getGeometry().isEmpty() || g->isEmpty() ) + if ( prepPoly->getGeometry().isEmpty() || g->isEmpty() ) { return DoubleInfinity; } - if ( prepPoly.intersects(g) ) return 0.0; + // If any point from g is contained by prepPoly, the distance is zero + if ( isAnyTestComponentInTarget(g) ) { + return 0.0; + } + + // Perform an indexed distance calculation between the boundaries of prepPoly and g + operation::distance::IndexedFacetDistance *idf = prepPoly->getIndexedFacetDistance(); + double dist = idf->distance(g); + + // If any point from prepPoly is contained by g, the distance is zero + // Do this last because this PIP test is not indexed. + if ( g->getDimension() == 2 && dist > 0 && isAnyTargetComponentInAreaTest(g, prepPoly->getRepresentativePoints())) { + return 0.0; + } + + return dist; +} + +bool +PreparedPolygonDistance::isWithinDistance(const geom::Geometry* g, double d) const +{ + if ( prepPoly->getGeometry().isEmpty() || g->isEmpty() ) + { + return false; + } + + // If any point from g is contained by prepPoly, the distance is zero + if ( isAnyTestComponentInTarget(g) ) { + return true; + } + + // Perform an indexed distance calculation between the boundaries of prepPoly and g + operation::distance::IndexedFacetDistance *idf = prepPoly->getIndexedFacetDistance(); + bool withinDistance = idf->isWithinDistance(g, d); + + // If any point from prepPoly is contained by g, the distance is zero + // Do this last because this PIP test is not indexed. + if ( g->getDimension() == 2 && !withinDistance) { + return isAnyTargetComponentInAreaTest(g, prepPoly->getRepresentativePoints()); + } - /* Not intersecting, compute distance from facets */ - operation::distance::IndexedFacetDistance *idf = prepPoly.getIndexedFacetDistance(); - return idf->distance(g); + return withinDistance; } } // namespace geos.geom.prep diff --git a/Sources/geos/src/geom/prep/PreparedPolygonPredicate.cpp b/Sources/geos/src/geom/prep/PreparedPolygonPredicate.cpp index 0782ca2..45c1567 100644 --- a/Sources/geos/src/geom/prep/PreparedPolygonPredicate.cpp +++ b/Sources/geos/src/geom/prep/PreparedPolygonPredicate.cpp @@ -49,7 +49,7 @@ struct LocationMatchingFilter : public GeometryComponentFilter { void filter_ro(const Geometry* g) override { if (g->isEmpty()) return; - const Coordinate* pt = g->getCoordinate(); + const CoordinateXY* pt = g->getCoordinate(); const auto loc = pt_locator->locate(pt); if (loc == test_loc) { @@ -73,7 +73,7 @@ struct LocationNotMatchingFilter : public GeometryComponentFilter { void filter_ro(const Geometry* g) override { if (g->isEmpty()) return; - const Coordinate* pt = g->getCoordinate(); + const CoordinateXY* pt = g->getCoordinate(); const auto loc = pt_locator->locate(pt); if (loc != test_loc) { @@ -99,7 +99,7 @@ struct OutermostLocationFilter : public GeometryComponentFilter { void filter_ro(const Geometry* g) override { if (g->isEmpty()) return; - const Coordinate* pt = g->getCoordinate(); + const CoordinateXY* pt = g->getCoordinate(); auto loc = pt_locator->locate(pt); if (outermost_loc == Location::NONE || outermost_loc == Location::INTERIOR) { @@ -161,12 +161,11 @@ PreparedPolygonPredicate::isAnyTestComponentInTargetInterior( bool PreparedPolygonPredicate::isAnyTargetComponentInAreaTest( const geom::Geometry* testGeom, - const geom::Coordinate::ConstVect* targetRepPts) const + const std::vector* targetRepPts) const { algorithm::locate::SimplePointInAreaLocator piaLoc(testGeom); - for(std::size_t i = 0, ni = targetRepPts->size(); i < ni; i++) { - const geom::Coordinate* pt = (*targetRepPts)[i]; + for(const auto& pt : *targetRepPts) { const Location loc = piaLoc.locate(pt); if(geom::Location::EXTERIOR != loc) { return true; diff --git a/Sources/geos/src/geom/util/ComponentCoordinateExtracter.cpp b/Sources/geos/src/geom/util/ComponentCoordinateExtracter.cpp index 1a2ba66..4028de8 100644 --- a/Sources/geos/src/geom/util/ComponentCoordinateExtracter.cpp +++ b/Sources/geos/src/geom/util/ComponentCoordinateExtracter.cpp @@ -20,7 +20,7 @@ namespace geos { namespace geom { // geos.geom namespace util { // geos.geom.util -ComponentCoordinateExtracter::ComponentCoordinateExtracter(std::vector& newComps) +ComponentCoordinateExtracter::ComponentCoordinateExtracter(std::vector& newComps) : comps(newComps) {} @@ -59,7 +59,7 @@ ComponentCoordinateExtracter::filter_ro(const Geometry* geom) void -ComponentCoordinateExtracter::getCoordinates(const Geometry& geom, std::vector& ret) +ComponentCoordinateExtracter::getCoordinates(const Geometry& geom, std::vector& ret) { ComponentCoordinateExtracter cce(ret); geom.apply_ro(&cce); diff --git a/Sources/geos/src/geom/util/CoordinateOperation.cpp b/Sources/geos/src/geom/util/CoordinateOperation.cpp index b0af99e..621b741 100644 --- a/Sources/geos/src/geom/util/CoordinateOperation.cpp +++ b/Sources/geos/src/geom/util/CoordinateOperation.cpp @@ -48,7 +48,7 @@ CoordinateOperation::edit(const Geometry* geometry, if(const Point* point = dynamic_cast(geometry)) { auto coords = point->getCoordinatesRO(); auto newCoords = edit(coords, geometry); - return std::unique_ptr(factory->createPoint(newCoords.release())); + return factory->createPoint(std::move(newCoords)); } return geometry->clone(); diff --git a/Sources/geos/src/geom/util/Densifier.cpp b/Sources/geos/src/geom/util/Densifier.cpp index 769c900..b4ee7b2 100644 --- a/Sources/geos/src/geom/util/Densifier.cpp +++ b/Sources/geos/src/geom/util/Densifier.cpp @@ -21,7 +21,6 @@ #include #include -#include #include #include #include @@ -38,6 +37,7 @@ #include #include #include +#include #include #include @@ -59,16 +59,15 @@ Densifier::DensifyTransformer::transformCoordinates(const CoordinateSequence* co { Coordinate::Vect emptyPts; Coordinate::Vect inputPts; - coords->toVector(inputPts); - std::unique_ptr newPts = Densifier::densifyPoints(inputPts, distanceTolerance, - parent->getPrecisionModel()); + auto newPts = Densifier::densifyPoints(*coords, distanceTolerance, parent->getPrecisionModel()); + if(const LineString* ls = dynamic_cast(parent)) { if(ls->getNumPoints() <= 1) { newPts->clear(); } } - CoordinateSequence::Ptr csp(factory->getCoordinateSequenceFactory()->create(newPts.release())); - return csp; + + return newPts; } Geometry::Ptr @@ -105,16 +104,17 @@ Densifier::Densifier(const Geometry* geom): inputGeom(geom) {} -std::unique_ptr -Densifier::densifyPoints(const Coordinate::Vect pts, double distanceTolerance, const PrecisionModel* precModel) +std::unique_ptr +Densifier::densifyPoints(const CoordinateSequence& pts, double distanceTolerance, const PrecisionModel* precModel) { geom::LineSegment seg; - geom::CoordinateList coordList; + auto coordList = detail::make_unique(); - for(Coordinate::Vect::const_iterator it = pts.begin(), itEnd = pts.end() - 1; it < itEnd; ++it) { + auto items = pts.items(); + for(auto it = items.cbegin(), itEnd = items.cend() - 1; it < itEnd; ++it) { seg.p0 = *it; seg.p1 = *(it + 1); - coordList.insert(coordList.end(), seg.p0, false); + coordList->add(seg.p0, false); const double len = seg.getLength(); const double densifiedSegCountDbl = ceil(len / distanceTolerance); if(densifiedSegCountDbl > std::numeric_limits::max()) { @@ -130,16 +130,17 @@ Densifier::densifyPoints(const Coordinate::Vect pts, double distanceTolerance, c Coordinate p; seg.pointAlong(segFract, p); precModel->makePrecise(p); - coordList.insert(coordList.end(), p, false); + coordList->add(p, false); } } else { // no densification required; insert the last coordinate and continue - coordList.insert(coordList.end(), seg.p1, false); + coordList->add(seg.p1, false); } } - coordList.insert(coordList.end(), pts[pts.size() - 1], false); - return coordList.toCoordinateArray(); + coordList->add(pts[pts.size() - 1], false); + + return coordList; } /** diff --git a/Sources/geos/src/geom/util/GeometryEditor.cpp b/Sources/geos/src/geom/util/GeometryEditor.cpp index 525e244..acba507 100644 --- a/Sources/geos/src/geom/util/GeometryEditor.cpp +++ b/Sources/geos/src/geom/util/GeometryEditor.cpp @@ -74,6 +74,8 @@ GeometryEditor::GeometryEditor(const GeometryFactory* newFactory) std::unique_ptr GeometryEditor::edit(const Geometry* geometry, GeometryEditorOperation* operation) { + geos::util::ensureNoCurvedComponents(geometry); + // if client did not supply a GeometryFactory, use the one from the input Geometry if(factory == nullptr) { factory = geometry->getFactory(); @@ -126,7 +128,7 @@ GeometryEditor::editPolygon(const Polygon* polygon, GeometryEditorOperation* ope return std::unique_ptr(factory->createPolygon(polygon->getCoordinateDimension())); } - auto holes = detail::make_unique>(); + std::vector> holes; for(std::size_t i = 0, n = newPolygon->getNumInteriorRing(); i < n; ++i) { std::unique_ptr hole(detail::down_cast( @@ -135,10 +137,10 @@ GeometryEditor::editPolygon(const Polygon* polygon, GeometryEditorOperation* ope if(hole->isEmpty()) { continue; } - holes->push_back(hole.release()); + holes.push_back(std::move(hole)); } - return std::unique_ptr(factory->createPolygon(shell.release(), holes.release())); + return factory->createPolygon(std::move(shell), std::move(holes)); } std::unique_ptr diff --git a/Sources/geos/src/geom/util/GeometryFixer.cpp b/Sources/geos/src/geom/util/GeometryFixer.cpp index 67a3026..6d73ecb 100644 --- a/Sources/geos/src/geom/util/GeometryFixer.cpp +++ b/Sources/geos/src/geom/util/GeometryFixer.cpp @@ -106,7 +106,7 @@ GeometryFixer::fixPointElement(const Point* p_geom) const bool GeometryFixer::isValidPoint(const Point* pt) const { - const Coordinate* p = pt->getCoordinate(); + const CoordinateXY* p = pt->getCoordinate(); return p->isValid(); } diff --git a/Sources/geos/src/geom/util/GeometryTransformer.cpp b/Sources/geos/src/geom/util/GeometryTransformer.cpp index 67b0966..acec018 100644 --- a/Sources/geos/src/geom/util/GeometryTransformer.cpp +++ b/Sources/geos/src/geom/util/GeometryTransformer.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include #include @@ -108,16 +107,6 @@ GeometryTransformer::transform(const Geometry* nInputGeom) throw IllegalArgumentException("Unknown Geometry subtype."); } -std::unique_ptr -GeometryTransformer::createCoordinateSequence( - std::unique_ptr< std::vector > coords) -{ - return std::unique_ptr( - factory->getCoordinateSequenceFactory()->create( - coords.release()) - ); -} - std::unique_ptr GeometryTransformer::transformCoordinates( const CoordinateSequence* coords, @@ -147,7 +136,7 @@ GeometryTransformer::transformPoint( CoordinateSequence::Ptr cs(transformCoordinates( geom->getCoordinatesRO(), geom)); - return Geometry::Ptr(factory->createPoint(cs.release())); + return factory->createPoint(std::move(cs)); } Geometry::Ptr diff --git a/Sources/geos/src/geom/util/LinearComponentExtracter.cpp b/Sources/geos/src/geom/util/LinearComponentExtracter.cpp index 2eca78a..2dbc764 100644 --- a/Sources/geos/src/geom/util/LinearComponentExtracter.cpp +++ b/Sources/geos/src/geom/util/LinearComponentExtracter.cpp @@ -18,6 +18,7 @@ #include #include +#include namespace geos { namespace geom { // geos.geom @@ -31,25 +32,21 @@ LinearComponentExtracter::LinearComponentExtracter(std::vector& ret) { + if (geom.getDimension() == Dimension::P) { + return; + } + LinearComponentExtracter lce(ret); geom.apply_ro(&lce); } -void -LinearComponentExtracter::filter_rw(Geometry* geom) -{ - if (geom->isEmpty()) return; - if(const LineString* ls = dynamic_cast(geom)) { - comps.push_back(ls); - } -} - void LinearComponentExtracter::filter_ro(const Geometry* geom) { if (geom->isEmpty()) return; - if(const LineString* ls = dynamic_cast(geom)) { - comps.push_back(ls); + auto typ = geom->getGeometryTypeId(); + if (typ == GEOS_LINEARRING || typ == GEOS_LINESTRING) { + comps.push_back(detail::down_cast(geom)); } } diff --git a/Sources/geos/src/geom/util/PointExtracter.cpp b/Sources/geos/src/geom/util/PointExtracter.cpp index 79f6154..70375ad 100644 --- a/Sources/geos/src/geom/util/PointExtracter.cpp +++ b/Sources/geos/src/geom/util/PointExtracter.cpp @@ -28,6 +28,10 @@ namespace util { // geos.geom.util void PointExtracter::getPoints(const Geometry& geom, Point::ConstVect& ret) { + if (!geom.hasDimension(Dimension::P)) { + return; + } + PointExtracter pe(ret); geom.apply_ro(&pe); } @@ -44,16 +48,16 @@ PointExtracter::PointExtracter(Point::ConstVect& newComps) void PointExtracter::filter_rw(Geometry* geom) { - if(const Point* p = dynamic_cast(geom)) { - comps.push_back(p); + if (geom->getGeometryTypeId() == GEOS_POINT) { + comps.push_back(static_cast(geom)); } } void PointExtracter::filter_ro(const Geometry* geom) { - if(const Point* p = dynamic_cast(geom)) { - comps.push_back(p); + if (geom->getGeometryTypeId() == GEOS_POINT) { + comps.push_back(static_cast(geom)); } } } diff --git a/Sources/geos/src/geom/util/PolygonExtracter.cpp b/Sources/geos/src/geom/util/PolygonExtracter.cpp index 5e4ae6b..879e913 100644 --- a/Sources/geos/src/geom/util/PolygonExtracter.cpp +++ b/Sources/geos/src/geom/util/PolygonExtracter.cpp @@ -27,6 +27,10 @@ namespace util { // geos.geom.util void PolygonExtracter::getPolygons(const Geometry& geom, std::vector& ret) { + if (!geom.hasDimension(Dimension::A)) { + return; + } + PolygonExtracter pe(ret); geom.apply_ro(&pe); } diff --git a/Sources/geos/src/geom/util/PolygonalExtracter.cpp b/Sources/geos/src/geom/util/PolygonalExtracter.cpp new file mode 100644 index 0000000..009b109 --- /dev/null +++ b/Sources/geos/src/geom/util/PolygonalExtracter.cpp @@ -0,0 +1,49 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2001-2002 Vivid Solutions Inc. + * Copyright (C) 2006 Refractions Research Inc. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include +#include +#include + +#include + +namespace geos { +namespace geom { // geos.geom +namespace util { // geos.geom.util + +void +PolygonalExtracter::getPolygonals(const Geometry& geom, std::vector& polys) +{ + getPolygonals(&geom, polys); +} + +void +PolygonalExtracter::getPolygonals(const Geometry* geom, std::vector& polys) +{ + if (dynamic_cast(geom) != nullptr + || dynamic_cast(geom) != nullptr ) { + polys.push_back(geom); + } + else if (dynamic_cast(geom) != nullptr) { + for (std::size_t i = 0; i < geom->getNumGeometries(); i++) { + getPolygonals(geom->getGeometryN(i), polys); + } + } +} + +} +} +} diff --git a/Sources/geos/src/geom/util/SineStarFactory.cpp b/Sources/geos/src/geom/util/SineStarFactory.cpp index dfa37ce..dbb77ef 100644 --- a/Sources/geos/src/geom/util/SineStarFactory.cpp +++ b/Sources/geos/src/geom/util/SineStarFactory.cpp @@ -19,12 +19,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include @@ -56,7 +56,7 @@ SineStarFactory::createSineStar() const double centreX = env->getMinX() + radius; double centreY = env->getMinY() + radius; - std::vector pts(nPts + 1); + auto pts = detail::make_unique(nPts + 1); uint32_t iPt = 0; for(uint32_t i = 0; i < nPts; i++) { // the fraction of the way thru the current arm - in [0,1] @@ -76,12 +76,11 @@ SineStarFactory::createSineStar() const double ang = i * (2 * MATH_PI / nPts); double x = curveRadius * cos(ang) + centreX; double y = curveRadius * sin(ang) + centreY; - pts[iPt++] = coord(x, y); + (*pts)[iPt++] = coord(x, y); } - pts[iPt] = pts[0]; + (*pts)[iPt] = (*pts)[0]; - auto cs = geomFact->getCoordinateSequenceFactory()->create(std::move(pts)); - auto ring = geomFact->createLinearRing(std::move(cs)); + auto ring = geomFact->createLinearRing(std::move(pts)); auto poly = geomFact->createPolygon(std::move(ring)); return poly; } diff --git a/Sources/geos/src/geomgraph/DirectedEdgeStar.cpp b/Sources/geos/src/geomgraph/DirectedEdgeStar.cpp index 118c98e..44b0ded 100644 --- a/Sources/geos/src/geomgraph/DirectedEdgeStar.cpp +++ b/Sources/geos/src/geomgraph/DirectedEdgeStar.cpp @@ -130,7 +130,7 @@ DirectedEdgeStar::getRightmostEdge() /*public*/ void -DirectedEdgeStar::computeLabelling(std::vector* geom) +DirectedEdgeStar::computeLabelling(const std::vector>&geom) //throw(TopologyException *) { // this call can throw a TopologyException diff --git a/Sources/geos/src/geomgraph/Edge.cpp b/Sources/geos/src/geomgraph/Edge.cpp index eff7ea7..6212703 100644 --- a/Sources/geos/src/geomgraph/Edge.cpp +++ b/Sources/geos/src/geomgraph/Edge.cpp @@ -33,7 +33,6 @@ #include #include #include -#include // FIXME: shouldn't use #include #include @@ -142,7 +141,7 @@ Edge* Edge::getCollapsedEdge() { testInvariant(); - CoordinateSequence* newPts = new CoordinateArraySequence(2); + CoordinateSequence* newPts = new CoordinateSequence(2); newPts->setAt(pts->getAt(0), 0); newPts->setAt(pts->getAt(1), 1); return new Edge(newPts, Label::toLineLabel(label)); diff --git a/Sources/geos/src/geomgraph/EdgeEndStar.cpp b/Sources/geos/src/geomgraph/EdgeEndStar.cpp index da3df4a..44fcc4f 100644 --- a/Sources/geos/src/geomgraph/EdgeEndStar.cpp +++ b/Sources/geos/src/geomgraph/EdgeEndStar.cpp @@ -91,10 +91,10 @@ EdgeEndStar::getNextCW(EdgeEnd* ee) /*public*/ void -EdgeEndStar::computeLabelling(std::vector* geomGraph) +EdgeEndStar::computeLabelling(const std::vector>& geomGraph) //throw(TopologyException *) { - computeEdgeEndLabels((*geomGraph)[0]->getBoundaryNodeRule()); + computeEdgeEndLabels(geomGraph[0]->getBoundaryNodeRule()); // Propagate side labels around the edges in the star // for each parent Geometry @@ -185,12 +185,12 @@ EdgeEndStar::computeEdgeEndLabels( /*public*/ Location EdgeEndStar::getLocation(uint32_t geomIndex, - const Coordinate& p, std::vector* geom) + const Coordinate& p, const std::vector>& geom) { // compute location only on demand if(ptInAreaLocation[geomIndex] == Location::NONE) { ptInAreaLocation[geomIndex] = algorithm::locate::SimplePointInAreaLocator::locate(p, - (*geom)[geomIndex]->getGeometry()); + geom[geomIndex]->getGeometry()); } return ptInAreaLocation[geomIndex]; } diff --git a/Sources/geos/src/geomgraph/EdgeIntersectionList.cpp b/Sources/geos/src/geomgraph/EdgeIntersectionList.cpp index b36679d..cd3bc6b 100644 --- a/Sources/geos/src/geomgraph/EdgeIntersectionList.cpp +++ b/Sources/geos/src/geomgraph/EdgeIntersectionList.cpp @@ -23,8 +23,8 @@ #include #include #include -#include // shouldn't be using this #include +#include #include #include @@ -145,26 +145,24 @@ EdgeIntersectionList::createSplitEdge(const EdgeIntersection* ei0, std::cerr << " npts:" << npts << std::endl; #endif // GEOS_DEBUG - std::vector vc; - vc.reserve(npts); + auto vc = detail::make_unique(); + vc->reserve(npts); - vc.push_back(ei0->coord); + vc->add(ei0->coord); for(auto i = ei0->segmentIndex + 1; i <= ei1->segmentIndex; ++i) { if(! useIntPt1 && ei1->segmentIndex == i) { - vc.push_back(ei1->coord); + vc->add(ei1->coord); } else { - vc.push_back(edge->pts->getAt(i)); + vc->add(edge->pts->getAt(i)); } } if(useIntPt1) { - vc.push_back(ei1->coord); + vc->add(ei1->coord); } - std::unique_ptr pts(new CoordinateArraySequence(std::move(vc))); - - return new Edge(pts.release(), edge->getLabel()); + return new Edge(vc.release(), edge->getLabel()); } std::string diff --git a/Sources/geos/src/geomgraph/EdgeList.cpp b/Sources/geos/src/geomgraph/EdgeList.cpp index d609235..d32f519 100644 --- a/Sources/geos/src/geomgraph/EdgeList.cpp +++ b/Sources/geos/src/geomgraph/EdgeList.cpp @@ -128,7 +128,7 @@ EdgeList::print() void EdgeList::clearList() { - for(auto & edge : edges) { + for(auto* edge : edges) { delete edge; } @@ -140,7 +140,7 @@ operator<< (std::ostream& os, const EdgeList& el) { os << "EdgeList: " << std::endl; for(std::size_t j = 0, s = el.edges.size(); j < s; ++j) { - Edge* e = el.edges[j]; + const Edge* e = el.edges[j]; os << " " << *e << std::endl; } return os; diff --git a/Sources/geos/src/geomgraph/EdgeRing.cpp b/Sources/geos/src/geomgraph/EdgeRing.cpp index 5259858..6cb8b47 100644 --- a/Sources/geos/src/geomgraph/EdgeRing.cpp +++ b/Sources/geos/src/geomgraph/EdgeRing.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -174,7 +173,7 @@ EdgeRing::computeRing() if(ring != nullptr) { return; // don't compute more than once } - auto coordSeq = geometryFactory->getCoordinateSequenceFactory()->create(std::move(pts)); + auto coordSeq = detail::make_unique(std::move(pts)); ring = geometryFactory->createLinearRing(std::move(coordSeq)); isHoleVar = Orientation::isCCW(ring->getCoordinatesRO()); @@ -322,11 +321,11 @@ EdgeRing::addPoints(Edge* edge, bool isForward, bool isFirstEdge) if(isForward) { if(isFirstEdge) { - edgePts->toVector(pts); + pts = *edgePts; return; } else { for(std::size_t i = 1; i < numEdgePts; ++i) { - pts.push_back(edgePts->getAt(i)); + pts.add(edgePts->getAt(i)); } } } @@ -337,7 +336,7 @@ EdgeRing::addPoints(Edge* edge, bool isForward, bool isFirstEdge) startIndex = numEdgePts; } for(std::size_t i = startIndex; i > 0; --i) { - pts.push_back(edgePts->getAt(i - 1)); + pts.add(edgePts->getAt(i - 1)); } } diff --git a/Sources/geos/src/geomgraph/GeometryGraph.cpp b/Sources/geos/src/geomgraph/GeometryGraph.cpp index 8461b75..48e5f6d 100644 --- a/Sources/geos/src/geomgraph/GeometryGraph.cpp +++ b/Sources/geos/src/geomgraph/GeometryGraph.cpp @@ -34,7 +34,6 @@ #include #include -#include #include #include #include @@ -127,7 +126,7 @@ GeometryGraph::getBoundaryPoints() if(! boundaryPoints.get()) { // Collection will be destroyed by GeometryGraph dtor std::vector* coll = getBoundaryNodes(); - boundaryPoints.reset(new CoordinateArraySequence(coll->size())); + boundaryPoints.reset(new CoordinateSequence(coll->size())); std::size_t i = 0; for(std::vector::iterator it = coll->begin(), endIt = coll->end(); it != endIt; ++it) { @@ -170,6 +169,8 @@ void GeometryGraph::add(const Geometry* g) //throw (UnsupportedOperationException *) { + util::ensureNoCurvedComponents(g); + if(g->isEmpty()) { return; } @@ -209,8 +210,7 @@ void GeometryGraph::addCollection(const GeometryCollection* gc) { for(std::size_t i = 0, n = gc->getNumGeometries(); i < n; ++i) { - const Geometry* g = gc->getGeometryN(i); - add(g); + add(gc->getGeometryN(i)); } } @@ -220,7 +220,7 @@ GeometryGraph::addCollection(const GeometryCollection* gc) void GeometryGraph::addPoint(const Point* p) { - const Coordinate& coord = *(p->getCoordinate()); + const Coordinate& coord = p->getCoordinatesRO()->getAt(0); insertPoint(argIndex, coord, Location::INTERIOR); } @@ -466,7 +466,7 @@ GeometryGraph::addSelfIntersectionNodes(uint8_t p_argIndex) { for(Edge* e : *edges) { Location eLoc = e->getLabel().getLocation(p_argIndex); - EdgeIntersectionList& eiL = e->eiList; + const EdgeIntersectionList& eiL = e->eiList; for(const EdgeIntersection& ei : eiL) { addSelfIntersectionNode(p_argIndex, ei.coord, eLoc); GEOS_CHECK_FOR_INTERRUPTS(); diff --git a/Sources/geos/src/geomgraph/Node.cpp b/Sources/geos/src/geomgraph/Node.cpp index 66cb815..211f7f9 100644 --- a/Sources/geos/src/geomgraph/Node.cpp +++ b/Sources/geos/src/geomgraph/Node.cpp @@ -133,9 +133,8 @@ Node::isIncidentEdgeInResult() const } void -Node::add(EdgeEnd* p_e) +Node::add(EdgeEnd* e) { - std::unique_ptr e(p_e); assert(e); #if GEOS_DEBUG std::cerr << "[" << this << "] Node::add(" << e->print() << ")" << std::endl; @@ -154,10 +153,10 @@ Node::add(EdgeEnd* p_e) assert(edges); //if (edges==NULL) return; - edges->insert(e.release()); - p_e->setNode(this); + edges->insert(e); + e->setNode(this); #if COMPUTE_Z - addZ(p_e->getCoordinate().z); + addZ(e->getCoordinate().z); #endif testInvariant(); } @@ -241,7 +240,7 @@ Node::computeMergedLocation(const Label& label2, uint8_t eltIndex) /*public*/ std::string -Node::print() +Node::print() const { testInvariant(); diff --git a/Sources/geos/src/geomgraph/NodeMap.cpp b/Sources/geos/src/geomgraph/NodeMap.cpp index 2175f15..3624cc8 100644 --- a/Sources/geos/src/geomgraph/NodeMap.cpp +++ b/Sources/geos/src/geomgraph/NodeMap.cpp @@ -46,13 +46,6 @@ NodeMap::NodeMap(const NodeFactory& newNodeFact) #endif } -NodeMap::~NodeMap() -{ - for(auto& it: nodeMap) { - delete it.second; - } -} - Node* NodeMap::addNode(const Coordinate& coord) { @@ -67,8 +60,8 @@ NodeMap::addNode(const Coordinate& coord) node = nodeFact.createNode(coord); Coordinate* c = const_cast( &(node->getCoordinate())); - nodeMap[c] = node; - //nodeMap[const_cast(&coord)]=node; + nodeMap[c] = std::unique_ptr(node); + node = nodeMap[c].get(); } else { #if GEOS_DEBUG @@ -95,8 +88,8 @@ NodeMap::addNode(Node* n) #if GEOS_DEBUG std::cerr << " is new" << std::endl; #endif - nodeMap[c] = n; - return n; + nodeMap[c] = std::unique_ptr(n); + return nodeMap[c].get(); } #if GEOS_DEBUG else { @@ -120,6 +113,13 @@ NodeMap::add(EdgeEnd* e) n->add(e); } +void +NodeMap::add(std::unique_ptr&& e) +{ + add(e.get()); + e.release(); +} + /* * @return the node if found; null otherwise */ @@ -128,21 +128,21 @@ NodeMap::find(const Coordinate& coord) const { Coordinate* c = const_cast(&coord); - NodeMap::const_iterator found = nodeMap.find(c); + const auto& found = nodeMap.find(c); if(found == nodeMap.end()) { return nullptr; } else { - return found->second; + return found->second.get(); } } void NodeMap::getBoundaryNodes(uint8_t geomIndex, std::vector& bdyNodes) const { - for(auto& it: nodeMap) { - Node* node = it.second; + for(const auto& it: nodeMap) { + Node* node = it.second.get(); if(node->getLabel().getLocation(geomIndex) == Location::BOUNDARY) { bdyNodes.push_back(node); } @@ -153,8 +153,8 @@ std::string NodeMap::print() const { std::string out = ""; - for(auto& it: nodeMap) { - Node* node = it.second; + for(const auto& it: nodeMap) { + const Node* node = it.second.get(); out += node->print(); } return out; diff --git a/Sources/geos/src/geomgraph/PlanarGraph.cpp b/Sources/geos/src/geomgraph/PlanarGraph.cpp index b9eb545..70e363d 100644 --- a/Sources/geos/src/geomgraph/PlanarGraph.cpp +++ b/Sources/geos/src/geomgraph/PlanarGraph.cpp @@ -160,11 +160,9 @@ void PlanarGraph::getNodes(std::vector& values) { assert(nodes); - NodeMap::iterator it = nodes->nodeMap.begin(); - while(it != nodes->nodeMap.end()) { - assert(it->second); - values.push_back(it->second); - ++it; + for( const auto& elem: nodes->nodeMap ) { + assert(elem.second.get()); + values.push_back(elem.second.get()); } } @@ -233,8 +231,8 @@ PlanarGraph::linkResultDirectedEdges() #if GEOS_DEBUG std::cerr << "PlanarGraph::linkResultDirectedEdges called" << std::endl; #endif - for(auto& nodeIt: nodes->nodeMap) { - Node* node = nodeIt.second; + for(const auto& nodeIt: nodes->nodeMap) { + Node* node = nodeIt.second.get(); assert(node); EdgeEndStar* ees = node->getEdges(); @@ -257,8 +255,8 @@ PlanarGraph::linkAllDirectedEdges() #if GEOS_DEBUG std::cerr << "PlanarGraph::linkAllDirectedEdges called" << std::endl; #endif - for(auto& nodeIt: nodes->nodeMap) { - Node* node = nodeIt.second; + for(const auto& nodeIt: nodes->nodeMap) { + Node* node = nodeIt.second.get(); assert(node); EdgeEndStar* ees = node->getEdges(); diff --git a/Sources/geos/src/index/VertexSequencePackedRtree.cpp b/Sources/geos/src/index/VertexSequencePackedRtree.cpp index 1811348..de75836 100644 --- a/Sources/geos/src/index/VertexSequencePackedRtree.cpp +++ b/Sources/geos/src/index/VertexSequencePackedRtree.cpp @@ -15,6 +15,7 @@ #include +#include #include #include @@ -31,7 +32,7 @@ namespace index { * @param pts a sequence of points */ /* public */ -VertexSequencePackedRtree::VertexSequencePackedRtree(const std::vector& pts) +VertexSequencePackedRtree::VertexSequencePackedRtree(const CoordinateSequence& pts) : items(pts) , removedItems(pts.size(), false) { @@ -160,7 +161,7 @@ VertexSequencePackedRtree::computeNodeEnvelope(const std::vector& bnds /* private static */ Envelope -VertexSequencePackedRtree::computeItemEnvelope(const std::vector& items, +VertexSequencePackedRtree::computeItemEnvelope(const geom::CoordinateSequence& items, std::size_t start, std::size_t end) { Envelope env; diff --git a/Sources/geos/src/index/chain/MonotoneChain.cpp b/Sources/geos/src/index/chain/MonotoneChain.cpp index 342b579..e3d336a 100644 --- a/Sources/geos/src/index/chain/MonotoneChain.cpp +++ b/Sources/geos/src/index/chain/MonotoneChain.cpp @@ -50,7 +50,7 @@ const Envelope& MonotoneChain::getEnvelope(double expansionDistance) const { if (env.isNull()) { - env.init(pts->getAt(start), pts->getAt(end)); + env.init(pts->getAt(start), pts->getAt(end)); if (expansionDistance > 0.0) { env.expandBy(expansionDistance); } @@ -75,8 +75,8 @@ MonotoneChain::computeSelect(const Envelope& searchEnv, std::size_t start0, std::size_t end0, MonotoneChainSelectAction& mcs) const { - const Coordinate& p0 = pts->getAt(start0); - const Coordinate& p1 = pts->getAt(end0); + const CoordinateXY& p0 = pts->getAt(start0); + const CoordinateXY& p1 = pts->getAt(end0); // terminating condition for the recursion if(end0 - start0 == 1) { @@ -164,8 +164,8 @@ MonotoneChain::computeOverlaps(std::size_t start0, std::size_t end0, /*private*/ bool -MonotoneChain::overlaps(const Coordinate& p1, const Coordinate& p2, - const Coordinate& q1, const Coordinate& q2, +MonotoneChain::overlaps(const CoordinateXY& p1, const CoordinateXY& p2, + const CoordinateXY& q1, const CoordinateXY& q2, double overlapTolerance) { double maxq = std::max(q1.x, q2.x); diff --git a/Sources/geos/src/index/chain/MonotoneChainBuilder.cpp b/Sources/geos/src/index/chain/MonotoneChainBuilder.cpp index ca4fe1c..9185fe8 100644 --- a/Sources/geos/src/index/chain/MonotoneChainBuilder.cpp +++ b/Sources/geos/src/index/chain/MonotoneChainBuilder.cpp @@ -57,7 +57,7 @@ class ChainBuilder : public CoordinateFilter { m_context(context), m_list(list) {} - void filter_ro(const Coordinate* c) override final { + void filter_ro(const CoordinateXY* c) override { process(c); m_prev = c; @@ -76,7 +76,7 @@ class ChainBuilder : public CoordinateFilter { m_start = chainEnd; } - void process(const Coordinate* curr) { + void process(const CoordinateXY* curr) { if (m_prev == nullptr || curr->equals2D(*m_prev)) { return; } @@ -93,7 +93,7 @@ class ChainBuilder : public CoordinateFilter { } } - const Coordinate* m_prev; + const CoordinateXY* m_prev; std::size_t m_i; int m_quadrant; std::size_t m_start; diff --git a/Sources/geos/src/io/GeoJSON.cpp b/Sources/geos/src/io/GeoJSON.cpp index cfb28ea..32e0f6c 100644 --- a/Sources/geos/src/io/GeoJSON.cpp +++ b/Sources/geos/src/io/GeoJSON.cpp @@ -224,16 +224,26 @@ bool GeoJSONValue::isArray() const // GeoJSONFeature GeoJSONFeature::GeoJSONFeature(std::unique_ptr g, - const std::map& p) : geometry(std::move(g)), properties(p) {} + const std::map& p) : geometry(std::move(g)), properties(p), id("") {} GeoJSONFeature::GeoJSONFeature(std::unique_ptr g, - std::map&& p) : geometry(std::move(g)), properties(std::move(p)) {} + std::map&& p) : geometry(std::move(g)), properties(std::move(p)), id("") {} + +GeoJSONFeature::GeoJSONFeature(std::unique_ptr g, + const std::map& p, + const std::string& i) + : geometry(std::move(g)), properties(p), id(i) {} + +GeoJSONFeature::GeoJSONFeature(std::unique_ptr g, + std::map&& p, + std::string i) + : geometry(std::move(g)), properties(std::move(p)), id(std::move(i)) {} GeoJSONFeature::GeoJSONFeature(GeoJSONFeature const& other) : geometry(other.geometry->clone()), - properties(other.properties) {} + properties(other.properties), id(other.id) {} GeoJSONFeature::GeoJSONFeature(GeoJSONFeature&& other) : geometry(std::move(other.geometry)), - properties(std::move(other.properties)) {} + properties(std::move(other.properties)), id(std::move(other.id)) {} GeoJSONFeature& GeoJSONFeature::operator=(const GeoJSONFeature& other) { @@ -262,6 +272,8 @@ const std::map& GeoJSONFeature::getProperties() const return properties; } +const std::string& GeoJSONFeature::getId() const { return id; } + // GeoJSONFeatureCollection GeoJSONFeatureCollection::GeoJSONFeatureCollection(const std::vector& f) : features(f) {} diff --git a/Sources/geos/src/io/GeoJSONReader.cpp b/Sources/geos/src/io/GeoJSONReader.cpp index 4733bbe..0f0817e 100644 --- a/Sources/geos/src/io/GeoJSONReader.cpp +++ b/Sources/geos/src/io/GeoJSONReader.cpp @@ -24,9 +24,8 @@ #include #include #include -#include -#include #include +#include #include #include @@ -99,7 +98,14 @@ GeoJSONFeature GeoJSONReader::readFeature(const geos_nlohmann::json& j) const { const auto& geometryJson = j.at("geometry"); const auto& properties = j.at("properties"); - return GeoJSONFeature{readGeometry(geometryJson), readProperties(properties)}; + + std::string id = ""; + if (j.contains("id") && !j.at("id").is_null()) { + if (j.at("id").is_string()) id = j.at("id").get(); + if (j.at("id").is_number()) id = j.at("id").dump(); + } + + return GeoJSONFeature{readGeometry(geometryJson), readProperties(properties), id}; } std::map GeoJSONReader::readProperties( @@ -204,13 +210,16 @@ geom::Coordinate GeoJSONReader::readCoordinate( const std::vector& coords) const { if (coords.size() == 1) { - throw ParseException("Expected two coordinates found one"); + throw ParseException("Expected two or three coordinates found one"); + } + else if (coords.size() == 2) { + return geom::Coordinate { coords[0], coords[1] }; } - else if (coords.size() > 2) { - throw ParseException("Expected two coordinates found more than two"); + else if (coords.size() == 3) { + return geom::Coordinate { coords[0], coords[1], coords[2] }; } else { - return geom::Coordinate {coords[0], coords[1]}; + throw ParseException("Expected two or three coordinates found more than three"); } } @@ -219,7 +228,7 @@ std::unique_ptr GeoJSONReader::readPoint( { const auto& coords = j.at("coordinates").get>(); if (coords.size() == 1) { - throw ParseException("Expected two coordinates found one"); + throw ParseException("Expected two or three coordinates found one"); } else if (coords.size() < 2) { return geometryFactory.createPoint(2); @@ -234,14 +243,14 @@ std::unique_ptr GeoJSONReader::readLineString( const geos_nlohmann::json& j) const { const auto& coords = j.at("coordinates").get>>(); - std::vector coordinates; - coordinates.reserve(coords.size()); + bool has_z = std::any_of(coords.begin(), coords.end(), [](auto v) { return v.size() > 2; }); + auto coordinates = detail::make_unique(0u, has_z, false); + coordinates->reserve(coords.size()); for (const auto& coord : coords) { const geom::Coordinate& c = readCoordinate(coord); - coordinates.push_back(c); + coordinates->add(c); } - auto coordinateSequence = geometryFactory.getCoordinateSequenceFactory()->create(std::move(coordinates)); - return geometryFactory.createLineString(std::move(coordinateSequence)); + return geometryFactory.createLineString(std::move(coordinates)); } std::unique_ptr GeoJSONReader::readPolygon( @@ -258,18 +267,18 @@ std::unique_ptr GeoJSONReader::readPolygon( std::vector> rings; rings.reserve(polygonCoords.size()); for (const auto& ring : polygonCoords) { - std::vector coordinates; - coordinates.reserve(ring.size()); + bool has_z = std::any_of(ring.begin(), ring.end(), [](auto v) { return v.size() > 2; }); + auto coordinates = detail::make_unique(0u, has_z, false); + coordinates->reserve(ring.size()); for (const auto& coord : ring) { const geom::Coordinate& c = readCoordinate(coord); - coordinates.push_back(c); + coordinates->add(c); } - auto coordinateSequence = geometryFactory.getCoordinateSequenceFactory()->create(std::move(coordinates)); if (!shell) { - shell = geometryFactory.createLinearRing(std::move(coordinateSequence)); + shell = geometryFactory.createLinearRing(std::move(coordinates)); } else { - rings.push_back(geometryFactory.createLinearRing(std::move(coordinateSequence))); + rings.push_back(geometryFactory.createLinearRing(std::move(coordinates))); } } if (!shell) { @@ -303,14 +312,14 @@ std::unique_ptr GeoJSONReader::readMultiLineString( std::vector> lines; lines.reserve(listOfCoords.size()); for (const auto& coords : listOfCoords) { - std::vector coordinates; - coordinates.reserve(coords.size()); + bool has_z = std::any_of(coords.begin(), coords.end(), [](auto v) { return v.size() > 2; }); + auto coordinates = detail::make_unique(0u, has_z, false); + coordinates->reserve(coords.size()); for (const auto& coord : coords) { const geom::Coordinate& c = readCoordinate(coord); - coordinates.push_back(geom::Coordinate{c.x, c.y}); + coordinates->add(c); } - auto coordinateSequence = geometryFactory.getCoordinateSequenceFactory()->create(std::move(coordinates)); - lines.push_back(geometryFactory.createLineString(std::move(coordinateSequence))); + lines.push_back(geometryFactory.createLineString(std::move(coordinates))); } return geometryFactory.createMultiLineString(std::move(lines)); } diff --git a/Sources/geos/src/io/GeoJSONWriter.cpp b/Sources/geos/src/io/GeoJSONWriter.cpp index 9ef0add..bf3826d 100644 --- a/Sources/geos/src/io/GeoJSONWriter.cpp +++ b/Sources/geos/src/io/GeoJSONWriter.cpp @@ -23,13 +23,15 @@ #include #include #include -#include #include #include #include #include #include +#include + +#include "geos/util.h" #define GEOS_COMPILATION @@ -39,6 +41,17 @@ using json = geos_nlohmann::ordered_json; namespace geos { namespace io { // geos.io + +/* public */ +void +GeoJSONWriter::setOutputDimension(uint8_t dims) +{ + if(dims < 2 || dims > 3) { + throw util::IllegalArgumentException("GeoJSON output dimension must be 2 or 3"); + } + defaultOutputDimension = dims; +} + std::string GeoJSONWriter::write(const geom::Geometry* geometry, GeoJSONType type) { json j; @@ -126,9 +139,13 @@ std::string GeoJSONWriter::write(const GeoJSONFeatureCollection& features) void GeoJSONWriter::encodeFeature(const GeoJSONFeature& feature, geos_nlohmann::ordered_json& j) { j["type"] = "Feature"; + + if (feature.getId().size() > 0) j["id"] = feature.getId(); + json geometryJson; encodeGeometry(feature.getGeometry(), geometryJson); j["geometry"] = geometryJson; + json propertiesJson = json::object(); for (auto const& property : feature.getProperties()) { std::string key = property.first; @@ -171,6 +188,8 @@ void GeoJSONWriter::encodeFeatureCollection(const geom::Geometry* g, geos_nlohma void GeoJSONWriter::encodeGeometry(const geom::Geometry* geometry, geos_nlohmann::ordered_json& j) { + util::ensureNoCurvedComponents(geometry); + auto type = geometry->getGeometryTypeId(); if (type == GEOS_POINT) { auto point = static_cast(geometry); @@ -210,7 +229,8 @@ void GeoJSONWriter::encodePoint(const geom::Point* point, geos_nlohmann::ordered { j["type"] = "Point"; if (!point->isEmpty()) { - j["coordinates"] = convertCoordinate(point->getCoordinate()); + auto as_coord = Coordinate { point->getX(), point->getY(), point->getZ()}; + j["coordinates"] = convertCoordinate(&as_coord); } else { j["coordinates"] = j.array(); @@ -226,7 +246,7 @@ void GeoJSONWriter::encodeLineString(const geom::LineString* line, geos_nlohmann void GeoJSONWriter::encodePolygon(const geom::Polygon* poly, geos_nlohmann::ordered_json& j) { j["type"] = "Polygon"; - std::vector>> rings; + std::vector>> rings; auto ring = poly->getExteriorRing(); rings.reserve(poly->getNumInteriorRing()+1); rings.push_back(convertCoordinateSequence(ring->getCoordinates().get())); @@ -245,7 +265,7 @@ void GeoJSONWriter::encodeMultiPoint(const geom::MultiPoint* multiPoint, geos_nl void GeoJSONWriter::encodeMultiLineString(const geom::MultiLineString* multiLineString, geos_nlohmann::ordered_json& j) { j["type"] = "MultiLineString"; - std::vector>> lines; + std::vector>> lines; lines.reserve(multiLineString->getNumGeometries()); for (size_t i = 0; i < multiLineString->getNumGeometries(); i++) { lines.push_back(convertCoordinateSequence(multiLineString->getGeometryN(i)->getCoordinates().get())); @@ -256,11 +276,11 @@ void GeoJSONWriter::encodeMultiLineString(const geom::MultiLineString* multiLine void GeoJSONWriter::encodeMultiPolygon(const geom::MultiPolygon* multiPolygon, geos_nlohmann::ordered_json& json) { json["type"] = "MultiPolygon"; - std::vector>>> polygons; + std::vector>>> polygons; polygons.reserve(multiPolygon->getNumGeometries()); for (size_t i = 0; i < multiPolygon->getNumGeometries(); i++) { const Polygon* polygon = multiPolygon->getGeometryN(i); - std::vector>> rings; + std::vector>> rings; auto ring = polygon->getExteriorRing(); rings.reserve(polygon->getNumInteriorRing() + 1); rings.push_back(convertCoordinateSequence(ring->getCoordinates().get())); @@ -284,15 +304,18 @@ void GeoJSONWriter::encodeGeometryCollection(const geom::GeometryCollection* g, j["geometries"] = geometryArray; } -std::pair GeoJSONWriter::convertCoordinate(const Coordinate* c) +std::vector GeoJSONWriter::convertCoordinate(const Coordinate* c) { - return std::make_pair(c->x, c->y); + if (std::isnan(c->z) || defaultOutputDimension == 2) { + return std::vector { c->x, c->y }; + } + return std::vector { c->x, c->y, c->z }; } -std::vector> GeoJSONWriter::convertCoordinateSequence(const CoordinateSequence* +std::vector> GeoJSONWriter::convertCoordinateSequence(const CoordinateSequence* coordinateSequence) { - std::vector> coordinates; + std::vector> coordinates; coordinates.reserve(coordinateSequence->size()); for (size_t i = 0; isize(); i++) { const geom::Coordinate& c = coordinateSequence->getAt(i); diff --git a/Sources/geos/src/io/StringTokenizer.cpp b/Sources/geos/src/io/StringTokenizer.cpp index 5735913..e016299 100644 --- a/Sources/geos/src/io/StringTokenizer.cpp +++ b/Sources/geos/src/io/StringTokenizer.cpp @@ -154,10 +154,8 @@ StringTokenizer::peekNextToken() return str[pos]; } - // It's either a Number or a Word, let's - // see when it ends - - pos = str.find_first_of("\n\r\t() ,", static_cast(iter - str.begin())); + // It's either a Number or a Word, let's see when it ends + pos = str.find_first_of("\n\r\t() ,", pos + 1); if(pos == string::npos) { if(iter != str.end()) { diff --git a/Sources/geos/src/io/WKBReader.cpp b/Sources/geos/src/io/WKBReader.cpp index a4d8d1f..5410ab0 100644 --- a/Sources/geos/src/io/WKBReader.cpp +++ b/Sources/geos/src/io/WKBReader.cpp @@ -20,19 +20,24 @@ #include #include #include +#include +#include +#include +#include #include #include #include #include #include #include +#include #include #include #include -#include +#include #include -#include #include +#include #include #include @@ -59,6 +64,7 @@ WKBReader::WKBReader() : WKBReader(*(GeometryFactory::getDefaultInstance())) {} + void WKBReader::setFixStructure(bool doFixStructure) { @@ -179,7 +185,7 @@ WKBReader::readHEX(std::istream& is) } void -WKBReader::minMemSize(int geomType, uint64_t size) +WKBReader::minMemSize(geom::GeometryTypeId geomType, uint64_t size) const { uint64_t minSize = 0; constexpr uint64_t minCoordSize = 2 * sizeof(double); @@ -192,18 +198,24 @@ WKBReader::minMemSize(int geomType, uint64_t size) switch(geomType) { case GEOS_LINESTRING: case GEOS_LINEARRING: + case GEOS_CIRCULARSTRING: + case GEOS_COMPOUNDCURVE: + case GEOS_POINT: minSize = size * minCoordSize; break; case GEOS_POLYGON: + case GEOS_CURVEPOLYGON: minSize = size * minRingSize; break; case GEOS_MULTIPOINT: minSize = size * minPtSize; break; case GEOS_MULTILINESTRING: + case GEOS_MULTICURVE: minSize = size * minLineSize; break; case GEOS_MULTIPOLYGON: + case GEOS_MULTISURFACE: minSize = size * minPolySize; break; case GEOS_GEOMETRYCOLLECTION: @@ -310,9 +322,18 @@ WKBReader::readGeometry() case WKBConstants::wkbLineString : result = readLineString(); break; + case WKBConstants::wkbCircularString : + result = readCircularString(); + break; + case WKBConstants::wkbCompoundCurve : + result = readCompoundCurve(); + break; case WKBConstants::wkbPolygon : result = readPolygon(); break; + case WKBConstants::wkbCurvePolygon : + result = readCurvePolygon(); + break; case WKBConstants::wkbMultiPoint : result = readMultiPoint(); break; @@ -325,6 +346,12 @@ WKBReader::readGeometry() case WKBConstants::wkbGeometryCollection : result = readGeometryCollection(); break; + case WKBConstants::wkbMultiCurve : + result = readMultiCurve(); + break; + case WKBConstants::wkbMultiSurface : + result = readMultiSurface(); + break; default: std::stringstream err; err << "Unknown WKB type " << geometryType; @@ -338,19 +365,15 @@ WKBReader::readGeometry() std::unique_ptr WKBReader::readPoint() { - readCoordinate(); + auto seq = readCoordinateSequence(1); // POINT EMPTY - if (std::isnan(ordValues[0]) && std::isnan(ordValues[1])) { - return std::unique_ptr(factory.createPoint(hasZ ? 3 : 2)); + const CoordinateXY& coord = seq->getAt(0); + if (std::isnan(coord.x) && std::isnan(coord.y)) { + seq->clear(); } - if (hasZ) { - return std::unique_ptr(factory.createPoint(Coordinate(ordValues[0], ordValues[1], ordValues[2]))); - } - else { - return std::unique_ptr(factory.createPoint(Coordinate(ordValues[0], ordValues[1]))); - } + return factory.createPoint(std::move(seq)); } std::unique_ptr @@ -376,13 +399,35 @@ WKBReader::readLinearRing() auto pts = readCoordinateSequence(size); // Replace unclosed ring with closed if (fixStructure && !pts->isRing()) { - std::unique_ptr cas(new CoordinateArraySequence(*pts)); - cas->closeRing(); - pts.reset(cas.release()); + pts->closeRing(); } return factory.createLinearRing(std::move(pts)); } +std::unique_ptr +WKBReader::readCircularString() +{ + uint32_t size = dis.readUnsigned(); + minMemSize(GEOS_CIRCULARSTRING, size); + auto pts = readCoordinateSequence(size); + return factory.createCircularString(std::move(pts)); +} + +std::unique_ptr +WKBReader::readCompoundCurve() +{ + auto numCurves = dis.readUnsigned(); + minMemSize(GEOS_COMPOUNDCURVE, numCurves); + + std::vector> curves(numCurves); + + for (std::uint32_t i = 0; i < numCurves; i++) { + curves[i] = readChild(); + } + + return factory.createCompoundCurve(std::move(curves)); +} + std::unique_ptr WKBReader::readPolygon() { @@ -393,12 +438,14 @@ WKBReader::readPolygon() std::size_t << "WKB numRings: " << numRings << std::endl; #endif - if(numRings == 0) { - return factory.createPolygon(hasZ ? 3 : 2); + std::unique_ptr shell; + if (numRings == 0) { + auto coords = detail::make_unique(0u, hasZ, hasM); + shell = factory.createLinearRing(std::move(coords)); + } else { + shell = readLinearRing(); } - std::unique_ptr shell(readLinearRing()); - if(numRings > 1) { std::vector> holes(numRings - 1); for(uint32_t i = 0; i < numRings - 1; i++) { @@ -407,9 +454,38 @@ WKBReader::readPolygon() return factory.createPolygon(std::move(shell), std::move(holes)); } + return factory.createPolygon(std::move(shell)); } +std::unique_ptr +WKBReader::readCurvePolygon() +{ + uint32_t numRings = dis.readUnsigned(); + minMemSize(GEOS_POLYGON, numRings); + +#if DEBUG_WKB_READER + std::size_t << "WKB numRings: " << numRings << std::endl; +#endif + + if (numRings == 0) { + return factory.createCurvePolygon(hasZ, hasM); + } + + auto shell = readChild(); + + if(numRings > 1) { + std::vector> holes(numRings - 1); + for(uint32_t i = 0; i < numRings - 1; i++) { + holes[i] = readChild(); + } + + return factory.createCurvePolygon(std::move(shell), std::move(holes)); + } + + return factory.createCurvePolygon(std::move(shell)); +} + std::unique_ptr WKBReader::readMultiPoint() { @@ -418,12 +494,7 @@ WKBReader::readMultiPoint() std::vector> geoms(numGeoms); for(uint32_t i = 0; i < numGeoms; i++) { - geoms[i] = readGeometry(); - if(!dynamic_cast(geoms[i].get())) { - std::stringstream err; - err << BAD_GEOM_TYPE_MSG << " MultiPoint"; - throw ParseException(err.str()); - } + geoms[i] = readChild(); } return factory.createMultiPoint(std::move(geoms)); @@ -437,12 +508,7 @@ WKBReader::readMultiLineString() std::vector> geoms(numGeoms); for(uint32_t i = 0; i < numGeoms; i++) { - geoms[i] = readGeometry(); - if(!dynamic_cast(geoms[i].get())) { - std::stringstream err; - err << BAD_GEOM_TYPE_MSG << " LineString"; - throw ParseException(err.str()); - } + geoms[i] = readChild(); } return factory.createMultiLineString(std::move(geoms)); @@ -456,17 +522,40 @@ WKBReader::readMultiPolygon() std::vector> geoms(numGeoms); for(uint32_t i = 0; i < numGeoms; i++) { - geoms[i] = readGeometry(); - if(!dynamic_cast(geoms[i].get())) { - std::stringstream err; - err << BAD_GEOM_TYPE_MSG << " Polygon"; - throw ParseException(err.str()); - } + geoms[i] = readChild(); } return factory.createMultiPolygon(std::move(geoms)); } +std::unique_ptr +WKBReader::readMultiCurve() +{ + uint32_t numGeoms = dis.readUnsigned(); + minMemSize(GEOS_MULTICURVE, numGeoms); + std::vector> geoms(numGeoms); + + for(uint32_t i = 0; i < numGeoms; i++) { + geoms[i] = readChild(); + } + + return factory.createMultiCurve(std::move(geoms)); +} + +std::unique_ptr +WKBReader::readMultiSurface() +{ + uint32_t numGeoms = dis.readUnsigned(); + minMemSize(GEOS_MULTISURFACE, numGeoms); + std::vector> geoms(numGeoms); + + for(uint32_t i = 0; i < numGeoms; i++) { + geoms[i] = readChild(); + } + + return factory.createMultiSurface(std::move(geoms)); +} + std::unique_ptr WKBReader::readGeometryCollection() { @@ -485,16 +574,23 @@ std::unique_ptr WKBReader::readCoordinateSequence(uint32_t size) { minMemSize(GEOS_LINESTRING, size); - unsigned int targetDim = 2 + (hasZ ? 1 : 0); - auto seq = factory.getCoordinateSequenceFactory()->create(size, targetDim); - if(targetDim > inputDimension) { - targetDim = inputDimension; - } + auto seq = detail::make_unique(size, hasZ, hasM, false); + + CoordinateXYZM coord(0, 0, DoubleNotANumber, DoubleNotANumber); for(uint32_t i = 0; i < size; i++) { readCoordinate(); - for(unsigned int j = 0; j < targetDim; j++) { - seq->setOrdinate(i, j, ordValues[j]); + + unsigned int j = 0; + coord.x = ordValues[j++]; + coord.y = ordValues[j++]; + if (hasZ) { + coord.z = ordValues[j++]; + } + if (hasM) { + coord.m = ordValues[j++]; } + + seq->setAt(coord, i); } return seq; } @@ -503,17 +599,13 @@ void WKBReader::readCoordinate() { const PrecisionModel& pm = *factory.getPrecisionModel(); + for(std::size_t i = 0; i < inputDimension; ++i) { if (i < 2) { ordValues[i] = pm.makePrecise(dis.readDouble()); - } - else if (hasZ) { + } else { ordValues[i] = dis.readDouble(); } - else { - // Read and throw away any extra (M) dimensions - dis.readDouble(); - } } #if DEBUG_WKB_READER std::size_t << "WKB coordinate: " << ordValues[0] << "," << ordValues[1] << std::endl; diff --git a/Sources/geos/src/io/WKBStreamReader.cpp b/Sources/geos/src/io/WKBStreamReader.cpp new file mode 100644 index 0000000..b705220 --- /dev/null +++ b/Sources/geos/src/io/WKBStreamReader.cpp @@ -0,0 +1,58 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include // for unique_ptr + +#include + +using geos::geom::Geometry; + +namespace geos { +namespace io { + +WKBStreamReader::WKBStreamReader(std::istream& p_instr) + : instr(p_instr) +{ +} + +WKBStreamReader::~WKBStreamReader() { + +} + +/*public*/ + + +/* +Return: nullptr if at EOF +*/ +std::unique_ptr +WKBStreamReader::next() +{ + std::string line; + std::getline(instr, line); + if (! instr) { + return nullptr; + } + std::istringstream hex(line); + auto g = rdr.readHEX( hex ); + return g; +} + +} +} diff --git a/Sources/geos/src/io/WKBWriter.cpp b/Sources/geos/src/io/WKBWriter.cpp index 23424d1..f7c83d3 100644 --- a/Sources/geos/src/io/WKBWriter.cpp +++ b/Sources/geos/src/io/WKBWriter.cpp @@ -19,8 +19,11 @@ #include #include #include +#include #include #include +#include +#include #include #include #include @@ -29,13 +32,14 @@ #include #include #include -#include #include #include #include #include +#include "geos/util.h" + #undef DEBUG_WKB_WRITER @@ -46,15 +50,15 @@ namespace io { // geos.io WKBWriter::WKBWriter(uint8_t dims, int bo, bool srid, int flv) : defaultOutputDimension(dims) + , outputOrdinates(getOutputOrdinates(OrdinateSet::createXYZM())) , byteOrder(bo) , flavor(flv) , includeSRID(srid) , outStream(nullptr) { - if(dims < 2 || dims > 3) { - throw util::IllegalArgumentException("WKB output dimension must be 2 or 3"); + if(dims < 2 || dims > 4) { + throw util::IllegalArgumentException("WKB output dimension must be 2, 3, or 4"); } - outputDimension = defaultOutputDimension; } @@ -62,8 +66,8 @@ WKBWriter::WKBWriter(uint8_t dims, int bo, bool srid, int flv) void WKBWriter::setOutputDimension(uint8_t dims) { - if(dims < 2 || dims > 3) { - throw util::IllegalArgumentException("WKB output dimension must be 2 or 3"); + if(dims < 2 || dims > 4) { + throw util::IllegalArgumentException("WKB output dimension must be 2, 3, or 4"); } defaultOutputDimension = dims; } @@ -97,43 +101,28 @@ WKBWriter::writeHEX(const Geometry& g, std::ostream& os) void WKBWriter::write(const Geometry& g, std::ostream& os) { - outputDimension = defaultOutputDimension; - if (outputDimension > g.getCoordinateDimension()) { - outputDimension = g.getCoordinateDimension(); - } + OrdinateSet inputOrdinates = OrdinateSet::createXY(); + inputOrdinates.setM(g.hasM()); + inputOrdinates.setZ(g.hasZ()); + outputOrdinates = getOutputOrdinates(inputOrdinates); outStream = &os; - if (const Point* x = dynamic_cast(&g)) { - return writePoint(*x); - } - - if (const LineString* x = dynamic_cast(&g)) { - return writeLineString(*x); - } - - if (const Polygon* x = dynamic_cast(&g)) { - return writePolygon(*x); - } - - if (const MultiPoint* x = dynamic_cast(&g)) { - return writeGeometryCollection(*x, WKBConstants::wkbMultiPoint); - } - - if (const MultiLineString* x = dynamic_cast(&g)) { - return writeGeometryCollection(*x, WKBConstants::wkbMultiLineString); - } - - if (const MultiPolygon* x = dynamic_cast(&g)) { - return writeGeometryCollection(*x, WKBConstants::wkbMultiPolygon); - } - - if (const GeometryCollection* x = - dynamic_cast(&g)) { - return writeGeometryCollection(*x, WKBConstants::wkbGeometryCollection); + switch(g.getGeometryTypeId()) { + case GEOS_POINT: writePoint(static_cast(g)); break; + case GEOS_LINESTRING: + case GEOS_LINEARRING: + case GEOS_CIRCULARSTRING: writeSimpleCurve(static_cast(g)); break; + case GEOS_COMPOUNDCURVE: writeCompoundCurve(static_cast(g)); break; + case GEOS_POLYGON: writePolygon(static_cast(g)); break; + case GEOS_CURVEPOLYGON: writeCurvePolygon(static_cast(g)); break; + case GEOS_MULTIPOINT: + case GEOS_MULTILINESTRING: + case GEOS_MULTIPOLYGON: + case GEOS_MULTICURVE: + case GEOS_MULTISURFACE: + case GEOS_GEOMETRYCOLLECTION: writeGeometryCollection(static_cast(g)); break; } - - assert(0); // Unknown Geometry type } void @@ -144,7 +133,7 @@ WKBWriter::writePointEmpty(const Point& g) writeSRID(g.getSRID()); Coordinate c(DoubleNotANumber, DoubleNotANumber, DoubleNotANumber); - CoordinateArraySequence cas(std::size_t(1), std::size_t(g.getCoordinateDimension())); + CoordinateSequence cas(std::size_t(1), std::size_t(g.getCoordinateDimension())); cas.setAt(c, 0); writeCoordinateSequence(cas, false); @@ -168,11 +157,11 @@ WKBWriter::writePoint(const Point& g) } void -WKBWriter::writeLineString(const LineString& g) +WKBWriter::writeSimpleCurve(const SimpleCurve& g) { writeByteOrder(); - writeGeometryType(WKBConstants::wkbLineString, g.getSRID()); + writeGeometryType(getWkbType(g), g.getSRID()); writeSRID(g.getSRID()); const CoordinateSequence* cs = g.getCoordinatesRO(); @@ -180,12 +169,33 @@ WKBWriter::writeLineString(const LineString& g) writeCoordinateSequence(*cs, true); } +void +WKBWriter::writeCompoundCurve(const CompoundCurve& g) +{ + writeByteOrder(); + + writeGeometryType(getWkbType(g), g.getSRID()); + writeSRID(g.getSRID()); + + writeInt(static_cast(g.getNumCurves())); + + auto orig_includeSRID = includeSRID; + includeSRID = false; + + for (std::size_t i = 0; i < g.getNumCurves(); i++) { + const SimpleCurve& section = *g.getCurveN(i); + writeSimpleCurve(section); + } + + includeSRID = orig_includeSRID; +} + void WKBWriter::writePolygon(const Polygon& g) { writeByteOrder(); - writeGeometryType(WKBConstants::wkbPolygon, g.getSRID()); + writeGeometryType(getWkbType(g), g.getSRID()); writeSRID(g.getSRID()); if (g.isEmpty()) { @@ -215,12 +225,42 @@ WKBWriter::writePolygon(const Polygon& g) } void -WKBWriter::writeGeometryCollection(const GeometryCollection& g, - int wkbtype) +WKBWriter::writeCurvePolygon(const CurvePolygon& g) { + // Why not combine this with writePolygon? + // A CurvePolygon differs from a Polygon in that its rings + // can one of three different types. Therefore, the type + // information is written for each ring, unlike a Polygon. + writeByteOrder(); - writeGeometryType(wkbtype, g.getSRID()); + writeGeometryType(getWkbType(g), g.getSRID()); + + writeSRID(g.getSRID()); + + if (g.isEmpty()) { + writeInt(0); + return; + } + + std::size_t nholes = g.getNumInteriorRing(); + writeInt(static_cast(nholes + 1)); + + const Curve* ring = g.getExteriorRing(); + write(*ring, *outStream); + + for(std::size_t i = 0; i < nholes; i++) { + ring = g.getInteriorRingN(i); + write(*ring, *outStream); + } +} + +void +WKBWriter::writeGeometryCollection(const GeometryCollection& g) +{ + writeByteOrder(); + + writeGeometryType(getWkbType(g), g.getSRID()); writeSRID(g.getSRID()); auto ngeoms = g.getNumGeometries(); @@ -272,16 +312,26 @@ void WKBWriter::writeGeometryType(int typeId, int SRID) { if (flavor == WKBConstants::wkbExtended) { - int flag3D = (outputDimension == 3) ? int(0x80000000) : 0; - typeId |= flag3D; + int dimFlag = 0; + if (outputOrdinates.hasZ()) { + dimFlag |= static_cast(0x80000000); + } + if (outputOrdinates.hasM()) { + dimFlag |= static_cast(0x40000000); + } + + typeId |= dimFlag; if(includeSRID && SRID != 0) { typeId |= 0x20000000; } } else if (flavor == WKBConstants::wkbIso) { - if (outputDimension == 3) { + if (outputOrdinates.hasZ()) { typeId += 1000; } + if (outputOrdinates.hasM()) { + typeId += 2000; + } } else { throw util::IllegalArgumentException("Unknown WKB flavor"); @@ -317,38 +367,76 @@ WKBWriter::writeCoordinateSequence(const CoordinateSequence& cs, bool sized) { std::size_t size = cs.getSize(); - bool is3d = false; - if(outputDimension > 2) { - is3d = true; - } if(sized) { writeInt(static_cast(size)); } for(std::size_t i = 0; i < size; i++) { - writeCoordinate(cs, i, is3d); + writeCoordinate(cs, i); } } void -WKBWriter::writeCoordinate(const CoordinateSequence& cs, std::size_t idx, - bool is3d) +WKBWriter::writeCoordinate(const CoordinateSequence& cs, std::size_t idx) { #if DEBUG_WKB_WRITER std::size_t << "writeCoordinate: X:" << cs.getX(idx) << " Y:" << cs.getY(idx) << std::endl; #endif assert(outStream); - ByteOrderValues::putDouble(cs.getX(idx), buf, byteOrder); + CoordinateXYZM coord(DoubleNotANumber, DoubleNotANumber, DoubleNotANumber, DoubleNotANumber); + cs.getAt(idx, coord); + + ByteOrderValues::putDouble(coord.x, buf, byteOrder); outStream->write(reinterpret_cast(buf), 8); - ByteOrderValues::putDouble(cs.getY(idx), buf, byteOrder); + ByteOrderValues::putDouble(coord.y, buf, byteOrder); outStream->write(reinterpret_cast(buf), 8); - if(is3d) { - ByteOrderValues::putDouble( - cs.getOrdinate(idx, CoordinateSequence::Z), - buf, byteOrder); + if(outputOrdinates.hasZ()) { + ByteOrderValues::putDouble(coord.z, buf, byteOrder); outStream->write(reinterpret_cast(buf), 8); } + if(outputOrdinates.hasM()) { + ByteOrderValues::putDouble(coord.m, buf, byteOrder); + outStream->write(reinterpret_cast(buf), 8); + } +} + +OrdinateSet +WKBWriter::getOutputOrdinates(OrdinateSet ordinates) +{ + // drop specified ordinates to meet defaultOutputDimension + OrdinateSet newOrdinates = ordinates; + while (newOrdinates.size() > defaultOutputDimension) { + if (newOrdinates.hasM()) { + newOrdinates.setM(false); + } else if (newOrdinates.hasZ()) { + newOrdinates.setZ(false); + } + } + + return newOrdinates; +} + +int +WKBWriter::getWkbType(const Geometry& g) { + switch(g.getGeometryTypeId()) { + case GEOS_POINT: return WKBConstants::wkbPoint; + case GEOS_LINESTRING: + case GEOS_LINEARRING: return WKBConstants::wkbLineString; + case GEOS_CIRCULARSTRING: return WKBConstants::wkbCircularString; + case GEOS_COMPOUNDCURVE: return WKBConstants::wkbCompoundCurve; + case GEOS_POLYGON: return WKBConstants::wkbPolygon; + case GEOS_CURVEPOLYGON: return WKBConstants::wkbCurvePolygon; + case GEOS_MULTIPOINT: return WKBConstants::wkbMultiPoint; + case GEOS_MULTILINESTRING: return WKBConstants::wkbMultiLineString; + case GEOS_MULTICURVE: return WKBConstants::wkbMultiCurve; + case GEOS_MULTIPOLYGON: return WKBConstants::wkbMultiPolygon; + case GEOS_MULTISURFACE: return WKBConstants::wkbMultiSurface; + case GEOS_GEOMETRYCOLLECTION: return WKBConstants::wkbGeometryCollection; + } + + // Avoid -Wreturn-type warning + throw util::IllegalArgumentException("Invalid geometry type."); } diff --git a/Sources/geos/src/io/WKTFileReader.cpp b/Sources/geos/src/io/WKTFileReader.cpp new file mode 100644 index 0000000..9a7c1e3 --- /dev/null +++ b/Sources/geos/src/io/WKTFileReader.cpp @@ -0,0 +1,87 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include // for unique_ptr +#include + +#include + +using namespace geos::geom; + +namespace geos { +namespace io { + +WKTFileReader::WKTFileReader() +{ + +} + +WKTFileReader::~WKTFileReader() { + +} + +/*public*/ +std::vector> +WKTFileReader::read(std::string fname) +{ + std::ifstream f( fname ); + std::vector> geoms; + geos::io::WKTReader rdr; + + while (true) { + auto g = readGeom( f, rdr ); + if (g == nullptr) { + break; + } + geoms.push_back(std::move(g)); + } + f.close(); + + return geoms; +} + +/* +Return: nullptr if at EOF +*/ +std::unique_ptr +WKTFileReader::readGeom(std::ifstream& f, geos::io::WKTReader& rdr) +{ + std::string wkt = ""; + + std::string::difference_type lParen = 0; + std::string::difference_type rParen = 0; + do { + std::string line; + std::getline(f, line); + if (! f) { + return nullptr; + } + + lParen += std::count(line.begin(), line.end(), '('); + rParen += std::count(line.begin(), line.end(), ')'); + + wkt += line; + } while (lParen == 0 || lParen != rParen); + + auto g = rdr.read( wkt.c_str() ); + return g; +} + +} +} diff --git a/Sources/geos/src/io/WKTReader.cpp b/Sources/geos/src/io/WKTReader.cpp index 9147fe8..fc850d2 100644 --- a/Sources/geos/src/io/WKTReader.cpp +++ b/Sources/geos/src/io/WKTReader.cpp @@ -21,20 +21,23 @@ #include #include #include -#include #include +#include +#include #include #include #include #include +#include #include #include +#include #include -#include +#include #include -#include -#include +#include #include +#include #include #include @@ -51,55 +54,68 @@ WKTReader::read(const std::string& wellKnownText) const { CLocalizer clocale; StringTokenizer tokenizer(wellKnownText); - return readGeometryTaggedText(&tokenizer); + OrdinateSet ordinateFlags = OrdinateSet::createXY(); + auto ret = readGeometryTaggedText(&tokenizer, ordinateFlags); + + if (tokenizer.peekNextToken() != StringTokenizer::TT_EOF) { + tokenizer.nextToken(); + throw ParseException("Unexpected text after end of geometry"); + } + + return ret; } std::unique_ptr -WKTReader::getCoordinates(StringTokenizer* tokenizer) const +WKTReader::getCoordinates(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const { - std::size_t dim = 2; - std::string nextToken = getNextEmptyOrOpener(tokenizer, dim); + std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags); if(nextToken == "EMPTY") { - return geometryFactory->getCoordinateSequenceFactory()->create(std::size_t(0), dim); + return detail::make_unique(0u, ordinateFlags.hasZ(), ordinateFlags.hasM()); } - Coordinate coord; - getPreciseCoordinate(tokenizer, coord, dim); - auto coordinates = detail::make_unique(0u, dim); + CoordinateXYZM coord(0, 0, DoubleNotANumber, DoubleNotANumber); + getPreciseCoordinate(tokenizer, ordinateFlags, coord); + + auto coordinates = detail::make_unique(0u, ordinateFlags.hasZ(), ordinateFlags.hasM()); coordinates->add(coord); nextToken = getNextCloserOrComma(tokenizer); while(nextToken == ",") { - getPreciseCoordinate(tokenizer, coord, dim); + getPreciseCoordinate(tokenizer, ordinateFlags, coord); coordinates->add(coord); nextToken = getNextCloserOrComma(tokenizer); } - return RETURN_UNIQUE_PTR(coordinates); + return coordinates; } - void WKTReader::getPreciseCoordinate(StringTokenizer* tokenizer, - Coordinate& coord, - size_t& dim) const -{ + OrdinateSet& ordinateFlags, + CoordinateXYZM& coord) const { coord.x = getNextNumber(tokenizer); coord.y = getNextNumber(tokenizer); - if(isNumberNext(tokenizer)) { - coord.z = getNextNumber(tokenizer); - dim = 3; - // If there is a fourth value (M) read and discard it. - if(isNumberNext(tokenizer)) { - getNextNumber(tokenizer); - } + // Check for undeclared Z dimension + if (ordinateFlags.changesAllowed() && isNumberNext(tokenizer)) { + ordinateFlags.setZ(true); + } + if (ordinateFlags.hasZ()) { + coord.z = getNextNumber(tokenizer); } - else { - coord.z = DoubleNotANumber; - dim = 2; + + // Check for undeclared M dimension + if (ordinateFlags.changesAllowed() && ordinateFlags.hasZ() && isNumberNext(tokenizer)) { + ordinateFlags.setM(true); + } + + if (ordinateFlags.hasM()) { + coord.m = getNextNumber(tokenizer); } + + ordinateFlags.setChangesAllowed(false); // First coordinate read; future coordinates must be consistent + precisionModel->makePrecise(coord); } @@ -109,6 +125,12 @@ WKTReader::isNumberNext(StringTokenizer* tokenizer) return tokenizer->peekNextToken() == StringTokenizer::TT_NUMBER; } +bool +WKTReader::isOpenerNext(StringTokenizer* tokenizer) +{ + return tokenizer->peekNextToken() == '('; +} + double WKTReader::getNextNumber(StringTokenizer* tokenizer) { @@ -133,25 +155,61 @@ WKTReader::getNextNumber(StringTokenizer* tokenizer) return 0; } +bool +WKTReader::isTypeName(const std::string & type, const std::string & typeName) { + return util::startsWith(type, typeName); +} + +void +WKTReader::readOrdinateFlags(const std::string & s, OrdinateSet& ordinateFlags) { + if (util::endsWith(s, "ZM")) { + ordinateFlags.setM(true); + ordinateFlags.setZ(true); + ordinateFlags.setChangesAllowed(false); + } else if (util::endsWith(s, 'M')) { + ordinateFlags.setM(true); + ordinateFlags.setChangesAllowed(false); + } else if (util::endsWith(s, 'Z')) { + ordinateFlags.setZ(true); + ordinateFlags.setChangesAllowed(false); + } +} + std::string -WKTReader::getNextEmptyOrOpener(StringTokenizer* tokenizer, std::size_t& dim) +WKTReader::getNextEmptyOrOpener(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) { std::string nextWord = getNextWord(tokenizer); - // Skip the Z, M or ZM of an SF1.2 3/4 dim coordinate. - if(nextWord == "Z" || nextWord == "ZM") { - dim = 3; - } + bool flagsModified = false; // Skip the Z, M or ZM of an SF1.2 3/4 dim coordinate. - if(nextWord == "Z" || nextWord == "M" || nextWord == "ZM") { + if (nextWord == "ZM") { + ordinateFlags.setZ(true); + ordinateFlags.setM(true); + flagsModified = true; nextWord = getNextWord(tokenizer); + } else { + if (nextWord == "Z") { + ordinateFlags.setZ(true); + flagsModified = true; + nextWord = getNextWord(tokenizer); + } + + if (nextWord == "M") { + ordinateFlags.setM(true); + flagsModified = true; + nextWord = getNextWord(tokenizer); + } + } + + if (flagsModified) { + ordinateFlags.setChangesAllowed(false); } if(nextWord == "EMPTY" || nextWord == "(") { return nextWord; } - throw ParseException("Expected 'Z', 'M', 'ZM', 'EMPTY' or '(' but encountered ", nextWord); + throw ParseException("Expected 'Z', 'M', 'ZM', 'EMPTY' or '(' but encountered ", nextWord); } std::string @@ -207,76 +265,163 @@ WKTReader::getNextWord(StringTokenizer* tokenizer) } std::unique_ptr -WKTReader::readGeometryTaggedText(StringTokenizer* tokenizer) const +WKTReader::readGeometryTaggedText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags, const GeometryTypeId* emptyType) const { std::string type = getNextWord(tokenizer); - if(type == "POINT") { - return readPointText(tokenizer); + + std::unique_ptr geom; + OrdinateSet origFlags = ordinateFlags; + + OrdinateSet newFlags = OrdinateSet::createXY(); + if (type == "EMPTY") { + newFlags = origFlags; + } else { + readOrdinateFlags(type, newFlags); + } + + if(isTypeName(type, "POINT")) { + geom = readPointText(tokenizer, newFlags); } - else if(type == "LINESTRING") { - return readLineStringText(tokenizer); + else if(isTypeName(type, "LINESTRING")) { + geom = readLineStringText(tokenizer, newFlags); } - else if(type == "LINEARRING") { - return readLinearRingText(tokenizer); + else if(isTypeName(type, "LINEARRING")) { + geom = readLinearRingText(tokenizer, newFlags); } - else if(type == "POLYGON") { - return readPolygonText(tokenizer); + else if(isTypeName(type, "CIRCULARSTRING")) { + geom = readCircularStringText(tokenizer, newFlags); } - else if(type == "MULTIPOINT") { - return readMultiPointText(tokenizer); + else if(isTypeName(type, "COMPOUNDCURVE")) { + geom = readCompoundCurveText(tokenizer, newFlags); } - else if(type == "MULTILINESTRING") { - return readMultiLineStringText(tokenizer); + else if(isTypeName(type, "POLYGON")) { + geom = readPolygonText(tokenizer, newFlags); } - else if(type == "MULTIPOLYGON") { - return readMultiPolygonText(tokenizer); + else if(isTypeName(type, "CURVEPOLYGON")) { + geom = readCurvePolygonText(tokenizer, newFlags); } - else if(type == "GEOMETRYCOLLECTION") { - return readGeometryCollectionText(tokenizer); + else if(isTypeName(type, "MULTIPOINT")) { + geom = readMultiPointText(tokenizer, newFlags); + } + else if(isTypeName(type, "MULTILINESTRING")) { + geom = readMultiLineStringText(tokenizer, newFlags); + } + else if(isTypeName(type, "MULTICURVE")) { + geom = readMultiCurveText(tokenizer, newFlags); + } + else if(isTypeName(type, "MULTIPOLYGON")) { + geom = readMultiPolygonText(tokenizer, newFlags); + } + else if(isTypeName(type, "MULTISURFACE")) { + geom = readMultiSurfaceText(tokenizer, newFlags); + } + else if(isTypeName(type, "GEOMETRYCOLLECTION")) { + geom = readGeometryCollectionText(tokenizer, newFlags); + } else if (type == "EMPTY" && emptyType != nullptr) { + return geometryFactory->createEmptyGeometry(*emptyType, newFlags.hasZ(), newFlags.hasM()); + } else { + throw ParseException("Unknown type", type); } - throw ParseException("Unknown type", type); -} -std::unique_ptr -WKTReader::readPointText(StringTokenizer* tokenizer) const -{ - std::size_t dim = 2; - std::string nextToken = getNextEmptyOrOpener(tokenizer, dim); - if(nextToken == "EMPTY") { - return geometryFactory->createPoint(dim); + if (!origFlags.changesAllowed() && newFlags != origFlags) { + throw ParseException("Cannot mix dimensionality in a geometry."); } - Coordinate coord; - getPreciseCoordinate(tokenizer, coord, dim); - getNextCloser(tokenizer); + return geom; + +} - return std::unique_ptr(geometryFactory->createPoint(coord)); +std::unique_ptr +WKTReader::readPointText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const +{ + auto&& coords = getCoordinates(tokenizer, ordinateFlags); + return geometryFactory->createPoint(std::move(coords)); } std::unique_ptr -WKTReader::readLineStringText(StringTokenizer* tokenizer) const +WKTReader::readLineStringText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const { - auto&& coords = getCoordinates(tokenizer); + auto&& coords = getCoordinates(tokenizer, ordinateFlags); return geometryFactory->createLineString(std::move(coords)); } std::unique_ptr -WKTReader::readLinearRingText(StringTokenizer* tokenizer) const +WKTReader::readLinearRingText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const { - auto&& coords = getCoordinates(tokenizer); + auto&& coords = getCoordinates(tokenizer, ordinateFlags); if (fixStructure && !coords->isRing()) { - std::unique_ptr cas(new CoordinateArraySequence(*coords)); - cas->closeRing(); - coords.reset(cas.release()); + coords->closeRing(); } return geometryFactory->createLinearRing(std::move(coords)); } +std::unique_ptr +WKTReader::readCircularStringText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const +{ + auto&& coords = getCoordinates(tokenizer, ordinateFlags); + return geometryFactory->createCircularString(std::move(coords)); +} + +std::unique_ptr +WKTReader::readCurveText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const +{ + int type = tokenizer->peekNextToken(); + if (type == '(') { + return readLineStringText(tokenizer, ordinateFlags); + } + + GeometryTypeId defaultType = GEOS_LINESTRING; + auto component = readGeometryTaggedText(tokenizer, ordinateFlags, &defaultType); + if (dynamic_cast(component.get())) { + return std::unique_ptr(static_cast(component.release())); + } + + throw ParseException("Expected LINESTRING/CIRCULARSTRING/COMPOUNDCURVE but got " + component->getGeometryType()); +} + +std::unique_ptr +WKTReader::readSurfaceText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const +{ + int type = tokenizer->peekNextToken(); + if (type == '(') { + return readPolygonText(tokenizer, ordinateFlags); + } + + GeometryTypeId defaultType = GEOS_POLYGON; + auto component = readGeometryTaggedText(tokenizer, ordinateFlags, &defaultType); + if (dynamic_cast(component.get())) { + return component; + } + + throw ParseException("Expected POLYGON or CURVEPOLYGON but got " + component->getGeometryType()); +} + +std::unique_ptr +WKTReader::readCompoundCurveText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const +{ + std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags); + if (nextToken == "EMPTY") { + return geometryFactory->createCompoundCurve(); + } + + std::vector> curves; + do { + auto curve = readCurveText(tokenizer, ordinateFlags); + if (dynamic_cast(curve.get())) { + curves.emplace_back(static_cast(curve.release())); + } else { + throw ParseException("Expected LINESTRING or CIRCULARSTRING but got " + curve->getGeometryType()); + } + nextToken = getNextCloserOrComma(tokenizer); + } while (nextToken == ","); + + return geometryFactory->createCompoundCurve(std::move(curves)); +} + std::unique_ptr -WKTReader::readMultiPointText(StringTokenizer* tokenizer) const +WKTReader::readMultiPointText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const { - std::size_t dim = 2; - std::string nextToken = getNextEmptyOrOpener(tokenizer, dim); + std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags); if(nextToken == "EMPTY") { return geometryFactory->createMultiPoint(); } @@ -284,13 +429,12 @@ WKTReader::readMultiPointText(StringTokenizer* tokenizer) const int tok = tokenizer->peekNextToken(); if(tok == StringTokenizer::TT_NUMBER) { + // Try to parse "MULTIPOINT (0 0, 1 1)" + auto coords = detail::make_unique(0u, ordinateFlags.hasZ(), ordinateFlags.hasM()); - // Try to parse deprecated form "MULTIPOINT(0 0, 1 1)" - auto coords = detail::make_unique(); - + CoordinateXYZM coord(0, 0, DoubleNotANumber, DoubleNotANumber); do { - Coordinate coord; - getPreciseCoordinate(tokenizer, coord, dim); + getPreciseCoordinate(tokenizer, ordinateFlags, coord); coords->add(coord); nextToken = getNextCloserOrComma(tokenizer); } @@ -299,13 +443,13 @@ WKTReader::readMultiPointText(StringTokenizer* tokenizer) const return std::unique_ptr(geometryFactory->createMultiPoint(*coords)); } - else if(tok == '(' || // Try to parse correct form "MULTIPOINT((0 0), (1 1))" - tok == StringTokenizer::TT_WORD) // EMPTY? + else if(tok == '(' || // Try to parse "MULTIPOINT ((0 0), (1 1))" + tok == StringTokenizer::TT_WORD) // "MULTIPOINT (EMPTY, (1 1))" { std::vector> points; do { - points.push_back(readPointText(tokenizer)); + points.push_back(readPointText(tokenizer, ordinateFlags)); nextToken = getNextCloserOrComma(tokenizer); } while(nextToken == ","); @@ -345,73 +489,126 @@ WKTReader::readMultiPointText(StringTokenizer* tokenizer) const } std::unique_ptr -WKTReader::readPolygonText(StringTokenizer* tokenizer) const +WKTReader::readPolygonText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const { - std::size_t dim = 2; - std::string nextToken = getNextEmptyOrOpener(tokenizer, dim); + std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags); if(nextToken == "EMPTY") { - return geometryFactory->createPolygon(dim); + auto coords = detail::make_unique(0u, ordinateFlags.hasZ(), ordinateFlags.hasM()); + auto ring = geometryFactory->createLinearRing(std::move(coords)); + return geometryFactory->createPolygon(std::move(ring)); } std::vector> holes; - auto shell = readLinearRingText(tokenizer); + auto shell = readLinearRingText(tokenizer, ordinateFlags); nextToken = getNextCloserOrComma(tokenizer); while(nextToken == ",") { - holes.push_back(readLinearRingText(tokenizer)); + holes.push_back(readLinearRingText(tokenizer, ordinateFlags)); nextToken = getNextCloserOrComma(tokenizer); } return geometryFactory->createPolygon(std::move(shell), std::move(holes)); } +std::unique_ptr +WKTReader::readCurvePolygonText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const +{ + std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags); + if(nextToken == "EMPTY") { + auto coords = detail::make_unique(0u, ordinateFlags.hasZ(), ordinateFlags.hasM()); + std::unique_ptr ring = geometryFactory->createLinearRing(std::move(coords)); + return geometryFactory->createCurvePolygon(std::move(ring)); + } + + std::vector> holes; + auto shell = readCurveText(tokenizer, ordinateFlags); + nextToken = getNextCloserOrComma(tokenizer); + while(nextToken == ",") { + holes.push_back(readCurveText(tokenizer, ordinateFlags)); + nextToken = getNextCloserOrComma(tokenizer); + } + + return geometryFactory->createCurvePolygon(std::move(shell), std::move(holes)); +} + std::unique_ptr -WKTReader::readMultiLineStringText(StringTokenizer* tokenizer) const +WKTReader::readMultiLineStringText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const { - std::size_t dim = 2; - std::string nextToken = getNextEmptyOrOpener(tokenizer, dim); + std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags); if(nextToken == "EMPTY") { return geometryFactory->createMultiLineString(); } std::vector> lineStrings; do { - lineStrings.push_back(readLineStringText(tokenizer)); + lineStrings.push_back(readLineStringText(tokenizer, ordinateFlags)); nextToken = getNextCloserOrComma(tokenizer); } while (nextToken == ","); return geometryFactory->createMultiLineString(std::move(lineStrings)); } +std::unique_ptr +WKTReader::readMultiCurveText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const +{ + std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags); + if(nextToken == "EMPTY") { + return geometryFactory->createMultiCurve(); + } + + std::vector> curves; + do { + curves.push_back(readCurveText(tokenizer, ordinateFlags)); + nextToken = getNextCloserOrComma(tokenizer); + } while(nextToken == ","); + + return geometryFactory->createMultiCurve(std::move(curves)); +} + std::unique_ptr -WKTReader::readMultiPolygonText(StringTokenizer* tokenizer) const +WKTReader::readMultiPolygonText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const { - std::size_t dim = 2; - std::string nextToken = getNextEmptyOrOpener(tokenizer, dim); + std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags); if(nextToken == "EMPTY") { return geometryFactory->createMultiPolygon(); } std::vector> polygons; do { - polygons.push_back(readPolygonText(tokenizer)); + polygons.push_back(readPolygonText(tokenizer, ordinateFlags)); nextToken = getNextCloserOrComma(tokenizer); } while(nextToken == ","); return geometryFactory->createMultiPolygon(std::move(polygons)); } +std::unique_ptr +WKTReader::readMultiSurfaceText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const +{ + std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags); + if(nextToken == "EMPTY") { + return geometryFactory->createMultiSurface(); + } + + std::vector> surfaces; + do { + surfaces.push_back(readSurfaceText(tokenizer, ordinateFlags)); + nextToken = getNextCloserOrComma(tokenizer); + } while(nextToken == ","); + + return geometryFactory->createMultiSurface(std::move(surfaces)); +} + std::unique_ptr -WKTReader::readGeometryCollectionText(StringTokenizer* tokenizer) const +WKTReader::readGeometryCollectionText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const { - std::size_t dim = 2; - std::string nextToken = getNextEmptyOrOpener(tokenizer, dim); + std::string nextToken = getNextEmptyOrOpener(tokenizer, ordinateFlags); if(nextToken == "EMPTY") { return geometryFactory->createGeometryCollection(); } std::vector> geoms; do { - geoms.push_back(readGeometryTaggedText(tokenizer)); + geoms.push_back(readGeometryTaggedText(tokenizer, ordinateFlags)); nextToken = getNextCloserOrComma(tokenizer); } while(nextToken == ","); diff --git a/Sources/geos/src/io/WKTStreamReader.cpp b/Sources/geos/src/io/WKTStreamReader.cpp new file mode 100644 index 0000000..63e94d1 --- /dev/null +++ b/Sources/geos/src/io/WKTStreamReader.cpp @@ -0,0 +1,71 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2020 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include // for unique_ptr +#include + +#include + +using namespace geos::geom; + +namespace geos { +namespace io { + +WKTStreamReader::WKTStreamReader(std::istream& p_instr) + : instr(p_instr) +{ +} + +WKTStreamReader::~WKTStreamReader() { + +} + +/*public*/ + + +/* +Return: nullptr if at EOF +*/ +std::unique_ptr +WKTStreamReader::next() +{ + std::string wkt = ""; + + std::string::difference_type lParen = 0; + std::string::difference_type rParen = 0; + do { + std::string line; + std::getline(instr, line); + if (! instr) { + return nullptr; + } + + lParen += std::count(line.begin(), line.end(), '('); + rParen += std::count(line.begin(), line.end(), ')'); + + wkt += line; + } while (lParen == 0 || lParen != rParen); + + auto g = rdr.read( wkt.c_str() ); + return g; +} + + +} +} diff --git a/Sources/geos/src/io/WKTWriter.cpp b/Sources/geos/src/io/WKTWriter.cpp index ea45a0b..53fa142 100644 --- a/Sources/geos/src/io/WKTWriter.cpp +++ b/Sources/geos/src/io/WKTWriter.cpp @@ -21,7 +21,11 @@ #include #include #include +#include +#include #include +#include +#include #include #include #include @@ -29,8 +33,12 @@ #include #include #include +#include +#include #include #include +#include +#include #include #include @@ -54,9 +62,8 @@ WKTWriter::WKTWriter(): decimalPlaces(6), isFormatted(false), roundingPrecision(-1), - trim(false), - level(0), - defaultOutputDimension(2), + trim(true), + defaultOutputDimension(4), old3D(false) { } @@ -65,8 +72,8 @@ WKTWriter::WKTWriter(): void WKTWriter::setOutputDimension(uint8_t dims) { - if(dims < 2 || dims > 3) { - throw util::IllegalArgumentException("WKT output dimension must be 2 or 3"); + if(dims < 2 || dims > 4) { + throw util::IllegalArgumentException("WKT output dimension must be 2, 3, or 4"); } defaultOutputDimension = dims; } @@ -100,17 +107,11 @@ WKTWriter::toLineString(const CoordinateSequence& seq) /*static*/ std::string -WKTWriter::toLineString(const Coordinate& p0, const Coordinate& p1) +WKTWriter::toLineString(const CoordinateXY& p0, const CoordinateXY& p1) { std::stringstream ret(std::ios_base::in | std::ios_base::out); ret << "LINESTRING (" << p0.x << " " << p0.y; -#if PRINT_Z - ret << " " << p0.z; -#endif ret << ", " << p1.x << " " << p1.y; -#if PRINT_Z - ret << " " << p1.z; -#endif ret << ")"; return ret.str(); @@ -130,6 +131,15 @@ WKTWriter::toPoint(const Coordinate& p0) return ret.str(); } +std::string +WKTWriter::toPoint(const CoordinateXY& p0) +{ + std::stringstream ret(std::ios_base::in | std::ios_base::out); + ret << "POINT ("; + ret << p0.x << " " << p0.y << " )"; + return ret.str(); +} + void WKTWriter::setRoundingPrecision(int p0) { @@ -154,6 +164,12 @@ WKTWriter::write(const Geometry* geometry) return res; } +std::string +WKTWriter::write(const Geometry& geometry) +{ + return write(&geometry); +} + void WKTWriter::write(const Geometry* geometry, Writer* writer) { @@ -183,191 +199,282 @@ WKTWriter::writeFormatted(const Geometry* geometry, bool p_isFormatted, decimalPlaces = roundingPrecision == -1 ? geometry->getPrecisionModel()->getMaximumSignificantDigits() : roundingPrecision; - appendGeometryTaggedText(geometry, 0, writer); + + appendGeometryTaggedText(*geometry, OrdinateSet::createXYZM(), 0, *writer); } void -WKTWriter::appendGeometryTaggedText(const Geometry* geometry, int p_level, - Writer* writer) +WKTWriter::appendGeometryTaggedText(const Geometry& geometry, + OrdinateSet checkOrdinates, + int level, + Writer& writer) const { - outputDimension = std::min(defaultOutputDimension, - geometry->getCoordinateDimension()); - - indent(p_level, writer); - if(const Point* point = dynamic_cast(geometry)) { - appendPointTaggedText(point->getCoordinate(), p_level, writer); - } - else if(const LinearRing* lr = - dynamic_cast(geometry)) { - appendLinearRingTaggedText(lr, p_level, writer); - } - else if(const LineString* ls = - dynamic_cast(geometry)) { - appendLineStringTaggedText(ls, p_level, writer); + OrdinateSet outputOrdinates = OrdinateSet::createXY(); + if (geometry.isEmpty() || !removeEmptyDimensions) { + // for an empty geometry, use the declared dimensionality + outputOrdinates.setZ(geometry.hasZ()); + outputOrdinates.setM(geometry.hasM()); + } else { + // for a non-empty geometry, evaluate the ordinates actually present in the geometry + CheckOrdinatesFilter cof(checkOrdinates); + geometry.apply_ro(cof); + // remove detected ordinates to stay within defaultOutputDimension + outputOrdinates = cof.getFoundOrdinates(); + } + + while (outputOrdinates.size() > defaultOutputDimension) { + if (outputOrdinates.hasZ() && outputOrdinates.hasM()) { + // 4D -> 3D + outputOrdinates.setM(false); + } else { + // 3D -> 2D + outputOrdinates.setM(false); + outputOrdinates.setZ(false); + } } - else if(const Polygon* x1 = - dynamic_cast(geometry)) { - appendPolygonTaggedText(x1, p_level, writer); + + indent(level, &writer); + switch(geometry.getGeometryTypeId()) { + case GEOS_POINT: appendPointTaggedText(static_cast(geometry), outputOrdinates, level, writer); break; + case GEOS_LINESTRING: + case GEOS_LINEARRING: + case GEOS_CIRCULARSTRING: appendSimpleCurveTaggedText(static_cast(geometry), outputOrdinates, level, writer); break; + case GEOS_COMPOUNDCURVE: appendCompoundCurveTaggedText(static_cast(geometry), outputOrdinates, level, writer); break; + case GEOS_CURVEPOLYGON: + case GEOS_POLYGON: appendSurfaceTaggedText(static_cast(geometry), outputOrdinates, level, writer); break; + case GEOS_MULTIPOINT: appendMultiPointTaggedText(static_cast(geometry), outputOrdinates, level, writer); break; + case GEOS_MULTICURVE: + case GEOS_MULTILINESTRING: appendMultiCurveTaggedText(static_cast(geometry), outputOrdinates, level, writer); break; + case GEOS_MULTISURFACE: + case GEOS_MULTIPOLYGON: appendMultiSurfaceTaggedText(static_cast(geometry), outputOrdinates, level, writer); break; + case GEOS_GEOMETRYCOLLECTION: appendGeometryCollectionTaggedText(static_cast(geometry), outputOrdinates, level, writer); break; } - else if(const MultiPoint* x2 = - dynamic_cast(geometry)) { - appendMultiPointTaggedText(x2, p_level, writer); +} + +void WKTWriter::appendTag(const Geometry& geometry, OrdinateSet outputOrdinates, Writer& writer) const +{ + std::string type = geometry.getGeometryType(); + util::toUpper(type); + writer.write(type); + writer.write(" "); + appendOrdinateText(outputOrdinates, writer); +} + +/*protected*/ +void +WKTWriter::appendOrdinateText(OrdinateSet outputOrdinates, Writer& writer) const +{ + if (old3D) { + if (!outputOrdinates.hasZ() && outputOrdinates.hasM()) { + writer.write("M "); + } + return; } - else if(const MultiLineString* x3 = - dynamic_cast(geometry)) { - appendMultiLineStringTaggedText(x3, p_level, writer); + + bool writeSpace = false; + if (outputOrdinates.hasZ()) { + writer.write("Z"); + writeSpace = true; } - else if(const MultiPolygon* x4 = - dynamic_cast(geometry)) { - appendMultiPolygonTaggedText(x4, p_level, writer); + if (outputOrdinates.hasM()) { + writer.write("M"); + writeSpace = true; } - else if(const GeometryCollection* x5 = - dynamic_cast(geometry)) { - appendGeometryCollectionTaggedText(x5, p_level, writer); + if (writeSpace) { + writer.write(" "); } - else { - assert(0); // Unsupported Geometry implementation +} + +void +WKTWriter::appendPointTaggedText(const Point& point, OrdinateSet outputOrdinates, int level, + Writer& writer) const +{ + writer.write("POINT "); + appendOrdinateText(outputOrdinates, writer); + + const CoordinateXY* coord = point.getCoordinate(); + if (coord == nullptr) { + writer.write("EMPTY"); + } else { + appendSequenceText(*point.getCoordinatesRO(), outputOrdinates, level, false, writer); } } -/*protected*/ void -WKTWriter::appendPointTaggedText(const Coordinate* coordinate, int p_level, - Writer* writer) +WKTWriter::appendSimpleCurveTaggedText(const SimpleCurve& curve, OrdinateSet outputOrdinates, int level, Writer& writer) const { - writer->write("POINT "); - if(outputDimension == 3 && !old3D && coordinate != nullptr) { - writer->write("Z "); + appendTag(curve, outputOrdinates, writer); + appendSequenceText(*curve.getCoordinatesRO(), outputOrdinates, level, false, writer); +} + +void +WKTWriter::appendCurveText(const Curve& curve, OrdinateSet outputOrdinates, int level, bool doIndent, Writer& writer) const { + if (doIndent) { + indent(level, &writer); } - appendPointText(coordinate, p_level, writer); + if (curve.getGeometryTypeId() == GEOS_COMPOUNDCURVE) { + appendCompoundCurveTaggedText(static_cast(curve), outputOrdinates, level, writer); + } else { + appendSimpleCurveText(static_cast(curve), outputOrdinates, level, false, writer); + } } void -WKTWriter::appendLineStringTaggedText(const LineString* lineString, int p_level, - Writer* writer) -{ - writer->write("LINESTRING "); - if(outputDimension == 3 && !old3D && !lineString->isEmpty()) { - writer->write("Z "); +WKTWriter::appendSimpleCurveText(const SimpleCurve& curve, OrdinateSet outputOrdinates, int level, bool doIndent, Writer& writer) const { + if (doIndent) { + indent(level, &writer); } - appendLineStringText(lineString, p_level, false, writer); + if (curve.getGeometryTypeId() == GEOS_CIRCULARSTRING) { + appendSimpleCurveTaggedText(curve, outputOrdinates, level, writer); + } else { + appendSequenceText(*curve.getCoordinatesRO(), outputOrdinates, level, false, writer); + } } -/** - * Converts a `LinearRing` to \ - * format, then appends it to the writer. - * - * @param linearRing the `LinearRing` to process - * @param writer the output writer to append to - */ + void -WKTWriter::appendLinearRingTaggedText(const LinearRing* linearRing, int p_level, Writer* writer) +WKTWriter::appendCompoundCurveTaggedText(const CompoundCurve& curve, OrdinateSet outputOrdinates, int level, Writer& writer) const { - writer->write("LINEARRING "); - if(outputDimension == 3 && !old3D && !linearRing->isEmpty()) { - writer->write("Z "); + writer.write("COMPOUNDCURVE "); + appendOrdinateText(outputOrdinates, writer); + + if (curve.isEmpty()) { + writer.write("EMPTY"); + } else { + writer.write("("); + bool indentFirst = false; + for (std::size_t i = 0; i < curve.getNumCurves(); i++) { + if (i > 0) { + writer.write(", "); + indentFirst = true; + } + + appendSimpleCurveText(*curve.getCurveN(i), outputOrdinates, level + (i > 0), indentFirst, writer); + } + writer.write(")"); } - appendLineStringText(linearRing, p_level, false, writer); } void -WKTWriter::appendPolygonTaggedText(const Polygon* polygon, int p_level, Writer* writer) +WKTWriter::appendSurfaceTaggedText(const Surface& surface, OrdinateSet outputOrdinates, int level, Writer& writer) const { - writer->write("POLYGON "); - if(outputDimension == 3 && !old3D && !polygon->isEmpty()) { - writer->write("Z "); - } - appendPolygonText(polygon, p_level, false, writer); + appendTag(surface, outputOrdinates, writer); + appendSurfaceText(surface, outputOrdinates, level, false, writer); } void -WKTWriter::appendMultiPointTaggedText(const MultiPoint* multipoint, int p_level, Writer* writer) +WKTWriter::appendMultiPointTaggedText(const MultiPoint& multipoint, OrdinateSet outputOrdinates, int level, Writer& writer) const { - writer->write("MULTIPOINT "); - if(outputDimension == 3 && !old3D && !multipoint->isEmpty()) { - writer->write("Z "); - } - appendMultiPointText(multipoint, p_level, writer); + writer.write("MULTIPOINT "); + appendOrdinateText(outputOrdinates, writer); + appendMultiPointText(multipoint, outputOrdinates, level, writer); } void -WKTWriter::appendMultiLineStringTaggedText(const MultiLineString* multiLineString, int p_level, Writer* writer) +WKTWriter::appendMultiCurveTaggedText(const GeometryCollection& multiCurve, OrdinateSet outputOrdinates, int level, Writer& writer) const { - writer->write("MULTILINESTRING "); - if(outputDimension == 3 && !old3D && !multiLineString->isEmpty()) { - writer->write("Z "); - } - appendMultiLineStringText(multiLineString, p_level, false, writer); + appendTag(multiCurve, outputOrdinates, writer); + appendMultiCurveText(multiCurve, outputOrdinates, level, false, writer); } void -WKTWriter::appendMultiPolygonTaggedText(const MultiPolygon* multiPolygon, int p_level, Writer* writer) +WKTWriter::appendMultiSurfaceTaggedText(const GeometryCollection& multiPolygon, OrdinateSet outputOrdinates, int level, Writer& writer) const { - writer->write("MULTIPOLYGON "); - if(outputDimension == 3 && !old3D && !multiPolygon->isEmpty()) { - writer->write("Z "); - } - appendMultiPolygonText(multiPolygon, p_level, writer); + appendTag(multiPolygon, outputOrdinates, writer); + appendMultiSurfaceText(multiPolygon, outputOrdinates, level, writer); } void -WKTWriter::appendGeometryCollectionTaggedText(const GeometryCollection* geometryCollection, int p_level, - Writer* writer) +WKTWriter::appendGeometryCollectionTaggedText(const GeometryCollection& geometryCollection, OrdinateSet outputOrdinates, int level, + Writer& writer) const { - writer->write("GEOMETRYCOLLECTION "); - if(outputDimension == 3 && !old3D && !geometryCollection->isEmpty()) { - writer->write("Z "); - } - appendGeometryCollectionText(geometryCollection, p_level, writer); + writer.write("GEOMETRYCOLLECTION "); + appendOrdinateText(outputOrdinates, writer); + appendGeometryCollectionText(geometryCollection, outputOrdinates, level, writer); } +/* protected */ void -WKTWriter::appendPointText(const Coordinate* coordinate, int /*level*/, - Writer* writer) +WKTWriter::appendCoordinate(const CoordinateXYZM& coordinate, + OrdinateSet outputOrdinates, + Writer& writer) const { - if(coordinate == nullptr) { - writer->write("EMPTY"); + writer.write(writeNumber(coordinate.x)); + writer.write(" "); + writer.write(writeNumber(coordinate.y)); + + if(outputOrdinates.hasZ()) { + writer.write(" "); + writer.write(writeNumber(coordinate.z)); } - else { - writer->write("("); - appendCoordinate(coordinate, writer); - writer->write(")"); + + if(outputOrdinates.hasM()) { + writer.write(" "); + writer.write(writeNumber(coordinate.m)); } } -/* protected */ void -WKTWriter::appendCoordinate(const Coordinate* coordinate, - Writer* writer) -{ - writer->write(writeNumber(coordinate->x)); - writer->write(" "); - writer->write(writeNumber(coordinate->y)); - if(outputDimension == 3) { - writer->write(" "); - if(std::isnan(coordinate->z)) { - writer->write(writeNumber(0.0)); +WKTWriter::appendSequenceText(const CoordinateSequence& seq, + OrdinateSet outputOrdinates, + int level, + bool doIndent, + Writer& writer) const +{ + if (seq.isEmpty()) { + writer.write("EMPTY"); + } + else { + if(doIndent) { + indent(level, &writer); } - else { - writer->write(writeNumber(coordinate->z)); + writer.write("("); + CoordinateXYZM c; + for(std::size_t i = 0, n = seq.size(); i < n; ++i) { + if(i > 0) { + writer.write(", "); + if(coordsPerLine > 0 && i % coordsPerLine == 0) { + indent(level + 2, &writer); + } + } + seq.getAt(i, c); + appendCoordinate(c, outputOrdinates, writer); } + writer.write(")"); } } -/* protected */ -std::string -WKTWriter::writeNumber(double d) const +int +WKTWriter::writeTrimmedNumber(double d, uint32_t precision, char* buf) { - uint32_t precision = decimalPlaces >= 0 ? static_cast(decimalPlaces) : 0; + const auto da = std::fabs(d); + if ( !std::isfinite(d) || (da == 0.0) ) + // non-finite or exactly zero + return geos_d2sfixed_buffered_n(d, precision, buf); + else if ( (da >= 1e+17) || (da < 1e-4) ) + // very large or small numbers, use scientific notation + return geos_d2sexp_buffered_n(d, precision, buf); + else { + // most real-world coordinates, use positional notation + if ( (precision < 4) && (da < 1.0) ) { + // adjust precision to avoid rounding to zero + precision = static_cast(-floor(log10(da))); + } + return geos_d2sfixed_buffered_n(d, precision, buf); + } +} + +std::string +WKTWriter::writeNumber(double d, bool trim, uint32_t precision) { /* * For a "trimmed" result, with no trailing zeros we use * the ryu library. */ if (trim) { - char buf[128]; - int len = geos_d2sfixed_buffered_n(d, precision, buf); + char buf[28]; + int len = writeTrimmedNumber(d, precision, buf); buf[len] = '\0'; std::string s(buf); return s; @@ -385,161 +492,158 @@ WKTWriter::writeNumber(double d) const } } -void -WKTWriter::appendLineStringText(const LineString* lineString, int p_level, - bool doIndent, Writer* writer) +/* protected */ +std::string +WKTWriter::writeNumber(double d) const { - if (lineString->isEmpty()) { - writer->write("EMPTY"); - } - else { - if(doIndent) { - indent(p_level, writer); - } - writer->write("("); - for(std::size_t i = 0, n = lineString->getNumPoints(); i < n; ++i) { - if(i > 0) { - writer->write(", "); - if(i % 10 == 0) { - indent(p_level + 2, writer); - } - } - appendCoordinate(&(lineString->getCoordinateN(i)), writer); - } - writer->write(")"); - } + uint32_t precision = decimalPlaces >= 0 ? static_cast(decimalPlaces) : 0; + return writeNumber(d, trim, precision); } void -WKTWriter::appendPolygonText(const Polygon* polygon, int /*level*/, - bool indentFirst, Writer* writer) +WKTWriter::appendSurfaceText(const Surface& polygon, OrdinateSet outputOrdinates, int level, + bool indentFirst, Writer& writer) const { - if(polygon->isEmpty()) { - writer->write("EMPTY"); + if(polygon.isEmpty()) { + writer.write("EMPTY"); } else { if(indentFirst) { - indent(level, writer); + indent(level, &writer); } - writer->write("("); - appendLineStringText(polygon->getExteriorRing(), level, false, writer); - for(std::size_t i = 0, n = polygon->getNumInteriorRing(); i < n; ++i) { - writer->write(", "); - const LineString* ls = polygon->getInteriorRingN(i); - appendLineStringText(ls, level + 1, true, writer); + writer.write("("); + + auto ring = polygon.getExteriorRing(); + appendCurveText(*ring, outputOrdinates, level, false, writer); + + for(std::size_t i = 0, n = polygon.getNumInteriorRing(); i < n; ++i) { + writer.write(", "); + + auto hole = polygon.getInteriorRingN(i); + appendCurveText(*hole, outputOrdinates, level + 1, true, writer); } - writer->write(")"); + writer.write(")"); } } void -WKTWriter::appendMultiPointText(const MultiPoint* multiPoint, - int /*level*/, Writer* writer) +WKTWriter::appendMultiPointText(const MultiPoint& multiPoint, OrdinateSet outputOrdinates, + int /*level*/, Writer& writer) const { - if(multiPoint->isEmpty()) { - writer->write("EMPTY"); + const std::size_t n = multiPoint.getNumGeometries(); + if(n == 0) { + writer.write("EMPTY"); } else { - writer->write("("); - for(std::size_t i = 0, n = multiPoint->getNumGeometries(); - i < n; ++i) { - + writer.write("("); + for(std::size_t i = 0; i < n; ++i) { if(i > 0) { - writer->write(", "); + writer.write(", "); } - const Coordinate* coord = multiPoint->getGeometryN(i)->getCoordinate(); - if(coord == nullptr) { - writer->write("EMPTY"); + const CoordinateSequence* seq = multiPoint.getGeometryN(i)->getCoordinatesRO(); + if(seq == nullptr || seq->isEmpty()) { + writer.write("EMPTY"); } else { - appendCoordinate(coord, writer); + CoordinateXYZM coord; + writer.write("("); + seq->getAt(0, coord); + appendCoordinate(coord, outputOrdinates, writer); + writer.write(")"); } } - writer->write(")"); + writer.write(")"); } } void -WKTWriter::appendMultiLineStringText(const MultiLineString* multiLineString, int p_level, bool indentFirst, - Writer* writer) +WKTWriter::appendMultiCurveText(const GeometryCollection& multiCurve, OrdinateSet outputOrdinates, int level, bool indentFirst, + Writer& writer) const { - if(multiLineString->isEmpty()) { - writer->write("EMPTY"); + const std::size_t n = multiCurve.getNumGeometries(); + if(n == 0) { + writer.write("EMPTY"); } else { - int level2 = p_level; + int level2 = level; bool doIndent = indentFirst; - writer->write("("); - for(std::size_t i = 0, n = multiLineString->getNumGeometries(); - i < n; ++i) { + writer.write("("); + for(std::size_t i = 0; i < n; ++i) { if(i > 0) { - writer->write(", "); - level2 = p_level + 1; + writer.write(", "); + level2 = level + 1; doIndent = true; } - const LineString* ls = multiLineString->getGeometryN(i); - appendLineStringText(ls, level2, doIndent, writer); + + const Curve* g = static_cast(multiCurve.getGeometryN(i)); + appendCurveText(*g, outputOrdinates, level2, doIndent, writer); } - writer->write(")"); + writer.write(")"); } } void -WKTWriter::appendMultiPolygonText(const MultiPolygon* multiPolygon, int p_level, Writer* writer) +WKTWriter::appendMultiSurfaceText(const GeometryCollection& multiSurface, OrdinateSet outputOrdinates, int level, Writer& writer) const { - if(multiPolygon->isEmpty()) { - writer->write("EMPTY"); + const std::size_t n = multiSurface.getNumGeometries(); + if(n == 0) { + writer.write("EMPTY"); } else { - int level2 = p_level; + int level2 = level; bool doIndent = false; - writer->write("("); - for(std::size_t i = 0, n = multiPolygon->getNumGeometries(); - i < n; ++i) { + writer.write("("); + for(std::size_t i = 0; i < n; ++i) { if(i > 0) { - writer->write(", "); - level2 = p_level + 1; + writer.write(", "); + level2 = level + 1; doIndent = true; } - const Polygon* p = multiPolygon->getGeometryN(i); - appendPolygonText(p, level2, doIndent, writer); + const Surface* p = static_cast(multiSurface.getGeometryN(i)); + if (p->getGeometryTypeId() == GEOS_POLYGON) { + appendSurfaceText(*p, outputOrdinates, level2, doIndent, writer); + } else { + // FIXME indent + appendSurfaceTaggedText(*p, outputOrdinates, level2, writer); + } } - writer->write(")"); + writer.write(")"); } } void WKTWriter::appendGeometryCollectionText( - const GeometryCollection* geometryCollection, - int p_level, - Writer* writer) -{ - if(geometryCollection->getNumGeometries() > 0) { - int level2 = p_level; - writer->write("("); - for(std::size_t i = 0, n = geometryCollection->getNumGeometries(); - i < n; ++i) { + const GeometryCollection& geometryCollection, + OrdinateSet outputOrdinates, + int level, + Writer& writer) const +{ + const std::size_t n = geometryCollection.getNumGeometries(); + if(n == 0) { + writer.write("EMPTY"); + } + else { + int level2 = level; + writer.write("("); + for(std::size_t i = 0; i < n; ++i) { if(i > 0) { - writer->write(", "); - level2 = p_level + 1; + writer.write(", "); + level2 = level + 1; } - appendGeometryTaggedText(geometryCollection->getGeometryN(i), level2, writer); + appendGeometryTaggedText(*geometryCollection.getGeometryN(i), outputOrdinates, level2, writer); } - writer->write(")"); - } - else { - writer->write("EMPTY"); + writer.write(")"); } } void -WKTWriter::indent(int p_level, Writer* writer) const +WKTWriter::indent(int level, Writer* writer) const { - if(!isFormatted || p_level <= 0) { + if(!isFormatted || level <= 0) { return; } writer->write("\n"); - writer->write(std::string(INDENT * static_cast(p_level), ' ')); + writer->write(std::string(INDENT * static_cast(level), ' ')); } } // namespace geos.io diff --git a/Sources/geos/src/linearref/ExtractLineByLocation.cpp b/Sources/geos/src/linearref/ExtractLineByLocation.cpp index 52908f3..625d4d3 100644 --- a/Sources/geos/src/linearref/ExtractLineByLocation.cpp +++ b/Sources/geos/src/linearref/ExtractLineByLocation.cpp @@ -18,7 +18,7 @@ **********************************************************************/ #include -#include +#include #include #include #include @@ -84,7 +84,7 @@ std::unique_ptr ExtractLineByLocation::computeLine(const LinearLocation& start, const LinearLocation& end) { auto coordinates = line->getCoordinates(); - CoordinateArraySequence newCoordinateArray; + CoordinateSequence newCoordinateArray; const std::size_t indexStep = 1; auto startSegmentIndex = start.getSegmentIndex(); diff --git a/Sources/geos/src/linearref/LengthIndexedLine.cpp b/Sources/geos/src/linearref/LengthIndexedLine.cpp index 012a9dc..5522eb3 100644 --- a/Sources/geos/src/linearref/LengthIndexedLine.cpp +++ b/Sources/geos/src/linearref/LengthIndexedLine.cpp @@ -57,6 +57,13 @@ LengthIndexedLine::extractPoint(double index, double offsetDistance) const std::unique_ptr LengthIndexedLine::extractLine(double startIndex, double endIndex) const { + if (std::isnan(startIndex)) { + throw util::IllegalArgumentException("startIndex is NaN"); + } + if (std::isnan(endIndex)) { + throw util::IllegalArgumentException("endIndex is NaN"); + } + const LocationIndexedLine lil(linearGeom); const double startIndex2 = clampIndex(startIndex); const double endIndex2 = clampIndex(endIndex); diff --git a/Sources/geos/src/linearref/LinearGeometryBuilder.cpp b/Sources/geos/src/linearref/LinearGeometryBuilder.cpp index 88bbfa9..445f2d7 100644 --- a/Sources/geos/src/linearref/LinearGeometryBuilder.cpp +++ b/Sources/geos/src/linearref/LinearGeometryBuilder.cpp @@ -18,7 +18,6 @@ **********************************************************************/ #include -#include #include #include #include @@ -27,6 +26,7 @@ #include #include #include +#include #include @@ -71,7 +71,7 @@ void LinearGeometryBuilder::add(const Coordinate& pt, bool allowRepeatedPoints) { if(!coordList) { - coordList = new CoordinateArraySequence(); + coordList = detail::make_unique(); } coordList->add(pt, allowRepeatedPoints); lastPt = pt; @@ -94,8 +94,7 @@ LinearGeometryBuilder::endLine() if(coordList->size() < 2) { if(ignoreInvalidLines) { if(coordList) { - delete coordList; - coordList = nullptr; + coordList.reset(); } return; } @@ -114,9 +113,9 @@ LinearGeometryBuilder::endLine() } } - LineString* line = nullptr; + std::unique_ptr line; try { - line = geomFact->createLineString(coordList); + line = geomFact->createLineString(std::move(coordList)); } catch(util::IllegalArgumentException & ex) { // exception is due to too few points in line. @@ -128,29 +127,24 @@ LinearGeometryBuilder::endLine() } if(line) { - lines.push_back(line); + lines.push_back(std::move(line)); } - coordList = nullptr; } /* public */ -Geometry* +std::unique_ptr LinearGeometryBuilder::getGeometry() { // end last line in case it was not done by user endLine(); // NOTE: lines elements are cloned - return geomFact->buildGeometry(lines); + return geomFact->buildGeometry(std::move(lines)); } /* public */ LinearGeometryBuilder::~LinearGeometryBuilder() { - for(GeomPtrVect::const_iterator i = lines.begin(), e = lines.end(); - i != e; ++i) { - delete *i; - } } } // namespace geos.linearref diff --git a/Sources/geos/src/linearref/LocationIndexOfLine.cpp b/Sources/geos/src/linearref/LocationIndexOfLine.cpp index a23b09b..8d543f8 100644 --- a/Sources/geos/src/linearref/LocationIndexOfLine.cpp +++ b/Sources/geos/src/linearref/LocationIndexOfLine.cpp @@ -52,8 +52,8 @@ LocationIndexOfLine::indicesOf(const Geometry* subLine) const if(! firstLine || !lastLine) { throw util::IllegalArgumentException("LocationIndexOfLine::indicesOf only works with geometry collections of LineString"); } - Coordinate startPt = firstLine->getCoordinateN(0); - Coordinate endPt = lastLine->getCoordinateN(lastLine->getNumPoints() - 1); + const CoordinateXY& startPt = firstLine->getCoordinateN(0); + const CoordinateXY& endPt = lastLine->getCoordinateN(lastLine->getNumPoints() - 1); LocationIndexOfPoint locPt(linearGeom); LinearLocation* subLineLoc = new LinearLocation[2]; diff --git a/Sources/geos/src/linearref/LocationIndexOfPoint.cpp b/Sources/geos/src/linearref/LocationIndexOfPoint.cpp index 213ae83..ad72c8b 100644 --- a/Sources/geos/src/linearref/LocationIndexOfPoint.cpp +++ b/Sources/geos/src/linearref/LocationIndexOfPoint.cpp @@ -36,7 +36,7 @@ namespace geos { namespace linearref { // geos.linearref LinearLocation -LocationIndexOfPoint::indexOfFromStart(const Coordinate& inputPt, +LocationIndexOfPoint::indexOfFromStart(const CoordinateXY& inputPt, const LinearLocation* minIndex) const { double minDistance = DoubleInfinity; @@ -74,14 +74,14 @@ LocationIndexOfPoint::indexOfFromStart(const Coordinate& inputPt, LinearLocation -LocationIndexOfPoint::indexOf(const Geometry* linearGeom, const Coordinate& inputPt) +LocationIndexOfPoint::indexOf(const Geometry* linearGeom, const CoordinateXY& inputPt) { LocationIndexOfPoint locater(linearGeom); return locater.indexOf(inputPt); } LinearLocation -LocationIndexOfPoint::indexOfAfter(const Geometry* linearGeom, const Coordinate& inputPt, +LocationIndexOfPoint::indexOfAfter(const Geometry* linearGeom, const CoordinateXY& inputPt, const LinearLocation* minIndex) { LocationIndexOfPoint locater(linearGeom); @@ -93,13 +93,13 @@ LocationIndexOfPoint::LocationIndexOfPoint(const Geometry* p_linearGeom) : {} LinearLocation -LocationIndexOfPoint::indexOf(const Coordinate& inputPt) const +LocationIndexOfPoint::indexOf(const CoordinateXY& inputPt) const { return indexOfFromStart(inputPt, nullptr); } LinearLocation -LocationIndexOfPoint::indexOfAfter(const Coordinate& inputPt, +LocationIndexOfPoint::indexOfAfter(const CoordinateXY& inputPt, const LinearLocation* minIndex) const { if(!minIndex) { diff --git a/Sources/geos/src/noding/BasicSegmentString.cpp b/Sources/geos/src/noding/BasicSegmentString.cpp index 5e0e6ea..9d89c22 100644 --- a/Sources/geos/src/noding/BasicSegmentString.cpp +++ b/Sources/geos/src/noding/BasicSegmentString.cpp @@ -30,7 +30,7 @@ std::ostream& BasicSegmentString::print(std::ostream& os) const { os << "BasicSegmentString: " << std::endl; - os << " LINESTRING" << *(pts) << ";" << std::endl; + os << " LINESTRING" << *(getCoordinates()) << ";" << std::endl; return os; } diff --git a/Sources/geos/src/noding/BoundaryChainNoder.cpp b/Sources/geos/src/noding/BoundaryChainNoder.cpp new file mode 100644 index 0000000..094c0df --- /dev/null +++ b/Sources/geos/src/noding/BoundaryChainNoder.cpp @@ -0,0 +1,190 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (C) 2022 Martin Davis + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include + +#include +#include +#include +#include + + +using geos::geom::CoordinateSequence; +using geos::geom::Coordinate; + + +namespace geos { // geos +namespace noding { // geos::noding + +/* public */ +void +BoundaryChainNoder::computeNodes(std::vector* segStrings) +{ + SegmentSet segSet; + std::vector bdySections; + bdySections.reserve(segStrings->size()); + addSegments(segStrings, segSet, bdySections); + markBoundarySegments(segSet); + chainList = extractChains(bdySections); +} + +/* public */ +std::vector* +BoundaryChainNoder::getNodedSubstrings() const +{ + return chainList; +} + +/* private */ +void +BoundaryChainNoder::addSegments( + std::vector* segStrings, + SegmentSet& segSet, + std::vector& includedSegs) +{ + for (SegmentString* ss : *segStrings) { + m_constructZ |= ss->getCoordinates()->hasZ(); + m_constructM |= ss->getCoordinates()->hasM(); + + includedSegs.emplace_back(ss); + BoundarySegmentMap& segInclude = includedSegs.back(); + addSegments(ss, segInclude, segSet); + } +} + +/* private static */ +bool +BoundaryChainNoder::segSetContains(SegmentSet& segSet, Segment& seg) +{ + auto search = segSet.find(seg); + if(search != segSet.end()) { + return true; + } + else { + return false; + } +} + +/* private static */ +void +BoundaryChainNoder::addSegments( + SegmentString* segString, + BoundarySegmentMap& segMap, + SegmentSet& segSet) +{ + const CoordinateSequence& segCoords = *segString->getCoordinates(); + + for (std::size_t i = 0; i < segString->size() - 1; i++) { + Segment seg(segCoords,segMap, i); + if (segSetContains(segSet, seg)) { + segSet.erase(seg); + } + else { + segSet.insert(seg); + } + } +} + + +/* private static */ +void +BoundaryChainNoder::markBoundarySegments(SegmentSet& segSet) +{ + for (const Segment& seg : segSet) { + seg.markInBoundary(); + } +} + +/* private */ +std::vector* +BoundaryChainNoder::extractChains(std::vector& sections) const +{ + std::vector* sectionList = new std::vector(); + for (BoundarySegmentMap& sect : sections) { + sect.createChains(*sectionList, m_constructZ, m_constructM); + } + return sectionList; +} + +/************************************************************************* + * BoundarySegmentMap + */ + +/* public */ +void +BoundaryChainNoder::BoundarySegmentMap::setBoundarySegment(std::size_t index) +{ + isBoundary[index] = true; +} + +/* public */ +void +BoundaryChainNoder::BoundarySegmentMap::createChains( + std::vector& chains, + bool constructZ, + bool constructM) +{ + std::size_t endIndex = 0; + while (true) { + std::size_t startIndex = findChainStart(endIndex); + if (startIndex >= segString->size() - 1) + break; + endIndex = findChainEnd(startIndex); + SegmentString* ss = createChain(segString, startIndex, endIndex, constructZ, constructM); + chains.push_back(ss); + } +} + + +/* private static */ +SegmentString* +BoundaryChainNoder::BoundarySegmentMap::createChain( + const SegmentString* segString, + std::size_t startIndex, + std::size_t endIndex, + bool constructZ, + bool constructM) +{ + auto npts = endIndex - startIndex + 1; + auto pts = detail::make_unique(0, constructZ, constructM); + pts->reserve(npts); + pts->add(*segString->getCoordinates(), startIndex, endIndex); + + return new NodedSegmentString(pts.release(), constructZ, constructM, segString->getData()); +} + +/* private */ +std::size_t +BoundaryChainNoder::BoundarySegmentMap::findChainStart(std::size_t index) const +{ + while (index < isBoundary.size() && ! isBoundary[index]) { + index++; + } + return index; +} + +/* private */ +std::size_t +BoundaryChainNoder::BoundarySegmentMap::findChainEnd(std::size_t index) const +{ + index++; + while (index < isBoundary.size() && isBoundary[index]) { + index++; + } + return index; +} + + +} // geos::noding +} // geos diff --git a/Sources/geos/src/noding/GeometryNoder.cpp b/Sources/geos/src/noding/GeometryNoder.cpp index 5d98cdb..71ad91e 100644 --- a/Sources/geos/src/noding/GeometryNoder.cpp +++ b/Sources/geos/src/noding/GeometryNoder.cpp @@ -48,8 +48,12 @@ namespace { */ class SegmentStringExtractor: public geom::GeometryComponentFilter { public: - SegmentStringExtractor(SegmentString::NonConstVect& to) + SegmentStringExtractor(SegmentString::NonConstVect& to, + bool constructZ, + bool constructM) : _to(to) + , _constructZ(constructZ) + , _constructM(constructM) {} void @@ -59,12 +63,14 @@ class SegmentStringExtractor: public geom::GeometryComponentFilter { if(ls) { auto coord = ls->getCoordinates(); // coord ownership transferred to SegmentString - SegmentString* ss = new NodedSegmentString(coord.release(), nullptr); + SegmentString* ss = new NodedSegmentString(coord.release(), _constructZ, _constructM, nullptr); _to.push_back(ss); } } private: SegmentString::NonConstVect& _to; + bool _constructZ; + bool _constructM; SegmentStringExtractor(SegmentStringExtractor const&); /*= delete*/ SegmentStringExtractor& operator=(SegmentStringExtractor const&); /*= delete*/ @@ -86,6 +92,7 @@ GeometryNoder::GeometryNoder(const geom::Geometry& g) : argGeom(g) { + util::ensureNoCurvedComponents(argGeom); } /* private */ @@ -138,12 +145,12 @@ GeometryNoder::getNoded() std::unique_ptr noded = toGeometry(*nodedEdges); - for(auto& elem : (*nodedEdges)) { + for(auto* elem : (*nodedEdges)) { delete elem; } delete nodedEdges; - for(auto& elem : p_lineList) { + for(auto* elem : p_lineList) { delete elem; } @@ -155,7 +162,7 @@ void GeometryNoder::extractSegmentStrings(const geom::Geometry& g, SegmentString::NonConstVect& to) { - SegmentStringExtractor ex(to); + SegmentStringExtractor ex(to, g.hasZ(), g.hasM()); g.apply_ro(&ex); } diff --git a/Sources/geos/src/noding/IntersectionAdder.cpp b/Sources/geos/src/noding/IntersectionAdder.cpp index fba4847..1ad9d2f 100644 --- a/Sources/geos/src/noding/IntersectionAdder.cpp +++ b/Sources/geos/src/noding/IntersectionAdder.cpp @@ -71,14 +71,10 @@ IntersectionAdder::processIntersections( numTests++; + const CoordinateSequence& seq0 = *e0->getCoordinates(); + const CoordinateSequence& seq1 = *e1->getCoordinates(); - const Coordinate& p00 = e0->getCoordinate(segIndex0); - const Coordinate& p01 = e0->getCoordinate(segIndex0 + 1); - const Coordinate& p10 = e1->getCoordinate(segIndex1); - const Coordinate& p11 = e1->getCoordinate(segIndex1 + 1); - - li.computeIntersection(p00, p01, p10, p11); -//if (li.hasIntersection() && li.isProper()) Debug.println(li); + li.computeIntersection(seq0, segIndex0, seq1, segIndex1); // No intersection, nothing to do if(! li.hasIntersection()) { diff --git a/Sources/geos/src/noding/IteratedNoder.cpp b/Sources/geos/src/noding/IteratedNoder.cpp index 4f56b72..e2ac4f2 100644 --- a/Sources/geos/src/noding/IteratedNoder.cpp +++ b/Sources/geos/src/noding/IteratedNoder.cpp @@ -40,7 +40,7 @@ namespace noding { // geos.noding void IteratedNoder::node(std::vector* segStrings, int& numInteriorIntersections, - Coordinate& intersectionPoint) + CoordinateXY& intersectionPoint) { IntersectionAdder si(li); MCIndexNoder noder; @@ -64,7 +64,7 @@ IteratedNoder::computeNodes(SegmentString::NonConstVect* segStrings) int nodingIterationCount = 0; int lastNodesCreated = -1; std::vector* lastStrings = nullptr; - Coordinate intersectionPoint = Coordinate::getNull(); + CoordinateXY intersectionPoint = CoordinateXY::getNull(); do { // NOTE: will change this.nodedSegStrings diff --git a/Sources/geos/src/noding/MCIndexNoder.cpp b/Sources/geos/src/noding/MCIndexNoder.cpp index 1803dbf..6cdd9a4 100644 --- a/Sources/geos/src/noding/MCIndexNoder.cpp +++ b/Sources/geos/src/noding/MCIndexNoder.cpp @@ -68,24 +68,13 @@ MCIndexNoder::intersectChains() SegmentOverlapAction overlapAction(*segInt); - for(const MonotoneChain& queryChain : monoChains) { - GEOS_CHECK_FOR_INTERRUPTS(); - - const geom::Envelope& queryEnv = queryChain.getEnvelope(overlapTolerance); - index.query(queryEnv, [&queryChain, &overlapAction, this](const MonotoneChain* testChain) { - /* - * following test makes sure we only compare each - * pair of chains once and that we don't compare a - * chain to itself - */ - if(testChain > &queryChain) { - queryChain.computeOverlaps(testChain, overlapTolerance, &overlapAction); - nOverlaps++; - } - - return !segInt->isDone(); // abort early if segInt->isDone() - }); - } + index.queryPairs([this, &overlapAction](const MonotoneChain* queryChain, const MonotoneChain* testChain) { + queryChain->computeOverlaps(testChain, overlapTolerance, &overlapAction); + nOverlaps++; + if ( nOverlaps % 100000 == 0 ) GEOS_CHECK_FOR_INTERRUPTS(); + + return !segInt->isDone(); // abort early if segInt->isDone() + }); } /*private*/ diff --git a/Sources/geos/src/noding/MCIndexSegmentSetMutualIntersector.cpp b/Sources/geos/src/noding/MCIndexSegmentSetMutualIntersector.cpp index b127024..b5b5e4d 100644 --- a/Sources/geos/src/noding/MCIndexSegmentSetMutualIntersector.cpp +++ b/Sources/geos/src/noding/MCIndexSegmentSetMutualIntersector.cpp @@ -10,12 +10,9 @@ * by the Free Software Foundation. * See the COPYING file for more information. * - ********************************************************************** - * - * Last port: noding/MCIndexSegmentSetMutualIntersector.java r388 (JTS-1.12) - * **********************************************************************/ +#include #include #include #include @@ -25,6 +22,7 @@ #include #include #include + // std #include @@ -50,8 +48,14 @@ MCIndexSegmentSetMutualIntersector::addToMonoChains(SegmentString* segStr) { if (segStr->size() == 0) return; + MonoChains segChains; MonotoneChainBuilder::getChains(segStr->getCoordinates(), - segStr, monoChains); + segStr, segChains); + for (auto& mc : segChains) { + if (envelope == nullptr || envelope->intersects(mc.getEnvelope())) { + monoChains.push_back(mc); + } + } } @@ -62,8 +66,8 @@ MCIndexSegmentSetMutualIntersector::intersectChains() MCIndexSegmentSetMutualIntersector::SegmentOverlapAction overlapAction(*segInt); for(auto& queryChain : monoChains) { - index.query(queryChain.getEnvelope(), [&queryChain, &overlapAction, this](const MonotoneChain* testChain) { - queryChain.computeOverlaps(testChain, &overlapAction); + index.query(queryChain.getEnvelope(overlapTolerance), [&queryChain, &overlapAction, this](const MonotoneChain* testChain) -> bool { + queryChain.computeOverlaps(testChain, overlapTolerance, &overlapAction); nOverlaps++; return !segInt->isDone(); // abort early if segInt->isDone() @@ -92,7 +96,9 @@ MCIndexSegmentSetMutualIntersector::process(SegmentString::ConstVect* segStrings { if (!indexBuilt) { for (auto& mc: indexChains) { - index.insert(&(mc.getEnvelope()), &mc); + if (envelope == nullptr || envelope->intersects(mc.getEnvelope())) { + index.insert(&(mc.getEnvelope(overlapTolerance)), &mc); + } } indexBuilt = true; } diff --git a/Sources/geos/src/noding/NodedSegmentString.cpp b/Sources/geos/src/noding/NodedSegmentString.cpp index e69602e..b2ca546 100644 --- a/Sources/geos/src/noding/NodedSegmentString.cpp +++ b/Sources/geos/src/noding/NodedSegmentString.cpp @@ -57,7 +57,7 @@ NodedSegmentString::getNodedSubstrings( } /* public */ -std::vector +std::unique_ptr NodedSegmentString::getNodedCoordinates() { return nodeList.getSplitCoordinates(); } @@ -74,31 +74,12 @@ NodedSegmentString::getNodedSubstrings( return resultEdgelist; } -/* virtual public */ -const geom::Coordinate& -NodedSegmentString::getCoordinate(std::size_t i) const -{ - return pts->getAt(i); -} - -/* virtual public */ -geom::CoordinateSequence* -NodedSegmentString::getCoordinates() const -{ - return pts.get(); -} - -geom::CoordinateSequence* +std::unique_ptr NodedSegmentString::releaseCoordinates() { - return pts.release(); -} - -/* virtual public */ -bool -NodedSegmentString::isClosed() const -{ - return pts->getAt(0) == pts->getAt(size() - 1); + auto ret = std::unique_ptr(seq); + seq = nullptr; + return ret; } /* public virtual */ @@ -106,7 +87,7 @@ std::ostream& NodedSegmentString::print(std::ostream& os) const { os << "NodedSegmentString: " << std::endl; - os << " LINESTRING" << *(pts) << ";" << std::endl; + os << " LINESTRING" << *(getCoordinates()) << ";" << std::endl; os << " Nodes: " << nodeList.size() << std::endl; return os; diff --git a/Sources/geos/src/noding/NodingIntersectionFinder.cpp b/Sources/geos/src/noding/NodingIntersectionFinder.cpp index ab6eb85..ce08241 100644 --- a/Sources/geos/src/noding/NodingIntersectionFinder.cpp +++ b/Sources/geos/src/noding/NodingIntersectionFinder.cpp @@ -79,8 +79,8 @@ NodingIntersectionFinder::processIntersections( /** * Check for an intersection between two vertices which are not both endpoints. */ - long long segDiff = static_cast(segIndex1 - segIndex0); - bool isAdjacentSegment = isSameSegString && std::abs(segDiff) <= 1; + std::size_t segDiff = std::max(segIndex0, segIndex1) - std::min(segIndex0, segIndex1); + bool isAdjacentSegment = isSameSegString && segDiff <= 1; bool isInteriorVertexInt = (!isAdjacentSegment) && isInteriorVertexIntersection(p00, p01, p10, p11, isEnd00, isEnd01, isEnd10, isEnd11); diff --git a/Sources/geos/src/noding/Octant.cpp b/Sources/geos/src/noding/Octant.cpp index 89bd95d..370f228 100644 --- a/Sources/geos/src/noding/Octant.cpp +++ b/Sources/geos/src/noding/Octant.cpp @@ -83,7 +83,7 @@ Octant::octant(double dx, double dy) /*public static*/ int -Octant::octant(const Coordinate& p0, const Coordinate& p1) +Octant::octant(const CoordinateXY& p0, const CoordinateXY& p1) { double dx = p1.x - p0.x; double dy = p1.y - p0.y; diff --git a/Sources/geos/src/noding/ScaledNoder.cpp b/Sources/geos/src/noding/ScaledNoder.cpp index a4991c3..f345362 100644 --- a/Sources/geos/src/noding/ScaledNoder.cpp +++ b/Sources/geos/src/noding/ScaledNoder.cpp @@ -89,7 +89,7 @@ class ScaledNoder::Scaler : public geom::CoordinateFilter { //void filter_ro(const geom::Coordinate* c) { assert(0); } void - filter_rw(geom::Coordinate* c) const override + filter_rw(geom::CoordinateXY* c) const override { c->x = util::round((c->x - sn.offsetX) * sn.scaleFactor); c->y = util::round((c->y - sn.offsetY) * sn.scaleFactor); @@ -114,14 +114,14 @@ class ScaledNoder::ReScaler: public geom::CoordinateFilter { } void - filter_ro(const geom::Coordinate* c) override + filter_ro(const geom::CoordinateXY* c) override { ::geos::ignore_unused_variable_warning(c); assert(0); } void - filter_rw(geom::Coordinate* c) const override + filter_rw(geom::CoordinateXY* c) const override { c->x = c->x / sn.scaleFactor + sn.offsetX; c->y = c->y / sn.scaleFactor + sn.offsetY; @@ -167,9 +167,10 @@ ScaledNoder::scale(SegmentString::NonConstVect& segStrings) const assert(cs->size() == npts); operation::valid::RepeatedPointTester rpt; + // FIXME remove hardcoded hasZ, hasM and derive from input if (rpt.hasRepeatedPoint(cs)) { auto cs2 = operation::valid::RepeatedPointRemover::removeRepeatedPoints(cs); - segStrings[i] = new NodedSegmentString(cs2.release(), ss->getData()); + segStrings[i] = new NodedSegmentString(cs2.release(), true, false, ss->getData()); delete ss; } } diff --git a/Sources/geos/src/noding/SegmentExtractingNoder.cpp b/Sources/geos/src/noding/SegmentExtractingNoder.cpp index aa61f86..de0548b 100644 --- a/Sources/geos/src/noding/SegmentExtractingNoder.cpp +++ b/Sources/geos/src/noding/SegmentExtractingNoder.cpp @@ -14,14 +14,12 @@ #include #include -#include #include #include #include using geos::geom::Coordinate; using geos::geom::CoordinateSequence; -using geos::geom::CoordinateArraySequence; using geos::noding::SegmentString; namespace geos { @@ -57,13 +55,17 @@ SegmentExtractingNoder::extractSegments( const SegmentString* ss, std::vector& outputSegs) { - std::size_t ssSize = ss->size() - 1; - for (std::size_t i = 0; i < ssSize; i++) { - std::vector coords(2); - coords[0] = ss->getCoordinate(i); - coords[1] = ss->getCoordinate(i + 1); - std::unique_ptr cs(new CoordinateArraySequence(std::move(coords))); - std::unique_ptr seg(new NodedSegmentString(cs.release(), ss->getData())); + const CoordinateSequence* ss_seq = ss->getCoordinates(); + + const NodedSegmentString* nss = dynamic_cast(ss); + bool constructZ = nss ? nss->getNodeList().getConstructZ() : ss_seq->hasZ(); + bool constructM = nss ? nss->getNodeList().getConstructM() : ss_seq->hasM(); + + for (std::size_t i = 0; i < ss_seq->getSize() - 1; i++) { + auto cs = detail::make_unique(0, constructZ, constructM); + cs->reserve(2); + cs->add(*ss_seq, i, i + 1); + std::unique_ptr seg(new NodedSegmentString(cs.release(), constructZ, constructM, ss->getData())); outputSegs.push_back(seg.release()); } } diff --git a/Sources/geos/src/noding/SegmentIntersectionDetector.cpp b/Sources/geos/src/noding/SegmentIntersectionDetector.cpp index 28c11dc..9792401 100644 --- a/Sources/geos/src/noding/SegmentIntersectionDetector.cpp +++ b/Sources/geos/src/noding/SegmentIntersectionDetector.cpp @@ -17,9 +17,10 @@ #include #include #include -#include +#include #include +using geos::geom::CoordinateXY; namespace geos { namespace noding { // geos::noding @@ -37,10 +38,10 @@ processIntersections( return; } - const geom::Coordinate& p00 = (*e0->getCoordinates())[ segIndex0 ]; - const geom::Coordinate& p01 = (*e0->getCoordinates())[ segIndex0 + 1 ]; - const geom::Coordinate& p10 = (*e1->getCoordinates())[ segIndex1 ]; - const geom::Coordinate& p11 = (*e1->getCoordinates())[ segIndex1 + 1 ]; + const CoordinateXY& p00 = e0->getCoordinate( segIndex0 ); + const CoordinateXY& p01 = e0->getCoordinate( segIndex0 + 1 ); + const CoordinateXY& p10 = e1->getCoordinate( segIndex1 ); + const CoordinateXY& p11 = e1->getCoordinate( segIndex1 + 1 ); li->computeIntersection(p00, p01, p10, p11); @@ -73,7 +74,7 @@ processIntersections( delete intSegments; // record intersecting segments - intSegments = new geom::CoordinateArraySequence(); + intSegments = new geom::CoordinateSequence(); intSegments->add(p00, true); intSegments->add(p01, true); intSegments->add(p10, true); diff --git a/Sources/geos/src/noding/SegmentNode.cpp b/Sources/geos/src/noding/SegmentNode.cpp index 21e53f0..318514c 100644 --- a/Sources/geos/src/noding/SegmentNode.cpp +++ b/Sources/geos/src/noding/SegmentNode.cpp @@ -26,24 +26,12 @@ #include +using geos::geom::CoordinateXYZM; using geos::geom::Coordinate; namespace geos { namespace noding { // geos.noding -/*public*/ -SegmentNode::SegmentNode(const NodedSegmentString& ss, const Coordinate& nCoord, - std::size_t nSegmentIndex, int nSegmentOctant) - : segmentOctant(nSegmentOctant) - , coord(nCoord) - , segmentIndex(nSegmentIndex) -{ - // Number of points in NodedSegmentString is one-more number of segments - assert(segmentIndex < ss.size()); - isInteriorVar = !coord.equals2D(ss.getCoordinate(segmentIndex)); -} - - std::ostream& operator<< (std::ostream& os, const SegmentNode& n) { diff --git a/Sources/geos/src/noding/SegmentNodeList.cpp b/Sources/geos/src/noding/SegmentNodeList.cpp index 50bcb85..962f4a7 100644 --- a/Sources/geos/src/noding/SegmentNodeList.cpp +++ b/Sources/geos/src/noding/SegmentNodeList.cpp @@ -29,8 +29,6 @@ #include // for use #include #include -#include // FIXME: should we really be using this ? -#include #ifndef GEOS_DEBUG #define GEOS_DEBUG 0 @@ -50,15 +48,6 @@ namespace noding { // geos.noding static Profiler* profiler = Profiler::instance(); #endif - -void -SegmentNodeList::add(const Coordinate& intPt, std::size_t segmentIndex) -{ - // SegmentNode sn(edge, intPt, segmentIndex, edge.getSegmentOctant(segmentIndex)); - nodeMap.emplace_back(edge, intPt, segmentIndex, edge.getSegmentOctant(segmentIndex)); - ready = false; -} - void SegmentNodeList::prepare() const { if (!ready) { std::sort(nodeMap.begin(), nodeMap.end(), [](const SegmentNode& s1, const SegmentNode& s2) { @@ -77,8 +66,15 @@ void SegmentNodeList::addEndpoints() { std::size_t maxSegIndex = edge.size() - 1; - add(&(edge.getCoordinate(0)), 0); - add(&(edge.getCoordinate(maxSegIndex)), maxSegIndex); + + const auto* edgePts = edge.getCoordinates(); + + CoordinateXYZM p0, p1; + edgePts->getAt(0, p0); + edgePts->getAt(maxSegIndex, p1); + + add(p0, 0); + add(p1, maxSegIndex); } /* private */ @@ -246,7 +242,7 @@ std::unique_ptr SegmentNodeList::createSplitEdge(const SegmentNode* ei0, const SegmentNode* ei1) const { auto pts = createSplitEdgePts(ei0, ei1); - return detail::make_unique(pts.release(), edge.getData()); + return detail::make_unique(pts.release(), constructZ, constructM, edge.getData()); } @@ -258,13 +254,13 @@ SegmentNodeList::createSplitEdgePts(const SegmentNode* ei0, const SegmentNode* e // if only two points in split edge they must be the node points if (twoPoints) { - auto pts = detail::make_unique>(); + auto pts = detail::make_unique(2u, constructZ, constructM); pts->setAt(ei0->coord, 0); pts->setAt(ei1->coord, 1); - return RETURN_UNIQUE_PTR(pts); + return pts; } - const Coordinate& lastSegStartPt = edge.getCoordinate(ei1->segmentIndex); + const CoordinateXY& lastSegStartPt = edge.getCoordinate(ei1->segmentIndex); /** * If the last intersection point is not equal to the its segment start pt, * add it to the points list as well. @@ -274,48 +270,50 @@ SegmentNodeList::createSplitEdgePts(const SegmentNode* ei0, const SegmentNode* e */ bool useIntPt1 = ei1->isInterior() || ! ei1->coord.equals2D(lastSegStartPt); - std::vector pts; - pts.reserve(1 + (ei1->segmentIndex - ei0->segmentIndex) + useIntPt1); + std::size_t npts = 1 + (ei1->segmentIndex - ei0->segmentIndex) + useIntPt1; - pts.emplace_back(ei0->coord); - for (std::size_t i = ei0->segmentIndex + 1; i <= ei1->segmentIndex; i++) { - pts.emplace_back(edge.getCoordinate(i)); - } + auto pts = detail::make_unique(0u, constructZ, constructM); + pts->reserve(npts); + + pts->add(ei0->coord); + const CoordinateSequence* edgeCoords = edge.getCoordinates(); + pts->add(*edgeCoords, ei0->segmentIndex + 1, ei1->segmentIndex); if (useIntPt1) { - pts.emplace_back(ei1->coord); + pts->add(ei1->coord); } - return std::unique_ptr(new CoordinateArraySequence(std::move(pts))); + assert(pts->size() == npts); + + return pts; } /*public*/ -std::vector +std::unique_ptr SegmentNodeList::getSplitCoordinates() { // ensure that the list has entries for the first and last point of the edge addEndpoints(); - std::vector coordList; + auto coordList = detail::make_unique(0u, constructZ, constructM); // there should always be at least two entries in the list, since the endpoints are nodes auto it = begin(); const SegmentNode* eiPrev = &(*it); for(auto itEnd = end(); it != itEnd; ++it) { const SegmentNode* ei = &(*it); - addEdgeCoordinates(eiPrev, ei, coordList); + addEdgeCoordinates(eiPrev, ei, *coordList); eiPrev = ei; } - // Remove duplicate Coordinates from coordList - coordList.erase(std::unique(coordList.begin(), coordList.end()), coordList.end()); + assert(!coordList->hasRepeatedPoints()); return coordList; } /*private*/ void -SegmentNodeList::addEdgeCoordinates(const SegmentNode* ei0, const SegmentNode* ei1, std::vector& coordList) const { +SegmentNodeList::addEdgeCoordinates(const SegmentNode* ei0, const SegmentNode* ei1, CoordinateSequence& coordList) const { auto pts = createSplitEdgePts(ei0, ei1); // Append pts to coordList - pts->toVector(coordList); + coordList.add(*pts, false); } diff --git a/Sources/geos/src/noding/snap/SnappingIntersectionAdder.cpp b/Sources/geos/src/noding/snap/SnappingIntersectionAdder.cpp index cd0c1aa..e3ce05c 100644 --- a/Sources/geos/src/noding/snap/SnappingIntersectionAdder.cpp +++ b/Sources/geos/src/noding/snap/SnappingIntersectionAdder.cpp @@ -65,8 +65,8 @@ SnappingIntersectionAdder::processIntersections(SegmentString* seg0, std::size_t */ if (li.hasIntersection() && li.getIntersectionNum() == 1) { - const Coordinate& intPt = li.getIntersection(0); - const Coordinate& snapPt = snapPointIndex.snap(intPt); + const auto& intPt = li.getIntersection(0); + const auto& snapPt = snapPointIndex.snap(intPt); static_cast(seg0)->addIntersection(snapPt, segIndex0); static_cast(seg1)->addIntersection(snapPt, segIndex1); diff --git a/Sources/geos/src/noding/snap/SnappingNoder.cpp b/Sources/geos/src/noding/snap/SnappingNoder.cpp index bbe1820..616bec8 100644 --- a/Sources/geos/src/noding/snap/SnappingNoder.cpp +++ b/Sources/geos/src/noding/snap/SnappingNoder.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include #include @@ -97,23 +97,24 @@ SnappingNoder::seedSnapIndex(std::vector& segStrings) SegmentString* SnappingNoder::snapVertices(SegmentString* ss) { - std::unique_ptr> snapCoords = snap(ss->getCoordinates()); - std::unique_ptr cs(new CoordinateArraySequence(snapCoords.release())); - return new NodedSegmentString(cs.release(), ss->getData()); + auto snapCoords = snap(ss->getCoordinates()); + // FIXME remove hardcoded hasZ, hasM and derive from input + return new NodedSegmentString(snapCoords.release(), false, false, ss->getData()); } /*private*/ -std::unique_ptr> -SnappingNoder::snap(CoordinateSequence* cs) +std::unique_ptr +SnappingNoder::snap(const CoordinateSequence* cs) { - std::unique_ptr> snapCoords(new std::vector); - for (std::size_t i = 0, sz = cs->size(); i < sz; i++) { - const Coordinate& pt = snapIndex.snap(cs->getAt(i)); - snapCoords->push_back(pt); - } - // Remove repeated points - snapCoords->erase(std::unique(snapCoords->begin(), snapCoords->end()), snapCoords->end()); + auto snapCoords = detail::make_unique(); + snapCoords->reserve(cs->size()); + + cs->forEach([&snapCoords, this](const Coordinate& origPt) { + const Coordinate& pt = snapIndex.snap(origPt); + snapCoords->add(pt, false); // Remove repeated points + + }); return snapCoords; } diff --git a/Sources/geos/src/noding/snapround/HotPixel.cpp b/Sources/geos/src/noding/snapround/HotPixel.cpp index 42a76de..0b0d0f0 100644 --- a/Sources/geos/src/noding/snapround/HotPixel.cpp +++ b/Sources/geos/src/noding/snapround/HotPixel.cpp @@ -20,8 +20,8 @@ #include #include #include -#include #include +#include #include // for std::min and std::max #include @@ -34,24 +34,8 @@ namespace geos { namespace noding { // geos.noding namespace snapround { // geos.noding.snapround -HotPixel::HotPixel(const Coordinate& newPt, double newScaleFactor) - : originalPt(newPt) - , scaleFactor(newScaleFactor) - , hpIsNode(false) - , hpx(newPt.x) - , hpy(newPt.y) -{ - if(scaleFactor <= 0.0) { - throw util::IllegalArgumentException("Scale factor must be non-zero"); - } - if(scaleFactor != 1.0) { - hpx = scaleRound(newPt.x); - hpy = scaleRound(newPt.y); - } -} - /*public*/ -const geom::Coordinate& +const geom::CoordinateXYZM& HotPixel::getCoordinate() const { return originalPt; @@ -59,7 +43,7 @@ HotPixel::getCoordinate() const /* public */ bool -HotPixel::intersects(const Coordinate& p) const +HotPixel::intersects(const CoordinateXY& p) const { double x = scale(p.x); double y = scale(p.y); @@ -76,8 +60,8 @@ HotPixel::intersects(const Coordinate& p) const /*public*/ bool -HotPixel::intersects(const Coordinate& p0, - const Coordinate& p1) const +HotPixel::intersects(const CoordinateXY& p0, + const CoordinateXY& p1) const { if(scaleFactor == 1.0) { return intersectsScaled(p0.x, p0.y, p1.x, p1.y); diff --git a/Sources/geos/src/noding/snapround/HotPixelIndex.cpp b/Sources/geos/src/noding/snapround/HotPixelIndex.cpp index 254af6b..16f3ce6 100644 --- a/Sources/geos/src/noding/snapround/HotPixelIndex.cpp +++ b/Sources/geos/src/noding/snapround/HotPixelIndex.cpp @@ -43,9 +43,8 @@ HotPixelIndex::HotPixelIndex(const PrecisionModel* p_pm) /*public*/ HotPixel* -HotPixelIndex::add(const Coordinate& p) +HotPixelIndex::addRounded(const CoordinateXYZM& pRound) { - Coordinate pRound = round(p); HotPixel* hp = find(pRound); /** @@ -53,6 +52,7 @@ HotPixelIndex::add(const Coordinate& p) * must have more than one vertex in them * and thus must be nodes. */ + if (hp != nullptr) { hp->setToNode(); return hp; @@ -94,8 +94,11 @@ HotPixelIndex::add(const CoordinateSequence *pts) std::mt19937 g(rd()); std::shuffle(idxs.begin(), idxs.end(), g); - for (auto i : idxs) { - add(pts->getAt(i)); + switch(pts->getCoordinateType()){ + case CoordinateType::XY: for (auto i : idxs) { add(CoordinateXYZM(pts->getAt(i))); } break; + case CoordinateType::XYZ: for (auto i : idxs) { add(pts->getAt(i)); } break; + case CoordinateType::XYM: for (auto i : idxs) { add(CoordinateXYZM(pts->getAt(i))); } break; + case CoordinateType::XYZM: for (auto i : idxs) { add(pts->getAt(i)); } break; } } @@ -118,12 +121,12 @@ HotPixelIndex::add(const std::vector& pts) /*public*/ void -HotPixelIndex::addNodes(const CoordinateSequence *pts) +HotPixelIndex::addNodes(const CoordinateSequence* pts) { - for (std::size_t i = 0, sz = pts->size(); i < sz; i++) { - HotPixel* hp = add(pts->getAt(i)); + pts->forEach([this](const auto& coord) -> void { + HotPixel* hp = this->add(coord); hp->setToNode(); - } + }); } /*public*/ @@ -147,19 +150,10 @@ HotPixelIndex::find(const geom::Coordinate& pixelPt) return (HotPixel*)(kdNode->getData()); } -/*private*/ -Coordinate -HotPixelIndex::round(const Coordinate& pt) -{ - Coordinate p2 = pt; - pm->makePrecise(p2); - return p2; -} - /*public*/ void -HotPixelIndex::query(const Coordinate& p0, const Coordinate& p1, index::kdtree::KdNodeVisitor& visitor) +HotPixelIndex::query(const CoordinateXY& p0, const CoordinateXY& p1, index::kdtree::KdNodeVisitor& visitor) { Envelope queryEnv(p0, p1); queryEnv.expandBy(1.0 / scaleFactor); diff --git a/Sources/geos/src/noding/snapround/SnapRoundingIntersectionAdder.cpp b/Sources/geos/src/noding/snapround/SnapRoundingIntersectionAdder.cpp index de90f99..f9687cc 100644 --- a/Sources/geos/src/noding/snapround/SnapRoundingIntersectionAdder.cpp +++ b/Sources/geos/src/noding/snapround/SnapRoundingIntersectionAdder.cpp @@ -47,17 +47,16 @@ SnapRoundingIntersectionAdder::processIntersections( // don't bother intersecting a segment with itself if (e0 == e1 && segIndex0 == segIndex1) return; - const Coordinate& p00 = e0->getCoordinate(segIndex0); - const Coordinate& p01 = e0->getCoordinate(segIndex0 + 1); - const Coordinate& p10 = e1->getCoordinate(segIndex1); - const Coordinate& p11 = e1->getCoordinate(segIndex1 + 1); + const CoordinateSequence& seq0 = *e0->getCoordinates(); + const CoordinateSequence& seq1 = *e1->getCoordinates(); + + li.computeIntersection(seq0, segIndex0, seq1, segIndex1); - li.computeIntersection(p00, p01, p10, p11); if (li.hasIntersection()) { if (li.isInteriorIntersection()) { for (std::size_t intIndex = 0, intNum = li.getIntersectionNum(); intIndex < intNum; intIndex++) { // Take a copy of the intersection coordinate - intersections->emplace_back(li.getIntersection(intIndex)); + intersections.add(li.getIntersection(intIndex)); } static_cast(e0)->addIntersections(&li, segIndex0, 0); static_cast(e1)->addIntersections(&li, segIndex1, 1); @@ -71,35 +70,39 @@ SnapRoundingIntersectionAdder::processIntersections( * To avoid certain robustness issues in snap-rounding, * also treat very near vertex-segment situations as intersections. */ - processNearVertex(p00, e1, segIndex1, p10, p11 ); - processNearVertex(p01, e1, segIndex1, p10, p11 ); - processNearVertex(p10, e0, segIndex0, p00, p01 ); - processNearVertex(p11, e0, segIndex0, p00, p01 ); + processNearVertex(seq0, segIndex0, seq1, segIndex1, e1); + processNearVertex(seq0, segIndex0 + 1, seq1, segIndex1, e1); + processNearVertex(seq1, segIndex1, seq0, segIndex0, e0); + processNearVertex(seq1, segIndex1 + 1, seq0, segIndex0, e0); +} + +bool +SnapRoundingIntersectionAdder::isNearSegmentInterior( + const geom::CoordinateXY& p, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1) const +{ + if (p.distance(p0) < nearnessTol) return false; + if (p.distance(p1) < nearnessTol) return false; + + double distSeg = algorithm::Distance::pointToSegment(p, p0, p1); + return distSeg < nearnessTol; } /*private*/ void SnapRoundingIntersectionAdder::processNearVertex( - const geom::Coordinate& p, SegmentString* edge, std::size_t segIndex, - const geom::Coordinate& p0, const geom::Coordinate& p1) + const CoordinateSequence& ptSeq, std::size_t ptIndex, + const CoordinateSequence& segSeq, std::size_t segIndex, + SegmentString* edge) { - /** - * Don't add intersection if candidate vertex is near endpoints of segment. - * This avoids creating "zig-zag" linework - * (since the vertex could actually be outside the segment envelope). - */ - if (p.distance(p0) < nearnessTol) return; - if (p.distance(p1) < nearnessTol) return; - - double distSeg = algorithm::Distance::pointToSegment(p, p0, p1); - if (distSeg < nearnessTol) { - intersections->emplace_back(p); - static_cast(edge)->addIntersection(p, segIndex); + const CoordinateXY& pt = ptSeq.getAt(ptIndex); + const CoordinateXY& seg0 = segSeq.getAt(segIndex); + const CoordinateXY& seg1 = segSeq.getAt(segIndex + 1); + if (isNearSegmentInterior(pt, seg0, seg1)) { + intersections.add(ptSeq, ptIndex, ptIndex); + static_cast(edge)->addIntersection(intersections.back(), segIndex); } } - - } // namespace geos.noding.snapround } // namespace geos.noding } // namespace geos diff --git a/Sources/geos/src/noding/snapround/SnapRoundingNoder.cpp b/Sources/geos/src/noding/snapround/SnapRoundingNoder.cpp index cfb76f9..3817cd7 100644 --- a/Sources/geos/src/noding/snapround/SnapRoundingNoder.cpp +++ b/Sources/geos/src/noding/snapround/SnapRoundingNoder.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include #include @@ -81,8 +81,8 @@ SnapRoundingNoder::addIntersectionPixels(std::vector& segStrings SnapRoundingIntersectionAdder intAdder(tolerance); MCIndexNoder noder(&intAdder, tolerance); noder.computeNodes(&segStrings); - std::unique_ptr> intPts = intAdder.getIntersections(); - pixelIndex.addNodes(*intPts); + const auto& intPts = intAdder.getIntersections(); + pixelIndex.addNodes(&intPts); } /*private void*/ @@ -96,14 +96,15 @@ SnapRoundingNoder::addVertexPixels(std::vector& segStrings) } /*private*/ -std::vector -SnapRoundingNoder::round(const std::vector& pts) const +std::unique_ptr +SnapRoundingNoder::round(const CoordinateSequence& pts) const { - std::vector roundPts = pts; - for (auto& pt: roundPts) { + auto roundPts = detail::make_unique(0u, pts.hasZ(), pts.hasM()); + roundPts->reserve(pts.size()); + pts.forEach([this, &roundPts](auto pt) { pm->makePrecise(pt); - } - roundPts.erase(std::unique(roundPts.begin(), roundPts.end()), roundPts.end()); + roundPts->add(pt, false); + }); return roundPts; } @@ -145,32 +146,31 @@ SnapRoundingNoder::computeSegmentSnaps(NodedSegmentString* ss) * The coordinates are now rounded to the grid, * in preparation for snapping to the Hot Pixels */ - std::vector pts = ss->getNodedCoordinates(); - std::vector ptsRoundVec = round(pts); - std::unique_ptr ptsRound(new CoordinateArraySequence(std::move(ptsRoundVec))); + auto pts = ss->getNodedCoordinates(); + auto ptsRound = round(*pts); // if complete collapse this edge can be eliminated if (ptsRound->size() <= 1) return nullptr; // Create new nodedSS to allow adding any hot pixel nodes - NodedSegmentString* snapSS = new NodedSegmentString(ptsRound.release(), ss->getData()); + NodedSegmentString* snapSS = new NodedSegmentString(ptsRound.release(), ss->getNodeList().getConstructZ(), ss->getNodeList().getConstructM(), ss->getData()); std::size_t snapSSindex = 0; - for (std::size_t i = 0, sz = pts.size()-1; i < sz; i++ ) { + for (std::size_t i = 0, sz = pts->size()-1; i < sz; i++ ) { - const geom::Coordinate& currSnap = snapSS->getCoordinate(snapSSindex); + const geom::CoordinateXY& currSnap = snapSS->getCoordinate(snapSSindex); /** * If the segment has collapsed completely, skip it */ - Coordinate p1 = pts[i+1]; - Coordinate p1Round = p1; + const CoordinateXY& p1 = pts->getAt(i+1); + CoordinateXY p1Round(p1); pm->makePrecise(p1Round); if (p1Round.equals2D(currSnap)) continue; - Coordinate p0 = pts[i]; + const CoordinateXY p0 = pts->getAt(i); /** * Add any Hot Pixel intersections with *original* segment to rounded segment. @@ -193,16 +193,16 @@ SnapRoundingNoder::computeSegmentSnaps(NodedSegmentString* ss) */ /*private*/ void -SnapRoundingNoder::snapSegment(Coordinate& p0, Coordinate& p1, NodedSegmentString* ss, std::size_t segIndex) +SnapRoundingNoder::snapSegment(const CoordinateXY& p0, const CoordinateXY& p1, NodedSegmentString* ss, std::size_t segIndex) { /* First define a visitor to use in the pixelIndex.query() */ struct SnapRoundingVisitor : KdNodeVisitor { - const Coordinate& p0; - const Coordinate& p1; + const CoordinateXY& p0; + const CoordinateXY& p1; NodedSegmentString* ss; std::size_t segIndex; - SnapRoundingVisitor(const Coordinate& pp0, const Coordinate& pp1, NodedSegmentString* pss, std::size_t psegIndex) + SnapRoundingVisitor(const CoordinateXY& pp0, const CoordinateXY& pp1, NodedSegmentString* pss, std::size_t psegIndex) : p0(pp0), p1(pp1), ss(pss), segIndex(psegIndex) {}; void visit(KdNode* node) override { @@ -242,24 +242,27 @@ void SnapRoundingNoder::addVertexNodeSnaps(NodedSegmentString* ss) { const CoordinateSequence* pts = ss->getCoordinates(); - for (std::size_t i = 1; i < pts->size() - 1; i++) { - const Coordinate& p0 = pts->getAt(i); - snapVertexNode(p0, ss, i); - } + std::size_t i = 0; + pts->forEach([this, ss, &i](const auto& p0) -> void { + if (i > 0 && i < ss->size() - 1) { + this->snapVertexNode(p0, ss, i); + } + i++; + }); } void -SnapRoundingNoder::snapVertexNode(const Coordinate& p0, NodedSegmentString* ss, std::size_t segIndex) +SnapRoundingNoder::snapVertexNode(const CoordinateXY& p0, NodedSegmentString* ss, std::size_t segIndex) { /* First define a visitor to use in the pixelIndex.query() */ struct SnapRoundingVertexNodeVisitor : KdNodeVisitor { - const Coordinate& p0; + const CoordinateXY& p0; NodedSegmentString* ss; std::size_t segIndex; - SnapRoundingVertexNodeVisitor(const Coordinate& pp0, NodedSegmentString* pss, std::size_t psegIndex) + SnapRoundingVertexNodeVisitor(const CoordinateXY& pp0, NodedSegmentString* pss, std::size_t psegIndex) : p0(pp0), ss(pss), segIndex(psegIndex) {}; void visit(KdNode* node) override { diff --git a/Sources/geos/src/operation/BoundaryOp.cpp b/Sources/geos/src/operation/BoundaryOp.cpp index 1579077..b407a2a 100644 --- a/Sources/geos/src/operation/BoundaryOp.cpp +++ b/Sources/geos/src/operation/BoundaryOp.cpp @@ -22,9 +22,11 @@ #include #include #include +#include #include using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; using geos::geom::Dimension; using geos::geom::Geometry; using geos::geom::LineString; @@ -50,6 +52,8 @@ BoundaryOp::BoundaryOp(const geom::Geometry& geom, const algorithm::BoundaryNode std::unique_ptr BoundaryOp::getBoundary() { + util::ensureNoCurvedComponents(m_geom); + if (auto ls = dynamic_cast(&m_geom)) { return boundaryLineString(*ls); } @@ -134,17 +138,17 @@ BoundaryOp::boundaryMultiLineString(const geom::MultiLineString& mLine) auto bdyPts = computeBoundaryCoordinates(mLine); // return Point or MultiPoint - if (bdyPts.size() == 1) { - return std::unique_ptr(m_geomFact.createPoint(bdyPts[0])); + if (bdyPts->size() == 1) { + return std::unique_ptr(m_geomFact.createPoint(bdyPts->getAt(0))); } // this handles 0 points case as well - return m_geomFact.createMultiPoint(std::move(bdyPts)); + return std::unique_ptr(m_geomFact.createMultiPoint(*bdyPts)); } -std::vector +std::unique_ptr BoundaryOp::computeBoundaryCoordinates(const geom::MultiLineString& mLine) { - std::vector bdyPts; + auto bdyPts = detail::make_unique(); std::map endpointMap; for (std::size_t i = 0; i < mLine.getNumGeometries(); i++) { @@ -161,7 +165,7 @@ BoundaryOp::computeBoundaryCoordinates(const geom::MultiLineString& mLine) for (const auto& entry: endpointMap) { auto valence = entry.second; if (m_bnRule.isInBoundary(valence)) { - bdyPts.push_back(entry.first); + bdyPts->add(entry.first); } } diff --git a/Sources/geos/src/operation/GeometryGraphOperation.cpp b/Sources/geos/src/operation/GeometryGraphOperation.cpp index 42d157b..65b5c29 100644 --- a/Sources/geos/src/operation/GeometryGraphOperation.cpp +++ b/Sources/geos/src/operation/GeometryGraphOperation.cpp @@ -54,9 +54,9 @@ GeometryGraphOperation::GeometryGraphOperation(const Geometry* g0, setComputationPrecision(pm1); } - arg[0] = new GeometryGraph(0, g0, + arg[0] = std::make_unique(0, g0, algorithm::BoundaryNodeRule::getBoundaryOGCSFS()); - arg[1] = new GeometryGraph(1, g1, + arg[1] = std::make_unique(1, g1, algorithm::BoundaryNodeRule::getBoundaryOGCSFS()); } @@ -80,8 +80,8 @@ GeometryGraphOperation::GeometryGraphOperation(const Geometry* g0, setComputationPrecision(pm1); } - arg[0] = new GeometryGraph(0, g0, boundaryNodeRule); - arg[1] = new GeometryGraph(1, g1, boundaryNodeRule); + arg[0] = std::make_unique(0, g0, boundaryNodeRule); + arg[1] = std::make_unique(1, g1, boundaryNodeRule); } @@ -93,7 +93,7 @@ GeometryGraphOperation::GeometryGraphOperation(const Geometry* g0): setComputationPrecision(pm0); - arg[0] = new GeometryGraph(0, g0); + arg[0] = std::make_unique(0, g0); } const Geometry* @@ -114,9 +114,6 @@ GeometryGraphOperation::setComputationPrecision(const PrecisionModel* pm) GeometryGraphOperation::~GeometryGraphOperation() { - for(unsigned int i = 0; i < arg.size(); ++i) { - delete arg[i]; - } } } // namespace geos.operation diff --git a/Sources/geos/src/operation/buffer/BufferBuilder.cpp b/Sources/geos/src/operation/buffer/BufferBuilder.cpp index b03b65e..716215b 100644 --- a/Sources/geos/src/operation/buffer/BufferBuilder.cpp +++ b/Sources/geos/src/operation/buffer/BufferBuilder.cpp @@ -15,11 +15,10 @@ * ********************************************************************** * - * Last port: operation/buffer/BufferBuilder.java r378 (JTS-1.12) + * Last port: operation/buffer/BufferBuilder.java 149b38907 (JTS-1.12) * **********************************************************************/ -#include #include #include #include @@ -32,10 +31,12 @@ #include #include #include -#include +#include #include #include #include +#include +#include #include #include #include @@ -86,14 +87,14 @@ template std::unique_ptr convertSegStrings(const GeometryFactory* fact, Iterator it, Iterator et) { - std::vector lines; + std::vector> lines; while(it != et) { const SegmentString* ss = *it; - LineString* line = fact->createLineString(ss->getCoordinates()->clone().release()); - lines.push_back(line); + auto line = fact->createLineString(ss->getCoordinates()->clone()); + lines.push_back(std::move(line)); ++it; } - return std::unique_ptr(fact->buildGeometry(lines.begin(), lines.end())); + return fact->buildGeometry(lines.begin(), lines.end()); } } @@ -192,7 +193,7 @@ BufferBuilder::bufferLineSingleSided(const Geometry* g, double distance, CoordinateSequence* seq = lineList[i]; // SegmentString takes ownership of CoordinateSequence - SegmentString* ss = new NodedSegmentString(seq, nullptr); + SegmentString* ss = new NodedSegmentString(seq, seq->hasZ(), seq->hasM(), nullptr); curveList.push_back(ss); } lineList.clear(); @@ -205,18 +206,15 @@ BufferBuilder::bufferLineSingleSided(const Geometry* g, double distance, SegmentString::NonConstVect* nodedEdges = noder->getNodedSubstrings(); // Create a geometry out of the noded substrings. - std::vector< Geometry* >* singleSidedNodedEdges = - new std::vector< Geometry* >(); - singleSidedNodedEdges->reserve(nodedEdges->size()); + std::vector> singleSidedNodedEdges; + singleSidedNodedEdges.reserve(nodedEdges->size()); for(std::size_t i = 0, n = nodedEdges->size(); i < n; ++i) { SegmentString* ss = (*nodedEdges)[i]; - Geometry* tmp = geomFact->createLineString( - ss->getCoordinates()->clone().release() - ); + auto tmp = geomFact->createLineString(ss->getCoordinates()->clone()); delete ss; - singleSidedNodedEdges->push_back(tmp); + singleSidedNodedEdges.push_back(std::move(tmp)); } delete nodedEdges; @@ -226,8 +224,7 @@ BufferBuilder::bufferLineSingleSided(const Geometry* g, double distance, } curveList.clear(); - std::unique_ptr singleSided(geomFact->createMultiLineString( - singleSidedNodedEdges)); + auto singleSided = geomFact->createMultiLineString(std::move(singleSidedNodedEdges)); #ifdef GEOS_DEBUG_SSB std::cerr << "edges|" << *singleSided << std::endl; @@ -241,7 +238,7 @@ BufferBuilder::bufferLineSingleSided(const Geometry* g, double distance, // intersections with caps and joins curves using geos::operation::overlay::snap::SnapOverlayOp; std::unique_ptr intersectedLines = SnapOverlayOp::overlayOp(*singleSided, *bufLineString, - OverlayOp::opINTERSECTION); + overlayng::OverlayNG::INTERSECTION); #ifdef GEOS_DEBUG_SSB std::cerr << "intersection" << "|" << *intersectedLines << std::endl; @@ -252,8 +249,8 @@ BufferBuilder::bufferLineSingleSided(const Geometry* g, double distance, lineMerge.add(intersectedLines.get()); auto mergedLines = lineMerge.getMergedLineStrings(); - // Convert the result into a std::vector< Geometry* >. - std::vector< Geometry* >* mergedLinesGeom = new std::vector< Geometry* >(); + // Convert the result into a vector of geometries + std::vector> mergedLinesGeom; const Coordinate& startPoint = l->getCoordinatesRO()->front(); const Coordinate& endPoint = l->getCoordinatesRO()->back(); while(!mergedLines.empty()) { @@ -330,9 +327,7 @@ BufferBuilder::bufferLineSingleSided(const Geometry* g, double distance, if (sz > 1) { if (sz < coords->size()) { // Points were removed; make a new CoordinateSequence - auto seqFactory = geomFact->getCoordinateSequenceFactory(); - - auto newSeq = seqFactory->create(sz, coords->getDimension()); + auto newSeq = detail::make_unique(sz, coords->getDimension()); for (std::size_t i = 0; i < sz; i++) { newSeq->setAt(coords->getAt(i + front), i); @@ -342,7 +337,7 @@ BufferBuilder::bufferLineSingleSided(const Geometry* g, double distance, } // Add the coordinates to the resultant line string. - mergedLinesGeom->push_back(geomFact->createLineString(coords.release())); + mergedLinesGeom.push_back(geomFact->createLineString(std::move(coords))); } } @@ -357,17 +352,14 @@ BufferBuilder::bufferLineSingleSided(const Geometry* g, double distance, singleSided.reset(); intersectedLines.reset(); - if(mergedLinesGeom->size() > 1) { - return std::unique_ptr(geomFact->createMultiLineString(mergedLinesGeom)); + if(mergedLinesGeom.size() > 1) { + return geomFact->createMultiLineString(std::move(mergedLinesGeom)); } - else if(mergedLinesGeom->size() == 1) { - - std::unique_ptr single((*mergedLinesGeom)[0]); - delete mergedLinesGeom; + else if(mergedLinesGeom.size() == 1) { + std::unique_ptr single = std::move(mergedLinesGeom[0]); return single; } else { - delete mergedLinesGeom; return geomFact->createLineString(); } } @@ -377,6 +369,27 @@ std::unique_ptr BufferBuilder::buffer(const Geometry* g, double distance) // throw(GEOSException *) { + // Single-sided buffer only works on single geometries + // so we'll need to do it individually and then union + // the result + if ( bufParams.isSingleSided() && g->getNumGeometries() > 1 ) + { + std::vector< std::unique_ptr > geoms_to_delete; + for ( size_t i=0, n=g->getNumGeometries(); igetGeometryN(i); + std::unique_ptr subbuf = subbuilder.buffer(subgeom, distance); + geoms_to_delete.push_back( std::move(subbuf) ); + } + std::vector< Geometry* > geoms; + for ( size_t i=0, n=geoms_to_delete.size(); igetPrecisionModel(); @@ -422,7 +435,7 @@ BufferBuilder::buffer(const Geometry* g, double distance) #endif std::unique_ptr resultGeom(nullptr); - std::unique_ptr< std::vector > resultPolyList; + std::vector> resultPolyList; std::vector subgraphList; try { @@ -449,7 +462,7 @@ BufferBuilder::buffer(const Geometry* g, double distance) PolygonBuilder polyBuilder(geomFact); buildSubgraphs(subgraphList, polyBuilder); - resultPolyList.reset(polyBuilder.getPolygons()); + resultPolyList = polyBuilder.getPolygons(); } // Get rid of the subgraphs, shouldn't be needed anymore @@ -459,22 +472,22 @@ BufferBuilder::buffer(const Geometry* g, double distance) subgraphList.clear(); #if GEOS_DEBUG - std::cerr << "PolygonBuilder got " << resultPolyList->size() + std::cerr << "PolygonBuilder got " << resultPolyList.size() << " polygons" << std::endl; #if GEOS_DEBUG > 1 - for(std::size_t i = 0, n = resultPolyList->size(); i < n; i++) { - std::cerr << (*resultPolyList)[i]->toString() << std::endl; + for(std::size_t i = 0, n = resultPolyList.size(); i < n; i++) { + std::cerr << resultPolyList[i]->toString() << std::endl; } #endif #endif // just in case ... - if(resultPolyList->empty()) { + if(resultPolyList.empty()) { return createEmptyResultGeometry(); } // resultPolyList ownership transferred here - resultGeom.reset(geomFact->buildGeometry(resultPolyList.release())); + resultGeom = geomFact->buildGeometry(std::move(resultPolyList)); } catch(const util::GEOSException& /* exc */) { @@ -488,6 +501,73 @@ BufferBuilder::buffer(const Geometry* g, double distance) throw; } + // Cleanup single-sided buffer artifacts, if needed + if ( bufParams.isSingleSided() ) + { + + // Get linework of input geom + const Geometry *inputLineString = g; + std::unique_ptr inputPolygonBoundary; + if ( g->getDimension() > 1 ) + { + inputPolygonBoundary = g->getBoundary(); + inputLineString = inputPolygonBoundary.get(); + } + +#if GEOS_DEBUG + std::cerr << "Input linework: " << *inputLineString << std::endl; +#endif + + // Get linework of buffer geom + std::unique_ptr bufferBoundary = resultGeom->getBoundary(); + +#if GEOS_DEBUG + std::cerr << "Buffer boundary: " << *bufferBoundary << std::endl; +#endif + + // Node all linework + using operation::overlayng::OverlayNG; + std::unique_ptr nodedLinework = OverlayNG::overlay(inputLineString, bufferBoundary.get(), OverlayNG::UNION); + +#if GEOS_DEBUG + std::cerr << "Noded linework: " << *nodedLinework << std::endl; +#endif + + using operation::polygonize::Polygonizer; + Polygonizer plgnzr; + plgnzr.add(nodedLinework.get()); + std::vector> polys = plgnzr.getPolygons(); + +#if GEOS_DEBUG + std::cerr << "Polygonization of noded linework returned: " << polys.size() << " polygons" << std::endl; +#endif + + if ( polys.size() > 1 ) + { + // Only keep larger polygon + std::vector>::iterator + it, + itEnd = polys.end(), + biggestPolygonIterator = itEnd; + double maxArea = 0; + for ( it = polys.begin(); it != itEnd; ++it ) + { + double area = (*it)->getArea(); + if ( area > maxArea ) + { + biggestPolygonIterator = it; + maxArea = area; + } + } + + if ( biggestPolygonIterator != itEnd ) { + Geometry *gg = (*biggestPolygonIterator).release(); + return std::unique_ptr( gg ); + } // else there were no polygons formed... + } + + } + return resultGeom; } @@ -540,7 +620,6 @@ BufferBuilder::computeNodedEdges(SegmentString::NonConstVect& bufferSegStrList, #if JTS_DEBUG geos::io::WKTWriter wktWriter; - wktWriter.setTrim(true); std::cerr << "before noding: " << wktWriter.write( convertSegStrings(geomFact, bufferSegStrList.begin(), diff --git a/Sources/geos/src/operation/buffer/BufferCurveSetBuilder.cpp b/Sources/geos/src/operation/buffer/BufferCurveSetBuilder.cpp index f8c316f..41038d7 100644 --- a/Sources/geos/src/operation/buffer/BufferCurveSetBuilder.cpp +++ b/Sources/geos/src/operation/buffer/BufferCurveSetBuilder.cpp @@ -14,7 +14,7 @@ * ********************************************************************** * - * Last port: operation/buffer/BufferCurveSetBuilder.java r378 (JTS-1.12) + * Last port: operation/buffer/BufferCurveSetBuilder.java 4c343e79f (JTS-1.19) * **********************************************************************/ @@ -38,10 +38,12 @@ #include #include #include +#include #include // for min #include #include +#include #include #include #include @@ -113,7 +115,7 @@ BufferCurveSetBuilder::addCurve(CoordinateSequence* coord, Label* newlabel = new Label(0, Location::BOUNDARY, leftLoc, rightLoc); // coord ownership transferred to SegmentString - SegmentString* e = new NodedSegmentString(coord, newlabel); + SegmentString* e = new NodedSegmentString(coord, coord->hasZ(), coord->hasM(), newlabel); // SegmentString doesnt own the sequence, so we need to delete in // the destructor @@ -245,6 +247,10 @@ BufferCurveSetBuilder::addPolygon(const Polygon* p) auto shellCoord = operation::valid::RepeatedPointRemover::removeRepeatedAndInvalidPoints(shell->getCoordinatesRO()); + if (shellCoord->isEmpty()) { + throw util::GEOSException("Shell empty after removing invalid points"); + } + // don't attempt to buffer a polygon // with too few distinct vertices if(distance <= 0.0 && shellCoord->size() < 3) { @@ -333,7 +339,7 @@ BufferCurveSetBuilder::addRingSide(const CoordinateSequence* coord, } std::vector lineList; curveBuilder.getRingCurve(coord, side, offsetDistance, lineList); - // ASSERT: lineList contains exactly 1 curve (this is teh JTS semantics) + // ASSERT: lineList contains exactly 1 curve (this is the JTS semantics) if (lineList.size() > 0) { const CoordinateSequence* curve = lineList[0]; /** @@ -353,56 +359,67 @@ BufferCurveSetBuilder::addRingSide(const CoordinateSequence* coord, /* private static*/ bool BufferCurveSetBuilder::isRingCurveInverted( - const CoordinateSequence* inputPts, double dist, - const CoordinateSequence* curvePts) + const CoordinateSequence* inputRing, double dist, + const CoordinateSequence* curveRing) { if (dist == 0.0) return false; /** * Only proper rings can invert. */ - if (inputPts->size() <= 3) return false; + if (inputRing->size() <= 3) return false; /** * Heuristic based on low chance that a ring with many vertices will invert. * This low limit ensures this test is fairly efficient. */ - if (inputPts->size() >= MAX_INVERTED_RING_SIZE) return false; + if (inputRing->size() >= MAX_INVERTED_RING_SIZE) return false; /** * An inverted curve has no more points than the input ring. * This also eliminates concave inputs (which will produce fillet arcs) */ - if (curvePts->size() > INVERTED_CURVE_VERTEX_FACTOR * inputPts->size()) return false; + if (curveRing->size() > INVERTED_CURVE_VERTEX_FACTOR * inputRing->size()) return false; /** - * Check if the curve vertices are all closer to the input ring - * than the buffer distance. - * If so, the curve is NOT a valid buffer curve. + * If curve contains points which are on the buffer, + * it is not inverted and can be included in the raw curves. */ - double distTol = NEARNESS_FACTOR * fabs(dist); - double maxDist = maxDistance(curvePts, inputPts); - bool isCurveTooClose = maxDist < distTol; - return isCurveTooClose; + if (hasPointOnBuffer(inputRing, dist, curveRing)) + return false; + + //-- curve is inverted, so discard it + return true; +//std::cout << std::setprecision(10) << io::WKTWriter::toLineString(*curveRing) << std::endl; +//std::cout << "isRingCurveInverted: " << isCurveTooClose << " maxDist = " << maxDist << std::endl; } -/** - * Computes the maximum distance out of a set of points to a linestring. - * - * @param pts the points - * @param line the linestring vertices - * @return the maximum distance - */ -/* private static */ -double -BufferCurveSetBuilder::maxDistance(const CoordinateSequence* pts, const CoordinateSequence* line) { - double maxDistance = 0; - for (std::size_t i = 0; i < pts->size(); i++) { - const Coordinate& p = pts->getAt(i); - double dist = Distance::pointToSegmentString(p, line); - if (dist > maxDistance) { - maxDistance = dist; +/* private static*/ +bool +BufferCurveSetBuilder::hasPointOnBuffer( + const CoordinateSequence* inputRing, double dist, + const CoordinateSequence* curveRing) +{ + double distTol = NEARNESS_FACTOR * fabs(dist); + + for (std::size_t i = 0; i < curveRing->size(); i++) { + const CoordinateXY& v = curveRing->getAt(i); + + //-- check curve vertices + double distVertex = Distance::pointToSegmentString(v, inputRing); + if (distVertex > distTol) { + return true; + } + + //-- check curve segment midpoints + std::size_t iNext = (i < curveRing->size() - 1) ? i + 1 : 0; + const CoordinateXY& vnext = curveRing->getAt(iNext); + CoordinateXY midPt = LineSegment::midPoint(v, vnext); + + double distMid = Distance::pointToSegmentString(midPt, inputRing); + if (distMid > distTol) { + return true; } } - return maxDistance; + return false; } /*private*/ @@ -438,7 +455,7 @@ BufferCurveSetBuilder::isTriangleErodedCompletely( { Triangle tri(triangleCoord->getAt(0), triangleCoord->getAt(1), triangleCoord->getAt(2)); - Coordinate inCentre; + CoordinateXY inCentre; tri.inCentre(inCentre); double distToCentre = Distance::pointToSegment(inCentre, tri.p0, tri.p1); bool ret = distToCentre < std::fabs(bufferDistance); diff --git a/Sources/geos/src/operation/buffer/BufferInputLineSimplifier.cpp b/Sources/geos/src/operation/buffer/BufferInputLineSimplifier.cpp index b3f3e3d..8db9515 100644 --- a/Sources/geos/src/operation/buffer/BufferInputLineSimplifier.cpp +++ b/Sources/geos/src/operation/buffer/BufferInputLineSimplifier.cpp @@ -18,7 +18,6 @@ #include #include // for inlines -#include // for constructing the return #include // for use #include // for use #include @@ -128,7 +127,7 @@ BufferInputLineSimplifier::findNextNonDeletedIndex(std::size_t index) const std::unique_ptr BufferInputLineSimplifier::collapseLine() const { - auto coordList = new CoordinateArraySequence(); + auto coordList = new CoordinateSequence(); for(std::size_t i = 0, n = inputLine.size(); i < n; ++i) { if(isDeleted[i] != DELETE) { diff --git a/Sources/geos/src/operation/buffer/BufferParameters.cpp b/Sources/geos/src/operation/buffer/BufferParameters.cpp index 8c97a51..76c6c13 100644 --- a/Sources/geos/src/operation/buffer/BufferParameters.cpp +++ b/Sources/geos/src/operation/buffer/BufferParameters.cpp @@ -19,6 +19,7 @@ #include // for std::abs() #include // for cos +#include #include #include @@ -95,7 +96,7 @@ BufferParameters::setQuadrantSegments(int quadSegs) double BufferParameters::bufferDistanceError(int quadSegs) { - double alpha = MATH_PI / 2.0 / quadSegs; + double alpha = algorithm::Angle::PI_OVER_2 / quadSegs; return 1 - cos(alpha / 2.0); } diff --git a/Sources/geos/src/operation/buffer/BufferSubgraph.cpp b/Sources/geos/src/operation/buffer/BufferSubgraph.cpp index 5c0211d..f43eb16 100644 --- a/Sources/geos/src/operation/buffer/BufferSubgraph.cpp +++ b/Sources/geos/src/operation/buffer/BufferSubgraph.cpp @@ -71,7 +71,7 @@ BufferSubgraph::create(Node* node) addReachable(node); // We are assuming that dirEdgeList - // contains *at leas* ONE forward DirectedEdge + // contains *at least* ONE forward DirectedEdge finder.findEdge(&dirEdgeList); rightMostCoord = &(finder.getCoordinate()); diff --git a/Sources/geos/src/operation/buffer/OffsetCurve.cpp b/Sources/geos/src/operation/buffer/OffsetCurve.cpp index 35916f4..f524ccd 100644 --- a/Sources/geos/src/operation/buffer/OffsetCurve.cpp +++ b/Sources/geos/src/operation/buffer/OffsetCurve.cpp @@ -12,59 +12,49 @@ * **********************************************************************/ -#include -#include -#include -#include -#include #include #include -#include -#include #include +#include +#include #include #include #include -#include +#include #include +#include #include #include #include +#include +#include + +#include +#include +#include +#include +#include -#include +using geos::algorithm::Distance; +using geos::geom::util::GeometryMapper; +using geos::index::chain::MonotoneChain; +using geos::index::chain::MonotoneChainSelectAction; +using geos::operation::valid::RepeatedPointRemover; -using namespace geos::index::chain; using namespace geos::geom; -using geos::geom::util::GeometryMapper; namespace geos { namespace operation { namespace buffer { -/* public static */ -std::unique_ptr -OffsetCurve::getCurve(const Geometry& geom, double distance) -{ - OffsetCurve oc(geom, distance); - return oc.getCurve(); -} - +static constexpr double NOT_IN_CURVE = -1.0; -/* public static */ -std::unique_ptr -OffsetCurve::getCurve(const Geometry& geom, - double dist, - int quadSegs, - BufferParameters::JoinStyle joinStyle, - double mitreLimit) +/* public */ +void +OffsetCurve::setJoined(bool pIsJoined) { - BufferParameters bufParms; - if (quadSegs >= 0) bufParms.setQuadrantSegments(quadSegs); - if (joinStyle >= 0) bufParms.setJoinStyle(joinStyle); - if (mitreLimit >= 0) bufParms.setMitreLimit(mitreLimit); - OffsetCurve oc(geom, dist, bufParms); - return oc.getCurve(); + isJoined = pIsJoined; } @@ -92,48 +82,103 @@ OffsetCurve::getCurve() return GeometryMapper::flatMap(inputGeom, 1, GetCurveMapOp); } +/* public static */ +std::unique_ptr +OffsetCurve::getCurve(const Geometry& geom, double distance) +{ + OffsetCurve oc(geom, distance); + return oc.getCurve(); +} + /* public static */ -void -OffsetCurve::rawOffset(const LineString& geom, double dist, - std::vector& lineList) +std::unique_ptr +OffsetCurve::getCurve(const Geometry& geom, + double dist, + int quadSegs, + BufferParameters::JoinStyle joinStyle, + double mitreLimit) { - BufferParameters bp; - rawOffset(geom, dist, bp, lineList); - return; + BufferParameters bufParms; + if (quadSegs >= 0) bufParms.setQuadrantSegments(quadSegs); + if (joinStyle >= 0) bufParms.setJoinStyle(joinStyle); + if (mitreLimit >= 0) bufParms.setMitreLimit(mitreLimit); + OffsetCurve oc(geom, dist, bufParms); + return oc.getCurve(); } /* public static */ -void -OffsetCurve::rawOffset(const LineString& geom, double distance, BufferParameters& bufParams, - std::vector& lineList) +std::unique_ptr +OffsetCurve::getCurveJoined(const Geometry& geom, double dist) { - OffsetCurveBuilder ocb(geom.getFactory()->getPrecisionModel(), bufParams); - ocb.getOffsetCurve(geom.getCoordinatesRO(), distance, lineList); - return; + OffsetCurve oc(geom, dist); + oc.setJoined(true); + return oc.getCurve(); } +/* public static */ +std::unique_ptr +OffsetCurve::rawOffsetCurve( + const LineString& line, + double dist, + BufferParameters& bufParams) +{ + const CoordinateSequence* pts = line.getCoordinatesRO(); + std::unique_ptr cleanPts = RepeatedPointRemover::removeRepeatedAndInvalidPoints(pts); + + OffsetCurveBuilder ocb(line.getFactory()->getPrecisionModel(), bufParams); + return ocb.getOffsetCurve(cleanPts.get(), dist); +} + + +/* public static */ +std::unique_ptr +OffsetCurve::rawOffset(const LineString& line, double dist) +{ + BufferParameters bufParams; + return rawOffsetCurve(line, dist, bufParams); +} + /* private */ -std::unique_ptr -OffsetCurve::computeCurve(const LineString& lineGeom, double p_distance) +std::unique_ptr +OffsetCurve::computeCurve(const LineString& lineGeom, double dist) { - //-- first handle special/simple cases + //-- first handle simple cases + //-- empty or single-point line if (lineGeom.getNumPoints() < 2 || lineGeom.getLength() == 0.0) { return geomFactory->createLineString(); } + //-- zero offset distance + if (dist == 0) { + return lineGeom.clone(); + } + //-- two-point line if (lineGeom.getNumPoints() == 2) { - return offsetSegment(lineGeom.getCoordinatesRO(), p_distance); + return offsetSegment(lineGeom.getCoordinatesRO(), dist); } - std::vector rawOffsetLines; - rawOffset(lineGeom, p_distance, bufferParams, rawOffsetLines); - if (rawOffsetLines.empty() || rawOffsetLines[0]->size() == 0) { - for (auto* cs: rawOffsetLines) - delete cs; - return geomFactory->createLineString(); + auto sections = computeSections(lineGeom, dist); + + if (isJoined) { + return OffsetCurveSection::toLine(sections, geomFactory); + } + else { + return OffsetCurveSection::toGeometry(sections, geomFactory); + } +} + +/* private */ +std::vector> +OffsetCurve::computeSections(const LineString& lineGeom, double dist) +{ + std::unique_ptr rawCurve = rawOffsetCurve(lineGeom, dist, bufferParams); + std::vector> sections; + if (rawCurve->size() == 0) { + return sections; } + /** * Note: If the raw offset curve has no * narrow concave angles or self-intersections it could be returned as is. @@ -141,213 +186,316 @@ OffsetCurve::computeCurve(const LineString& lineGeom, double p_distance) * and testing indicates little performance advantage, * so not doing this. */ + std::unique_ptr bufferPoly = getBufferOriented(lineGeom, dist, bufferParams); - std::unique_ptr bufferPoly = getBufferOriented(lineGeom, p_distance, bufferParams); + //-- first extract offset curve sections from shell + auto shell = bufferPoly->getExteriorRing()->getCoordinatesRO(); + computeCurveSections(shell, *rawCurve, sections); - //-- first try matching shell to raw curve - const CoordinateSequence* shell = bufferPoly->getExteriorRing()->getCoordinatesRO(); - std::unique_ptr offsetCurve = computeCurve(shell, rawOffsetLines); - if (! offsetCurve->isEmpty() || bufferPoly->getNumInteriorRing() == 0) { - for (auto* cs: rawOffsetLines) - delete cs; - return offsetCurve; + //-- extract offset curve sections from holes + for (std::size_t i = 0; i < bufferPoly->getNumInteriorRing(); i++) { + auto hole = bufferPoly->getInteriorRingN(i)->getCoordinatesRO(); + computeCurveSections(hole, *rawCurve, sections); } - - //-- if shell didn't work, try matching to largest hole - auto longestHole = extractLongestHole(*bufferPoly); - const CoordinateSequence* holePts = longestHole ? longestHole->getCoordinatesRO() : nullptr; - offsetCurve = computeCurve(holePts, rawOffsetLines); - for (auto* cs: rawOffsetLines) - delete cs; - return offsetCurve; + return sections; } - /* private */ +/* private */ std::unique_ptr -OffsetCurve::offsetSegment(const CoordinateSequence* pts, double p_distance) +OffsetCurve::offsetSegment(const CoordinateSequence* pts, double dist) { - LineSegment ls(pts->getAt(0), pts->getAt(1)); - LineSegment offsetSeg = ls.offset(p_distance); - std::vector coords = { offsetSeg.p0, offsetSeg.p1 }; - return geomFactory->createLineString(std::move(coords)); + LineSegment offsetSeg(pts->getAt(0), pts->getAt(1)); + offsetSeg = offsetSeg.offset(dist); + CoordinateSequence cs; + cs.add(offsetSeg.p0); + cs.add(offsetSeg.p1); + return geomFactory->createLineString(std::move(cs)); } /* private static */ std::unique_ptr -OffsetCurve::getBufferOriented(const LineString& geom, double p_distance, BufferParameters& bufParms) +OffsetCurve::getBufferOriented(const LineString& geom, double dist, BufferParameters& bufParams) { - std::unique_ptr buffer = BufferOp::bufferOp(&geom, std::abs(p_distance), bufParms); - std::unique_ptr bufferPoly = extractMaxAreaPolygon(*buffer); + std::unique_ptr buffer = BufferOp::bufferOp(&geom, std::abs(dist), bufParams); + const Polygon* bufferPoly = extractMaxAreaPolygon(buffer.get()); //-- for negative distances (Right of input) reverse buffer direction to match offset curve - if (p_distance < 0) { - bufferPoly = bufferPoly->reverse(); - } - return bufferPoly; + return dist < 0 + ? bufferPoly->reverse() + : bufferPoly->clone(); } /* private static */ -std::unique_ptr -OffsetCurve::extractMaxAreaPolygon(const Geometry& geom) +const Polygon* +OffsetCurve::extractMaxAreaPolygon(const Geometry* geom) { - const std::size_t numGeometries = geom.getNumGeometries(); - if (numGeometries == 1) { - const Polygon& poly = static_cast(geom); - return poly.clone(); - } - - assert(numGeometries > 1); - const Polygon* maxPoly = static_cast(geom.getGeometryN(0)); - double maxArea = maxPoly->getArea(); - for (std::size_t i = 1; i < numGeometries; i++) { - const Polygon* poly = static_cast(geom.getGeometryN(i)); + if (geom->getGeometryTypeId() == GEOS_POLYGON) + return static_cast(geom); + + double maxArea = 0.0; + const Polygon* maxPoly = nullptr; + for (std::size_t i = 0; i < geom->getNumGeometries(); i++) { + const Geometry* subgeom = geom->getGeometryN(i); + if (subgeom->getGeometryTypeId() != GEOS_POLYGON) continue; + const Polygon* poly = static_cast(subgeom); double area = poly->getArea(); - if (area > maxArea) { + if (maxPoly == nullptr || area > maxArea) { maxPoly = poly; maxArea = area; } } - return maxPoly->clone(); + return maxPoly; } -/* private static */ -std::unique_ptr -OffsetCurve::extractLongestHole(const Polygon& poly) -{ - const LinearRing* largestHole = nullptr; - double maxLen = -1; - for (std::size_t i = 0; i < poly.getNumInteriorRing(); i++) { - const LinearRing* hole = poly.getInteriorRingN(i); - double len = hole->getLength(); - if (len > maxLen) { - largestHole = hole; - maxLen = len; - } - } - return largestHole ? largestHole->clone() : nullptr; -} /* private */ -std::unique_ptr -OffsetCurve::computeCurve(const CoordinateSequence* bufferPts, std::vector& rawOffsetList) +void +OffsetCurve::computeCurveSections( + const CoordinateSequence* bufferRingPts, + const CoordinateSequence& rawCurve, + std::vector>& sections) { - std::vector isInCurve; - isInCurve.resize(bufferPts->size() - 1, false); - - SegmentMCIndex segIndex(bufferPts); - - int curveStart = -1; - CoordinateSequence* cs = rawOffsetList[0]; - for (std::size_t i = 0; i < cs->size() - 1; i++) { - int index = markMatchingSegments( - cs->getAt(i), cs->getAt(i+1), - segIndex, bufferPts, isInCurve); - if (curveStart < 0) { - curveStart = index; + std::vector rawPosition(bufferRingPts->size()-1, NOT_IN_CURVE); + + SegmentMCIndex bufferSegIndex(bufferRingPts); + std::size_t bufferFirstIndex = NO_COORD_INDEX; + double minRawPosition = -1; + for (std::size_t i = 0; i < rawCurve.size() - 1; i++) { + std::size_t minBufferIndexForSeg = matchSegments(rawCurve[i], rawCurve[i+1], i, bufferSegIndex, bufferRingPts, rawPosition); + if (minBufferIndexForSeg != NO_COORD_INDEX) { + double pos = rawPosition[minBufferIndexForSeg]; + if (bufferFirstIndex == NO_COORD_INDEX || pos < minRawPosition) { + minRawPosition = pos; + bufferFirstIndex = minBufferIndexForSeg; + } } } - std::vector curvePts; - extractSection(bufferPts, curveStart, isInCurve, curvePts); - return geomFactory->createLineString(std::move(curvePts)); + //-- no matching sections found in this buffer ring + if (bufferFirstIndex == NO_COORD_INDEX) + return; + + extractSections(bufferRingPts, rawPosition, bufferFirstIndex, sections); } -void -OffsetCurve::MatchCurveSegmentAction::select(const MonotoneChain& mc, std::size_t segIndex) + +/* private */ +std::size_t +OffsetCurve::matchSegments( + const Coordinate& raw0, const Coordinate& raw1, + std::size_t rawCurveIndex, + SegmentMCIndex& bufferSegIndex, + const CoordinateSequence* bufferPts, + std::vector& rawCurvePos) { - (void)mc; // Quiet unused variable warning /** - * A curveRingPt segment may match all or only a portion of a single raw segment. - * There may be multiple curve ring segs that match along the raw segment. - * The one closest to the segment start is recorded as the offset curve start. + * An action to match a raw offset curve segment + * to segments in a buffer ring + * and record the matched segment locations(s) along the raw curve. + * + * @author Martin Davis */ - double frac = subsegmentMatchFrac(bufferPts->getAt(segIndex), bufferPts->getAt(segIndex+1), p0, p1, matchDistance); - //-- no match - if (frac < 0) return; + /* private static */ + class MatchCurveSegmentAction : public MonotoneChainSelectAction + { + + public: + + const Coordinate& p0; + const Coordinate& p1; + std::size_t rawCurveIndex; + double matchDistance; + const CoordinateSequence* bufferRingPts; + std::vector& rawCurveLoc; + double minRawLocation; + std::size_t bufferRingMinIndex; + + MatchCurveSegmentAction( + const Coordinate& p_p0, + const Coordinate& p_p1, + std::size_t p_rawCurveIndex, + double p_matchDistance, + const CoordinateSequence* p_bufferRingPts, + std::vector& p_rawCurveLoc) + : p0(p_p0) + , p1(p_p1) + , rawCurveIndex(p_rawCurveIndex) + , matchDistance(p_matchDistance) + , bufferRingPts(p_bufferRingPts) + , rawCurveLoc(p_rawCurveLoc) + , minRawLocation(-1.0) + , bufferRingMinIndex(NO_COORD_INDEX) + {}; + + std::size_t getBufferMinIndex() { + return bufferRingMinIndex; + } - isInCurve[segIndex] = true; + void select(const geom::LineSegment& seg) override { + (void)seg; // quiet unused variable warning + return; + } - //-- record lowest index - if (minFrac < 0 || frac < minFrac) { - minFrac = frac; - minCurveIndex = static_cast(segIndex); - } -} + void select(const MonotoneChain& mc, std::size_t segIndex) override + { + (void)mc; // quiet unused variable warning + /** + * A curveRingPt segment may match all or only a portion of a single raw segment. + * There may be multiple curve ring segs that match along the raw segment. + */ + double frac = segmentMatchFrac( + bufferRingPts->getAt(segIndex), + bufferRingPts->getAt(segIndex+1), + p0, p1, matchDistance); + + //-- no match + if (frac < 0) return; + + //-- location is used to sort segments along raw curve + double location = static_cast(rawCurveIndex) + frac; + rawCurveLoc[segIndex] = location; + //-- record lowest index + if (minRawLocation < 0 || location < minRawLocation) { + minRawLocation = location; + bufferRingMinIndex = segIndex; + } + } + }; -/* private */ -int -OffsetCurve::markMatchingSegments( - const Coordinate& p0, const Coordinate& p1, - SegmentMCIndex& segIndex, const CoordinateSequence* bufferPts, - std::vector& isInCurve) -{ - Envelope matchEnv(p0, p1); + Envelope matchEnv(raw0, raw1); matchEnv.expandBy(matchDistance); - MatchCurveSegmentAction action(p0, p1, bufferPts, matchDistance, isInCurve); - segIndex.query(&matchEnv, action); - return action.getMinCurveIndex(); + MatchCurveSegmentAction matchAction(raw0, raw1, rawCurveIndex, matchDistance, bufferPts, rawCurvePos); + bufferSegIndex.query(&matchEnv, matchAction); + return matchAction.getBufferMinIndex(); } - /* private static */ double -OffsetCurve::subsegmentMatchFrac(const Coordinate& p0, const Coordinate& p1, - const Coordinate& seg0, const Coordinate& seg1, double matchDistance) +OffsetCurve::segmentMatchFrac( + const Coordinate& p0, const Coordinate& p1, + const Coordinate& seg0, const Coordinate& seg1, + double matchDistance) { - if (matchDistance < algorithm::Distance::pointToSegment(p0, seg0, seg1)) - return -1; - if (matchDistance < algorithm::Distance::pointToSegment(p1, seg0, seg1)) - return -1; - //-- matched - determine position as fraction + if (matchDistance < Distance::pointToSegment(p0, seg0, seg1)) + return -1.0; + if (matchDistance < Distance::pointToSegment(p1, seg0, seg1)) + return -1.0; + //-- matched - determine position as fraction along segment LineSegment seg(seg0, seg1); return seg.segmentFraction(p0); } -/* private static */ +/* private */ void -OffsetCurve::extractSection(const CoordinateSequence* ring, int iStartIndex, - std::vector& isExtracted, std::vector& extractedPoints) +OffsetCurve::extractSections( + const CoordinateSequence* ringPts, + std::vector& rawCurveLoc, + std::size_t startIndex, + std::vector>& sections) { - if (iStartIndex < 0) - return; + std::size_t sectionStart = startIndex; + std::size_t sectionCount = 0; + std::size_t sectionEnd; + do { + sectionEnd = findSectionEnd(rawCurveLoc, sectionStart, startIndex); + double location = rawCurveLoc[sectionStart]; + std::size_t lastIndex = prevIndex(sectionEnd, rawCurveLoc.size()); + double lastLoc = rawCurveLoc[lastIndex]; + std::unique_ptr section = OffsetCurveSection::create(ringPts, sectionStart, sectionEnd, location, lastLoc); + sections.emplace_back(section.release()); + sectionStart = findSectionStart(rawCurveLoc, sectionEnd); + + //-- check for an abnormal state + if (sectionCount++ > ringPts->size()) { + util::Assert::shouldNeverReachHere("Too many sections for ring - probable bug"); + } + } while (sectionStart != startIndex && sectionEnd != startIndex); +} - CoordinateList coordList; - std::size_t startIndex = static_cast(iStartIndex); - std::size_t i = startIndex; + +/* private */ +std::size_t +OffsetCurve::findSectionStart( + const std::vector& loc, + std::size_t end) +{ + std::size_t start = end; do { - coordList.insert(coordList.end(), ring->getAt(i), false); - if (! isExtracted[i]) { - break; + std::size_t next = nextIndex(start, loc.size()); + //-- skip ahead if segment is not in raw curve + if (loc[start] == NOT_IN_CURVE) { + start = next; + continue; } - i = next(i, ring->size() - 1); - } while (i != startIndex); - //-- handle case where every segment is extracted - if (isExtracted[i]) { - coordList.insert(coordList.end(), ring->getAt(i), false); - } + std::size_t prev = prevIndex(start, loc.size()); + //-- if prev segment is not in raw curve then have found a start + if (loc[prev] == NOT_IN_CURVE) { + return start; + } + if (isJoined) { + /** + * Start section at next gap in raw curve. + * Only needed for joined curve, since otherwise + * contiguous buffer segments can be in same curve section. + */ + double locDelta = std::abs(loc[start] - loc[prev]); + if (locDelta > 1.0) + return start; + } + start = next; + } while (start != end); + return start; +} - //-- if only one point found return empty LineString - if (coordList.size() == 1) - return; - std::copy(coordList.begin(), coordList.end(), - std::back_inserter(extractedPoints)); +/* private */ +std::size_t +OffsetCurve::findSectionEnd( + const std::vector& loc, + std::size_t start, + std::size_t firstStartIndex) +{ + // assert: pos[start] is IN CURVE + std::size_t end = start; + std::size_t next; + do { + next = nextIndex(end, loc.size()); + if (loc[next] == NOT_IN_CURVE) + return next; + if (isJoined) { + /** + * End section at gap in raw curve. + * Only needed for joined curve, since otherwise + * contiguous buffer segments can be in same section + */ + double locDelta = std::abs(loc[next] - loc[end]); + if (locDelta > 1) + return next; + } + end = next; + } while (end != start && end != firstStartIndex); + return end; +} - return; +/* private static */ +std::size_t +OffsetCurve::nextIndex(std::size_t i, std::size_t size) +{ + return i >= size - 1 ? 0 : i + 1; } /* private static */ std::size_t -OffsetCurve::next(std::size_t i, std::size_t size) { - i += 1; - return (i < size) ? i : 0; +OffsetCurve::prevIndex(std::size_t i, std::size_t size) +{ + return i == 0 ? size - 1 : i - 1; } + } // namespace geos.operation.buffer } // namespace geos.operation } // namespace geos - diff --git a/Sources/geos/src/operation/buffer/OffsetCurveBuilder.cpp b/Sources/geos/src/operation/buffer/OffsetCurveBuilder.cpp index c250084..1628ef9 100644 --- a/Sources/geos/src/operation/buffer/OffsetCurveBuilder.cpp +++ b/Sources/geos/src/operation/buffer/OffsetCurveBuilder.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -64,23 +63,50 @@ OffsetCurveBuilder::getLineCurve(const CoordinateSequence* inputPts, double posDistance = std::abs(distance); - std::unique_ptr segGen = getSegGen(posDistance); + OffsetSegmentGenerator segGen(precisionModel, bufParams, posDistance); if(inputPts->getSize() <= 1) { - computePointCurve(inputPts->getAt(0), *segGen); + computePointCurve(inputPts->getAt(0), segGen); } else { if(bufParams.isSingleSided()) { bool isRightSide = distance < 0.0; - computeSingleSidedBufferCurve(*inputPts, isRightSide, *segGen); + computeSingleSidedBufferCurve(*inputPts, isRightSide, segGen); } else { - computeLineBufferCurve(*inputPts, *segGen); + computeLineBufferCurve(*inputPts, segGen); } } - segGen->getCoordinates(lineList); + segGen.getCoordinates(lineList); } + +/* public */ +std::unique_ptr +OffsetCurveBuilder::getLineCurve(const CoordinateSequence* inputPts, double pDistance) +{ + distance = pDistance; + + if (isLineOffsetEmpty(distance)) return nullptr; + + double posDistance = std::abs(distance); + OffsetSegmentGenerator segGen(precisionModel, bufParams, posDistance); + if (inputPts->size() <= 1) { + computePointCurve(inputPts->getAt(0), segGen); + } + else { + if (bufParams.isSingleSided()) { + bool isRightSide = distance < 0.0; + computeSingleSidedBufferCurve(*inputPts, isRightSide, segGen); + } + else + computeLineBufferCurve(*inputPts, segGen); + } + + return segGen.getCoordinates(); +} + + /* public */ void OffsetCurveBuilder::getOffsetCurve( @@ -95,26 +121,98 @@ OffsetCurveBuilder::getOffsetCurve( bool isRightSide = p_distance < 0.0; double posDistance = std::abs(p_distance); - std::unique_ptr segGen = getSegGen(posDistance); + OffsetSegmentGenerator segGen(precisionModel, bufParams, posDistance); if (inputPts->size() <= 1) { - computePointCurve(inputPts->getAt(0), *segGen); + computePointCurve(inputPts->getAt(0), segGen); } else { - computeSingleSidedBufferCurve(*inputPts, isRightSide, *segGen); + computeSingleSidedBufferCurve(*inputPts, isRightSide, segGen); } - segGen->getCoordinates(lineList); + segGen.getCoordinates(lineList); // for right side line is traversed in reverse direction, so have to reverse generated line if (isRightSide) { for (auto* cs: lineList) { - CoordinateSequence::reverse(cs); + cs->reverse(); } } return; } +/* public */ +std::unique_ptr +OffsetCurveBuilder::getOffsetCurve( + const CoordinateSequence* inputPts, + double pDistance) +{ + distance = pDistance; + + // a zero width offset curve is empty + if (distance == 0.0) return nullptr; + + bool isRightSide = distance < 0.0; + double posDistance = std::abs(distance); + OffsetSegmentGenerator segGen(precisionModel, bufParams, posDistance); + if (inputPts->size() <= 1) { + computePointCurve(inputPts->getAt(0), segGen); + } + else { + computeOffsetCurve(inputPts, isRightSide, segGen); + } + std::unique_ptr curvePts = segGen.getCoordinates(); + // for right side line is traversed in reverse direction, so have to reverse generated line + if (isRightSide) + curvePts->reverse(); + + return curvePts; +} + + +/* private */ +void +OffsetCurveBuilder::computeOffsetCurve( + const CoordinateSequence* inputPts, + bool isRightSide, + OffsetSegmentGenerator& segGen) +{ + double distTol = simplifyTolerance(std::abs(distance)); + + if (isRightSide) { + //---------- compute points for right side of line + // Simplify the appropriate side of the line before generating + auto simp2 = BufferInputLineSimplifier::simplify(*inputPts, -distTol); + std::size_t n2 = simp2->size() - 1; + if (!n2) + throw util::IllegalArgumentException("Cannot get offset of single-vertex line"); + + // since we are traversing line in opposite order, offset position is still LEFT + segGen.initSideSegments(simp2->getAt(n2), simp2->getAt(n2-1), Position::LEFT); + segGen.addFirstSegment(); + for (std::size_t i = n2 - 1; i > 0; --i) { + segGen.addNextSegment(simp2->getAt(i - 1), true); + } + } + else { + //--------- compute points for left side of line + // Simplify the appropriate side of the line before generating + auto simp1 = BufferInputLineSimplifier::simplify(*inputPts, distTol); + std::size_t n1 = simp1->size() - 1; + if (!n1) + throw util::IllegalArgumentException("Cannot get offset of single-vertex line"); + + segGen.initSideSegments(simp1->getAt(0), simp1->getAt(1), Position::LEFT); + segGen.addFirstSegment(); + for (std::size_t i = 2; i <= n1; i++) { + segGen.addNextSegment(simp1->getAt(i), true); + } + } + segGen.addLastSegment(); +} + + + /* private */ void OffsetCurveBuilder::computePointCurve(const Coordinate& pt, @@ -151,7 +249,7 @@ OffsetCurveBuilder::getSingleSidedLineCurve(const CoordinateSequence* inputPts, double distTol = simplifyTolerance(p_distance); - std::unique_ptr segGen = getSegGen(p_distance); + OffsetSegmentGenerator segGen(precisionModel, bufParams, p_distance); if(leftSide) { //--------- compute points for left side of line @@ -165,12 +263,12 @@ OffsetCurveBuilder::getSingleSidedLineCurve(const CoordinateSequence* inputPts, if(! n1) { throw util::IllegalArgumentException("Cannot get offset of single-vertex line"); } - segGen->initSideSegments(simp1[0], simp1[1], Position::LEFT); - segGen->addFirstSegment(); + segGen.initSideSegments(simp1[0], simp1[1], Position::LEFT); + segGen.addFirstSegment(); for(std::size_t i = 2; i <= n1; ++i) { - segGen->addNextSegment(simp1[i], true); + segGen.addNextSegment(simp1[i], true); } - segGen->addLastSegment(); + segGen.addLastSegment(); } if(rightSide) { @@ -185,15 +283,15 @@ OffsetCurveBuilder::getSingleSidedLineCurve(const CoordinateSequence* inputPts, if(! n2) { throw util::IllegalArgumentException("Cannot get offset of single-vertex line"); } - segGen->initSideSegments(simp2[n2], simp2[n2 - 1], Position::LEFT); - segGen->addFirstSegment(); + segGen.initSideSegments(simp2[n2], simp2[n2 - 1], Position::LEFT); + segGen.addFirstSegment(); for(std::size_t i = n2 - 1; i > 0; --i) { - segGen->addNextSegment(simp2[i - 1], true); + segGen.addNextSegment(simp2[i - 1], true); } - segGen->addLastSegment(); + segGen.addLastSegment(); } - segGen->getCoordinates(lineList); + segGen.getCoordinates(lineList); } /*public*/ @@ -228,11 +326,30 @@ OffsetCurveBuilder::getRingCurve(const CoordinateSequence* inputPts, return; } - std::unique_ptr segGen = getSegGen(std::abs(distance)); - computeRingBufferCurve(*inputPts, side, *segGen); - segGen->getCoordinates(lineList); + OffsetSegmentGenerator segGen(precisionModel, bufParams, std::abs(distance)); + computeRingBufferCurve(*inputPts, side, segGen); + segGen.getCoordinates(lineList); } + +/* public */ +std::unique_ptr +OffsetCurveBuilder::getRingCurve(const CoordinateSequence* inputPts, int side, double pDistance) +{ + distance = pDistance; + if (inputPts->size() <= 2) + return getLineCurve(inputPts, distance); + + // optimize creating ring for for zero distance + if (distance == 0.0) { + return inputPts->clone(); + } + OffsetSegmentGenerator segGen(precisionModel, bufParams, distance); + computeRingBufferCurve(*inputPts, side, segGen); + return segGen.getCoordinates(); +} + + /* private */ double OffsetCurveBuilder::simplifyTolerance(double bufDistance) @@ -355,15 +472,6 @@ OffsetCurveBuilder::computeSingleSidedBufferCurve( segGen.closeRing(); } -/*private*/ -std::unique_ptr -OffsetCurveBuilder::getSegGen(double dist) -{ - std::unique_ptr osg( - new OffsetSegmentGenerator(precisionModel, bufParams, dist) - ); - return osg; -} } // namespace geos.operation.buffer } // namespace geos.operation diff --git a/Sources/geos/src/operation/buffer/OffsetCurveSection.cpp b/Sources/geos/src/operation/buffer/OffsetCurveSection.cpp new file mode 100644 index 0000000..b7051a7 --- /dev/null +++ b/Sources/geos/src/operation/buffer/OffsetCurveSection.cpp @@ -0,0 +1,168 @@ +/********************************************************************** + * + * GEOS - Geometry Engine Open Source + * http://geos.osgeo.org + * + * Copyright (c) 2021 Martin Davis + * Copyright (C) 2021 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU Lesser General Public Licence as published + * by the Free Software Foundation. + * See the COPYING file for more information. + * + **********************************************************************/ + +#include +#include + +#include +#include +#include +#include + +using geos::geom::Geometry; +using geos::geom::Coordinate; +using geos::geom::CoordinateSequence; +using geos::geom::LineString; + + +namespace geos { // geos +namespace operation { // geos.operation +namespace buffer { // geos.operation.buffer + + +/*public*/ +const CoordinateSequence* +OffsetCurveSection::getCoordinates() const +{ + return sectionPts.get(); +} + +std::unique_ptr +OffsetCurveSection::releaseCoordinates() +{ + return std::move(sectionPts); +} + + +/* private */ +bool +OffsetCurveSection::isEndInSameSegment(double nextLoc) const +{ + long segIndex = std::lround(std::floor(locLast)); + long nextIndex = std::lround(std::floor(nextLoc)); + // long segIndex = static_cast(locLast); + // long nextIndex = static_cast(nextLoc); + return segIndex == nextIndex; +} + +bool +OffsetCurveSection::OffsetCurveSectionComparator( + const std::unique_ptr& a, + const std::unique_ptr& b) +{ + if (a->getLocation() < b->getLocation()) + return true; + else + return false; +} + +/* public static */ +std::unique_ptr +OffsetCurveSection::toGeometry( + std::vector>& sections, + const GeometryFactory* geomFactory) +{ + if (sections.size() == 0) + return geomFactory->createLineString(); + if (sections.size() == 1) { + auto cs = sections[0]->releaseCoordinates(); + return geomFactory->createLineString(std::move(cs)); + } + + //-- sort sections in order along the offset curve + + std::sort(sections.begin(), sections.end(), OffsetCurveSectionComparator); + std::vector> lines; + for (auto& section : sections) { + auto cs = section->releaseCoordinates(); + auto ls = geomFactory->createLineString(std::move(cs)); + lines.emplace_back(ls.release()); + } + return geomFactory->createMultiLineString(std::move(lines)); +} + +/** +* Joins section coordinates into a LineString. +* Join vertices which lie in the same raw curve segment +* are removed, to simplify the result linework. +* +* @param sections the sections to join +* @param geomFactory the geometry factory to use +* @return the simplified linestring for the joined sections +*/ +/* public static */ +std::unique_ptr +OffsetCurveSection::toLine( + std::vector>& sections, + const GeometryFactory* geomFactory) +{ + if (sections.size() == 0) + return geomFactory->createLineString(); + if (sections.size() == 1) { + auto cs = sections[0]->releaseCoordinates(); + return geomFactory->createLineString(std::move(cs)); + } + + //-- sort sections in order along the offset curve + std::sort(sections.begin(), sections.end(), OffsetCurveSectionComparator); + + std::unique_ptr pts(new CoordinateSequence()); + + bool removeStartPt = false; + for (std::size_t i = 0; i < sections.size(); i++) { + auto& section = sections[i]; + bool removeEndPt = false; + if (i < sections.size() - 1) { + double nextStartLoc = sections[i+1]->getLocation(); + removeEndPt = section->isEndInSameSegment(nextStartLoc); + } + const CoordinateSequence* secPts = section->getCoordinates(); + for (std::size_t j = 0; j < secPts->size(); j++) { + if ((removeStartPt && j == 0) || (removeEndPt && j == secPts->size()-1)) + continue; + pts->add(secPts->getAt(j), false); + } + removeStartPt = removeEndPt; + } + return geomFactory->createLineString(std::move(pts)); +} + +/* public static */ +std::unique_ptr +OffsetCurveSection::create( + const CoordinateSequence* srcPts, + std::size_t start, std::size_t end, + double loc, double locLast) +{ + std::size_t len; + if (end <= start) + len = srcPts->size() - start + end; + else + len = end - start + 1; + + std::unique_ptr secPts(new CoordinateSequence()); + for (std::size_t i = 0; i < len; i++) { + std::size_t index = (start + i) % (srcPts->size() - 1); + secPts->add(srcPts->getAt(index)); + } + std::unique_ptr ocs(new OffsetCurveSection(std::move(secPts), loc, locLast)); + return ocs; +} + + + +} // namespace geos.operation.buffer +} // namespace geos.operation +} // namespace geos diff --git a/Sources/geos/src/operation/buffer/OffsetSegmentGenerator.cpp b/Sources/geos/src/operation/buffer/OffsetSegmentGenerator.cpp index 579a302..4537f9b 100644 --- a/Sources/geos/src/operation/buffer/OffsetSegmentGenerator.cpp +++ b/Sources/geos/src/operation/buffer/OffsetSegmentGenerator.cpp @@ -28,18 +28,13 @@ #include #include #include -#include #include #include #include #include -#include +#include #include -#ifndef GEOS_DEBUG -#define GEOS_DEBUG 0 -#endif - using namespace geos::algorithm; using namespace geos::geom; @@ -80,11 +75,11 @@ OffsetSegmentGenerator::OffsetSegmentGenerator( { // compute intersections in full precision, to provide accuracy // the points are rounded as they are inserted into the curve line - filletAngleQuantum = MATH_PI / 2.0 / bufParams.getQuadrantSegments(); + filletAngleQuantum = Angle::PI_OVER_2 / bufParams.getQuadrantSegments(); int quadSegs = bufParams.getQuadrantSegments(); if (quadSegs < 1) quadSegs = 1; - filletAngleQuantum = MATH_PI / 2.0 / quadSegs; + filletAngleQuantum = Angle::PI_OVER_2 / quadSegs; /* * Non-round joins cause issues with short closing segments, @@ -209,7 +204,7 @@ OffsetSegmentGenerator::addLineEndCap(const Coordinate& p0, const Coordinate& p1 case BufferParameters::CAP_ROUND: // add offset seg points with a fillet between them segList.addPt(offsetL.p1); - addDirectedFillet(p1, angle + MATH_PI / 2.0, angle - MATH_PI / 2.0, + addDirectedFillet(p1, angle + Angle::PI_OVER_2, angle - Angle::PI_OVER_2, Orientation::CLOCKWISE, distance); segList.addPt(offsetR.p1); break; @@ -222,8 +217,10 @@ OffsetSegmentGenerator::addLineEndCap(const Coordinate& p0, const Coordinate& p1 // add a square defined by extensions of the offset // segment endpoints Coordinate squareCapSideOffset; - squareCapSideOffset.x = fabs(distance) * cos(angle); - squareCapSideOffset.y = fabs(distance) * sin(angle); + double sinangle, cosangle; + Angle::sinCosSnap(angle, sinangle, cosangle); + squareCapSideOffset.x = fabs(distance) * cosangle; + squareCapSideOffset.y = fabs(distance) * sinangle; Coordinate squareCapLOffset( offsetL.p1.x + squareCapSideOffset.x, @@ -251,12 +248,12 @@ OffsetSegmentGenerator::addDirectedFillet(const Coordinate& p, const Coordinate& if(direction == Orientation::CLOCKWISE) { if(startAngle <= endAngle) { - startAngle += 2.0 * MATH_PI; + startAngle += Angle::PI_TIMES_2; } } else { // direction==COUNTERCLOCKWISE if(startAngle >= endAngle) { - startAngle -= 2.0 * MATH_PI; + startAngle -= Angle::PI_TIMES_2; } } @@ -278,13 +275,13 @@ OffsetSegmentGenerator::addDirectedFillet(const Coordinate& p, double startAngle // no segments because angle is less than increment-nothing to do! if(nSegs < 1) return; - // double initAngle, currAngleInc; double angleInc = totalAngle / nSegs; + double sinangle, cosangle; Coordinate pt; for (int i = 0; i < nSegs; i++) { - double angle = startAngle + directionFactor * i * angleInc; - pt.x = p.x + radius * cos(angle); - pt.y = p.y + radius * sin(angle); + Angle::sinCosSnap(startAngle + directionFactor * i * angleInc, sinangle, cosangle); + pt.x = p.x + radius * cosangle; + pt.y = p.y + radius * sinangle; segList.addPt(pt); } } @@ -296,7 +293,7 @@ OffsetSegmentGenerator::createCircle(const Coordinate& p, double p_distance) // add start point Coordinate pt(p.x + p_distance, p.y); segList.addPt(pt); - addDirectedFillet(p, 0.0, 2.0 * MATH_PI, -1, p_distance); + addDirectedFillet(p, 0.0, Angle::PI_TIMES_2, -1, p_distance); segList.closeRing(); } @@ -484,10 +481,10 @@ OffsetSegmentGenerator::addMitreJoin(const geom::Coordinate& cornerPt, * However, this situation should have been eliminated earlier by the check * for whether the offset segment endpoints are almost coincident */ - Coordinate intPt = algorithm::Intersection::intersection(p_offset0.p0, p_offset0.p1, p_offset1.p0, p_offset1.p1); + CoordinateXY intPt = algorithm::CGAlgorithmsDD::intersection(p_offset0.p0, p_offset0.p1, p_offset1.p0, p_offset1.p1); if (!intPt.isNull() && intPt.distance(cornerPt) <= mitreLimitDistance) { - segList.addPt(intPt); + segList.addPt(Coordinate(intPt)); return; } /** @@ -532,20 +529,14 @@ OffsetSegmentGenerator::addLimitedMitreJoin( Coordinate bevelMidPt = project(cornerPt, p_mitreLimitDistance, dirBisectorOut); // slope angle of bevel segment - double dirBevel = Angle::normalize(dirBisectorOut + MATH_PI/2.0); + double dirBevel = Angle::normalize(dirBisectorOut + Angle::PI_OVER_2); // compute the candidate bevel segment by projecting both sides of the midpoint Coordinate bevel0 = project(bevelMidPt, p_distance, dirBevel); Coordinate bevel1 = project(bevelMidPt, p_distance, dirBevel + MATH_PI); - LineSegment bevel(bevel0, bevel1); - - //-- compute intersections with extended offset segments - double extendLen = p_mitreLimitDistance < p_distance ? p_distance : p_mitreLimitDistance; - LineSegment extend0 = extend(p_offset0, 2 * extendLen); - LineSegment extend1 = extend(p_offset1, -2 * extendLen); - Coordinate bevelInt0 = bevel.intersection(extend0); - Coordinate bevelInt1 = bevel.intersection(extend1); + Coordinate bevelInt0(Intersection::intersectionLineSegment(p_offset0.p0, p_offset0.p1, bevel0, bevel1)); + Coordinate bevelInt1(Intersection::intersectionLineSegment(p_offset1.p0, p_offset1.p1, bevel0, bevel1)); //-- add the limited bevel, if it intersects the offsets if (!bevelInt0.isNull() && !bevelInt1.isNull()) { @@ -562,27 +553,14 @@ OffsetSegmentGenerator::addLimitedMitreJoin( } -/* private static */ -LineSegment -OffsetSegmentGenerator::extend(const LineSegment& seg, double dist) -{ - double distFrac = std::abs(dist) / seg.getLength(); - double segFrac = dist >= 0 ? 1 + distFrac : 0 - distFrac; - Coordinate extendPt; - seg.pointAlong(segFrac, extendPt); - if (dist > 0) - return LineSegment(seg.p0, extendPt); - else - return LineSegment(extendPt, seg.p1); -} - - /* private static */ Coordinate OffsetSegmentGenerator::project(const Coordinate& pt, double d, double dir) { - double x = pt.x + d * std::cos(dir); - double y = pt.y + d * std::sin(dir); + double sindir, cosdir; + Angle::sinCosSnap(dir, sindir, cosdir); + double x = pt.x + d * cosdir; + double y = pt.y + d * sindir; return Coordinate(x, y); } diff --git a/Sources/geos/src/operation/buffer/SubgraphDepthLocater.cpp b/Sources/geos/src/operation/buffer/SubgraphDepthLocater.cpp index 44d06c5..4fc9d57 100644 --- a/Sources/geos/src/operation/buffer/SubgraphDepthLocater.cpp +++ b/Sources/geos/src/operation/buffer/SubgraphDepthLocater.cpp @@ -70,7 +70,7 @@ class DepthSegment { } /** - * Defines a comparision operation on DepthSegments + * Defines a comparison operation on DepthSegments * which orders them left to right * *
@@ -183,10 +183,9 @@ SubgraphDepthLocater::findStabbedSegments(const Coordinate& stabbingRayLeftPt,
 
         // optimization - don't bother checking subgraphs
         // which the ray does not intersect
-        Envelope* env = bsg->getEnvelope();
+        const Envelope* env = bsg->getEnvelope();
         if(stabbingRayLeftPt.y < env->getMinY()
                 || stabbingRayLeftPt.y > env->getMaxY()
-                || stabbingRayLeftPt.x < env->getMinX()
                 || stabbingRayLeftPt.x > env->getMaxX()) {
             continue;
         }
@@ -213,6 +212,14 @@ SubgraphDepthLocater::findStabbedSegments(
         if(!de->isForward()) {
             continue;
         }
+
+        const Envelope* env = de->getEdge()->getEnvelope();
+        if(stabbingRayLeftPt.y < env->getMinY()
+                || stabbingRayLeftPt.y > env->getMaxY()
+                || stabbingRayLeftPt.x > env->getMaxX()) {
+            continue;
+        }
+
         findStabbedSegments(stabbingRayLeftPt, de, stabbedSegments);
     }
 }
diff --git a/Sources/geos/src/operation/cluster/AbstractClusterFinder.cpp b/Sources/geos/src/operation/cluster/AbstractClusterFinder.cpp
new file mode 100644
index 0000000..2753207
--- /dev/null
+++ b/Sources/geos/src/operation/cluster/AbstractClusterFinder.cpp
@@ -0,0 +1,141 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2020-2022 Daniel Baston
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+namespace geos {
+namespace operation {
+namespace cluster {
+
+using geom::Geometry;
+
+std::vector>
+AbstractClusterFinder::clusterToVector(const geom::Geometry& g) {
+    return clusterToVector(g.clone());
+}
+
+std::unique_ptr
+AbstractClusterFinder::clusterToCollection(std::unique_ptr && g) {
+    auto gfact = g->getFactory();
+
+    return gfact->createGeometryCollection(clusterToVector(std::move(g)));
+}
+
+std::unique_ptr
+AbstractClusterFinder::clusterToCollection(const geom::Geometry & g) {
+    return clusterToCollection(g.clone());
+}
+
+
+Clusters
+AbstractClusterFinder::cluster(const std::vector & components) {
+    index::strtree::TemplateSTRtree tree;
+
+    for (std::size_t i = 0; i < components.size(); i++) {
+        tree.insert(*components[i]->getEnvelopeInternal(), i);
+    }
+
+    UnionFind uf(components.size());
+    return process(components, tree, uf);
+}
+
+std::vector>
+AbstractClusterFinder::clusterToVector(std::unique_ptr && g) {
+    const geom::GeometryFactory& gfact = *g->getFactory();
+
+    std::vector components(g->getNumGeometries());
+    for (size_t i = 0; i < g->getNumGeometries(); i++) {
+        components[i]= g->getGeometryN(i);
+    }
+
+    const auto& clusters = cluster(components);
+
+    std::vector> component_geoms = getComponents(std::move(g));
+
+    std::vector> cluster_geoms;
+
+    for (size_t i = 0; i < clusters.getNumClusters(); i++) {
+        std::vector> cluster_component_geoms;
+        cluster_component_geoms.reserve(clusters.getSize(i));
+        for (auto it = clusters.begin(i); it != clusters.end(i); ++it) {
+            cluster_component_geoms.push_back(std::move(component_geoms[*it]));
+        }
+        cluster_geoms.push_back(gfact.buildGeometry(std::move(cluster_component_geoms)));
+    }
+
+    return cluster_geoms;
+}
+
+Clusters
+AbstractClusterFinder::process(const std::vector & components,
+                   index::strtree::TemplateSTRtree & tree,
+                   UnionFind & uf) {
+
+    std::vector hits;
+
+    for (size_t i = 0; i < components.size(); i++) {
+        const geom::Geometry* gi = components[i];
+
+        hits.clear();
+
+        // Sort candidates based on envelope area to try and perform simpler intersection tests instead of
+        // complex ones. This seems to perform better than sorting on the number of points.
+        tree.query(queryEnvelope(gi), hits);
+        std::sort(hits.begin(), hits.end(), [&components](std::size_t a, std::size_t b) {
+            return components[a]->getEnvelopeInternal()->getArea() < components[b]->getEnvelopeInternal()->getArea();
+        });
+
+        for (std::size_t j : hits) {
+            if (uf.different(i, j)) {
+                const geom::Geometry* gj = components[j];
+
+                // Only call shouldJoin with the more complex geometry in the LHS, where it will become
+                // the prepared geometry.
+                // TODO move point check to subclasses that benefit to avoid for those that don't?
+                if (gi->getNumPoints() >= gj->getNumPoints() && shouldJoin(gi, gj)) {
+                    uf.join(i, j);
+                }
+            }
+        }
+
+    }
+
+    return uf.getClusters();
+}
+
+std::vector>
+AbstractClusterFinder::getComponents(std::unique_ptr&& g)
+{
+    if (g->isCollection()) {
+        return detail::down_cast(g.get())->releaseGeometries();
+    } else {
+        std::vector> ret(1);
+        ret[0] = std::move(g);
+        return ret;
+    }
+}
+
+
+
+}
+}
+}
diff --git a/Sources/geos/src/operation/cluster/Clusters.cpp b/Sources/geos/src/operation/cluster/Clusters.cpp
new file mode 100644
index 0000000..891f5e5
--- /dev/null
+++ b/Sources/geos/src/operation/cluster/Clusters.cpp
@@ -0,0 +1,56 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2021-2022 Daniel Baston
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+
+namespace geos {
+namespace operation {
+namespace cluster {
+
+Clusters::Clusters(UnionFind & uf, std::vector elemsInCluster, size_t numElems) {
+    m_elemsInCluster = std::move(elemsInCluster);
+    m_numElems = numElems;
+
+    if (!m_elemsInCluster.empty()) {
+        uf.sortByCluster(m_elemsInCluster.begin(), m_elemsInCluster.end());
+
+        m_starts.reserve(uf.getNumClusters());
+        m_starts.push_back(0);
+
+        for (std::size_t i = 1; i < m_elemsInCluster.size(); i++) {
+            if (uf.find(m_elemsInCluster[i]) != uf.find(m_elemsInCluster[i - 1])) {
+                m_starts.push_back(i);
+            }
+        }
+    }
+}
+
+std::vector
+Clusters::getClusterIds(std::size_t noClusterValue) const {
+    std::vector cluster_ids(m_numElems, noClusterValue);
+
+    for (std::size_t i = 0; i < getNumClusters(); i++) {
+        for (auto it = begin(i); it != end(i); ++it) {
+            cluster_ids[*it] = i;
+        }
+    }
+
+    return cluster_ids;
+}
+
+}
+}
+}
+
diff --git a/Sources/geos/src/operation/cluster/DBSCANClusterFinder.cpp b/Sources/geos/src/operation/cluster/DBSCANClusterFinder.cpp
new file mode 100644
index 0000000..1a87eb8
--- /dev/null
+++ b/Sources/geos/src/operation/cluster/DBSCANClusterFinder.cpp
@@ -0,0 +1,133 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2015-2021 Daniel Baston
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace geos {
+namespace operation {
+namespace cluster {
+
+static inline void unionIfAvailable(UnionFind & uf,
+                                    size_t p,
+                                    size_t q,
+                                    std::vector & is_in_core,
+                                    std::vector & in_a_cluster) {
+    if (in_a_cluster[q]) {
+        // Can we merge p's cluster with q's cluster? We can do this only
+        // if both p and q are considered _core_ points of their respective
+        // clusters.
+        if (is_in_core[q]) {
+            uf.join(p, q);
+        }
+    } else {
+        uf.join(p, q);
+        in_a_cluster[q] = true;
+    }
+}
+
+Clusters DBSCANClusterFinder::process(const std::vector & components,
+                      index::strtree::TemplateSTRtree & tree,
+                      UnionFind & uf) {
+
+    std::vector in_a_cluster(components.size());
+    std::vector is_in_core(components.size());
+
+    std::vector hits;
+    std::vector neighbors;
+    for (size_t p = 0; p < components.size(); p++) {
+        hits.clear();
+        neighbors.clear();
+        const geom::Geometry *gp = components[p];
+
+        tree.query(queryEnvelope(gp), [&hits](std::size_t hit) {
+            hits.push_back(hit);
+        });
+
+        if (hits.size() < m_minPoints) {
+            // We didn't find enough points do do anything even if they're all within eps.
+            continue;
+        }
+
+        std::unique_ptr prep;
+
+        for (size_t q : hits) {
+            if (neighbors.size() >= m_minPoints) {
+                // If we've already identified p as a core point, and it's already in the same cluster
+                // as q, then there's nothing to learn by computing the distance.
+                if (uf.same(p, q)) {
+                    continue;
+                }
+
+                // Similarly, if q is already identified as a border point of another cluster, there's
+                // no point figuring out what the distance is.
+                if (in_a_cluster[q] && !is_in_core[q]) {
+                    continue;
+                }
+            }
+
+            double dist;
+            if (p == q) {
+                dist = 0;
+            } else {
+                if (!prep) {
+                    prep = geom::prep::PreparedGeometryFactory::prepare(components[p]);
+                }
+
+                dist = prep->distance(components[q]);
+            }
+
+            if (dist <= m_eps) {
+                // If we haven't hit minPoints yet, we don't know if we can union p and q.
+                // Just set q aside for now.
+                if (neighbors.size() < m_minPoints) {
+                    neighbors.push_back(q);
+
+                    // If we just hit minPoints, we can now union all of the neighbor geometries
+                    // we've been saving.
+                    if (neighbors.size() == m_minPoints) {
+                        is_in_core[p] = true;
+                        in_a_cluster[p] = true;
+                        for (auto& n : neighbors) {
+                            unionIfAvailable(uf, p, n, is_in_core, in_a_cluster);
+                        }
+                    }
+                } else {
+                    // If we're above minPoints, no need to store our neighbors, just go ahead
+                    // and union them now.
+                    unionIfAvailable(uf, p, q, is_in_core, in_a_cluster);
+                }
+            }
+        }
+    }
+
+
+    std::vector includedInCluster;
+    includedInCluster.reserve(components.size());
+    for (size_t p = 0; p < components.size(); p++) {
+        if (in_a_cluster[p]) {
+            includedInCluster.push_back(p);
+        }
+    }
+
+    return uf.getClusters(includedInCluster);
+}
+
+
+}
+}
+}
diff --git a/Sources/geos/src/operation/cluster/GeometryFlattener.cpp b/Sources/geos/src/operation/cluster/GeometryFlattener.cpp
new file mode 100644
index 0000000..7bfd8ee
--- /dev/null
+++ b/Sources/geos/src/operation/cluster/GeometryFlattener.cpp
@@ -0,0 +1,56 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2022 ISciences LLC
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+
+namespace geos  {
+namespace operation {
+namespace cluster {
+
+std::unique_ptr
+GeometryFlattener::flatten(std::unique_ptr&& g) {
+    if (!g->isCollection()) {
+        return std::move(g);
+    }
+
+    if (g->isEmpty()) {
+        return std::move(g);
+    }
+
+    const geom::GeometryFactory* factory = g->getFactory();
+
+    std::vector> components;
+    flatten(std::move(g), components);
+
+    return factory->buildGeometry(std::move(components));
+}
+
+void
+GeometryFlattener::flatten(std::unique_ptr&& g, std::vector>& components)
+{
+    if (g->isCollection()) {
+        auto gc = static_cast(g.get());
+
+        for (auto& gi : gc->releaseGeometries()) {
+            flatten(std::move(gi), components);
+        }
+    } else {
+        components.push_back(std::move(g));
+    }
+}
+
+
+}
+}
+}
diff --git a/Sources/geos/src/operation/cluster/UnionFind.cpp b/Sources/geos/src/operation/cluster/UnionFind.cpp
new file mode 100644
index 0000000..85104a4
--- /dev/null
+++ b/Sources/geos/src/operation/cluster/UnionFind.cpp
@@ -0,0 +1,34 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2020-2021 Daniel Baston
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+
+namespace geos {
+namespace operation {
+namespace cluster {
+
+Clusters UnionFind::getClusters() {
+    std::vector elems(clusters.size());
+    std::iota(elems.begin(), elems.end(), 0);
+
+    return Clusters(*this, std::move(elems), clusters.size());
+}
+
+Clusters UnionFind::getClusters(std::vector elems) {
+    return Clusters(*this, std::move(elems), clusters.size());
+}
+
+}
+}
+}
diff --git a/Sources/geos/src/operation/distance/ConnectedElementLocationFilter.cpp b/Sources/geos/src/operation/distance/ConnectedElementLocationFilter.cpp
index 71feb28..b117627 100644
--- a/Sources/geos/src/operation/distance/ConnectedElementLocationFilter.cpp
+++ b/Sources/geos/src/operation/distance/ConnectedElementLocationFilter.cpp
@@ -35,7 +35,7 @@ namespace operation { // geos.operation
 namespace distance { // geos.operation.distance
 
 /*public*/
-std::vector>
+std::vector
 ConnectedElementLocationFilter::getLocations(const Geometry* geom)
 {
     ConnectedElementLocationFilter c;
@@ -51,7 +51,7 @@ ConnectedElementLocationFilter::filter_ro(const Geometry* geom)
             (typeid(*geom) == typeid(LineString)) ||
             (typeid(*geom) == typeid(LinearRing)) ||
             (typeid(*geom) == typeid(Polygon))) {
-        locations.emplace_back(new GeometryLocation(geom, 0, *(geom->getCoordinate())));
+        locations.emplace_back(geom, 0, *(geom->getCoordinate()));
     }
 }
 
@@ -64,7 +64,7 @@ ConnectedElementLocationFilter::filter_rw(Geometry* geom)
             (typeid(*geom) == typeid(LineString)) ||
             (typeid(*geom) == typeid(LinearRing)) ||
             (typeid(*geom) == typeid(Polygon))) {
-        locations.emplace_back(new GeometryLocation(geom, 0, *(geom->getCoordinate())));
+        locations.emplace_back(geom, 0, *(geom->getCoordinate()));
     }
 }
 
diff --git a/Sources/geos/src/operation/distance/ConnectedElementPointFilter.cpp b/Sources/geos/src/operation/distance/ConnectedElementPointFilter.cpp
index cdc4ea6..2ffd117 100644
--- a/Sources/geos/src/operation/distance/ConnectedElementPointFilter.cpp
+++ b/Sources/geos/src/operation/distance/ConnectedElementPointFilter.cpp
@@ -37,10 +37,10 @@ namespace distance { // geos.operation.distance
  * found inside the specified geometry. Thus, if the specified geometry is
  * not a GeometryCollection, an empty list will be returned.
  */
-std::vector*
+std::vector*
 ConnectedElementPointFilter::getCoordinates(const Geometry* geom)
 {
-    std::vector* points = new std::vector();
+    std::vector* points = new std::vector();
     ConnectedElementPointFilter c(points);
     geom->apply_ro(&c);
     return points;
diff --git a/Sources/geos/src/operation/distance/DistanceOp.cpp b/Sources/geos/src/operation/distance/DistanceOp.cpp
index d65cc58..18ae46c 100644
--- a/Sources/geos/src/operation/distance/DistanceOp.cpp
+++ b/Sources/geos/src/operation/distance/DistanceOp.cpp
@@ -25,7 +25,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -35,6 +34,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include 
 #include 
@@ -104,12 +104,19 @@ DistanceOp::distance()
 {
     using geos::util::IllegalArgumentException;
 
+    util::ensureNoCurvedComponents(geom[0]);
+    util::ensureNoCurvedComponents(geom[1]);
+
     if(geom[0] == nullptr || geom[1] == nullptr) {
         throw IllegalArgumentException("null geometries are not supported");
     }
     if(geom[0]->isEmpty() || geom[1]->isEmpty()) {
         return 0.0;
     }
+    if(geom[0]->getGeometryTypeId() == GEOS_POINT && geom[1]->getGeometryTypeId() == GEOS_POINT) {
+        return static_cast(geom[0])->getCoordinate()->distance(*static_cast(geom[1])->getCoordinate());
+    }
+
     computeMinDistance();
     return minDistance;
 }
@@ -124,26 +131,26 @@ DistanceOp::nearestPoints()
     auto& locs = minDistanceLocation;
 
     // Empty input geometries result in this behaviour
-    if(locs[0] == nullptr || locs[1] == nullptr) {
+    if(locs[0].getGeometryComponent() == nullptr || locs[1].getGeometryComponent() == nullptr) {
         // either both or none are set..
-        assert(locs[0] == nullptr && locs[1] == nullptr);
+        assert(locs[0].getGeometryComponent() == nullptr && locs[1].getGeometryComponent() == nullptr);
 
         return nullptr;
     }
 
-    std::unique_ptr> nearestPts(new std::vector(2));
-    (*nearestPts)[0] = locs[0]->getCoordinate();
-    (*nearestPts)[1] = locs[1]->getCoordinate();
+    auto nearestPts = detail::make_unique(2u);
+    nearestPts->setAt(locs[0].getCoordinate(), 0);
+    nearestPts->setAt(locs[1].getCoordinate(), 1);
 
-    return std::unique_ptr(new CoordinateArraySequence(nearestPts.release()));
+    return nearestPts;
 }
 
 void
-DistanceOp::updateMinDistance(std::array, 2> & locGeom, bool flip)
+DistanceOp::updateMinDistance(std::array & locGeom, bool flip)
 {
     // if not set then don't update
-    if(locGeom[0] == nullptr) {
-        assert(locGeom[1] == nullptr);
+    if(locGeom[0].getGeometryComponent() == nullptr) {
+        assert(locGeom[1].getGeometryComponent() == nullptr);
 #if GEOS_DEBUG
         std::cerr << "updateMinDistance called with loc[0] == null and loc[1] == null" << std::endl;
 #endif
@@ -206,15 +213,15 @@ DistanceOp::computeContainmentDistance()
     // Expected to fill minDistanceLocation items
     // if minDistance <= terminateDistance
 
-    std::array, 2> locPtPoly;
+    std::array locPtPoly;
     // test if either geometry has a vertex inside the other
     if(! polys1.empty()) {
         auto insideLocs0 = ConnectedElementLocationFilter::getLocations(geom[0]);
         computeInside(insideLocs0, polys1, locPtPoly);
 
         if(minDistance <= terminateDistance) {
-            assert(locPtPoly[0]);
-            assert(locPtPoly[1]);
+            assert(locPtPoly[0].getGeometryComponent());
+            assert(locPtPoly[1].getGeometryComponent());
 
             minDistanceLocation[0] = std::move(locPtPoly[0]);
             minDistanceLocation[1] = std::move(locPtPoly[1]);
@@ -236,8 +243,8 @@ DistanceOp::computeContainmentDistance()
         computeInside(insideLocs1, polys0, locPtPoly);
         if(minDistance <= terminateDistance) {
             // flip locations, since we are testing geom 1 VS geom 0
-            assert(locPtPoly[0]);
-            assert(locPtPoly[1]);
+            assert(locPtPoly[0].getGeometryComponent());
+            assert(locPtPoly[1].getGeometryComponent());
 
             minDistanceLocation[0] = std::move(locPtPoly[1]);
             minDistanceLocation[1] = std::move(locPtPoly[0]);
@@ -250,18 +257,18 @@ DistanceOp::computeContainmentDistance()
 
 /*private*/
 void
-DistanceOp::computeInside(std::vector> & locs,
+DistanceOp::computeInside(std::vector & locs,
                           const Polygon::ConstVect& polys,
-                          std::array, 2> & locPtPoly)
+                          std::array & locPtPoly)
 {
     for(auto& loc : locs) {
         for(const auto& poly : polys) {
-			const Coordinate& pt = loc->getCoordinate();
+            const auto& pt = loc.getCoordinate();
 
 			if (Location::EXTERIOR != ptLocator.locate(pt, static_cast(poly))) {
 				minDistance = 0.0;
 				locPtPoly[0] = std::move(loc);
-				locPtPoly[1].reset(new GeometryLocation(poly, pt));
+                locPtPoly[1] = GeometryLocation(poly, pt);
 				return;
 			}
         }
@@ -275,7 +282,7 @@ DistanceOp::computeFacetDistance()
     using geom::util::LinearComponentExtracter;
     using geom::util::PointExtracter;
 
-    std::array, 2> locGeom;
+    std::array locGeom;
 
     /*
      * Geometries are not wholly inside, so compute distance from lines
@@ -310,8 +317,8 @@ DistanceOp::computeFacetDistance()
     std::cerr << "PointExtracter found " << pts1.size() << " points in geometry 2" << std::endl;
 #endif
 
-    locGeom[0] = nullptr;
-    locGeom[1] = nullptr;
+    locGeom[0] = GeometryLocation();
+    locGeom[1] = GeometryLocation();
     computeMinDistanceLinesPoints(lines0, pts1, locGeom);
     updateMinDistance(locGeom, false);
     if(minDistance <= terminateDistance) {
@@ -328,8 +335,8 @@ DistanceOp::computeFacetDistance()
     std::cerr << "PointExtracter found " << pts0.size() << " points in geometry 1" << std::endl;
 #endif
 
-    locGeom[0] = nullptr;
-    locGeom[1] = nullptr;
+    locGeom[0] = GeometryLocation();
+    locGeom[1] = GeometryLocation();
     computeMinDistanceLinesPoints(lines1, pts0, locGeom);
     updateMinDistance(locGeom, true);
     if(minDistance <= terminateDistance) {
@@ -339,8 +346,8 @@ DistanceOp::computeFacetDistance()
         return;
     }
 
-    locGeom[0] = nullptr;
-    locGeom[1] = nullptr;
+    locGeom[0] = GeometryLocation();
+    locGeom[1] = GeometryLocation();
     computeMinDistancePoints(pts0, pts1, locGeom);
     updateMinDistance(locGeom, false);
 
@@ -354,10 +361,14 @@ void
 DistanceOp::computeMinDistanceLines(
     const LineString::ConstVect& lines0,
     const LineString::ConstVect& lines1,
-    std::array, 2> & locGeom)
+    std::array & locGeom)
 {
     for(const LineString* line0 : lines0) {
         for(const LineString* line1 : lines1) {
+
+            if (line0->isEmpty() || line1->isEmpty())
+                continue;
+
             computeMinDistance(line0, line1, locGeom);
             if(minDistance <= terminateDistance) {
                 return;
@@ -371,16 +382,14 @@ void
 DistanceOp::computeMinDistancePoints(
     const Point::ConstVect& points0,
     const Point::ConstVect& points1,
-    std::array, 2> & locGeom)
+    std::array & locGeom)
 {
     for(const Point* pt0 : points0) {
-        if (pt0->isEmpty()) {
-            continue;
-        }
         for(const Point* pt1 : points1) {
-            if (pt1->isEmpty()) {
+
+            if (pt1->isEmpty() || pt0->isEmpty())
                 continue;
-            }
+
             double dist = pt0->getCoordinate()->distance(*(pt1->getCoordinate()));
 
 #if GEOS_DEBUG
@@ -394,8 +403,8 @@ DistanceOp::computeMinDistancePoints(
             if(dist < minDistance) {
                 minDistance = dist;
                 // this is wrong - need to determine closest points on both segments!!!
-                locGeom[0].reset(new GeometryLocation(pt0, 0, *(pt0->getCoordinate())));
-                locGeom[1].reset(new GeometryLocation(pt1, 0, *(pt1->getCoordinate())));
+                locGeom[0] = GeometryLocation(pt0, 0, *(pt0->getCoordinate()));
+                locGeom[1] = GeometryLocation(pt1, 0, *(pt1->getCoordinate()));
             }
 
             if(minDistance <= terminateDistance) {
@@ -410,10 +419,14 @@ void
 DistanceOp::computeMinDistanceLinesPoints(
     const LineString::ConstVect& lines,
     const Point::ConstVect& points,
-    std::array, 2> & locGeom)
+    std::array & locGeom)
 {
     for(const LineString* line : lines) {
         for(const Point* pt : points) {
+
+            if (line->isEmpty() || pt->isEmpty())
+                continue;
+
             computeMinDistance(line, pt, locGeom);
             if(minDistance <= terminateDistance) {
                 return;
@@ -427,7 +440,7 @@ void
 DistanceOp::computeMinDistance(
     const LineString* line0,
     const LineString* line1,
-    std::array, 2> & locGeom)
+    std::array & locGeom)
 {
     using geos::algorithm::Distance;
 
@@ -444,8 +457,8 @@ DistanceOp::computeMinDistance(
 
     // brute force approach!
     for(std::size_t i = 0; i < npts0 - 1; ++i) {
-        const Coordinate& p00 = coord0->getAt(i);
-        const Coordinate& p01 = coord0->getAt(i+1);
+        const CoordinateXY& p00 = coord0->getAt(i);
+        const CoordinateXY& p01 = coord0->getAt(i+1);
 
         Envelope segEnv0(p00, p01);
 
@@ -454,8 +467,8 @@ DistanceOp::computeMinDistance(
         }
 
         for(std::size_t j = 0; j < npts1 - 1; ++j) {
-            const Coordinate& p10 = coord1->getAt(j);
-            const Coordinate& p11 = coord1->getAt(j+1);
+            const CoordinateXY& p10 = coord1->getAt(j);
+            const CoordinateXY& p11 = coord1->getAt(j+1);
 
             Envelope segEnv1(p10, p11);
 
@@ -470,12 +483,12 @@ DistanceOp::computeMinDistance(
                 // TODO avoid copy from constructing segs, maybe
                 // by making a static closestPoints that takes four
                 // coordinate references
-                LineSegment seg0(p00, p01);
-                LineSegment seg1(p10, p11);
+                LineSegment seg0{Coordinate(p00), Coordinate(p01)};
+                LineSegment seg1{Coordinate(p10), Coordinate(p11)};
                 auto closestPt = seg0.closestPoints(seg1);
 
-                locGeom[0].reset(new GeometryLocation(line0, i, closestPt[0]));
-                locGeom[1].reset(new GeometryLocation(line1, j, closestPt[1]));
+                locGeom[0] = GeometryLocation(line0, i, closestPt[0]);
+                locGeom[1] = GeometryLocation(line1, j, closestPt[1]);
             }
             if(minDistance <= terminateDistance) {
                 return;
@@ -488,7 +501,7 @@ DistanceOp::computeMinDistance(
 void
 DistanceOp::computeMinDistance(const LineString* line,
                                const Point* pt,
-                               std::array, 2> & locGeom)
+                               std::array & locGeom)
 {
     using geos::algorithm::Distance;
 
@@ -498,24 +511,24 @@ DistanceOp::computeMinDistance(const LineString* line,
         return;
     }
     const CoordinateSequence* coord0 = line->getCoordinatesRO();
-    const Coordinate* coord = pt->getCoordinate();
+    const CoordinateXY* coord = pt->getCoordinate();
 
     // brute force approach!
     std::size_t npts0 = coord0->getSize();
     for(std::size_t i = 0; i < npts0 - 1; ++i) {
-        double dist = Distance::pointToSegment(*coord, coord0->getAt(i), coord0->getAt(i + 1));
+        double dist = Distance::pointToSegment(*coord, coord0->getAt(i), coord0->getAt(i + 1));
         if(dist < minDistance) {
             minDistance = dist;
 
             // TODO avoid copy from constructing segs, maybe
             // by making a static closestPoints that takes three
             // coordinate references
-            LineSegment seg(coord0->getAt(i), coord0->getAt(i + 1));
+            LineSegment seg{Coordinate(coord0->getAt(i)), Coordinate(coord0->getAt(i + 1))};
             Coordinate segClosestPoint;
             seg.closestPoint(*coord, segClosestPoint);
 
-            locGeom[0].reset(new GeometryLocation(line, i, segClosestPoint));
-            locGeom[1].reset(new GeometryLocation(pt, 0, *coord));
+            locGeom[0] = GeometryLocation(line, i, segClosestPoint);
+            locGeom[1] = GeometryLocation(pt, 0, *coord);
         }
         if(minDistance <= terminateDistance) {
             return;
diff --git a/Sources/geos/src/operation/distance/GeometryLocation.cpp b/Sources/geos/src/operation/distance/GeometryLocation.cpp
index ec4b20d..6c46b5e 100644
--- a/Sources/geos/src/operation/distance/GeometryLocation.cpp
+++ b/Sources/geos/src/operation/distance/GeometryLocation.cpp
@@ -21,7 +21,7 @@
 #include 
 
 using geos::geom::Geometry;
-using geos::geom::Coordinate;
+using geos::geom::CoordinateXY;
 
 namespace geos {
 namespace operation { // geos.operation
@@ -31,7 +31,7 @@ namespace distance { // geos.operation.distance
  * Constructs a GeometryLocation specifying a point on a geometry, as well as the
  * segment that the point is on (or INSIDE_AREA if the point is not on a segment).
  */
-GeometryLocation::GeometryLocation(const Geometry* newComponent, std::size_t newSegIndex, const Coordinate& newPt)
+GeometryLocation::GeometryLocation(const Geometry* newComponent, std::size_t newSegIndex, const CoordinateXY& newPt)
 {
     component = newComponent;
     segIndex = newSegIndex;
@@ -42,7 +42,7 @@ GeometryLocation::GeometryLocation(const Geometry* newComponent, std::size_t new
 /**
  * Constructs a GeometryLocation specifying a point inside an area geometry.
  */
-GeometryLocation::GeometryLocation(const Geometry* newComponent, const Coordinate& newPt)
+GeometryLocation::GeometryLocation(const Geometry* newComponent, const CoordinateXY& newPt)
 {
     component = newComponent;
     inside_area = true;
@@ -72,7 +72,7 @@ GeometryLocation::getSegmentIndex()
 /**
  * Returns the location.
  */
-Coordinate&
+CoordinateXY&
 GeometryLocation::getCoordinate()
 {
     return pt;
diff --git a/Sources/geos/src/operation/distance/IndexedFacetDistance.cpp b/Sources/geos/src/operation/distance/IndexedFacetDistance.cpp
index 5a4c81f..a8fe234 100644
--- a/Sources/geos/src/operation/distance/IndexedFacetDistance.cpp
+++ b/Sources/geos/src/operation/distance/IndexedFacetDistance.cpp
@@ -36,7 +36,7 @@ IndexedFacetDistance::distance(const Geometry* g1, const Geometry* g2)
 }
 
 /*public static*/
-std::vector
+std::unique_ptr
 IndexedFacetDistance::nearestPoints(const geom::Geometry* g1, const geom::Geometry* g2)
 {
     IndexedFacetDistance dist(g1);
@@ -46,12 +46,6 @@ IndexedFacetDistance::nearestPoints(const geom::Geometry* g1, const geom::Geomet
 double
 IndexedFacetDistance::distance(const Geometry* g) const
 {
-    struct FacetDistance {
-        double operator()(const FacetSequence* a, const FacetSequence* b) {
-            return a->distance(*b);
-        }
-    };
-
     auto tree2 = FacetSequenceTreeBuilder::build(g);
     auto nearest = cachedTree->nearestNeighbour(*tree2);
 
@@ -62,15 +56,32 @@ IndexedFacetDistance::distance(const Geometry* g) const
     return nearest.first->distance(*nearest.second);
 }
 
+bool
+IndexedFacetDistance::isWithinDistance(const Geometry* g, double maxDistance) const
+{
+    // short-circuit check
+    double envDist = baseGeometry.getEnvelopeInternal()->distance(*g->getEnvelopeInternal());
+    if (envDist > maxDistance) {
+        return false;
+    }
+
+    //-- heuristic: for atomic indexed geom, test distance to envelope of test geom
+    if (baseGeometry.getNumGeometries() == 1
+        && ! g->getEnvelopeInternal()->contains(baseGeometry.getEnvelopeInternal()))
+    {
+        auto env2 = g->getEnvelope();
+        if (distance(env2.get()) > maxDistance) {
+            return false;
+        }
+    }
+
+    auto tree2 = FacetSequenceTreeBuilder::build(g);
+    return cachedTree->isWithinDistance(*tree2, maxDistance);
+}
+
 std::vector
 IndexedFacetDistance::nearestLocations(const geom::Geometry* g) const
 {
-    struct FacetDistance {
-        double operator()(const FacetSequence* a, const FacetSequence* b) const
-        {
-            return a->distance(*b);
-        }
-    };
 
     auto tree2 = FacetSequenceTreeBuilder::build(g);
     auto nearest = cachedTree->nearestNeighbour(*tree2);
@@ -82,13 +93,13 @@ IndexedFacetDistance::nearestLocations(const geom::Geometry* g) const
     return nearest.first->nearestLocations(*nearest.second);
 }
 
-std::vector
+std::unique_ptr
 IndexedFacetDistance::nearestPoints(const geom::Geometry* g) const
 {
     std::vector minDistanceLocation = nearestLocations(g);
-    std::vector nearestPts;
-    nearestPts.push_back(minDistanceLocation[0].getCoordinate());
-    nearestPts.push_back(minDistanceLocation[1].getCoordinate());
+    auto nearestPts = detail::make_unique(2u);
+    nearestPts->setAt(minDistanceLocation[0].getCoordinate(), 0);
+    nearestPts->setAt(minDistanceLocation[1].getCoordinate(), 1);
     return nearestPts;
 }
 
diff --git a/Sources/geos/src/operation/intersection/Rectangle.cpp b/Sources/geos/src/operation/intersection/Rectangle.cpp
index 9ba1ac5..6f3d343 100644
--- a/Sources/geos/src/operation/intersection/Rectangle.cpp
+++ b/Sources/geos/src/operation/intersection/Rectangle.cpp
@@ -16,9 +16,9 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
+#include 
 
 namespace geos {
 namespace operation { // geos::operation
@@ -39,24 +39,22 @@ Rectangle::Rectangle(double x1, double y1, double x2, double y2)
     }
 }
 
-geom::Polygon*
+std::unique_ptr
 Rectangle::toPolygon(const geom::GeometryFactory& f) const
 {
-    geom::LinearRing* ls = toLinearRing(f);
-    return f.createPolygon(ls, nullptr);
+    return f.createPolygon(toLinearRing(f));
 }
 
-geom::LinearRing*
+std::unique_ptr
 Rectangle::toLinearRing(const geom::GeometryFactory& f) const
 {
-    const geom::CoordinateSequenceFactory* csf = f.getCoordinateSequenceFactory();
-    auto seq = csf->create(5, 2);
+    auto seq = detail::make_unique(5u, false, false, false);
     seq->setAt(geom::Coordinate(xMin, yMin), 0);
     seq->setAt(geom::Coordinate(xMin, yMax), 1);
     seq->setAt(geom::Coordinate(xMax, yMax), 2);
     seq->setAt(geom::Coordinate(xMax, yMin), 3);
     seq->setAt(seq->getAt(0), 4); // close
-    return f.createLinearRing(seq.release());
+    return f.createLinearRing(std::move(seq));
 }
 
 } // namespace geos::operation::intersection
diff --git a/Sources/geos/src/operation/intersection/RectangleIntersection.cpp b/Sources/geos/src/operation/intersection/RectangleIntersection.cpp
index bad0391..e3f7750 100644
--- a/Sources/geos/src/operation/intersection/RectangleIntersection.cpp
+++ b/Sources/geos/src/operation/intersection/RectangleIntersection.cpp
@@ -17,9 +17,9 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -29,11 +29,13 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
 using geos::operation::intersection::Rectangle;
 using geos::operation::intersection::RectangleIntersectionBuilder;
+using geos::operation::overlayng::ElevationModel;
 using namespace geos::geom;
 using namespace geos::algorithm;
 namespace geos {
@@ -62,15 +64,18 @@ different(double x1, double y1, double x2, double y2)
 
 inline
 void
-clip_one_edge(double& x1, double& y1, double x2, double y2, double limit)
+clip_one_edge(double& x1, double& y1, double& z1, double x2, double y2, double z2, double limit)
 {
     if(x2 == limit) {
         y1 = y2;
         x1 = x2;
+        z1 = z2;
     }
 
     if(x1 != x2) {
-        y1 += (y2 - y1) * (limit - x1) / (x2 - x1);
+        double fraction = (limit - x1) / (x2 - x1);
+        y1 += (y2 - y1) * fraction;
+        z1 += (z2 - z1) * fraction;
         x1 = limit;
     }
 }
@@ -86,22 +91,22 @@ clip_one_edge(double& x1, double& y1, double x2, double y2, double limit)
  */
 
 void
-clip_to_edges(double& x1, double& y1,
-              double x2, double y2,
+clip_to_edges(double& x1, double& y1, double& z1,
+              double x2, double y2, double z2,
               const Rectangle& rect)
 {
     if(x1 < rect.xmin()) {
-        clip_one_edge(x1, y1, x2, y2, rect.xmin());
+        clip_one_edge(x1, y1, z1, x2, y2, z2, rect.xmin());
     }
     else if(x1 > rect.xmax()) {
-        clip_one_edge(x1, y1, x2, y2, rect.xmax());
+        clip_one_edge(x1, y1, z1, x2, y2, z2, rect.xmax());
     }
 
     if(y1 < rect.ymin()) {
-        clip_one_edge(y1, x1, y2, x2, rect.ymin());
+        clip_one_edge(y1, x1, z1, y2, x2, z2, rect.ymin());
     }
     else if(y1 > rect.ymax()) {
-        clip_one_edge(y1, x1, y2, x2, rect.ymax());
+        clip_one_edge(y1, x1, z1, y2, x2, z2, rect.ymax());
     }
 }
 
@@ -117,7 +122,7 @@ RectangleIntersection::clip_point(const geom::Point* g,
                                   RectangleIntersectionBuilder& parts,
                                   const Rectangle& rect)
 {
-    if(g == nullptr) {
+    if(g == nullptr || g->isEmpty()) {
         return;
     }
 
@@ -153,6 +158,7 @@ RectangleIntersection::clip_linestring_parts(const geom::LineString* gi,
 
     double x0 = 0;
     double y0 = 0;
+    double z0 = 0;
     bool add_start = false;
 
     // Start iterating
@@ -164,6 +170,7 @@ RectangleIntersection::clip_linestring_parts(const geom::LineString* gi,
 
         double x = cs[i].x;
         double y = cs[i].y;
+        double z = cs[i].z;
         Rectangle::Position pos = rect.position(x, y);
 
         if(pos == Rectangle::Outside) {
@@ -199,12 +206,14 @@ RectangleIntersection::clip_linestring_parts(const geom::LineString* gi,
             // Establish new position
             x = cs[i].x;
             y = cs[i].y;
+            z = cs[i].z;
             pos = rect.position(x, y);
 
             // Handle all possible cases
             x0 = cs[i - 1].x;
             y0 = cs[i - 1].y;
-            clip_to_edges(x0, y0, x, y, rect);
+            z0 = cs[i - 1].z;
+            clip_to_edges(x0, y0, z0, x, y, z, rect);
 
             if(pos == Rectangle::Inside) {
                 add_start = true;	// x0,y0 must have clipped the rectangle
@@ -218,7 +227,7 @@ RectangleIntersection::clip_linestring_parts(const geom::LineString* gi,
                 // which will then enter the Outside section.
 
                 // Clip the other end too
-                clip_to_edges(x, y, x0, y0, rect);
+                clip_to_edges(x, y, z, x0, y0, z0, rect);
 
                 Rectangle::Position prev_pos = rect.position(x0, y0);
                 pos = rect.position(x, y);
@@ -228,12 +237,11 @@ RectangleIntersection::clip_linestring_parts(const geom::LineString* gi,
                         Rectangle::onEdge(pos) &&
                         !Rectangle::onSameEdge(prev_pos, pos)	// discard if travels along edge
                   ) {
-                    std::vector coords(2);
-                    coords[0] = Coordinate(x0, y0);
-                    coords[1] = Coordinate(x, y);
-                    auto seq = _csf->create(std::move(coords));
-                    geom::LineString* line = _gf->createLineString(seq.release());
-                    parts.add(line);
+                    auto coords = detail::make_unique(2u);
+                    coords->setAt(Coordinate(x0, y0, z0), 0);
+                    coords->setAt(Coordinate(x, y, z), 1);
+                    auto line = _gf->createLineString(std::move(coords));
+                    parts.add(line.release());
                 }
 
                 // Continue main loop outside the rect
@@ -268,6 +276,7 @@ RectangleIntersection::clip_linestring_parts(const geom::LineString* gi,
             while(!go_outside && ++i < n) {
                 x = cs[i].x;
                 y = cs[i].y;
+                z = cs[i].z;
 
                 Rectangle::Position prev_pos = pos;
                 pos = rect.position(x, y);
@@ -279,7 +288,7 @@ RectangleIntersection::clip_linestring_parts(const geom::LineString* gi,
                     go_outside = true;
 
                     // Clip the outside point to edges
-                    clip_to_edges(x, y, cs[i - 1].x, cs[i - 1].y, rect);
+                    clip_to_edges(x, y, z, cs[i - 1].x, cs[i - 1].y, cs[i - 1].z, rect);
                     pos = rect.position(x, y);
 
                     // Does the line exit through the inside of the box?
@@ -290,22 +299,21 @@ RectangleIntersection::clip_linestring_parts(const geom::LineString* gi,
                     // Output a LineString if it at least one segment long
 
                     if(start_index < i - 1 || add_start || through_box) {
-                        std::vector coords;
+                        auto coords = detail::make_unique();
                         if(add_start) {
-                            coords.emplace_back(x0, y0);
+                            coords->add(Coordinate(x0, y0, z0));
                             add_start = false;
                         }
                         //line->addSubLineString(&g, start_index, i-1);
-                        coords.insert(coords.end(), cs.begin() + static_cast(start_index), cs.begin() +
-                                                                                                 static_cast(i));
+                        coords->add(cs.begin() + static_cast(start_index),
+                                    cs.begin() + static_cast(i));
 
                         if(through_box) {
-                            coords.emplace_back(x, y);
+                            coords->add(Coordinate(x, y, z));
                         }
 
-                        auto seq = _csf->create(std::move(coords));
-                        geom::LineString* line = _gf->createLineString(seq.release());
-                        parts.add(line);
+                        auto line = _gf->createLineString(std::move(coords));
+                        parts.add(line.release());
                     }
                     // And continue main loop on the outside
                 }
@@ -314,20 +322,19 @@ RectangleIntersection::clip_linestring_parts(const geom::LineString* gi,
                     if(Rectangle::onSameEdge(prev_pos, pos)) {
                         // Nothing to output if we haven't been elsewhere
                         if(start_index < i - 1 || add_start) {
-                            std::vector coords;
+                        auto coords = detail::make_unique();
                             //geom::LineString * line = new geom::LineString();
                             if(add_start) {
                                 //line->addPoint(x0,y0);
-                                coords.emplace_back(x0, y0);
+                                coords->add(Coordinate(x0, y0, z0));
                                 add_start = false;
                             }
                             //line->addSubLineString(&g, start_index, i-1);
-                            coords.insert(coords.end(), cs.begin() + static_cast(start_index), cs.begin() +
-                                                                                                     static_cast(i));
+                            coords->add(cs.begin() + static_cast(start_index),
+                                        cs.begin() + static_cast(i));
 
-                            auto seq = _csf->create(std::move(coords));
-                            geom::LineString* line = _gf->createLineString(seq.release());
-                            parts.add(line);
+                            auto line = _gf->createLineString(std::move(coords));
+                            parts.add(line.release());
                         }
                         start_index = i;
                     }
@@ -348,20 +355,19 @@ RectangleIntersection::clip_linestring_parts(const geom::LineString* gi,
 
             if(!go_outside &&						// meaning data ended
                     (start_index < i - 1 || add_start)) {	// meaning something has to be generated
-                std::vector coords;
+                auto coords = detail::make_unique();
                 //geom::LineString * line = new geom::LineString();
                 if(add_start) {
                     //line->addPoint(x0,y0);
-                    coords.emplace_back(x0, y0);
+                    coords->add(Coordinate(x0, y0, z0));
                     add_start = false;
                 }
                 //line->addSubLineString(&g, start_index, i-1);
-                coords.insert(coords.end(), cs.begin() + static_cast(start_index), cs.begin() +
-                                                                                         static_cast(i));
+                coords->add(cs.begin() + static_cast(start_index),
+                            cs.begin() + static_cast(i));
 
-                auto seq = _csf->create(std::move(coords));
-                geom::LineString* line = _gf->createLineString(seq.release());
-                parts.add(line);
+                auto line = _gf->createLineString(std::move(coords));
+                parts.add(line.release());
             }
 
         }
@@ -424,9 +430,9 @@ RectangleIntersection::clip_polygon_to_linestrings(const geom::Polygon* g,
     for(std::size_t i = 0, n = g->getNumInteriorRing(); i < n; ++i) {
         if(clip_linestring_parts(g->getInteriorRingN(i), parts, rect)) {
             // clones
-            LinearRing* hole = new LinearRing(*(g->getInteriorRingN(i)));
+            auto hole =g->getInteriorRingN(i)->clone();
             // becomes exterior
-            Polygon* poly = _gf->createPolygon(hole, nullptr);
+            Polygon* poly = _gf->createPolygon(std::move(hole)).release();
             toParts.add(poly);
         }
         else if(!parts.empty()) {
@@ -496,8 +502,8 @@ RectangleIntersection::clip_polygon_to_polygons(const geom::Polygon* g,
         const LinearRing* hole = g->getInteriorRingN(i);
         if(clip_linestring_parts(hole, holeparts, rect)) {
             // becomes exterior
-            LinearRing* cloned = new LinearRing(*hole);
-            Polygon* poly = _gf->createPolygon(cloned, nullptr);
+            auto cloned = hole->clone();
+            Polygon* poly = _gf->createPolygon(std::move(cloned)).release();
             parts.add(poly);
         }
         else {
@@ -525,7 +531,6 @@ RectangleIntersection::clip_polygon_to_polygons(const geom::Polygon* g,
 
     parts.reconnectPolygons(rect);
     parts.release(toParts);
-
 }
 
 /**
@@ -538,6 +543,10 @@ RectangleIntersection::clip_polygon(const geom::Polygon* g,
                                     const Rectangle& rect,
                                     bool keep_polygons)
 {
+    if(g == nullptr || g->isEmpty()) {
+        return;
+    }
+
     if(keep_polygons) {
         clip_polygon_to_polygons(g, parts, rect);
     }
@@ -682,7 +691,14 @@ std::unique_ptr
 RectangleIntersection::clip(const geom::Geometry& g, const Rectangle& rect)
 {
     RectangleIntersection ri(g, rect);
-    return ri.clip();
+    std::unique_ptr result = ri.clip();
+
+    if (g.hasZ()) {
+        std::unique_ptr elevModel = ElevationModel::create(g);
+        elevModel->populateZ(*result);
+    }
+
+    return result;
 }
 
 std::unique_ptr
@@ -700,7 +716,6 @@ RectangleIntersection::RectangleIntersection(const geom::Geometry& geom, const R
     : _geom(geom), _rect(rect),
       _gf(geom.getFactory())
 {
-    _csf = _gf->getCoordinateSequenceFactory();
 }
 
 } // namespace geos::operation::intersection
diff --git a/Sources/geos/src/operation/intersection/RectangleIntersectionBuilder.cpp b/Sources/geos/src/operation/intersection/RectangleIntersectionBuilder.cpp
index b3e8eb4..ca443a5 100644
--- a/Sources/geos/src/operation/intersection/RectangleIntersectionBuilder.cpp
+++ b/Sources/geos/src/operation/intersection/RectangleIntersectionBuilder.cpp
@@ -15,7 +15,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -24,6 +23,8 @@
 #include 
 #include 
 #include 
+#include 
+#include 
 
 #include  // for fabs()
 
@@ -74,16 +75,16 @@ RectangleIntersectionBuilder::reconnect()
 
     // Merge the two linestrings
     auto ncs = valid::RepeatedPointRemover::removeRepeatedPoints(&cs2);
-    ncs->add(&cs1, false, true);
+    ncs->add(cs1, false, true);
 
     delete line1;
     delete line2;
 
-    LineString* nline = _gf.createLineString(ncs.release());
+    auto nline = _gf.createLineString(std::move(ncs));
     lines.pop_front();
     lines.pop_back();
 
-    lines.push_front(nline);
+    lines.push_front(nline.release());
 }
 
 
@@ -168,27 +169,26 @@ RectangleIntersectionBuilder::build()
         return std::unique_ptr(_gf.createGeometryCollection());
     }
 
-    std::vector* geoms = new std::vector;
-    geoms->reserve(n);
+    std::vector> geoms;
+    geoms.reserve(n);
 
     for(std::list::iterator i = polygons.begin(), e = polygons.end(); i != e; ++i) {
-        geoms->push_back(*i);
+        geoms.emplace_back(*i);
     }
     polygons.clear();
 
     for(std::list::iterator i = lines.begin(), e = lines.end(); i != e; ++i) {
-        geoms->push_back(*i);
+        geoms.emplace_back(*i);
     }
     lines.clear();
 
     for(std::list::iterator i = points.begin(), e = points.end(); i != e; ++i) {
-        geoms->push_back(*i);
+        geoms.emplace_back(*i);
     }
     points.clear();
 
-    return std::unique_ptr(
-               (*geoms)[0]->getFactory()->buildGeometry(geoms)
-           );
+    const auto* factory = geoms[0]->getFactory();
+    return factory->buildGeometry(std::move(geoms));
 }
 
 /**
@@ -250,7 +250,7 @@ distance(const Rectangle& rect,
 
 double
 distance(const Rectangle& rect,
-         const std::vector& ring,
+         const CoordinateSequence& ring,
          const geom::LineString* line)
 {
     auto nr = ring.size();
@@ -264,7 +264,7 @@ distance(const Rectangle& rect,
 
 double
 distance(const Rectangle& rect,
-         const std::vector& ring)
+         const CoordinateSequence& ring)
 {
     auto nr = ring.size();
     const Coordinate& c1 = ring[nr - 1]; // TODO: ring.back() ?
@@ -276,7 +276,7 @@ distance(const Rectangle& rect,
  * \brief Reverse given segment in a coordinate vector
  */
 void
-reverse_points(std::vector& v, std::size_t start, std::size_t end)
+reverse_points(CoordinateSequence& v, std::size_t start, std::size_t end)
 {
     geom::Coordinate p1;
     geom::Coordinate p2;
@@ -294,9 +294,9 @@ reverse_points(std::vector& v, std::size_t start, std::size_t end)
  * \brief Normalize a ring into lexicographic order
  */
 void
-normalize_ring(std::vector& ring)
+normalize_ring(CoordinateSequence& ring)
 {
-    if(ring.empty()) {
+    if(ring.isEmpty()) {
         return;
     }
 
@@ -338,7 +338,7 @@ normalize_ring(std::vector& ring)
 void
 RectangleIntersectionBuilder::close_boundary(
     const Rectangle& rect,
-    std::vector* ring,
+    CoordinateSequence* ring,
     double x1, double y1,
     double x2, double y2)
 {
@@ -356,7 +356,7 @@ RectangleIntersectionBuilder::close_boundary(
                     (y1 == rect.ymin() && x2 <= x1))
           ) {
             if(x1 != x2 || y1 != y2) {	// the polygon may have started at a corner
-                ring->push_back(Coordinate(x2, y2));
+                ring->add(Coordinate(x2, y2));
             }
             break;
         }
@@ -375,14 +375,14 @@ RectangleIntersectionBuilder::close_boundary(
             y1 = rect.ymin();
         }
 
-        ring->push_back(Coordinate(x1, y1));
+        ring->add(Coordinate(x1, y1));
 
     }
 }
 
 void
 RectangleIntersectionBuilder::close_ring(const Rectangle& rect,
-        std::vector* ring)
+        CoordinateSequence* ring)
 {
     auto nr = ring->size();
     Coordinate& c2 = (*ring)[0];
@@ -402,33 +402,31 @@ RectangleIntersectionBuilder::reconnectPolygons(const Rectangle& rect)
 {
     // Build the exterior rings first
 
-    typedef std::vector< geom::LinearRing*> LinearRingVect;
-    typedef std::pair< geom::LinearRing*, LinearRingVect* > ShellAndHoles;
+    typedef std::vector< std::unique_ptr> LinearRingVect;
+    typedef std::pair< std::unique_ptr, LinearRingVect > ShellAndHoles;
     typedef std::list< ShellAndHoles > ShellAndHolesList;
 
     ShellAndHolesList exterior;
 
-    const CoordinateSequenceFactory& _csf = *_gf.getCoordinateSequenceFactory();
-
     // If there are no lines, the rectangle must have been
     // inside the exterior ring.
 
     if(lines.empty()) {
-        geom::LinearRing* ring = rect.toLinearRing(_gf);
-        exterior.push_back(make_pair(ring, new LinearRingVect()));
+        auto ring = rect.toLinearRing(_gf);
+        exterior.push_back(make_pair(std::move(ring), LinearRingVect()));
     }
     else {
         // Reconnect all lines into one or more linearrings
         // using box boundaries if necessary
 
-        std::vector* ring = nullptr;
+        std::unique_ptr ring;
 
         while(!lines.empty() || ring != nullptr) {
             if(ring == nullptr) {
-                ring = new std::vector();
+                ring = detail::make_unique();
                 LineString* line = lines.front();
                 lines.pop_front();
-                line->getCoordinatesRO()->toVector(*ring);
+                ring->add(*line->getCoordinatesRO());
                 delete line;
             }
 
@@ -449,36 +447,35 @@ RectangleIntersectionBuilder::reconnectPolygons(const Rectangle& rect)
 
             // If own end point is closest, close the ring and continue
             if(best_distance < 0 || own_distance < best_distance) {
-                close_ring(rect, ring);
+                close_ring(rect, ring.get());
                 normalize_ring(*ring);
-                auto shell_cs = _csf.create(ring);
                 // This apes the behaviour that existed back when
                 // it was impossible to create a LinearRing with < 4
                 // points. In order to maintain compatibility
                 // with prior behaviour for rectangle intersection
                 // we are pulling that check back here.
-                if (shell_cs->size() < 4) {
+                if (ring->size() < 4) {
                     std::ostringstream os;
                     os << "Invalid number of points in LinearRing found "
-                       << shell_cs->size() << " - must be 0 or >= 4";
+                       << ring->size() << " - must be 0 or >= 4";
                     throw util::IllegalArgumentException(os.str());
                 }
-                geom::LinearRing* shell = _gf.createLinearRing(shell_cs.release());
-                exterior.push_back(make_pair(shell, new LinearRingVect()));
+                auto shell = _gf.createLinearRing(std::move(ring));
+                exterior.push_back(make_pair(std::move(shell), LinearRingVect()));
                 ring = nullptr;
             }
             else {
                 LineString* line = *best_pos;
                 auto nr = ring->size();
                 const CoordinateSequence& cs = *line->getCoordinatesRO();
-                close_boundary(rect, ring,
+                close_boundary(rect, ring.get(),
                                (*ring)[nr - 1].x,
                                (*ring)[nr - 1].y,
                                cs[0].x,
                                cs[0].y);
                 // above function adds the 1st point
                 for(std::size_t i = 1; i < cs.size(); ++i) {
-                    ring->push_back(cs[i]);
+                    ring->add(cs[i]);
                 }
                 //ring->addSubLineString(line,1);
                 delete line;
@@ -494,7 +491,7 @@ RectangleIntersectionBuilder::reconnectPolygons(const Rectangle& rect)
         const geom::LinearRing* hole = poly->getExteriorRing();
 
         if(exterior.size() == 1) {
-            exterior.front().second->push_back(new LinearRing(*hole));
+            exterior.front().second.push_back(hole->clone());
         }
         else {
             using geos::algorithm::PointLocation;
@@ -504,7 +501,7 @@ RectangleIntersectionBuilder::reconnectPolygons(const Rectangle& rect)
                 const CoordinateSequence* shell_cs = p.first->getCoordinatesRO();
                 if(PointLocation::isInRing(c, shell_cs)) {
                     // add hole to shell
-                    p.second->push_back(new LinearRing(*hole));
+                    p.second.push_back(hole->clone());
                     break;
                 }
             }
@@ -518,8 +515,8 @@ RectangleIntersectionBuilder::reconnectPolygons(const Rectangle& rect)
     std::list new_polygons;
     for(ShellAndHolesList::iterator i = exterior.begin(), e = exterior.end(); i != e; ++i) {
         ShellAndHoles& p = *i;
-        geom::Polygon* poly = _gf.createPolygon(p.first, p.second);
-        new_polygons.push_back(poly);
+        auto poly = _gf.createPolygon(std::move(p.first), std::move(p.second));
+        new_polygons.push_back(poly.release());
     }
 
     clear();
diff --git a/Sources/geos/src/operation/linemerge/EdgeString.cpp b/Sources/geos/src/operation/linemerge/EdgeString.cpp
index 67b359f..7c10a2a 100644
--- a/Sources/geos/src/operation/linemerge/EdgeString.cpp
+++ b/Sources/geos/src/operation/linemerge/EdgeString.cpp
@@ -22,9 +22,7 @@
 #include 
 #include 
 #include 
-#include 
 #include 
-#include 
 #include 
 #include 
 
@@ -57,12 +55,12 @@ EdgeString::add(LineMergeDirectedEdge* directedEdge)
     directedEdges.push_back(directedEdge);
 }
 
-CoordinateSequence*
-EdgeString::getCoordinates()
+std::unique_ptr
+EdgeString::getCoordinates() const
 {
     int forwardDirectedEdges = 0;
     int reverseDirectedEdges = 0;
-    auto coordinates = detail::make_unique();
+    auto coordinates = detail::make_unique();
     for(std::size_t i = 0, e = directedEdges.size(); i < e; ++i) {
         LineMergeDirectedEdge* directedEdge = directedEdges[i];
         if(directedEdge->getEdgeDirection()) {
@@ -74,21 +72,21 @@ EdgeString::getCoordinates()
 
         LineMergeEdge* lme = detail::down_cast(directedEdge->getEdge());
 
-        coordinates->add(lme->getLine()->getCoordinatesRO(),
+        coordinates->add(*lme->getLine()->getCoordinatesRO(),
                          false,
                          directedEdge->getEdgeDirection());
     }
     if(reverseDirectedEdges > forwardDirectedEdges) {
-        CoordinateSequence::reverse(coordinates.get());
+        coordinates->reverse();
     }
-    return coordinates.release();
+    return coordinates;
 }
 
 /*
  * Converts this EdgeString into a new LineString.
  */
-LineString*
-EdgeString::toLineString()
+std::unique_ptr
+EdgeString::toLineString() const
 {
     return factory->createLineString(getCoordinates());
 }
diff --git a/Sources/geos/src/operation/linemerge/LineMerger.cpp b/Sources/geos/src/operation/linemerge/LineMerger.cpp
index 8943440..e26aab2 100644
--- a/Sources/geos/src/operation/linemerge/LineMerger.cpp
+++ b/Sources/geos/src/operation/linemerge/LineMerger.cpp
@@ -76,7 +76,7 @@ struct LMGeometryComponentFilter: public GeometryComponentFilter {
     LMGeometryComponentFilter(LineMerger* newLm): lm(newLm) {}
 
     void
-    filter(const Geometry* geom)
+    filter_ro(const Geometry* geom) override
     {
         const LineString* ls = dynamic_cast(geom);
         if(ls) {
@@ -94,8 +94,10 @@ struct LMGeometryComponentFilter: public GeometryComponentFilter {
 void
 LineMerger::add(const Geometry* geometry)
 {
+    util::ensureNoCurvedComponents(geometry);
+
     LMGeometryComponentFilter lmgcf(this);
-    geometry->applyComponentFilter(lmgcf);
+    geometry->apply_ro(&lmgcf);
 }
 
 void
diff --git a/Sources/geos/src/operation/linemerge/LineSequencer.cpp b/Sources/geos/src/operation/linemerge/LineSequencer.cpp
index 8a5c515..12b8266 100644
--- a/Sources/geos/src/operation/linemerge/LineSequencer.cpp
+++ b/Sources/geos/src/operation/linemerge/LineSequencer.cpp
@@ -197,7 +197,7 @@ LineSequencer::computeSequence()
 Geometry*
 LineSequencer::buildSequencedGeometry(const Sequences& sequences)
 {
-    std::unique_ptr lines(new Geometry::NonConstVect);
+    std::vector> lines;
 
     for(Sequences::const_iterator
             i1 = sequences.begin(), i1End = sequences.end();
@@ -211,24 +211,24 @@ LineSequencer::buildSequencedGeometry(const Sequences& sequences)
             const LineString* line = e->getLine();
 
             // lineToAdd will be a *copy* of input things
-            LineString* lineToAdd;
+            std::unique_ptr lineToAdd;
 
             if(! de->getEdgeDirection() && ! line->isClosed()) {
-                lineToAdd = line->reverse().release();
+                lineToAdd = line->reverse();
             }
             else {
-                lineToAdd = line->clone().release();
+                lineToAdd = line->clone();
             }
 
-            lines->push_back(lineToAdd);
+            lines.push_back(std::move(lineToAdd));
         }
     }
 
-    if(lines->empty()) {
+    if(lines.empty()) {
         return nullptr;
     }
     else {
-        return factory->buildGeometry(lines.release());
+        return factory->buildGeometry(std::move(lines)).release();
     }
 }
 
@@ -309,7 +309,7 @@ LineSequencer::addReverseSubpath(const planargraph::DirectedEdge* de,
     if(expectedClosed) {
         // the path should end at the toNode of this de,
         // otherwise we have an error
-        util::Assert::isTrue(fromNode == endNode, "path not contiguos");
+        util::Assert::isTrue(fromNode == endNode, "path not contiguous");
         //assert(fromNode == endNode);
     }
 
diff --git a/Sources/geos/src/operation/overlay/EdgeSetNoder.cpp b/Sources/geos/src/operation/overlay/EdgeSetNoder.cpp
deleted file mode 100644
index 3ba98d7..0000000
--- a/Sources/geos/src/operation/overlay/EdgeSetNoder.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/**********************************************************************
- *
- * GEOS - Geometry Engine Open Source
- * http://geos.osgeo.org
- *
- * Copyright (C) 2001-2002 Vivid Solutions Inc.
- * Copyright (C) 2005 Refractions Research Inc.
- *
- * This is free software; you can redistribute and/or modify it under
- * the terms of the GNU Lesser General Public Licence as published
- * by the Free Software Foundation.
- * See the COPYING file for more information.
- *
- ***********************************************************************
- *
- * Last port: operation/overlay/EdgeSetNoder.java rev. 1.12 (JTS-1.10)
- *
- **********************************************************************/
-
-#include 
-
-#include 
-#include 
-#include 
-#include 
-#include 
-
-
-using namespace geos::algorithm;
-using namespace geos::geomgraph;
-using namespace geos::geomgraph::index;
-
-namespace geos {
-namespace operation { // geos.operation
-namespace overlay { // geos.operation.overlay
-
-
-void
-EdgeSetNoder::addEdges(std::vector* edges)
-{
-    inputEdges->insert(inputEdges->end(), edges->begin(), edges->end());
-}
-
-std::vector*
-EdgeSetNoder::getNodedEdges()
-{
-    EdgeSetIntersector* esi = new SimpleMCSweepLineIntersector();
-    SegmentIntersector* si = new SegmentIntersector(li, true, false);
-    esi->computeIntersections(inputEdges, si, true);
-    //Debug.println("has proper int = " + si.hasProperIntersection());
-    std::vector* splitEdges = new std::vector();
-    for(Edge* e : *inputEdges) {
-        e->getEdgeIntersectionList().addSplitEdges(splitEdges);
-    }
-    return splitEdges;
-}
-
-} // namespace geos.operation.overlay
-} // namespace geos.operation
-} // namespace geos
diff --git a/Sources/geos/src/operation/overlay/ElevationMatrix.cpp b/Sources/geos/src/operation/overlay/ElevationMatrix.cpp
deleted file mode 100644
index 07dc954..0000000
--- a/Sources/geos/src/operation/overlay/ElevationMatrix.cpp
+++ /dev/null
@@ -1,249 +0,0 @@
-/**********************************************************************
- *
- * GEOS - Geometry Engine Open Source
- * http://geos.osgeo.org
- *
- * Copyright (C) 2001-2002 Vivid Solutions Inc.
- *
- * This is free software; you can redistribute and/or modify it under
- * the terms of the GNU Lesser General Public Licence as published
- * by the Free Software Foundation.
- * See the COPYING file for more information.
- *
- ***********************************************************************
- *
- * Last port: original (by strk)
- *
- **********************************************************************/
-
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-
-#include 
-#include 
-#include 
-#include 
-#include 
-
-#ifndef GEOS_DEBUG
-#define GEOS_DEBUG 0
-#endif
-#define PARANOIA_LEVEL 0
-
-#ifdef _MSC_VER
-#pragma warning(disable:4355)
-#endif
-
-
-using namespace geos::geom;
-
-namespace geos {
-namespace operation { // geos.operation
-namespace overlay { // geos.operation.overlay
-
-ElevationMatrixFilter::ElevationMatrixFilter(ElevationMatrix& newEm):
-    em(newEm)
-{ }
-
-void
-ElevationMatrixFilter::filter_rw(Coordinate* c) const
-{
-#if GEOS_DEBUG
-    std::cerr << "ElevationMatrixFilter::filter_rw(" << c->toString() << ") called"
-         << std::endl;
-#endif
-
-    // already has a Z value, nothing to do
-    if(! std::isnan(c->z)) {
-        return;
-    }
-
-    double p_avgElevation = em.getAvgElevation();
-
-    try {
-        const ElevationMatrixCell& emc = em.getCell(*c);
-        c->z = emc.getAvg();
-        if(std::isnan(c->z)) {
-            c->z = p_avgElevation;
-        }
-#if GEOS_DEBUG
-        std::cerr << "  z set to " << c->z << std::endl;
-#endif
-    }
-    catch(const util::IllegalArgumentException& /* ex */) {
-        c->z = avgElevation;
-    }
-}
-
-void
-ElevationMatrixFilter::filter_ro(const Coordinate* c)
-{
-#if GEOS_DEBUG
-    std::cerr << "ElevationMatrixFilter::filter_ro(" << c->toString() << ") called"
-         << std::endl;
-#endif
-    em.add(*c);
-}
-
-
-ElevationMatrix::ElevationMatrix(const Envelope& newEnv,
-                                 unsigned int newRows, unsigned int newCols):
-    filter(*this),
-    env(newEnv), cols(newCols), rows(newRows),
-    avgElevationComputed(false),
-    avgElevation(DoubleNotANumber),
-    cells(newRows * newCols)
-{
-    cellwidth = env.getWidth() / cols;
-    cellheight = env.getHeight() / rows;
-    if(cellwidth == 0) {
-        cols = 1;
-    }
-    if(cellheight == 0) {
-        rows = 1;
-    }
-}
-
-void
-ElevationMatrix::add(const Geometry* geom)
-{
-#if GEOS_DEBUG
-    std::cerr << "ElevationMatrix::add(Geometry *) called" << std::endl;
-#endif // GEOS_DEBUG
-
-    // Cannot add Geometries to an ElevationMatrix after it's average
-    // elevation has been computed
-    assert(!avgElevationComputed);
-
-    //ElevationMatrixFilter filter(this);
-    geom->apply_ro(&filter);
-
-}
-
-
-void
-ElevationMatrix::add(const Coordinate& c)
-{
-    if(std::isnan(c.z) || std::isnan(c.y)) {
-        return;
-    }
-    try {
-        ElevationMatrixCell& emc = getCell(c);
-        emc.add(c);
-    }
-    catch(const util::IllegalArgumentException& exp) {
-        // coordinate do not overlap matrix
-        std::cerr << "ElevationMatrix::add(" << c.toString()
-             << "): Coordinate does not overlap grid extent: "
-             << exp.what() << std::endl;
-        return;
-    }
-}
-
-ElevationMatrixCell&
-ElevationMatrix::getCell(const Coordinate& c)
-{
-    int col, row;
-
-    if(cellwidth == 0) {
-        col = 0;
-    }
-    else {
-        double xoffset = c.x - env.getMinX();
-        col = (int)(xoffset / cellwidth);
-        if(col == (int)cols) {
-            col = static_cast(cols - 1);
-        }
-    }
-    if(cellheight == 0) {
-        row = 0;
-    }
-    else {
-        double yoffset = c.y - env.getMinY();
-        row = (int)(yoffset / cellheight);
-        if(row == (int)rows) {
-            row = static_cast(rows - 1);
-        }
-    }
-    int celloffset = static_cast(cols) * row + col;
-
-    if(celloffset < 0 || celloffset >= (int)(cols * rows)) {
-        std::ostringstream s;
-        s << "ElevationMatrix::getCell got a Coordinate out of grid extent (" << env.toString() << ") - cols:" << cols <<
-          " rows:" << rows;
-        throw util::IllegalArgumentException(s.str());
-    }
-
-    return cells[static_cast(celloffset)];
-}
-
-const ElevationMatrixCell&
-ElevationMatrix::getCell(const Coordinate& c) const
-{
-    return const_cast(
-        const_cast(this)->getCell(c));
-}
-
-double
-ElevationMatrix::getAvgElevation() const
-{
-    if(avgElevationComputed) {
-        return avgElevation;
-    }
-    double ztot = 0;
-    int zvals = 0;
-    for(unsigned int r = 0; r < rows; r++) {
-        for(unsigned int c = 0; c < cols; c++) {
-            const ElevationMatrixCell& cell = cells[(r * cols) + c];
-            double e = cell.getAvg();
-            if(!std::isnan(e)) {
-                zvals++;
-                ztot += e;
-            }
-        }
-    }
-    if(zvals) {
-        avgElevation = ztot / zvals;
-    }
-    else {
-        avgElevation = DoubleNotANumber;
-    }
-
-    avgElevationComputed = true;
-
-    return avgElevation;
-}
-
-std::string
-ElevationMatrix::print() const
-{
-    std::ostringstream ret;
-    ret << "Cols:" << cols << " Rows:" << rows << " AvgElevation:" << getAvgElevation() << std::endl;
-    for(unsigned int r = 0; r < rows; r++) {
-        for(unsigned int c = 0; c < cols; c++) {
-            ret << cells[(r * cols) + c].print() << '\t';
-        }
-        ret << std::endl;
-    }
-    return ret.str();
-}
-
-void
-ElevationMatrix::elevate(Geometry* g) const
-{
-
-    // Nothing to do if no elevation info in matrix
-    if(std::isnan(getAvgElevation())) {
-        return;
-    }
-
-    g->apply_rw(&filter);
-}
-
-} // namespace geos.operation.overlay
-} // namespace geos.operation
-} // namespace geos;
diff --git a/Sources/geos/src/operation/overlay/ElevationMatrixCell.cpp b/Sources/geos/src/operation/overlay/ElevationMatrixCell.cpp
deleted file mode 100644
index 0993d70..0000000
--- a/Sources/geos/src/operation/overlay/ElevationMatrixCell.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-/**********************************************************************
- *
- * GEOS - Geometry Engine Open Source
- * http://geos.osgeo.org
- *
- * Copyright (C) 2001-2002 Vivid Solutions Inc.
- *
- * This is free software; you can redistribute and/or modify it under
- * the terms of the GNU Lesser General Public Licence as published
- * by the Free Software Foundation.
- * See the COPYING file for more information.
- *
- ***********************************************************************
- *
- * Last port: original (by strk)
- *
- **********************************************************************/
-
-#include 
-#include 
-#include 
-
-#include 
-#include 
-#include 
-#include 
-
-
-using namespace geos::geom;
-
-namespace geos {
-namespace operation { // geos.operation
-namespace overlay { // geos.operation.overlay
-
-ElevationMatrixCell::ElevationMatrixCell(): ztot(0)
-{
-}
-
-void
-ElevationMatrixCell::add(const Coordinate& c)
-{
-    if(!std::isnan(c.z)) {
-        if(zvals.insert(c.z).second) {
-            ztot += c.z;
-        }
-    }
-}
-
-void
-ElevationMatrixCell::add(double z)
-{
-    if(!std::isnan(z)) {
-        if(zvals.insert(z).second) {
-            ztot += z;
-        }
-    }
-}
-
-double
-ElevationMatrixCell::getTotal() const
-{
-    return ztot;
-}
-
-double
-ElevationMatrixCell::getAvg() const
-{
-    return  zvals.size() ?
-            ztot / static_cast(zvals.size()) :
-            DoubleNotANumber;
-}
-
-std::string
-ElevationMatrixCell::print() const
-{
-    std::ostringstream ret;
-    //ret<<"["<
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-
-#include 
-#include 
-#include 
-#include 
-
-#ifndef GEOS_DEBUG
-#define GEOS_DEBUG 0
-#endif
-#define COMPUTE_Z 1
-
-
-using namespace geos::algorithm;
-using namespace geos::geomgraph;
-using namespace geos::geom;
-
-namespace geos {
-namespace operation { // geos.operation
-namespace overlay { // geos.operation.overlay
-
-LineBuilder::LineBuilder(OverlayOp* newOp,
-                         const GeometryFactory* newGeometryFactory,
-                         PointLocator* newPtLocator):
-    op(newOp),
-    geometryFactory(newGeometryFactory),
-    ptLocator(newPtLocator),
-    //lineEdgesList(new std::vector()),
-    resultLineList(new std::vector())
-{
-}
-
-/*
- * @return a list of the LineStrings in the result of the
- *         specified overlay operation
- */
-std::vector*
-LineBuilder::build(OverlayOp::OpCode opCode)
-{
-    findCoveredLineEdges();
-    collectLines(opCode);
-    //labelIsolatedLines(&lineEdgesList);
-    buildLines(opCode);
-    return resultLineList;
-}
-
-/*
- * Find and mark L edges which are "covered" by the result area (if any).
- * L edges at nodes which also have A edges can be checked by checking
- * their depth at that node.
- * L edges at nodes which do not have A edges can be checked by doing a
- * point-in-polygon test with the previously computed result areas.
- */
-void
-LineBuilder::findCoveredLineEdges()
-{
-    // first set covered for all L edges at nodes which have A edges too
-    auto& nodeMap = op->getGraph().getNodeMap()->nodeMap;
-    for(auto& entry : nodeMap) {
-        Node* node = entry.second;
-        //node.print(System.out);
-        DirectedEdgeStar* des = detail::down_cast(node->getEdges());
-        des->findCoveredLineEdges();
-        //((DirectedEdgeStar*)node->getEdges())->findCoveredLineEdges();
-    }
-
-    /*
-     * For all L edges which weren't handled by the above,
-     * use a point-in-poly test to determine whether they are covered
-     */
-    std::vector* ee = op->getGraph().getEdgeEnds();
-    for(std::size_t i = 0, s = ee->size(); i < s; ++i) {
-        DirectedEdge* de = detail::down_cast((*ee)[i]);
-        Edge* e = de->getEdge();
-        if(de->isLineEdge() && !e->isCoveredSet()) {
-            bool isCovered = op->isCoveredByA(de->getCoordinate());
-            e->setCovered(isCovered);
-        }
-    }
-}
-
-void
-LineBuilder::collectLines(OverlayOp::OpCode opCode)
-{
-    std::vector* ee = op->getGraph().getEdgeEnds();
-    for(std::size_t i = 0, s = ee->size(); i < s; ++i) {
-        DirectedEdge* de = detail::down_cast((*ee)[i]);
-        collectLineEdge(de, opCode, &lineEdgesList);
-        collectBoundaryTouchEdge(de, opCode, &lineEdgesList);
-    }
-}
-
-void
-LineBuilder::collectLineEdge(DirectedEdge* de, OverlayOp::OpCode opCode,
-                             std::vector* edges)
-{
-
-    // include L edges which are in the result
-    if(de->isLineEdge()) {
-
-        const Label& label = de->getLabel();
-
-        Edge* e = de->getEdge();
-
-        if(!de->isVisited()
-                && OverlayOp::isResultOfOp(label, opCode)
-                && !e->isCovered()) {
-            //Debug.println("de: "+de.getLabel());
-            //Debug.println("edge: "+e.getLabel());
-            edges->push_back(e);
-            de->setVisitedEdge(true);
-        }
-
-    }
-
-}
-
-/*private*/
-void
-LineBuilder::collectBoundaryTouchEdge(DirectedEdge* de,
-                                      OverlayOp::OpCode opCode, std::vector* edges)
-{
-    if(de->isLineEdge()) {
-        return;    // only interested in area edges
-    }
-    if(de->isVisited()) {
-        return;    // already processed
-    }
-
-    // added to handle dimensional collapses
-    if(de->isInteriorAreaEdge()) {
-        return;
-    }
-
-    // if the edge linework is already included, don't include it again
-    if(de->getEdge()->isInResult()) {
-        return;
-    }
-
-    // sanity check for labelling of result edgerings
-    assert(!(de->isInResult() || de->getSym()->isInResult())
-           ||
-           ! de->getEdge()->isInResult());
-
-
-    // include the linework if it's in the result of the operation
-    const Label& label = de->getLabel();
-    if(OverlayOp::isResultOfOp(label, opCode)
-            && opCode == OverlayOp::opINTERSECTION) {
-        edges->push_back(de->getEdge());
-        de->setVisitedEdge(true);
-    }
-}
-
-void
-LineBuilder::buildLines(OverlayOp::OpCode /* opCode */)
-{
-    for(std::size_t i = 0, s = lineEdgesList.size(); i < s; ++i) {
-        Edge* e = lineEdgesList[i];
-        auto cs = e->getCoordinates()->clone();
-#if COMPUTE_Z
-        propagateZ(cs.get());
-#endif
-        LineString* line = geometryFactory->createLineString(cs.release());
-        resultLineList->push_back(line);
-        e->setInResult(true);
-    }
-}
-
-/*
- * If the given CoordinateSequence has mixed 3d/2d vertexes
- * set Z for all vertexes missing it.
- * The Z value is interpolated between 3d vertexes and copied
- * from a 3d vertex to the end.
- */
-void
-LineBuilder::propagateZ(CoordinateSequence* cs)
-{
-#if GEOS_DEBUG
-    std::cerr << "LineBuilder::propagateZ() called" << std::endl;
-#endif
-
-    std::vector v3d; // vertex 3d
-    std::size_t cssize = cs->getSize();
-    for(std::size_t i = 0; i < cssize; ++i) {
-        if(!std::isnan(cs->getAt(i).z)) {
-            v3d.push_back(i);
-        }
-    }
-
-#if GEOS_DEBUG
-    std::cerr << "  found " << v3d.size() << " 3d vertexes" << std::endl;
-#endif
-
-    if(v3d.empty()) {
-#if GEOS_DEBUG
-        std::cerr << "  nothing to do" << std::endl;
-#endif
-        return;
-    }
-
-    Coordinate buf;
-
-    // fill initial part
-    if(v3d[0] != 0) {
-        double z = cs->getAt(v3d[0]).z;
-        for(std::size_t j = 0; j < v3d[0]; ++j) {
-            buf = cs->getAt(j);
-            buf.z = z;
-            cs->setAt(buf, j);
-        }
-    }
-
-    // interpolate inbetweens
-    std::size_t prev = v3d[0];
-    for(std::size_t i = 1; i < v3d.size(); ++i) {
-        auto curr = v3d[i];
-        auto dist = curr - prev;
-        if(dist > 1) {
-            const Coordinate& cto = cs->getAt(curr);
-            const Coordinate& cfrom = cs->getAt(prev);
-            double gap = cto.z - cfrom.z;
-            double zstep = gap / static_cast(dist);
-            double z = cfrom.z;
-            for(std::size_t j = prev + 1; j < curr; ++j) {
-                buf = cs->getAt(j);
-                z += zstep;
-                buf.z = z;
-                cs->setAt(buf, j);
-            }
-        }
-        prev = curr;
-    }
-
-    // fill final part
-    if(prev < cssize - 1) {
-        double z = cs->getAt(prev).z;
-        for(std::size_t j = prev + 1; j < cssize; j++) {
-            buf = cs->getAt(j);
-            buf.z = z;
-            cs->setAt(buf, j);
-        }
-    }
-
-}
-
-
-
-void
-LineBuilder::labelIsolatedLines(std::vector* edgesList)
-{
-    for(std::size_t i = 0, s = edgesList->size(); i < s; ++i) {
-        Edge* e = (*edgesList)[i];
-        const Label& label = e->getLabel();
-        //n.print(System.out);
-        if(e->isIsolated()) {
-            if(label.isNull(0)) {
-                labelIsolatedLine(e, 0);
-            }
-            else {
-                labelIsolatedLine(e, 1);
-            }
-        }
-    }
-}
-
-/*
- * Label an isolated node with its relationship to the target geometry.
- */
-void
-LineBuilder::labelIsolatedLine(Edge* e, uint8_t targetIndex)
-{
-    Location loc = ptLocator->locate(e->getCoordinate(),
-                                op->getArgGeometry(targetIndex));
-    e->getLabel().setLocation(targetIndex, loc);
-}
-
-} // namespace geos.operation.overlay
-} // namespace geos.operation
-} // namespace geos
diff --git a/Sources/geos/src/operation/overlay/OverlayOp.cpp b/Sources/geos/src/operation/overlay/OverlayOp.cpp
deleted file mode 100644
index 2fa0c10..0000000
--- a/Sources/geos/src/operation/overlay/OverlayOp.cpp
+++ /dev/null
@@ -1,1121 +0,0 @@
-/**********************************************************************
- *
- * GEOS - Geometry Engine Open Source
- * http://geos.osgeo.org
- *
- * Copyright (C) 2005-2006 Refractions Research Inc.
- * Copyright (C) 2001-2002 Vivid Solutions Inc.
- *
- * This is free software; you can redistribute and/or modify it under
- * the terms of the GNU Lesser General Public Licence as published
- * by the Free Software Foundation.
- * See the COPYING file for more information.
- *
- ***********************************************************************
- *
- * Last port: operation/overlay/OverlayOp.java r567 (JTS-1.12+)
- *
- **********************************************************************/
-
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-
-#include 
-#include 
-#include 
-#include 
-#include  // for unique_ptr
-
-#ifndef GEOS_DEBUG
-#define GEOS_DEBUG 0
-#endif
-
-#define COMPUTE_Z 1
-#define USE_ELEVATION_MATRIX 1
-#define USE_INPUT_AVGZ 0
-
-// A result validator using FuzzyPointLocator to
-// check validity of result. Pretty expensive...
-//#define ENABLE_OVERLAY_RESULT_VALIDATOR 1
-
-// Edge noding validator, more lightweighted and
-// catches robustness errors earlier
-#define ENABLE_EDGE_NODING_VALIDATOR 1
-
-// Define this to get some reports about validations
-//#define GEOS_DEBUG_VALIDATION 1
-
-// Other validators, not found in JTS
-//#define ENABLE_OTHER_OVERLAY_RESULT_VALIDATORS 1
-
-
-using namespace geos::geom;
-using namespace geos::geomgraph;
-using namespace geos::algorithm;
-
-namespace geos {
-namespace operation { // geos.operation
-namespace overlay { // geos.operation.overlay
-
-/* static public */
-Geometry*
-OverlayOp::overlayOp(const Geometry* geom0, const Geometry* geom1,
-                     OverlayOp::OpCode opCode)
-// throw(TopologyException *)
-{
-    OverlayOp gov(geom0, geom1);
-    return gov.getResultGeometry(opCode);
-}
-
-/* static public */
-bool
-OverlayOp::isResultOfOp(const Label& label, OverlayOp::OpCode opCode)
-{
-    Location loc0 = label.getLocation(0);
-    Location loc1 = label.getLocation(1);
-    return isResultOfOp(loc0, loc1, opCode);
-}
-
-
-/* static public */
-bool
-OverlayOp::isResultOfOp(Location loc0, Location loc1, OverlayOp::OpCode opCode)
-{
-    if(loc0 == Location::BOUNDARY) {
-        loc0 = Location::INTERIOR;
-    }
-    if(loc1 == Location::BOUNDARY) {
-        loc1 = Location::INTERIOR;
-    }
-    switch(opCode) {
-    case opINTERSECTION:
-        return loc0 == Location::INTERIOR && loc1 == Location::INTERIOR;
-    case opUNION:
-        return loc0 == Location::INTERIOR || loc1 == Location::INTERIOR;
-    case opDIFFERENCE:
-        return loc0 == Location::INTERIOR && loc1 != Location::INTERIOR;
-    case opSYMDIFFERENCE:
-        return (loc0 == Location::INTERIOR && loc1 != Location::INTERIOR)
-               || (loc0 != Location::INTERIOR && loc1 == Location::INTERIOR);
-    }
-    return false;
-}
-
-OverlayOp::OverlayOp(const Geometry* g0, const Geometry* g1)
-
-    :
-
-    // this builds graphs in arg[0] and arg[1]
-    GeometryGraphOperation(g0, g1),
-
-    /*
-     * Use factory of primary geometry.
-     * Note that this does NOT handle mixed-precision arguments
-     * where the second arg has greater precision than the first.
-     */
-    geomFact(g0->getFactory()),
-
-    resultGeom(nullptr),
-    graph(OverlayNodeFactory::instance()),
-    resultPolyList(nullptr),
-    resultLineList(nullptr),
-    resultPointList(nullptr)
-
-{
-
-#if COMPUTE_Z
-#if USE_INPUT_AVGZ
-    avgz[0] = DoubleNotANumber;
-    avgz[1] = DoubleNotANumber;
-    avgzcomputed[0] = false;
-    avgzcomputed[1] = false;
-#endif // USE_INPUT_AVGZ
-
-    Envelope env(*(g0->getEnvelopeInternal()));
-    env.expandToInclude(g1->getEnvelopeInternal());
-#if USE_ELEVATION_MATRIX
-    elevationMatrix = new ElevationMatrix(env, 3, 3);
-    elevationMatrix->add(g0);
-    elevationMatrix->add(g1);
-#if GEOS_DEBUG
-    std::cerr << elevationMatrix->print() << std::endl;
-#endif
-#endif // USE_ELEVATION_MATRIX
-#endif // COMPUTE_Z
-}
-
-OverlayOp::~OverlayOp()
-{
-    delete resultPolyList;
-    delete resultLineList;
-    delete resultPointList;
-    for(std::size_t i = 0; i < dupEdges.size(); i++) {
-        delete dupEdges[i];
-    }
-#if USE_ELEVATION_MATRIX
-    delete elevationMatrix;
-#endif
-}
-
-/*public*/
-Geometry*
-OverlayOp::getResultGeometry(OverlayOp::OpCode funcCode)
-//throw(TopologyException *)
-{
-    computeOverlay(funcCode);
-    return resultGeom;
-}
-
-/*private*/
-void
-OverlayOp::insertUniqueEdges(std::vector* edges, const Envelope* env)
-{
-    for(std::size_t i = 0, n = edges->size(); i < n; ++i) {
-        Edge* e = (*edges)[i];
-        if(env && ! env->intersects(e->getEnvelope())) {
-            dupEdges.push_back(e); // or could it be deleted directly ?
-            continue;
-        }
-#if GEOS_DEBUG
-        std::cerr << " " << e->print() << std::endl;
-#endif
-        insertUniqueEdge(e);
-    }
-}
-
-/*private*/
-void
-OverlayOp::replaceCollapsedEdges()
-{
-    std::vector& edges = edgeList.getEdges();
-
-    for(std::size_t i = 0, nedges = edges.size(); i < nedges; ++i) {
-        Edge* e = edges[i];
-        assert(e);
-        if(e->isCollapsed()) {
-#if GEOS_DEBUG
-            std::cerr << " replacing collapsed edge " << i << std::endl;
-#endif // GEOS_DEBUG
-            //Debug.print(e);
-            edges[i] = e->getCollapsedEdge();
-
-            // should we keep this alive some more ?
-            delete e;
-        }
-    }
-}
-
-/*private*/
-void
-OverlayOp::copyPoints(uint8_t argIndex, const Envelope* env)
-{
-//#define GEOS_DEBUG_COPY_POINTS 1
-
-#ifdef GEOS_DEBUG_COPY_POINTS
-    int copied = 0;
-#endif
-
-    //env = 0; // WARNING: uncomment to disable env-optimization
-
-    // TODO: set env to null if it covers arg geometry envelope
-
-    NodeMap::container& nodeMap = arg[argIndex]->getNodeMap()->nodeMap;
-    for(NodeMap::const_iterator it = nodeMap.begin(), itEnd = nodeMap.end();
-            it != itEnd; ++it) {
-        Node* graphNode = it->second;
-        assert(graphNode);
-        const Coordinate& coord = graphNode->getCoordinate();
-
-        if(env && ! env->covers(&coord)) {
-            continue;
-        }
-
-#ifdef GEOS_DEBUG_COPY_POINTS
-        ++copied;
-#endif
-        Node* newNode = graph.addNode(coord);
-        assert(newNode);
-
-        newNode->setLabel(argIndex, graphNode->getLabel().getLocation(argIndex));
-    }
-
-#ifdef GEOS_DEBUG_COPY_POINTS
-    std::cerr << "Copied " << copied << " nodes out of " << nodeMap.size() << " for geom " << argIndex << std::endl;
-#endif
-}
-
-/*private*/
-void
-OverlayOp::computeLabelling()
-//throw(TopologyException *) // and what else ?
-{
-    NodeMap::container& nodeMap = graph.getNodeMap()->nodeMap;
-
-#if GEOS_DEBUG
-    std::cerr << "OverlayOp::computeLabelling(): at call time: " << edgeList.print() << std::endl;
-#endif
-
-#if GEOS_DEBUG
-    std::cerr << "OverlayOp::computeLabelling() scanning " << nodeMap.size() << " nodes from map:" << std::endl;
-#endif
-
-    for(NodeMap::const_iterator it = nodeMap.begin(), itEnd = nodeMap.end();
-            it != itEnd; ++it) {
-        Node* node = it->second;
-#if GEOS_DEBUG
-        std::cerr << "     " << node->print() << " has " << node->getEdges()->getEdges().size() << " edgeEnds" << std::endl;
-#endif
-        node->getEdges()->computeLabelling(&arg);
-    }
-#if GEOS_DEBUG
-    std::cerr << "OverlayOp::computeLabelling(): after edge labelling: " << edgeList.print() << std::endl;
-#endif
-    mergeSymLabels();
-#if GEOS_DEBUG
-    std::cerr << "OverlayOp::computeLabelling(): after labels sym merging: " << edgeList.print() << std::endl;
-#endif
-    updateNodeLabelling();
-#if GEOS_DEBUG
-    std::cerr << "OverlayOp::computeLabelling(): after node labeling update: " << edgeList.print() << std::endl;
-#endif
-}
-
-/*private*/
-void
-OverlayOp::mergeSymLabels()
-{
-    NodeMap::container& nodeMap = graph.getNodeMap()->nodeMap;
-
-#if GEOS_DEBUG
-    std::cerr << "OverlayOp::mergeSymLabels() scanning " << nodeMap.size() << " nodes from map:" << std::endl;
-#endif
-
-    for(NodeMap::const_iterator it = nodeMap.begin(), itEnd = nodeMap.end();
-            it != itEnd; ++it) {
-        Node* node = it->second;
-        EdgeEndStar* ees = node->getEdges();
-        detail::down_cast(ees)->mergeSymLabels();
-        //((DirectedEdgeStar*)node->getEdges())->mergeSymLabels();
-#if GEOS_DEBUG
-        std::cerr << "     " << node->print() << std::endl;
-#endif
-        //node.print(System.out);
-    }
-}
-
-/*private*/
-void
-OverlayOp::updateNodeLabelling()
-{
-    // update the labels for nodes
-    // The label for a node is updated from the edges incident on it
-    // (Note that a node may have already been labelled
-    // because it is a point in one of the input geometries)
-
-    NodeMap::container& nodeMap = graph.getNodeMap()->nodeMap;
-
-#if GEOS_DEBUG
-    std::cerr << "OverlayOp::updateNodeLabelling() scanning "
-         << nodeMap.size() << " nodes from map:" << std::endl;
-#endif
-
-    for(NodeMap::const_iterator it = nodeMap.begin(), itEnd = nodeMap.end();
-            it != itEnd; ++it) {
-        Node* node = it->second;
-        EdgeEndStar* ees = node->getEdges();
-        DirectedEdgeStar* des = detail::down_cast(ees);
-        Label& lbl = des->getLabel();
-        node->getLabel().merge(lbl);
-#if GEOS_DEBUG
-        std::cerr << "     " << node->print() << std::endl;
-#endif
-    }
-}
-
-/*private*/
-void
-OverlayOp::labelIncompleteNodes()
-{
-    NodeMap::container& nodeMap = graph.getNodeMap()->nodeMap;
-
-#if GEOS_DEBUG
-    std::cerr << "OverlayOp::labelIncompleteNodes() scanning " << nodeMap.size() << " nodes from map:" << std::endl;
-#endif
-
-    for(NodeMap::const_iterator it = nodeMap.begin(), itEnd = nodeMap.end();
-            it != itEnd; ++it) {
-        Node* n = it->second;
-        const Label& label = n->getLabel();
-        if(n->isIsolated()) {
-            if(label.isNull(0)) {
-                labelIncompleteNode(n, 0);
-            }
-            else {
-                labelIncompleteNode(n, 1);
-            }
-        }
-        // now update the labelling for the DirectedEdges incident on this node
-        EdgeEndStar* ees = n->getEdges();
-        DirectedEdgeStar* des = detail::down_cast(ees);
-
-        des->updateLabelling(label);
-        //((DirectedEdgeStar*)n->getEdges())->updateLabelling(label);
-        //n.print(System.out);
-    }
-}
-
-/*private*/
-void
-OverlayOp::labelIncompleteNode(Node* n, uint8_t targetIndex)
-{
-#if GEOS_DEBUG
-    std::cerr << "OverlayOp::labelIncompleteNode(" << n->print() << ", " << targetIndex << ")" << std::endl;
-#endif
-    const Geometry* targetGeom = arg[targetIndex]->getGeometry();
-    Location loc = ptLocator.locate(n->getCoordinate(), targetGeom);
-    n->getLabel().setLocation(targetIndex, loc);
-
-#if GEOS_DEBUG
-    std::cerr << "   after location set: " << n->print() << std::endl;
-#endif
-
-#if COMPUTE_Z
-    /*
-     * If this node has been labeled INTERIOR of a line
-     * or BOUNDARY of a polygon we must merge
-     * Z values of the intersected segment.
-     * The intersection point has been already computed
-     * by LineIntersector invoked by PointLocator.
-     */
-
-    // Only do this if input does have Z
-    // See https://trac.osgeo.org/geos/ticket/811
-    if(targetGeom->getCoordinateDimension() < 3u) {
-        return;
-    }
-
-    const LineString* line = dynamic_cast(targetGeom);
-    if(loc == Location::INTERIOR && line) {
-        mergeZ(n, line);
-    }
-    const Polygon* poly = dynamic_cast(targetGeom);
-    if(loc == Location::BOUNDARY && poly) {
-        mergeZ(n, poly);
-    }
-#if USE_INPUT_AVGZ
-    if(loc == Location::INTERIOR && poly) {
-        n->addZ(getAverageZ(targetIndex));
-    }
-#endif // USE_INPUT_AVGZ
-#endif // COMPUTE_Z
-}
-
-/*static private*/
-double
-OverlayOp::getAverageZ(const Polygon* poly)
-{
-    double totz = 0.0;
-    int zcount = 0;
-
-    const CoordinateSequence* pts =
-        poly->getExteriorRing()->getCoordinatesRO();
-    std::size_t npts = pts->getSize();
-    for(std::size_t i = 0; i < npts; ++i) {
-        const Coordinate& c = pts->getAt(i);
-        if(!std::isnan(c.z)) {
-            totz += c.z;
-            zcount++;
-        }
-    }
-
-    if(zcount) {
-        return totz / zcount;
-    }
-    else {
-        return DoubleNotANumber;
-    }
-}
-
-/*private*/
-double
-OverlayOp::getAverageZ(uint8_t targetIndex)
-{
-    if(avgzcomputed[targetIndex]) {
-        return avgz[targetIndex];
-    }
-
-    const Geometry* targetGeom = arg[targetIndex]->getGeometry();
-
-    // OverlayOp::getAverageZ(int) called with a ! polygon
-    assert(targetGeom->getGeometryTypeId() == GEOS_POLYGON);
-
-    const Polygon* p = dynamic_cast(targetGeom);
-    avgz[targetIndex] = getAverageZ(p);
-    avgzcomputed[targetIndex] = true;
-    return avgz[targetIndex];
-}
-
-/*private*/
-int
-OverlayOp::mergeZ(Node* n, const Polygon* poly) const
-{
-    const LineString* ls;
-    int found = 0;
-    ls = poly->getExteriorRing();
-    found = mergeZ(n, ls);
-    if(found) {
-        return 1;
-    }
-    for(std::size_t i = 0, nr = poly->getNumInteriorRing(); i < nr; ++i) {
-        ls = poly->getInteriorRingN(i);
-        found = mergeZ(n, ls);
-        if(found) {
-            return 1;
-        }
-    }
-    return 0;
-}
-
-/*private*/
-int
-OverlayOp::mergeZ(Node* n, const LineString* line) const
-{
-    const CoordinateSequence* pts = line->getCoordinatesRO();
-    const Coordinate& p = n->getCoordinate();
-    LineIntersector p_li;
-    for(std::size_t i = 1, size = pts->size(); i < size; ++i) {
-        const Coordinate& p0 = pts->getAt(i - 1);
-        const Coordinate& p1 = pts->getAt(i);
-        p_li.computeIntersection(p, p0, p1);
-        if(p_li.hasIntersection()) {
-            if(p == p0) {
-                n->addZ(p0.z);
-            }
-            else if(p == p1) {
-                n->addZ(p1.z);
-            }
-            else {
-                n->addZ(LineIntersector::interpolateZ(p,
-                                                      p0, p1));
-            }
-            return 1;
-        }
-    }
-    return 0;
-}
-
-/*private*/
-void
-OverlayOp::findResultAreaEdges(OverlayOp::OpCode opCode)
-{
-    std::vector* ee = graph.getEdgeEnds();
-    for(std::size_t i = 0, e = ee->size(); i < e; ++i) {
-        DirectedEdge* de = (DirectedEdge*)(*ee)[i];
-        // mark all dirEdges with the appropriate label
-        const Label& label = de->getLabel();
-        if(label.isArea()
-                && !de->isInteriorAreaEdge()
-                && isResultOfOp(label.getLocation(0, Position::RIGHT),
-                                label.getLocation(1, Position::RIGHT),
-                                opCode)
-          ) {
-            de->setInResult(true);
-            //Debug.print("in result "); Debug.println(de);
-        }
-    }
-}
-
-/*private*/
-void
-OverlayOp::cancelDuplicateResultEdges()
-{
-    // remove any dirEdges whose sym is also included
-    // (they "cancel each other out")
-    std::vector* ee = graph.getEdgeEnds();
-    for(std::size_t i = 0, eesize = ee->size(); i < eesize; ++i) {
-        DirectedEdge* de = static_cast((*ee)[i]);
-        DirectedEdge* sym = de->getSym();
-        if(de->isInResult() && sym->isInResult()) {
-            de->setInResult(false);
-            sym->setInResult(false);
-            //Debug.print("cancelled "); Debug.println(de); Debug.println(sym);
-        }
-    }
-}
-
-/*public*/
-bool
-OverlayOp::isCoveredByLA(const Coordinate& coord)
-{
-    if(isCovered(coord, resultLineList)) {
-        return true;
-    }
-    if(isCovered(coord, resultPolyList)) {
-        return true;
-    }
-    return false;
-}
-
-/*public*/
-bool
-OverlayOp::isCoveredByA(const Coordinate& coord)
-{
-    if(isCovered(coord, resultPolyList)) {
-        return true;
-    }
-    return false;
-}
-
-/*private*/
-bool
-OverlayOp::isCovered(const Coordinate& coord, std::vector* geomList)
-{
-    for(std::size_t i = 0, n = geomList->size(); i < n; ++i) {
-        Geometry* geom = (*geomList)[i];
-        Location loc = ptLocator.locate(coord, geom);
-        if(loc != Location::EXTERIOR) {
-            return true;
-        }
-    }
-    return false;
-}
-
-/*private*/
-bool
-OverlayOp::isCovered(const Coordinate& coord, std::vector* geomList)
-{
-    for(std::size_t i = 0, n = geomList->size(); i < n; ++i) {
-        Geometry* geom = (Geometry*)(*geomList)[i];
-        Location loc = ptLocator.locate(coord, geom);
-        if(loc != Location::EXTERIOR) {
-            return true;
-        }
-    }
-    return false;
-}
-
-/*private*/
-bool
-OverlayOp::isCovered(const Coordinate& coord, std::vector* geomList)
-{
-    for(std::size_t i = 0, n = geomList->size(); i < n; ++i) {
-        Geometry* geom = (Geometry*)(*geomList)[i];
-        Location loc = ptLocator.locate(coord, geom);
-        if(loc != Location::EXTERIOR) {
-            return true;
-        }
-    }
-    return false;
-}
-
-Dimension::DimensionType
-OverlayOp::resultDimension(OverlayOp::OpCode overlayOpCode,
-                           const Geometry* g0, const Geometry* g1)
-{
-    Dimension::DimensionType dim0 = g0->getDimension();
-    Dimension::DimensionType dim1 = g1->getDimension();
-
-    Dimension::DimensionType resultDimension = Dimension::False;
-    switch(overlayOpCode) {
-    case OverlayOp::opINTERSECTION:
-        resultDimension = std::min(dim0, dim1);
-        break;
-    case OverlayOp::opUNION:
-        resultDimension = std::max(dim0, dim1);
-        break;
-    case OverlayOp::opDIFFERENCE:
-        resultDimension = dim0;
-        break;
-    case OverlayOp::opSYMDIFFERENCE:
-        resultDimension = std::max(dim0, dim1);
-        break;
-    }
-    return resultDimension;
-}
-
-std::unique_ptr
-OverlayOp::createEmptyResult(OverlayOp::OpCode overlayOpCode,
-                             const geom::Geometry* a, const geom::Geometry* b,
-                             const GeometryFactory* geomFact)
-{
-    std::unique_ptr result = nullptr;
-    switch(resultDimension(overlayOpCode, a, b)) {
-    case Dimension::P:
-        result = geomFact->createPoint();
-        break;
-    case Dimension::L:
-        result = geomFact->createLineString();
-        break;
-    case Dimension::A:
-        result = geomFact->createPolygon();
-        break;
-    default:
-        result = geomFact->createGeometryCollection();
-        break;
-    }
-    return result;
-}
-
-/*private*/
-Geometry*
-OverlayOp::computeGeometry(std::vector* nResultPointList,
-                           std::vector* nResultLineList,
-                           std::vector* nResultPolyList,
-                           OverlayOp::OpCode opCode)
-{
-    std::size_t nPoints = nResultPointList->size();
-    std::size_t nLines = nResultLineList->size();
-    std::size_t nPolys = nResultPolyList->size();
-
-    std::unique_ptr> geomList{new std::vector()};
-    geomList->reserve(nPoints + nLines + nPolys);
-
-    // element geometries of the result are always in the order P,L,A
-    geomList->insert(geomList->end(),
-                     nResultPointList->begin(),
-                     nResultPointList->end());
-
-    geomList->insert(geomList->end(),
-                     nResultLineList->begin(),
-                     nResultLineList->end());
-
-    geomList->insert(geomList->end(),
-                     nResultPolyList->begin(),
-                     nResultPolyList->end());
-
-
-    if(geomList->empty()) {
-        return createEmptyResult(opCode, arg[0]->getGeometry(),
-                                 arg[1]->getGeometry(), geomFact).release();
-    }
-    // build the most specific geometry possible
-    Geometry* g = geomFact->buildGeometry(geomList.release());
-    return g;
-}
-
-/*private*/
-void
-OverlayOp::computeOverlay(OverlayOp::OpCode opCode)
-//throw(TopologyException *)
-{
-
-    // Compute the target envelope
-    const Envelope* env = nullptr;
-    const Envelope* env0 = getArgGeometry(0)->getEnvelopeInternal();
-    const Envelope* env1 = getArgGeometry(1)->getEnvelopeInternal();
-    Envelope opEnv;
-    if(resultPrecisionModel->isFloating()) {
-        // Envelope-based optimization only works in floating precision
-        switch(opCode) {
-        case opINTERSECTION:
-            env0->intersection(*env1, opEnv);
-            env = &opEnv;
-            break;
-        case opDIFFERENCE:
-            opEnv = *env0;
-            env = &opEnv;
-            break;
-        default:
-            break;
-        }
-    }
-    //env = 0; // WARNING: uncomment to disable env-optimization
-
-    // copy points from input Geometries.
-    // This ensures that any Point geometries
-    // in the input are considered for inclusion in the result set
-    copyPoints(0, env);
-    copyPoints(1, env);
-
-    GEOS_CHECK_FOR_INTERRUPTS();
-
-    // node the input Geometries
-    arg[0]->computeSelfNodes(li, false, env);
-    GEOS_CHECK_FOR_INTERRUPTS();
-    arg[1]->computeSelfNodes(li, false, env);
-
-#if GEOS_DEBUG
-    std::cerr << "OverlayOp::computeOverlay: computed SelfNodes" << std::endl;
-#endif
-
-    GEOS_CHECK_FOR_INTERRUPTS();
-
-    // compute intersections between edges of the two input geometries
-    arg[0]->computeEdgeIntersections(arg[1], &li, true, env);
-
-#if GEOS_DEBUG
-    std::cerr << "OverlayOp::computeOverlay: computed EdgeIntersections" << std::endl;
-    std::cerr << "OverlayOp::computeOverlay: li: " << li.toString() << std::endl;
-#endif
-
-
-    GEOS_CHECK_FOR_INTERRUPTS();
-
-    std::vector baseSplitEdges;
-    arg[0]->computeSplitEdges(&baseSplitEdges);
-    GEOS_CHECK_FOR_INTERRUPTS();
-    arg[1]->computeSplitEdges(&baseSplitEdges);
-
-    GEOS_CHECK_FOR_INTERRUPTS();
-
-    // add the noded edges to this result graph
-    insertUniqueEdges(&baseSplitEdges, env);
-    computeLabelsFromDepths();
-    replaceCollapsedEdges();
-    //Debug.println(edgeList);
-
-    GEOS_CHECK_FOR_INTERRUPTS();
-
-#ifdef ENABLE_EDGE_NODING_VALIDATOR // {
-    /*
-     * Check that the noding completed correctly.
-     *
-     * This test is slow, but necessary in order to catch
-     * robustness failure situations.
-     * If an exception is thrown because of a noding failure,
-     * then snapping will be performed, which will hopefully avoid
-     * the problem.
-     * In the future hopefully a faster check can be developed.
-     *
-     */
-    try {
-#ifdef GEOS_DEBUG_VALIDATION
-        std::size_t << "EdgeNodingValidator about to evaluate " << edgeList.getEdges().size() << " edges" << std::endl;
-#endif
-        // Will throw TopologyException if noding is
-        // found to be invalid
-        EdgeNodingValidator::checkValid(edgeList.getEdges());
-#ifdef GEOS_DEBUG_VALIDATION
-        std::size_t << "EdgeNodingValidator accepted the noding" << std::endl;
-#endif
-    }
-    catch(const util::TopologyException& ex) {
-#ifdef GEOS_DEBUG_VALIDATION
-        std::size_t << "EdgeNodingValidator found noding invalid: " << ex.what() << std::endl;
-#endif
-        ::geos::ignore_unused_variable_warning(ex);
-        // In the error scenario, the edgeList is not properly
-        // deleted. Cannot add to the destructor of EdgeList
-        // (as it should) because
-        // "graph.addEdges(edgeList.getEdges());" below
-        // takes over edgeList ownership in the success case.
-        edgeList.clearList();
-
-        throw;
-    }
-
-#endif // ENABLE_EDGE_NODING_VALIDATOR }
-
-    GEOS_CHECK_FOR_INTERRUPTS();
-
-    graph.addEdges(edgeList.getEdges());
-
-    GEOS_CHECK_FOR_INTERRUPTS();
-
-    // this can throw TopologyException *
-    computeLabelling();
-
-    //Debug.printWatch();
-    labelIncompleteNodes();
-    //Debug.printWatch();
-    //nodeMap.print(System.out);
-
-    GEOS_CHECK_FOR_INTERRUPTS();
-
-    /*
-     * The ordering of building the result Geometries is important.
-     * Areas must be built before lines, which must be built
-     * before points.
-     * This is so that lines which are covered by areas are not
-     * included explicitly, and similarly for points.
-     */
-    findResultAreaEdges(opCode);
-    cancelDuplicateResultEdges();
-
-    GEOS_CHECK_FOR_INTERRUPTS();
-
-    PolygonBuilder polyBuilder(geomFact);
-
-    // might throw a TopologyException *
-    polyBuilder.add(&graph);
-
-    std::vector* gv = polyBuilder.getPolygons();
-    std::size_t gvsize = gv->size();
-    resultPolyList = new std::vector(gvsize);
-    for(std::size_t i = 0; i < gvsize; ++i) {
-        Polygon* p = dynamic_cast((*gv)[i]);
-        (*resultPolyList)[i] = p;
-    }
-    delete gv;
-
-    LineBuilder lineBuilder(this, geomFact, &ptLocator);
-    resultLineList = lineBuilder.build(opCode);
-
-    PointBuilder pointBuilder(this, geomFact, &ptLocator);
-    resultPointList = pointBuilder.build(opCode);
-
-    // gather the results from all calculations into a single
-    // Geometry for the result set
-    resultGeom = computeGeometry(resultPointList, resultLineList, resultPolyList, opCode);
-
-    checkObviouslyWrongResult(opCode);
-
-
-#if USE_ELEVATION_MATRIX
-    elevationMatrix->elevate(resultGeom);
-#endif // USE_ELEVATION_MATRIX
-
-}
-
-/*protected*/
-void
-OverlayOp::insertUniqueEdge(Edge* e)
-{
-    //Debug.println(e);
-#if GEOS_DEBUG
-    std::cerr << "OverlayOp::insertUniqueEdge(" << e->print() << ")" << std::endl;
-#endif
-
-    // MD 8 Oct 03  speed up identical edge lookup
-    // fast lookup
-    Edge* existingEdge = edgeList.findEqualEdge(e);
-
-    // If an identical edge already exists, simply update its label
-    if(existingEdge) {
-
-#if GEOS_DEBUG
-        std::cerr << "  found identical edge, should merge Z" << std::endl;
-#endif
-
-        Label& existingLabel = existingEdge->getLabel();
-
-        Label labelToMerge = e->getLabel();
-
-        // check if new edge is in reverse direction to existing edge
-        // if so, must flip the label before merging it
-        if(!existingEdge->isPointwiseEqual(e)) {
-            labelToMerge.flip();
-        }
-        Depth& depth = existingEdge->getDepth();
-
-        // if this is the first duplicate found for this edge,
-        // initialize the depths
-        if(depth.isNull()) {
-            depth.add(existingLabel);
-        }
-
-        depth.add(labelToMerge);
-
-        existingLabel.merge(labelToMerge);
-
-        //Debug.print("inserted edge: "); Debug.println(e);
-        //Debug.print("existing edge: "); Debug.println(existingEdge);
-        dupEdges.push_back(e);
-    }
-    else {
-        // no matching existing edge was found
-#if GEOS_DEBUG
-        std::cerr << "  no matching existing edge" << std::endl;
-#endif
-        // add this new edge to the list of edges in this graph
-        //e.setName(name+edges.size());
-        //e.getDepth().add(e.getLabel());
-        edgeList.add(e);
-    }
-}
-
-/*private*/
-void
-OverlayOp::computeLabelsFromDepths()
-{
-    for(auto& e : edgeList.getEdges()) {
-        Label& lbl = e->getLabel();
-        Depth& depth = e->getDepth();
-
-        /*
-         * Only check edges for which there were duplicates,
-         * since these are the only ones which might
-         * be the result of dimensional collapses.
-         */
-        if(depth.isNull()) {
-            continue;
-        }
-
-        depth.normalize();
-        for(uint8_t i = 0; i < 2; i++) {
-            if(!lbl.isNull(i) && lbl.isArea() && !depth.isNull(i)) {
-                /*
-                 * if the depths are equal, this edge is the result of
-                 * the dimensional collapse of two or more edges.
-                 * It has the same location on both sides of the edge,
-                 * so it has collapsed to a line.
-                 */
-                if(depth.getDelta(i) == 0) {
-                    lbl.toLine(i);
-                }
-                else {
-                    /*
-                     * This edge may be the result of a dimensional collapse,
-                     * but it still has different locations on both sides.  The
-                     * label of the edge must be updated to reflect the resultant
-                     * side locations indicated by the depth values.
-                     */
-                    assert(!depth.isNull(i, Position::LEFT)); // depth of LEFT side has not been initialized
-                    lbl.setLocation(i, Position::LEFT, depth.getLocation(i, Position::LEFT));
-                    assert(!depth.isNull(i, Position::RIGHT)); // depth of RIGHT side has not been initialized
-                    lbl.setLocation(i, Position::RIGHT, depth.getLocation(i, Position::RIGHT));
-                }
-            }
-        }
-    }
-}
-
-struct PointCoveredByAny: public geom::CoordinateFilter {
-    const std::vector& geoms;
-    PointLocator locator;
-    PointCoveredByAny(const std::vector& nGeoms)
-        : geoms(nGeoms)
-    {}
-
-    void
-    filter_ro(const Coordinate* coord) override
-    {
-        for(std::size_t i = 0, n = geoms.size(); i < n; ++i) {
-            Location loc = locator.locate(*coord, geoms[i]);
-            if(loc == Location::INTERIOR ||
-                    loc == Location::BOUNDARY) {
-                return;
-            }
-        }
-        throw util::TopologyException("Obviously wrong result: "
-                                      "A point on first geom boundary isn't covered "
-                                      "by either result or second geom");
-    }
-
-private:
-    // Declare type as noncopyable
-    PointCoveredByAny(const PointCoveredByAny& other) = delete;
-    PointCoveredByAny& operator=(const PointCoveredByAny& rhs) = delete;
-};
-
-void
-OverlayOp::checkObviouslyWrongResult(OverlayOp::OpCode opCode)
-{
-    using std::cerr;
-    using std::endl;
-
-    assert(resultGeom);
-
-#ifdef ENABLE_OTHER_OVERLAY_RESULT_VALIDATORS
-
-    if(opCode == opINTERSECTION
-            && arg[0]->getGeometry()->getDimension() == Dimension::A
-            && arg[1]->getGeometry()->getDimension() == Dimension::A) {
-        // Area of result must be less or equal area of smaller geom
-        double area0 = arg[0]->getGeometry()->getArea();
-        double area1 = arg[1]->getGeometry()->getArea();
-        double minarea = min(area0, area1);
-        double resultArea = resultGeom->getArea();
-
-        if(resultArea > minarea) {
-            throw util::TopologyException("Obviously wrong result: "
-                                          "area of intersection "
-                                          "result is bigger then minimum area between "
-                                          "input geometries");
-        }
-    }
-
-    else if(opCode == opDIFFERENCE
-            && arg[0]->getGeometry()->getDimension() == Dimension::A
-            && arg[1]->getGeometry()->getDimension() == Dimension::A) {
-        // Area of result must be less or equal area of first geom
-        double area0 = arg[0]->getGeometry()->getArea();
-        double resultArea = resultGeom->getArea();
-
-        if(resultArea > area0) {
-            throw util::TopologyException("Obviously wrong result: "
-                                          "area of difference "
-                                          "result is bigger then area of first "
-                                          "input geometry");
-        }
-
-        // less obvious check:
-        // each vertex in first geom must be either covered by
-        // result or second geom
-        std::vector testGeoms;
-        testGeoms.reserve(2);
-        testGeoms.push_back(resultGeom);
-        testGeoms.push_back(arg[1]->getGeometry());
-        PointCoveredByAny tester(testGeoms);
-        arg[0]->getGeometry()->apply_ro(&tester);
-    }
-
-    // Add your tests here
-
-#else
-    ::geos::ignore_unused_variable_warning(opCode);
-#endif
-
-#ifdef ENABLE_OVERLAY_RESULT_VALIDATOR
-    // This only work for FLOATING precision
-    if(resultPrecisionModel->isFloating()) {
-        validate::OverlayResultValidator validator(*(arg[0]->getGeometry()),
-                *(arg[1]->getGeometry()), *(resultGeom));
-        bool isvalid = validator.isValid(opCode);
-        if(! isvalid) {
-#ifdef GEOS_DEBUG_VALIDATION
-            std::size_t << "OverlayResultValidator considered result INVALID" << std::endl;
-#endif
-            throw util::TopologyException(
-                "Obviously wrong result: "
-                "OverlayResultValidator didn't like "
-                "the result: \n"
-                "Invalid point: " +
-                validator.getInvalidLocation().toString() +
-                std::string("\nInvalid result: ") +
-                resultGeom->toString());
-        }
-#ifdef GEOS_DEBUG_VALIDATION
-        else {
-            std::size_t << "OverlayResultValidator considered result valid" << std::endl;
-        }
-#endif
-    }
-#ifdef GEOS_DEBUG_VALIDATION
-    else {
-        std::size_t << "Did not run OverlayResultValidator as the precision model is not floating" << std::endl;
-    }
-#endif // ndef GEOS_DEBUG
-#endif
-}
-
-} // namespace geos.operation.overlay
-} // namespace geos.operation
-} // namespace geos
-
diff --git a/Sources/geos/src/operation/overlay/PointBuilder.cpp b/Sources/geos/src/operation/overlay/PointBuilder.cpp
deleted file mode 100644
index 0c70e93..0000000
--- a/Sources/geos/src/operation/overlay/PointBuilder.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-/**********************************************************************
- *
- * GEOS - Geometry Engine Open Source
- * http://geos.osgeo.org
- *
- * Copyright (C) 2001-2002 Vivid Solutions Inc.
- *
- * This is free software; you can redistribute and/or modify it under
- * the terms of the GNU Lesser General Public Licence as published
- * by the Free Software Foundation.
- * See the COPYING file for more information.
- *
- ***********************************************************************
- *
- * Last port: operation/overlay/PointBuilder.java rev. 1.16 (JTS-1.10)
- *
- **********************************************************************/
-
-#include 
-#include 
-
-#include 
-#include 
-#include 
-
-#include 
-#include 
-
-#ifndef GEOS_DEBUG
-#define GEOS_DEBUG 0
-#endif
-
-
-using namespace geos::geomgraph;
-using namespace geos::geom;
-
-namespace geos {
-namespace operation { // geos.operation
-namespace overlay { // geos.operation.overlay
-
-
-/*
- * @return a list of the Points in the result of the specified
- * overlay operation
- */
-std::vector*
-PointBuilder::build(OverlayOp::OpCode opCode)
-{
-    extractNonCoveredResultNodes(opCode);
-    return resultPointList;
-}
-
-/*
- * Determines nodes which are in the result, and creates Point for them.
- *
- * This method determines nodes which are candidates for the result via their
- * labelling and their graph topology.
- *
- * @param opCode the overlay operation
- */
-void
-PointBuilder::extractNonCoveredResultNodes(OverlayOp::OpCode opCode)
-{
-    auto& nodeMap = op->getGraph().getNodeMap()->nodeMap;
-    for(auto& entry : nodeMap) {
-        Node* n = entry.second;
-
-        // filter out nodes which are known to be in the result
-        if(n->isInResult()) {
-            continue;
-        }
-
-        // if an incident edge is in the result, then
-        // the node coordinate is included already
-        if(n->isIncidentEdgeInResult()) {
-            continue;
-        }
-
-        if(n->getEdges()->getDegree() == 0 ||
-                opCode == OverlayOp::opINTERSECTION) {
-
-            /*
-             * For nodes on edges, only INTERSECTION can result
-             * in edge nodes being included even
-             * if none of their incident edges are included
-             */
-            const Label& label = n->getLabel();
-            if(OverlayOp::isResultOfOp(label, opCode)) {
-                filterCoveredNodeToPoint(n);
-            }
-        }
-    }
-}
-
-void
-PointBuilder::filterCoveredNodeToPoint(const Node* n)
-{
-    const Coordinate& coord = n->getCoordinate();
-    if(!op->isCoveredByLA(coord)) {
-        Point* pt = geometryFactory->createPoint(coord);
-        resultPointList->push_back(pt);
-    }
-}
-
-} // namespace geos.operation.overlay
-} // namespace geos.operation
-} // namespace geos
-
diff --git a/Sources/geos/src/operation/overlay/PolygonBuilder.cpp b/Sources/geos/src/operation/overlay/PolygonBuilder.cpp
index 49d6806..e0223b4 100644
--- a/Sources/geos/src/operation/overlay/PolygonBuilder.cpp
+++ b/Sources/geos/src/operation/overlay/PolygonBuilder.cpp
@@ -18,17 +18,16 @@
  **********************************************************************/
 
 #include 
-#include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -88,12 +87,11 @@ PolygonBuilder::add(PlanarGraph* graph)
         dirEdges[i] = de;
     }
 
-    NodeMap::container& nodeMap = graph->getNodeMap()->nodeMap;
+    const auto& nodeMap = graph->getNodeMap()->nodeMap;
     std::vector nodes;
     nodes.reserve(nodeMap.size());
-    for(NodeMap::iterator it = nodeMap.begin(), itEnd = nodeMap.end();
-            it != itEnd; ++it) {
-        Node* node = it->second;
+    for(const auto& nodeIt: nodeMap) {
+        Node* node = nodeIt.second.get();
         nodes.push_back(node);
     }
 
@@ -131,10 +129,10 @@ PolygonBuilder::add(const std::vector* dirEdges,
 }
 
 /*public*/
-std::vector*
+std::vector>
 PolygonBuilder::getPolygons()
 {
-    std::vector* resultPolyList = computePolygons(shellList);
+    std::vector> resultPolyList = computePolygons(shellList);
     return resultPolyList;
 }
 
@@ -361,19 +359,19 @@ PolygonBuilder::findEdgeRingContaining(EdgeRing* testEr,
 }
 
 /*private*/
-std::vector*
+std::vector>
 PolygonBuilder::computePolygons(std::vector& newShellList)
 {
 #if GEOS_DEBUG
     std::cerr << "PolygonBuilder::computePolygons: got " << newShellList.size() << " shells" << std::endl;
 #endif
-    std::vector* resultPolyList = new std::vector();
+    std::vector> resultPolyList;
 
     // add Polygons for all shells
     for(std::size_t i = 0, n = newShellList.size(); i < n; i++) {
         EdgeRing* er = newShellList[i];
-        Polygon* poly = er->toPolygon(geometryFactory).release();
-        resultPolyList->push_back(poly);
+        auto poly = er->toPolygon(geometryFactory);
+        resultPolyList.push_back(std::move(poly));
     }
     return resultPolyList;
 }
diff --git a/Sources/geos/src/operation/overlay/snap/GeometrySnapper.cpp b/Sources/geos/src/operation/overlay/snap/GeometrySnapper.cpp
index 43c78d0..a2aed6e 100644
--- a/Sources/geos/src/operation/overlay/snap/GeometrySnapper.cpp
+++ b/Sources/geos/src/operation/overlay/snap/GeometrySnapper.cpp
@@ -25,7 +25,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -59,13 +58,10 @@ class SnapTransformer: public geos::geom::util::GeometryTransformer {
         using std::unique_ptr;
 
         assert(srcPts);
-        std::vector coords;
-        srcPts->toVector(coords);
-        LineStringSnapper snapper(coords, snapTol);
-        std::unique_ptr newPts = snapper.snapTo(snapPts);
-
-        const CoordinateSequenceFactory* cfact = factory->getCoordinateSequenceFactory();
-        return std::unique_ptr(cfact->create(newPts.release()));
+        auto coords = detail::make_unique();
+        coords->add(*srcPts);
+        LineStringSnapper snapper(*coords, snapTol);
+        return snapper.snapTo(snapPts);
     }
 
 public:
diff --git a/Sources/geos/src/operation/overlay/snap/LineStringSnapper.cpp b/Sources/geos/src/operation/overlay/snap/LineStringSnapper.cpp
index 610cb90..d196414 100644
--- a/Sources/geos/src/operation/overlay/snap/LineStringSnapper.cpp
+++ b/Sources/geos/src/operation/overlay/snap/LineStringSnapper.cpp
@@ -52,7 +52,7 @@ namespace overlay { // geos.operation.overlay
 namespace snap { // geos.operation.overlay.snap
 
 /*public*/
-std::unique_ptr
+std::unique_ptr
 LineStringSnapper::snapTo(const geom::Coordinate::ConstVect& snapPts)
 {
     geom::CoordinateList coordList(srcPts);
@@ -60,7 +60,7 @@ LineStringSnapper::snapTo(const geom::Coordinate::ConstVect& snapPts)
     snapVertices(coordList, snapPts);
     snapSegments(coordList, snapPts);
 
-    return coordList.toCoordinateArray();
+    return coordList.toCoordinateSequence();
 }
 
 /*private*/
diff --git a/Sources/geos/src/operation/overlay/snap/SnapOverlayOp.cpp b/Sources/geos/src/operation/overlay/snap/SnapOverlayOp.cpp
index 4150657..0272cbc 100644
--- a/Sources/geos/src/operation/overlay/snap/SnapOverlayOp.cpp
+++ b/Sources/geos/src/operation/overlay/snap/SnapOverlayOp.cpp
@@ -43,18 +43,16 @@ SnapOverlayOp::computeSnapTolerance()
 {
     snapTolerance = GeometrySnapper::computeOverlaySnapTolerance(geom0,
                     geom1);
-
-    // std::size_t << "Snap tol = " <<  snapTolerance << std::endl;
 }
 
 /* public */
 std::unique_ptr
-SnapOverlayOp::getResultGeometry(OverlayOp::OpCode opCode)
+SnapOverlayOp::getResultGeometry(int opCode)
 {
     geom::GeomPtrPair prepGeom;
     snap(prepGeom);
-    GeomPtr result(OverlayOp::overlayOp(prepGeom.first.get(),
-                                        prepGeom.second.get(), opCode));
+    auto result = overlayng::OverlayNG::overlay(prepGeom.first.get(), prepGeom.second.get(), opCode);
+
     prepareResult(*result);
     return result;
 }
@@ -72,13 +70,6 @@ SnapOverlayOp::snap(geom::GeomPtrPair& snapGeom)
     // MD - may want to do this at some point, but it adds cycles
 //    checkValid(snapGeom[0]);
 //    checkValid(snapGeom[1]);
-
-    /*
-    System.out.println("Snapped geoms: ");
-    System.out.println(snapGeom[0]);
-    System.out.println(snapGeom[1]);
-    */
-
 }
 
 /* private */
diff --git a/Sources/geos/src/operation/overlay/validate/OverlayResultValidator.cpp b/Sources/geos/src/operation/overlay/validate/OverlayResultValidator.cpp
index 82204de..ac2de54 100644
--- a/Sources/geos/src/operation/overlay/validate/OverlayResultValidator.cpp
+++ b/Sources/geos/src/operation/overlay/validate/OverlayResultValidator.cpp
@@ -20,10 +20,10 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
-#include 
 
 #include 
 #include 
@@ -46,7 +46,6 @@
 
 
 using namespace geos::geom;
-using namespace geos::geomgraph;
 using namespace geos::algorithm;
 
 namespace geos {
@@ -54,32 +53,11 @@ namespace operation { // geos.operation
 namespace overlay { // geos.operation.overlay
 namespace validate { // geos.operation.overlay.validate
 
-namespace { // anonymous namespace
-
-#if GEOS_DEBUG
-std::unique_ptr
-toMultiPoint(std::vector& coords)
-{
-    const GeometryFactory& gf = *(GeometryFactory::getDefaultInstance());
-    const CoordinateSequenceFactory& csf =
-        *(gf.getCoordinateSequenceFactory());
-
-    std::unique_ptr< std::vector > nc(new std::vector(coords));
-    std::unique_ptr cs(csf.create(nc.release()));
-
-    std::unique_ptr mp(gf.createMultiPoint(*cs));
-
-    return mp;
-}
-#endif
-
-} // anonymous namespace
-
 
 /* static public */
 bool
 OverlayResultValidator::isValid(const Geometry& geom0, const Geometry& geom1,
-                                OverlayOp::OpCode opCode,
+                                int opCode,
                                 const Geometry& result)
 {
     OverlayResultValidator validator(geom0, geom1, result);
@@ -107,7 +85,7 @@ OverlayResultValidator::OverlayResultValidator(
 
 /*public*/
 bool
-OverlayResultValidator::isValid(OverlayOp::OpCode overlayOp)
+OverlayResultValidator::isValid(int overlayOp)
 {
 
     addTestPts(g0);
@@ -115,15 +93,6 @@ OverlayResultValidator::isValid(OverlayOp::OpCode overlayOp)
     addTestPts(gres);
 
     if(! testValid(overlayOp)) {
-#if GEOS_DEBUG
-        std::cerr << "OverlayResultValidator:" << std::endl
-             << "Points:" << *toMultiPoint(testCoords) << std::endl
-             << "Geom0: " << g0 << std::endl
-             << "Geom1: " << g1 << std::endl
-             << "Reslt: " << gres << std::endl
-             << "Locat: " << getInvalidLocation()
-             << std::endl;
-#endif
         return false;
     }
 
@@ -156,7 +125,7 @@ OverlayResultValidator::addVertices(const Geometry& g)
 
 /*private*/
 bool
-OverlayResultValidator::testValid(OverlayOp::OpCode overlayOp)
+OverlayResultValidator::testValid(int overlayOp)
 {
     for(std::size_t i = 0, n = testCoords.size(); i < n; ++i) {
         Coordinate& pt = testCoords[i];
@@ -170,7 +139,7 @@ OverlayResultValidator::testValid(OverlayOp::OpCode overlayOp)
 
 /*private*/
 bool
-OverlayResultValidator::testValid(OverlayOp::OpCode overlayOp,
+OverlayResultValidator::testValid(int overlayOp,
                                   const Coordinate& pt)
 {
     // TODO use std::array ?
@@ -204,11 +173,11 @@ OverlayResultValidator::testValid(OverlayOp::OpCode overlayOp,
 
 /* private */
 bool
-OverlayResultValidator::isValidResult(OverlayOp::OpCode overlayOp,
+OverlayResultValidator::isValidResult(int overlayOp,
                                       std::vector& location)
 {
-    bool expectedInterior = OverlayOp::isResultOfOp(location[0],
-                            location[1], overlayOp);
+    bool expectedInterior = overlayng::OverlayNG::isResultOfOp(overlayOp, location[0],
+                            location[1]);
 
     bool resultInInterior = (location[2] == Location::INTERIOR);
 
diff --git a/Sources/geos/src/operation/overlayng/CoverageUnion.cpp b/Sources/geos/src/operation/overlayng/CoverageUnion.cpp
index 915070c..c0c8a14 100644
--- a/Sources/geos/src/operation/overlayng/CoverageUnion.cpp
+++ b/Sources/geos/src/operation/overlayng/CoverageUnion.cpp
@@ -15,11 +15,14 @@
 #include 
 
 #include 
+#include 
 #include 
 #include 
+#include 
 
 using geos::geom::Geometry;
 using geos::noding::SegmentExtractingNoder;
+using geos::noding::BoundaryChainNoder;
 
 namespace geos {      // geos
 namespace operation { // geos.operation
@@ -30,10 +33,27 @@ namespace overlayng { // geos.operation.overlayng
 std::unique_ptr
 CoverageUnion::geomunion(const Geometry* coverage)
 {
-    SegmentExtractingNoder sen;
+    double area_in = coverage->getArea();
+    std::unique_ptr result;
 
     // a precision model is not needed since no noding is done
-    return OverlayNG::geomunion(coverage, nullptr, &sen);
+    //-- linear networks require a segment-extracting noder
+    if (coverage->getDimension() < 2) {
+        SegmentExtractingNoder sen;
+        result = OverlayNG::geomunion(coverage, nullptr, &sen);
+    }
+    else {
+        BoundaryChainNoder bcn;
+        result = OverlayNG::geomunion(coverage, nullptr, &bcn);
+    }
+
+    double area_out = result->getArea();
+
+    if (std::abs((area_out - area_in)/area_in) > AREA_PCT_DIFF_TOL) {
+        throw geos::util::TopologyException("CoverageUnion cannot process overlapping inputs.");
+    }
+
+    return result;
 }
 
 
diff --git a/Sources/geos/src/operation/overlayng/Edge.cpp b/Sources/geos/src/operation/overlayng/Edge.cpp
index ec95716..fce8b1e 100644
--- a/Sources/geos/src/operation/overlayng/Edge.cpp
+++ b/Sources/geos/src/operation/overlayng/Edge.cpp
@@ -27,14 +27,14 @@ using namespace geos::geom;
 using geos::util::GEOSException;
 
 /*public*/
-Edge::Edge(CoordinateSequence* p_pts, const EdgeSourceInfo* info)
+Edge::Edge(std::unique_ptr&& p_pts, const EdgeSourceInfo* info)
     : aDim(OverlayLabel::DIM_UNKNOWN)
     , aDepthDelta(0)
     , aIsHole(false)
     , bDim(OverlayLabel::DIM_UNKNOWN)
     , bDepthDelta(0)
     , bIsHole(false)
-    , pts(p_pts)
+    , pts(std::move(p_pts))
 {
     copyInfo(info);
 }
diff --git a/Sources/geos/src/operation/overlayng/EdgeNodingBuilder.cpp b/Sources/geos/src/operation/overlayng/EdgeNodingBuilder.cpp
index 478b62b..fd3fd85 100644
--- a/Sources/geos/src/operation/overlayng/EdgeNodingBuilder.cpp
+++ b/Sources/geos/src/operation/overlayng/EdgeNodingBuilder.cpp
@@ -80,6 +80,9 @@ EdgeNodingBuilder::setClipEnvelope(const Envelope* p_clipEnv)
 std::vector
 EdgeNodingBuilder::build(const Geometry* geom0, const Geometry* geom1)
 {
+    inputHasZ = geom0->hasZ() || (geom1 != nullptr && geom1->hasZ());
+    inputHasM = geom0->hasM() || (geom1 != nullptr && geom1->hasM());
+
     add(geom0, 0);
     add(geom1, 1);
     std::vector nodedEdges = node(inputEdges.get());
@@ -226,7 +229,7 @@ EdgeNodingBuilder::addPolygonRing(const LinearRing* ring, bool isHole, uint8_t g
     if (isClippedCompletely(ring->getEnvelopeInternal()))
       return;
 
-    std::unique_ptr pts = clip(ring);
+    std::unique_ptr pts = clip(ring);
 
     /**
     * Don't add edges that collapse to a point
@@ -261,34 +264,25 @@ EdgeNodingBuilder::createEdgeSourceInfo(uint8_t index, int depthDelta, bool isHo
 
 /*private*/
 void
-EdgeNodingBuilder::addEdge(std::unique_ptr& cas, const EdgeSourceInfo* info)
+EdgeNodingBuilder::addEdge(std::unique_ptr& cas, const EdgeSourceInfo* info)
 {
     // TODO: manage these internally to EdgeNodingBuilder in a std::deque,
     // since they do not have a life span longer than the EdgeNodingBuilder
     // in OverlayNG::buildGraph()
-    NodedSegmentString* ss = new NodedSegmentString(cas.release(), reinterpret_cast(info));
-    inputEdges->push_back(ss);
-}
-
-/*private*/
-void
-EdgeNodingBuilder::addEdge(std::unique_ptr> pts, const EdgeSourceInfo* info)
-{
-    CoordinateArraySequence* cas = new CoordinateArraySequence(pts.release());
-    NodedSegmentString* ss = new NodedSegmentString(cas, reinterpret_cast(info));
+    NodedSegmentString* ss = new NodedSegmentString(cas.release(), inputHasZ, inputHasM, reinterpret_cast(info));
     inputEdges->push_back(ss);
 }
 
 /*private*/
 bool
-EdgeNodingBuilder::isClippedCompletely(const Envelope* env)
+EdgeNodingBuilder::isClippedCompletely(const Envelope* env) const
 {
     if (clipEnv == nullptr) return false;
     return clipEnv->disjoint(env);
 }
 
 /* private */
-std::unique_ptr
+std::unique_ptr
 EdgeNodingBuilder::clip(const LinearRing* ring)
 {
     const Envelope* env = ring->getEnvelopeInternal();
@@ -305,7 +299,7 @@ EdgeNodingBuilder::clip(const LinearRing* ring)
 }
 
 /*private*/
-std::unique_ptr
+std::unique_ptr
 EdgeNodingBuilder::removeRepeatedPoints(const LineString* line)
 {
     const CoordinateSequence* pts = line->getCoordinatesRO();
@@ -358,20 +352,20 @@ EdgeNodingBuilder::addLine(const LineString* line, uint8_t geomIndex)
         return;
 
     if (isToBeLimited(line)) {
-        std::vector>& sections = limit(line);
+        std::vector>& sections = limit(line);
         for (auto& pts : sections) {
             addLine(pts, geomIndex);
         }
     }
     else {
-        std::unique_ptr ptsNoRepeat = removeRepeatedPoints(line);
+        std::unique_ptr ptsNoRepeat = removeRepeatedPoints(line);
         addLine(ptsNoRepeat, geomIndex);
     }
 }
 
 /*private*/
 void
-EdgeNodingBuilder::addLine(std::unique_ptr& pts, uint8_t geomIndex)
+EdgeNodingBuilder::addLine(std::unique_ptr& pts, uint8_t geomIndex)
 {
     /**
      * Don't add edges that collapse to a point
@@ -402,7 +396,7 @@ EdgeNodingBuilder::isToBeLimited(const LineString* line) const
 }
 
 /*private*/
-std::vector>&
+std::vector>&
 EdgeNodingBuilder::limit(const LineString* line)
 {
     const CoordinateSequence* pts = line->getCoordinatesRO();
diff --git a/Sources/geos/src/operation/overlayng/ElevationModel.cpp b/Sources/geos/src/operation/overlayng/ElevationModel.cpp
index 57e2223..af35df5 100644
--- a/Sources/geos/src/operation/overlayng/ElevationModel.cpp
+++ b/Sources/geos/src/operation/overlayng/ElevationModel.cpp
@@ -113,7 +113,7 @@ ElevationModel::add(const Geometry& geom)
         void filter_ro(const geom::CoordinateSequence& seq, std::size_t i) override
         {
             if (! seq.hasZ()) {
-                hasZ = false;;
+                hasZ = false;
                 return;
             }
             const Coordinate& c = seq.getAt(i);
diff --git a/Sources/geos/src/operation/overlayng/IndexedPointOnLineLocator.cpp b/Sources/geos/src/operation/overlayng/IndexedPointOnLineLocator.cpp
index f0cbd5b..95a7e12 100644
--- a/Sources/geos/src/operation/overlayng/IndexedPointOnLineLocator.cpp
+++ b/Sources/geos/src/operation/overlayng/IndexedPointOnLineLocator.cpp
@@ -28,7 +28,7 @@ namespace overlayng { // geos.operation.overlayng
 
 /*public*/
 geom::Location
-IndexedPointOnLineLocator::locate(const geom::Coordinate* p) {
+IndexedPointOnLineLocator::locate(const geom::CoordinateXY* p) {
     // TODO: optimize this with a segment index
     algorithm::PointLocator locator;
     return locator.locate(*p, &inputGeom);
diff --git a/Sources/geos/src/operation/overlayng/LineBuilder.cpp b/Sources/geos/src/operation/overlayng/LineBuilder.cpp
index 47188d2..a676b36 100644
--- a/Sources/geos/src/operation/overlayng/LineBuilder.cpp
+++ b/Sources/geos/src/operation/overlayng/LineBuilder.cpp
@@ -18,7 +18,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 
 
@@ -179,9 +178,15 @@ std::unique_ptr
 LineBuilder::toLine(OverlayEdge* edge) const
 {
     // bool isForward = edge->isForward();
-    std::unique_ptr pts(new CoordinateArraySequence());
+    const auto* edgePts = edge->getCoordinatesRO();
+
+    std::unique_ptr pts(new CoordinateSequence(0u, edgePts->hasZ(), edgePts->hasM()));
+    pts->reserve(edgePts->size());
     pts->add(edge->orig(), false);
     edge->addCoordinates(pts.get());
+
+    assert(pts->size() == edgePts->size());
+
     return geometryFactory->createLineString(std::move(pts));
 }
 
@@ -241,7 +246,7 @@ LineBuilder::buildLine(OverlayEdge* node)
 {
     // assert: edgeStart degree = 1
     // assert: edgeStart direction = forward
-    std::unique_ptr pts(new CoordinateArraySequence());
+    std::unique_ptr pts(new CoordinateSequence());
     pts->add(node->orig(), false);
 
     bool isNodeForward = node->isForward();
@@ -261,7 +266,7 @@ LineBuilder::buildLine(OverlayEdge* node)
     while (e != nullptr);
     // reverse coordinates before constructing
     if(!isNodeForward) {
-        CoordinateSequence::reverse(pts.get());
+        pts->reverse();
     }
 
     return geometryFactory->createLineString(std::move(pts));
diff --git a/Sources/geos/src/operation/overlayng/LineLimiter.cpp b/Sources/geos/src/operation/overlayng/LineLimiter.cpp
index c51b9bd..1c8106c 100644
--- a/Sources/geos/src/operation/overlayng/LineLimiter.cpp
+++ b/Sources/geos/src/operation/overlayng/LineLimiter.cpp
@@ -16,6 +16,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include 
 
@@ -24,7 +25,7 @@ namespace operation { // geos.operation
 namespace overlayng { // geos.operation.overlayng
 
 /*public*/
-std::vector>&
+std::vector>&
 LineLimiter::limit(const CoordinateSequence *pts)
 {
     // Reset for new limit run
@@ -51,7 +52,7 @@ void
 LineLimiter::addPoint(const Coordinate* p)
 {
     startSection();
-    ptList->emplace_back(*p);
+    ptList->add(*p, false);
 }
 
 /*private*/
@@ -96,11 +97,11 @@ void
 LineLimiter::startSection()
 {
     if (!isSectionOpen()) {
-        ptList.reset(new std::vector);
+        ptList = detail::make_unique();
     }
 
     if (lastOutside != nullptr) {
-        ptList->emplace_back(*lastOutside);
+        ptList->add(*lastOutside, false);
     }
     lastOutside = nullptr;
 }
@@ -114,16 +115,15 @@ LineLimiter::finishSection()
 
     // finish off this section
     if (lastOutside != nullptr) {
-        ptList->emplace_back(*lastOutside);
+        ptList->add(*lastOutside, false);
         lastOutside = nullptr;
     }
 
     // remove repeated points from the section
-    ptList->erase(std::unique(ptList->begin(), ptList->end()), ptList->end());
+    assert(!ptList->hasRepeatedPoints());
+    //ptList->erase(std::unique(ptList->begin(), ptList->end()), ptList->end());
 
-    // std::unique_ptr cas();
-    CoordinateArraySequence* cas = new CoordinateArraySequence(ptList.release());
-    sections.emplace_back(cas);
+    sections.emplace_back(ptList.release());
     ptList.reset(nullptr);
 }
 
diff --git a/Sources/geos/src/operation/overlayng/MaximalEdgeRing.cpp b/Sources/geos/src/operation/overlayng/MaximalEdgeRing.cpp
index 6182d7b..84b73e3 100644
--- a/Sources/geos/src/operation/overlayng/MaximalEdgeRing.cpp
+++ b/Sources/geos/src/operation/overlayng/MaximalEdgeRing.cpp
@@ -203,7 +203,7 @@ MaximalEdgeRing::linkMaxInEdge(OverlayEdge* currOut, OverlayEdge* currMaxRingOut
 std::ostream&
 operator<<(std::ostream& os, const MaximalEdgeRing& mer)
 {
-    CoordinateArraySequence coords;
+    CoordinateSequence coords;
     OverlayEdge* edge = mer.startEdge;
     do {
         coords.add(edge->orig());
diff --git a/Sources/geos/src/operation/overlayng/OverlayEdge.cpp b/Sources/geos/src/operation/overlayng/OverlayEdge.cpp
index c9e1917..a7e95c1 100644
--- a/Sources/geos/src/operation/overlayng/OverlayEdge.cpp
+++ b/Sources/geos/src/operation/overlayng/OverlayEdge.cpp
@@ -18,12 +18,11 @@
 #include 
 #include 
 #include 
-#include 
+#include 
 
 
 using geos::geom::CoordinateSequence;
 using geos::geom::Coordinate;
-using geos::geom::CoordinateArraySequence;
 using geos::geom::Location;
 
 
@@ -39,7 +38,7 @@ OverlayEdge::getCoordinatesOriented()
         return pts->clone();
     }
     std::unique_ptr ptsCopy = pts->clone();
-    CoordinateSequence::reverse(ptsCopy.get());
+    ptsCopy->reverse();
     return ptsCopy;
 }
 
@@ -54,7 +53,7 @@ OverlayEdge::getCoordinatesOriented()
 */
 /*public*/
 void
-OverlayEdge::addCoordinates(CoordinateArraySequence* coords) const
+OverlayEdge::addCoordinates(CoordinateSequence* coords) const
 {
     bool isFirstEdge = coords->size() > 0;
     if (direction) {
@@ -62,9 +61,7 @@ OverlayEdge::addCoordinates(CoordinateArraySequence* coords) const
         if (isFirstEdge) {
             startIndex = 0;
         }
-        for (std::size_t i = startIndex, sz = pts->size(); i < sz; i++) {
-            coords->add(pts->getAt(i), false);
-        }
+        coords->add(*pts, startIndex, pts->size() - 1, false);
     }
     else { // is backward
         int startIndex = (int)(pts->size()) - 2;
@@ -72,7 +69,7 @@ OverlayEdge::addCoordinates(CoordinateArraySequence* coords) const
             startIndex = (int)(pts->size()) - 1;
         }
         for (int i = startIndex; i >= 0; i--) {
-            coords->add(pts->getAt(static_cast(i)), false);
+            coords->add(*pts, static_cast(i), static_cast(i), false);
         }
     }
 }
diff --git a/Sources/geos/src/operation/overlayng/OverlayEdgeRing.cpp b/Sources/geos/src/operation/overlayng/OverlayEdgeRing.cpp
index 0949c2d..a1b3d1b 100644
--- a/Sources/geos/src/operation/overlayng/OverlayEdgeRing.cpp
+++ b/Sources/geos/src/operation/overlayng/OverlayEdgeRing.cpp
@@ -41,7 +41,7 @@ OverlayEdgeRing::OverlayEdgeRing(OverlayEdge* start, const GeometryFactory* geom
     , locator(nullptr)
     , shell(nullptr)
 {
-    auto ringPts = detail::make_unique();
+    auto ringPts = detail::make_unique(0u, start->getCoordinatesRO()->hasZ(), start->getCoordinatesRO()->hasM());
     computeRingPts(start, *ringPts);
     computeRing(std::move(ringPts), geometryFactory);
 }
@@ -118,7 +118,7 @@ OverlayEdgeRing::addHole(OverlayEdgeRing* p_ring)
 }
 
 void
-OverlayEdgeRing::closeRing(CoordinateArraySequence& pts)
+OverlayEdgeRing::closeRing(CoordinateSequence& pts)
 {
     if(pts.size() > 0) {
         pts.add(pts.getAt(0), false);
@@ -127,7 +127,7 @@ OverlayEdgeRing::closeRing(CoordinateArraySequence& pts)
 
 /*private*/
 void
-OverlayEdgeRing::computeRingPts(OverlayEdge* start, CoordinateArraySequence& pts)
+OverlayEdgeRing::computeRingPts(OverlayEdge* start, CoordinateSequence& pts)
 {
     OverlayEdge* edge = start;
     do {
@@ -148,7 +148,7 @@ OverlayEdgeRing::computeRingPts(OverlayEdge* start, CoordinateArraySequence& pts
 
 /*private*/
 void
-OverlayEdgeRing::computeRing(std::unique_ptr && p_ringPts, const GeometryFactory* geometryFactory)
+OverlayEdgeRing::computeRing(std::unique_ptr && p_ringPts, const GeometryFactory* geometryFactory)
 {
     if (ring != nullptr) return;   // don't compute more than once
     ring = geometryFactory->createLinearRing(std::move(p_ringPts));
@@ -162,10 +162,10 @@ OverlayEdgeRing::computeRing(std::unique_ptr && p_ringP
 * @return an array of the {@link Coordinate}s in this ring
 */
 /*private*/
-const CoordinateArraySequence&
+const CoordinateSequence&
 OverlayEdgeRing::getCoordinates()
 {
-    return *(detail::down_cast(ring->getCoordinatesRO()));
+    return *(detail::down_cast(ring->getCoordinatesRO()));
 }
 
 /**
@@ -253,14 +253,17 @@ OverlayEdgeRing::getCoordinate()
 std::unique_ptr
 OverlayEdgeRing::toPolygon(const GeometryFactory* factory)
 {
-    std::vector> holeLR;
-    if (holes.size() > 0) {
+    if (holes.empty()) {
+        return factory->createPolygon(std::move(ring));
+    } else {
+        std::vector> holeLR(holes.size());
+
         for (std::size_t i = 0; i < holes.size(); i++) {
-            std::unique_ptr r = holes[i]->getRing();
-            holeLR.push_back(std::move(r));
+            holeLR[i] = holes[i]->getRing();
         }
+
+        return factory->createPolygon(std::move(ring), std::move(holeLR));
     }
-    return factory->createPolygon(std::move(ring), std::move(holeLR));
 }
 
 /*public*/
diff --git a/Sources/geos/src/operation/overlayng/OverlayGraph.cpp b/Sources/geos/src/operation/overlayng/OverlayGraph.cpp
index 2033c19..01ce395 100644
--- a/Sources/geos/src/operation/overlayng/OverlayGraph.cpp
+++ b/Sources/geos/src/operation/overlayng/OverlayGraph.cpp
@@ -47,7 +47,8 @@ std::vector
 OverlayGraph::getNodeEdges()
 {
     std::vector nodeEdges;
-    for (auto nodeMapPair : nodeMap) {
+    nodeEdges.reserve(nodeMap.size());
+    for (const auto& nodeMapPair : nodeMap) {
         nodeEdges.push_back(nodeMapPair.second);
     }
     return nodeEdges;
@@ -57,7 +58,7 @@ OverlayGraph::getNodeEdges()
 OverlayEdge*
 OverlayGraph::getNodeEdge(const Coordinate& nodePt) const
 {
-    auto it = nodeMap.find(nodePt);
+    const auto& it = nodeMap.find(nodePt);
     if (it == nodeMap.end()) {
         return nullptr;
     }
@@ -107,17 +108,18 @@ OverlayGraph::createEdgePair(const CoordinateSequence *pts, OverlayLabel *lbl)
 OverlayEdge*
 OverlayGraph::createOverlayEdge(const CoordinateSequence* pts, OverlayLabel* lbl, bool direction)
 {
-    Coordinate origin;
-    Coordinate dirPt;
+    CoordinateXYZM origin;
+    CoordinateXYZM dirPt;
+
     if (direction) {
-        origin = pts->getAt(0);
-        dirPt = pts->getAt(1);
+        pts->getAt(0, origin);
+        pts->getAt(1, dirPt);
     }
     else {
         assert(pts->size() > 0);
         std::size_t ilast = pts->size() - 1;
-        origin = pts->getAt(ilast);
-        dirPt = pts->getAt(ilast-1);
+        pts->getAt(ilast, origin);
+        pts->getAt(ilast-1, dirPt);
     }
     ovEdgeQue.emplace_back(origin, dirPt, direction, lbl, pts);
     OverlayEdge& ove = ovEdgeQue.back();
@@ -128,7 +130,7 @@ OverlayGraph::createOverlayEdge(const CoordinateSequence* pts, OverlayLabel* lbl
 OverlayLabel*
 OverlayGraph::createOverlayLabel(const Edge* edge)
 {
-    // Instantate OverlayLabel on the std::deque
+    // Instantiate OverlayLabel on the std::deque
     ovLabelQue.emplace_back();
     // Read back a reference
     OverlayLabel& ovl = ovLabelQue.back();
@@ -149,7 +151,7 @@ OverlayGraph::insert(OverlayEdge* e)
      * insert the edge into the star of edges around the node.
      * Otherwise, add a new node for the origin.
      */
-    auto it = nodeMap.find(e->orig());
+    const auto& it = nodeMap.find(e->orig());
     if (it != nodeMap.end()) {
         // found in map
         OverlayEdge* nodeEdge = it->second;
@@ -166,7 +168,7 @@ operator<<(std::ostream& os, const OverlayGraph& og)
 {
     os << "OGRPH " << std::endl << "NODEMAP [" << og.nodeMap.size() << "]";
     // pair
-    for (auto& pr: og.nodeMap) {
+    for (const auto& pr: og.nodeMap) {
         os << std::endl << " ";
         os << pr.first << " ";
         os << *(pr.second);
@@ -174,7 +176,7 @@ operator<<(std::ostream& os, const OverlayGraph& og)
     os << std::endl;
     os << "EDGES [" << og.edges.size() << "]";
     // pair
-    for (auto& e: og.edges) {
+    for (const auto& e: og.edges) {
         os << std::endl << " ";
         os << *e << " ";
     }
diff --git a/Sources/geos/src/operation/overlayng/OverlayMixedPoints.cpp b/Sources/geos/src/operation/overlayng/OverlayMixedPoints.cpp
index 87befe5..1285186 100644
--- a/Sources/geos/src/operation/overlayng/OverlayMixedPoints.cpp
+++ b/Sources/geos/src/operation/overlayng/OverlayMixedPoints.cpp
@@ -16,8 +16,9 @@
 
 #include 
 #include 
-#include 
+#include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -27,6 +28,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 
 namespace geos {      // geos
@@ -39,9 +41,9 @@ using namespace geos::geom;
  * @brief Extracts and rounds coordinates from a geometry
  *
  */
-class CoordinateExtractingFilter: public geom::CoordinateFilter {
+class CoordinateExtractingFilter: public geom::CoordinateInspector {
 public:
-    CoordinateExtractingFilter(CoordinateArraySequence& p_pts, const PrecisionModel& p_pm)
+    CoordinateExtractingFilter(CoordinateSequence& p_pts, const PrecisionModel& p_pm)
         : pts(p_pts), pm(p_pm)
     {}
 
@@ -58,23 +60,23 @@ class CoordinateExtractingFilter: public geom::CoordinateFilter {
      * @param coord The "read-only" Coordinate to which
      * 				the filter is applied.
      */
-    void
-    filter_ro(const geom::Coordinate* coord) override
+    template
+    void filter(const CoordType* coord)
     {
-        Coordinate p(*coord);
+        CoordType p(*coord);
         pm.makePrecise(p);
         pts.add(p);
     }
 
 private:
-    CoordinateArraySequence& pts;
+    CoordinateSequence& pts;
     const PrecisionModel& pm;
 };
 
 /*public*/
 OverlayMixedPoints::OverlayMixedPoints(int p_opCode, const Geometry* geom0, const Geometry* geom1, const PrecisionModel* p_pm)
     : opCode(p_opCode)
-    , pm(p_pm)
+    , pm(p_pm ? p_pm : geom0->getPrecisionModel())
     , geometryFactory(geom0->getFactory())
     , resultDim(OverlayUtil::resultDimension(opCode, geom0->getDimension(), geom1->getDimension()))
 {
@@ -109,7 +111,7 @@ OverlayMixedPoints::getResult()
     geomNonPointDim = geomNonPoint->getDimension();
     locator = createLocator(geomNonPoint.get());
 
-    std::unique_ptr coords = extractCoordinates(geomPoint, pm);
+    std::unique_ptr coords = extractCoordinates(geomPoint, pm);
 
     switch (opCode) {
         case OverlayNG::INTERSECTION: {
@@ -124,7 +126,7 @@ OverlayMixedPoints::getResult()
             return computeDifference(coords.get());
         }
     }
-    util::Assert::shouldNeverReachHere("Unknown overlay op code");
+    geos::util::Assert::shouldNeverReachHere("Unknown overlay op code");
     return nullptr;
 }
 
@@ -141,8 +143,8 @@ OverlayMixedPoints::createLocator(const Geometry* p_geomNonPoint)
         return ipoll;
     }
     // never get here
-    std::unique_ptr n(nullptr);
-    return n;
+    // std::unique_ptr n(nullptr);
+    // return n;
 }
 
 
@@ -160,15 +162,15 @@ OverlayMixedPoints::prepareNonPoint(const Geometry* geomInput)
 
 /*private*/
 std::unique_ptr
-OverlayMixedPoints::computeIntersection(const CoordinateArraySequence* coords) const
+OverlayMixedPoints::computeIntersection(const CoordinateSequence* coords) const
 {
-    std::vector> points = findPoints(true, coords);
+    auto&& points = findPoints(true, coords);
     return createPointResult(points);
 }
 
 /*private*/
 std::unique_ptr
-OverlayMixedPoints::computeUnion(const CoordinateArraySequence* coords)
+OverlayMixedPoints::computeUnion(const CoordinateSequence* coords)
 {
     std::vector> resultPointList = findPoints(false, coords);
     std::vector> resultLineList;
@@ -185,7 +187,7 @@ OverlayMixedPoints::computeUnion(const CoordinateArraySequence* coords)
 
 /*private*/
 std::unique_ptr
-OverlayMixedPoints::computeDifference(const CoordinateArraySequence* coords)
+OverlayMixedPoints::computeDifference(const CoordinateSequence* coords)
 {
     if (isPointRHS) {
         return geomNonPoint->clone();
@@ -224,35 +226,41 @@ OverlayMixedPoints::createPointResult(std::vector>& point
 
 /*private*/
 std::vector>
-OverlayMixedPoints::findPoints(bool isCovered, const CoordinateArraySequence* coords) const
+OverlayMixedPoints::findPoints(bool isCovered, const CoordinateSequence* coords) const
 {
-    // use set to remove duplicates
-    std::set resultCoords;
-    // keep only points contained
-    for (std::size_t i = 0; i < coords->size(); i++) {
-        const Coordinate& coord = coords->getAt(i);
-        if (hasLocation(isCovered, coord)) {
-            resultCoords.insert(coord);
+    CoordinateSequence resultCoords(0, coords->hasZ(), coords->hasM());
+
+    coords->forEach([&resultCoords, isCovered, this](const auto& coord) {
+        // keep only points contained
+        if (this->hasLocation(isCovered, coord)) {
+            resultCoords.add(coord);
         }
+    });
+
+    // remove duplicates by sorting and removing repeated points
+    resultCoords.sort();
+    if (resultCoords.hasRepeatedPoints()) {
+        resultCoords = *valid::RepeatedPointRemover::removeRepeatedPoints(&resultCoords);
     }
+
     return createPoints(resultCoords);
 }
 
 /*private*/
 std::vector>
-OverlayMixedPoints::createPoints(std::set& coords) const
+OverlayMixedPoints::createPoints(const CoordinateSequence& coords) const
 {
     std::vector> points;
-    for (const Coordinate& coord : coords) {
-        std::unique_ptr point(geometryFactory->createPoint(coord));
-        points.push_back(std::move(point));
-    }
+    points.reserve(coords.size());
+    coords.forEach([&points, this](const auto& coord) {
+        points.push_back(geometryFactory->createPoint(coord));
+    });
     return points;
 }
 
 /*private*/
 bool
-OverlayMixedPoints::hasLocation(bool isCovered, const Coordinate& coord) const
+OverlayMixedPoints::hasLocation(bool isCovered, const CoordinateXY& coord) const
 {
     bool isExterior = (Location::EXTERIOR == locator->locate(&coord));
     if (isCovered) {
@@ -263,10 +271,11 @@ OverlayMixedPoints::hasLocation(bool isCovered, const Coordinate& coord) const
 
 
 /*private*/
-std::unique_ptr
+std::unique_ptr
 OverlayMixedPoints::extractCoordinates(const Geometry* points, const PrecisionModel* p_pm) const
 {
-    std::unique_ptr coords(new CoordinateArraySequence());
+    auto coords = detail::make_unique(0u, points->hasZ(), points->hasM());
+    coords->reserve(points->getNumPoints());
 
     CoordinateExtractingFilter filter(*coords, *p_pm);
     points->apply_ro(&filter);
diff --git a/Sources/geos/src/operation/overlayng/OverlayNG.cpp b/Sources/geos/src/operation/overlayng/OverlayNG.cpp
index 54101cb..02cfb93 100644
--- a/Sources/geos/src/operation/overlayng/OverlayNG.cpp
+++ b/Sources/geos/src/operation/overlayng/OverlayNG.cpp
@@ -184,8 +184,6 @@ OverlayNG::getResult()
 
 #if GEOS_DEBUG
     io::WKTWriter w;
-    w.setOutputDimension(3);
-    w.setTrim(true);
 
     std::cerr << "Before populatingZ: " << w.write(result.get()) << std::endl;
 #endif
@@ -249,8 +247,8 @@ OverlayNG::computeEdgeOverlay()
     // std::sort(edges.begin(), edges.end(), EdgeComparator);
     OverlayGraph graph;
     for (Edge* e : edges) {
-        // Write out edge graph as hex for examination
-        // std::cout << *e << std::endl;
+        // Write out edge coordinates
+        // std::cout << *e->getCoordinatesRO() << std::endl;
         graph.addEdge(e);
     }
 
diff --git a/Sources/geos/src/operation/overlayng/OverlayNGRobust.cpp b/Sources/geos/src/operation/overlayng/OverlayNGRobust.cpp
index 016eeb3..e9af208 100644
--- a/Sources/geos/src/operation/overlayng/OverlayNGRobust.cpp
+++ b/Sources/geos/src/operation/overlayng/OverlayNGRobust.cpp
@@ -83,6 +83,9 @@ OverlayNGRobust::Union(const Geometry* a)
 std::unique_ptr
 OverlayNGRobust::Overlay(const Geometry* geom0, const Geometry* geom1, int opCode)
 {
+    geos::util::ensureNoCurvedComponents(geom0);
+    geos::util::ensureNoCurvedComponents(geom1);
+
     std::unique_ptr result;
     std::runtime_error exOriginal("");
 
@@ -106,7 +109,9 @@ OverlayNGRobust::Overlay(const Geometry* geom0, const Geometry* geom1, int opCod
      */
     try {
         geom::PrecisionModel PM_FLOAT;
-        // std::cerr << "Using floating point overlay." << std::endl;
+#if GEOS_DEBUG
+        std::cerr << "Using floating point overlay." << std::endl;
+#endif
         result = OverlayNG::overlay(geom0, geom1, opCode, &PM_FLOAT);
 
         // Simple noding with no validation
diff --git a/Sources/geos/src/operation/overlayng/OverlayPoints.cpp b/Sources/geos/src/operation/overlayng/OverlayPoints.cpp
index c328687..6afe7a2 100644
--- a/Sources/geos/src/operation/overlayng/OverlayPoints.cpp
+++ b/Sources/geos/src/operation/overlayng/OverlayPoints.cpp
@@ -27,14 +27,14 @@ namespace geos {      // geos
 namespace operation { // geos.operation
 namespace overlayng { // geos.operation.overlayng
 
-struct PointExtractingFilter: public GeometryComponentFilter {
+struct PointExtractingFilter final: public GeometryComponentFilter {
 
-    PointExtractingFilter(std::map>& p_ptMap, const PrecisionModel* p_pm)
+    PointExtractingFilter(std::map>& p_ptMap, const PrecisionModel* p_pm)
         : ptMap(p_ptMap), pm(p_pm)
     {}
 
     void
-    filter_ro(const Geometry* geom)
+    filter_ro(const Geometry* geom) override
     {
         if (geom->getGeometryTypeId() != GEOS_POINT) return;
 
@@ -42,30 +42,33 @@ struct PointExtractingFilter: public GeometryComponentFilter {
         // don't add empty points
         if (pt->isEmpty()) return;
 
-        Coordinate p = roundCoord(pt, pm);
-        /**
-        * Only add first occurrence of a point.
-        * This provides the merging semantics of overlay
-        */
-        if (ptMap.find(p) == ptMap.end()) {
-            std::unique_ptr newPt(pt->getFactory()->createPoint(p));
-            ptMap[p] = std::move(newPt);
-        }
+        pt->getCoordinatesRO()->forEach([this, &pt](const auto& coord) -> void {
+            auto rounded = roundCoord(coord, pm);
+
+            /**
+            * Only add first occurrence of a point.
+            * This provides the merging semantics of overlay
+            */
+            if (ptMap.find(rounded) == ptMap.end()) {
+                std::unique_ptr newPt(pt->getFactory()->createPoint(rounded));
+                ptMap[rounded] = std::move(newPt);
+            }
+        });
     }
 
-    static Coordinate
-    roundCoord(const Point* pt, const PrecisionModel* p_pm)
+    template
+    static CoordType
+    roundCoord(const CoordType& p, const PrecisionModel* p_pm)
     {
-        const Coordinate* p = pt->getCoordinate();
         if (OverlayUtil::isFloating(p_pm))
-            return *p;
-        Coordinate p2 = *p;
+            return p;
+        CoordType p2(p);
         p_pm->makePrecise(p2);
         return p2;
     }
 
 private:
-    std::map>& ptMap;
+    std::map>& ptMap;
     const PrecisionModel* pm;
 };
 
@@ -82,8 +85,8 @@ OverlayPoints::overlay(int opCode, const Geometry* geom0, const Geometry* geom1,
 std::unique_ptr
 OverlayPoints::getResult()
 {
-    std::map> map0 = buildPointMap(geom0);
-    std::map> map1 = buildPointMap(geom1);
+    PointMap map0 = buildPointMap(geom0);
+    PointMap map1 = buildPointMap(geom1);
 
     std::vector> rsltList;
     switch (opCode) {
@@ -113,8 +116,8 @@ OverlayPoints::getResult()
 
 /*private*/
 void
-OverlayPoints::computeIntersection(std::map>& map0,
-                    std::map>& map1,
+OverlayPoints::computeIntersection(PointMap& map0,
+                    PointMap& map1,
                     std::vector>& rsltList)
 {
     // for each entry in map0
@@ -130,8 +133,8 @@ OverlayPoints::computeIntersection(std::map>&
 
 /*private*/
 void
-OverlayPoints::computeDifference(std::map>& map0,
-                  std::map>& map1,
+OverlayPoints::computeDifference(PointMap& map0,
+                  PointMap& map1,
                   std::vector>& rsltList)
 {
     // for each entry in map0
@@ -147,8 +150,8 @@ OverlayPoints::computeDifference(std::map>& m
 
 /*private*/
 void
-OverlayPoints::computeUnion(std::map>& map0,
-             std::map>& map1,
+OverlayPoints::computeUnion(PointMap& map0,
+             PointMap& map1,
              std::vector>& rsltList)
 {
     // take all map0 points
@@ -168,10 +171,10 @@ OverlayPoints::computeUnion(std::map>& map0,
 }
 
 /*private*/
-std::map>
+std::map>
 OverlayPoints::buildPointMap(const Geometry* geom)
 {
-    std::map> map;
+    std::map> map;
     PointExtractingFilter filter(map, pm);
     geom->apply_ro(&filter);
     return map;
diff --git a/Sources/geos/src/operation/overlayng/OverlayUtil.cpp b/Sources/geos/src/operation/overlayng/OverlayUtil.cpp
index 55dd0b3..94e6387 100644
--- a/Sources/geos/src/operation/overlayng/OverlayUtil.cpp
+++ b/Sources/geos/src/operation/overlayng/OverlayUtil.cpp
@@ -308,8 +308,7 @@ bool
 OverlayUtil::round(const Point* pt, const PrecisionModel* pm, Coordinate& rsltCoord)
 {
     if (pt->isEmpty()) return false;
-    const Coordinate* p = pt->getCoordinate();
-    rsltCoord = *p;
+    pt->getCoordinatesRO()->getAt(0, rsltCoord);
     if (! isFloating(pm)) {
         pm->makePrecise(rsltCoord);
     }
diff --git a/Sources/geos/src/operation/overlayng/PolygonBuilder.cpp b/Sources/geos/src/operation/overlayng/PolygonBuilder.cpp
index 31f16ba..80b8f5b 100644
--- a/Sources/geos/src/operation/overlayng/PolygonBuilder.cpp
+++ b/Sources/geos/src/operation/overlayng/PolygonBuilder.cpp
@@ -45,6 +45,7 @@ std::vector>
 PolygonBuilder::computePolygons(const std::vector& shells) const
 {
     std::vector> resultPolyList;
+    resultPolyList.reserve(shells.size());
     // add Polygons for all shells
     for (OverlayEdgeRing* er : shells) {
         std::unique_ptr poly = er->toPolygon(geometryFactory);
diff --git a/Sources/geos/src/operation/overlayng/RingClipper.cpp b/Sources/geos/src/operation/overlayng/RingClipper.cpp
index c311dd9..6a87d5f 100644
--- a/Sources/geos/src/operation/overlayng/RingClipper.cpp
+++ b/Sources/geos/src/operation/overlayng/RingClipper.cpp
@@ -21,10 +21,10 @@ namespace overlayng { // geos.operation.overlayng
 
 
 /*public*/
-std::unique_ptr
+std::unique_ptr
 RingClipper::clip(const CoordinateSequence* cs) const
 {
-    std::unique_ptr pts;
+    std::unique_ptr pts;
     for (int edgeIndex = 0; edgeIndex < 4; edgeIndex++) {
         bool closeRing = (edgeIndex == 3);
         pts = clipToBoxEdge(cs, edgeIndex, closeRing);
@@ -36,11 +36,11 @@ RingClipper::clip(const CoordinateSequence* cs) const
 }
 
 /*private*/
-std::unique_ptr
+std::unique_ptr
 RingClipper::clipToBoxEdge(const CoordinateSequence* pts, int edgeIndex, bool closeRing) const
 {
     // TODO: is it possible to avoid copying array 4 times?
-    std::unique_ptr ptsClip(new CoordinateArraySequence());
+    std::unique_ptr ptsClip(new CoordinateSequence());
 
     Coordinate p0;
     pts->getAt(pts->size() - 1, p0);
diff --git a/Sources/geos/src/operation/polygonize/BuildArea.cpp b/Sources/geos/src/operation/polygonize/BuildArea.cpp
index ac76a16..37ccd37 100644
--- a/Sources/geos/src/operation/polygonize/BuildArea.cpp
+++ b/Sources/geos/src/operation/polygonize/BuildArea.cpp
@@ -94,11 +94,11 @@ static bool ringsEqualAnyDirection(const LinearRing* r1, const LinearRing* r2)
 
     const Coordinate& firstPoint = cs1->getAt(0);
     size_t offset = CoordinateSequence::indexOf(&firstPoint, cs2);
-    if ( offset == std::numeric_limits::max() ) return false;
+    if ( offset == NO_COORD_INDEX ) return false;
 
     bool equal = true;
 
-    /* Check equals forward (skip first point, we checked it alread) */
+    /* Check equals forward (skip first point, we checked it already) */
     for (size_t i=1; i>& faces) {
 static std::unique_ptr collectFacesWithEvenAncestors(
     const std::vector>& faces) {
     std::vector> geoms;
-    for( auto& face: faces ) {
+    for( const auto& face: faces ) {
         if( face->countParents() % 2 ) {
             continue; /* we skip odd parents geoms */
         }
diff --git a/Sources/geos/src/operation/polygonize/EdgeRing.cpp b/Sources/geos/src/operation/polygonize/EdgeRing.cpp
index ec882f9..7bff9c7 100644
--- a/Sources/geos/src/operation/polygonize/EdgeRing.cpp
+++ b/Sources/geos/src/operation/polygonize/EdgeRing.cpp
@@ -20,13 +20,11 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -199,12 +197,21 @@ EdgeRing::getPolygon()
 
 /*public*/
 bool
-EdgeRing::isValid()
+EdgeRing::isValid() const
 {
-    if(! getRingInternal()) {
-        return false;    // computes cached ring
+    return is_valid;
+}
+
+void
+EdgeRing::computeValid()
+{
+    getCoordinates();
+    if (ringPts->size() <= 3) {
+        is_valid = false;
+        return;
     }
-    return ring->isValid();
+    getRingInternal();
+    is_valid = ring->isValid();
 }
 
 /*private*/
@@ -212,7 +219,7 @@ const CoordinateSequence*
 EdgeRing::getCoordinates()
 {
     if(ringPts == nullptr) {
-        ringPts = detail::make_unique(0u, 0u);
+        ringPts = detail::make_unique(0u, 0u);
         for(const auto& de : deList) {
             auto edge = dynamic_cast(de->getEdge());
             addEdge(edge->getLine()->getCoordinatesRO(),
@@ -240,7 +247,7 @@ EdgeRing::getRingInternal()
 
     getCoordinates();
     try {
-        ring.reset(factory->createLinearRing(*ringPts));
+        ring = factory->createLinearRing(*ringPts);
     }
     catch(const geos::util::IllegalArgumentException& e) {
 #if GEOS_DEBUG
@@ -265,7 +272,7 @@ EdgeRing::getRingOwnership()
 /*private*/
 void
 EdgeRing::addEdge(const CoordinateSequence* coords, bool isForward,
-                  CoordinateArraySequence* coordList)
+                  CoordinateSequence* coordList)
 {
     const std::size_t npts = coords->getSize();
     if(isForward) {
diff --git a/Sources/geos/src/operation/polygonize/HoleAssigner.cpp b/Sources/geos/src/operation/polygonize/HoleAssigner.cpp
index 6509d86..44d487c 100644
--- a/Sources/geos/src/operation/polygonize/HoleAssigner.cpp
+++ b/Sources/geos/src/operation/polygonize/HoleAssigner.cpp
@@ -16,7 +16,6 @@
  *
  **********************************************************************/
 
-#include 
 #include 
 #include 
 
diff --git a/Sources/geos/src/operation/polygonize/Polygonizer.cpp b/Sources/geos/src/operation/polygonize/Polygonizer.cpp
index 3f44dad..92eefdf 100644
--- a/Sources/geos/src/operation/polygonize/Polygonizer.cpp
+++ b/Sources/geos/src/operation/polygonize/Polygonizer.cpp
@@ -25,7 +25,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 // std
 #include 
@@ -104,20 +103,6 @@ Polygonizer::add(std::vector* geomList)
     }
 }
 
-/*
- * Add a geometry to the linework to be polygonized.
- * May be called multiple times.
- * Any dimension of Geometry may be added;
- * the constituent linework will be extracted and used
- *
- * @param g a Geometry with linework to be polygonized
- */
-void
-Polygonizer::add(Geometry* g)
-{
-    g->apply_ro(&lineStringAdder);
-}
-
 /*
  * Add a geometry to the linework to be polygonized.
  * May be called multiple times.
@@ -129,6 +114,7 @@ Polygonizer::add(Geometry* g)
 void
 Polygonizer::add(const Geometry* g)
 {
+    util::ensureNoCurvedComponents(g);
     g->apply_ro(&lineStringAdder);
 }
 
@@ -234,8 +220,10 @@ Polygonizer::polygonize()
     std::cerr << "Polygonizer::polygonize(): " << edgeRingList.size() << " edgeRings in graph" << std::endl;
 #endif
     std::vector validEdgeRingList;
+    std::vector invalidRings;
     invalidRingLines.clear(); /* what if it was populated already ? we should clean ! */
-    findValidRings(edgeRingList, validEdgeRingList, invalidRingLines);
+    findValidRings(edgeRingList, validEdgeRingList, invalidRings);
+    invalidRingLines = extractInvalidLines(invalidRings);
 #if GEOS_DEBUG
     std::cerr << "                           " << validEdgeRingList.size() << " valid" << std::endl;
     std::cerr << "                           " << invalidRingLines.size() << " invalid" << std::endl;
@@ -263,19 +251,68 @@ Polygonizer::polygonize()
 void
 Polygonizer::findValidRings(const std::vector& edgeRingList,
                             std::vector& validEdgeRingList,
-                            std::vector>& invalidRingList)
+                            std::vector& invalidRingList)
 {
     for(const auto& er : edgeRingList) {
+        er->computeValid();
         if(er->isValid()) {
             validEdgeRingList.push_back(er);
         }
         else {
-            invalidRingList.push_back(er->getLineString());
+            invalidRingList.push_back(er);
         }
         GEOS_CHECK_FOR_INTERRUPTS();
     }
 }
 
+std::vector>
+Polygonizer::extractInvalidLines(std::vector& invalidRings)
+{
+    /**
+     * Sort rings by increasing envelope area.
+     * This causes inner rings to be processed before the outer rings
+     * containing them, which allows outer invalid rings to be discarded
+     * since their linework is already reported in the inner rings.
+     */
+    std::sort(invalidRings.begin(),
+              invalidRings.end(),
+              [](EdgeRing* a, EdgeRing* b) {
+                return a->getRingInternal()->getEnvelope()->getArea() <
+                       b->getRingInternal()->getEnvelope()->getArea();
+    });
+
+    /**
+     * Scan through rings.  Keep only rings which have an adjacent EdgeRing
+     * which is either valid or marked as not processed.
+     * This avoids including outer rings which have linework which is duplicated.
+     */
+    std::vector> invalidLines;
+    for (EdgeRing* er : invalidRings) {
+        if (isIncludedInvalid(er)) {
+            invalidLines.push_back(er->getLineString());
+        }
+        er->setProcessed(true);
+    }
+
+    return invalidLines;
+}
+
+bool
+Polygonizer::isIncludedInvalid(EdgeRing* invalidRing)
+{
+    for (const PolygonizeDirectedEdge* de: invalidRing->getEdges()) {
+        const PolygonizeDirectedEdge* deAdj = static_cast(de->getSym());
+        const EdgeRing* erAdj = deAdj->getRing();
+
+        bool isEdgeIncluded = erAdj->isValid() || erAdj->isProcessed();
+        if (!isEdgeIncluded) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 /* private */
 void
 Polygonizer::findShellsAndHoles(const std::vector& edgeRingList)
diff --git a/Sources/geos/src/operation/predicate/RectangleContains.cpp b/Sources/geos/src/operation/predicate/RectangleContains.cpp
index 47e4d48..3f75e8f 100644
--- a/Sources/geos/src/operation/predicate/RectangleContains.cpp
+++ b/Sources/geos/src/operation/predicate/RectangleContains.cpp
@@ -80,7 +80,7 @@ RectangleContains::isPointContainedInBoundary(const Point& point)
 
 /*private*/
 bool
-RectangleContains::isPointContainedInBoundary(const Coordinate& pt)
+RectangleContains::isPointContainedInBoundary(const CoordinateXY& pt)
 {
     /**
      * contains = false iff the point is properly contained
diff --git a/Sources/geos/src/operation/predicate/RectangleIntersects.cpp b/Sources/geos/src/operation/predicate/RectangleIntersects.cpp
index a2feea7..c31fe8c 100644
--- a/Sources/geos/src/operation/predicate/RectangleIntersects.cpp
+++ b/Sources/geos/src/operation/predicate/RectangleIntersects.cpp
@@ -166,7 +166,7 @@ class ContainsPointVisitor: public geom::util::ShortCircuitedGeometryVisitor {
 
             // check rect point in poly (rect is known not to
             // touch polygon at this point)
-            if(SimplePointInAreaLocator::locatePointInPolygon(rectPt, poly) != geom::Location::EXTERIOR) {
+            if(SimplePointInAreaLocator::locatePointInSurface(rectPt, *poly) != geom::Location::EXTERIOR) {
                 containsPointVar = true;
                 return;
             }
diff --git a/Sources/geos/src/operation/relate/EdgeEndBuilder.cpp b/Sources/geos/src/operation/relate/EdgeEndBuilder.cpp
index 5861092..d952dc1 100644
--- a/Sources/geos/src/operation/relate/EdgeEndBuilder.cpp
+++ b/Sources/geos/src/operation/relate/EdgeEndBuilder.cpp
@@ -23,6 +23,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include 
 
@@ -33,12 +34,12 @@ namespace geos {
 namespace operation { // geos.operation
 namespace relate { // geos.operation.relate
 
-std::vector
+std::vector>
 EdgeEndBuilder::computeEdgeEnds(std::vector* edges)
 {
-    std::vector l;
+    std::vector> l;
     for(Edge* e : *edges) {
-        computeEdgeEnds(e, &l);
+        computeEdgeEnds(e, l);
     }
     return l;
 }
@@ -48,7 +49,7 @@ EdgeEndBuilder::computeEdgeEnds(std::vector* edges)
  * Edge (if any) and inserts them into the graph.
  */
 void
-EdgeEndBuilder::computeEdgeEnds(Edge* edge, std::vector* l)
+EdgeEndBuilder::computeEdgeEnds(Edge* edge, std::vector>& l)
 {
     EdgeIntersectionList& eiList = edge->getEdgeIntersectionList();
     //Debug.print(eiList);
@@ -91,7 +92,7 @@ EdgeEndBuilder::computeEdgeEnds(Edge* edge, std::vector* l)
  * eiCurr will always be an EdgeIntersection, but eiPrev may be null.
  */
 void
-EdgeEndBuilder::createEdgeEndForPrev(Edge* edge, std::vector* l,
+EdgeEndBuilder::createEdgeEndForPrev(Edge* edge, std::vector>& l,
                                      const EdgeIntersection* eiCurr, const EdgeIntersection* eiPrev)
 {
     auto iPrev = eiCurr->segmentIndex;
@@ -110,9 +111,9 @@ EdgeEndBuilder::createEdgeEndForPrev(Edge* edge, std::vector* l,
     Label label(edge->getLabel());
     // since edgeStub is oriented opposite to it's parent edge, have to flip sides for edge label
     label.flip();
-    EdgeEnd* e = new EdgeEnd(edge, eiCurr->coord, pPrev, label);
+    auto e = detail::make_unique(edge, eiCurr->coord, pPrev, label);
     //e.print(System.out);  System.out.println();
-    l->push_back(e);
+    l.push_back(std::move(e));
 }
 
 /**
@@ -124,7 +125,7 @@ EdgeEndBuilder::createEdgeEndForPrev(Edge* edge, std::vector* l,
  * eiCurr will always be an EdgeIntersection, but eiNext may be null.
  */
 void
-EdgeEndBuilder::createEdgeEndForNext(Edge* edge, std::vector* l,
+EdgeEndBuilder::createEdgeEndForNext(Edge* edge, std::vector>& l,
                                      const EdgeIntersection* eiCurr, const EdgeIntersection* eiNext)
 {
     std::size_t iNext = eiCurr->segmentIndex + 1;
@@ -137,9 +138,10 @@ EdgeEndBuilder::createEdgeEndForNext(Edge* edge, std::vector* l,
     if(eiNext != nullptr && eiNext->segmentIndex == eiCurr->segmentIndex) {
         pNext = eiNext->coord;
     }
-    EdgeEnd* e = new EdgeEnd(edge, eiCurr->coord, pNext, edge->getLabel());
+
+    auto e = detail::make_unique(edge, eiCurr->coord, pNext, edge->getLabel());
     //Debug.println(e);
-    l->push_back(e);
+    l.push_back(std::move(e));
 }
 
 } // namespace geos.operation.relate
diff --git a/Sources/geos/src/operation/relate/RelateComputer.cpp b/Sources/geos/src/operation/relate/RelateComputer.cpp
index dea48a2..90a18ed 100644
--- a/Sources/geos/src/operation/relate/RelateComputer.cpp
+++ b/Sources/geos/src/operation/relate/RelateComputer.cpp
@@ -61,7 +61,7 @@ namespace geos {
 namespace operation { // geos.operation
 namespace relate { // geos.operation.relate
 
-RelateComputer::RelateComputer(std::vector* newArg):
+RelateComputer::RelateComputer(std::vector>& newArg):
     arg(newArg),
     nodes(RelateNodeFactory::instance()),
     im(new IntersectionMatrix())
@@ -74,10 +74,10 @@ RelateComputer::computeIM()
     // since Geometries are finite and embedded in a 2-D space, the EE element must always be 2
     im->set(Location::EXTERIOR, Location::EXTERIOR, 2);
     // if the Geometries don't overlap there is nothing to do
-    const Envelope* e1 = (*arg)[0]->getGeometry()->getEnvelopeInternal();
-    const Envelope* e2 = (*arg)[1]->getGeometry()->getEnvelopeInternal();
+    const Envelope* e1 = arg[0]->getGeometry()->getEnvelopeInternal();
+    const Envelope* e2 = arg[1]->getGeometry()->getEnvelopeInternal();
     if(!e1->intersects(e2)) {
-        computeDisjointIM(im.get(), (*arg)[0]->getBoundaryNodeRule());
+        computeDisjointIM(im.get(), arg[0]->getBoundaryNodeRule());
         return std::move(im);
     }
 
@@ -88,7 +88,7 @@ RelateComputer::computeIM()
 #endif
 
     std::unique_ptr si1(
-        (*arg)[0]->computeSelfNodes(&li, false)
+        arg[0]->computeSelfNodes(&li, false)
     );
 
     GEOS_CHECK_FOR_INTERRUPTS();
@@ -100,7 +100,7 @@ RelateComputer::computeIM()
 #endif
 
     std::unique_ptr si2(
-        (*arg)[1]->computeSelfNodes(&li, false)
+        arg[1]->computeSelfNodes(&li, false)
     );
 
     GEOS_CHECK_FOR_INTERRUPTS();
@@ -113,7 +113,7 @@ RelateComputer::computeIM()
 
     // compute intersections between edges of the two input geometries
     std::unique_ptr< SegmentIntersector> intersector(
-        (*arg)[0]->computeEdgeIntersections((*arg)[1], &li, false)
+        arg[0]->computeEdgeIntersections(arg[1].get(), &li, false)
     );
 
     GEOS_CHECK_FOR_INTERRUPTS();
@@ -188,9 +188,9 @@ RelateComputer::computeIM()
      */
     // build EdgeEnds for all intersections
     EdgeEndBuilder eeBuilder;
-    std::vector ee0 = eeBuilder.computeEdgeEnds((*arg)[0]->getEdges());
-    insertEdgeEnds(&ee0);
-    std::vector ee1 = eeBuilder.computeEdgeEnds((*arg)[1]->getEdges());
+    auto&& ee0 = eeBuilder.computeEdgeEnds(arg[0]->getEdges());
+    insertEdgeEnds(ee0);
+    auto&& ee1 = eeBuilder.computeEdgeEnds(arg[1]->getEdges());
 
 #if GEOS_DEBUG
     std::cerr << "RelateComputer::computeIM: "
@@ -198,7 +198,7 @@ RelateComputer::computeIM()
               << std::endl;
 #endif
 
-    insertEdgeEnds(&ee1);
+    insertEdgeEnds(ee1);
 
 #if GEOS_DEBUG
     std::cerr << "RelateComputer::computeIM: "
@@ -234,10 +234,10 @@ RelateComputer::computeIM()
 }
 
 void
-RelateComputer::insertEdgeEnds(std::vector* ee)
+RelateComputer::insertEdgeEnds(std::vector>& ee)
 {
-    for(EdgeEnd* e: *ee) {
-        nodes.add(e);
+    for(auto& e : ee) {
+        nodes.add(std::move(e));
     }
 }
 
@@ -246,8 +246,8 @@ void
 RelateComputer::computeProperIntersectionIM(SegmentIntersector* intersector, IntersectionMatrix* imX)
 {
     // If a proper intersection is found, we can set a lower bound on the IM.
-    int dimA = (*arg)[0]->getGeometry()->getDimension();
-    int dimB = (*arg)[1]->getGeometry()->getDimension();
+    int dimA = arg[0]->getGeometry()->getDimension();
+    int dimB = arg[1]->getGeometry()->getDimension();
     bool hasProper = intersector->hasProperIntersection();
     bool hasProperInterior = intersector->hasProperInteriorIntersection();
     // For Geometry's of dim 0 there can never be proper intersections.
@@ -311,9 +311,9 @@ RelateComputer::computeProperIntersectionIM(SegmentIntersector* intersector, Int
 void
 RelateComputer::copyNodesAndLabels(uint8_t argIndex)
 {
-    const NodeMap* nm = (*arg)[argIndex]->getNodeMap();
+    const NodeMap* nm = arg[argIndex]->getNodeMap();
     for(const auto& it: *nm) {
-        const Node* graphNode = it.second;
+        const Node* graphNode = it.second.get();
         Node* newNode = nodes.addNode(graphNode->getCoordinate());
         newNode->setLabel(argIndex,
                           graphNode->getLabel().getLocation(argIndex));
@@ -333,10 +333,10 @@ RelateComputer::copyNodesAndLabels(uint8_t argIndex)
 void
 RelateComputer::computeIntersectionNodes(uint8_t argIndex)
 {
-    std::vector* edges = (*arg)[argIndex]->getEdges();
+    std::vector* edges = arg[argIndex]->getEdges();
     for(Edge* e: *edges) {
         Location eLoc = e->getLabel().getLocation(argIndex);
-        EdgeIntersectionList& eiL = e->getEdgeIntersectionList();
+        const EdgeIntersectionList& eiL = e->getEdgeIntersectionList();
         for(const EdgeIntersection & ei : eiL) {
             RelateNode* n = detail::down_cast(nodes.addNode(ei.coord));
             if(eLoc == Location::BOUNDARY) {
@@ -361,10 +361,10 @@ RelateComputer::computeIntersectionNodes(uint8_t argIndex)
 void
 RelateComputer::labelIntersectionNodes(uint8_t argIndex)
 {
-    std::vector* edges = (*arg)[argIndex]->getEdges();
-    for(Edge* e: *edges) {
+    const std::vector* edges = arg[argIndex]->getEdges();
+    for(const Edge* e: *edges) {
         Location eLoc = e->getLabel().getLocation(argIndex);
-        EdgeIntersectionList& eiL = e->getEdgeIntersectionList();
+        const EdgeIntersectionList& eiL = e->getEdgeIntersectionList();
 
         for(const EdgeIntersection& ei : eiL) {
             RelateNode* n = static_cast(nodes.find(ei.coord));
@@ -384,12 +384,12 @@ RelateComputer::labelIntersectionNodes(uint8_t argIndex)
 void
 RelateComputer::computeDisjointIM(IntersectionMatrix* imX, const algorithm::BoundaryNodeRule& boundaryNodeRule)
 {
-    const Geometry* ga = (*arg)[0]->getGeometry();
+    const Geometry* ga = arg[0]->getGeometry();
     if(!ga->isEmpty()) {
         imX->set(Location::INTERIOR, Location::EXTERIOR, ga->getDimension());
         imX->set(Location::BOUNDARY, Location::EXTERIOR, getBoundaryDim(*ga, boundaryNodeRule));
     }
-    const Geometry* gb = (*arg)[1]->getGeometry();
+    const Geometry* gb = arg[1]->getGeometry();
     if(!gb->isEmpty()) {
         imX->set(Location::EXTERIOR, Location::INTERIOR, gb->getDimension());
         imX->set(Location::EXTERIOR, Location::BOUNDARY, getBoundaryDim(*gb, boundaryNodeRule));
@@ -418,9 +418,9 @@ RelateComputer::getBoundaryDim(const Geometry& geom, const algorithm::BoundaryNo
 void
 RelateComputer::labelNodeEdges()
 {
-    auto& nMap = nodes.nodeMap;
-    for(auto& entry : nMap) {
-        RelateNode* node = detail::down_cast(entry.second);
+    const auto& nMap = nodes.nodeMap;
+    for(const auto& entry : nMap) {
+        RelateNode* node = detail::down_cast(entry.second.get());
 #if GEOS_DEBUG
         std::cerr << "RelateComputer::labelNodeEdges: "
                   << "node edges: " << *(node->getEdges())
@@ -441,9 +441,9 @@ RelateComputer::updateIM(IntersectionMatrix& imX)
         Edge* e = *ei;
         e->GraphComponent::updateIM(imX);
     }
-    auto& nMap = nodes.nodeMap;
-    for(auto& entry : nMap) {
-        RelateNode* node = detail::down_cast(entry.second);
+    const auto& nMap = nodes.nodeMap;
+    for(const auto& entry : nMap) {
+        RelateNode* node = detail::down_cast(entry.second.get());
         node->updateIM(imX);
         node->updateIMFromEdges(imX);
     }
@@ -453,10 +453,10 @@ RelateComputer::updateIM(IntersectionMatrix& imX)
 void
 RelateComputer::labelIsolatedEdges(uint8_t thisIndex, uint8_t targetIndex)
 {
-    std::vector* edges = (*arg)[thisIndex]->getEdges();
+    std::vector* edges = arg[thisIndex]->getEdges();
     for(Edge* e: *edges) {
         if(e->isIsolated()) {
-            labelIsolatedEdge(e, targetIndex, (*arg)[targetIndex]->getGeometry());
+            labelIsolatedEdge(e, targetIndex, arg[targetIndex]->getGeometry());
             isolatedEdges.push_back(e);
         }
     }
@@ -485,7 +485,7 @@ void
 RelateComputer::labelIsolatedNodes()
 {
     for(const auto& it: nodes) {
-        Node* n = it.second;
+        Node* n = it.second.get();
         const Label& label = n->getLabel();
         // isolated nodes should always have at least one geometry in their label
         assert(label.getGeometryCount() > 0); // node with empty label found
@@ -505,7 +505,7 @@ void
 RelateComputer::labelIsolatedNode(Node* n, uint8_t targetIndex)
 {
     Location loc = ptLocator.locate(n->getCoordinate(),
-                               (*arg)[targetIndex]->getGeometry());
+                               arg[targetIndex]->getGeometry());
     n->getLabel().setAllLocations(targetIndex, loc);
     //debugPrintln(n.getLabel());
 }
diff --git a/Sources/geos/src/operation/relate/RelateNodeGraph.cpp b/Sources/geos/src/operation/relate/RelateNodeGraph.cpp
index 9278e7f..1ae3592 100644
--- a/Sources/geos/src/operation/relate/RelateNodeGraph.cpp
+++ b/Sources/geos/src/operation/relate/RelateNodeGraph.cpp
@@ -72,8 +72,8 @@ RelateNodeGraph::build(GeometryGraph* geomGraph)
      * Build EdgeEnds for all intersections.
      */
     EdgeEndBuilder eeBuilder;
-    std::vector eeList = eeBuilder.computeEdgeEnds(geomGraph->getEdges());
-    insertEdgeEnds(&eeList);
+    auto&& eeList = eeBuilder.computeEdgeEnds(geomGraph->getEdges());
+    insertEdgeEnds(eeList);
 }
 
 /**
@@ -95,7 +95,7 @@ RelateNodeGraph::computeIntersectionNodes(GeometryGraph *geomGraph,
     for(; edgeIt < edges->end(); ++edgeIt) {
         Edge* e = *edgeIt;
         Location eLoc = e->getLabel().getLocation(argIndex);
-        EdgeIntersectionList& eiL = e->getEdgeIntersectionList();
+        const EdgeIntersectionList& eiL = e->getEdgeIntersectionList();
         for(const EdgeIntersection& ei : eiL) {
             RelateNode* n = detail::down_cast(nodes->addNode(ei.coord));
             if(eLoc == Location::BOUNDARY) {
@@ -122,19 +122,19 @@ RelateNodeGraph::computeIntersectionNodes(GeometryGraph *geomGraph,
 void
 RelateNodeGraph::copyNodesAndLabels(GeometryGraph *geomGraph, uint8_t argIndex)
 {
-    auto& nMap = geomGraph->getNodeMap()->nodeMap;
-    for(auto& entry : nMap) {
-        Node* graphNode = entry.second;
+    const auto& nMap = geomGraph->getNodeMap()->nodeMap;
+    for(const auto& entry : nMap) {
+        const Node* graphNode = entry.second.get();
         Node* newNode = nodes->addNode(graphNode->getCoordinate());
         newNode->setLabel(argIndex, graphNode->getLabel().getLocation(argIndex));
     }
 }
 
 void
-RelateNodeGraph::insertEdgeEnds(std::vector* ee)
+RelateNodeGraph::insertEdgeEnds(std::vector>& ee)
 {
-    for(EdgeEnd* e: *ee) {
-        nodes->add(e);
+    for(auto& e : ee) {
+        nodes->add(std::move(e));
     }
 }
 
diff --git a/Sources/geos/src/operation/relate/RelateOp.cpp b/Sources/geos/src/operation/relate/RelateOp.cpp
index 719cddb..739b6d4 100644
--- a/Sources/geos/src/operation/relate/RelateOp.cpp
+++ b/Sources/geos/src/operation/relate/RelateOp.cpp
@@ -52,7 +52,7 @@ RelateOp::relate(const Geometry* a, const Geometry* b,
 
 RelateOp::RelateOp(const Geometry* g0, const Geometry* g1):
     GeometryGraphOperation(g0, g1),
-    relateComp(&arg)
+    relateComp(arg)
 {
 }
 
@@ -60,7 +60,7 @@ RelateOp::RelateOp(const Geometry* g0, const Geometry* g1,
                    const algorithm::BoundaryNodeRule& boundaryNodeRule)
     :
     GeometryGraphOperation(g0, g1, boundaryNodeRule),
-    relateComp(&arg)
+    relateComp(arg)
 {
 }
 
diff --git a/Sources/geos/src/operation/relateng/AdjacentEdgeLocator.cpp b/Sources/geos/src/operation/relateng/AdjacentEdgeLocator.cpp
new file mode 100644
index 0000000..aeb5f80
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/AdjacentEdgeLocator.cpp
@@ -0,0 +1,159 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+using geos::algorithm::Orientation;
+using geos::algorithm::PointLocation;
+using geos::geom::CoordinateXY;
+using geos::geom::Dimension;
+using geos::geom::Geometry;
+using geos::geom::LinearRing;
+using geos::geom::Location;
+using geos::geom::Polygon;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public */
+Location
+AdjacentEdgeLocator::locate(const CoordinateXY* p)
+{
+    NodeSections sections(p);
+    for (const CoordinateSequence* ring : ringList) {
+        addSections(p, ring, sections);
+    }
+    std::unique_ptr node = sections.createNode();
+
+    return node->hasExteriorEdge(true) ? Location::BOUNDARY : Location::INTERIOR;
+}
+
+
+/* private */
+void
+AdjacentEdgeLocator::addSections(
+    const CoordinateXY* p,
+    const CoordinateSequence* ring,
+    NodeSections& sections)
+{
+    for (std::size_t i = 0; i < ring->getSize() - 1; i++) {
+        const CoordinateXY& p0 = ring->getAt(i);
+        const CoordinateXY& pnext = ring->getAt(i+1);
+        if (p->equals2D(pnext)) {
+            //-- segment final point is assigned to next segment
+            continue;
+        }
+        else if (p->equals2D(p0)) {
+            std::size_t iprev = i > 0 ? i - 1 : ring->getSize() - 2;
+            const CoordinateXY& pprev = ring->getAt(iprev);
+            NodeSection *ns = createSection(p, &pprev, &pnext);
+            sections.addNodeSection(ns);
+        }
+        else if (PointLocation::isOnSegment(*p, p0, pnext)) {
+            NodeSection *ns = createSection(p, &p0, &pnext);
+            sections.addNodeSection(ns);
+        }
+    }
+}
+
+
+/* private */
+NodeSection*
+AdjacentEdgeLocator::createSection(const CoordinateXY* p,
+    const CoordinateXY* prev,
+    const CoordinateXY* next)
+{
+    if (prev->distance(*p) == 0 || next->distance(*p) == 0) {
+        //System.out.println("Found zero-length section segment");
+    };
+    return new NodeSection(true, Dimension::A, 1, 0, nullptr, false, prev, *p, next);
+}
+
+
+/* private */
+void
+AdjacentEdgeLocator::init(const Geometry* geom)
+{
+    if (geom->isEmpty())
+        return;
+    addRings(geom);
+}
+
+
+/* private */
+void
+AdjacentEdgeLocator::addRings(const Geometry* geom)
+{
+    if (const Polygon* poly = dynamic_cast(geom)) {
+        const LinearRing* shell = poly->getExteriorRing();
+        addRing(shell, true);
+        for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) {
+            const LinearRing* hole = poly->getInteriorRingN(i);
+            addRing(hole, false);
+        }
+    }
+    else if (geom->isCollection()) {
+        //-- recurse through collections
+        for (std::size_t i = 0; i < geom->getNumGeometries(); i++) {
+            addRings(geom->getGeometryN(i));
+        }
+    }
+}
+
+
+/* private */
+void
+AdjacentEdgeLocator::addRing(const LinearRing* ring, bool requireCW)
+{
+    //TODO: remove repeated points?
+    const CoordinateSequence* pts = ring->getCoordinatesRO();
+    bool isFlipped = requireCW == Orientation::isCCW(pts);
+    /*
+     * In case of flipped rings, we need to keep a local copy
+     * since we cannot mutate the const geometry we are fed
+     * in the constructor.
+     */
+    if (isFlipped) {
+        std::unique_ptr localPts = pts->clone();
+        localPts->reverse();
+        ringList.push_back(localPts.get());
+        localRingList.push_back(std::move(localPts));
+    }
+    else {
+        ringList.push_back(pts);
+    }
+    return;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
diff --git a/Sources/geos/src/operation/relateng/BasicPredicate.cpp b/Sources/geos/src/operation/relateng/BasicPredicate.cpp
new file mode 100644
index 0000000..c225de6
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/BasicPredicate.cpp
@@ -0,0 +1,137 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+using geos::geom::Envelope;
+using geos::geom::Location;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+
+/* private static */
+bool
+BasicPredicate::isKnown(int val)
+{
+     return val > UNKNOWN;
+}
+
+/* private static */
+bool
+BasicPredicate::toBoolean(int val)
+{
+    return val == TRUE;
+}
+
+/* private static */
+int
+BasicPredicate::toValue(bool val)
+{
+    return val ? TRUE : FALSE;
+}
+
+
+/* public static */
+bool
+BasicPredicate::isIntersection(Location locA, Location locB)
+{
+    //-- i.e. some location on both geometries intersects
+    return locA != Location::EXTERIOR && locB != Location::EXTERIOR;
+}
+
+
+// /* public */
+// bool isSelfNodingRequired() {
+//     return false;
+//   }
+
+
+/* public override */
+bool
+BasicPredicate::isKnown() const
+{
+    return isKnown(m_value);
+}
+
+/* public override */
+bool
+BasicPredicate::value() const
+{
+    return toBoolean(m_value);
+}
+
+
+/* protected */
+void
+BasicPredicate::setValue(bool val)
+{
+    //-- don't change already-known value
+    if (isKnown())
+        return;
+    m_value = toValue(val);
+}
+
+/* protected */
+void
+BasicPredicate::setValue(int val)
+{
+    //-- don't change already-known value
+    if (isKnown())
+        return;
+    m_value = val;
+}
+
+
+/* protected */
+void
+BasicPredicate::setValueIf(bool val, bool cond)
+{
+    if (cond)
+        setValue(val);
+}
+
+/* protected */
+void
+BasicPredicate::require(bool cond)
+{
+    if (! cond)
+        setValue(false);
+}
+
+/* protected */
+void
+BasicPredicate::requireCovers(const Envelope& a, const Envelope& b)
+{
+    require(a.covers(b));
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/Sources/geos/src/operation/relateng/DimensionLocation.cpp b/Sources/geos/src/operation/relateng/DimensionLocation.cpp
new file mode 100644
index 0000000..50a3457
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/DimensionLocation.cpp
@@ -0,0 +1,125 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+
+
+using geos::geom::Location;
+using geos::geom::Dimension;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public static */
+int
+DimensionLocation::locationArea(Location loc)
+{
+    switch (loc) {
+        case Location::INTERIOR: return AREA_INTERIOR;
+        case Location::BOUNDARY: return AREA_BOUNDARY;
+        default:
+            return EXTERIOR;
+    }
+}
+
+
+/* public static */
+int
+DimensionLocation::locationLine(Location loc)
+{
+    switch (loc) {
+        case Location::INTERIOR: return LINE_INTERIOR;
+        case Location::BOUNDARY: return LINE_BOUNDARY;
+        default:
+            return EXTERIOR;
+    }
+}
+
+
+/* public static */
+int
+DimensionLocation::locationPoint(Location loc)
+{
+    switch (loc) {
+        case Location::INTERIOR: return POINT_INTERIOR;
+        default:
+            return EXTERIOR;
+    }
+}
+
+
+/* public static */
+Location
+DimensionLocation::location(int dimLoc)
+{
+    switch (dimLoc) {
+        case POINT_INTERIOR:
+        case LINE_INTERIOR:
+        case AREA_INTERIOR:
+            return Location::INTERIOR;
+        case LINE_BOUNDARY:
+        case AREA_BOUNDARY:
+            return Location::BOUNDARY;
+        default:
+            return Location::EXTERIOR;
+    }
+}
+
+
+/* public static */
+int
+DimensionLocation::dimension(int dimLoc)
+{
+    switch (dimLoc) {
+        case POINT_INTERIOR:
+            return Dimension::P;
+        case LINE_INTERIOR:
+        case LINE_BOUNDARY:
+            return Dimension::L;
+        case AREA_INTERIOR:
+        case AREA_BOUNDARY:
+            return Dimension::A;
+        default:
+            return Dimension::False;
+    }
+}
+
+
+/* public static */
+int
+DimensionLocation::dimension(int dimLoc, int exteriorDim)
+{
+    if (dimLoc == EXTERIOR)
+        return exteriorDim;
+    else
+        return dimension(dimLoc);
+}
+
+
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/Sources/geos/src/operation/relateng/EdgeSegmentIntersector.cpp b/Sources/geos/src/operation/relateng/EdgeSegmentIntersector.cpp
new file mode 100644
index 0000000..8ea3c52
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/EdgeSegmentIntersector.cpp
@@ -0,0 +1,114 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+using geos::geom::CoordinateXYZM;
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+using geos::noding::SegmentString;
+using geos::index::chain::MonotoneChain;
+using geos::index::chain::MonotoneChainBuilder;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public override */
+bool
+EdgeSegmentIntersector::isDone() const
+{
+    return topoComputer.isResultKnown();
+}
+
+
+/* public override */
+void
+EdgeSegmentIntersector::processIntersections(
+    SegmentString* ss0, std::size_t segIndex0,
+    SegmentString* ss1, std::size_t segIndex1)
+{
+    // don't intersect a segment with itself
+    if (ss0 == ss1 && segIndex0 == segIndex1)
+        return;
+
+    RelateSegmentString* rss0 = static_cast(ss0);
+    RelateSegmentString* rss1 = static_cast(ss1);
+    //TODO: move this ordering logic to TopologyBuilder
+    if (rss0->isA()) {
+        addIntersections(rss0, segIndex0, rss1, segIndex1);
+    }
+    else {
+        addIntersections(rss1, segIndex1, rss0, segIndex0);
+    }
+}
+
+
+/* private */
+void
+EdgeSegmentIntersector::addIntersections(
+    RelateSegmentString* ssA, std::size_t segIndexA,
+    RelateSegmentString* ssB, std::size_t segIndexB)
+{
+
+    const CoordinateXY& a0 = ssA->getCoordinate(segIndexA);
+    const CoordinateXY& a1 = ssA->getCoordinate(segIndexA + 1);
+    const CoordinateXY& b0 = ssB->getCoordinate(segIndexB);
+    const CoordinateXY& b1 = ssB->getCoordinate(segIndexB + 1);
+
+    li.computeIntersection(a0, a1, b0, b1);
+
+    if (! li.hasIntersection())
+        return;
+
+    for (uint32_t i = 0; i < li.getIntersectionNum(); i++)
+    {
+        const CoordinateXYZM& intPtXYZM = li.getIntersection(i);
+        CoordinateXY intPt(intPtXYZM.x, intPtXYZM.y);
+        /**
+         * Ensure endpoint intersections are added once only, for their canonical segments.
+         * Proper intersections lie on a unique segment so do not need to be checked.
+         * And it is important that the Containing Segment check not be used,
+         * since due to intersection computation roundoff,
+         * it is not reliable in that situation.
+         */
+        if (li.isProper() ||
+            (ssA->isContainingSegment(segIndexA, &intPt) &&
+             ssB->isContainingSegment(segIndexB, &intPt)))
+        {
+            NodeSection* nsa = ssA->createNodeSection(segIndexA, intPt);
+            NodeSection* nsb = ssB->createNodeSection(segIndexB, intPt);
+            topoComputer.addIntersection(nsa, nsb);
+        }
+    }
+}
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
diff --git a/Sources/geos/src/operation/relateng/EdgeSegmentOverlapAction.cpp b/Sources/geos/src/operation/relateng/EdgeSegmentOverlapAction.cpp
new file mode 100644
index 0000000..cd32bca
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/EdgeSegmentOverlapAction.cpp
@@ -0,0 +1,53 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2001-2002 Vivid Solutions Inc.
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************
+ *
+ * Last port: index/chain/MonotoneChainOverlapAction.java rev. 1.6 (JTS-1.10)
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+//#include 
+
+using geos::index::chain::MonotoneChain;
+using geos::noding::SegmentString;
+using geos::noding::SegmentIntersector;
+
+namespace geos {
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public override */
+void
+EdgeSegmentOverlapAction::overlap(
+    const MonotoneChain& mc1, std::size_t start1,
+    const MonotoneChain& mc2, std::size_t start2)
+{
+    SegmentString* ss1 = static_cast(mc1.getContext());
+    SegmentString* ss2 = static_cast(mc2.getContext());
+    si.processIntersections(ss1, start1, ss2, start2);
+}
+
+
+} // namespace geos.index.chain
+} // namespace geos.index
+} // namespace geos
diff --git a/Sources/geos/src/operation/relateng/EdgeSetIntersector.cpp b/Sources/geos/src/operation/relateng/EdgeSetIntersector.cpp
new file mode 100644
index 0000000..5a33ef3
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/EdgeSetIntersector.cpp
@@ -0,0 +1,92 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+using geos::geom::Geometry;
+using geos::geom::Envelope;
+using geos::noding::SegmentString;
+using geos::index::chain::MonotoneChain;
+using geos::index::chain::MonotoneChainBuilder;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* private */
+void
+EdgeSetIntersector::addEdges(std::vector& segStrings)
+{
+    for (const SegmentString* ss : segStrings) {
+        addToIndex(ss);
+    }
+}
+
+/* private */
+void
+EdgeSetIntersector::addToIndex(const SegmentString* segStr)
+{
+    std::vector segChains;
+    MonotoneChainBuilder::getChains(segStr->getCoordinates(), const_cast(segStr), segChains);
+
+    for (MonotoneChain& mc : segChains) {
+        if (envelope == nullptr || envelope->intersects(mc.getEnvelope())) {
+            // mc.setId(idCounter++);
+            monoChains.push_back(mc);
+            MonotoneChain* mcPtr = &(monoChains.back());
+            index.insert(mcPtr->getEnvelope(), mcPtr);
+        }
+    }
+}
+
+/* public */
+void
+EdgeSetIntersector::process(EdgeSegmentIntersector& intersector)
+{
+    EdgeSegmentOverlapAction overlapAction(intersector);
+
+    // Replaces JTS implementation that manually iterates on the
+    // monoChains with the automatic queryPairs method in TemplateSTRTree
+    index.queryPairs([this, &overlapAction, &intersector](const MonotoneChain* queryChain, const MonotoneChain* testChain) {
+
+        if (overlapCounter++ % 100000 == 0)
+            GEOS_CHECK_FOR_INTERRUPTS();
+
+        testChain->computeOverlaps(queryChain, &overlapAction);
+
+        return !intersector.isDone();
+    });
+
+}
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
diff --git a/Sources/geos/src/operation/relateng/IMPatternMatcher.cpp b/Sources/geos/src/operation/relateng/IMPatternMatcher.cpp
new file mode 100644
index 0000000..d70c880
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/IMPatternMatcher.cpp
@@ -0,0 +1,149 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+
+#include 
+
+
+using geos::geom::Envelope;
+using geos::geom::Location;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public */
+std::string
+IMPatternMatcher::name() const
+{
+    return "IMPattern";
+}
+
+
+/* public */
+void
+IMPatternMatcher::init(const Envelope& envA, const Envelope& envB)
+{
+    IMPredicate::init(dimA, dimB);
+    //-- if pattern specifies any non-E/non-E interaction, envelopes must not be disjoint
+    bool requiresInteraction = requireInteraction(patternMatrix);
+    bool isDisjoint = envA.disjoint(&envB);
+    setValueIf(false, requiresInteraction && isDisjoint);
+}
+
+
+/* public */
+bool
+IMPatternMatcher::requireInteraction() const
+{
+    return requireInteraction(patternMatrix);
+}
+
+
+/* private static */
+bool
+IMPatternMatcher::requireInteraction(const IntersectionMatrix& im)
+{
+    bool requiresInteraction =
+        isInteraction(im.get(Location::INTERIOR, Location::INTERIOR)) ||
+        isInteraction(im.get(Location::INTERIOR, Location::BOUNDARY)) ||
+        isInteraction(im.get(Location::BOUNDARY, Location::INTERIOR)) ||
+        isInteraction(im.get(Location::BOUNDARY, Location::BOUNDARY));
+    return requiresInteraction;
+}
+
+
+/* private static */
+bool
+IMPatternMatcher::isInteraction(int imDim)
+{
+    return imDim == Dimension::True || imDim >= Dimension::P;
+}
+
+
+/* public */
+bool
+IMPatternMatcher::isDetermined() const
+{
+    /**
+     * Matrix entries only increase in dimension as topology is computed.
+     * The predicate can be short-circuited (as false) if
+     * any computed entry is greater than the mask value.
+     */
+    std::array locs = {
+        Location::INTERIOR, Location::BOUNDARY, Location::EXTERIOR};
+
+    for (Location i : locs) {
+        for (Location j : locs) {
+            int patternEntry = patternMatrix.get(i, j);
+
+            if (patternEntry == Dimension::DONTCARE)
+                continue;
+
+            int matrixVal = getDimension(i, j);
+
+            //-- mask entry TRUE requires a known matrix entry
+            if (patternEntry == Dimension::True) {
+                if (matrixVal < 0)
+                    return false;
+            }
+            //-- result is known (false) if matrix entry has exceeded mask
+            else if (matrixVal > patternEntry)
+                return true;
+        }
+    }
+    return false;
+}
+
+
+/* public */
+bool
+IMPatternMatcher::valueIM()
+{
+    bool val = intMatrix.matches(imPattern);
+    return val;
+}
+
+
+/* public */
+std::string
+IMPatternMatcher::toString() const
+{
+    return name() + "(" + imPattern + ")";
+}
+
+
+/* public friend */
+std::ostream&
+operator<<(std::ostream& os, const IMPatternMatcher& imp)
+{
+    os << imp.toString();
+    return os;
+}
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/Sources/geos/src/operation/relateng/IMPredicate.cpp b/Sources/geos/src/operation/relateng/IMPredicate.cpp
new file mode 100644
index 0000000..3b4411d
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/IMPredicate.cpp
@@ -0,0 +1,155 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+using geos::geom::Envelope;
+using geos::geom::Location;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public static */
+bool
+IMPredicate::isDimsCompatibleWithCovers(int dim0, int dim1)
+{
+    //- allow Points coveredBy zero-length Lines
+    if (dim0 == Dimension::P && dim1 == Dimension::L)
+        return true;
+    return dim0 >= dim1;
+}
+
+
+/* public */
+void
+IMPredicate::init(int dA, int dB)
+{
+    dimA = dA;
+    dimB = dB;
+}
+
+
+/* public */
+void
+IMPredicate::updateDimension(Location locA, Location locB, int dimension)
+{
+    //-- only record an increased dimension value
+    if (isDimChanged(locA, locB, dimension)) {
+        intMatrix.set(locA, locB, dimension);
+        //-- set value if predicate value can be known
+        if (isDetermined()) {
+            setValue(valueIM());
+        }
+    }
+}
+
+
+/* public */
+bool
+IMPredicate::isDimChanged(Location locA, Location locB, int dimension) const
+{
+    return dimension > intMatrix.get(locA, locB);
+}
+
+
+/* protected */
+bool
+IMPredicate::intersectsExteriorOf(bool isA) const
+{
+    if (isA) {
+        return isIntersects(Location::EXTERIOR, Location::INTERIOR)
+            || isIntersects(Location::EXTERIOR, Location::BOUNDARY);
+    }
+    else {
+        return isIntersects(Location::INTERIOR, Location::EXTERIOR)
+            || isIntersects(Location::BOUNDARY, Location::EXTERIOR);
+    }
+}
+
+
+/* protected */
+bool
+IMPredicate::isIntersects(Location locA, Location locB) const
+{
+    return intMatrix.get(locA, locB) >= Dimension::P;
+}
+
+
+/* public */
+bool
+IMPredicate::isKnown(Location locA, Location locB) const
+{
+    return intMatrix.get(locA, locB) != DIM_UNKNOWN;
+}
+
+
+/* public */
+bool
+IMPredicate::isDimension(Location locA, Location locB, int dimension) const
+{
+    return intMatrix.get(locA, locB) == dimension;
+}
+
+
+/* public */
+int
+IMPredicate::getDimension(Location locA, Location locB) const
+{
+    return intMatrix.get(locA, locB);
+}
+
+
+/* public */
+void
+IMPredicate::finish()
+{
+    setValue(valueIM());
+}
+
+
+/* public */
+std::string
+IMPredicate::toString() const
+{
+    return name() + ": " + intMatrix.toString();
+}
+
+
+/* public friend */
+std::ostream&
+operator<<(std::ostream& os, const IMPredicate& imp)
+{
+    os << imp.toString() << " " << imp.intMatrix;
+    return os;
+}
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/Sources/geos/src/operation/relateng/LineStringExtracter.cpp b/Sources/geos/src/operation/relateng/LineStringExtracter.cpp
new file mode 100644
index 0000000..b1c229c
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/LineStringExtracter.cpp
@@ -0,0 +1,87 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+using geos::geom::Geometry;
+using geos::geom::GeometryFactory;
+using geos::geom::LineString;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public static */
+void
+LineStringExtracter::getLines(const Geometry* geom, std::vector& lines)
+{
+    if (geom->getGeometryTypeId() == geom::GEOS_LINESTRING) {
+        lines.push_back(static_cast(geom));
+    }
+    else if (geom->isCollection()) {
+        LineStringExtracter lse(lines);
+        geom->apply_ro(&lse);
+    }
+    // skip non-LineString elemental geometries
+
+    return;
+}
+
+
+/* public static */
+std::vector
+LineStringExtracter::getLines(const Geometry* geom)
+{
+    std::vector lines;
+    getLines(geom, lines);
+    return lines;
+}
+
+
+/* public static */
+// std::unique_ptr
+// LineStringExtracter::getGeometry(const Geometry* geom)
+// {
+//     std::vector lines;
+//     getLines(geom, lines);
+//     return geom->getFactory()->buildGeometry(lines);
+// }
+
+
+/* public */
+void
+LineStringExtracter::filter_ro(const Geometry* geom)
+{
+    if (geom->getGeometryTypeId() == geom::GEOS_LINESTRING)
+        comps.push_back(static_cast(geom));
+}
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/Sources/geos/src/operation/relateng/LinearBoundary.cpp b/Sources/geos/src/operation/relateng/LinearBoundary.cpp
new file mode 100644
index 0000000..c97d6b8
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/LinearBoundary.cpp
@@ -0,0 +1,114 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+using geos::algorithm::BoundaryNodeRule;
+using geos::geom::CoordinateXY;
+using geos::geom::CoordinateSequence;
+using geos::geom::LineString;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+LinearBoundary::LinearBoundary(std::vector& lines, const BoundaryNodeRule& bnRule)
+    : m_boundaryNodeRule(bnRule)
+{
+    //assert: dim(geom) == 1
+    computeBoundaryPoints(lines, m_vertexDegree);
+    m_hasBoundary = checkBoundary(m_vertexDegree);
+}
+
+/* private */
+bool
+LinearBoundary::checkBoundary(Coordinate::ConstIntMap& vertexDegree) const
+{
+    // Iterate over the map and test the values
+    for (const auto& pair : vertexDegree) {
+        int degree = pair.second;
+        if (m_boundaryNodeRule.isInBoundary(degree)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/* public */
+bool
+LinearBoundary::hasBoundary() const
+{
+    return m_hasBoundary;
+}
+
+/* public */
+bool
+LinearBoundary::isBoundary(const CoordinateXY* pt) const
+{
+    auto it = m_vertexDegree.find(pt);
+    if (it == m_vertexDegree.end())
+        return false;
+
+    int degree = it->second;
+    return m_boundaryNodeRule.isInBoundary(degree);
+}
+
+/* private static */
+void
+LinearBoundary::computeBoundaryPoints(std::vector& lines, Coordinate::ConstIntMap& vertexDegree)
+{
+    for (const LineString* line : lines) {
+        if (line->isEmpty())
+            continue;
+        const CoordinateSequence* cs = line->getCoordinatesRO();
+        const Coordinate& cs0 = cs->getAt(0);
+        const Coordinate& csn = cs->getAt(line->getNumPoints() - 1);
+        addEndpoint(&cs0, vertexDegree);
+        addEndpoint(&csn, vertexDegree);
+    }
+}
+
+/* private static */
+void
+LinearBoundary::addEndpoint(const CoordinateXY *p, Coordinate::ConstIntMap& vertexDegree)
+{
+    int dim = 0;
+    auto it = vertexDegree.find(p);
+
+    if (it != vertexDegree.end()) {
+        dim = it->second;
+    }
+    vertexDegree[p] = dim + 1;
+}
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/Sources/geos/src/operation/relateng/NodeSection.cpp b/Sources/geos/src/operation/relateng/NodeSection.cpp
new file mode 100644
index 0000000..d8d48f5
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/NodeSection.cpp
@@ -0,0 +1,253 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+using geos::geom::Dimension;
+using geos::io::WKTWriter;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public */
+const CoordinateXY *
+NodeSection::getVertex(int i) const
+{
+    return i == 0 ? m_v0 : m_v1;
+}
+
+
+/* public */
+const CoordinateXY &
+NodeSection::nodePt() const
+{
+    return m_nodePt;
+}
+
+
+/* public */
+int
+NodeSection::dimension() const
+{
+    return m_dim;
+}
+
+
+/* public */
+int
+NodeSection::id() const
+{
+    return m_id;
+}
+
+
+/* public */
+int
+NodeSection::ringId() const
+{
+    return m_ringId;
+}
+
+
+/* public */
+const Geometry *
+NodeSection::getPolygonal() const
+{
+    return m_poly;
+}
+
+
+/* public */
+bool
+NodeSection::isShell() const
+{
+    return m_ringId == 0;
+}
+
+
+/* public */
+bool
+NodeSection::isArea() const
+{
+    return m_dim == Dimension::A;
+}
+
+
+/* public static */
+bool
+NodeSection::isAreaArea(const NodeSection& a, const NodeSection& b)
+{
+    return a.dimension() == Dimension::A && b.dimension() == Dimension::A;
+}
+
+
+/* public */
+bool
+NodeSection::isA() const
+{
+     return m_isA;
+}
+
+
+/* public */
+bool
+NodeSection::isSameGeometry(const NodeSection& ns) const
+{
+    return isA() == ns.isA();
+}
+
+
+/* public */
+bool
+NodeSection::isSamePolygon(const NodeSection& ns) const
+{
+    return isA() == ns.isA() && id() == ns.id();
+}
+
+
+/* public */
+bool
+NodeSection::isNodeAtVertex() const
+{
+    return m_isNodeAtVertex;
+}
+
+
+/* public */
+bool
+NodeSection::isProper() const
+{
+    return ! m_isNodeAtVertex;
+}
+
+
+/* public static */
+bool
+NodeSection::isProper(const NodeSection& a, const NodeSection& b)
+{
+    return a.isProper() && b.isProper();
+}
+
+
+/* public static */
+std::string
+NodeSection::edgeRep(const CoordinateXY* p0, const CoordinateXY* p1)
+{
+    if (p0 == nullptr || p1 == nullptr)
+        return "null";
+    return WKTWriter::toLineString(*p0, *p1);
+}
+
+
+/* private */
+int
+NodeSection::compare(int a, int b)
+{
+    if (a < b) return -1;
+    if (a > b) return 1;
+    return 0;
+}
+
+
+/* public */
+int
+NodeSection::compareTo(const NodeSection& o) const
+{
+    // Assert: nodePt.equals2D(o.nodePt())
+
+    // sort A before B
+    if (m_isA != o.m_isA) {
+        if (m_isA) return -1;
+        return 1;
+    }
+    //-- sort on dimensions
+    int compDim = compare(m_dim,  o.m_dim);
+    if (compDim != 0) return compDim;
+
+    //-- sort on id and ring id
+    int compId = compare(m_id, o.m_id);
+    if (compId != 0) return compId;
+
+    int compRingId = compare(m_ringId, o.m_ringId);
+    if (compRingId != 0) return compRingId;
+
+    //-- sort on edge coordinates
+    int compV0 = compareWithNull(m_v0, o.m_v0);
+    if (compV0 != 0) return compV0;
+
+    return compareWithNull(m_v1, o.m_v1);
+}
+
+
+/* private static */
+int
+NodeSection::compareWithNull(const CoordinateXY* v0, const CoordinateXY* v1)
+{
+    if (v0 == nullptr) {
+        if (v1 == nullptr)
+            return 0;
+        //-- null is lower than non-null
+        return -1;
+    }
+    // v0 is non-null
+    if (v1 == nullptr)
+        return 1;
+    return v0->compareTo(*v1);
+}
+
+
+/* public */
+std::string
+NodeSection::toString() const
+{
+    // TODO, port RelateGeometry
+    // os << RelateGeometry::name(m_isA);
+    std::stringstream ss;
+    ss << m_dim;
+    if (m_id >= 0) {
+        ss << "[" << m_id << ":" << m_ringId << "]";
+    }
+    ss << ": " << edgeRep(m_v0, &m_nodePt);
+    ss << (m_isNodeAtVertex ? "-V-" : "---");
+    ss << " " << edgeRep(&m_nodePt, m_v1);
+    return ss.str();
+}
+
+/* public friend */
+std::ostream&
+operator<<(std::ostream& os, const NodeSection& ns)
+{
+    os << ns.toString();
+    return os;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
diff --git a/Sources/geos/src/operation/relateng/NodeSections.cpp b/Sources/geos/src/operation/relateng/NodeSections.cpp
new file mode 100644
index 0000000..e02433d
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/NodeSections.cpp
@@ -0,0 +1,163 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+/* public */
+const CoordinateXY*
+NodeSections::getCoordinate() const
+{
+    return nodePt;
+}
+
+
+/* public */
+void
+NodeSections::addNodeSection(NodeSection* e)
+{
+    //System.out.println(e);
+    sections.emplace_back(e);
+}
+
+
+/* public */
+bool
+NodeSections::hasInteractionAB() const
+{
+    bool isA = false;
+    bool isB = false;
+    for (const std::unique_ptr& ns : sections) {
+        if (ns->isA())
+            isA = true;
+        else
+            isB = true;
+
+        if (isA && isB)
+            return true;
+    }
+    return false;
+}
+
+
+/* public */
+const Geometry*
+NodeSections::getPolygonal(bool isA) const
+{
+    for (const std::unique_ptr& ns : sections) {
+        if (ns->isA() == isA) {
+            const Geometry* poly = ns->getPolygonal();
+            if (poly != nullptr)
+                return poly;
+        }
+    }
+    return nullptr;
+}
+
+
+/* public */
+std::unique_ptr
+NodeSections::createNode()
+{
+    prepareSections();
+
+    std::unique_ptr node(new RelateNode(nodePt));
+    std::size_t i = 0;
+    while (i < sections.size()) {
+        const std::unique_ptr& ns = sections[i];
+        //-- if there multiple polygon sections incident at node convert them to maximal-ring structure
+        if (ns->isArea() && hasMultiplePolygonSections(sections, i)) {
+            std::vector polySections = collectPolygonSections(sections, i);
+            std::vector> nsConvert = PolygonNodeConverter::convert(polySections);
+            node->addEdges(nsConvert);
+            i += polySections.size();
+        }
+        else {
+            //-- the most common case is a line or a single polygon ring section
+            node->addEdges(ns.get());
+            i += 1;
+        }
+    }
+    return node;
+}
+
+
+/* private */
+void
+NodeSections::prepareSections()
+{
+    // Comparator lambda for sort support
+    auto comparator = [](
+        const std::unique_ptr& a,
+        const std::unique_ptr& b)
+    {
+        return a->compareTo(*b) < 0;
+    };
+
+    std::sort(sections.begin(), sections.end(), comparator);
+    //TODO: remove duplicate sections
+}
+
+
+/* private static */
+bool
+NodeSections::hasMultiplePolygonSections(
+    std::vector>& sections,
+    std::size_t i)
+{
+    //-- if last section can only be one
+    if (i >= sections.size() - 1)
+        return false;
+    //-- check if there are at least two sections for same polygon
+    std::unique_ptr& ns = sections[i];
+    std::unique_ptr& nsNext = sections[i + 1];
+    return ns->isSamePolygon(*nsNext);
+}
+
+
+/* private static */
+std::vector
+NodeSections::collectPolygonSections(
+    std::vector>& sections,
+    std::size_t i)
+{
+    std::vector polySections;
+    //-- note ids are only unique to a geometry
+    std::unique_ptr& polySection = sections[i];
+    while (i < sections.size() &&
+        polySection->isSamePolygon(*(sections[i])))
+    {
+        polySections.push_back(sections[i].get());
+        i++;
+    }
+    return polySections;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
diff --git a/Sources/geos/src/operation/relateng/PolygonNodeConverter.cpp b/Sources/geos/src/operation/relateng/PolygonNodeConverter.cpp
new file mode 100644
index 0000000..edc582c
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/PolygonNodeConverter.cpp
@@ -0,0 +1,193 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+using geos::algorithm::PolygonNodeTopology;
+using geos::geom::CoordinateXY;
+using geos::geom::Dimension;
+// using geos::geom::Location;
+// using geos::geom::Position;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public static */
+std::vector>
+PolygonNodeConverter::convert(std::vector& polySections)
+{
+    auto comparator = [](
+        const NodeSection* ns1,
+        const NodeSection* ns2)
+    {
+        int comp = PolygonNodeTopology::compareAngle(
+            &(ns1->nodePt()),
+            ns1->getVertex(0),
+            ns2->getVertex(0));
+        return comp < 0;
+    };
+
+    std::sort(polySections.begin(), polySections.end(), comparator);
+    // polySections.sort(new NodeSection.EdgeAngleComparator());
+
+    //TODO: move uniquing up to caller
+    std::vector sections = extractUnique(polySections);
+    if (sections.size() == 1) {
+        std::vector> nss;
+        nss.emplace_back(new NodeSection(sections[0]));
+        return nss;
+    }
+
+    //-- find shell section index
+    std::size_t shellIndex = findShell(sections);
+    if (shellIndex == INDEX_UNKNOWN) {
+        return convertHoles(sections);
+    }
+    //-- at least one shell is present.  Handle multiple ones if present
+    std::vector> convertedSections;
+    std::size_t nextShellIndex = shellIndex;
+    do {
+        nextShellIndex = convertShellAndHoles(
+            sections, nextShellIndex, convertedSections);
+    } while (nextShellIndex != shellIndex);
+
+    return convertedSections;
+}
+
+
+/* private static */
+std::size_t
+PolygonNodeConverter::convertShellAndHoles(
+    std::vector& sections,
+    std::size_t shellIndex,
+    std::vector>& convertedSections)
+{
+    const NodeSection* shellSection = sections[shellIndex];
+    const CoordinateXY* inVertex = shellSection->getVertex(0);
+    std::size_t i = next(sections, shellIndex);
+    const NodeSection* holeSection = nullptr;
+    while (! sections[i]->isShell()) {
+        holeSection = sections[i];
+        // Assert: holeSection.isShell() = false
+        const CoordinateXY* outVertex = holeSection->getVertex(1);
+        NodeSection* ns = createSection(shellSection, inVertex, outVertex);
+        convertedSections.emplace_back(ns);
+        inVertex = holeSection->getVertex(0);
+        i = next(sections, i);
+    }
+    //-- create final section for corner from last hole to shell
+    const CoordinateXY* outVertex = shellSection->getVertex(1);
+    NodeSection* ns = createSection(shellSection, inVertex, outVertex);
+    convertedSections.emplace_back(ns);
+    return i;
+}
+
+
+/* private static */
+std::vector>
+PolygonNodeConverter::convertHoles(std::vector& sections)
+{
+    std::vector> convertedSections;
+    const NodeSection* copySection = sections[0];
+    for (std::size_t i = 0; i < sections.size(); i++) {
+        std::size_t inext = next(sections, i);
+        const CoordinateXY* inVertex = sections[i]->getVertex(0);
+        const CoordinateXY* outVertex = sections[inext]->getVertex(1);
+        NodeSection* ns = createSection(copySection, inVertex, outVertex);
+        convertedSections.emplace_back(ns);
+    }
+    return convertedSections;
+}
+
+
+/* private static */
+NodeSection*
+PolygonNodeConverter::createSection(
+    const NodeSection* ns,
+    const CoordinateXY* v0,
+    const CoordinateXY* v1)
+{
+    return new NodeSection(
+        ns->isA(),
+        Dimension::A,
+        ns->id(), 0,
+        ns->getPolygonal(),
+        ns->isNodeAtVertex(),
+        v0, ns->nodePt(), v1);
+}
+
+
+
+/* private static */
+std::vector
+PolygonNodeConverter::extractUnique(std::vector& sections)
+{
+    std::vector uniqueSections;
+    const NodeSection* lastUnique = sections[0];
+    uniqueSections.push_back(lastUnique);
+    for (const NodeSection* ns : sections) {
+        if (0 != lastUnique->compareTo(ns)) {
+            uniqueSections.push_back(ns);
+            lastUnique = ns;
+        }
+    }
+    return uniqueSections;
+}
+
+
+/* private static */
+std::size_t
+PolygonNodeConverter::next(std::vector& ns, std::size_t i)
+{
+    std::size_t nxt = i;
+    if (nxt == INDEX_UNKNOWN)
+        nxt = 0;
+    else
+        nxt = i + 1;
+
+    if (nxt >= ns.size())
+        nxt = 0;
+
+    return nxt;
+}
+
+
+/* private static */
+std::size_t
+PolygonNodeConverter::findShell(std::vector& polySections)
+{
+    for (std::size_t i = 0; i < polySections.size(); i++) {
+        if (polySections[i]->isShell())
+            return i;
+    }
+    return INDEX_UNKNOWN;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
diff --git a/Sources/geos/src/operation/relateng/RelateEdge.cpp b/Sources/geos/src/operation/relateng/RelateEdge.cpp
new file mode 100644
index 0000000..4670b05
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/RelateEdge.cpp
@@ -0,0 +1,475 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+using geos::algorithm::PolygonNodeTopology;
+using geos::geom::CoordinateXY;
+using geos::geom::Dimension;
+using geos::geom::Position;
+using geos::io::WKTWriter;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public */
+RelateEdge::RelateEdge(const RelateNode* rNode, const CoordinateXY* pt, bool isA, bool isForward)
+    : node(rNode)
+    , dirPt(pt)
+{
+    setLocationsArea(isA, isForward);
+}
+
+
+/* public */
+RelateEdge::RelateEdge(const RelateNode* rNode, const CoordinateXY* pt, bool isA)
+    : node(rNode)
+    , dirPt(pt)
+{
+    setLocationsLine(isA);
+}
+
+
+/* public */
+RelateEdge::RelateEdge(const RelateNode* rNode, const CoordinateXY* pt,
+    bool isA, Location locLeft, Location locRight, Location locLine)
+    : node(rNode)
+    , dirPt(pt)
+{
+    setLocations(isA, locLeft, locRight, locLine);
+}
+
+
+/* public static */
+RelateEdge *
+RelateEdge::create(const RelateNode* node, const CoordinateXY* dirPt, bool isA, int dim, bool isForward)
+{
+    if (dim == Dimension::A)
+        //-- create an area edge
+        return new RelateEdge(node, dirPt, isA, isForward);
+    //-- create line edge
+    return new RelateEdge(node, dirPt, isA);
+}
+
+
+/* public static */
+std::size_t
+RelateEdge::findKnownEdgeIndex(
+    std::vector>& edges,
+    bool isA)
+{
+    for (std::size_t i = 0; i < edges.size(); i++) {
+        auto& e = edges[i];
+        if (e->isKnown(isA))
+            return i;
+    }
+    return INDEX_UNKNOWN;
+}
+
+
+/* public static */
+void
+RelateEdge::setAreaInterior(
+    std::vector>& edges,
+    bool isA)
+{
+    for (auto& e : edges) {
+        e->setAreaInterior(isA);
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setLocations(bool isA, Location locLeft, Location locRight, Location locLine)
+{
+    if (isA) {
+        aDim = 2;
+        aLocLeft = locLeft;
+        aLocRight = locRight;
+        aLocLine = locLine;
+    }
+    else {
+        bDim = 2;
+        bLocLeft = locLeft;
+        bLocRight = locRight;
+        bLocLine = locLine;
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setLocationsLine(bool isA)
+{
+    if (isA) {
+        aDim = 1;
+        aLocLeft = Location::EXTERIOR;
+        aLocRight = Location::EXTERIOR;
+        aLocLine = Location::INTERIOR;
+    }
+    else {
+        bDim = 1;
+        bLocLeft = Location::EXTERIOR;
+        bLocRight = Location::EXTERIOR;
+        bLocLine = Location::INTERIOR;
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setLocationsArea(bool isA, bool isForward)
+{
+    Location locLeft = isForward ? Location::EXTERIOR : Location::INTERIOR;
+    Location locRight = isForward ? Location::INTERIOR : Location::EXTERIOR;
+    if (isA) {
+        aDim = 2;
+        aLocLeft = locLeft;
+        aLocRight = locRight;
+        aLocLine = Location::BOUNDARY;
+    }
+    else {
+        bDim = 2;
+        bLocLeft = locLeft;
+        bLocRight = locRight;
+        bLocLine = Location::BOUNDARY;
+    }
+}
+
+
+/* public */
+int
+RelateEdge::compareToEdge(const CoordinateXY* edgeDirPt) const
+{
+    return PolygonNodeTopology::compareAngle(
+        node->getCoordinate(),
+        dirPt,
+        edgeDirPt);
+}
+
+
+/* public */
+void
+RelateEdge::merge(bool isA, int dim, bool isForward)
+{
+    Location locEdge = Location::INTERIOR;
+    Location locLeft = Location::EXTERIOR;
+    Location locRight = Location::EXTERIOR;
+    if (dim == Dimension::A) {
+        locEdge = Location::BOUNDARY;
+        locLeft = isForward ? Location::EXTERIOR : Location::INTERIOR;
+        locRight = isForward ? Location::INTERIOR : Location::EXTERIOR;
+    }
+
+    if (! isKnown(isA)) {
+        setDimension(isA, dim);
+        setOn(isA, locEdge);
+        setLeft(isA, locLeft);
+        setRight(isA, locRight);
+        return;
+    }
+
+    // Assert: node-dirpt is collinear with node-pt
+    mergeDimEdgeLoc(isA, locEdge);
+    mergeSideLocation(isA, Position::LEFT, locLeft);
+    mergeSideLocation(isA, Position::RIGHT, locRight);
+}
+
+/**
+* Area edges override Line edges.
+* Merging edges of same dimension is a no-op for
+* the dimension and on location.
+* But merging an area edge into a line edge
+* sets the dimension to A and the location to BOUNDARY.
+*
+* @param isA
+* @param locEdge
+*/
+/* private */
+void
+RelateEdge::mergeDimEdgeLoc(bool isA, Location locEdge)
+{
+    //TODO: this logic needs work - ie handling A edges marked as Interior
+    int dim = (locEdge == Location::BOUNDARY) ? Dimension::A : Dimension::L;
+    if (dim == Dimension::A && dimension(isA) == Dimension::L) {
+        setDimension(isA, dim);
+        setOn(isA, Location::BOUNDARY);
+    }
+}
+
+
+/* private */
+void
+RelateEdge::mergeSideLocation(bool isA, int pos, Location loc)
+{
+    Location currLoc = location(isA, pos);
+    //-- INTERIOR takes precedence over EXTERIOR
+    if (currLoc != Location::INTERIOR) {
+        setLocation(isA, pos, loc);
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setDimension(bool isA, int dimension)
+{
+    if (isA) {
+        aDim = dimension;
+    }
+    else {
+        bDim = dimension;
+    }
+}
+
+
+/* public */
+void
+RelateEdge::setLocation(bool isA, int pos, Location loc)
+{
+    switch (pos) {
+    case Position::LEFT:
+        setLeft(isA, loc);
+        break;
+    case Position::RIGHT:
+        setRight(isA, loc);
+        break;
+    case Position::ON:
+        setOn(isA, loc);
+        break;
+    }
+}
+
+
+/* public */
+void
+RelateEdge::setAllLocations(bool isA, Location loc)
+{
+    setLeft(isA, loc);
+    setRight(isA, loc);
+    setOn(isA, loc);
+}
+
+
+/* public */
+void
+RelateEdge::setUnknownLocations(bool isA, Location loc)
+{
+    if (! isKnown(isA, Position::LEFT)) {
+        setLocation(isA, Position::LEFT, loc);
+    }
+    if (! isKnown(isA, Position::RIGHT)) {
+        setLocation(isA, Position::RIGHT, loc);
+    }
+    if (! isKnown(isA, Position::ON)) {
+        setLocation(isA, Position::ON, loc);
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setLeft(bool isA, Location loc)
+{
+    if (isA) {
+        aLocLeft = loc;
+    }
+    else {
+        bLocLeft = loc;
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setRight(bool isA, Location loc)
+{
+    if (isA) {
+        aLocRight = loc;
+    }
+    else {
+        bLocRight = loc;
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setOn(bool isA, Location loc)
+{
+    if (isA) {
+        aLocLine = loc;
+    }
+    else {
+        bLocLine = loc;
+    }
+}
+
+
+/* public */
+Location
+RelateEdge::location(bool isA, int position) const
+{
+    if (isA) {
+        switch (position) {
+            case Position::LEFT: return aLocLeft;
+            case Position::RIGHT: return aLocRight;
+            case Position::ON: return aLocLine;
+        }
+    }
+    else {
+        switch (position) {
+            case Position::LEFT: return bLocLeft;
+            case Position::RIGHT: return bLocRight;
+            case Position::ON: return bLocLine;
+        }
+    }
+    assert(false && "never get here");
+    return LOC_UNKNOWN;
+}
+
+/* private */
+int
+RelateEdge::dimension(bool isA) const
+{
+    return isA ? aDim : bDim;
+}
+
+/* private */
+bool
+RelateEdge::isKnown(bool isA) const
+{
+    if (isA)
+        return aDim != DIM_UNKNOWN;
+    return bDim != DIM_UNKNOWN;
+}
+
+/* private */
+bool
+RelateEdge::isKnown(bool isA, int pos) const
+{
+    return location(isA, pos) != LOC_UNKNOWN;
+}
+
+
+/* public */
+bool
+RelateEdge::isInterior(bool isA, int position) const
+{
+    return location(isA, position) == Location::INTERIOR;
+}
+
+
+/* public */
+void
+RelateEdge::setDimLocations(bool isA, int dim, Location loc)
+{
+    if (isA) {
+        aDim = dim;
+        aLocLeft = loc;
+        aLocRight = loc;
+        aLocLine = loc;
+    }
+    else {
+        bDim = dim;
+        bLocLeft = loc;
+        bLocRight = loc;
+        bLocLine = loc;
+    }
+}
+
+
+/* public */
+void
+RelateEdge::setAreaInterior(bool isA)
+{
+    if (isA) {
+        aLocLeft = Location::INTERIOR;
+        aLocRight = Location::INTERIOR;
+        aLocLine = Location::INTERIOR;
+    }
+    else {
+        bLocLeft = Location::INTERIOR;
+        bLocRight = Location::INTERIOR;
+        bLocLine = Location::INTERIOR;
+    }
+}
+
+
+/* public */
+std::string
+RelateEdge::toString() const
+{
+    std::stringstream ss;
+    ss << WKTWriter::toLineString(*(node->getCoordinate()), *dirPt);
+    ss << " - " << labelString();
+    return ss.str();
+}
+
+
+/* private */
+std::string
+RelateEdge::labelString() const
+{
+    std::stringstream ss;
+    ss << "A:";
+    ss << locationString(RelateGeometry::GEOM_A);
+    ss << "/B:";
+    ss << locationString(RelateGeometry::GEOM_B);
+    return ss.str();
+}
+
+
+/* private */
+std::string
+RelateEdge::locationString(bool isA) const
+{
+    std::stringstream ss;
+    ss << location(isA, Position::LEFT);
+    ss << location(isA, Position::ON);
+    ss << location(isA, Position::RIGHT);
+    return ss.str();
+}
+
+
+/* public friend */
+std::ostream&
+operator<<(std::ostream& os, const RelateEdge& re)
+{
+    os << re.toString();
+    return os;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/Sources/geos/src/operation/relateng/RelateGeometry.cpp b/Sources/geos/src/operation/relateng/RelateGeometry.cpp
new file mode 100644
index 0000000..668968e
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/RelateGeometry.cpp
@@ -0,0 +1,521 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+using geos::algorithm::BoundaryNodeRule;
+using geos::algorithm::Orientation;
+using namespace geos::geom;
+using geos::geom::util::ComponentCoordinateExtracter;
+using geos::geom::util::GeometryLister;
+using geos::geom::util::PointExtracter;
+using geos::operation::valid::RepeatedPointRemover;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+RelateGeometry::RelateGeometry(const Geometry* input, bool isPrepared, const BoundaryNodeRule& bnRule)
+    : geom(input)
+    , m_isPrepared(isPrepared)
+    , geomEnv(input->getEnvelopeInternal())
+    , boundaryNodeRule(bnRule)
+    , geomDim(input->getDimension())
+    , isLineZeroLen(isZeroLengthLine(input))
+    , isGeomEmpty(input->isEmpty())
+{
+    analyzeDimensions();
+}
+
+
+/* public static */
+std::string
+RelateGeometry::name(bool isA)
+{
+    return isA ? "A" : "B";
+}
+
+
+/* private */
+void
+RelateGeometry::analyzeDimensions()
+{
+    if (isGeomEmpty) {
+        return;
+    }
+    GeometryTypeId typeId = geom->getGeometryTypeId();
+    if (typeId == GEOS_POINT || typeId == GEOS_MULTIPOINT) {
+        hasPoints = true;
+        geomDim = Dimension::P;
+        return;
+    }
+    if (typeId == GEOS_LINESTRING || typeId == GEOS_LINEARRING || typeId == GEOS_MULTILINESTRING) {
+        hasLines = true;
+        geomDim = Dimension::L;
+        return;
+    }
+    if (typeId == GEOS_POLYGON || typeId == GEOS_MULTIPOLYGON) {
+        hasAreas = true;
+        geomDim = Dimension::A;
+        return;
+    }
+    //-- analyze a (possibly mixed type) collection
+    std::vector elems;
+    GeometryLister::list(geom, elems);
+    for (const Geometry* elem : elems)
+    {
+        if (elem->isEmpty())
+            continue;
+        if (elem->getGeometryTypeId() == GEOS_POINT) {
+            hasPoints = true;
+            if (geomDim < Dimension::P) geomDim = Dimension::P;
+        }
+        if (elem->getGeometryTypeId() == GEOS_LINESTRING ||
+            elem->getGeometryTypeId() == GEOS_LINEARRING) {
+            hasLines = true;
+            if (geomDim < Dimension::L) geomDim = Dimension::L;
+        }
+        if (elem->getGeometryTypeId() == GEOS_POLYGON) {
+            hasAreas = true;
+            if (geomDim < Dimension::A) geomDim = Dimension::A;
+        }
+    }
+}
+
+
+/* private static */
+bool
+RelateGeometry::isZeroLength(const Geometry* geom)
+{
+    std::vector elems;
+    GeometryLister::list(geom, elems);
+    for (const Geometry* elem : elems) {
+        if (elem->getGeometryTypeId() == GEOS_LINESTRING ||
+            elem->getGeometryTypeId() == GEOS_LINEARRING ) {
+            if (! isZeroLength(static_cast(elem))) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+/* private static */
+bool
+RelateGeometry::isZeroLength(const LineString* line) {
+    if (line->getNumPoints() >= 2) {
+        const CoordinateXY& p0 = line->getCoordinateN(0);
+        for (std::size_t i = 1; i < line->getNumPoints(); i++) {
+            // NOTE !!! CHANGE FROM JTS, original below
+            // const CoordinateXY& pi = line.getCoordinateN(1);
+            const CoordinateXY& pi = line->getCoordinateN(i);
+            //-- most non-zero-len lines will trigger this right away
+            if (! p0.equals2D(pi)) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+
+/* public */
+int
+RelateGeometry::getDimensionReal() const
+{
+    if (isGeomEmpty)
+        return Dimension::False;
+    if (getDimension() == Dimension::L && isLineZeroLen)
+        return Dimension::P;
+    if (hasAreas)
+        return Dimension::A;
+    if (hasLines)
+        return Dimension::L;
+    return Dimension::P;
+}
+
+
+/* public */
+bool
+RelateGeometry::hasEdges() const
+{
+    return hasLines || hasAreas;
+}
+
+/* private */
+RelatePointLocator*
+RelateGeometry::getLocator()
+{
+    if (locator == nullptr)
+        locator.reset(new RelatePointLocator(geom, m_isPrepared, boundaryNodeRule));
+    return locator.get();
+}
+
+
+/* public */
+bool
+RelateGeometry::isNodeInArea(const CoordinateXY* nodePt, const Geometry* parentPolygonal)
+{
+    int dimLoc = getLocator()->locateNodeWithDim(nodePt, parentPolygonal);
+    return dimLoc == DimensionLocation::AREA_INTERIOR;
+}
+
+
+/* public */
+int
+RelateGeometry::locateLineEndWithDim(const CoordinateXY* p)
+{
+    return getLocator()->locateLineEndWithDim(p);
+}
+
+
+/* public */
+Location
+RelateGeometry::locateAreaVertex(const CoordinateXY* pt)
+{
+    /**
+     * Can pass a null polygon, because the point is an exact vertex,
+     * which will be detected as being on the boundary of its polygon
+     */
+    return locateNode(pt, nullptr);
+}
+
+
+/* public */
+Location
+RelateGeometry::locateNode(const CoordinateXY* pt, const Geometry* parentPolygonal)
+{
+    return getLocator()->locateNode(pt, parentPolygonal);
+}
+
+
+/* public */
+int
+RelateGeometry::locateWithDim(const CoordinateXY* pt)
+{
+    int loc = getLocator()->locateWithDim(pt);
+    return loc;
+}
+
+
+/* public */
+bool
+RelateGeometry::isSelfNodingRequired() const
+{
+    GeometryTypeId typeId = geom->getGeometryTypeId();
+    if (typeId == GEOS_POINT
+        || typeId == GEOS_MULTIPOINT
+        || typeId == GEOS_POLYGON
+        || typeId == GEOS_MULTIPOLYGON)
+    {
+         return false;
+     }
+    //-- a GC with a single polygon does not need noding
+    if (hasAreas && geom->getNumGeometries() == 1)
+        return false;
+    return true;
+}
+
+
+/* public */
+bool
+RelateGeometry::isPolygonal() const
+{
+    //TODO: also true for a GC containing one polygonal element (and possibly some lower-dimension elements)
+    GeometryTypeId typeId = geom->getGeometryTypeId();
+    return typeId == GEOS_POLYGON
+        || typeId == GEOS_MULTIPOLYGON;
+}
+
+
+/* public */
+bool
+RelateGeometry::isEmpty() const
+{
+    return isGeomEmpty;
+}
+
+
+/* public */
+bool
+RelateGeometry::hasBoundary()
+{
+    return getLocator()->hasBoundary();
+}
+
+
+/* public */
+Coordinate::ConstXYSet&
+RelateGeometry::getUniquePoints()
+{
+    if (uniquePoints.empty()) {
+        uniquePoints = createUniquePoints();
+    }
+    return uniquePoints;
+}
+
+
+/* private */
+Coordinate::ConstXYSet
+RelateGeometry::createUniquePoints()
+{
+    //-- only called on P geometries
+    std::vector pts;
+    ComponentCoordinateExtracter::getCoordinates(*geom, pts);
+    Coordinate::ConstXYSet set(pts.begin(), pts.end());
+    return set;
+}
+
+
+/* public */
+std::vector
+RelateGeometry::getEffectivePoints()
+{
+    std::vector ptListAll;
+    geom::util::PointExtracter::getPoints(*geom, ptListAll);
+
+    if (getDimensionReal() <= Dimension::P)
+        return ptListAll;
+
+    //-- only return Points not covered by another element
+    std::vector ptList;
+    for (const Point* p : ptListAll) {
+        if (p->isEmpty())
+            continue;
+        int locDim = locateWithDim(p->getCoordinate());
+        if (DimensionLocation::dimension(locDim) == Dimension::P) {
+            ptList.push_back(p);
+        }
+    }
+    return ptList;
+}
+
+
+/* public */
+std::vector
+RelateGeometry::extractSegmentStrings(bool isA, const Envelope* env)
+{
+    std::vector segStrings;
+
+    // When we get called in the context of a prepared geometry
+    // geomA might already have segments extracted and stored,
+    // so check and reuse them if possible
+    if (isA && isPrepared() && env == nullptr) {
+        if (segStringPermStore.empty()) {
+            extractSegmentStrings(isA, env, geom, segStrings, segStringPermStore);
+        }
+        else {
+            for (auto& ss : segStringPermStore) {
+                segStrings.push_back(ss.get());
+            }
+        }
+    }
+    // In the context of geomB we always extract for each call,
+    // and same goes for geomA when not in prepared mode, or when
+    // using an envelope filter.
+    else {
+        segStringTempStore.clear();
+        extractSegmentStrings(isA, env, geom, segStrings, segStringTempStore);
+    }
+    return segStrings;
+}
+
+
+/* private */
+void
+RelateGeometry::extractSegmentStrings(bool isA,
+    const Envelope* env, const Geometry* p_geom,
+    std::vector& segStrings,
+    std::vector>& segStore)
+{
+    //-- record if parent is MultiPolygon
+    const MultiPolygon* parentPolygonal = nullptr;
+    if (p_geom->getGeometryTypeId() == GEOS_MULTIPOLYGON) {
+        parentPolygonal = static_cast(p_geom);
+    }
+
+    for (std::size_t i = 0; i < p_geom->getNumGeometries(); i++) {
+        const Geometry* g = p_geom->getGeometryN(i);
+        // if (g->getGeometryTypeId() == GEOS_GEOMETRYCOLLECTION) {
+        if (g->isCollection()) {
+            extractSegmentStrings(isA, env, g, segStrings, segStore);
+        }
+        else {
+            extractSegmentStringsFromAtomic(isA, g, parentPolygonal, env, segStrings, segStore);
+        }
+    }
+}
+
+
+/* private */
+void
+RelateGeometry::extractSegmentStringsFromAtomic(bool isA,
+    const Geometry* p_geom, const MultiPolygon* parentPolygonal,
+    const Envelope* env,
+    std::vector& segStrings,
+    std::vector>& segStore)
+{
+    if (p_geom->isEmpty())
+        return;
+
+    bool doExtract = (env == nullptr) || env->intersects(p_geom->getEnvelopeInternal());
+    if (! doExtract)
+        return;
+
+    elementId++;
+    if (p_geom->getGeometryTypeId() == GEOS_LINESTRING ||
+        p_geom->getGeometryTypeId() == GEOS_LINEARRING) {
+        const LineString* line = static_cast(p_geom);
+        /*
+         * Condition the input Coordinate sequence so that it has no repeated points.
+         * This requires taking a copy which removeRepeated does behind the scenes and stores in csStore.
+         */
+        const CoordinateSequence* cs = removeRepeated(line->getCoordinatesRO());
+        auto ss = RelateSegmentString::createLine(cs, isA, elementId, this);
+        segStore.emplace_back(ss);
+        segStrings.push_back(ss);
+    }
+    else if (p_geom->getGeometryTypeId() == GEOS_POLYGON) {
+        const Polygon* poly = static_cast(p_geom);
+        const Geometry* parentPoly;
+        if (parentPolygonal != nullptr)
+            parentPoly = static_cast(parentPolygonal);
+        else
+            parentPoly = static_cast(poly);
+        extractRingToSegmentString(isA, poly->getExteriorRing(), 0, env, parentPoly, segStrings, segStore);
+        for (uint32_t i = 0; i < poly->getNumInteriorRing(); i++) {
+            extractRingToSegmentString(isA, poly->getInteriorRingN(i), static_cast(i+1), env, parentPoly, segStrings, segStore);
+        }
+    }
+}
+
+
+/* private */
+void
+RelateGeometry::extractRingToSegmentString(bool isA,
+    const LinearRing* ring, int ringId, const Envelope* env,
+    const Geometry* parentPoly,
+    std::vector& segStrings,
+    std::vector>& segStore)
+{
+    if (ring->isEmpty())
+        return;
+    if (env != nullptr && ! env->intersects(ring->getEnvelopeInternal()))
+        return;
+
+    /*
+     * Condition the input Coordinate sequence so that it has no repeated points
+     * and is oriented in a deterministic way. This requires taking a copy which
+     * orientAndRemoveRepeated does behind the scenes and stores in csStore.
+     */
+    bool requireCW = (ringId == 0);
+    const CoordinateSequence* cs = orientAndRemoveRepeated(ring->getCoordinatesRO(), requireCW);
+    auto ss = RelateSegmentString::createRing(cs, isA, elementId, ringId, parentPoly, this);
+    segStore.emplace_back(ss);
+    segStrings.push_back(ss);
+}
+
+
+/* public */
+std::string
+RelateGeometry::toString() const
+{
+    return geom->toString();
+}
+
+
+/* public friend */
+std::ostream&
+operator<<(std::ostream& os, const RelateGeometry& rg)
+{
+    os << rg.toString();
+    return os;
+}
+
+
+
+/* private */
+const CoordinateSequence *
+RelateGeometry::orientAndRemoveRepeated(const CoordinateSequence *seq, bool orientCW)
+{
+    bool isFlipped = (orientCW == Orientation::isCCW(seq));
+    bool hasRepeated = seq->hasRepeatedPoints();
+    /* Already conditioned */
+    if (!isFlipped && !hasRepeated) {
+        return seq;
+    }
+
+    if (hasRepeated) {
+        auto deduped = RepeatedPointRemover::removeRepeatedPoints(seq);
+        if (isFlipped)
+            deduped->reverse();
+        CoordinateSequence* cs = deduped.release();
+        csStore.emplace_back(cs);
+        return cs;
+    }
+
+    if (isFlipped) {
+        auto reversed = seq->clone();
+        reversed->reverse();
+        CoordinateSequence* cs = reversed.release();
+        csStore.emplace_back(cs);
+        return cs;
+    }
+
+    return seq;
+}
+
+/* private */
+const CoordinateSequence *
+RelateGeometry::removeRepeated(const CoordinateSequence *seq)
+{
+    bool hasRepeated = seq->hasRepeatedPoints();
+    if (!hasRepeated)
+        return seq;
+    auto deduped = RepeatedPointRemover::removeRepeatedPoints(seq);
+    CoordinateSequence* cs = deduped.release();
+    csStore.emplace_back(cs);
+    return cs;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
diff --git a/Sources/geos/src/operation/relateng/RelateNG.cpp b/Sources/geos/src/operation/relateng/RelateNG.cpp
new file mode 100644
index 0000000..9338123
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/RelateNG.cpp
@@ -0,0 +1,703 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+using namespace geos::geom;
+using geos::algorithm::BoundaryNodeRule;
+using geos::noding::MCIndexSegmentSetMutualIntersector;
+using geos::noding::SegmentString;
+using geos::geom::util::GeometryLister;
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+#define GEOM_A RelateGeometry::GEOM_A
+#define GEOM_B RelateGeometry::GEOM_B
+
+
+/************************************************************************/
+
+/* public static */
+bool
+RelateNG::intersects(const Geometry* a, const Geometry* b)
+{
+    RelatePredicate::IntersectsPredicate pred;
+    return RelateNG::relate(a, b, pred);
+}
+
+/* public static */
+bool
+RelateNG::crosses(const Geometry* a, const Geometry* b)
+{
+    RelatePredicate::CrossesPredicate pred;
+    return RelateNG::relate(a, b, pred);
+}
+
+/* public static */
+bool
+RelateNG::disjoint(const Geometry* a, const Geometry* b)
+{
+    RelatePredicate::DisjointPredicate pred;
+    return RelateNG::relate(a, b, pred);
+}
+
+/* public static */
+bool
+RelateNG::touches(const Geometry* a, const Geometry* b)
+{
+    RelatePredicate::TouchesPredicate pred;
+    return RelateNG::relate(a, b, pred);
+}
+
+/* public static */
+bool
+RelateNG::within(const Geometry* a, const Geometry* b)
+{
+    RelatePredicate::WithinPredicate pred;
+    return RelateNG::relate(a, b, pred);
+}
+
+/* public static */
+bool
+RelateNG::contains(const Geometry* a, const Geometry* b)
+{
+    RelatePredicate::ContainsPredicate pred;
+    return RelateNG::relate(a, b, pred);
+}
+
+/* public static */
+bool
+RelateNG::overlaps(const Geometry* a, const Geometry* b)
+{
+    RelatePredicate::OverlapsPredicate pred;
+    return RelateNG::relate(a, b, pred);
+}
+
+/* public static */
+bool
+RelateNG::covers(const Geometry* a, const Geometry* b)
+{
+    RelatePredicate::CoversPredicate pred;
+    return RelateNG::relate(a, b, pred);
+}
+
+/* public static */
+bool
+RelateNG::coveredBy(const Geometry* a, const Geometry* b)
+{
+    RelatePredicate::CoveredByPredicate pred;
+    return RelateNG::relate(a, b, pred);
+}
+
+/* public static */
+bool
+RelateNG::equalsTopo(const Geometry* a, const Geometry* b)
+{
+    RelatePredicate::EqualsTopoPredicate pred;
+    return RelateNG::relate(a, b, pred);
+}
+
+/* public static */
+bool
+RelateNG::relate(const Geometry* a, const Geometry* b, TopologyPredicate& pred)
+{
+    RelateNG rng(a, false);
+    return rng.evaluate(b, pred);
+}
+
+/* public static */
+bool
+RelateNG::relate(const Geometry* a, const Geometry* b, TopologyPredicate& pred, const BoundaryNodeRule& bnRule)
+{
+    RelateNG rng(a, false, bnRule);
+    return rng.evaluate(b, pred);
+}
+
+/* public static */
+bool
+RelateNG::relate(const Geometry* a, const Geometry* b, const std::string& imPattern)
+{
+    RelateNG rng(a, false);
+    return rng.evaluate(b, imPattern);
+}
+
+/* public static */
+std::unique_ptr
+RelateNG::relate(const Geometry* a, const Geometry* b)
+{
+    RelateNG rng(a, false);
+    return rng.evaluate(b);
+}
+
+/* public static */
+std::unique_ptr
+RelateNG::relate(const Geometry* a, const Geometry* b, const BoundaryNodeRule& bnRule)
+{
+    RelateNG rng(a, false, bnRule);
+    return rng.evaluate(b);
+}
+
+/************************************************************************/
+
+/* public */
+bool
+RelateNG::intersects(const Geometry* b)
+{
+    RelatePredicate::IntersectsPredicate pred;
+    return evaluate(b, pred);
+}
+
+/* public */
+bool
+RelateNG::crosses(const Geometry* b)
+{
+    RelatePredicate::CrossesPredicate pred;
+    return evaluate(b, pred);
+}
+
+/* public */
+bool
+RelateNG::disjoint(const Geometry* b)
+{
+    RelatePredicate::DisjointPredicate pred;
+    return evaluate(b, pred);
+}
+
+/* public */
+bool
+RelateNG::touches(const Geometry* b)
+{
+    RelatePredicate::TouchesPredicate pred;
+    return evaluate(b, pred);
+}
+
+/* public */
+bool
+RelateNG::within(const Geometry* a)
+{
+    RelatePredicate::WithinPredicate pred;
+    return evaluate(a, pred);
+}
+
+/* public */
+bool
+RelateNG::contains(const Geometry* b)
+{
+    RelatePredicate::ContainsPredicate pred;
+    return evaluate(b, pred);
+}
+
+/* public */
+bool
+RelateNG::overlaps(const Geometry* b)
+{
+    RelatePredicate::OverlapsPredicate pred;
+    return evaluate(b, pred);
+}
+
+/* public */
+bool
+RelateNG::covers(const Geometry* b)
+{
+    RelatePredicate::CoversPredicate pred;
+    return evaluate(b, pred);
+}
+
+/* public */
+bool
+RelateNG::coveredBy(const Geometry* b)
+{
+    RelatePredicate::CoveredByPredicate pred;
+    return evaluate(b, pred);
+}
+
+/* public */
+bool
+RelateNG::equalsTopo(const Geometry* b)
+{
+    RelatePredicate::EqualsTopoPredicate pred;
+    return evaluate(b, pred);
+}
+
+/* public */
+bool
+RelateNG::relate(const Geometry* b, const std::string& imPattern)
+{
+    return evaluate(b, imPattern);
+}
+
+/* public */
+std::unique_ptr
+RelateNG::relate(const Geometry* b)
+{
+    return evaluate(b);
+}
+
+/************************************************************************/
+
+/* public static */
+std::unique_ptr
+RelateNG::prepare(const Geometry* a)
+{
+    return std::unique_ptr(new RelateNG(a, true));
+}
+
+
+/* public static */
+std::unique_ptr
+RelateNG::prepare(const Geometry* a, const BoundaryNodeRule& bnRule)
+{
+    return std::unique_ptr(new RelateNG(a, true, bnRule));
+}
+
+/************************************************************************/
+
+/* public */
+std::unique_ptr
+RelateNG::evaluate(const Geometry* b)
+{
+    RelateMatrixPredicate rel;
+    evaluate(b, rel);
+    return rel.getIM();
+}
+
+
+/* public */
+bool
+RelateNG::evaluate(const Geometry* b, const std::string& imPattern)
+{
+    auto predicate = RelatePredicate::matches(imPattern);
+    return evaluate(b, *predicate);
+}
+
+
+/* public */
+bool
+RelateNG::evaluate(const Geometry* b, TopologyPredicate& predicate)
+{
+    //-- fast envelope checks
+    if (! hasRequiredEnvelopeInteraction(b, predicate)) {
+        return false;
+    }
+
+    geos::util::ensureNoCurvedComponents(geomA.getGeometry());
+    geos::util::ensureNoCurvedComponents(b);
+    
+    RelateGeometry geomB(b, boundaryNodeRule);
+
+    int dimA = geomA.getDimensionReal();
+    int dimB = geomB.getDimensionReal();
+
+    //-- check if predicate is determined by dimension or envelope
+    predicate.init(dimA, dimB);
+    if (predicate.isKnown())
+        return finishValue(predicate);
+
+    predicate.init(*(geomA.getEnvelope()), *(geomB.getEnvelope()));
+    if (predicate.isKnown())
+        return finishValue(predicate);
+
+    TopologyComputer topoComputer(predicate, geomA, geomB);
+
+    //-- optimized P/P evaluation
+    if (dimA == Dimension::P && dimB == Dimension::P) {
+        computePP(geomB, topoComputer);
+        topoComputer.finish();
+        return topoComputer.getResult();
+    }
+
+    //-- test points against (potentially) indexed geometry first
+    computeAtPoints(geomB, GEOM_B, geomA, topoComputer);
+    if (topoComputer.isResultKnown()) {
+        return topoComputer.getResult();
+    }
+    computeAtPoints(geomA, GEOM_A, geomB, topoComputer);
+    if (topoComputer.isResultKnown()) {
+        return topoComputer.getResult();
+    }
+
+    if (geomA.hasEdges() && geomB.hasEdges()) {
+        computeAtEdges(geomB, topoComputer);
+    }
+
+    //-- after all processing, set remaining unknown values in IM
+    topoComputer.finish();
+    return topoComputer.getResult();
+}
+
+
+/* private */
+bool
+RelateNG::hasRequiredEnvelopeInteraction(const Geometry* b, TopologyPredicate& predicate)
+{
+    const Envelope* envB = b->getEnvelopeInternal();
+    bool isInteracts = false;
+    if (predicate.requireCovers(GEOM_A)) {
+        if (! geomA.getEnvelope()->covers(envB)) {
+            return false;
+        }
+        isInteracts = true;
+    }
+    else if (predicate.requireCovers(GEOM_B)) {
+        if (! envB->covers(geomA.getEnvelope())) {
+            return false;
+        }
+        isInteracts = true;
+    }
+    if (! isInteracts
+        && predicate.requireInteraction()
+        && ! geomA.getEnvelope()->intersects(envB)) {
+        return false;
+    }
+    return true;
+}
+
+/* private */
+bool
+RelateNG::finishValue(TopologyPredicate& predicate)
+{
+    predicate.finish();
+    return predicate.value();
+}
+
+
+/* private */
+void
+RelateNG::computePP(RelateGeometry& geomB, TopologyComputer& topoComputer)
+{
+    Coordinate::ConstXYSet& ptsA = geomA.getUniquePoints();
+    //TODO: only query points in interaction extent?
+    Coordinate::ConstXYSet& ptsB = geomB.getUniquePoints();
+
+    uint32_t numBinA = 0;
+    for (const CoordinateXY* ptB : ptsB) {
+        auto it = ptsA.find(ptB);
+        bool found = (it != ptsA.end());
+        if (found) {
+            numBinA++;
+            topoComputer.addPointOnPointInterior(ptB);
+        }
+        else {
+            topoComputer.addPointOnPointExterior(GEOM_B, ptB);
+        }
+        if (topoComputer.isResultKnown()) {
+            return;
+        }
+    }
+    /**
+     * If number of matched B points is less than size of A,
+     * there must be at least one A point in the exterior of B
+     */
+    if (numBinA < ptsA.size()) {
+        //TODO: determine actual exterior point?
+        topoComputer.addPointOnPointExterior(GEOM_A, nullptr);
+    }
+}
+
+
+/* private */
+void
+RelateNG::computeAtPoints(
+    RelateGeometry& geom, bool isA,
+    RelateGeometry& geomTarget, TopologyComputer& topoComputer)
+{
+    bool isResultKnown = false;
+    isResultKnown = computePoints(geom, isA, geomTarget, topoComputer);
+    if (isResultKnown)
+        return;
+
+    /**
+     * Performance optimization: only check points against target
+     * if it has areas OR if the predicate requires checking for
+     * exterior interaction.
+     * In particular, this avoids testing line ends against lines
+     * for the intersects predicate (since these are checked
+     * during segment/segment intersection checking anyway).
+     * Checking points against areas is necessary, since the input
+     * linework is disjoint if one input lies wholly inside an area,
+     * so segment intersection checking is not sufficient.
+     */
+    bool checkDisjointPoints = geomTarget.hasDimension(Dimension::A)
+                                || topoComputer.isExteriorCheckRequired(isA);
+    if (! checkDisjointPoints)
+        return;
+
+    isResultKnown = computeLineEnds(geom, isA, geomTarget, topoComputer);
+    if (isResultKnown)
+        return;
+
+    computeAreaVertex(geom, isA, geomTarget, topoComputer);
+}
+
+
+/* private */
+bool
+RelateNG::computePoints(
+    RelateGeometry& geom, bool isA,
+    RelateGeometry& geomTarget, TopologyComputer& topoComputer)
+{
+    if (! geom.hasDimension(Dimension::P)) {
+        return false;
+    }
+
+    std::vector points = geom.getEffectivePoints();
+    for (const Point* point : points) {
+        //TODO: exit when all possible target locations (E,I,B) have been found?
+        if (point->isEmpty())
+            continue;
+
+        const CoordinateXY* pt = point->getCoordinate();
+        computePoint(isA, pt, geomTarget, topoComputer);
+        if (topoComputer.isResultKnown()) {
+            return true;
+        }
+    }
+    return false;
+}
+
+
+/* private */
+void
+RelateNG::computePoint(bool isA, const CoordinateXY* pt, RelateGeometry& geomTarget, TopologyComputer& topoComputer)
+{
+      int locDimTarget = geomTarget.locateWithDim(pt);
+      Location locTarget = DimensionLocation::location(locDimTarget);
+      int dimTarget = DimensionLocation::dimension(locDimTarget, topoComputer.getDimension(! isA));
+      topoComputer.addPointOnGeometry(isA, locTarget, dimTarget, pt);
+}
+
+
+/* private */
+bool
+RelateNG::computeLineEnds(
+    RelateGeometry& geom, bool isA,
+    RelateGeometry& geomTarget, TopologyComputer& topoComputer)
+{
+    if (! geom.hasDimension(Dimension::L)) {
+        return false;
+    }
+
+    bool hasExteriorIntersection = false;
+    std::vector elems;
+    GeometryLister::list(geom.getGeometry(), elems);
+    for (const Geometry* elem : elems) {
+        if (elem->isEmpty())
+            continue;
+
+        if (elem->getGeometryTypeId() == GEOS_LINESTRING ||
+            elem->getGeometryTypeId() == GEOS_LINEARRING) {
+            //-- once an intersection with target exterior is recorded, skip further known-exterior points
+            if (hasExteriorIntersection
+                && elem->getEnvelopeInternal()->disjoint(geomTarget.getEnvelope()))
+                continue;
+
+            const LineString* line = static_cast(elem);
+            //TODO: add optimzation to skip disjoint elements once exterior point found
+            const CoordinateXY& e0 = line->getCoordinatesRO()->getAt(0);
+            hasExteriorIntersection |= computeLineEnd(geom, isA, &e0, geomTarget, topoComputer);
+            if (topoComputer.isResultKnown()) {
+                return true;
+            }
+
+            if (! line->isClosed()) {
+                const CoordinateXY& e1 = line->getCoordinatesRO()->getAt(line->getNumPoints() - 1);
+                hasExteriorIntersection |= computeLineEnd(geom, isA, &e1, geomTarget, topoComputer);
+                if (topoComputer.isResultKnown()) {
+                    return true;
+                }
+            }
+        //TODO: break when all possible locations have been found?
+        }
+    }
+    return false;
+}
+
+
+/* private */
+bool
+RelateNG::computeLineEnd(RelateGeometry& geom, bool isA, const CoordinateXY* pt,
+      RelateGeometry& geomTarget, TopologyComputer& topoComputer)
+{
+    int locDimLineEnd = geom.locateLineEndWithDim(pt);
+    int dimLineEnd = DimensionLocation::dimension(locDimLineEnd, topoComputer.getDimension(isA));
+    //-- skip line ends which are in a GC area
+    if (dimLineEnd != Dimension::L)
+        return false;
+    Location locLineEnd = DimensionLocation::location(locDimLineEnd);
+    int locDimTarget = geomTarget.locateWithDim(pt);
+    Location locTarget = DimensionLocation::location(locDimTarget);
+    int dimTarget = DimensionLocation::dimension(locDimTarget, topoComputer.getDimension(! isA));
+    topoComputer.addLineEndOnGeometry(isA, locLineEnd, locTarget, dimTarget, pt);
+    return locTarget == Location::EXTERIOR;
+}
+
+/* private */
+bool
+RelateNG::computeAreaVertex(RelateGeometry& geom, bool isA, RelateGeometry& geomTarget, TopologyComputer& topoComputer)
+{
+    if (! geom.hasDimension(Dimension::A)) {
+        return false;
+    }
+    //-- evaluate for line and area targets only, since points are handled in the reverse direction
+    if (geomTarget.getDimension() < Dimension::L)
+        return false;
+
+    bool hasExteriorIntersection = false;
+
+    std::vector elems;
+    GeometryLister::list(geom.getGeometry(), elems);
+    for (const Geometry* elem : elems) {
+        if (elem->isEmpty())
+            continue;
+
+        if (elem->getGeometryTypeId() == GEOS_POLYGON) {
+            //-- once an intersection with target exterior is recorded, skip further known-exterior points
+            if (hasExteriorIntersection && elem->getEnvelopeInternal()->disjoint(geomTarget.getEnvelope()))
+                continue;
+
+            const Polygon* poly = static_cast(elem);
+            hasExteriorIntersection |= computeAreaVertex(geom, isA, poly->getExteriorRing(), geomTarget, topoComputer);
+            if (topoComputer.isResultKnown()) {
+                return true;
+            }
+            for (std::size_t j = 0; j < poly->getNumInteriorRing(); j++) {
+                hasExteriorIntersection |= computeAreaVertex(geom, isA, poly->getInteriorRingN(j), geomTarget, topoComputer);
+                if (topoComputer.isResultKnown()) {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
+
+/* private */
+bool
+RelateNG::computeAreaVertex(RelateGeometry& geom, bool isA, const LinearRing* ring, RelateGeometry& geomTarget, TopologyComputer& topoComputer)
+{
+    //TODO: use extremal (highest) point to ensure one is on boundary of polygon cluster
+    const CoordinateXY* pt = ring->getCoordinate();
+
+    Location locArea = geom.locateAreaVertex(pt);
+    int locDimTarget = geomTarget.locateWithDim(pt);
+    Location locTarget = DimensionLocation::location(locDimTarget);
+    int dimTarget = DimensionLocation::dimension(locDimTarget, topoComputer.getDimension(! isA));
+    topoComputer.addAreaVertex(isA, locArea, locTarget, dimTarget, pt);
+    return locTarget == Location::EXTERIOR;
+}
+
+
+/* private */
+void
+RelateNG::computeAtEdges(RelateGeometry& geomB, TopologyComputer& topoComputer)
+{
+    Envelope envInt;
+    geomA.getEnvelope()->intersection(*(geomB.getEnvelope()), envInt);
+    if (envInt.isNull())
+        return;
+
+    std::vector edgesB = geomB.extractSegmentStrings(GEOM_B, &envInt);
+    EdgeSegmentIntersector intersector(topoComputer);
+
+    if (topoComputer.isSelfNodingRequired()) {
+        computeEdgesAll(edgesB, &envInt, intersector);
+    }
+    else {
+        computeEdgesMutual(edgesB, &envInt, intersector);
+    }
+    if (topoComputer.isResultKnown()) {
+        return;
+    }
+
+    topoComputer.evaluateNodes();
+}
+
+
+/* private */
+void
+RelateNG::computeEdgesAll(std::vector& edgesB, const Envelope* envInt, EdgeSegmentIntersector& intersector)
+{
+    //TODO: find a way to reuse prepared index?
+    std::vector edgesA = geomA.extractSegmentStrings(GEOM_A, envInt);
+
+    EdgeSetIntersector edgeInt(edgesA, edgesB, envInt);
+    edgeInt.process(intersector);
+}
+
+
+/* private */
+void
+RelateNG::computeEdgesMutual(std::vector& edgesB, const Envelope* envInt, EdgeSegmentIntersector& intersector)
+{
+    //-- in prepared mode the A edge index is reused
+    if (edgeMutualInt == nullptr) {
+        const Envelope* envExtract = geomA.isPrepared() ? nullptr : envInt;
+        std::vector edgesA = geomA.extractSegmentStrings(GEOM_A, envExtract);
+        edgeMutualInt.reset(new MCIndexSegmentSetMutualIntersector(envExtract));
+        edgeMutualInt->setBaseSegments(&edgesA);
+
+    }
+
+    edgeMutualInt->setSegmentIntersector(&intersector);
+    edgeMutualInt->process(&edgesB);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
diff --git a/Sources/geos/src/operation/relateng/RelateNode.cpp b/Sources/geos/src/operation/relateng/RelateNode.cpp
new file mode 100644
index 0000000..0ec3045
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/RelateNode.cpp
@@ -0,0 +1,323 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+using geos::geom::Dimension;
+using geos::geom::Position;
+using geos::io::WKTWriter;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public */
+const CoordinateXY*
+RelateNode::getCoordinate() const
+{
+    return nodePt;
+}
+
+
+/* public */
+const std::vector>&
+RelateNode::getEdges() const
+{
+    return edges;
+}
+
+
+/* public */
+void
+RelateNode::addEdges(std::vector& nss)
+{
+    for (auto* ns : nss) {
+        addEdges(ns);
+    }
+}
+
+/* public */
+void
+RelateNode::addEdges(std::vector>& nss)
+{
+    for (auto& ns : nss) {
+        addEdges(ns.get());
+    }
+}
+
+/* private */
+std::size_t
+RelateNode::indexOf(
+    const std::vector>& vEdges,
+    const RelateEdge* edge) const
+{
+    for (std::size_t i = 0; i < vEdges.size(); i++)
+    {
+        const std::unique_ptr& e = vEdges[i];
+        if (e.get() == edge)
+            return i;
+    }
+    return INDEX_UNKNOWN;
+}
+
+
+/* public */
+void
+RelateNode::addEdges(const NodeSection* ns)
+{
+  //Debug.println("Adding NS: " + ns);
+    switch (ns->dimension()) {
+    case Dimension::L:
+        addLineEdge(ns->isA(), ns->getVertex(0));
+        addLineEdge(ns->isA(), ns->getVertex(1));
+        break;
+    case Dimension::A:
+        //-- assumes node edges have CW orientation (as per JTS norm)
+        //-- entering edge - interior on L
+        const RelateEdge* e0 = addAreaEdge(ns->isA(), ns->getVertex(0), false);
+        //-- exiting edge - interior on R
+        const RelateEdge* e1 = addAreaEdge(ns->isA(), ns->getVertex(1), true);
+
+        std::size_t index0 = indexOf(edges, e0);
+        std::size_t index1 = indexOf(edges, e1);
+        updateEdgesInArea(ns->isA(), index0, index1);
+        updateIfAreaPrev(ns->isA(), index0);
+        updateIfAreaNext(ns->isA(), index1);
+    }
+}
+
+
+/* private */
+void
+RelateNode::updateEdgesInArea(bool isA, std::size_t indexFrom, std::size_t indexTo)
+{
+    std::size_t index = nextIndex(edges, indexFrom);
+    while (index != indexTo) {
+        auto& edge = edges[index];
+        edge->setAreaInterior(isA);
+        index = nextIndex(edges, index);
+    }
+}
+
+
+/* private */
+void
+RelateNode::updateIfAreaPrev(bool isA, std::size_t index)
+{
+    std::size_t indexPrev = prevIndex(edges, index);
+    auto& edgePrev = edges[indexPrev];
+    if (edgePrev->isInterior(isA, Position::LEFT)) {
+        std::unique_ptr& edge = edges[index];
+        edge->setAreaInterior(isA);
+    }
+}
+
+
+/* private */
+void
+RelateNode::updateIfAreaNext(bool isA, std::size_t index)
+{
+    std::size_t indexNext = nextIndex(edges, index);
+    auto& edgeNext = edges[indexNext];
+    if (edgeNext->isInterior(isA, Position::RIGHT)) {
+        auto& edge = edges[index];
+        edge->setAreaInterior(isA);
+    }
+}
+
+
+/* private */
+const RelateEdge*
+RelateNode::addLineEdge(bool isA, const CoordinateXY* dirPt)
+{
+    return addEdge(isA, dirPt, Dimension::L, false);
+}
+
+
+/* private */
+const RelateEdge*
+RelateNode::addAreaEdge(bool isA, const CoordinateXY* dirPt, bool isForward)
+{
+    return addEdge(isA, dirPt, Dimension::A, isForward);
+}
+
+
+/* private */
+const RelateEdge*
+RelateNode::addEdge(bool isA, const CoordinateXY* dirPt, int dim, bool isForward)
+{
+    //-- check for well-formed edge - skip null or zero-len input
+    if (dirPt == nullptr)
+        return nullptr;
+    if (nodePt->equals2D(*dirPt))
+        return nullptr;
+
+    std::size_t insertIndex = INDEX_UNKNOWN;
+    for (std::size_t i = 0; i < edges.size(); i++) {
+        auto* e = edges[i].get();
+        int comp = e->compareToEdge(dirPt);
+        if (comp == 0) {
+            e->merge(isA, dim, isForward);
+            return e;
+        }
+        if (comp == 1) {
+            //-- found further edge, so insert a new edge at this position
+            insertIndex = i;
+            break;
+        }
+    }
+    //-- add a new edge
+    RelateEdge* e = RelateEdge::create(this, dirPt, isA, dim, isForward);
+    if (insertIndex == INDEX_UNKNOWN) {
+        //-- add edge at end of list
+        edges.emplace_back(e);
+    }
+    else {
+        //-- add edge before higher edge found
+        std::unique_ptr re(e);
+        edges.insert(
+            edges.begin() + static_cast(insertIndex),
+            std::move(re));
+    }
+    return e;
+}
+
+
+/* public */
+void
+RelateNode::finish(bool isAreaInteriorA, bool isAreaInteriorB)
+{
+    //Debug.println("finish Node.");
+    //Debug.println("Before: " + this);
+    finishNode(RelateGeometry::GEOM_A, isAreaInteriorA);
+    finishNode(RelateGeometry::GEOM_B, isAreaInteriorB);
+    //Debug.println("After: " + this);
+}
+
+
+/* private */
+void
+RelateNode::finishNode(bool isA, bool isAreaInterior)
+{
+    if (isAreaInterior) {
+        RelateEdge::setAreaInterior(edges, isA);
+    }
+    else {
+        std::size_t startIndex = RelateEdge::findKnownEdgeIndex(edges, isA);
+        //-- only interacting nodes are finished, so this should never happen
+        //Assert.isTrue(startIndex >= 0l, "Node at "+ nodePt + "does not have AB interaction");
+        propagateSideLocations(isA, startIndex);
+    }
+}
+
+
+/* private */
+void
+RelateNode::propagateSideLocations(bool isA, std::size_t startIndex)
+{
+    Location currLoc = edges[startIndex]->location(isA, Position::LEFT);
+    //-- edges are stored in CCW order
+    std::size_t index = nextIndex(edges, startIndex);
+    while (index != startIndex) {
+        const auto& e = edges[index];
+        e->setUnknownLocations(isA, currLoc);
+        currLoc = e->location(isA, Position::LEFT);
+        index = nextIndex(edges, index);
+    }
+}
+
+
+/* private static */
+std::size_t
+RelateNode::prevIndex(
+    std::vector>& list,
+    std::size_t index)
+{
+    if (index > 0 && index != INDEX_UNKNOWN) {
+        return index - 1;
+    }
+    //-- index == 0
+    return list.size() - 1;
+}
+
+
+/* private static */
+std::size_t
+RelateNode::nextIndex(
+    std::vector>& list,
+    std::size_t index)
+{
+    if (index >= list.size() - 1 || index == INDEX_UNKNOWN) {
+        return 0;
+    }
+    return index + 1;
+}
+
+
+/* public */
+bool
+RelateNode::hasExteriorEdge(bool isA)
+{
+    for (auto& e : edges) {
+        if (Location::EXTERIOR == e->location(isA, Position::LEFT) ||
+            Location::EXTERIOR == e->location(isA, Position::RIGHT))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+
+/* public */
+std::string
+RelateNode::toString() const
+{
+    std::stringstream ss;
+    ss << "Node[" << WKTWriter::toPoint(*nodePt) << "]:" << std::endl;
+    for (auto& e : edges) {
+        ss << e->toString() << std::endl;
+    }
+    return ss.str();
+}
+
+
+/* public friend */
+std::ostream&
+operator<<(std::ostream& os, const RelateNode& rn)
+{
+    os << rn.toString();
+    return os;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
diff --git a/Sources/geos/src/operation/relateng/RelatePointLocator.cpp b/Sources/geos/src/operation/relateng/RelatePointLocator.cpp
new file mode 100644
index 0000000..f727bc0
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/RelatePointLocator.cpp
@@ -0,0 +1,338 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+using geos::algorithm::PointLocation;
+using geos::algorithm::locate::IndexedPointInAreaLocator;
+using geos::algorithm::locate::PointOnGeometryLocator;
+using geos::algorithm::locate::SimplePointInAreaLocator;
+using namespace geos::geom;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* private */
+void
+RelatePointLocator::init(const Geometry* p_geom)
+{
+    //-- cache empty status, since may be checked many times
+    isEmpty = p_geom->isEmpty();
+    extractElements(p_geom);
+
+    if (!lines.empty()) {
+        lineBoundary.reset(new LinearBoundary(lines, boundaryRule));
+    }
+
+    if (!polygons.empty()) {
+        polyLocator.resize(polygons.size());
+    }
+}
+
+
+/* public */
+bool
+RelatePointLocator::hasBoundary() const
+{
+    return lineBoundary->hasBoundary();
+}
+
+
+/* private */
+void
+RelatePointLocator::extractElements(const Geometry* p_geom)
+{
+    if (p_geom->isEmpty())
+        return;
+
+    if (p_geom->getGeometryTypeId() == GEOS_POINT) {
+        addPoint(static_cast(p_geom));
+    }
+    else if (p_geom->getGeometryTypeId() == GEOS_LINESTRING ||
+             p_geom->getGeometryTypeId() == GEOS_LINEARRING) {
+        addLine(static_cast(p_geom));
+    }
+    else if (p_geom->getGeometryTypeId() == GEOS_POLYGON ||
+             p_geom->getGeometryTypeId() == GEOS_MULTIPOLYGON)
+    {
+        addPolygonal(p_geom);
+    }
+    else if (p_geom->isCollection()) {
+        for (std::size_t i = 0; i < p_geom->getNumGeometries(); i++) {
+            const Geometry* g = p_geom->getGeometryN(i);
+            extractElements(g);
+        }
+    }
+}
+
+
+/* private */
+void
+RelatePointLocator::addPoint(const Point* pt)
+{
+    points.insert(pt->getCoordinate());
+}
+
+
+/* private */
+void
+RelatePointLocator::addLine(const LineString* line)
+{
+    lines.push_back(line);
+}
+
+
+/* private */
+void
+RelatePointLocator::addPolygonal(const Geometry* polygonal)
+{
+    polygons.push_back(polygonal);
+}
+
+
+/* public */
+Location
+RelatePointLocator::locate(const CoordinateXY* p)
+{
+    return DimensionLocation::location(locateWithDim(p));
+}
+
+
+/* public */
+int
+RelatePointLocator::locateLineEndWithDim(const CoordinateXY* p)
+{
+    if (!polygons.empty()) {
+        Location locPoly = locateOnPolygons(p, false, nullptr);
+        if (locPoly != Location::EXTERIOR)
+            return DimensionLocation::locationArea(locPoly);
+    }
+    return lineBoundary->isBoundary(p)
+        ? DimensionLocation::LINE_BOUNDARY
+        : DimensionLocation::LINE_INTERIOR;
+}
+
+
+/* public */
+Location
+RelatePointLocator::locateNode(const CoordinateXY* p, const Geometry* parentPolygonal)
+{
+    return DimensionLocation::location(locateNodeWithDim(p, parentPolygonal));
+}
+
+
+/* public */
+int
+RelatePointLocator::locateNodeWithDim(const CoordinateXY* p, const Geometry* parentPolygonal)
+{
+    return locateWithDim(p, true, parentPolygonal);
+}
+
+
+/* public */
+int
+RelatePointLocator::locateWithDim(const CoordinateXY* p)
+{
+    return locateWithDim(p, false, nullptr);
+}
+
+
+/* private */
+int
+RelatePointLocator::locateWithDim(const CoordinateXY* p, bool isNode, const Geometry* parentPolygonal)
+{
+    if (isEmpty) return DimensionLocation::EXTERIOR;
+
+    /**
+     * In a polygonal geometry a node must be on the boundary.
+     * (This is not the case for a mixed collection, since
+     * the node may be in the interior of a polygon.)
+     */
+    GeometryTypeId geomType = geom->getGeometryTypeId();
+    if (isNode && (geomType == GEOS_POLYGON || geomType == GEOS_MULTIPOLYGON))
+        return DimensionLocation::AREA_BOUNDARY;
+
+    int dimLoc = computeDimLocation(p, isNode, parentPolygonal);
+    return dimLoc;
+}
+
+
+/* private */
+int
+RelatePointLocator::computeDimLocation(const CoordinateXY* p, bool isNode, const Geometry* parentPolygonal)
+{
+    //-- check dimensions in order of precedence
+    if (!polygons.empty()) {
+        Location locPoly = locateOnPolygons(p, isNode, parentPolygonal);
+        if (locPoly != Location::EXTERIOR)
+            return DimensionLocation::locationArea(locPoly);
+    }
+    if (!lines.empty()) {
+        Location locLine = locateOnLines(p, isNode);
+        if (locLine != Location::EXTERIOR)
+            return DimensionLocation::locationLine(locLine);
+    }
+    if (!points.empty()) {
+        Location locPt = locateOnPoints(p);
+        if (locPt != Location::EXTERIOR)
+            return DimensionLocation::locationPoint(locPt);
+    }
+    return DimensionLocation::EXTERIOR;
+}
+
+
+/* private */
+Location
+RelatePointLocator::locateOnPoints(const CoordinateXY* p) const
+{
+    auto search = points.find(p);
+    if (search != points.end())
+        return Location::INTERIOR;
+    else
+        return Location::EXTERIOR;
+}
+
+
+/* private */
+Location
+RelatePointLocator::locateOnLines(const CoordinateXY* p, bool isNode)
+{
+    if (lineBoundary != nullptr && lineBoundary->isBoundary(p)) {
+        return Location::BOUNDARY;
+    }
+    //-- must be on line, in interior
+    if (isNode)
+        return Location::INTERIOR;
+
+    //TODO: index the lines
+    for (const LineString* line : lines) {
+        //-- have to check every line, since any/all may contain point
+        Location loc = locateOnLine(p, /*isNode,*/ line);
+        if (loc != Location::EXTERIOR)
+            return loc;
+        //TODO: minor optimization - some BoundaryNodeRules can short-circuit
+    }
+    return Location::EXTERIOR;
+}
+
+
+/* private */
+Location
+RelatePointLocator::locateOnLine(const CoordinateXY* p, /*bool isNode,*/ const LineString* l)
+{
+    // bounding-box check
+    if (! l->getEnvelopeInternal()->intersects(*p))
+        return Location::EXTERIOR;
+
+    const CoordinateSequence* seq = l->getCoordinatesRO();
+    if (PointLocation::isOnLine(*p, seq)) {
+        return Location::INTERIOR;
+    }
+    return Location::EXTERIOR;
+}
+
+
+/* private */
+Location
+RelatePointLocator::locateOnPolygons(const CoordinateXY* p, bool isNode, const Geometry* parentPolygonal)
+{
+    int numBdy = 0;
+    //TODO: use a spatial index on the polygons
+    for (std::size_t i = 0; i < polygons.size(); i++) {
+        Location loc = locateOnPolygonal(p, isNode, parentPolygonal, i);
+        if (loc == Location::INTERIOR) {
+            return Location::INTERIOR;
+        }
+        if (loc == Location::BOUNDARY) {
+            numBdy += 1;
+        }
+    }
+    if (numBdy == 1) {
+        return Location::BOUNDARY;
+    }
+    //-- check for point lying on adjacent boundaries
+    else if (numBdy > 1) {
+        if (adjEdgeLocator == nullptr) {
+            adjEdgeLocator.reset(new AdjacentEdgeLocator(geom));
+        }
+        return adjEdgeLocator->locate(p);
+    }
+    return Location::EXTERIOR;
+}
+
+
+/* private */
+Location
+RelatePointLocator::locateOnPolygonal(const CoordinateXY* p,
+    bool isNode,
+    const Geometry* parentPolygonal,
+    std::size_t index)
+{
+    const Geometry* polygonal = polygons[index];
+    if (isNode && parentPolygonal == polygonal) {
+        return Location::BOUNDARY;
+    }
+    PointOnGeometryLocator* locator = getLocator(index);
+    return locator->locate(p);
+}
+
+
+/* private */
+PointOnGeometryLocator *
+RelatePointLocator::getLocator(std::size_t index)
+{
+    std::unique_ptr& locator = polyLocator[index];
+    if (locator == nullptr) {
+        const Geometry* polygonal = polygons[index];
+        if (isPrepared) {
+            locator.reset(new IndexedPointInAreaLocator(*polygonal));
+        }
+        else {
+            locator.reset(new SimplePointInAreaLocator(*polygonal));
+        }
+    }
+    return locator.get();
+}
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/Sources/geos/src/operation/relateng/RelatePredicate.cpp b/Sources/geos/src/operation/relateng/RelatePredicate.cpp
new file mode 100644
index 0000000..3efe553
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/RelatePredicate.cpp
@@ -0,0 +1,109 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+// #include 
+
+#include 
+
+
+// using geos::geom::Envelope;
+// using geos::geom::Location;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public static */
+std::unique_ptr
+RelatePredicate::intersects()
+{
+    return std::unique_ptr(new IntersectsPredicate());
+}
+
+/* public static */
+std::unique_ptr
+RelatePredicate::disjoint()
+{
+    return std::unique_ptr(new DisjointPredicate());
+}
+
+/* public static */
+std::unique_ptr
+RelatePredicate::contains()
+{
+    return std::unique_ptr(new ContainsPredicate());
+}
+
+/* public static */
+std::unique_ptr
+RelatePredicate::within()
+{
+    return std::unique_ptr(new WithinPredicate());
+}
+
+/* public static */
+std::unique_ptr
+RelatePredicate::covers()
+{
+    return std::unique_ptr(new CoversPredicate());
+}
+
+/* public static */
+std::unique_ptr
+RelatePredicate::coveredBy()
+{
+    return std::unique_ptr(new CoveredByPredicate());
+}
+
+/* public static */
+std::unique_ptr
+RelatePredicate::crosses()
+{
+    return std::unique_ptr(new CrossesPredicate());
+}
+
+
+/* public static */
+std::unique_ptr
+RelatePredicate::equalsTopo()
+{
+    return std::unique_ptr(new EqualsTopoPredicate());
+}
+
+
+/* public static */
+std::unique_ptr
+RelatePredicate::overlaps()
+{
+    return std::unique_ptr(new OverlapsPredicate());
+}
+
+/* public static */
+std::unique_ptr
+RelatePredicate::touches()
+{
+    return std::unique_ptr(new TouchesPredicate());
+}
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/Sources/geos/src/operation/relateng/RelateSegmentString.cpp b/Sources/geos/src/operation/relateng/RelateSegmentString.cpp
new file mode 100644
index 0000000..41c0c4b
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/RelateSegmentString.cpp
@@ -0,0 +1,161 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateXY;
+using geos::geom::Dimension;
+using geos::geom::Geometry;
+using geos::algorithm::Orientation;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public static */
+const RelateSegmentString*
+RelateSegmentString::createLine(
+    const CoordinateSequence* pts,
+    bool isA, int elementId,
+    const RelateGeometry* parent)
+{
+    return createSegmentString(pts, isA, Dimension::L, elementId, -1, nullptr, parent);
+}
+
+
+/* public static */
+const RelateSegmentString*
+RelateSegmentString::createRing(
+    const CoordinateSequence* pts,
+    bool isA, int elementId, int ringId,
+    const Geometry* poly, const RelateGeometry* parent)
+{
+    return createSegmentString(pts, isA, Dimension::A, elementId, ringId, poly, parent);
+}
+
+
+/* private static */
+const RelateSegmentString*
+RelateSegmentString::createSegmentString(
+    const CoordinateSequence* pts,
+    bool isA, int dim, int elementId, int ringId,
+    const Geometry* poly, const RelateGeometry* parent)
+{
+    return new RelateSegmentString(pts, isA, dim, elementId, ringId, poly, parent);
+}
+
+
+/* public */
+NodeSection*
+RelateSegmentString::createNodeSection(std::size_t segIndex, const CoordinateXY intPt) const
+{
+    const CoordinateXY& c0 = getCoordinate(segIndex);
+    const CoordinateXY& c1 = getCoordinate(segIndex + 1);
+    bool isNodeAtVertex = intPt.equals2D(c0) || intPt.equals2D(c1);
+    const CoordinateXY* prev = prevVertex(segIndex, &intPt);
+    const CoordinateXY* next = nextVertex(segIndex, &intPt);
+    NodeSection* a = new NodeSection(m_isA, m_dimension, m_id, m_ringId, m_parentPolygonal, isNodeAtVertex, prev, intPt, next);
+    return a;
+}
+
+
+/* private */
+const CoordinateXY*
+RelateSegmentString::prevVertex(std::size_t segIndex, const CoordinateXY* pt) const
+{
+    const CoordinateXY& segStart = getCoordinate(segIndex);
+    if (! segStart.equals2D(*pt))
+        return &segStart;
+
+    //-- pt is at segment start, so get previous vertex
+    if (segIndex > 0) {
+        const CoordinateXY& seg = getCoordinate(segIndex - 1);
+        return &seg;
+    }
+
+    if (isClosed())
+        return &(prevInRing(segIndex));
+
+    return nullptr;
+}
+
+
+/* private */
+const CoordinateXY*
+RelateSegmentString::nextVertex(std::size_t segIndex, const CoordinateXY* pt) const
+{
+    const CoordinateXY& segEnd = getCoordinate(segIndex + 1);
+    if (! segEnd.equals2D(*pt))
+        return &segEnd;
+
+    //-- pt is at seg end, so get next vertex
+    if (size() == 2 && segIndex == INDEX_UNKNOWN) {
+        const CoordinateXY& seg = getCoordinate(0);
+        return &seg;
+    }
+
+    if (segIndex < size() - 2) {
+        const CoordinateXY& seg = getCoordinate(segIndex + 2);
+        return &seg;
+    }
+
+    if (isClosed())
+        return &(SegmentString::nextInRing(segIndex + 1));
+
+    //-- segstring is not closed, so there is no next segment
+    return nullptr;
+}
+
+
+/* public */
+bool
+RelateSegmentString::isContainingSegment(std::size_t segIndex, const CoordinateXY* pt) const
+{
+    //-- intersection is at segment start vertex - process it
+    const CoordinateXY& c0 = getCoordinate(segIndex);
+    if (pt->equals2D(c0))
+        return true;
+    const CoordinateXY& c1 = getCoordinate(segIndex+1);
+    if (pt->equals2D(c1)) {
+        bool isFinalSegment = segIndex == size() - 2;
+        if (isClosed() || ! isFinalSegment)
+            return false;
+        //-- for final segment, process intersections with final endpoint
+        return true;
+    }
+    //-- intersection is interior - process it
+    return true;
+}
+
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
diff --git a/Sources/geos/src/operation/relateng/TopologyComputer.cpp b/Sources/geos/src/operation/relateng/TopologyComputer.cpp
new file mode 100644
index 0000000..1e1c646
--- /dev/null
+++ b/Sources/geos/src/operation/relateng/TopologyComputer.cpp
@@ -0,0 +1,568 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+using geos::algorithm::PolygonNodeTopology;
+using geos::geom::Coordinate;
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+using geos::geom::Dimension;
+using geos::geom::Location;
+using geos::geom::Position;
+using geos::util::IllegalStateException;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* private */
+void
+TopologyComputer::initExteriorDims()
+{
+    int dimRealA = geomA.getDimensionReal();
+    int dimRealB = geomB.getDimensionReal();
+
+    /**
+     * For P/L case, P exterior intersects L interior
+     */
+    if (dimRealA == Dimension::P && dimRealB == Dimension::L) {
+        updateDim(Location::EXTERIOR, Location::INTERIOR, Dimension::L);
+    }
+    else if (dimRealA == Dimension::L && dimRealB == Dimension::P) {
+        updateDim(Location::INTERIOR, Location::EXTERIOR, Dimension::L);
+    }
+    /**
+     * For P/A case, the Area Int and Bdy intersect the Point exterior.
+     */
+    else if (dimRealA == Dimension::P && dimRealB == Dimension::A) {
+        updateDim(Location::EXTERIOR, Location::INTERIOR, Dimension::A);
+        updateDim(Location::EXTERIOR, Location::BOUNDARY, Dimension::L);
+    }
+    else if (dimRealA == Dimension::A && dimRealB == Dimension::P) {
+        updateDim(Location::INTERIOR, Location::EXTERIOR, Dimension::A);
+        updateDim(Location::BOUNDARY, Location::EXTERIOR, Dimension::L);
+    }
+    else if (dimRealA == Dimension::L && dimRealB == Dimension::A) {
+        updateDim(Location::EXTERIOR, Location::INTERIOR, Dimension::A);
+     }
+    else if (dimRealA == Dimension::A && dimRealB == Dimension::L) {
+        updateDim(Location::INTERIOR, Location::EXTERIOR, Dimension::A);
+    }
+    //-- cases where one geom is EMPTY
+    else if (dimRealA == Dimension::False || dimRealB == Dimension::False) {
+        if (dimRealA != Dimension::False) {
+            initExteriorEmpty(RelateGeometry::GEOM_A);
+        }
+        if (dimRealB != Dimension::False) {
+            initExteriorEmpty(RelateGeometry::GEOM_B);
+        }
+    }
+}
+
+
+/* private */
+void
+TopologyComputer::initExteriorEmpty(bool geomNonEmpty)
+{
+    int dimNonEmpty = getDimension(geomNonEmpty);
+    switch (dimNonEmpty) {
+        case Dimension::P:
+            updateDim(geomNonEmpty, Location::INTERIOR, Location::EXTERIOR, Dimension::P);
+            break;
+        case Dimension::L:
+            if (getGeometry(geomNonEmpty).hasBoundary()) {
+                updateDim(geomNonEmpty, Location::BOUNDARY, Location::EXTERIOR, Dimension::P);
+            }
+            updateDim(geomNonEmpty, Location::INTERIOR, Location::EXTERIOR, Dimension::L);
+            break;
+        case Dimension::A:
+            updateDim(geomNonEmpty, Location::BOUNDARY, Location::EXTERIOR, Dimension::L);
+            updateDim(geomNonEmpty, Location::INTERIOR, Location::EXTERIOR, Dimension::A);
+            break;
+    }
+}
+
+
+/* public */
+bool
+TopologyComputer::isAreaArea() const
+{
+    return getDimension(RelateGeometry::GEOM_A) == Dimension::A
+        && getDimension(RelateGeometry::GEOM_B) == Dimension::A;
+}
+
+
+/* public */
+int
+TopologyComputer::getDimension(bool isA) const
+{
+    return getGeometry(isA).getDimension();
+}
+
+
+/* public */
+bool
+TopologyComputer::isSelfNodingRequired() const
+{
+    if (predicate.requireSelfNoding()) {
+        if (geomA.isSelfNodingRequired() ||
+            geomB.isSelfNodingRequired())
+        return true;
+    }
+    return false;
+}
+
+
+/* public */
+bool
+TopologyComputer::isExteriorCheckRequired(bool isA) const
+{
+    return predicate.requireExteriorCheck(isA);
+}
+
+// static char
+// toSymbol(Location loc) {
+//     switch (loc) {
+//         case Location::NONE: return '-';
+//         case Location::INTERIOR: return 'I';
+//         case Location::BOUNDARY: return 'B';
+//         case Location::EXTERIOR: return 'E';
+//     }
+//     return ' ';
+// }
+
+/* private */
+void
+TopologyComputer::updateDim(Location locA, Location locB, int dimension)
+{
+    //std::cout << toSymbol(locA) << toSymbol(locB) << " <- " << dimension << std::endl;
+    predicate.updateDimension(locA, locB, dimension);
+}
+
+/* private */
+void
+TopologyComputer::updateDim(bool isAB, Location loc1, Location loc2, int dimension)
+{
+    if (isAB) {
+        updateDim(loc1, loc2, dimension);
+    }
+    else {
+        // is ordered BA
+        updateDim(loc2, loc1, dimension);
+    }
+}
+
+
+/* public */
+bool
+TopologyComputer::isResultKnown() const
+{
+    return predicate.isKnown();
+}
+
+
+/* public */
+bool
+TopologyComputer::getResult() const
+{
+    return predicate.value();
+}
+
+
+/* public */
+void
+TopologyComputer::finish()
+{
+    predicate.finish();
+}
+
+
+/* private */
+NodeSections *
+TopologyComputer::getNodeSections(const CoordinateXY& nodePt)
+{
+    NodeSections* ns;
+    auto result = nodeMap.find(nodePt);
+    if (result == nodeMap.end()) {
+        ns = new NodeSections(&nodePt);
+        nodeSectionsStore.emplace_back(ns);
+        nodeMap[nodePt] = ns;
+    }
+    else {
+        ns = result->second;
+    }
+    return ns;
+}
+
+
+/* public */
+void
+TopologyComputer::addIntersection(NodeSection* a, NodeSection* b)
+{
+    // add edges to node to allow full topology evaluation later
+    // we run this first (unlike JTS) in case the subsequent test throws
+    // an exception and the NodeSection pointers are not correctly
+    // saved in the memory managed store on the NodeSections, causing
+    // a small memeory leak
+    addNodeSections(a, b);
+
+    if (! a->isSameGeometry(b)) {
+        updateIntersectionAB(a, b);
+    }
+}
+
+
+/* private */
+void
+TopologyComputer::updateIntersectionAB(const NodeSection* a, const NodeSection* b)
+{
+    if (NodeSection::isAreaArea(*a, *b)) {
+        updateAreaAreaCross(a, b);
+    }
+    updateNodeLocation(a, b);
+}
+
+
+/* private */
+void
+TopologyComputer::updateAreaAreaCross(const NodeSection* a, const NodeSection* b)
+{
+    bool isProper = NodeSection::isProper(*a, *b);
+    if (isProper || PolygonNodeTopology::isCrossing(&(a->nodePt()),
+        a->getVertex(0), a->getVertex(1),
+        b->getVertex(0), b->getVertex(1)))
+    {
+        updateDim(Location::INTERIOR, Location::INTERIOR, Dimension::A);
+    }
+}
+
+
+/* private */
+void
+TopologyComputer::updateNodeLocation(const NodeSection* a, const NodeSection* b)
+{
+    const CoordinateXY& pt = a->nodePt();
+    Location locA = geomA.locateNode(&pt, a->getPolygonal());
+    Location locB = geomB.locateNode(&pt, b->getPolygonal());
+    updateDim(locA, locB, Dimension::P);
+}
+
+/* private */
+void
+TopologyComputer::addNodeSections(NodeSection* ns0, NodeSection* ns1)
+{
+    NodeSections* sections = getNodeSections(ns0->nodePt());
+    sections->addNodeSection(ns0);
+    sections->addNodeSection(ns1);
+}
+
+/* public */
+void
+TopologyComputer::addPointOnPointInterior(const CoordinateXY* pt)
+{
+    (void)pt;
+    updateDim(Location::INTERIOR, Location::INTERIOR, Dimension::P);
+}
+
+/* public */
+void
+TopologyComputer::addPointOnPointExterior(bool isGeomA, const CoordinateXY* pt)
+{
+    (void)pt;
+    updateDim(isGeomA, Location::INTERIOR, Location::EXTERIOR, Dimension::P);
+}
+
+/* public */
+void
+TopologyComputer::addPointOnGeometry(bool isA, Location locTarget, int dimTarget, const CoordinateXY* pt)
+{
+    (void)pt;
+    updateDim(isA, Location::INTERIOR, locTarget, Dimension::P);
+    switch (dimTarget) {
+    case Dimension::P:
+        return;
+    case Dimension::L:
+        /**
+         * Because zero-length lines are handled,
+         * a point lying in the exterior of the line target
+         * may imply either P or L for the Exterior interaction
+         */
+        //TODO: determine if effective dimension of linear target is L?
+        //updateDim(isGeomA, Location::EXTERIOR, locTarget, Dimension::P);
+        return;
+    case Dimension::A:
+        /**
+         * If a point intersects an area target, then the area interior and boundary
+         * must extend beyond the point and thus interact with its exterior.
+         */
+        updateDim(isA, Location::EXTERIOR, Location::INTERIOR, Dimension::A);
+        updateDim(isA, Location::EXTERIOR, Location::BOUNDARY, Dimension::L);
+        return;
+    }
+    throw IllegalStateException("Unknown target dimension: " + std::to_string(dimTarget));
+}
+
+/* public */
+void
+TopologyComputer::addLineEndOnGeometry(bool isLineA, Location locLineEnd, Location locTarget, int dimTarget, const CoordinateXY* pt)
+{
+    (void)pt;
+
+    //-- record topology at line end point
+    updateDim(isLineA, locLineEnd, locTarget, Dimension::P);
+
+    //-- Line and Area targets may have additional topology
+    switch (dimTarget) {
+    case Dimension::P:
+        return;
+    case Dimension::L:
+        addLineEndOnLine(isLineA, locLineEnd, locTarget, pt);
+        return;
+    case Dimension::A:
+        addLineEndOnArea(isLineA, locLineEnd, locTarget, pt);
+        return;
+    }
+    throw IllegalStateException("Unknown target dimension: " + std::to_string(dimTarget));
+}
+
+
+/* private */
+void
+TopologyComputer::addLineEndOnLine(bool isLineA, Location locLineEnd, Location locLine, const CoordinateXY* pt)
+{
+    (void)pt;
+    (void)locLineEnd;
+    /**
+     * When a line end is in the EXTERIOR of a Line,
+     * some length of the source Line INTERIOR
+     * is also in the target Line EXTERIOR.
+     * This works for zero-length lines as well.
+     */
+    if (locLine == Location::EXTERIOR) {
+        updateDim(isLineA, Location::INTERIOR, Location::EXTERIOR, Dimension::L);
+    }
+}
+
+
+/* private */
+void
+TopologyComputer::addLineEndOnArea(bool isLineA, Location locLineEnd, Location locArea, const CoordinateXY* pt)
+{
+    (void)pt;
+    (void)locLineEnd;
+    if (locArea != Location::BOUNDARY) {
+        /**
+         * When a line end is in an Area INTERIOR or EXTERIOR
+         * some length of the source Line Interior
+         * AND the Exterior of the line
+         * is also in that location of the target.
+         * NOTE: this assumes the line end is NOT also in an Area of a mixed-dim GC
+         */
+        //TODO: handle zero-length lines?
+        updateDim(isLineA, Location::INTERIOR, locArea, Dimension::L);
+        updateDim(isLineA, Location::EXTERIOR, locArea, Dimension::A);
+    }
+}
+
+
+/* public */
+void
+TopologyComputer::addAreaVertex(bool isAreaA, Location locArea, Location locTarget, int dimTarget, const CoordinateXY* pt)
+{
+    (void)pt;
+    if (locTarget == Location::EXTERIOR) {
+        updateDim(isAreaA, Location::INTERIOR, Location::EXTERIOR, Dimension::A);
+        /**
+         * If area vertex is on Boundary further topology can be deduced
+         * from the neighbourhood around the boundary vertex.
+         * This is always the case for polygonal geometries.
+         * For GCs, the vertex may be either on boundary or in interior
+         * (i.e. of overlapping or adjacent polygons)
+         */
+        if (locArea == Location::BOUNDARY) {
+            updateDim(isAreaA, Location::BOUNDARY, Location::EXTERIOR, Dimension::L);
+            updateDim(isAreaA, Location::EXTERIOR, Location::EXTERIOR, Dimension::A);
+        }
+        return;
+    }
+
+    switch (dimTarget) {
+        case Dimension::P:
+            addAreaVertexOnPoint(isAreaA, locArea, pt);
+            return;
+        case Dimension::L:
+            addAreaVertexOnLine(isAreaA, locArea, locTarget, pt);
+            return;
+        case Dimension::A:
+            addAreaVertexOnArea(isAreaA, locArea, locTarget, pt);
+            return;
+    }
+    throw IllegalStateException("Unknown target dimension: " + std::to_string(dimTarget));
+}
+
+
+/* private */
+void
+TopologyComputer::addAreaVertexOnPoint(bool isAreaA, Location locArea, const CoordinateXY* pt)
+{
+    (void)pt;
+    //-- Assert: locArea != EXTERIOR
+    //-- Assert: locTarget == INTERIOR
+    /**
+     * The vertex location intersects the Point.
+     */
+    updateDim(isAreaA, locArea, Location::INTERIOR, Dimension::P);
+    /**
+     * The area interior intersects the point's exterior neighbourhood.
+     */
+    updateDim(isAreaA, Location::INTERIOR, Location::EXTERIOR, Dimension::A);
+    /**
+     * If the area vertex is on the boundary,
+     * the area boundary and exterior intersect the point's exterior neighbourhood
+     */
+    if (locArea == Location::BOUNDARY) {
+        updateDim(isAreaA, Location::BOUNDARY, Location::EXTERIOR, Dimension::L);
+        updateDim(isAreaA, Location::EXTERIOR, Location::EXTERIOR, Dimension::A);
+    }
+}
+
+
+/* private */
+void
+TopologyComputer::addAreaVertexOnLine(bool isAreaA, Location locArea, Location locTarget, const CoordinateXY* pt)
+{
+    (void)pt;
+    //-- Assert: locArea != EXTERIOR
+    /**
+     * If an area vertex intersects a line, all we know is the
+     * intersection at that point.
+     * e.g. the line may or may not be collinear with the area boundary,
+     * and the line may or may not intersect the area interior.
+     * Full topology is determined later by node analysis
+     */
+    updateDim(isAreaA, locArea, locTarget, Dimension::P);
+    if (locArea == Location::INTERIOR) {
+        /**
+         * The area interior intersects the line's exterior neighbourhood.
+         */
+        updateDim(isAreaA, Location::INTERIOR, Location::EXTERIOR, Dimension::A);
+    }
+}
+
+
+/* public */
+void
+TopologyComputer::addAreaVertexOnArea(bool isAreaA, Location locArea, Location locTarget, const CoordinateXY* pt)
+{
+    (void)pt;
+    if (locTarget == Location::BOUNDARY) {
+        if (locArea == Location::BOUNDARY) {
+            //-- B/B topology is fully computed later by node analysis
+            updateDim(isAreaA, Location::BOUNDARY, Location::BOUNDARY, Dimension::P);
+        }
+        else {
+            // locArea == INTERIOR
+            updateDim(isAreaA, Location::INTERIOR, Location::INTERIOR, Dimension::A);
+            updateDim(isAreaA, Location::INTERIOR, Location::BOUNDARY, Dimension::L);
+            updateDim(isAreaA, Location::INTERIOR, Location::EXTERIOR, Dimension::A);
+        }
+    }
+    else {
+        //-- locTarget is INTERIOR or EXTERIOR`
+        updateDim(isAreaA, Location::INTERIOR, locTarget, Dimension::A);
+        /**
+         * If area vertex is on Boundary further topology can be deduced
+         * from the neighbourhood around the boundary vertex.
+         * This is always the case for polygonal geometries.
+         * For GCs, the vertex may be either on boundary or in interior
+         * (i.e. of overlapping or adjacent polygons)
+         */
+        if (locArea == Location::BOUNDARY) {
+            updateDim(isAreaA, Location::BOUNDARY, locTarget, Dimension::L);
+            updateDim(isAreaA, Location::EXTERIOR, locTarget, Dimension::A);
+        }
+    }
+}
+
+
+/* public */
+void
+TopologyComputer::evaluateNodes()
+{
+    for (auto& kv : nodeMap) {
+        NodeSections* nodeSections = kv.second;
+        if (nodeSections->hasInteractionAB()) {
+            evaluateNode(nodeSections);
+            if (isResultKnown())
+                return;
+        }
+    }
+}
+
+
+/* private */
+void
+TopologyComputer::evaluateNode(NodeSections* nodeSections)
+{
+    const CoordinateXY* p = nodeSections->getCoordinate();
+    std::unique_ptr node = nodeSections->createNode();
+    //-- Node must have edges for geom, but may also be in interior of a overlapping GC
+    bool isAreaInteriorA = geomA.isNodeInArea(p, nodeSections->getPolygonal(RelateGeometry::GEOM_A));
+    bool isAreaInteriorB = geomB.isNodeInArea(p, nodeSections->getPolygonal(RelateGeometry::GEOM_B));
+    node->finish(isAreaInteriorA, isAreaInteriorB);
+    evaluateNodeEdges(node.get());
+}
+
+
+/* private */
+void
+TopologyComputer::evaluateNodeEdges(const RelateNode* node)
+{
+    //TODO: collect distinct dim settings by using temporary matrix?
+    for (const std::unique_ptr& e : node->getEdges()) {
+        //-- An optimization to avoid updates for cases with a linear geometry
+        if (isAreaArea()) {
+            updateDim(e->location(RelateGeometry::GEOM_A, Position::LEFT),
+                      e->location(RelateGeometry::GEOM_B, Position::LEFT), Dimension::A);
+            updateDim(e->location(RelateGeometry::GEOM_A, Position::RIGHT),
+                      e->location(RelateGeometry::GEOM_B, Position::RIGHT), Dimension::A);
+        }
+        updateDim(e->location(RelateGeometry::GEOM_A, Position::ON),
+                  e->location(RelateGeometry::GEOM_B, Position::ON), Dimension::L);
+    }
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
diff --git a/Sources/geos/src/operation/sharedpaths/SharedPathsOp.cpp b/Sources/geos/src/operation/sharedpaths/SharedPathsOp.cpp
index 606f67c..807881f 100644
--- a/Sources/geos/src/operation/sharedpaths/SharedPathsOp.cpp
+++ b/Sources/geos/src/operation/sharedpaths/SharedPathsOp.cpp
@@ -30,7 +30,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 
 using namespace geos::geom;
@@ -104,15 +103,12 @@ SharedPathsOp::clearEdges(PathList& edges)
 void
 SharedPathsOp::findLinearIntersections(PathList& to)
 {
-    using geos::operation::overlay::OverlayOp;
-
     // TODO: optionally use the tolerance,
     //       snapping _g2 over _g1 ?
 
-    std::unique_ptr full(OverlayOp::overlayOp(
-                                       &_g1, &_g2, OverlayOp::opINTERSECTION));
+    auto full = _g1.intersection(&_g2);
 
-    // NOTE: intersection of equal lines yields splitted lines,
+    // NOTE: intersection of equal lines yields split lines,
     //       should we sew them back ?
 
     for(std::size_t i = 0, n = full->getNumGeometries(); i < n; ++i) {
diff --git a/Sources/geos/src/operation/union/CascadedPolygonUnion.cpp b/Sources/geos/src/operation/union/CascadedPolygonUnion.cpp
index 6619c87..2a4e736 100644
--- a/Sources/geos/src/operation/union/CascadedPolygonUnion.cpp
+++ b/Sources/geos/src/operation/union/CascadedPolygonUnion.cpp
@@ -24,7 +24,7 @@
 #include 
 #include 
 #include 
-#include 
+#include 
 #include 
 #include 
 #include 
@@ -204,7 +204,7 @@ ClassicUnionStrategy::Union(const geom::Geometry* g0, const geom::Geometry* g1)
     // TODO make an rvalue overload for this that can consume its inputs.
     // At a minimum, a copy in the buffer fallback can be eliminated.
     try {
-        return geom::HeuristicOverlay(g0, g1, operation::overlay::OverlayOp::opUNION);
+        return geom::HeuristicOverlay(g0, g1, operation::overlayng::OverlayNG::UNION);
     }
     catch (const util::TopologyException &ex) {
         ::geos::ignore_unused_variable_warning(ex);
diff --git a/Sources/geos/src/operation/union/CoverageUnion.cpp b/Sources/geos/src/operation/union/CoverageUnion.cpp
index 0e249d8..93a8848 100644
--- a/Sources/geos/src/operation/union/CoverageUnion.cpp
+++ b/Sources/geos/src/operation/union/CoverageUnion.cpp
@@ -17,10 +17,12 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
 #include 
+#include 
 
 namespace geos {
 namespace operation {
@@ -29,15 +31,16 @@ namespace geounion {
 using geos::geom::Geometry;
 using geos::geom::LineSegment;
 using geos::geom::LineString;
+using geos::geom::LinearRing;
 using geos::geom::Polygon;
 using geos::geom::GeometryCollection;
 using geos::geom::GeometryFactory;
 using geos::operation::polygonize::Polygonizer;
 
-void CoverageUnion::extractSegments(const Geometry* geom) {
+void CoverageUnion::extractRings(const Geometry* geom) {
     const Polygon* p = dynamic_cast(geom);
     if (p != nullptr) {
-        extractSegments(p);
+        extractRings(p);
     } else {
         auto gc = dynamic_cast(geom);
         if (gc == nullptr) {
@@ -45,20 +48,24 @@ void CoverageUnion::extractSegments(const Geometry* geom) {
         }
 
         for (std::size_t i = 0; i < gc->getNumGeometries(); i++) {
-            extractSegments(gc->getGeometryN(i));
+            extractRings(gc->getGeometryN(i));
         }
     }
 }
 
-void CoverageUnion::extractSegments(const Polygon* p) {
-    const LineString* ring = p->getExteriorRing();
+void CoverageUnion::extractRings(const Polygon* p) {
+    const LinearRing* ring = p->getExteriorRing();
 
-    extractSegments(ring);
+    rings.push_back(ring);
     for (std::size_t i = 0; i < p->getNumInteriorRing(); i++) {
-        extractSegments(p->getInteriorRingN(i));
+        rings.push_back(p->getInteriorRingN(i));
     }
 }
 
+void CoverageUnion::sortRings() {
+    shape::fractal::HilbertEncoder::sort(rings.begin(), rings.end());
+}
+
 void CoverageUnion::extractSegments(const LineString* ls) {
     auto coords = ls->getCoordinatesRO();
 
@@ -103,7 +110,13 @@ std::unique_ptr CoverageUnion::polygonize(const GeometryFactory* gf) {
 
 std::unique_ptr CoverageUnion::Union(const geom::Geometry* geom) {
     CoverageUnion cu;
-    cu.extractSegments(geom);
+
+    cu.extractRings(geom);
+    cu.sortRings();
+
+    for (const auto& lr : cu.rings) {
+        cu.extractSegments(lr);
+    }
 
     double area_in = geom->getArea();
 
diff --git a/Sources/geos/src/operation/union/PointGeometryUnion.cpp b/Sources/geos/src/operation/union/PointGeometryUnion.cpp
index cc77eac..4ff5699 100644
--- a/Sources/geos/src/operation/union/PointGeometryUnion.cpp
+++ b/Sources/geos/src/operation/union/PointGeometryUnion.cpp
@@ -53,7 +53,7 @@ PointGeometryUnion::Union() const
             continue;
         }
 
-        const Coordinate* coord = point->getCoordinate();
+        const Coordinate* coord = static_cast(point->getCoordinate());
         Location loc = locater.locate(*coord, &otherGeom);
         if(loc == Location::EXTERIOR) {
             exteriorCoords.insert(*coord);
@@ -69,18 +69,14 @@ PointGeometryUnion::Union() const
     std::unique_ptr ptComp;
 
     if(exteriorCoords.size() == 1) {
-        ptComp.reset(geomFact->createPoint(*(exteriorCoords.begin())));
+        ptComp = geomFact->createPoint(*(exteriorCoords.begin()));
     }
     else {
-        std::vector coords(exteriorCoords.size());
-        std::copy(exteriorCoords.begin(), exteriorCoords.end(), coords.begin());
-        ptComp.reset(geomFact->createMultiPoint(coords));
+        ptComp = geomFact->createMultiPoint(exteriorCoords);
     }
 
     // add point component to the other geometry
-    return std::unique_ptr (
-               GeometryCombiner::combine(ptComp.get(), &otherGeom)
-           );
+    return GeometryCombiner::combine(ptComp.get(), &otherGeom);
 }
 
 /* public  static */
diff --git a/Sources/geos/src/operation/union/UnaryUnionOp.cpp b/Sources/geos/src/operation/union/UnaryUnionOp.cpp
index 8bd96b8..a801e56 100644
--- a/Sources/geos/src/operation/union/UnaryUnionOp.cpp
+++ b/Sources/geos/src/operation/union/UnaryUnionOp.cpp
@@ -35,6 +35,8 @@
 #include 
 #include 
 
+#include "geos/util.h"
+
 namespace geos {
 namespace operation { // geos::operation
 namespace geounion {  // geos::operation::geounion
diff --git a/Sources/geos/src/operation/valid/IndexedNestedHoleTester.cpp b/Sources/geos/src/operation/valid/IndexedNestedHoleTester.cpp
index 89f4df8..c4b9d49 100644
--- a/Sources/geos/src/operation/valid/IndexedNestedHoleTester.cpp
+++ b/Sources/geos/src/operation/valid/IndexedNestedHoleTester.cpp
@@ -65,7 +65,7 @@ IndexedNestedHoleTester::isNested()
              * the topology of the incident edges.
              */
             if (PolygonTopologyAnalyzer::isRingNested(hole, testHole)) {
-                nestedPt = hole->getCoordinateN(0);
+                nestedPt = hole->getCoordinatesRO()->getAt(0);
                 return true;
             }
         }
diff --git a/Sources/geos/src/operation/valid/IndexedNestedPolygonTester.cpp b/Sources/geos/src/operation/valid/IndexedNestedPolygonTester.cpp
index ac8f166..3dbaa3f 100644
--- a/Sources/geos/src/operation/valid/IndexedNestedPolygonTester.cpp
+++ b/Sources/geos/src/operation/valid/IndexedNestedPolygonTester.cpp
@@ -114,12 +114,14 @@ IndexedNestedPolygonTester::findNestedPoint(
     const LinearRing* shell,
     const Polygon* possibleOuterPoly,
     IndexedPointInAreaLocator& locator,
-    Coordinate& coordNested)
+    CoordinateXY& coordNested)
 {
     /**
      * Try checking two points, since checking point location is fast.
      */
-    const Coordinate& shellPt0 = shell->getCoordinateN(0);
+    const CoordinateSequence* shellCoords = shell->getCoordinatesRO();
+
+    const CoordinateXY& shellPt0 = shellCoords->front();
     Location loc0 = locator.locate(&shellPt0);
     if (loc0 == Location::EXTERIOR) return false;
     if (loc0 == Location::INTERIOR) {
@@ -127,7 +129,7 @@ IndexedNestedPolygonTester::findNestedPoint(
         return true;
     }
 
-    const Coordinate& shellPt1 = shell->getCoordinateN(1);
+    const CoordinateXY& shellPt1 = shellCoords->getAt(1);
     Location loc1 = locator.locate(&shellPt1);
     if (loc1 == Location::EXTERIOR) return false;
     if (loc1 == Location::INTERIOR) {
@@ -158,7 +160,7 @@ bool
 IndexedNestedPolygonTester::findIncidentSegmentNestedPoint(
     const LinearRing* shell,
     const Polygon* poly,
-    Coordinate& coordNested)
+    CoordinateXY& coordNested)
 {
     const LinearRing* polyShell = poly->getExteriorRing();
     if (polyShell->isEmpty())
@@ -184,7 +186,7 @@ IndexedNestedPolygonTester::findIncidentSegmentNestedPoint(
      * The shell is contained in the polygon, but is not contained in a hole.
      * This is invalid.
      */
-    coordNested = shell->getCoordinateN(0);
+    coordNested = shell->getCoordinatesRO()->getAt(0);
     return true;
 }
 
diff --git a/Sources/geos/src/operation/valid/IsSimpleOp.cpp b/Sources/geos/src/operation/valid/IsSimpleOp.cpp
index 5e4fd8a..065cd18 100644
--- a/Sources/geos/src/operation/valid/IsSimpleOp.cpp
+++ b/Sources/geos/src/operation/valid/IsSimpleOp.cpp
@@ -55,7 +55,7 @@ IsSimpleOp::isSimple(const Geometry& geom)
 }
 
 /* public static */
-Coordinate
+CoordinateXY
 IsSimpleOp::getNonSimpleLocation(const Geometry& geom)
 {
     IsSimpleOp op(geom);
@@ -78,21 +78,21 @@ IsSimpleOp::isSimple()
 }
 
 /* public */
-Coordinate
+CoordinateXY
 IsSimpleOp::getNonSimpleLocation()
 {
     compute();
     if (nonSimplePts.size() == 0) {
-        Coordinate c;
+        CoordinateXY c;
         c.setNull();
         return c;
     }
-    return nonSimplePts.at(0);
+    return nonSimplePts[0];
 }
 
 
 /* public */
-const std::vector&
+const std::vector&
 IsSimpleOp::getNonSimpleLocations()
 {
     compute();
@@ -115,24 +115,27 @@ IsSimpleOp::computeSimple(const Geometry& geom)
 {
     if (geom.isEmpty()) return true;
     switch(geom.getGeometryTypeId()) {
+        case GEOS_POINT:
+            return true;
         case GEOS_MULTIPOINT:
             return isSimpleMultiPoint(dynamic_cast(geom));
         case GEOS_LINESTRING:
-            return isSimpleLinearGeometry(geom);
         case GEOS_MULTILINESTRING:
             return isSimpleLinearGeometry(geom);
         case GEOS_LINEARRING:
-            return isSimplePolygonal(geom);
         case GEOS_POLYGON:
-            return isSimplePolygonal(geom);
         case GEOS_MULTIPOLYGON:
             return isSimplePolygonal(geom);
         case GEOS_GEOMETRYCOLLECTION:
             return isSimpleGeometryCollection(geom);
-        // all other geometry types are simple by definition
-        default:
-            return true;
+        case GEOS_CIRCULARSTRING:
+        case GEOS_COMPOUNDCURVE:
+        case GEOS_MULTICURVE:
+        case GEOS_CURVEPOLYGON:
+        case GEOS_MULTISURFACE:
+            throw util::UnsupportedOperationException("Curved types not supported in IsSimpleOp.");
     }
+    throw util::UnsupportedOperationException("Unexpected type.");
 }
 
 /* private */
@@ -141,11 +144,13 @@ IsSimpleOp::isSimpleMultiPoint(const MultiPoint& mp)
 {
     if (mp.isEmpty()) return true;
     bool bIsSimple = true;
-    std::unordered_set points;
+    std::unordered_set points;
 
     for (std::size_t i = 0; i < mp.getNumGeometries(); i++) {
         const Point* pt = mp.getGeometryN(i);
-        const Coordinate* p = pt->getCoordinate();
+        if (pt->isEmpty())
+            continue;
+        const CoordinateXY* p = pt->getCoordinate();
         if (points.find(*p) != points.end()) {
             nonSimplePts.push_back(*p);
             bIsSimple = false;
@@ -219,10 +224,10 @@ IsSimpleOp::isSimpleLinearGeometry(const Geometry& geom)
 }
 
 /* private static */
-std::vector>
+std::vector>
 IsSimpleOp::removeRepeatedPts(const Geometry& geom)
 {
-    std::vector> coordseqs;
+    std::vector> coordseqs;
     for (std::size_t i = 0, sz = geom.getNumGeometries(); i < sz; i++) {
         const LineString* line = dynamic_cast(geom.getGeometryN(i));
         if (line) {
@@ -235,7 +240,7 @@ IsSimpleOp::removeRepeatedPts(const Geometry& geom)
 
 /* private static */
 std::vector>
-IsSimpleOp::createSegmentStrings(std::vector>& seqs)
+IsSimpleOp::createSegmentStrings(std::vector>& seqs)
 {
     std::vector> segStrings;
     for (auto& seq : seqs) {
@@ -269,10 +274,10 @@ IsSimpleOp::NonSimpleIntersectionFinder::processIntersections(
     if (isSameSegment)
         return;
 
-    const Coordinate& p00 = ss0->getCoordinate(segIndex0);
-    const Coordinate& p01 = ss0->getCoordinate(segIndex0 + 1);
-    const Coordinate& p10 = ss1->getCoordinate(segIndex1);
-    const Coordinate& p11 = ss1->getCoordinate(segIndex1 + 1);
+    const CoordinateXY& p00 = ss0->getCoordinate(segIndex0);
+    const CoordinateXY& p01 = ss0->getCoordinate(segIndex0 + 1);
+    const CoordinateXY& p10 = ss1->getCoordinate(segIndex1);
+    const CoordinateXY& p11 = ss1->getCoordinate(segIndex1 + 1);
 
     bool hasInt = findIntersection(
         ss0, segIndex0, ss1, segIndex1,
@@ -280,7 +285,7 @@ IsSimpleOp::NonSimpleIntersectionFinder::processIntersections(
 
     // found an intersection!
     if (hasInt) {
-        const Coordinate& intPt = li.getIntersection(0);
+        const CoordinateXY& intPt = li.getIntersection(0);
         // don't save dupes
         for (auto& pt: intersectionPts) {
             if (intPt.equals2D(pt))
@@ -296,8 +301,8 @@ bool
 IsSimpleOp::NonSimpleIntersectionFinder::findIntersection(
     SegmentString* ss0, std::size_t segIndex0,
     SegmentString* ss1, std::size_t segIndex1,
-    const Coordinate& p00, const Coordinate& p01,
-    const Coordinate& p10, const Coordinate& p11)
+    const CoordinateXY& p00, const CoordinateXY& p01,
+    const CoordinateXY& p10, const CoordinateXY& p11)
 {
 
     li.computeIntersection(p00, p01, p10, p11);
@@ -379,8 +384,8 @@ IsSimpleOp::NonSimpleIntersectionFinder::intersectionVertexIndex(
     const LineIntersector& lineInter,
     std::size_t segmentIndex) const
 {
-    const Coordinate& intPt = lineInter.getIntersection(0);
-    const Coordinate* endPt0 = lineInter.getEndpoint(segmentIndex, 0);
+    const CoordinateXY& intPt = lineInter.getIntersection(0);
+    const CoordinateXY* endPt0 = lineInter.getEndpoint(segmentIndex, 0);
     return intPt.equals2D(*endPt0) ? 0 : 1;
 }
 
diff --git a/Sources/geos/src/operation/valid/IsValidOp.cpp b/Sources/geos/src/operation/valid/IsValidOp.cpp
index 89776c3..454d9a1 100644
--- a/Sources/geos/src/operation/valid/IsValidOp.cpp
+++ b/Sources/geos/src/operation/valid/IsValidOp.cpp
@@ -49,7 +49,7 @@ IsValidOp::isValid()
 
 /* public static */
 bool
-IsValidOp::isValid(const Coordinate* coord)
+IsValidOp::isValid(const CoordinateXY* coord)
 {
     if (std::isfinite(coord->x) && std::isfinite(coord->y)) {
         return true;
@@ -68,15 +68,12 @@ IsValidOp::getValidationError()
     return validErr.get();
 }
 
-
-/* private */
 void
-IsValidOp::logInvalid(int code, const Coordinate* pt)
+IsValidOp::logInvalid(int code, const CoordinateXY& pt)
 {
-    validErr.reset(new TopologyValidationError(code, *pt));
+    validErr = detail::make_unique(code, pt);
 }
 
-
 /* private */
 bool
 IsValidOp::isValidGeometry(const Geometry* g)
@@ -105,6 +102,12 @@ IsValidOp::isValidGeometry(const Geometry* g)
             return isValid(static_cast(g));
         case GEOS_GEOMETRYCOLLECTION:
             return isValid(static_cast(g));
+        case GEOS_CIRCULARSTRING:
+        case GEOS_COMPOUNDCURVE:
+        case GEOS_CURVEPOLYGON:
+        case GEOS_MULTICURVE:
+        case GEOS_MULTISURFACE:
+            throw util::UnsupportedOperationException("Curved types not supported in IsValidOp.");
     }
 
     // geometry type not known
@@ -131,7 +134,7 @@ IsValidOp::isValid(const MultiPoint* g)
         if (p->isEmpty()) continue;
         if (!isValid(p->getCoordinate())) {
             logInvalid(TopologyValidationError::eInvalidCoordinate,
-                       p->getCoordinate());
+                       *(p->getCoordinate()));
             return false;;
         }
     }
@@ -264,9 +267,9 @@ void
 IsValidOp::checkCoordinatesValid(const CoordinateSequence* coords)
 {
     for (std::size_t i = 0; i < coords->size(); i++) {
-        if (! isValid(coords->getAt(i))) {
+        if (! isValid(coords->getAt(i))) {
             logInvalid(TopologyValidationError::eInvalidCoordinate,
-                       &coords->getAt(i));
+                       coords->getAt(i));
             return;
         }
     }
@@ -295,7 +298,7 @@ IsValidOp::checkRingClosed(const LinearRing* ring)
         Coordinate pt = ring->getNumPoints() >= 1
                         ? ring->getCoordinateN(0)
                         : Coordinate();
-        logInvalid(TopologyValidationError::eRingNotClosed, &pt);
+        logInvalid(TopologyValidationError::eRingNotClosed, pt);
         return;
     }
 }
@@ -341,10 +344,10 @@ void
 IsValidOp::checkTooFewPoints(const LineString* line, std::size_t minSize)
 {
     if (! isNonRepeatedSizeAtLeast(line, minSize) ) {
-        Coordinate pt = line->getNumPoints() >= 1
-                        ? line->getCoordinateN(0)
+        CoordinateXY pt = line->getNumPoints() >= 1
+                        ? line->getCoordinatesRO()->getAt(0)
                         : Coordinate();
-        logInvalid(TopologyValidationError::eTooFewPoints, &pt);
+        logInvalid(TopologyValidationError::eTooFewPoints, pt);
     }
 }
 
@@ -354,10 +357,11 @@ bool
 IsValidOp::isNonRepeatedSizeAtLeast(const LineString* line, std::size_t minSize)
 {
     std::size_t numPts = 0;
-    const Coordinate* prevPt = nullptr;
-    for (std::size_t i = 0; i < line->getNumPoints(); i++) {
+    const CoordinateXY* prevPt = nullptr;
+    const CoordinateSequence& seq = *line->getCoordinatesRO();
+    for (std::size_t i = 0; i < seq.size(); i++) {
         if (numPts >= minSize) return true;
-        const Coordinate& pt = line->getCoordinateN(i);
+        const CoordinateXY& pt = seq.getAt(i);
         if (prevPt == nullptr || ! pt.equals2D(*prevPt))
             numPts++;
         prevPt = &pt;
@@ -372,7 +376,7 @@ IsValidOp::checkAreaIntersections(PolygonTopologyAnalyzer& areaAnalyzer)
 {
     if (areaAnalyzer.hasInvalidIntersection()) {
         logInvalid(areaAnalyzer.getInvalidCode(),
-                   &areaAnalyzer.getInvalidLocation());
+                   areaAnalyzer.getInvalidLocation());
     }
 }
 
@@ -381,10 +385,10 @@ IsValidOp::checkAreaIntersections(PolygonTopologyAnalyzer& areaAnalyzer)
 void
 IsValidOp::checkRingSimple(const LinearRing* ring)
 {
-    Coordinate intPt = PolygonTopologyAnalyzer::findSelfIntersection(ring);
+    CoordinateXY intPt = PolygonTopologyAnalyzer::findSelfIntersection(ring);
     if (! intPt.isNull()) {
         logInvalid(TopologyValidationError::eRingSelfIntersection,
-            &intPt);
+            intPt);
     }
 }
 
@@ -403,7 +407,7 @@ IsValidOp::checkHolesInShell(const Polygon* poly)
         const LinearRing* hole = poly->getInteriorRingN(i);
         if (hole->isEmpty()) continue;
 
-        const Coordinate* invalidPt = nullptr;
+        const CoordinateXY* invalidPt = nullptr;
         if (isShellEmpty) {
             invalidPt = hole->getCoordinate();
         }
@@ -413,7 +417,7 @@ IsValidOp::checkHolesInShell(const Polygon* poly)
         if (invalidPt != nullptr) {
             logInvalid(
                 TopologyValidationError::eHoleOutsideShell,
-                invalidPt);
+                *invalidPt);
             return;
         }
     }
@@ -421,10 +425,10 @@ IsValidOp::checkHolesInShell(const Polygon* poly)
 
 
 /* private */
-const Coordinate *
+const CoordinateXY*
 IsValidOp::findHoleOutsideShellPoint(const LinearRing* hole, const LinearRing* shell)
 {
-    const Coordinate& holePt0 = hole->getCoordinateN(0);
+    const CoordinateXY& holePt0 = hole->getCoordinatesRO()->getAt(0);
     /**
      * If hole envelope is not covered by shell, it must be outside
      */
@@ -447,7 +451,7 @@ IsValidOp::checkHolesNotNested(const Polygon* poly)
     IndexedNestedHoleTester nestedTester(poly);
     if (nestedTester.isNested()) {
         logInvalid(TopologyValidationError::eNestedHoles,
-                   &nestedTester.getNestedPoint());
+                   nestedTester.getNestedPoint());
     }
 }
 
@@ -463,7 +467,7 @@ IsValidOp::checkShellsNotNested(const MultiPolygon* mp)
     IndexedNestedPolygonTester nestedTester(mp);
     if (nestedTester.isNested()) {
         logInvalid(TopologyValidationError::eNestedShells,
-                   &nestedTester.getNestedPoint());
+                   nestedTester.getNestedPoint());
     }
 }
 
@@ -474,7 +478,7 @@ IsValidOp::checkInteriorConnected(PolygonTopologyAnalyzer& analyzer)
 {
     if (analyzer.isInteriorDisconnected())
         logInvalid(TopologyValidationError::eDisconnectedInterior,
-                   &analyzer.getDisconnectionLocation());
+                   analyzer.getDisconnectionLocation());
 }
 
 
diff --git a/Sources/geos/src/operation/valid/MakeValid.cpp b/Sources/geos/src/operation/valid/MakeValid.cpp
index 89477d8..f544283 100644
--- a/Sources/geos/src/operation/valid/MakeValid.cpp
+++ b/Sources/geos/src/operation/valid/MakeValid.cpp
@@ -22,7 +22,7 @@
 #include 
 #include 
 
-#include 
+#include 
 #include 
 #include 
 #include 
@@ -50,7 +50,7 @@
 #endif
 
 using namespace geos::geom;
-using namespace geos::operation::overlay;
+using geos::operation::overlayng::OverlayNG;
 
 namespace geos {
 namespace operation { // geos.operation
@@ -60,19 +60,19 @@ namespace valid { // geos.operation.valid
 static std::unique_ptr
 makeValidSymDifference(const geom::Geometry* g0, const geom::Geometry* g1)
 {
-    return HeuristicOverlay(g0, g1, OverlayOp::opSYMDIFFERENCE);
+    return HeuristicOverlay(g0, g1, OverlayNG::SYMDIFFERENCE);
 }
 
 static std::unique_ptr
 makeValidDifference(const geom::Geometry* g0, const geom::Geometry* g1)
 {
-    return HeuristicOverlay(g0, g1, OverlayOp::opDIFFERENCE);
+    return HeuristicOverlay(g0, g1, OverlayNG::DIFFERENCE);
 }
 
 static std::unique_ptr
 makeValidUnion(const geom::Geometry* g0, const geom::Geometry* g1)
 {
-    return HeuristicOverlay(g0, g1, OverlayOp::opUNION);
+    return HeuristicOverlay(g0, g1, OverlayNG::UNION);
 }
 
 /*
diff --git a/Sources/geos/src/operation/valid/PolygonIntersectionAnalyzer.cpp b/Sources/geos/src/operation/valid/PolygonIntersectionAnalyzer.cpp
index a8ebe83..d64a37c 100644
--- a/Sources/geos/src/operation/valid/PolygonIntersectionAnalyzer.cpp
+++ b/Sources/geos/src/operation/valid/PolygonIntersectionAnalyzer.cpp
@@ -13,21 +13,22 @@
  *
  **********************************************************************/
 
+#include 
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
 
+using geos::geom::Coordinate;
+using geos::noding::SegmentString;
+
+
 namespace geos {      // geos
 namespace operation { // geos.operation
 namespace valid {     // geos.operation.valid
 
-using namespace geos::geom;
-
-
 /* public */
 void
 PolygonIntersectionAnalyzer::processIntersections(
@@ -57,10 +58,10 @@ PolygonIntersectionAnalyzer::findInvalidIntersection(
     const SegmentString* ss0, std::size_t segIndex0,
     const SegmentString* ss1, std::size_t segIndex1)
 {
-    const Coordinate& p00 = ss0->getCoordinate(segIndex0);
-    const Coordinate& p01 = ss0->getCoordinate(segIndex0 + 1);
-    const Coordinate& p10 = ss1->getCoordinate(segIndex1);
-    const Coordinate& p11 = ss1->getCoordinate(segIndex1 + 1);
+    const CoordinateXY& p00 = ss0->getCoordinate(segIndex0);
+    const CoordinateXY& p01 = ss0->getCoordinate(segIndex0 + 1);
+    const CoordinateXY& p10 = ss1->getCoordinate(segIndex1);
+    const CoordinateXY& p11 = ss1->getCoordinate(segIndex1 + 1);
 
     li.computeIntersection(p00, p01, p10, p11);
 
@@ -116,19 +117,19 @@ PolygonIntersectionAnalyzer::findInvalidIntersection(
      * Check topology of a vertex intersection.
      * The ring(s) must not cross.
      */
-    const Coordinate* e00 = &p00;
-    const Coordinate* e01 = &p01;
+    const CoordinateXY* e00 = &p00;
+    const CoordinateXY* e01 = &p01;
     if (intPt.equals2D(p00)) {
         e00 = &(prevCoordinateInRing(ss0, segIndex0));
         e01 = &p01;
     }
-    const Coordinate* e10 = &p10;
-    const Coordinate* e11 = &p11;
+    const CoordinateXY* e10 = &p10;
+    const CoordinateXY* e11 = &p11;
     if (intPt.equals2D(p10)) {
         e10 = &(prevCoordinateInRing(ss1, segIndex1));
         e11 = &p11;
     }
-    bool hasCrossing = PolygonNode::isCrossing(&intPt, e00, e01, e10, e11);
+    bool hasCrossing = algorithm::PolygonNodeTopology::isCrossing(&intPt, e00, e01, e10, e11);
     if (hasCrossing) {
         return TopologyValidationError::eSelfIntersection;
     }
@@ -163,7 +164,7 @@ PolygonIntersectionAnalyzer::findInvalidIntersection(
 bool
 PolygonIntersectionAnalyzer::addDoubleTouch(
     const SegmentString* ss0, const SegmentString* ss1,
-    const Coordinate& intPt)
+    const CoordinateXY& intPt)
 {
     return PolygonRing::addTouch(
         const_cast(static_cast(ss0->getData())),
@@ -174,9 +175,9 @@ PolygonIntersectionAnalyzer::addDoubleTouch(
 /* private */
 void
 PolygonIntersectionAnalyzer::addSelfTouch(
-    const SegmentString* ss, const Coordinate& intPt,
-    const Coordinate* e00, const Coordinate* e01,
-    const Coordinate* e10, const Coordinate* e11)
+    const SegmentString* ss, const CoordinateXY& intPt,
+    const CoordinateXY* e00, const CoordinateXY* e01,
+    const CoordinateXY* e10, const CoordinateXY* e11)
 {
     const PolygonRing* constPolyRing = static_cast(ss->getData());
     PolygonRing* polyRing = const_cast(constPolyRing);
@@ -187,7 +188,7 @@ PolygonIntersectionAnalyzer::addSelfTouch(
 }
 
 /* private */
-const Coordinate&
+const CoordinateXY&
 PolygonIntersectionAnalyzer::prevCoordinateInRing(
     const SegmentString* ringSS, std::size_t segIndex) const
 {
@@ -198,7 +199,7 @@ PolygonIntersectionAnalyzer::prevCoordinateInRing(
     else {
         prevIndex = segIndex - 1;
     }
-    return ringSS->getCoordinate(prevIndex);
+    return ringSS->getCoordinate(prevIndex);
 }
 
 /* private */
diff --git a/Sources/geos/src/operation/valid/PolygonNode.cpp b/Sources/geos/src/operation/valid/PolygonNode.cpp
deleted file mode 100644
index 73821a1..0000000
--- a/Sources/geos/src/operation/valid/PolygonNode.cpp
+++ /dev/null
@@ -1,114 +0,0 @@
-/**********************************************************************
- *
- * GEOS - Geometry Engine Open Source
- * http://geos.osgeo.org
- *
- * Copyright (C) 2021 Paul Ramsey 
- * Copyright (C) 2021 Martin Davis
- *
- * This is free software; you can redistribute and/or modify it under
- * the terms of the GNU Lesser General Public Licence as published
- * by the Free Software Foundation.
- * See the COPYING file for more information.
- *
- **********************************************************************/
-
-#include 
-#include 
-#include 
-#include 
-
-
-namespace geos {      // geos
-namespace operation { // geos.operation
-namespace valid {     // geos.operation.valid
-
-using namespace geos::geom;
-
-
-/* public static */
-bool
-PolygonNode::isCrossing(const Coordinate* nodePt, const Coordinate* a0, const Coordinate* a1, const Coordinate* b0, const Coordinate* b1)
-{
-    const Coordinate* aLo = a0;
-    const Coordinate* aHi = a1;
-    if (isAngleGreater(nodePt, aLo, aHi)) {
-        aLo = a1;
-        aHi = a0;
-    }
-    /**
-     * Find positions of b0 and b1.
-     * If they are the same they do not cross the other edge
-     */
-    bool isBetween0 = isBetween(nodePt, b0, aLo, aHi);
-    bool isBetween1 = isBetween(nodePt, b1, aLo, aHi);
-
-    return isBetween0 != isBetween1;
-}
-
-
-/* public static */
-bool
-PolygonNode::isInteriorSegment(const Coordinate* nodePt, const Coordinate* a0, const Coordinate* a1, const Coordinate* b)
-{
-    const Coordinate* aLo = a0;
-    const Coordinate* aHi = a1;
-    bool bIsInteriorBetween = true;
-    if (isAngleGreater(nodePt, aLo, aHi)) {
-        aLo = a1;
-        aHi = a0;
-        bIsInteriorBetween = false;
-    }
-    bool bIsBetween = isBetween(nodePt, b, aLo, aHi);
-    bool bIsInterior = (bIsBetween && bIsInteriorBetween)
-        || (! bIsBetween && ! bIsInteriorBetween);
-    return bIsInterior;
-}
-
-
-/* private static */
-bool
-PolygonNode::isBetween(const Coordinate* origin, const Coordinate* p, const Coordinate* e0, const Coordinate* e1)
-{
-    bool isGreater0 = isAngleGreater(origin, p, e0);
-    if (! isGreater0) return false;
-    bool isGreater1 = isAngleGreater(origin, p, e1);
-    return ! isGreater1;
-}
-
-
-/* private static */
-bool
-PolygonNode::isAngleGreater(const Coordinate* origin, const Coordinate* p, const Coordinate* q)
-{
-    int quadrantP = quadrant(origin, p);
-    int quadrantQ = quadrant(origin, q);
-
-    /**
-     * If the vectors are in different quadrants,
-     * that determines the ordering
-     */
-    if (quadrantP > quadrantQ) return true;
-    if (quadrantP < quadrantQ) return false;
-
-    //--- vectors are in the same quadrant
-    // Check relative orientation of vectors
-    // P > Q if it is CCW of Q
-    int orient = algorithm::Orientation::index(*origin, *q, *p);
-    return orient == algorithm::Orientation::COUNTERCLOCKWISE;
-}
-
-
-/* private static */
-int
-PolygonNode::quadrant(const Coordinate* origin, const Coordinate* p)
-{
-    double dx = p->x - origin->x;
-    double dy = p->y - origin->y;
-    return Quadrant::quadrant(dx, dy);
-}
-
-
-} // namespace geos.operation.valid
-} // namespace geos.operation
-} // namespace geos
diff --git a/Sources/geos/src/operation/valid/PolygonRing.cpp b/Sources/geos/src/operation/valid/PolygonRing.cpp
index a586e60..5a0c583 100644
--- a/Sources/geos/src/operation/valid/PolygonRing.cpp
+++ b/Sources/geos/src/operation/valid/PolygonRing.cpp
@@ -14,6 +14,7 @@
  **********************************************************************/
 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -38,7 +39,7 @@ PolygonRing::isShell(const PolygonRing* polyRing)
 
 /* public static */
 bool
-PolygonRing::addTouch(PolygonRing* ring0, PolygonRing* ring1, const Coordinate& pt)
+PolygonRing::addTouch(PolygonRing* ring0, PolygonRing* ring1, const CoordinateXY& pt)
 {
     //--- skip if either polygon does not have holes
     if (ring0 == nullptr || ring1 == nullptr)
@@ -57,12 +58,12 @@ PolygonRing::addTouch(PolygonRing* ring0, PolygonRing* ring1, const Coordinate&
 
 
 /* public static */
-const Coordinate*
+const CoordinateXY*
 PolygonRing::findHoleCycleLocation(std::vector polyRings)
 {
     for (PolygonRing* polyRing : polyRings) {
         if (! polyRing->isInTouchSet()) {
-            const Coordinate* holeCycleLoc = polyRing->findHoleCycleLocation();
+            const CoordinateXY* holeCycleLoc = polyRing->findHoleCycleLocation();
             if (holeCycleLoc != nullptr) return holeCycleLoc;
         }
     }
@@ -71,11 +72,11 @@ PolygonRing::findHoleCycleLocation(std::vector polyRings)
 
 
 /* public static */
-const Coordinate*
+const CoordinateXY*
 PolygonRing::findInteriorSelfNode(std::vector polyRings)
 {
     for (PolygonRing* polyRing : polyRings) {
-        const Coordinate* interiorSelfNode = polyRing->findInteriorSelfNode();
+        const CoordinateXY* interiorSelfNode = polyRing->findInteriorSelfNode();
         if (interiorSelfNode != nullptr) {
             return interiorSelfNode;
         }
@@ -99,7 +100,7 @@ PolygonRing::getTouches() const
 
 /* private */
 void
-PolygonRing::addTouch(PolygonRing* polyRing, const Coordinate& pt)
+PolygonRing::addTouch(PolygonRing* polyRing, const CoordinateXY& pt)
 {
     std::size_t nTouches = touches.count(polyRing->id);
     if (nTouches == 0) {
@@ -114,9 +115,9 @@ PolygonRing::addTouch(PolygonRing* polyRing, const Coordinate& pt)
 
 /* public */
 void
-PolygonRing::addSelfTouch(const Coordinate& origin,
-    const Coordinate* e00, const Coordinate* e01,
-    const Coordinate* e10, const Coordinate* e11)
+PolygonRing::addSelfTouch(const CoordinateXY& origin,
+    const CoordinateXY* e00, const CoordinateXY* e01,
+    const CoordinateXY* e10, const CoordinateXY* e11)
 {
     selfNodes.emplace_back(origin, e00, e01, e10, e11);
 }
@@ -124,7 +125,7 @@ PolygonRing::addSelfTouch(const Coordinate& origin,
 
 /* private */
 bool
-PolygonRing::isOnlyTouch(const PolygonRing* polyRing, const Coordinate& pt) const
+PolygonRing::isOnlyTouch(const PolygonRing* polyRing, const CoordinateXY& pt) const
 {
     //--- no touches for this ring
     if (touches.empty()) return true;
@@ -142,7 +143,7 @@ PolygonRing::isOnlyTouch(const PolygonRing* polyRing, const Coordinate& pt) cons
 
 
 /* private */
-const Coordinate*
+const CoordinateXY*
 PolygonRing::findHoleCycleLocation()
 {
     //--- the touch set including this ring is already processed
@@ -162,7 +163,7 @@ PolygonRing::findHoleCycleLocation()
     while (! touchStack.empty()) {
         PolygonRingTouch* touch = touchStack.top();
         touchStack.pop();
-        const Coordinate* holeCyclePt = scanForHoleCycle(touch, root, touchStack);
+        const CoordinateXY* holeCyclePt = scanForHoleCycle(touch, root, touchStack);
         if (holeCyclePt != nullptr) {
             return holeCyclePt;
         }
@@ -183,13 +184,13 @@ PolygonRing::init(PolygonRing* root, std::stack& touchStack)
 
 
 /* private */
-const Coordinate*
+const CoordinateXY*
 PolygonRing::scanForHoleCycle(PolygonRingTouch* currentTouch,
     PolygonRing* root,
     std::stack& touchStack)
 {
     PolygonRing* polyRing = currentTouch->getRing();
-    const Coordinate* currentPt = currentTouch->getCoordinate();
+    const CoordinateXY* currentPt = currentTouch->getCoordinate();
 
     /**
      * Scan the touched rings
@@ -227,7 +228,7 @@ PolygonRing::scanForHoleCycle(PolygonRingTouch* currentTouch,
 
 
 /* public */
-const Coordinate*
+const CoordinateXY*
 PolygonRing::findInteriorSelfNode()
 {
     if (selfNodes.empty()) return nullptr;
diff --git a/Sources/geos/src/operation/valid/PolygonRingSelfNode.cpp b/Sources/geos/src/operation/valid/PolygonRingSelfNode.cpp
index 0c6e0e0..3e07af3 100644
--- a/Sources/geos/src/operation/valid/PolygonRingSelfNode.cpp
+++ b/Sources/geos/src/operation/valid/PolygonRingSelfNode.cpp
@@ -13,8 +13,7 @@
  *
  **********************************************************************/
 
-#include 
-#include 
+#include 
 #include 
 
 
@@ -22,8 +21,7 @@ namespace geos {      // geos
 namespace operation { // geos.operation
 namespace valid {     // geos.operation.valid
 
-using namespace geos::geom;
-
+using geos::algorithm::PolygonNodeTopology;
 
 /* public */
 bool
@@ -34,7 +32,7 @@ PolygonRingSelfNode::isExterior(bool isInteriorOnRight) const
      * Note that either corner and either of the other edges could be used to test.
      * The situation is fully symmetrical.
      */
-    bool bIsInteriorSeg = PolygonNode::isInteriorSegment(&nodePt, e00, e01, e10);
+    bool bIsInteriorSeg = PolygonNodeTopology::isInteriorSegment(&nodePt, e00, e01, e10);
     bool bIsExterior = isInteriorOnRight ? ! bIsInteriorSeg : bIsInteriorSeg;
     return bIsExterior;
 }
diff --git a/Sources/geos/src/operation/valid/PolygonRingTouch.cpp b/Sources/geos/src/operation/valid/PolygonRingTouch.cpp
index 7b8b752..922ccf2 100644
--- a/Sources/geos/src/operation/valid/PolygonRingTouch.cpp
+++ b/Sources/geos/src/operation/valid/PolygonRingTouch.cpp
@@ -25,7 +25,7 @@ namespace valid {     // geos.operation.valid
 using namespace geos::geom;
 
 /* public */
-const Coordinate*
+const CoordinateXY*
 PolygonRingTouch::getCoordinate() const
 {
     return &touchPt;
@@ -40,7 +40,7 @@ PolygonRingTouch::getRing() const
 
 /* public */
 bool
-PolygonRingTouch::isAtLocation(const Coordinate& pt) const
+PolygonRingTouch::isAtLocation(const CoordinateXY& pt) const
 {
     return touchPt.equals2D(pt);
 }
diff --git a/Sources/geos/src/operation/valid/PolygonTopologyAnalyzer.cpp b/Sources/geos/src/operation/valid/PolygonTopologyAnalyzer.cpp
index 3fa9c4d..c2b9d0a 100644
--- a/Sources/geos/src/operation/valid/PolygonTopologyAnalyzer.cpp
+++ b/Sources/geos/src/operation/valid/PolygonTopologyAnalyzer.cpp
@@ -16,6 +16,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -25,7 +26,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -62,7 +62,7 @@ PolygonTopologyAnalyzer::PolygonTopologyAnalyzer(const Geometry* geom, bool p_is
 
 
 /* public static */
-Coordinate
+CoordinateXY
 PolygonTopologyAnalyzer::findSelfIntersection(const LinearRing* ring)
 {
     PolygonTopologyAnalyzer ata(ring, false);
@@ -76,7 +76,7 @@ bool
 PolygonTopologyAnalyzer::isRingNested(const LinearRing* test,
     const LinearRing* target)
 {
-    const Coordinate& p0 = test->getCoordinateN(0);
+    const CoordinateXY& p0 = test->getCoordinatesRO()->getAt(0);
     const CoordinateSequence* targetPts = target->getCoordinatesRO();
     Location loc = algorithm::PointLocation::locateInRing(p0, *targetPts);
     if (loc == Location::EXTERIOR) return false;
@@ -87,69 +87,70 @@ PolygonTopologyAnalyzer::isRingNested(const LinearRing* test,
      * Use the topology at the node to check if the segment
      * is inside or outside the ring.
      */
-    Coordinate p1 = findNonEqualVertex(test, p0);
+    const CoordinateXY& p1 = findNonEqualVertex(test, p0);
     return isIncidentSegmentInRing(&p0, &p1, targetPts);
 }
 
 /* private static */
-const Coordinate&
-PolygonTopologyAnalyzer::findNonEqualVertex(const LinearRing* ring, const Coordinate& p)
+const CoordinateXY&
+PolygonTopologyAnalyzer::findNonEqualVertex(const LinearRing* ring, const CoordinateXY& p)
 {
     std::size_t i = 1;
-    const Coordinate* next = &(ring->getCoordinateN(i));
+    const CoordinateSequence& ringPts = *ring->getCoordinatesRO();
+    const CoordinateXY* next = &(ringPts.getAt(i));
     while (next->equals2D(p) && i < ring->getNumPoints() - 1) {
         i += 1;
-        next = &(ring->getCoordinateN(i));
+        next = &(ringPts.getAt(i));
     }
-    return ring->getCoordinateN(i);
+    return ringPts.getAt(i);
 }
 
 /* private static */
 bool
 PolygonTopologyAnalyzer::isIncidentSegmentInRing(
-    const Coordinate* p0, const Coordinate* p1,
+    const CoordinateXY* p0, const CoordinateXY* p1,
     const CoordinateSequence* ringPts)
 {
     std::size_t index = intersectingSegIndex(ringPts, p0);
-    const Coordinate* rPrev = &findRingVertexPrev(ringPts, index, *p0);
-    const Coordinate* rNext = &findRingVertexNext(ringPts, index, *p0);
+    const CoordinateXY* rPrev = &findRingVertexPrev(ringPts, index, *p0);
+    const CoordinateXY* rNext = &findRingVertexNext(ringPts, index, *p0);
     /**
     * If ring orientation is not normalized, flip the corner orientation
     */
     bool isInteriorOnRight = ! algorithm::Orientation::isCCW(ringPts);
     if (! isInteriorOnRight) {
-        const Coordinate* temp = rPrev;
+        const CoordinateXY* temp = rPrev;
         rPrev = rNext;
         rNext = temp;
     }
-    return PolygonNode::isInteriorSegment(p0, rPrev, rNext, p1);
+    return algorithm::PolygonNodeTopology::isInteriorSegment(p0, rPrev, rNext, p1);
 }
 
 /* private static */
-const Coordinate&
-PolygonTopologyAnalyzer::findRingVertexPrev(const CoordinateSequence* ringPts, std::size_t index, const Coordinate& node)
+const CoordinateXY&
+PolygonTopologyAnalyzer::findRingVertexPrev(const CoordinateSequence* ringPts, std::size_t index, const CoordinateXY& node)
 {
     std::size_t iPrev = index;
-    const Coordinate* prev = &(ringPts->getAt(iPrev));
+    const CoordinateXY* prev = &(ringPts->getAt(iPrev));
     while (prev->equals2D(node)) {
       iPrev = ringIndexPrev(ringPts, iPrev);
-      prev = &(ringPts->getAt(iPrev));
+      prev = &(ringPts->getAt(iPrev));
     }
-    return ringPts->getAt(iPrev);
+    return ringPts->getAt(iPrev);
 }
 
 /* private static */
-const Coordinate&
-PolygonTopologyAnalyzer::findRingVertexNext(const CoordinateSequence* ringPts, std::size_t index, const Coordinate& node)
+const CoordinateXY&
+PolygonTopologyAnalyzer::findRingVertexNext(const CoordinateSequence* ringPts, std::size_t index, const CoordinateXY& node)
 {
     //-- safe, since index is always the start of a ring segment
     std::size_t iNext = index + 1;
-    const Coordinate* next = &(ringPts->getAt(iNext));
+    const CoordinateXY* next = &(ringPts->getAt(iNext));
     while (next->equals2D(node)) {
       iNext = ringIndexNext(ringPts, iNext);
-      next = &(ringPts->getAt(iNext));
+      next = &(ringPts->getAt(iNext));
     }
-    return ringPts->getAt(iNext);
+    return ringPts->getAt(iNext);
 }
 
 /* private static */
@@ -175,14 +176,13 @@ PolygonTopologyAnalyzer::ringIndexNext(const CoordinateSequence* ringPts, std::s
 /* private static */
 std::size_t
 PolygonTopologyAnalyzer::intersectingSegIndex(const CoordinateSequence* ringPts,
-    const Coordinate* pt)
+    const CoordinateXY* pt)
 {
     algorithm::LineIntersector li;
     for (std::size_t i = 0; i < ringPts->size() - 1; i++) {
-      li.computeIntersection(*pt, ringPts->getAt(i), ringPts->getAt(i+1));
-      if (li.hasIntersection()) {
+      if ( algorithm::PointLocation::isOnSegment(*pt, ringPts->getAt(i), ringPts->getAt(i+1)) ) {
         //-- check if pt is the start point of the next segment
-        if (pt->equals2D(ringPts->getAt(i + 1))) {
+        if (pt->equals2D(ringPts->getAt(i + 1))) {
           return i + 1;
         }
         return i;
@@ -220,7 +220,7 @@ void
 PolygonTopologyAnalyzer::checkInteriorDisconnectedBySelfTouch()
 {
     if (! polyRings.empty()) {
-        const Coordinate* dPt = PolygonRing::findInteriorSelfNode(polyRings);
+        const CoordinateXY* dPt = PolygonRing::findInteriorSelfNode(polyRings);
         if (dPt)
             disconnectionPt = *dPt;
     }
@@ -235,7 +235,7 @@ PolygonTopologyAnalyzer::checkInteriorDisconnectedByHoleCycle()
     * PolyRings will be null for empty, no hole or LinearRing inputs
     */
     if (! polyRings.empty()) {
-        const Coordinate* dPt = PolygonRing::findHoleCycleLocation(polyRings);
+        const CoordinateXY* dPt = PolygonRing::findHoleCycleLocation(polyRings);
         if (dPt)
             disconnectionPt = *dPt;
     }
diff --git a/Sources/geos/src/operation/valid/RepeatedPointRemover.cpp b/Sources/geos/src/operation/valid/RepeatedPointRemover.cpp
index ca289a9..22b4a9a 100644
--- a/Sources/geos/src/operation/valid/RepeatedPointRemover.cpp
+++ b/Sources/geos/src/operation/valid/RepeatedPointRemover.cpp
@@ -16,9 +16,7 @@
 
 #include 
 #include 
-#include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -27,10 +25,8 @@
 #include 
 
 using geos::geom::Coordinate;
-using geos::geom::CoordinateArraySequence;
 using geos::geom::CoordinateFilter;
 using geos::geom::CoordinateSequence;
-using geos::geom::CoordinateSequenceFactory;
 using geos::geom::Geometry;
 using geos::geom::GeometryFactory;
 using geos::geom::util::CoordinateOperation;
@@ -40,18 +36,16 @@ namespace geos {
 namespace operation {
 namespace valid {
 
-class RepeatedPointFilter : public CoordinateFilter {
+class RepeatedPointFilter : public geom::CoordinateInspector {
     public:
 
-        RepeatedPointFilter()
-            : m_prev(nullptr)
-            , sqTolerance(0.0) {}
-
-        RepeatedPointFilter(double tolerance)
-            : m_prev(nullptr)
+        RepeatedPointFilter(bool has_z, bool has_m, double tolerance = 0.0)
+            : m_coords(detail::make_unique(0u, has_z, has_m))
+            , m_prev(nullptr)
             , sqTolerance(tolerance*tolerance) {}
 
-        void filter_ro(const Coordinate* curr) override final {
+        template
+        void filter(const CoordType* curr) {
 
             // skip duplicate point or too-close poinnt
             if (m_prev != nullptr && (
@@ -61,48 +55,53 @@ class RepeatedPointFilter : public CoordinateFilter {
                 return;
             }
 
-            m_coords.push_back(*curr);
+            m_coords->add(*curr);
             m_prev = curr;
         }
 
-        std::vector getCoords() {
+        std::unique_ptr getCoords() {
             return std::move(m_coords);
         }
 
     private:
 
-        std::vector m_coords;
-        const Coordinate* m_prev;
+        std::unique_ptr m_coords;
+        const geom::CoordinateXY* m_prev;
         double sqTolerance;
 };
 
 
 /* public static */
-std::unique_ptr
+std::unique_ptr
 RepeatedPointRemover::removeRepeatedPoints(const CoordinateSequence* seq, double tolerance) {
 
     if (seq->isEmpty()) {
-        return detail::make_unique(0u, seq->getDimension());
+        return detail::make_unique(0u, seq->hasZ(), seq->hasM());
+    }
+
+    if (tolerance == 0.0) {
+        auto ret = detail::make_unique(0u, seq->hasZ(), seq->hasM());
+        ret->reserve(seq->size());
+        ret->add(*seq, false);
+        return ret;
     }
 
-    RepeatedPointFilter filter(tolerance);
+    RepeatedPointFilter filter(seq->hasZ(), seq->hasM(), tolerance);
     seq->apply_ro(&filter);
-    return detail::make_unique(filter.getCoords());
+    return filter.getCoords();
 }
 
 
-class RepeatedInvalidPointFilter : public CoordinateFilter {
+class RepeatedInvalidPointFilter : public geom::CoordinateInspector {
     public:
 
-        RepeatedInvalidPointFilter()
-            : m_prev(nullptr)
-            , sqTolerance(0.0) {}
-
-        RepeatedInvalidPointFilter(double tolerance)
-            : m_prev(nullptr)
+        RepeatedInvalidPointFilter(bool has_z, bool has_m, double tolerance = 0.0)
+            : m_coords(detail::make_unique(0u, has_z, has_m))
+            , m_prev(nullptr)
             , sqTolerance(tolerance*tolerance) {}
 
-        void filter_ro(const Coordinate* curr) override final {
+        template
+        void filter(const CoordType* curr) {
 
             bool invalid = ! curr->isValid();
             // skip initial invalids
@@ -118,33 +117,33 @@ class RepeatedInvalidPointFilter : public CoordinateFilter {
                 return;
             }
 
-            m_coords.push_back(*curr);
+            m_coords->add(*curr);
             m_prev = curr;
         }
 
-        std::vector getCoords() {
+        std::unique_ptr getCoords() {
             return std::move(m_coords);
         }
 
     private:
 
-        std::vector m_coords;
-        const Coordinate* m_prev;
+        std::unique_ptr m_coords;
+        const geom::CoordinateXY* m_prev;
         double sqTolerance;
 };
 
 
 /* public static */
-std::unique_ptr
+std::unique_ptr
 RepeatedPointRemover::removeRepeatedAndInvalidPoints(const CoordinateSequence* seq, double tolerance) {
 
     if (seq->isEmpty()) {
-        return detail::make_unique(0u, seq->getDimension());
+        return detail::make_unique(0u, seq->getDimension());
     }
 
-    RepeatedInvalidPointFilter filter(tolerance);
+    RepeatedInvalidPointFilter filter(seq->hasZ(), seq->hasM(), tolerance);
     seq->apply_ro(&filter);
-    return detail::make_unique(filter.getCoords());
+    return filter.getCoords();
 }
 
 
@@ -183,7 +182,7 @@ class RepeatedPointCoordinateOperation : public CoordinateOperation
             minLength = 2;
         }
         if(geom->getGeometryTypeId() == geom::GEOS_LINEARRING) {
-            minLength = 4;
+            minLength = geom::LinearRing::MINIMUM_VALID_SIZE;
         }
 
         // No way to filter short sequences.
@@ -196,36 +195,35 @@ class RepeatedPointCoordinateOperation : public CoordinateOperation
 
         // Create new vector of coordinates filtered to the
         // the tolerance.
-        RepeatedInvalidPointFilter filter(tolerance);
+        RepeatedInvalidPointFilter filter(coordinates->hasZ(), coordinates->hasM(), tolerance);
         coordinates->apply_ro(&filter);
-        std::vector filtCoords = filter.getCoords();
+        auto filtCoords = filter.getCoords();
 
-        if (filtCoords.size() == 0) return nullptr;
+        if (filtCoords->size() == 0) return nullptr;
 
         // End points for comparison and sequence repair
         const Coordinate& origEndCoord = coordinates->back();
-        const Coordinate& filtEndCoord = filtCoords.back();
 
         // Fluff up overly small filtered outputs
-        if(filtCoords.size() < minLength) {
-            filtCoords.push_back(origEndCoord);
+        if(filtCoords->size() < minLength) {
+            filtCoords->add(origEndCoord);
         }
 
+        const Coordinate& filtEndCoord = filtCoords->back();
+
         // We stripped the last point, let's put it back on
         if (!origEndCoord.equals2D(filtEndCoord)) {
             // If the end of the filtered coordinates is within
             // tolerance of the original end, we drop the last filtered
             // coordinate so the output still follows the tolerance rule
             if(origEndCoord.distanceSquared(filtEndCoord) <= tolerance*tolerance) {
-                filtCoords.pop_back();
+                filtCoords->pop_back();
             }
             // Put the original end coordinate back on
-            filtCoords.push_back(origEndCoord);
+            filtCoords->add(origEndCoord);
         }
 
-        auto fact = geom->getFactory();
-        auto csfact = fact->getCoordinateSequenceFactory();
-        return csfact->create(std::move(filtCoords));
+        return filtCoords;
     };
 }; // RepeatedPointCoordinateOperation
 
diff --git a/Sources/geos/src/operation/valid/RepeatedPointTester.cpp b/Sources/geos/src/operation/valid/RepeatedPointTester.cpp
index ec96e90..fdd2b48 100644
--- a/Sources/geos/src/operation/valid/RepeatedPointTester.cpp
+++ b/Sources/geos/src/operation/valid/RepeatedPointTester.cpp
@@ -37,7 +37,7 @@ namespace geos {
 namespace operation { // geos.operation
 namespace valid { // geos.operation.valid
 
-Coordinate&
+CoordinateXY&
 RepeatedPointTester::getCoordinate()
 {
     return repeatedCoord;
diff --git a/Sources/geos/src/operation/valid/TopologyValidationError.cpp b/Sources/geos/src/operation/valid/TopologyValidationError.cpp
index 9d80c50..4e1176a 100644
--- a/Sources/geos/src/operation/valid/TopologyValidationError.cpp
+++ b/Sources/geos/src/operation/valid/TopologyValidationError.cpp
@@ -45,7 +45,7 @@ const char* TopologyValidationError::errMsg[] = {
 };
 
 TopologyValidationError::TopologyValidationError(int newErrorType,
-        const Coordinate& newPt)
+        const CoordinateXY& newPt)
     :
     errorType(newErrorType),
     pt(newPt)
@@ -65,7 +65,7 @@ TopologyValidationError::getErrorType() const
     return errorType;
 }
 
-const Coordinate&
+const CoordinateXY&
 TopologyValidationError::getCoordinate() const
 {
     return pt;
diff --git a/Sources/geos/src/planargraph/DirectedEdgeStar.cpp b/Sources/geos/src/planargraph/DirectedEdgeStar.cpp
index 0c3a7f4..d95a2f3 100644
--- a/Sources/geos/src/planargraph/DirectedEdgeStar.cpp
+++ b/Sources/geos/src/planargraph/DirectedEdgeStar.cpp
@@ -85,7 +85,8 @@ Coordinate&
 DirectedEdgeStar::getCoordinate() const
 {
     if(outEdges.empty()) {
-        return Coordinate::getNull();
+        static Coordinate nullCoord = Coordinate::getNull();
+        return nullCoord;
     }
     DirectedEdge* e = outEdges[0];
     return e->getCoordinate();
diff --git a/Sources/geos/src/precision/GeometryPrecisionReducer.cpp b/Sources/geos/src/precision/GeometryPrecisionReducer.cpp
index a5f5d45..a4d6a3d 100644
--- a/Sources/geos/src/precision/GeometryPrecisionReducer.cpp
+++ b/Sources/geos/src/precision/GeometryPrecisionReducer.cpp
@@ -23,7 +23,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -42,28 +41,6 @@ namespace geos {
 namespace precision { // geos.precision
 
 
-/* public */
-std::unique_ptr
-GeometryPrecisionReducer::reduce(const Geometry& geom)
-{
-    std::unique_ptr reduced;
-    if (isPointwise) {
-        reduced = PointwisePrecisionReducerTransformer::reduce(geom, targetPM);
-    }
-    else {
-        reduced = PrecisionReducerTransformer::reduce(geom, targetPM, removeCollapsed);
-    }
-
-    // TODO: incorporate this in the Transformer above
-    if (changePrecisionModel &&
-        (&targetPM != geom.getFactory()->getPrecisionModel()))
-    {
-         return changePM(reduced.get(), targetPM);
-    }
-
-    return reduced;
-}
-
 
 /* private */
 std::unique_ptr
@@ -122,7 +99,7 @@ GeometryPrecisionReducer::fixPolygonalTopology(const Geometry& geom)
 
     if(! newFactory) {
         tmpFactory = createFactory(*geom.getFactory(), targetPM);
-        tmp.reset(tmpFactory->createGeometry(&geom));
+        tmp = tmpFactory->createGeometry(&geom);
         geomToBuffer = tmp.get();
     }
 
@@ -130,7 +107,7 @@ GeometryPrecisionReducer::fixPolygonalTopology(const Geometry& geom)
 
     if(! newFactory) {
         // a slick way to copy the geometry with the original precision factory
-        bufGeom.reset(geom.getFactory()->createGeometry(bufGeom.get()));
+        bufGeom = geom.getFactory()->createGeometry(bufGeom.get());
     }
 
     return bufGeom;
@@ -143,12 +120,41 @@ GeometryPrecisionReducer::createFactory(const GeometryFactory& oldGF,
 {
     GeometryFactory::Ptr p_newFactory(
         GeometryFactory::create(&newPM,
-                                oldGF.getSRID(),
-                                const_cast(oldGF.getCoordinateSequenceFactory()))
+                                oldGF.getSRID())
     );
     return p_newFactory;
 }
 
+/* public */
+std::unique_ptr
+GeometryPrecisionReducer::reduce(const Geometry& geom)
+{
+    std::unique_ptr reduced;
+    if (isPointwise) {
+        reduced = PointwisePrecisionReducerTransformer::reduce(geom, targetPM);
+    }
+    else {
+        reduced = PrecisionReducerTransformer::reduce(geom, targetPM, removeCollapsed);
+    }
+
+    // Match the collection level of the output to the input
+    // if necessary
+    if (geom.isCollection()
+        && ! reduced->isCollection()
+        && (geom.getCoordinateDimension() == reduced->getCoordinateDimension()))
+    {
+        reduced = geom.getFactory()->createMulti(std::move(reduced));
+    }
+
+    // TODO: incorporate this in the Transformer above
+    if (changePrecisionModel &&
+        (&targetPM != geom.getFactory()->getPrecisionModel()))
+    {
+         return changePM(reduced.get(), targetPM);
+    }
+
+    return reduced;
+}
 
 
 } // namespace geos.precision
diff --git a/Sources/geos/src/precision/MinimumClearance.cpp b/Sources/geos/src/precision/MinimumClearance.cpp
index 532d111..0a5e9e8 100644
--- a/Sources/geos/src/precision/MinimumClearance.cpp
+++ b/Sources/geos/src/precision/MinimumClearance.cpp
@@ -20,7 +20,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 
@@ -165,8 +164,7 @@ MinimumClearance::compute()
     }
 
     // initialize to "No Distance Exists" state
-    minClearancePts = std::unique_ptr(inputGeom->getFactory()->getCoordinateSequenceFactory()->create(2,
-                      2));
+    minClearancePts = detail::make_unique(2u, 2u);
     minClearance = DoubleInfinity;
 
     // handle empty geometries
@@ -178,6 +176,10 @@ MinimumClearance::compute()
     MinClearanceDistance mcd;
     auto nearest = tree->nearestNeighbour(mcd);
 
+    if (nearest.first == nullptr || nearest.second == nullptr) {
+        throw util::GEOSException("Failed to find nearest items");
+    }
+
     minClearance = mcd.distance(nearest.first, nearest.second);
 
     const std::vector* minClearancePtsVec = mcd.getCoordinates();
diff --git a/Sources/geos/src/precision/PointwisePrecisionReducerTransformer.cpp b/Sources/geos/src/precision/PointwisePrecisionReducerTransformer.cpp
index b528474..1b867b4 100644
--- a/Sources/geos/src/precision/PointwisePrecisionReducerTransformer.cpp
+++ b/Sources/geos/src/precision/PointwisePrecisionReducerTransformer.cpp
@@ -19,12 +19,12 @@
 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
 #include 
 #include 
+#include 
 
 
 using namespace geos::geom;
@@ -49,29 +49,25 @@ PointwisePrecisionReducerTransformer::transformCoordinates(const CoordinateSeque
     (void)(parent); // ignore unused variable
 
     if (coords->isEmpty()) {
-        CoordinateArraySequence* cas = new CoordinateArraySequence(std::size_t(0u), coords->getDimension());
-        return std::unique_ptr(static_cast(cas));
+        return detail::make_unique(0u, coords->getDimension());
     }
 
-    std::vector coordsReduce = reducePointwise(coords);
-    CoordinateArraySequence* cas = new CoordinateArraySequence(std::move(coordsReduce));
-    return std::unique_ptr(static_cast(cas));
+    return reducePointwise(coords);
 }
 
 
 /* private */
-std::vector
+std::unique_ptr
 PointwisePrecisionReducerTransformer::reducePointwise(const CoordinateSequence* coordinates)
 {
-    std::vector coordReduce;
-    coordReduce.reserve(coordinates->size());
+    auto coordReduce = detail::make_unique();
+    coordReduce->reserve(coordinates->size());
 
     // copy coordinates and reduce
-    for (std::size_t i = 0; i < coordinates->size(); i++) {
-        Coordinate coord = coordinates->getAt(i);
+    coordinates->forEach([&coordReduce, this](Coordinate coord) {
         targetPM.makePrecise(coord);
-        coordReduce.emplace_back(coord);
-    }
+        coordReduce->add(coord);
+    });
     return coordReduce;
 }
 
diff --git a/Sources/geos/src/precision/PrecisionReducerCoordinateOperation.cpp b/Sources/geos/src/precision/PrecisionReducerCoordinateOperation.cpp
index 6e9b40a..4f03388 100644
--- a/Sources/geos/src/precision/PrecisionReducerCoordinateOperation.cpp
+++ b/Sources/geos/src/precision/PrecisionReducerCoordinateOperation.cpp
@@ -18,7 +18,6 @@
 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -44,17 +43,14 @@ PrecisionReducerCoordinateOperation::edit(const CoordinateSequence* cs,
         return nullptr;
     }
 
-    auto vc = detail::make_unique>(csSize);
+    auto reducedCoords = detail::make_unique(csSize);
 
     // copy coordinates and reduce
     for(std::size_t i = 0; i < csSize; ++i) {
-        (*vc)[i] = cs->getAt(i);
-        targetPM.makePrecise((*vc)[i]);
+        (*reducedCoords)[i] = cs->getAt(i);
+        targetPM.makePrecise((*reducedCoords)[i]);
     }
 
-    // reducedCoords take ownership of 'vc'
-    auto reducedCoords = geom->getFactory()->getCoordinateSequenceFactory()->create(vc.release());
-
     // remove repeated points, to simplify returned geometry as
     // much as possible.
     std::unique_ptr noRepeatedCoords = operation::valid::RepeatedPointRemover::removeRepeatedPoints(reducedCoords.get());
diff --git a/Sources/geos/src/precision/PrecisionReducerTransformer.cpp b/Sources/geos/src/precision/PrecisionReducerTransformer.cpp
index 9ef9e19..74339e6 100644
--- a/Sources/geos/src/precision/PrecisionReducerTransformer.cpp
+++ b/Sources/geos/src/precision/PrecisionReducerTransformer.cpp
@@ -18,9 +18,8 @@
  **********************************************************************/
 
 #include 
+#include 
 #include 
-#include 
-#include 
 #include 
 #include 
 #include 
@@ -39,38 +38,39 @@ namespace geos {
 namespace precision { // geos.precision
 
 
-class PrecisionReducerFilter : public CoordinateFilter {
+class PrecisionReducerFilter : public CoordinateInspector {
     public:
 
-        PrecisionReducerFilter(bool p_removeRepeated, const PrecisionModel& p_filterPM)
-            : removeRepeated(p_removeRepeated)
+        PrecisionReducerFilter(bool p_removeRepeated, const PrecisionModel& p_filterPM, bool has_z, bool has_m)
+            : m_coords(detail::make_unique(0u, has_z, has_m))
+            , m_prev(nullptr)
+            , removeRepeated(p_removeRepeated)
             , filterPM(p_filterPM)
-            {
-                m_prev.setNull();
-            }
+            {}
 
-        void filter_ro(const Coordinate* curr) override final {
+        template
+        void filter(const CoordType* curr) {
 
             // round to precision model
-            Coordinate coord = *curr;
+            CoordType coord = *curr;
             filterPM.makePrecise(coord);
 
             // skip duplicate point
-            if (removeRepeated && !m_prev.isNull() && coord.equals2D(m_prev))
+            if (removeRepeated && m_prev != nullptr && coord.equals2D(*m_prev))
                 return;
 
-            m_coords.push_back(coord);
-            m_prev = coord;
+            m_coords->add(coord);
+            m_prev = &m_coords->back();
         }
 
-        std::vector getCoords() {
+        std::unique_ptr getCoords() {
             return std::move(m_coords);
         }
 
     private:
 
-        std::vector m_coords;
-        Coordinate m_prev;
+        std::unique_ptr m_coords;
+        CoordinateXY* m_prev;
         bool removeRepeated;
         const PrecisionModel& filterPM;
 };
@@ -90,15 +90,15 @@ PrecisionReducerTransformer::reduce(
 /* private */
 void
 PrecisionReducerTransformer::extend(
-    std::vector& coords,
+    CoordinateSequence& coords,
     std::size_t minLength)
 {
     if (coords.size() >= minLength)
         return;
 
     while(coords.size() < minLength) {
-        Coordinate endCoord = coords.back();
-        coords.push_back(endCoord);
+        const Coordinate& endCoord = coords.back();
+        coords.add(endCoord);
     }
 }
 
@@ -112,14 +112,13 @@ PrecisionReducerTransformer::transformCoordinates(
         return nullptr;
 
     if (coords->isEmpty()) {
-        return detail::make_unique(0u, coords->getDimension());
+        return detail::make_unique(0u, coords->getDimension());
     }
 
     const bool removeRepeated = true;
-    PrecisionReducerFilter filter(removeRepeated, targetPM);
+    PrecisionReducerFilter filter(removeRepeated, targetPM, coords->hasZ(), coords->hasM());
     coords->apply_ro(&filter);
-    std::vector coordsReduce = filter.getCoords();
-    // std::unique_ptr coordSeqReduced();
+    auto coordsReduce = filter.getCoords();
 
     /**
      * Check to see if the removal of repeated points collapsed the coordinate
@@ -140,14 +139,13 @@ PrecisionReducerTransformer::transformCoordinates(
     * Handle collapse. If specified return null so parent geometry is removed or empty,
     * otherwise extend to required length.
     */
-    if (coordsReduce.size() < minLength) {
+    if (coordsReduce->size() < minLength) {
         if (isRemoveCollapsed)
             return nullptr;
-        extend(coordsReduce, minLength);
+        extend(*coordsReduce, minLength);
     }
 
-    CoordinateArraySequence* cas = new CoordinateArraySequence(std::move(coordsReduce));
-    return std::unique_ptr(static_cast(cas));
+    return coordsReduce;
 }
 
 
diff --git a/Sources/geos/src/precision/SimpleGeometryPrecisionReducer.cpp b/Sources/geos/src/precision/SimpleGeometryPrecisionReducer.cpp
index b4fe2dc..84f022a 100644
--- a/Sources/geos/src/precision/SimpleGeometryPrecisionReducer.cpp
+++ b/Sources/geos/src/precision/SimpleGeometryPrecisionReducer.cpp
@@ -22,7 +22,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -74,17 +73,14 @@ PrecisionReducerCoordinateOperation::edit(const CoordinateSequence* cs,
 
     auto csSize = cs->size();
 
-    auto vc = detail::make_unique>(csSize);
+    auto reducedCoords = detail::make_unique(csSize);
 
     // copy coordinates and reduce
     for(unsigned int i = 0; i < csSize; ++i) {
-        (*vc)[i] = cs->getAt(i);
-        sgpr->getPrecisionModel()->makePrecise((*vc)[i]);
+        (*reducedCoords)[i] = cs->getAt(i);
+        sgpr->getPrecisionModel()->makePrecise((*reducedCoords)[i]);
     }
 
-    // reducedCoords take ownership of 'vc'
-    auto reducedCoords = geom->getFactory()->getCoordinateSequenceFactory()->create(vc.release());
-
     // remove repeated points, to simplify returned geometry as
     // much as possible.
     std::unique_ptr noRepeatedCoords = operation::valid::RepeatedPointRemover::removeRepeatedPoints(reducedCoords.get());
diff --git a/Sources/geos/src/shape/fractal/HilbertEncoder.cpp b/Sources/geos/src/shape/fractal/HilbertEncoder.cpp
index 1ce6d3d..8c1b2c6 100644
--- a/Sources/geos/src/shape/fractal/HilbertEncoder.cpp
+++ b/Sources/geos/src/shape/fractal/HilbertEncoder.cpp
@@ -59,34 +59,7 @@ HilbertEncoder::encode(const geom::Envelope* env)
 void
 HilbertEncoder::sort(std::vector& geoms)
 {
-    struct HilbertComparator {
-
-        HilbertEncoder& enc;
-
-        HilbertComparator(HilbertEncoder& e)
-            : enc(e) {};
-
-        bool
-        operator()(const geom::Geometry* a, const geom::Geometry* b)
-        {
-            return enc.encode(a->getEnvelopeInternal()) > enc.encode(b->getEnvelopeInternal());
-        }
-    };
-
-    geom::Envelope extent;
-    for (const geom::Geometry* geom: geoms)
-    {
-        if (extent.isNull())
-            extent = *(geom->getEnvelopeInternal());
-        else
-            extent.expandToInclude(*(geom->getEnvelopeInternal()));
-    }
-    if (extent.isNull()) return;
-
-    HilbertEncoder encoder(12, extent);
-    HilbertComparator hilbertCompare(encoder);
-    std::sort(geoms.begin(), geoms.end(), hilbertCompare);
-    return;
+    sort(geoms.begin(), geoms.end());
 }
 
 
diff --git a/Sources/geos/src/simplify/ComponentJumpChecker.cpp b/Sources/geos/src/simplify/ComponentJumpChecker.cpp
new file mode 100644
index 0000000..c09a5f9
--- /dev/null
+++ b/Sources/geos/src/simplify/ComponentJumpChecker.cpp
@@ -0,0 +1,202 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2006 Refractions Research Inc.
+ * Copyright (C) 2023 Martin Davis 
+ * Copyright (C) 2023 Paul Ramsey 
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+using geos::algorithm::RayCrossingCounter;
+using geos::geom::Coordinate;
+using geos::geom::CoordinateSequence;
+using geos::geom::Envelope;
+using geos::geom::LineSegment;
+
+
+namespace geos {
+namespace simplify { // geos::simplify
+
+/**
+* Checks if a line section jumps a component if flattened.
+*
+* Assumes start <= end.
+*
+* @param line the line containing the section being flattened
+* @param start start index of the section
+* @param end end index of the section
+* @param seg the flattening segment
+* @return true if the flattened section jumps a component
+*/
+/*public*/
+bool
+ComponentJumpChecker::hasJump(
+    const TaggedLineString* line,
+    std::size_t start, std::size_t end,
+    const LineSegment& seg) const
+{
+    Envelope sectionEnv = computeEnvelope(line, start, end);
+    for (TaggedLineString* comp : components) {
+      //-- don't test component against itself
+        if (comp == line)
+            continue;
+
+        const Coordinate& compPt = comp->getComponentPoint();
+        if (sectionEnv.intersects(compPt)) {
+            if (hasJumpAtComponent(compPt, line, start, end, seg)) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+
+/**
+* Checks if two consecutive segments jumps a component if flattened.
+* The segments are assumed to be consecutive.
+* (so the seg1.p1 = seg2.p0).
+* The flattening segment must be the segment between seg1.p0 and seg2.p1.
+*
+* @param line the line containing the section being flattened
+* @param seg1 the first replaced segment
+* @param seg2 the next replaced segment
+* @param seg the flattening segment
+* @return true if the flattened segment jumps a component
+*/
+/* public */
+bool
+ComponentJumpChecker::hasJump(
+    const TaggedLineString* line,
+    const LineSegment* seg1,
+    const LineSegment* seg2,
+    const LineSegment& seg) const
+{
+    Envelope sectionEnv = computeEnvelope(seg1, seg2);
+    for (TaggedLineString* comp : components) {
+        //-- don't test component against itself
+        if (comp == line)
+            continue;
+
+        const Coordinate& compPt = comp->getComponentPoint();
+        if (sectionEnv.intersects(compPt)) {
+            if (hasJumpAtComponent(compPt, seg1, seg2, seg)) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+
+/*private static*/
+bool
+ComponentJumpChecker::hasJumpAtComponent(
+    const Coordinate& compPt,
+    const TaggedLineString* line,
+    std::size_t start, std::size_t end,
+    const LineSegment& seg)
+{
+    std::size_t sectionCount = crossingCount(compPt, line, start, end);
+    std::size_t segCount = crossingCount(compPt, seg);
+    bool hasJump = sectionCount % 2 != segCount % 2;
+    return hasJump;
+}
+
+/*private static*/
+bool
+ComponentJumpChecker::hasJumpAtComponent(
+    const Coordinate& compPt,
+    const LineSegment* seg1, const LineSegment* seg2,
+    const LineSegment& seg)
+{
+    std::size_t sectionCount = crossingCount(compPt, seg1, seg2);
+    std::size_t segCount = crossingCount(compPt, seg);
+    bool hasJump = sectionCount % 2 != segCount % 2;
+    return hasJump;
+}
+
+/*private static*/
+std::size_t
+ComponentJumpChecker::crossingCount(
+    const Coordinate& compPt,
+    const LineSegment& seg)
+{
+    RayCrossingCounter rcc(compPt);
+    rcc.countSegment(seg.p0,  seg.p1);
+    return rcc.getCount();
+}
+
+/*private static*/
+std::size_t
+ComponentJumpChecker::crossingCount(
+    const Coordinate& compPt,
+    const LineSegment* seg1, const LineSegment* seg2)
+{
+    RayCrossingCounter rcc(compPt);
+    rcc.countSegment(seg1->p0,  seg1->p1);
+    rcc.countSegment(seg2->p0,  seg2->p1);
+    return rcc.getCount();
+}
+
+/*private static*/
+std::size_t
+ComponentJumpChecker::crossingCount(
+    const Coordinate& compPt,
+    const TaggedLineString* line,
+    std::size_t start, std::size_t end)
+{
+    RayCrossingCounter rcc(compPt);
+    for (std::size_t i = start; i < end; i++) {
+        rcc.countSegment(line->getCoordinate(i), line->getCoordinate(i + 1));
+    }
+    return rcc.getCount();
+}
+
+/*private static*/
+Envelope
+ComponentJumpChecker::computeEnvelope(
+    const LineSegment* seg1, const LineSegment* seg2)
+{
+    Envelope env;
+    env.expandToInclude(seg1->p0);
+    env.expandToInclude(seg1->p1);
+    env.expandToInclude(seg2->p0);
+    env.expandToInclude(seg2->p1);
+    return env;
+}
+
+/*private static*/
+Envelope
+ComponentJumpChecker::computeEnvelope(
+    const TaggedLineString* line,
+    std::size_t start, std::size_t end)
+{
+    Envelope env;
+    for (std::size_t i = start; i <= end; i++) {
+        env.expandToInclude(line->getCoordinate(i));
+    }
+    return env;
+}
+
+
+
+} // namespace geos::simplify
+} // namespace geos
diff --git a/Sources/geos/src/simplify/DouglasPeuckerLineSimplifier.cpp b/Sources/geos/src/simplify/DouglasPeuckerLineSimplifier.cpp
index 634ad55..49e040e 100644
--- a/Sources/geos/src/simplify/DouglasPeuckerLineSimplifier.cpp
+++ b/Sources/geos/src/simplify/DouglasPeuckerLineSimplifier.cpp
@@ -19,29 +19,36 @@
 #include 
 #include 
 #include 
+#include 
+#include 
+#include 
 
 #include 
 #include  // for unique_ptr
 
+using geos::geom::CoordinateSequence;
+
 namespace geos {
 
 /// Line simplification algorithms
 namespace simplify { // geos::simplify
 
 /*public static*/
-DouglasPeuckerLineSimplifier::CoordsVectAutoPtr
+std::unique_ptr
 DouglasPeuckerLineSimplifier::simplify(
-    const DouglasPeuckerLineSimplifier::CoordsVect& nPts,
-    double distanceTolerance)
+    const CoordinateSequence& nPts,
+    double distanceTolerance,
+    bool preserveClosedEndpoint)
 {
     DouglasPeuckerLineSimplifier simp(nPts);
     simp.setDistanceTolerance(distanceTolerance);
+    simp.setPreserveClosedEndpoint(preserveClosedEndpoint);
     return simp.simplify();
 }
 
 /*public*/
 DouglasPeuckerLineSimplifier::DouglasPeuckerLineSimplifier(
-    const DouglasPeuckerLineSimplifier::CoordsVect& nPts)
+    const CoordinateSequence& nPts)
     :
     pts(nPts)
 {
@@ -52,31 +59,51 @@ void
 DouglasPeuckerLineSimplifier::setDistanceTolerance(
     double nDistanceTolerance)
 {
+    if (std::isnan(nDistanceTolerance)) {
+        throw util::IllegalArgumentException("Tolerance must not be NaN");
+    }
     distanceTolerance = nDistanceTolerance;
 }
 
+void
+DouglasPeuckerLineSimplifier::setPreserveClosedEndpoint(bool preserve)
+{
+    preserveEndpoint = preserve;
+}
+
 /*public*/
-DouglasPeuckerLineSimplifier::CoordsVectAutoPtr
+std::unique_ptr
 DouglasPeuckerLineSimplifier::simplify()
 {
-    CoordsVectAutoPtr coordList(new CoordsVect());
+    auto coordList = detail::make_unique();
 
     // empty coordlist is the simplest, won't simplify further
-    if(pts.empty()) {
+    if(pts.isEmpty()) {
         return coordList;
     }
 
-    usePt = BoolVectAutoPtr(new BoolVect(pts.size(), true));
+    usePt = std::vector(pts.size(), true);
     simplifySection(0, pts.size() - 1);
 
     for(std::size_t i = 0, n = pts.size(); i < n; ++i) {
-        if(usePt->operator[](i)) {
-            coordList->push_back(pts[i]);
+        if(usePt[i]) {
+            coordList->add(pts[i]);
+        }
+    }
+
+    // TODO avoid copying entire sequence?
+    bool simplifyRing = !preserveEndpoint && pts.isRing();
+    if (simplifyRing && coordList->size() > geom::LinearRing::MINIMUM_VALID_SIZE) {
+        geom::LineSegment seg(coordList->getAt(coordList->size() - 2), coordList->getAt(1));
+        if (seg.distance(coordList->getAt(0)) <= distanceTolerance) {
+            auto ret = detail::make_unique();
+            ret->reserve(coordList->size() - 1);
+            ret->add(*coordList, 1, coordList->size() - 2);
+            ret->closeRing();
+            coordList = std::move(ret);
         }
     }
 
-    // unique_ptr transfer ownership to its
-    // returned copy
     return coordList;
 }
 
@@ -87,10 +114,11 @@ DouglasPeuckerLineSimplifier::simplifySection(
     std::size_t j)
 {
     if((i + 1) == j) {
+
         return;
     }
 
-    geos::geom::LineSegment seg(pts[i], pts[j]);
+    geom::LineSegment seg(pts[i], pts[j]);
     double maxDistance = -1.0;
 
     std::size_t maxIndex = i;
@@ -104,7 +132,7 @@ DouglasPeuckerLineSimplifier::simplifySection(
     }
     if(maxDistance <= distanceTolerance) {
         for(std::size_t k = i + 1; k < j; k++) {
-            usePt->operator[](k) = false;
+            usePt[k] = false;
         }
     }
     else {
diff --git a/Sources/geos/src/simplify/DouglasPeuckerSimplifier.cpp b/Sources/geos/src/simplify/DouglasPeuckerSimplifier.cpp
index 378dea9..893d785 100644
--- a/Sources/geos/src/simplify/DouglasPeuckerSimplifier.cpp
+++ b/Sources/geos/src/simplify/DouglasPeuckerSimplifier.cpp
@@ -23,7 +23,6 @@
 #include 
 #include  // for Ptr typedefs
 #include 
-#include 
 #include  // for DPTransformer inheritance
 #include 
 #include 
@@ -113,16 +112,9 @@ DPTransformer::transformCoordinates(
 {
     ::geos::ignore_unused_variable_warning(parent);
 
-    Coordinate::Vect inputPts;
-    coords->toVector(inputPts);
+    bool preserveRingEndpoint = parent->getGeometryTypeId() != GEOS_LINEARRING;
 
-    std::unique_ptr newPts =
-        DouglasPeuckerLineSimplifier::simplify(inputPts, distanceTolerance);
-
-    return CoordinateSequence::Ptr(
-               factory->getCoordinateSequenceFactory()->create(
-                   newPts.release()
-               ));
+    return DouglasPeuckerLineSimplifier::simplify(*coords, distanceTolerance, preserveRingEndpoint);
 }
 
 Geometry::Ptr
diff --git a/Sources/geos/src/simplify/LineSegmentIndex.cpp b/Sources/geos/src/simplify/LineSegmentIndex.cpp
index 42b99e1..7de458c 100644
--- a/Sources/geos/src/simplify/LineSegmentIndex.cpp
+++ b/Sources/geos/src/simplify/LineSegmentIndex.cpp
@@ -54,15 +54,14 @@ class LineSegmentVisitor: public index::ItemVisitor {
 
     const LineSegment* querySeg;
 
-    std::unique_ptr< std::vector > items;
+    std::vector items;
 
 public:
 
     LineSegmentVisitor(const LineSegment* s)
         :
         ItemVisitor(),
-        querySeg(s),
-        items(new std::vector())
+        querySeg(s)
     {}
 
     ~LineSegmentVisitor() override
@@ -70,43 +69,23 @@ class LineSegmentVisitor: public index::ItemVisitor {
         // nothing to do, LineSegments are not owned by us
     }
 
-    LineSegmentVisitor(const LineSegmentVisitor& o)
-        :
-        ItemVisitor(),
-        querySeg(o.querySeg),
-        items(new std::vector(*(o.items.get())))
-    {
-    }
-
-    LineSegmentVisitor&
-    operator=(const LineSegmentVisitor& o)
-    {
-        if(this == &o) {
-            return *this;
-        }
-        querySeg = o.querySeg;
-        items.reset(new std::vector(*(o.items.get())));
-        return *this;
-    }
-
     void
     visitItem(void* item) override
     {
-        LineSegment* seg = static_cast(item);
+        const LineSegment* seg = static_cast(item);
         if(Envelope::intersects(seg->p0, seg->p1,
                                 querySeg->p0, querySeg->p1)) {
-            items->push_back(seg);
+            items.push_back(seg);
         }
     }
 
-    std::unique_ptr< std::vector >
+    std::vector
     getItems()
     {
         // NOTE: Apparently, this is 'source' method giving up the object resource.
         return std::move(items);
     }
 
-
 };
 
 
@@ -144,7 +123,7 @@ LineSegmentIndex::remove(const LineSegment* seg)
 }
 
 /*public*/
-std::unique_ptr< std::vector >
+std::vector
 LineSegmentIndex::query(const LineSegment* querySeg)
 {
     Envelope env(querySeg->p0, querySeg->p1);
@@ -152,7 +131,7 @@ LineSegmentIndex::query(const LineSegment* querySeg)
     LineSegmentVisitor visitor(querySeg);
     index.query(&env, visitor);
 
-    std::unique_ptr< std::vector > itemsFound = visitor.getItems();
+    auto itemsFound = visitor.getItems();
 
     return itemsFound;
 }
diff --git a/Sources/geos/src/simplify/LinkedLine.cpp b/Sources/geos/src/simplify/LinkedLine.cpp
new file mode 100644
index 0000000..684e493
--- /dev/null
+++ b/Sources/geos/src/simplify/LinkedLine.cpp
@@ -0,0 +1,183 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2022 Paul Ramsey 
+ * Copyright (c) 2022 Martin Davis.
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateSequence;
+using geos::io::WKTWriter;
+
+
+namespace geos {
+namespace simplify { // geos.simplify
+
+
+LinkedLine::LinkedLine(const CoordinateSequence& pts)
+    : m_coord(pts)
+    , m_isRing(pts.isRing())
+    , m_size(pts.isRing() ? pts.size() - 1 : pts.size())
+{
+    createNextLinks(m_size);
+    createPrevLinks(m_size);
+}
+
+
+/* public */
+bool
+LinkedLine::isRing() const
+{
+    return m_isRing;
+}
+
+/* public */
+bool
+LinkedLine::isCorner(std::size_t i) const
+{
+    if (! isRing()
+        && (i == 0 || i == m_coord.size() - 1))
+    {
+        return false;
+    }
+    return true;
+}
+
+/* private */
+void
+LinkedLine::createNextLinks(std::size_t size)
+{
+    m_next.resize(size);
+    for (std::size_t i = 0; i < size; i++) {
+      m_next[i] = i + 1;
+    }
+    m_next[size - 1] = m_isRing ? 0 : NO_COORD_INDEX;
+    return;
+}
+
+/* private */
+void
+LinkedLine::createPrevLinks(std::size_t size)
+{
+    m_prev.resize(size);
+    for (std::size_t i = 1; i < size; i++) {
+      m_prev[i] = i - 1;
+    }
+    m_prev[0] = m_isRing ? size - 1 : NO_COORD_INDEX;
+    return;
+}
+
+/* public */
+std::size_t
+LinkedLine::size() const
+{
+    return m_size;
+}
+
+/* public */
+std::size_t
+LinkedLine::next(std::size_t i) const
+{
+    return m_next[i];
+}
+
+/* public */
+std::size_t
+LinkedLine::prev(std::size_t i) const
+{
+    return m_prev[i];
+}
+
+/* public */
+const Coordinate&
+LinkedLine::getCoordinate(std::size_t index) const
+{
+    return m_coord.getAt(index);
+}
+
+/* public */
+const Coordinate&
+LinkedLine::prevCoordinate(std::size_t index) const
+{
+    return m_coord.getAt(prev(index));
+}
+
+/* public */
+const Coordinate&
+LinkedLine::nextCoordinate(std::size_t index) const
+{
+    return m_coord.getAt(next(index));
+}
+
+/* public */
+bool
+LinkedLine::hasCoordinate(std::size_t index) const
+{
+    //-- if not a ring, endpoints are always present
+    if (! m_isRing && (index == 0 || index == m_coord.size() - 1))
+        return true;
+
+    return index != NO_COORD_INDEX
+        && index < m_prev.size()
+        && m_prev[index] != NO_COORD_INDEX;
+}
+
+/* public */
+void
+LinkedLine::remove(std::size_t index)
+{
+    std::size_t iprev = m_prev[index];
+    std::size_t inext = m_next[index];
+    if (iprev != NO_COORD_INDEX)
+        m_next[iprev] = inext;
+    if (inext != NO_COORD_INDEX)
+        m_prev[inext] = iprev;
+    m_prev[index] = NO_COORD_INDEX;
+    m_next[index] = NO_COORD_INDEX;
+    m_size = m_size > 0 ? m_size - 1 : m_size;
+}
+
+/* public */
+std::unique_ptr
+LinkedLine::getCoordinates() const
+{
+    std::unique_ptr coords(new CoordinateSequence());
+    std::size_t len = m_isRing ? m_coord.size() - 1 : m_coord.size();
+    for (std::size_t i = 0; i < len; i++) {
+        if (hasCoordinate(i)) {
+            coords->add(m_coord.getAt(i), false);
+        }
+    }
+    if (m_isRing) {
+        coords->closeRing();
+    }
+    return coords;
+}
+
+std::ostream&
+operator<< (std::ostream& os, const LinkedLine& ll)
+{
+    auto cs = ll.getCoordinates();
+    os << WKTWriter::toLineString(*cs);
+    return os;
+}
+
+
+
+} // namespace geos.simplify
+} // namespace geos
diff --git a/Sources/geos/src/simplify/LinkedRing.cpp b/Sources/geos/src/simplify/LinkedRing.cpp
index 4a8d9f3..51bc28e 100644
--- a/Sources/geos/src/simplify/LinkedRing.cpp
+++ b/Sources/geos/src/simplify/LinkedRing.cpp
@@ -14,7 +14,8 @@
 
 #include 
 
-#include 
+#include 
+#include 
 
 
 using geos::geom::Coordinate;
@@ -113,10 +114,10 @@ LinkedRing::remove(std::size_t index)
 }
 
 /* public */
-std::unique_ptr
+std::unique_ptr
 LinkedRing::getCoordinates() const
 {
-    std::unique_ptr coords(new CoordinateArraySequence());
+    std::unique_ptr coords(new CoordinateSequence());
     for (std::size_t i = 0; i < m_coord.size() - 1; i++) {
         if (m_prev[i] != NO_COORD_INDEX) {
             coords->add(m_coord[i], false);
diff --git a/Sources/geos/src/simplify/RingHull.cpp b/Sources/geos/src/simplify/RingHull.cpp
index 0ec1265..0fbd73d 100644
--- a/Sources/geos/src/simplify/RingHull.cpp
+++ b/Sources/geos/src/simplify/RingHull.cpp
@@ -20,13 +20,13 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
 #include 
 #include 
 #include 
+#include 
 
 using geos::algorithm::Orientation;
 using geos::geom::Envelope;
@@ -51,10 +51,10 @@ namespace simplify { // geos.simplify
 * @param isOuter whether the hull is outer or inner
 */
 RingHull::RingHull(const LinearRing* p_ring, bool p_isOuter)
+    : inputRing(p_ring)
+    , vertex(inputRing->getCoordinates())
 {
-    inputRing = p_ring;
-    inputRing->getCoordinatesRO()->toVector(vertex);
-    init(vertex, p_isOuter);
+    init(*vertex, p_isOuter);
 }
 
 /* public */
@@ -91,7 +91,7 @@ RingHull::getHull(RingHullIndex& hullIndex)
 
 /* private */
 void
-RingHull::init(std::vector& ring, bool isOuter)
+RingHull::init(CoordinateSequence& ring, bool isOuter)
 {
     /**
      * Ensure ring is oriented according to outer/inner:
@@ -101,7 +101,7 @@ RingHull::init(std::vector& ring, bool isOuter)
     bool orientCW = isOuter;
     if (orientCW == Orientation::isCCW(inputRing->getCoordinatesRO()))
     {
-        std::reverse(ring.begin(), ring.end());
+        ring.reverse();
     }
 
     vertexRing.reset(new LinkedRing(ring));
@@ -118,7 +118,7 @@ RingHull::init(std::vector& ring, bool isOuter)
 
 /* private */
 void
-RingHull::addCorner(std::size_t i, std::priority_queue& queue)
+RingHull::addCorner(std::size_t i, Corner::PriorityQueue& queue)
 {
     //-- convex corners are left untouched
     if (isConvex(*vertexRing, i))
@@ -204,7 +204,7 @@ RingHull::isAtTarget(const Corner& corner)
 */
 /* private */
 void
-RingHull::removeCorner(const Corner& corner, std::priority_queue& queue)
+RingHull::removeCorner(const Corner& corner, Corner::PriorityQueue& queue)
 {
     std::size_t index = corner.getIndex();
     std::size_t prev = vertexRing->prev(index);
@@ -379,10 +379,10 @@ RingHull::Corner::isRemoved(const LinkedRing& ring) const
 std::unique_ptr
 RingHull::Corner::toLineString(const LinkedRing& ring)
 {
-    std::vector coords;
-    coords.push_back(ring.getCoordinate(prev));
-    coords.push_back(ring.getCoordinate(index));
-    coords.push_back(ring.getCoordinate(next));
+    auto coords = detail::make_unique();
+    coords->add(ring.getCoordinate(prev));
+    coords->add(ring.getCoordinate(index));
+    coords->add(ring.getCoordinate(next));
     auto gfact = GeometryFactory::create();
     return gfact->createLineString(std::move(coords));
 }
diff --git a/Sources/geos/src/simplify/TaggedLineString.cpp b/Sources/geos/src/simplify/TaggedLineString.cpp
index fc64e82..e0dc40d 100644
--- a/Sources/geos/src/simplify/TaggedLineString.cpp
+++ b/Sources/geos/src/simplify/TaggedLineString.cpp
@@ -23,7 +23,7 @@
 #include 
 #include  // for unique_ptr destructor
 #include 
-#include 
+#include 
 
 #include 
 #include 
@@ -44,10 +44,11 @@ namespace simplify { // geos::simplify
 
 /*public*/
 TaggedLineString::TaggedLineString(const geom::LineString* nParentLine,
-                                   std::size_t nMinimumSize)
-    :
-    parentLine(nParentLine),
-    minimumSize(nMinimumSize)
+    std::size_t nMinimumSize,
+    bool bIsRing)
+    : parentLine(nParentLine)
+    , minimumSize(nMinimumSize)
+    , m_isRing(bIsRing)
 {
     init();
 }
@@ -111,6 +112,13 @@ TaggedLineString::getMinimumSize() const
     return minimumSize;
 }
 
+/*public*/
+bool
+TaggedLineString::isRing() const
+{
+    return m_isRing;
+}
+
 /*public*/
 const geom::LineString*
 TaggedLineString::getParent() const
@@ -136,25 +144,22 @@ TaggedLineString::getResultCoordinates() const
          << resultSegs.size() << std::endl;
 #endif
 
-    CoordVectPtr pts = extractCoordinates(resultSegs);
+    auto pts = extractCoordinates(resultSegs);
 
 #if GEOS_DEBUG
     std::cerr << __FUNCTION__ << " extracted Coords.size: "
          << pts->size() << std::endl;
 #endif
 
-
-    CoordVect* v = pts.release();
-    return CoordinateSequence::Ptr(parentLine->getFactory()->getCoordinateSequenceFactory()->create(v));
-
+    return pts;
 }
 
 /*private static*/
-TaggedLineString::CoordVectPtr
+std::unique_ptr
 TaggedLineString::extractCoordinates(
     const std::vector& segs)
 {
-    CoordVectPtr pts(new CoordVect());
+    auto pts = detail::make_unique();
 
 #if GEOS_DEBUG
     std::cerr << __FUNCTION__ << " segs.size: " << segs.size() << std::endl;
@@ -166,16 +171,40 @@ TaggedLineString::extractCoordinates(
         for(std::size_t i = 0; i < size; i++) {
             TaggedLineSegment* seg = segs[i];
             assert(seg);
-            pts->push_back(seg->p0);
+            pts->add(seg->p0);
         }
 
         // add last point
-        pts->push_back(segs[size - 1]->p1);
+        pts->add(segs[size - 1]->p1);
     }
 
     return pts;
 }
 
+
+const Coordinate&
+TaggedLineString::getCoordinate(std::size_t i) const
+{
+
+    return parentLine->getCoordinateN(i);
+}
+
+std::size_t
+TaggedLineString::size() const
+{
+    return parentLine->getNumPoints();
+}
+
+const Coordinate&
+TaggedLineString::getComponentPoint() const
+{
+    return getParentCoordinates()->getAt(1);
+}
+
+
+
+
+
 /*public*/
 std::size_t
 TaggedLineString::getResultSize() const
@@ -202,7 +231,6 @@ TaggedLineString::getSegment(std::size_t i) const
 std::vector&
 TaggedLineString::getSegments()
 {
-    assert(0);
     return segs;
 }
 
@@ -213,6 +241,13 @@ TaggedLineString::getSegments() const
     return segs;
 }
 
+/*public*/
+const std::vector&
+TaggedLineString::getResultSegments() const
+{
+    return resultSegs;
+}
+
 /*public*/
 std::unique_ptr
 TaggedLineString::asLineString() const
@@ -246,5 +281,17 @@ TaggedLineString::addToResult(std::unique_ptr seg)
 #endif
 }
 
+const TaggedLineSegment*
+TaggedLineString::removeRingEndpoint()
+{
+    auto* firstSeg = resultSegs.front();
+    auto* lastSeg = resultSegs.back();
+
+    firstSeg->p0 = lastSeg->p0;
+    resultSegs.pop_back();
+    delete lastSeg;
+    return firstSeg;
+}
+
 } // namespace geos::simplify
 } // namespace geos
diff --git a/Sources/geos/src/simplify/TaggedLineStringSimplifier.cpp b/Sources/geos/src/simplify/TaggedLineStringSimplifier.cpp
index d6bda53..842710a 100644
--- a/Sources/geos/src/simplify/TaggedLineStringSimplifier.cpp
+++ b/Sources/geos/src/simplify/TaggedLineStringSimplifier.cpp
@@ -16,16 +16,16 @@
  *
  **********************************************************************/
 
-#include 
-#include 
 #include 
-#include 
-#include 
+#include 
 #include 
 #include 
-//#include  // for std::unique_ptr destructor
-//#include 
-//#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
 
 #include 
 #include 
@@ -39,10 +39,11 @@
 #include 
 #endif
 
-using namespace geos::geom;
-using std::pair;
-using std::unique_ptr;
-using std::vector;
+using geos::geom::LineSegment;
+using geos::geom::Coordinate;
+using geos::geom::LineString;
+using geos::algorithm::LineIntersector;
+using geos::algorithm::Orientation;
 
 namespace geos {
 namespace simplify { // geos::simplify
@@ -50,20 +51,20 @@ namespace simplify { // geos::simplify
 /*public*/
 TaggedLineStringSimplifier::TaggedLineStringSimplifier(
     LineSegmentIndex* nInputIndex,
-    LineSegmentIndex* nOutputIndex)
-    :
-    inputIndex(nInputIndex),
-    outputIndex(nOutputIndex),
-    li(new algorithm::LineIntersector()),
-    line(nullptr),
-    linePts(nullptr),
-    distanceTolerance(0.0)
-{
-}
+    LineSegmentIndex* nOutputIndex,
+    const ComponentJumpChecker* crossChecker)
+    : inputIndex(nInputIndex)
+    , outputIndex(nOutputIndex)
+    , jumpChecker(crossChecker)
+    , li(new LineIntersector())
+    , line(nullptr)
+    , linePts(nullptr)
+{}
+
 
 /*public*/
 void
-TaggedLineStringSimplifier::simplify(TaggedLineString* nLine)
+TaggedLineStringSimplifier::simplify(TaggedLineString* nLine, double distanceTolerance)
 {
     assert(nLine);
     line = nLine;
@@ -81,15 +82,17 @@ TaggedLineStringSimplifier::simplify(TaggedLineString* nLine)
     if(linePts->isEmpty()) {
         return;
     }
-    simplifySection(0, linePts->size() - 1, 0);
+    simplifySection(0, linePts->size() - 1, 0, distanceTolerance);
 
+    if(line->isRing() && linePts->isRing()) {
+        simplifyRingEndpoint(distanceTolerance);
+    }
 }
 
-
 /*private*/
 void
 TaggedLineStringSimplifier::simplifySection(std::size_t i,
-        std::size_t j, std::size_t depth)
+        std::size_t j, std::size_t depth, double distanceTolerance)
 {
     depth += 1;
 
@@ -141,16 +144,27 @@ TaggedLineStringSimplifier::simplifySection(std::size_t i,
               << std::endl;
 #endif
 
+    if (distance < 0) {
+        // negative distance indicates that we could not compute distance to the
+        // farthest point, probably because of infinite or large-magnitude coordinates.
+        // avoid trying to simplify this section.
+        for (std::size_t k = i; k < j; k++) {
+            auto newSeg = std::make_unique(*(line->getSegment(k)));
+            line->addToResult(std::move(newSeg));
+        }
+
+        return;
+    }
+
     // flattening must be less than distanceTolerance
     if(distance > distanceTolerance) {
         isValidToSimplify = false;
     }
 
-    // test if flattened section would cause intersection
-    LineSegment candidateSeg(linePts->getAt(i), linePts->getAt(j));
-
-    if(hasBadIntersection(line, std::make_pair(i, j), candidateSeg)) {
-        isValidToSimplify = false;
+    if (isValidToSimplify) {
+        // test if flattened section would cause intersection or jump
+        LineSegment flatSeg(linePts->getAt(i), linePts->getAt(j));
+        isValidToSimplify = isTopologyValid(line, i, j, flatSeg);
     }
 
     if(isValidToSimplify) {
@@ -168,11 +182,35 @@ TaggedLineStringSimplifier::simplifySection(std::size_t i,
         return;
     }
 
-    simplifySection(i, furthestPtIndex, depth);
-    simplifySection(furthestPtIndex, j, depth);
-
+    simplifySection(i, furthestPtIndex, depth, distanceTolerance);
+    simplifySection(furthestPtIndex, j, depth, distanceTolerance);
 }
 
+/*private*/
+void
+TaggedLineStringSimplifier::simplifyRingEndpoint(double distanceTolerance)
+{
+    if (line->getResultSize() > line->getMinimumSize()) {
+        const auto* firstSeg = static_cast(line->getResultSegments().front());
+        const auto* lastSeg = static_cast(line->getResultSegments().back());
+
+        LineSegment simpSeg(lastSeg->p0, firstSeg->p1);
+        const Coordinate& endPt = firstSeg->p0;
+        if (simpSeg.distance(endPt) <= distanceTolerance &&
+            isTopologyValid(line, firstSeg, lastSeg, simpSeg))
+        {
+            //-- don't know if segments are original or new, so remove from all indexes
+            inputIndex->remove(firstSeg);
+            inputIndex->remove(lastSeg);
+            outputIndex->remove(firstSeg);
+            outputIndex->remove(lastSeg);
+
+            const TaggedLineSegment* flatSeg = line->removeRingEndpoint();
+            //-- removed endpoint alters an existing result edge
+            outputIndex->add(flatSeg);
+        }
+    }
+}
 
 /*private*/
 std::unique_ptr
@@ -183,39 +221,66 @@ TaggedLineStringSimplifier::flatten(std::size_t start, std::size_t end)
     const Coordinate& p1 = linePts->getAt(end);
     std::unique_ptr newSeg(new TaggedLineSegment(p0, p1));
     // update the indexes
-    remove(line, start, end);
     outputIndex->add(newSeg.get());
+    remove(line, start, end);
     return newSeg;
 }
 
 /*private*/
 bool
-TaggedLineStringSimplifier::hasBadIntersection(
-    const TaggedLineString* parentLine,
-    const pair& sectionIndex,
-    const LineSegment& candidateSeg)
+TaggedLineStringSimplifier::isTopologyValid(
+    const TaggedLineString* lineIn,
+    std::size_t sectionStart, std::size_t sectionEnd,
+    const LineSegment& flatSeg)
 {
-    if(hasBadOutputIntersection(candidateSeg)) {
-        return true;
-    }
+    if (hasOutputIntersection(flatSeg))
+        return false;
+    if (hasInputIntersection(lineIn, sectionStart, sectionEnd, flatSeg))
+        return false;
+    if (jumpChecker->hasJump(lineIn, sectionStart, sectionEnd, flatSeg))
+        return false;
+    return true;
+}
 
-    if(hasBadInputIntersection(parentLine, sectionIndex, candidateSeg)) {
+/*private*/
+bool
+TaggedLineStringSimplifier::isTopologyValid(
+    const TaggedLineString* lineIn,
+    const LineSegment* seg1, const LineSegment* seg2,
+    const LineSegment& flatSeg)
+{
+    //-- if segments are already flat, topology is unchanged and so is valid
+    //-- (otherwise, output and/or input intersection test would report false positive)
+    if (isCollinear(seg1->p0, flatSeg))
         return true;
-    }
+    if (hasOutputIntersection(flatSeg))
+        return false;
+    if (hasInputIntersection(flatSeg))
+        return false;
+    if (jumpChecker->hasJump(lineIn, seg1, seg2, flatSeg))
+        return false;
+    return true;
+}
 
-    return false;
+/*private*/
+bool
+TaggedLineStringSimplifier::isCollinear(
+    const Coordinate& pt,
+    const LineSegment& seg) const
+{
+    return Orientation::COLLINEAR == seg.orientationIndex(pt);
 }
 
 /*private*/
 bool
-TaggedLineStringSimplifier::hasBadOutputIntersection(
-    const LineSegment& candidateSeg)
+TaggedLineStringSimplifier::hasOutputIntersection(
+    const LineSegment& flatSeg)
 {
-    std::unique_ptr< std::vector > querySegs =
-        outputIndex->query(&candidateSeg);
+    //std::unique_ptr>
+    auto querySegs = outputIndex->query(&flatSeg);
 
-    for(const LineSegment* querySeg : *querySegs) {
-        if(hasInteriorIntersection(*querySeg, candidateSeg)) {
+    for(const LineSegment* querySeg : querySegs) {
+        if(hasInvalidIntersection(*querySeg, flatSeg)) {
             return true;
         }
     }
@@ -225,29 +290,44 @@ TaggedLineStringSimplifier::hasBadOutputIntersection(
 
 /*private*/
 bool
-TaggedLineStringSimplifier::hasInteriorIntersection(
+TaggedLineStringSimplifier::hasInvalidIntersection(
     const LineSegment& seg0,
     const LineSegment& seg1) const
 {
+    if(seg0.equalsTopo(seg1))
+        return true;
     li->computeIntersection(seg0.p0, seg0.p1, seg1.p0, seg1.p1);
     return li->isInteriorIntersection();
 }
 
 /*private*/
 bool
-TaggedLineStringSimplifier::hasBadInputIntersection(
+TaggedLineStringSimplifier::hasInputIntersection(const LineSegment& flatSeg)
+{
+    return hasInputIntersection(nullptr, 0, 0, flatSeg);
+}
+
+/*private*/
+bool
+TaggedLineStringSimplifier::hasInputIntersection(
     const TaggedLineString* parentLine,
-    const pair& sectionIndex,
-    const LineSegment& candidateSeg)
+    const std::size_t excludeStart, const std::size_t excludeEnd,
+    const LineSegment& flatSeg)
 {
-    std::unique_ptr< std::vector > querySegs =
-        inputIndex->query(&candidateSeg);
+    const auto& querySegs = inputIndex->query(&flatSeg);
 
-    for(const LineSegment* ls : *querySegs) {
+    for(const LineSegment* ls : querySegs) {
         const TaggedLineSegment* querySeg = static_cast(ls);
 
-        if(!isInLineSection(parentLine, sectionIndex, querySeg) && hasInteriorIntersection(*querySeg, candidateSeg)) {
-
+        if (hasInvalidIntersection(*ls, flatSeg)) {
+            /**
+             * Ignore the intersection if the intersecting segment is part of the section being collapsed
+             * to the candidate segment
+             */
+            if (parentLine != nullptr &&
+                isInLineSection(line, excludeStart, excludeEnd, querySeg)) {
+                continue;
+            }
             return true;
         }
     }
@@ -258,20 +338,26 @@ TaggedLineStringSimplifier::hasBadInputIntersection(
 /*static private*/
 bool
 TaggedLineStringSimplifier::isInLineSection(
-    const TaggedLineString* line,
-    const pair& sectionIndex,
+    const TaggedLineString* lineIn,
+    const std::size_t excludeStart, const std::size_t excludeEnd,
     const TaggedLineSegment* seg)
 {
     // not in this line
-    if(seg->getParent() != line->getParent()) {
+    if(seg->getParent() != lineIn->getParent()) {
         return false;
     }
 
     std::size_t segIndex = seg->getIndex();
-    if(segIndex >= sectionIndex.first && segIndex < sectionIndex.second) {
-        return true;
+    if (excludeStart <= excludeEnd) {
+        //-- section is contiguous
+        if (segIndex >= excludeStart && segIndex < excludeEnd)
+            return true;
+    }
+    else {
+        //-- section wraps around the end of a ring
+        if (segIndex >= excludeStart || segIndex <= excludeEnd)
+            return true;
     }
-
     return false;
 }
 
@@ -323,8 +409,8 @@ TaggedLineStringSimplifier::findFurthestPoint(
     }
     maxDistance = maxDist;
     return maxIndex;
-
 }
 
+
 } // namespace geos::simplify
 } // namespace geos
diff --git a/Sources/geos/src/simplify/TaggedLinesSimplifier.cpp b/Sources/geos/src/simplify/TaggedLinesSimplifier.cpp
index d360c3a..af75f35 100644
--- a/Sources/geos/src/simplify/TaggedLinesSimplifier.cpp
+++ b/Sources/geos/src/simplify/TaggedLinesSimplifier.cpp
@@ -16,8 +16,9 @@
  *
  **********************************************************************/
 
-#include 
+#include 
 #include 
+#include 
 #include 
 #include 
 
@@ -41,27 +42,36 @@ namespace simplify { // geos::simplify
 
 /*public*/
 TaggedLinesSimplifier::TaggedLinesSimplifier()
-    :
-    inputIndex(new LineSegmentIndex()),
-    outputIndex(new LineSegmentIndex()),
-    taggedlineSimplifier(new TaggedLineStringSimplifier(inputIndex.get(),
-                         outputIndex.get()))
-{
-}
+    : inputIndex(new LineSegmentIndex())
+    , outputIndex(new LineSegmentIndex())
+    , distanceTolerance(0.0)
+{}
+
 
 /*public*/
 void
 TaggedLinesSimplifier::setDistanceTolerance(double d)
 {
-    taggedlineSimplifier->setDistanceTolerance(d);
+    distanceTolerance = d;
 }
 
-/*private*/
+
+/*public*/
 void
-TaggedLinesSimplifier::simplify(TaggedLineString& tls)
+TaggedLinesSimplifier::simplify(std::vector& taggedLines)
 {
-    taggedlineSimplifier->simplify(&tls);
+    ComponentJumpChecker jumpChecker(taggedLines);
+
+    for (auto* tls : taggedLines) {
+        inputIndex->add(*tls);
+    }
+
+    for (auto* tls : taggedLines) {
+        TaggedLineStringSimplifier tlss(inputIndex.get(), outputIndex.get(), &jumpChecker);
+        tlss.simplify(tls, distanceTolerance);
+    }
 }
 
+
 } // namespace geos::simplify
 } // namespace geos
diff --git a/Sources/geos/src/simplify/TopologyPreservingSimplifier.cpp b/Sources/geos/src/simplify/TopologyPreservingSimplifier.cpp
index 1e76cd2..85e39e1 100644
--- a/Sources/geos/src/simplify/TopologyPreservingSimplifier.cpp
+++ b/Sources/geos/src/simplify/TopologyPreservingSimplifier.cpp
@@ -92,6 +92,8 @@ LineStringTransformer::transformCoordinates(
     std::cerr << __FUNCTION__ << ": parent: " << parent
               << std::endl;
 #endif
+    if (coords->size() == 0) return nullptr;
+
     if(dynamic_cast(parent)) {
         LinesMap::iterator it = linestringMap.find(parent);
         assert(it != linestringMap.end());
@@ -134,9 +136,6 @@ class LineStringMapBuilderFilter: public geom::GeometryComponentFilter {
 
 public:
 
-    // no more needed
-    //friend class TopologyPreservingSimplifier;
-
     /**
      * Filters linear geometries.
      *
@@ -172,17 +171,21 @@ LineStringMapBuilderFilter::LineStringMapBuilderFilter(LinesMap& nMap, std::vect
 void
 LineStringMapBuilderFilter::filter_ro(const Geometry* geom)
 {
-    TaggedLineString* taggedLine;
+    auto typ = geom->getGeometryTypeId();
+    bool isRing = false;
 
-    if(const LineString* ls =
-                dynamic_cast(geom)) {
-        std::size_t minSize = ls->isClosed() ? 4 : 2;
-        taggedLine = new TaggedLineString(ls, minSize);
-    }
-    else {
+    if (geom->isEmpty()) return;
+
+    if (typ == GEOS_LINEARRING) {
+        isRing = true;
+    } else if (typ != GEOS_LINESTRING) {
         return;
     }
 
+    auto ls = static_cast(geom);
+    std::size_t minSize = ls->isClosed() ? 4 : 2;
+    TaggedLineString* taggedLine = new TaggedLineString(ls, minSize, isRing);
+
     // Duplicated Geometry pointers shouldn't happen
     if(! linestringMap.insert(std::make_pair(geom, taggedLine)).second) {
         delete taggedLine;
@@ -253,7 +256,7 @@ TopologyPreservingSimplifier::getResultGeometry()
                   << linestringMap.size() << " elements\n";
 #endif
 
-        lineSimplifier->simplify(tlsVector.begin(), tlsVector.end());
+        lineSimplifier->simplify(tlsVector);
 
 #if GEOS_DEBUG
         std::cerr << "all TaggedLineString simplified\n";
diff --git a/Sources/geos/src/triangulate/DelaunayTriangulationBuilder.cpp b/Sources/geos/src/triangulate/DelaunayTriangulationBuilder.cpp
index 9c5e9bb..cd80ecd 100644
--- a/Sources/geos/src/triangulate/DelaunayTriangulationBuilder.cpp
+++ b/Sources/geos/src/triangulate/DelaunayTriangulationBuilder.cpp
@@ -22,7 +22,6 @@
 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -47,14 +46,8 @@ DelaunayTriangulationBuilder::extractUniqueCoordinates(
 std::unique_ptr
 DelaunayTriangulationBuilder::unique(const CoordinateSequence* seq)
 {
-    auto seqFactory = CoordinateArraySequenceFactory::instance();
-    auto dim = seq->getDimension();
-
-    std::vector coords;
-    seq->toVector(coords);
-    std::sort(coords.begin(), coords.end(), geos::geom::CoordinateLessThen());
-
-    std::unique_ptr sortedSeq(seqFactory->create(std::move(coords), dim));
+    auto sortedSeq = detail::make_unique(*seq);
+    std::sort(sortedSeq->items().begin(), sortedSeq->items().end(), geos::geom::CoordinateLessThan());
 
     operation::valid::RepeatedPointTester rpt;
     if (rpt.hasRepeatedPoint(sortedSeq.get())) {
@@ -84,6 +77,8 @@ DelaunayTriangulationBuilder::DelaunayTriangulationBuilder() :
 void
 DelaunayTriangulationBuilder::setSites(const Geometry& geom)
 {
+    util::ensureNoCurvedComponents(geom);
+
     // remove any duplicate points (they will cause the triangulation to fail)
     siteCoords = extractUniqueCoordinates(geom);
 }
diff --git a/Sources/geos/src/triangulate/IncrementalDelaunayTriangulator.cpp b/Sources/geos/src/triangulate/IncrementalDelaunayTriangulator.cpp
index d4cf79d..1b6c545 100644
--- a/Sources/geos/src/triangulate/IncrementalDelaunayTriangulator.cpp
+++ b/Sources/geos/src/triangulate/IncrementalDelaunayTriangulator.cpp
@@ -21,18 +21,29 @@
 #include 
 #include 
 #include 
+#include 
+
+using geos::geom::Coordinate;
 
 namespace geos {
 namespace triangulate { //geos.triangulate
 
+using namespace algorithm;
 using namespace quadedge;
 
 IncrementalDelaunayTriangulator::IncrementalDelaunayTriangulator(
     QuadEdgeSubdivision* p_subdiv) :
-    subdiv(p_subdiv), isUsingTolerance(p_subdiv->getTolerance() > 0.0)
+    subdiv(p_subdiv), isUsingTolerance(p_subdiv->getTolerance() > 0.0),
+    m_isForceConvex(true)
 {
 }
 
+void 
+IncrementalDelaunayTriangulator::forceConvex(bool isForceConvex) 
+{
+    m_isForceConvex = isForceConvex;
+}
+
 void
 IncrementalDelaunayTriangulator::insertSites(const VertexList& vertices)
 {
@@ -82,25 +93,76 @@ IncrementalDelaunayTriangulator::insertSite(const Vertex& v)
     }
     while(&e->lNext() != startEdge);
 
-
-    // Examine suspect edges to ensure that the Delaunay condition
-    // is satisfied.
+    /**
+     * Examine suspect edges to ensure that the Delaunay condition is satisfied.
+     * If it is not, flip the edge and continue scanning.
+     * 
+     * Since the frame is not infinitely far away,
+     * edges which touch the frame or are adjacent to it require special logic
+     * to ensure the inner triangulation maintains a convex boundary.
+     */
     for(;;) {
+        //-- general case - flip if vertex is in circumcircle
         QuadEdge* t = &e->oPrev();
-        if(t->dest().rightOf(*e) &&
-                v.isInCircle(e->orig(), t->dest(), e->dest())) {
+        bool doFlip = t->dest().rightOf(*e) &&
+                v.isInCircle(e->orig(), t->dest(), e->dest());
+        
+        if (m_isForceConvex) {
+            //-- special cases to ensure triangulation boundary is convex
+            if (isConcaveBoundary(*e)) {
+            //-- flip if the triangulation boundary is concave
+                doFlip = true;
+            }
+            else if (isBetweenFrameAndInserted(*e, v)) {
+            //-- don't flip if edge lies between the inserted vertex and a frame vertex
+                doFlip = false;
+            }
+        }
+
+        if (doFlip) {
             QuadEdge::swap(*e);
             e = &e->oPrev();
+            continue;
         }
-        else if(&e->oNext() == startEdge) {
+        if (&e->oNext() == startEdge) {
             return *base; // no more suspect edges.
         }
-        else {
-            e = &e->oNext().lPrev();
-        }
+        //-- check next edge
+        e = &e->oNext().lPrev();
+    }
+}
+
+bool 
+IncrementalDelaunayTriangulator::isConcaveBoundary(const QuadEdge& e) const
+{
+    if (subdiv->isFrameVertex(e.dest())) {
+        return isConcaveAtOrigin(e);
     }
+    if (subdiv->isFrameVertex(e.orig())) {
+        return isConcaveAtOrigin(e.sym());
+    }
+    return false;
+}
+
+bool 
+IncrementalDelaunayTriangulator::isConcaveAtOrigin(const QuadEdge& e) const 
+{
+    const Coordinate& p = e.orig().getCoordinate();
+    const Coordinate& pp = e.oPrev().dest().getCoordinate();
+    const Coordinate& pn = e.oNext().dest().getCoordinate();
+    bool isConcave = Orientation::COUNTERCLOCKWISE == Orientation::index(pp, pn, p);
+    return isConcave;
+}
+
+bool 
+IncrementalDelaunayTriangulator::isBetweenFrameAndInserted(const QuadEdge& e, const Vertex& vInsert) const
+{
+    const Vertex& v1 = e.oNext().dest();
+    const Vertex& v2 = e.oPrev().dest();
+    return (v1.getCoordinate() == vInsert.getCoordinate() && subdiv->isFrameVertex(v2))
+        || (v2.getCoordinate() == vInsert.getCoordinate() && subdiv->isFrameVertex(v1));
 }
 
 } //namespace geos.triangulate
-} //namespace goes
+} //namespace geos
 
diff --git a/Sources/geos/src/triangulate/VoronoiDiagramBuilder.cpp b/Sources/geos/src/triangulate/VoronoiDiagramBuilder.cpp
index c9cfc60..eeae7f0 100644
--- a/Sources/geos/src/triangulate/VoronoiDiagramBuilder.cpp
+++ b/Sources/geos/src/triangulate/VoronoiDiagramBuilder.cpp
@@ -22,6 +22,8 @@
 #include 
 #include 
 #include 
+#include 
+#include 
 
 #include 
 #include 
@@ -42,20 +44,23 @@ using namespace geos::geom;
 
 
 VoronoiDiagramBuilder::VoronoiDiagramBuilder() :
-    tolerance(0.0), clipEnv(nullptr)
+    tolerance(0.0), clipEnv(nullptr), inputGeom(nullptr), inputSeq(nullptr), isOrdered(false)
 {
 }
 
 void
 VoronoiDiagramBuilder::setSites(const geom::Geometry& geom)
 {
+    util::ensureNoCurvedComponents(geom);
     siteCoords = DelaunayTriangulationBuilder::extractUniqueCoordinates(geom);
+    inputGeom = &geom;
 }
 
 void
 VoronoiDiagramBuilder::setSites(const geom::CoordinateSequence& coords)
 {
     siteCoords = DelaunayTriangulationBuilder::unique(&coords);
+    inputSeq = &coords;
 }
 
 void
@@ -64,6 +69,12 @@ VoronoiDiagramBuilder::setClipEnvelope(const geom::Envelope* nClipEnv)
     clipEnv = nClipEnv;
 }
 
+void
+VoronoiDiagramBuilder::setOrdered(bool p_isOrdered)
+{
+    isOrdered = p_isOrdered;
+}
+
 void
 VoronoiDiagramBuilder::setTolerance(double nTolerance)
 {
@@ -94,6 +105,11 @@ VoronoiDiagramBuilder::create()
 
     subdiv.reset(new quadedge::QuadEdgeSubdivision(diagramEnv, tolerance));
     IncrementalDelaunayTriangulator triangulator(subdiv.get());
+    /**
+     * Avoid creating very narrow triangles along triangulation boundary.
+     * These otherwise can cause malformed Voronoi cells.
+     */
+    triangulator.forceConvex(false);
     triangulator.insertSites(vertices);
 }
 
@@ -105,6 +121,15 @@ VoronoiDiagramBuilder::getSubdivision()
     return std::move(subdiv);
 }
 
+std::size_t
+VoronoiDiagramBuilder::getNumInputPoints() const {
+    if (inputGeom) {
+        return inputGeom->getNumPoints();
+    } else {
+        return inputSeq->getSize();
+    }
+}
+
 std::unique_ptr
 VoronoiDiagramBuilder::getDiagram(const geom::GeometryFactory& geomFact)
 {
@@ -113,6 +138,17 @@ VoronoiDiagramBuilder::getDiagram(const geom::GeometryFactory& geomFact)
     std::unique_ptr ret;
     if (subdiv) {
         auto polys = subdiv->getVoronoiCellPolygons(geomFact);
+
+        if (isOrdered) {
+            reorderCellsToInput(polys);
+        }
+
+        for (auto& p : polys) {
+            // Don't let references to Vertex objects
+            // owned by the QuadEdgeSubdivision escape
+            p->setUserData(nullptr);
+        }
+
         ret = clipGeometryCollection(polys, diagramEnv);
     }
 
@@ -123,7 +159,7 @@ VoronoiDiagramBuilder::getDiagram(const geom::GeometryFactory& geomFact)
     return ret;
 }
 
-std::unique_ptr
+std::unique_ptr
 VoronoiDiagramBuilder::getDiagramEdges(const geom::GeometryFactory& geomFact)
 {
     create();
@@ -132,13 +168,29 @@ VoronoiDiagramBuilder::getDiagramEdges(const geom::GeometryFactory& geomFact)
         return geomFact.createMultiLineString();
     }
 
-    std::unique_ptr edges = subdiv->getVoronoiDiagramEdges(geomFact);
+    auto edges = subdiv->getVoronoiDiagramEdges(geomFact);
+
     if(edges->isEmpty()) {
-        return std::unique_ptr(edges.release());
+        return edges;
     }
+
     std::unique_ptr clipPoly(geomFact.toGeometry(&diagramEnv));
     std::unique_ptr clipped(clipPoly->intersection(edges.get()));
-    return clipped;
+
+    switch (clipped->getGeometryTypeId()) {
+        case GEOS_LINESTRING: {
+            std::vector> lines;
+            lines.emplace_back(static_cast(clipped.release()));
+            return geomFact.createMultiLineString(std::move(lines));
+        }
+        case GEOS_MULTILINESTRING: {
+            std::unique_ptr mls(static_cast(clipped.release()));
+            return mls;
+        }
+        default: {
+            throw util::GEOSException("Unknown state");
+        }
+    }
 }
 
 std::unique_ptr
@@ -157,10 +209,8 @@ VoronoiDiagramBuilder::clipGeometryCollection(std::vectorgetEnvelopeInternal())) {
             clipped.push_back(std::move(g));
-            // TODO: check if userData is correctly cloned here?
         } else if(clipEnv.intersects(g->getEnvelopeInternal())) {
             auto result = clipPoly->intersection(g.get());
-            result->setUserData(g->getUserData()); // TODO: needed ?
             if (!result->isEmpty()) {
                 clipped.push_back(std::move(result));
             }
@@ -170,5 +220,69 @@ VoronoiDiagramBuilder::clipGeometryCollection(std::vectorcreateGeometryCollection(std::move(clipped));
 }
 
+void
+VoronoiDiagramBuilder::addCellsForCoordinates(CoordinateCellMap& cellMap,
+                                              const CoordinateSequence& seq,
+                                              std::vector> & polys) {
+    for (const CoordinateXY& c : seq.items()) {
+        auto cell = cellMap.find(c);
+
+        if (cell == cellMap.end()) {
+            std::stringstream ss;
+            ss << "No cell found for input coordinate " << c;
+            throw util::GEOSException(ss.str());
+        }
+
+        if (cell->second == nullptr) {
+            std::stringstream ss;
+            ss << "Multiple input coordinates in cell at " << c;
+            throw util::GEOSException(ss.str());
+        }
+
+        polys.push_back(std::move(cell->second));
+    }
+}
+
+void
+VoronoiDiagramBuilder::addCellsForCoordinates(CoordinateCellMap& cellMap,
+                                              const Geometry& g,
+                                              std::vector> & polys) {
+    auto typ = g.getGeometryTypeId();
+
+    if (typ == GEOS_LINESTRING) {
+        const auto& seq = *static_cast(g).getCoordinatesRO();
+        addCellsForCoordinates(cellMap, seq, polys);
+    } else if (typ == GEOS_POINT) {
+        const auto& seq = *static_cast(g).getCoordinatesRO();
+        addCellsForCoordinates(cellMap, seq, polys);
+    } else {
+        for (std::size_t i = 0; i < g.getNumGeometries(); i++) {
+            addCellsForCoordinates(cellMap, *g.getGeometryN(i), polys);
+        }
+    }
+}
+
+void
+VoronoiDiagramBuilder::reorderCellsToInput(std::vector> & polys) const
+{
+    CoordinateCellMap cellMap;
+    for (auto& p : polys) {
+        const CoordinateXY* c = reinterpret_cast(p->getUserData());
+        cellMap.emplace(*c, std::move(p));
+    }
+
+    auto npts = getNumInputPoints();
+    std::vector> reorderedPolys;
+    reorderedPolys.reserve(npts);
+
+    if (inputSeq) {
+        addCellsForCoordinates(cellMap, *inputSeq, reorderedPolys);
+    } else {
+        addCellsForCoordinates(cellMap, *inputGeom, reorderedPolys);
+    }
+
+    polys = std::move(reorderedPolys);
+}
+
 } //namespace geos.triangulate
 } //namespace geos
diff --git a/Sources/geos/src/triangulate/polygon/ConstrainedDelaunayTriangulator.cpp b/Sources/geos/src/triangulate/polygon/ConstrainedDelaunayTriangulator.cpp
index c52f855..5f56b12 100644
--- a/Sources/geos/src/triangulate/polygon/ConstrainedDelaunayTriangulator.cpp
+++ b/Sources/geos/src/triangulate/polygon/ConstrainedDelaunayTriangulator.cpp
@@ -39,8 +39,10 @@ ConstrainedDelaunayTriangulator::triangulate(const Geometry* geom)
 
 /* private */
 std::unique_ptr
-ConstrainedDelaunayTriangulator::compute()
+ConstrainedDelaunayTriangulator::compute() const
 {
+    util::ensureNoCurvedComponents(inputGeom);
+
     // short circuit empty input case
     if(inputGeom->isEmpty()) {
         auto gf = inputGeom->getFactory();
diff --git a/Sources/geos/src/triangulate/polygon/PolygonEarClipper.cpp b/Sources/geos/src/triangulate/polygon/PolygonEarClipper.cpp
index 91c0797..3bb5406 100644
--- a/Sources/geos/src/triangulate/polygon/PolygonEarClipper.cpp
+++ b/Sources/geos/src/triangulate/polygon/PolygonEarClipper.cpp
@@ -16,7 +16,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -35,7 +34,7 @@ namespace polygon {
 
 
 /* public */
-PolygonEarClipper::PolygonEarClipper(const std::vector& polyShell)
+PolygonEarClipper::PolygonEarClipper(const geom::CoordinateSequence& polyShell)
     : vertex(polyShell)
     , vertexSize(polyShell.size()-1)
     , vertexFirst(0)
@@ -45,6 +44,7 @@ PolygonEarClipper::PolygonEarClipper(const std::vector& polyShell)
     initCornerIndex();
 }
 
+
 /* private */
 std::vector
 PolygonEarClipper::createNextLinks(std::size_t size) const
@@ -60,21 +60,12 @@ PolygonEarClipper::createNextLinks(std::size_t size) const
 
 /* public static */
 void
-PolygonEarClipper::triangulate(const std::vector& polyShell, TriList& triListResult)
+PolygonEarClipper::triangulate(const geom::CoordinateSequence& polyShell, TriList& triListResult)
 {
     PolygonEarClipper clipper(polyShell);
     clipper.compute(triListResult);
 }
 
-/* public static */
-void
-PolygonEarClipper::triangulate(const CoordinateSequence& polyShellCs, TriList& triListResult)
-{
-    std::vector pts;
-    polyShellCs.toVector(pts);
-    PolygonEarClipper clipper(pts);
-    clipper.compute(triListResult);
-}
 
 /* public */
 void
@@ -151,7 +142,7 @@ PolygonEarClipper::isValidEar(std::size_t cornerIdx, const std::array result;
     vertexCoordIndex.query(cornerEnv, result);
 
-    std::size_t dupApexIndex = NO_VERTEX_INDEX;
+    std::size_t dupApexIndex = NO_COORD_INDEX;
     //--- check for duplicate vertices
     for (std::size_t i = 0; i < result.size(); i++) {
         std::size_t vertIndex = result[i];
@@ -200,10 +191,10 @@ PolygonEarClipper::findIntersectingVertex(std::size_t cornerIdx, const std::arra
         else if (geom::Triangle::intersects(corner[0], corner[1], corner[2], v))
             return vertIndex;
     }
-    if (dupApexIndex != NO_VERTEX_INDEX) {
+    if (dupApexIndex != NO_COORD_INDEX) {
         return dupApexIndex;
     }
-    return NO_VERTEX_INDEX;
+    return NO_COORD_INDEX;
 }
 
 
@@ -269,7 +260,7 @@ PolygonEarClipper::removeCorner()
     }
     vertexNext[cornerIndex[0]] = vertexNext[cornerApexIndex];
     vertexCoordIndex.remove(cornerApexIndex);
-    vertexNext[cornerApexIndex] = NO_VERTEX_INDEX;
+    vertexNext[cornerApexIndex] = NO_COORD_INDEX;
     vertexSize--;
     //-- adjust following corner indexes
     cornerIndex[1] = nextIndex(cornerIndex[0]);
@@ -281,7 +272,7 @@ PolygonEarClipper::removeCorner()
 bool
 PolygonEarClipper::isRemoved(std::size_t vertexIndex) const
 {
-    return NO_VERTEX_INDEX == vertexNext[vertexIndex];
+    return NO_COORD_INDEX == vertexNext[vertexIndex];
 }
 
 
@@ -356,7 +347,7 @@ std::unique_ptr
 PolygonEarClipper::toGeometry() const
 {
     auto gf = geom::GeometryFactory::create();
-    std::unique_ptr cs(new geom::CoordinateArraySequence());
+    std::unique_ptr cs(new geom::CoordinateSequence());
     std::size_t index = vertexFirst;
     for (std::size_t i = 0; i < vertexSize; i++) {
         const Coordinate& v = vertex[index];
diff --git a/Sources/geos/src/triangulate/polygon/PolygonHoleJoiner.cpp b/Sources/geos/src/triangulate/polygon/PolygonHoleJoiner.cpp
index 896c0ce..8b4c0e9 100644
--- a/Sources/geos/src/triangulate/polygon/PolygonHoleJoiner.cpp
+++ b/Sources/geos/src/triangulate/polygon/PolygonHoleJoiner.cpp
@@ -20,7 +20,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -38,7 +37,6 @@ using geos::algorithm::LineIntersector;
 using geos::algorithm::Orientation;
 using geos::algorithm::PolygonNodeTopology;
 using geos::geom::Coordinate;
-using geos::geom::CoordinateArraySequence;
 using geos::geom::CoordinateSequence;
 using geos::noding::BasicSegmentString;
 using geos::noding::SegmentString;
@@ -127,12 +125,11 @@ PolygonHoleJoiner::compute()
         nodeRings();
     }
     joinedRing.clear();
-    shellRing->toVector(joinedRing);
+    joinedRing.add(*shellRing);
     if (holeRings.size() > 0) {
         joinHoles();
     }
-    std::unique_ptr cas(new CoordinateArraySequence(std::move(joinedRing)));
-    return cas;
+    return detail::make_unique(joinedRing);
 }
 
 /* private */
@@ -154,7 +151,7 @@ PolygonHoleJoiner::extractOrientedRing(const LinearRing* ring, bool isCW)
     std::unique_ptr pts = ring->getCoordinates();
     bool isRingCW = ! Orientation::isCCW(pts.get());
     if (isCW != isRingCW) {
-        CoordinateSequence::reverse(pts.get());
+        pts->reverse();
     }
     return pts;
 }
@@ -167,7 +164,7 @@ PolygonHoleJoiner::nodeRings()
     noder.node();
     shellRing = noder.getNodedShell();
     for (std::size_t i = 0; i < holeRings.size(); i++) {
-        holeRings[i] = noder.getNodedHole(i);
+        holeRings[i].reset(noder.getNodedHole(i).release());
     }
     isHoleTouchingHint = noder.getHolesTouching();
 }
@@ -180,7 +177,7 @@ PolygonHoleJoiner::joinHoles()
     boundaryIntersector = createBoundaryIntersector();
 
     joinedPts.clear();
-    joinedPts.insert(joinedRing.begin(), joinedRing.end());
+    joinedPts.insert(joinedRing.items().begin(), joinedRing.items().end());
 
     for (std::size_t i = 0; i < holeRings.size(); i++) {
         joinHole(i, *(holeRings[i]));
@@ -208,7 +205,7 @@ PolygonHoleJoiner::joinTouchingHole(const CoordinateSequence& holeCoords)
     std::size_t holeTouchIndex = findHoleTouchIndex(holeCoords);
 
     //-- hole does not touch
-    if (holeTouchIndex == NO_INDEX)
+    if (holeTouchIndex == NO_COORD_INDEX)
         return false;
 
     /**
@@ -228,11 +225,14 @@ PolygonHoleJoiner::joinTouchingHole(const CoordinateSequence& holeCoords)
 std::size_t
 PolygonHoleJoiner::findHoleTouchIndex(const CoordinateSequence& holeCoords)
 {
-    for (std::size_t i = 0; i < holeCoords.size(); i++) {
-        if (joinedPts.count(holeCoords.getAt(i)) > 0)
+    std::size_t i = 0;
+    for (auto& coord : holeCoords.items()) {
+        if (joinedPts.count(coord) > 0) {
             return i;
+        }
+        i++;
     }
-    return NO_INDEX;
+    return NO_COORD_INDEX;
 }
 
 
@@ -270,14 +270,13 @@ PolygonHoleJoiner::findJoinableVertex(const Coordinate& holeJoinCoord)
 std::size_t
 PolygonHoleJoiner::findJoinIndex(const Coordinate& joinCoord, const Coordinate& holeJoinCoord)
 {
-    std::size_t i = 0;
-    for (auto& coord: joinedRing) {
-        if (joinCoord.equals2D(coord)) {
+    //-- linear scan is slow but only done once per hole
+    for (std::size_t i = 0; i < joinedRing.size() - 1; i++) {
+        if (joinCoord.equals2D(joinedRing.getAt(i))) {
             if (isLineInterior(joinedRing, i, holeJoinCoord)) {
                 return i;
             }
         }
-        i++;
     }
     throw IllegalStateException("Unable to find shell join index with interior join line");
 }
@@ -285,11 +284,11 @@ PolygonHoleJoiner::findJoinIndex(const Coordinate& joinCoord, const Coordinate&
 
 /* private static */
 bool
-PolygonHoleJoiner::isLineInterior(const std::vector& ring, std::size_t ringIndex, const Coordinate& linePt)
+PolygonHoleJoiner::isLineInterior(const CoordinateSequence& ring, std::size_t ringIndex, const Coordinate& linePt)
 {
-    const Coordinate& nodePt = ring.at(ringIndex);
-    const Coordinate& shell0 = ring.at(prev(ringIndex, ring.size()));
-    const Coordinate& shell1 = ring.at(next(ringIndex, ring.size()));
+    const Coordinate& nodePt = ring.getAt(ringIndex);
+    const Coordinate& shell0 = ring.getAt(prev(ringIndex, ring.size()));
+    const Coordinate& shell1 = ring.getAt(next(ringIndex, ring.size()));
     return PolygonNodeTopology::isInteriorSegment(&nodePt, &shell0, &shell1, &linePt);
 }
 
@@ -317,7 +316,7 @@ PolygonHoleJoiner::next(std::size_t i, std::size_t size)
 void
 PolygonHoleJoiner::addJoinedHole(std::size_t joinIndex, const CoordinateSequence& holeCoords, std::size_t holeJoinIndex)
 {
-    const Coordinate& joinPt = joinedRing.at(joinIndex);
+    const Coordinate& joinPt = joinedRing.getAt(joinIndex);
     const Coordinate& holeJoinPt = holeCoords.getAt(holeJoinIndex);
 
     //-- check for touching (zero-length) join to avoid inserting duplicate vertices
@@ -328,8 +327,8 @@ PolygonHoleJoiner::addJoinedHole(std::size_t joinIndex, const CoordinateSequence
     std::vector newSection = createHoleSection(holeCoords, holeJoinIndex, addJoinPt);
 
     //-- add section after shell join vertex
-    long addIndex = static_cast(joinIndex + 1);
-    joinedRing.insert(joinedRing.begin() + addIndex, newSection.begin(), newSection.end());
+    std::size_t addIndex = joinIndex + 1;
+    joinedRing.add(addIndex, newSection.begin(), newSection.end());
     joinedPts.insert(newSection.begin(), newSection.end());
 }
 
@@ -388,14 +387,14 @@ PolygonHoleJoiner::sortHoles(const Polygon* poly)
 
 /* private static */
 std::size_t
-PolygonHoleJoiner::findLowestLeftVertexIndex(const CoordinateSequence& coords)
+PolygonHoleJoiner::findLowestLeftVertexIndex(const CoordinateSequence& holeCoords)
 {
     Coordinate lowestLeftCoord;
     lowestLeftCoord.setNull();
-    std::size_t lowestLeftIndex = NO_INDEX;
-    for (std::size_t i = 0; i < coords.size() - 1; i++) {
-        if (lowestLeftCoord.isNull() || coords.getAt(i).compareTo(lowestLeftCoord) < 0) {
-            lowestLeftCoord = coords.getAt(i);
+    std::size_t lowestLeftIndex = NO_COORD_INDEX;
+    for (std::size_t i = 0; i < holeCoords.size() - 1; i++) {
+        if (lowestLeftCoord.isNull() || holeCoords.getAt(i).compareTo(lowestLeftCoord) < 0) {
+            lowestLeftCoord = holeCoords.getAt(i);
             lowestLeftIndex = i;
         }
     }
@@ -407,8 +406,7 @@ PolygonHoleJoiner::findLowestLeftVertexIndex(const CoordinateSequence& coords)
 bool
 PolygonHoleJoiner::intersectsBoundary(const Coordinate& p0, const Coordinate& p1)
 {
-    CoordinateArraySequence cs;
-    cs.add(p0); cs.add(p1);
+    CoordinateSequence cs { p0, p1 };
     BasicSegmentString bss(&cs, nullptr);
     std::vector segStrings { &bss };
 
@@ -435,7 +433,7 @@ PolygonHoleJoiner::createBoundaryIntersector()
     }
     std::unique_ptr mssmi(new MCIndexSegmentSetMutualIntersector());
     mssmi->setBaseSegments(&polySegStrings);
-    return RETURN_UNIQUE_PTR(mssmi);
+    return mssmi;
 }
 
 
diff --git a/Sources/geos/src/triangulate/polygon/PolygonNoder.cpp b/Sources/geos/src/triangulate/polygon/PolygonNoder.cpp
index 2cd7189..b949bd5 100644
--- a/Sources/geos/src/triangulate/polygon/PolygonNoder.cpp
+++ b/Sources/geos/src/triangulate/polygon/PolygonNoder.cpp
@@ -14,8 +14,6 @@
 
 #include 
 #include 
-#include 
-#include 
 #include 
 #include 
 #include 
@@ -26,8 +24,6 @@
 
 using geos::algorithm::LineIntersector;
 using geos::geom::Coordinate;
-using geos::geom::CoordinateSequence;
-using geos::geom::CoordinateArraySequence;
 using geos::noding::SegmentString;
 using geos::noding::NodedSegmentString;
 using geos::noding::SegmentIntersector;
@@ -154,16 +150,14 @@ PolygonNoder::isHoleNoded(std::size_t i)
 std::unique_ptr
 PolygonNoder::getNodedShell()
 {
-    std::vector nodedCoords = nodedRings[0]->getNodedCoordinates();
-    return detail::make_unique(std::move(nodedCoords));
+    return nodedRings[0]->getNodedCoordinates();
 }
 
 /* public */
 std::unique_ptr
 PolygonNoder::getNodedHole(std::size_t i)
 {
-    std::vector nodedCoords = nodedRings[i+1]->getNodedCoordinates();
-    return detail::make_unique(std::move(nodedCoords));\
+    return nodedRings[i+1]->getNodedCoordinates();
 }
 
 /* public */
@@ -194,7 +188,7 @@ PolygonNoder::createNodedSegString(std::unique_ptr& ringPts,
     // note: in PolygonHoleJoiner::nodeRings we will replace the contents
     // of the shellRing and holeRings with the results of the calculation
     // here, so it's OK to take ownership of the points from them here
-    NodedSegmentString* nss = new NodedSegmentString(ringPts.release(), nullptr);
+    NodedSegmentString* nss = new NodedSegmentString(ringPts.release(), false, false, nullptr);
     nss->setData(nss);
     // need to map the identity of this nss to the index number of the
     // ring it represents. use an external map to avoid abusing the void*
diff --git a/Sources/geos/src/triangulate/polygon/TriDelaunayImprover.cpp b/Sources/geos/src/triangulate/polygon/TriDelaunayImprover.cpp
index 2a6f9ac..21bae66 100644
--- a/Sources/geos/src/triangulate/polygon/TriDelaunayImprover.cpp
+++ b/Sources/geos/src/triangulate/polygon/TriDelaunayImprover.cpp
@@ -138,7 +138,7 @@ TriDelaunayImprover::isInCircle(
     const Coordinate& a, const Coordinate& b,
     const Coordinate& c, const Coordinate& p)
 {
-    return triangulate::quadedge::TrianglePredicate::isInCircleRobust(a, c, b, p);
+    return triangulate::quadedge::TrianglePredicate::isInCircleRobust(a, c, b, p) == geom::Location::INTERIOR;
 }
 
 
diff --git a/Sources/geos/src/triangulate/quadedge/QuadEdgeSubdivision.cpp b/Sources/geos/src/triangulate/quadedge/QuadEdgeSubdivision.cpp
index 9cbd953..ba92a5e 100644
--- a/Sources/geos/src/triangulate/quadedge/QuadEdgeSubdivision.cpp
+++ b/Sources/geos/src/triangulate/quadedge/QuadEdgeSubdivision.cpp
@@ -27,9 +27,6 @@
 #include 
 #include 
 #include 
-#include 
-#include 
-#include 
 #include 
 #include 
 #include 
@@ -40,6 +37,7 @@
 #include 
 #include 
 #include 
+#include 
 
 
 using namespace geos::geom;
@@ -49,6 +47,11 @@ namespace geos {
 namespace triangulate { //geos.triangulate
 namespace quadedge { //geos.triangulate.quadedge
 
+const double EDGE_COINCIDENCE_TOL_FACTOR = 1000;
+
+//-- Frame size factor for initializing subdivision frame.  Larger is more robust
+const double FRAME_SIZE_FACTOR = 10;
+
 void
 QuadEdgeSubdivision::getTriangleEdges(const QuadEdge& startQE,
                                       const QuadEdge* triEdge[3])
@@ -356,7 +359,6 @@ class
     QuadEdgeSubdivision::TriangleCoordinatesVisitor : public TriangleVisitor {
 private:
     QuadEdgeSubdivision::TriList* triCoords;
-    CoordinateArraySequenceFactory coordSeqFact;
 
 public:
     TriangleCoordinatesVisitor(QuadEdgeSubdivision::TriList* p_triCoords): triCoords(p_triCoords)
@@ -366,7 +368,7 @@ class
     void
     visit(std::array& triEdges) override
     {
-        auto coordSeq = coordSeqFact.create(4, 0);
+        auto coordSeq = detail::make_unique(4u, 0u);
         for(std::size_t i = 0; i < 3; i++) {
             Vertex v = triEdges[i]->orig();
             coordSeq->setAt(v.getCoordinate(), i);
@@ -442,16 +444,15 @@ QuadEdgeSubdivision::getEdges(const geom::GeometryFactory& geomFact)
 {
     std::unique_ptr p_quadEdges(getPrimaryEdges(false));
     std::vector> edges;
-    const CoordinateSequenceFactory* coordSeqFact = geomFact.getCoordinateSequenceFactory();
 
     edges.reserve(p_quadEdges->size());
     for(const QuadEdge* qe : *p_quadEdges) {
-        auto coordSeq = coordSeqFact->create(2);
+        auto coordSeq = detail::make_unique(2u);
 
         coordSeq->setAt(qe->orig().getCoordinate(), 0);
         coordSeq->setAt(qe->dest().getCoordinate(), 1);
 
-        edges.emplace_back(geomFact.createLineString(coordSeq.release()));
+        edges.emplace_back(geomFact.createLineString(std::move(coordSeq)));
     }
 
     return geomFact.createMultiLineString(std::move(edges));
@@ -526,13 +527,13 @@ QuadEdgeSubdivision::getVoronoiCellEdges(const geom::GeometryFactory& geomFact)
 std::unique_ptr
 QuadEdgeSubdivision::getVoronoiCellPolygon(const QuadEdge* qe, const geom::GeometryFactory& geomFact)
 {
-    std::vector cellPts;
+    auto cellPts = detail::make_unique();
 
     const QuadEdge* startQE = qe;
     do {
         const Coordinate& cc = qe->rot().orig().getCoordinate();
-        if(cellPts.empty() || cellPts.back() != cc) {  // no duplicates
-            cellPts.push_back(cc);
+        if(cellPts->isEmpty() || cellPts->back() != cc) {  // no duplicates
+            cellPts->add(cc);
         }
         qe = &qe->oPrev();
 
@@ -540,36 +541,32 @@ QuadEdgeSubdivision::getVoronoiCellPolygon(const QuadEdge* qe, const geom::Geome
     while(qe != startQE);
 
     // Close the ring
-    if (cellPts.front() != cellPts.back()) {
-        cellPts.push_back(cellPts.front());
+    if (cellPts->front() != cellPts->back()) {
+        cellPts->closeRing();
     }
-    if (cellPts.size() < 4) {
-        cellPts.push_back(cellPts.back());
+    if (cellPts->size() < 4) {
+        cellPts->add(cellPts->back());
     }
 
-    auto seq = geomFact.getCoordinateSequenceFactory()->create(std::move(cellPts));
-    std::unique_ptr cellPoly = geomFact.createPolygon(geomFact.createLinearRing(std::move(seq)));
+    std::unique_ptr cellPoly = geomFact.createPolygon(geomFact.createLinearRing(std::move(cellPts)));
+
+    const Vertex& v = startQE->orig();
+    const Coordinate& c = v.getCoordinate();
+    cellPoly->setUserData(const_cast(&c));
 
-    /*
-    //-- attach cell centroid coordinate
-    // MD - disable this since memory handling is problematic
-    Vertex v = startQE->orig();
-    c = v.getCoordinate();
-    cellPoly->setUserData(reinterpret_cast(&c));
-    */
     return cellPoly;
 }
 
 std::unique_ptr
 QuadEdgeSubdivision::getVoronoiCellEdge(const QuadEdge* qe, const geom::GeometryFactory& geomFact)
 {
-    std::vector cellPts;
+    auto cellPts = detail::make_unique();
 
     const QuadEdge* startQE = qe;
     do {
         const Coordinate& cc = qe->rot().orig().getCoordinate();
-        if(cellPts.empty() || cellPts.back() != cc) {  // no duplicates
-            cellPts.push_back(cc);
+        if(cellPts->isEmpty() || cellPts->back() != cc) {  // no duplicates
+            cellPts->add(cc);
         }
         qe = &qe->oPrev();
 
@@ -577,18 +574,13 @@ QuadEdgeSubdivision::getVoronoiCellEdge(const QuadEdge* qe, const geom::Geometry
     while(qe != startQE);
 
     // Close the ring
-    if (cellPts.front() != cellPts.back()) {
-        cellPts.push_back(cellPts.front());
+    if (cellPts->front() != cellPts->back()) {
+        cellPts->closeRing();
     }
 
     std::unique_ptr cellEdge(
-        geomFact.createLineString(new geom::CoordinateArraySequence(std::move(cellPts))));
+        geomFact.createLineString(std::move(cellPts)));
 
-    // FIXME why is this returning a pointer to a local variable?
-    Vertex v = startQE->orig();
-    Coordinate c(0, 0);
-    c = v.getCoordinate();
-    cellEdge->setUserData(reinterpret_cast(&c));
     return cellEdge;
 }
 
@@ -602,9 +594,8 @@ QuadEdgeSubdivision::getVertexUniqueEdges(bool includeFrame)
         QuadEdge* qe = &quartet.base();
         const Vertex& v = qe->orig();
 
-        if(visitedVertices.find(v) == visitedVertices.end()) {	//if v not found
-            visitedVertices.insert(v);
-
+        if(visitedVertices.insert(v).second) {
+            // v was not found and was newly inserted
             if(includeFrame || ! QuadEdgeSubdivision::isFrameVertex(v)) {
                 edges->push_back(qe);
             }
@@ -612,8 +603,8 @@ QuadEdgeSubdivision::getVertexUniqueEdges(bool includeFrame)
         QuadEdge* qd = &(qe->sym());
         const Vertex& vd = qd->orig();
 
-        if(visitedVertices.find(vd) == visitedVertices.end()) {
-            visitedVertices.insert(vd);
+        if (visitedVertices.insert(vd).second) {
+            // vd was not found and was newly inserted
             if(includeFrame || ! QuadEdgeSubdivision::isFrameVertex(vd)) {
                 edges->push_back(qd);
             }
@@ -624,4 +615,5 @@ QuadEdgeSubdivision::getVertexUniqueEdges(bool includeFrame)
 
 } //namespace geos.triangulate.quadedge
 } //namespace geos.triangulate
+
 } //namespace goes
diff --git a/Sources/geos/src/triangulate/quadedge/TrianglePredicate.cpp b/Sources/geos/src/triangulate/quadedge/TrianglePredicate.cpp
index 4c8da95..09a606e 100644
--- a/Sources/geos/src/triangulate/quadedge/TrianglePredicate.cpp
+++ b/Sources/geos/src/triangulate/quadedge/TrianglePredicate.cpp
@@ -19,6 +19,7 @@
 #include 
 
 #include 
+#include 
 
 using geos::geom::Coordinate;
 
@@ -26,24 +27,24 @@ namespace geos {
 namespace triangulate {
 namespace quadedge {
 
-bool
+geom::Location
 TrianglePredicate::isInCircleNonRobust(
-    const Coordinate& a, const Coordinate& b, const Coordinate& c,
-    const Coordinate& p)
+    const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c,
+    const CoordinateXY& p)
 {
-    bool isInCircle =
+    auto det =
         (a.x * a.x + a.y * a.y) * triArea(b, c, p)
         - (b.x * b.x + b.y * b.y) * triArea(a, c, p)
         + (c.x * c.x + c.y * c.y) * triArea(a, b, p)
-        - (p.x * p.x + p.y * p.y) * triArea(a, b, c)
-        > 0;
-    return isInCircle;
+        - (p.x * p.x + p.y * p.y) * triArea(a, b, c);
+
+    return det > 0 ? geom::Location::EXTERIOR : (det < 0 ? geom::Location::INTERIOR : geom::Location::BOUNDARY);
 }
 
-bool
+geom::Location
 TrianglePredicate::isInCircleNormalized(
-    const Coordinate& a, const Coordinate& b, const Coordinate& c,
-    const Coordinate& p)
+    const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c,
+    const CoordinateXY& p)
 {
     // Unfortunately this implementation is not robust either. For robust one see:
     // https://www.cs.cmu.edu/~quake/robust.html
@@ -67,22 +68,31 @@ TrianglePredicate::isInCircleNormalized(
     long double adxbdy = adx * bdy;
     long double bdxady = bdx * ady;
     long double clift = cdx * cdx + cdy * cdy;
-    return (alift * bdxcdy + blift * cdxady + clift * adxbdy) >
-           (alift * cdxbdy + blift * adxcdy + clift * bdxady);
+
+    long double A = (alift * bdxcdy + blift * cdxady + clift * adxbdy);
+    long double B = (alift * cdxbdy + blift * adxcdy + clift * bdxady);
+
+    if (A < B) {
+        return geom::Location::EXTERIOR;
+    } else if (A == B) {
+        return geom::Location::BOUNDARY;
+    } else {
+        return geom::Location::INTERIOR;
+    }
 }
 
 double
-TrianglePredicate::triArea(const Coordinate& a,
-                           const Coordinate& b, const Coordinate& c)
+TrianglePredicate::triArea(const CoordinateXY& a,
+                           const CoordinateXY& b, const CoordinateXY& c)
 {
     return (b.x - a.x) * (c.y - a.y)
            - (b.y - a.y) * (c.x - a.x);
 }
 
-bool
+geom::Location
 TrianglePredicate::isInCircleRobust(
-    const Coordinate& a, const Coordinate& b, const Coordinate& c,
-    const Coordinate& p)
+    const CoordinateXY& a, const CoordinateXY& b, const CoordinateXY& c,
+    const CoordinateXY& p)
 {
     // This implementation is not robust, name is ported from JTS.
     return isInCircleNormalized(a, b, c, p);
diff --git a/Sources/geos/src/triangulate/tri/Tri.cpp b/Sources/geos/src/triangulate/tri/Tri.cpp
index 2bd0f9b..55dce92 100644
--- a/Sources/geos/src/triangulate/tri/Tri.cpp
+++ b/Sources/geos/src/triangulate/tri/Tri.cpp
@@ -15,7 +15,6 @@
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -23,6 +22,8 @@
 #include 
 #include 
 #include 
+#include 
+#include 
 
 
 using geos::util::IllegalArgumentException;
@@ -61,7 +62,7 @@ Tri::setTri(TriIndex edgeIndex, Tri* tri)
         case 1: tri1 = tri; return;
         case 2: tri2 = tri; return;
     }
-    assert(false); // never reach here
+    throw util::IllegalArgumentException("Tri::setTri - invalid index");
 }
 
 /* private */
@@ -283,13 +284,12 @@ Tri::hasCoordinate(const Coordinate& v) const
 const Coordinate&
 Tri::getCoordinate(TriIndex i) const
 {
-    if ( i == 0 ) {
-        return p0;
-    }
-    if ( i == 1 ) {
-        return p1;
+    switch(i) {
+    case 0: return p0;
+    case 1: return p1;
+    case 2: return p2;
     }
-    return p2;
+    throw util::IllegalArgumentException("Tri::getCoordinate - invalid index");
 }
 
 /* public */
@@ -327,8 +327,7 @@ Tri::getAdjacent(TriIndex i) const
     case 1: return tri1;
     case 2: return tri2;
     }
-    assert(false); // Never get here
-    return nullptr;
+    throw util::IllegalArgumentException("Tri::getAdjacent - invalid index");
 }
 
 /* public */
@@ -377,6 +376,9 @@ Tri::isInteriorVertex(TriIndex index) const
         const Tri* adj = curr->getAdjacent(currIndex);
         if (adj == nullptr) return false;
         TriIndex adjIndex = adj->getIndex(curr);
+        if (adjIndex < 0) {
+            throw util::IllegalStateException("Inconsistent adjacency - invalid triangulation");
+        }
         curr = adj;
         currIndex = Tri::next(adjIndex);
     }
@@ -473,11 +475,13 @@ Tri::getLength(TriIndex i) const
 std::unique_ptr
 Tri::toPolygon(const geom::GeometryFactory* gf) const
 {
-    std::vector coords(4);
-    coords[0] = p0; coords[1] = p1;
-    coords[2] = p2; coords[3] = p0;
+    auto coords = detail::make_unique(4u);
+    (*coords)[0] = p0;
+    (*coords)[1] = p1;
+    (*coords)[2] = p2;
+    (*coords)[3] = p0;
 
-    return gf->createPolygon(std::move(coords));
+    return gf->createPolygon(gf->createLinearRing(std::move(coords)));
 }
 
 /* public static */
@@ -508,4 +512,3 @@ operator<<(std::ostream& os, const Tri& tri)
 } // namespace geos.triangulate.tri
 } // namespace geos.triangulate
 } // namespace geos
-
diff --git a/Sources/geos/src/util/Assert.cpp b/Sources/geos/src/util/Assert.cpp
index 923a7e2..941766d 100644
--- a/Sources/geos/src/util/Assert.cpp
+++ b/Sources/geos/src/util/Assert.cpp
@@ -38,8 +38,8 @@ Assert::isTrue(bool assertion, const std::string& message)
 }
 
 void
-Assert::equals(const Coordinate& expectedValue,
-               const Coordinate& actualValue, const std::string& message)
+Assert::equals(const CoordinateXY& expectedValue,
+               const CoordinateXY& actualValue, const std::string& message)
 {
     if(!(actualValue == expectedValue)) {
         throw  AssertionFailedException("Expected " + expectedValue.toString() + " but encountered "
diff --git a/Sources/geos/src/util/GeometricShapeFactory.cpp b/Sources/geos/src/util/GeometricShapeFactory.cpp
index 3f04ee4..141ae59 100644
--- a/Sources/geos/src/util/GeometricShapeFactory.cpp
+++ b/Sources/geos/src/util/GeometricShapeFactory.cpp
@@ -17,11 +17,11 @@
  *
  **********************************************************************/
 
+#include 
 #include 
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 #include 
@@ -32,6 +32,7 @@
 #include 
 
 
+using namespace geos::algorithm;
 using namespace geos::geom;
 
 namespace geos {
@@ -46,13 +47,13 @@ GeometricShapeFactory::GeometricShapeFactory(const GeometryFactory* factory)
 }
 
 void
-GeometricShapeFactory::setBase(const Coordinate& base)
+GeometricShapeFactory::setBase(const CoordinateXY& base)
 {
     dim.setBase(base);
 }
 
 void
-GeometricShapeFactory::setCentre(const Coordinate& centre)
+GeometricShapeFactory::setCentre(const CoordinateXY& centre)
 {
     dim.setCentre(centre);
 }
@@ -94,31 +95,30 @@ GeometricShapeFactory::createRectangle()
     double XsegLen = env->getWidth() / nSide;
     double YsegLen = env->getHeight() / nSide;
 
-    std::vector vc(4 * nSide + 1);
+    auto vc = detail::make_unique(4 * nSide + 1);
 
     for(i = 0; i < nSide; i++) {
         double x = env->getMinX() + i * XsegLen;
         double y = env->getMinY();
-        vc[ipt++] = coord(x, y);
+        (*vc)[ipt++] = coord(x, y);
     }
     for(i = 0; i < nSide; i++) {
         double x = env->getMaxX();
         double y = env->getMinY() + i * YsegLen;
-        vc[ipt++] = coord(x, y);
+        (*vc)[ipt++] = coord(x, y);
     }
     for(i = 0; i < nSide; i++) {
         double x = env->getMaxX() - i * XsegLen;
         double y = env->getMaxY();
-        vc[ipt++] = coord(x, y);
+        (*vc)[ipt++] = coord(x, y);
     }
     for(i = 0; i < nSide; i++) {
         double x = env->getMinX();
         double y = env->getMaxY() - i * YsegLen;
-        vc[ipt++] = coord(x, y);
+        (*vc)[ipt++] = coord(x, y);
     }
-    vc[ipt++] = vc[0];
-    auto cs = geomFact->getCoordinateSequenceFactory()->create(std::move(vc));
-    auto ring = geomFact->createLinearRing(std::move(cs));
+    (*vc)[ipt++] = (*vc)[0];
+    auto ring = geomFact->createLinearRing(std::move(vc));
     auto poly = geomFact->createPolygon(std::move(ring));
     return poly;
 }
@@ -134,17 +134,17 @@ GeometricShapeFactory::createCircle()
     double centreY = env->getMinY() + yRadius;
     env.reset();
 
-    std::vector pts(nPts + 1);
+    auto pts = detail::make_unique(nPts + 1);
     uint32_t iPt = 0;
+    double sinang, cosang;
     for(uint32_t i = 0; i < nPts; i++) {
-        double ang = i * (2 * 3.14159265358979 / nPts);
-        double x = xRadius * cos(ang) + centreX;
-        double y = yRadius * sin(ang) + centreY;
-        pts[iPt++] = coord(x, y);
+        Angle::sinCosSnap(i * Angle::PI_TIMES_2 / nPts, sinang, cosang);
+        double x = xRadius * cosang + centreX;
+        double y = yRadius * sinang + centreY;
+        (*pts)[iPt++] = coord(x, y);
     }
-    pts[iPt++] = pts[0];
-    auto cs = geomFact->getCoordinateSequenceFactory()->create(std::move(pts));
-    auto ring = geomFact->createLinearRing(std::move(cs));
+    (*pts)[iPt++] = (*pts)[0];
+    auto ring = geomFact->createLinearRing(std::move(pts));
     auto poly = geomFact->createPolygon(std::move(ring));
     return poly;
 }
@@ -161,21 +161,21 @@ GeometricShapeFactory::createArc(double startAng, double angExtent)
     env.reset();
 
     double angSize = angExtent;
-    if(angSize <= 0.0 || angSize > 2 * MATH_PI) {
-        angSize = 2 * MATH_PI;
+    if(angSize <= 0.0 || angSize > Angle::PI_TIMES_2) {
+        angSize = Angle::PI_TIMES_2;
     }
     double angInc = angSize / (nPts - 1);
 
-    std::vector pts(nPts);
+    auto pts = detail::make_unique(nPts);
     uint32_t iPt = 0;
+    double sinang, cosang;
     for(uint32_t i = 0; i < nPts; i++) {
-        double ang = startAng + i * angInc;
-        double x = xRadius * cos(ang) + centreX;
-        double y = yRadius * sin(ang) + centreY;
-        pts[iPt++] = coord(x, y);
+        Angle::sinCosSnap(startAng + i * angInc, sinang, cosang);
+        double x = xRadius * cosang + centreX;
+        double y = yRadius * sinang + centreY;
+        (*pts)[iPt++] = coord(x, y);
     }
-    auto cs = geomFact->getCoordinateSequenceFactory()->create(std::move(pts));
-    auto line = geomFact->createLineString(std::move(cs));
+    auto line = geomFact->createLineString(std::move(pts));
     return line;
 }
 
@@ -191,43 +191,43 @@ GeometricShapeFactory::createArcPolygon(double startAng, double angExtent)
     env.reset();
 
     double angSize = angExtent;
-    if(angSize <= 0.0 || angSize > 2 * MATH_PI) {
-        angSize = 2 * MATH_PI;
+    if(angSize <= 0.0 || angSize > Angle::PI_TIMES_2) {
+        angSize = Angle::PI_TIMES_2;
     }
     double angInc = angSize / (nPts - 1);
 
-    std::vector pts(nPts + 2);
+    auto pts = detail::make_unique(nPts + 2);
     uint32_t iPt = 0;
-    pts[iPt++] = coord(centreX, centreY);
+    (*pts)[iPt++] = coord(centreX, centreY);
+    double sinang, cosang;
     for(uint32_t i = 0; i < nPts; i++) {
-        double ang = startAng + i * angInc;
-        double x = xRadius * cos(ang) + centreX;
-        double y = yRadius * sin(ang) + centreY;
-        pts[iPt++] = coord(x, y);
+        Angle::sinCosSnap(startAng + i * angInc, sinang, cosang);
+        double x = xRadius * cosang + centreX;
+        double y = yRadius * sinang + centreY;
+        (*pts)[iPt++] = coord(x, y);
     }
-    pts[iPt++] = coord(centreX, centreY);
+    (*pts)[iPt++] = coord(centreX, centreY);
 
-    auto cs = geomFact->getCoordinateSequenceFactory()->create(std::move(pts));
-    auto ring = geomFact->createLinearRing(std::move(cs));
+    auto ring = geomFact->createLinearRing(std::move(pts));
 
     return geomFact->createPolygon(std::move(ring));
 }
 
 GeometricShapeFactory::Dimensions::Dimensions()
     :
-    base(Coordinate::getNull()),
-    centre(Coordinate::getNull())
+    base(CoordinateXY::getNull()),
+    centre(CoordinateXY::getNull())
 {
 }
 
 void
-GeometricShapeFactory::Dimensions::setBase(const Coordinate& newBase)
+GeometricShapeFactory::Dimensions::setBase(const CoordinateXY& newBase)
 {
     base = newBase;
 }
 
 void
-GeometricShapeFactory::Dimensions::setCentre(const Coordinate& newCentre)
+GeometricShapeFactory::Dimensions::setCentre(const CoordinateXY& newCentre)
 {
     centre = newCentre;
 }
@@ -264,10 +264,10 @@ GeometricShapeFactory::Dimensions::getEnvelope() const
 }
 
 /*protected*/
-Coordinate
+CoordinateXY
 GeometricShapeFactory::coord(double x, double y) const
 {
-    Coordinate ret(x, y);
+    CoordinateXY ret(x, y);
     precModel->makePrecise(&ret);
     return ret;
 }
diff --git a/Sources/geos/src/util/math.cpp b/Sources/geos/src/util/math.cpp
index a12d40e..8a0f98e 100644
--- a/Sources/geos/src/util/math.cpp
+++ b/Sources/geos/src/util/math.cpp
@@ -12,6 +12,7 @@
  *
  **********************************************************************/
 
+#include 
 #include 
 #include 
 
@@ -21,7 +22,7 @@ namespace util { // geos.util
 /*
  * Symmetric Rounding Algorithm  - equivalent to C99 round()
  */
-double
+double GEOS_DLL
 sym_round(double val)
 {
     double n;
@@ -53,7 +54,7 @@ sym_round(double val)
 /*
  * Asymmetric Rounding Algorithm  - equivalent to Java Math.round()
  */
-double
+double GEOS_DLL
 java_math_round(double val)
 {
     double n;
@@ -116,7 +117,7 @@ rint_vc(double val)
 }
 
 
-double
+double GEOS_DLL
 clamp(double x, double min, double max)
 {
     if (x < min) return min;
diff --git a/Sources/geos/src/util/string.cpp b/Sources/geos/src/util/string.cpp
new file mode 100644
index 0000000..b61b147
--- /dev/null
+++ b/Sources/geos/src/util/string.cpp
@@ -0,0 +1,64 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (C) 2022 ISciences LLC
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************/
+
+#include 
+#include 
+
+namespace geos {
+namespace util {
+
+// https://stackoverflow.com/a/874160/2171894
+bool endsWith(const std::string & s, const std::string & suffix) {
+    if (s.length() < suffix.length()) {
+        return false;
+    }
+
+    return s.compare(s.length() - suffix.length(),
+                     suffix.length(),
+                     suffix) == 0;
+}
+
+bool endsWith(const std::string & s, char suffix) {
+    if (s.empty()) {
+        return false;
+    }
+
+    return s[s.length() - 1] == suffix;
+}
+
+bool startsWith(const std::string & s, const std::string & prefix) {
+    if (s.length() < prefix.length()) {
+        return false;
+    }
+
+    auto cmp = s.compare(0, prefix.length(), prefix);
+    return cmp == 0;
+}
+
+bool startsWith(const std::string & s, char prefix) {
+    if (s.empty() == 0) {
+        return false;
+    }
+
+    return s[0] == prefix;
+}
+
+void toUpper(std::string& s)
+{
+    std::transform(s.begin(), s.end(), s.begin(), ::toupper);
+}
+
+
+}
+}
diff --git a/geos.podspec b/geos.podspec
index b53878f..254a9af 100644
--- a/geos.podspec
+++ b/geos.podspec
@@ -1,6 +1,6 @@
 Pod::Spec.new do |s|
   s.name = 'geos'
-  s.version = '8.1.0'
+  s.version = '9.0.0'
   s.summary = 'GEOS (Geometry Engine - Open Source) is a C++ port of the Java Topology Suite (JTS).'
   s.homepage = 'http://trac.osgeo.org/geos'
   s.license = {
@@ -12,12 +12,10 @@ Pod::Spec.new do |s|
     git: 'https://github.com/GEOSwift/geos.git',
     tag: s.version,
   }
-  s.platforms = {
-    ios: '9.0',
-    osx: '10.9',
-    tvos: '9.0',
-    watchos: '2.0',
-  }
+  s.ios.deployment_target = '12.0'
+  s.osx.deployment_target = '10.13'
+  s.tvos.deployment_target = '12.0'
+  s.watchos.deployment_target = '4.0'
   s.preserve_paths = 'Sources/geos/**/*'
   s.source_files = 'Sources/geos/{src,capi,public}/**/*'
   s.public_header_files = 'Sources/geos/public/**/*'
diff --git a/update.sh b/update.sh
index b73f018..591e08b 100755
--- a/update.sh
+++ b/update.sh
@@ -6,7 +6,7 @@ rm -rf .update
 mkdir .update
 
 pushd .update
-curl https://download.osgeo.org/geos/geos-3.11.2.tar.bz2 | bunzip2 | tar --strip-components=1 -x
+curl https://download.osgeo.org/geos/geos-3.13.0.tar.bz2 | bunzip2 | tar --strip-components=1 -x
 cmake -DCMAKE_BUILD_TYPE=Release .
 popd