From 9d77ade5c82cbbc017eb9252c7d47c609ae06070 Mon Sep 17 00:00:00 2001 From: Daniel Baston Date: Wed, 21 Feb 2024 13:32:52 -0500 Subject: [PATCH] Add tests for curved geometry types --- include/geos/algorithm/ConvexHull.h | 6 +- include/geos/geom/CircularString.h | 5 + include/geos/geom/CompoundCurve.h | 20 +- include/geos/geom/Curve.h | 11 + include/geos/geom/CurvePolygon.h | 2 + include/geos/geom/Geometry.h | 5 + include/geos/geom/Polygon.h | 3 - include/geos/geom/SimpleCurve.h | 13 +- include/geos/geom/Surface.h | 5 +- include/geos/geom/SurfaceImpl.h | 11 +- include/geos/operation/union/UnaryUnionOp.h | 4 + include/geos/util.h | 23 ++ src/algorithm/Centroid.cpp | 4 + src/geom/CircularString.cpp | 1 + src/geom/CompoundCurve.cpp | 81 ++--- src/geom/Curve.cpp | 52 ++++ src/geom/CurvePolygon.cpp | 26 +- src/geom/Geometry.cpp | 19 ++ src/geom/GeometryFactory.cpp | 3 +- src/geom/Polygon.cpp | 17 -- src/geom/SimpleCurve.cpp | 52 +--- src/geom/Surface.cpp | 9 + src/geom/prep/BasicPreparedGeometry.cpp | 2 + src/geom/prep/PreparedGeometryFactory.cpp | 2 + src/operation/BoundaryOp.cpp | 2 + src/operation/distance/DistanceOp.cpp | 3 + src/operation/overlayng/OverlayNGRobust.cpp | 3 + src/operation/union/UnaryUnionOp.cpp | 2 + src/operation/valid/IsSimpleOp.cpp | 15 +- src/operation/valid/IsValidOp.cpp | 2 +- tests/unit/geom/CircularStringTest.cpp | 178 +++++++++++ tests/unit/geom/CompoundCurveTest.cpp | 318 ++++++++++++++++++++ tests/unit/geom/CurvePolygonTest.cpp | 178 +++++++++++ tests/unit/geom/MultiCurveTest.cpp | 199 ++++++++++++ tests/unit/geom/MultiSurfaceTest.cpp | 170 +++++++++++ tests/unit/io/WKTReaderTest.cpp | 2 - 36 files changed, 1294 insertions(+), 154 deletions(-) create mode 100644 src/geom/Curve.cpp create mode 100644 tests/unit/geom/CircularStringTest.cpp create mode 100644 tests/unit/geom/CompoundCurveTest.cpp create mode 100644 tests/unit/geom/CurvePolygonTest.cpp create mode 100644 tests/unit/geom/MultiCurveTest.cpp create mode 100644 tests/unit/geom/MultiSurfaceTest.cpp diff --git a/include/geos/algorithm/ConvexHull.h b/include/geos/algorithm/ConvexHull.h index 048d3aa715..df0f69ab60 100644 --- a/include/geos/algorithm/ConvexHull.h +++ b/include/geos/algorithm/ConvexHull.h @@ -33,6 +33,8 @@ #include #include +#include "geos/util.h" + #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class @@ -178,7 +180,9 @@ class GEOS_DLL ConvexHull { ConvexHull(const geom::Geometry* newGeometry) : inputGeom(newGeometry) , geomFactory(newGeometry->getFactory()) - {}; + { + util::ensureNotCurvedType(inputGeom); + }; ~ConvexHull() {}; diff --git a/include/geos/geom/CircularString.h b/include/geos/geom/CircularString.h index 3e7db7bbc7..5a87349546 100644 --- a/include/geos/geom/CircularString.h +++ b/include/geos/geom/CircularString.h @@ -15,6 +15,7 @@ #pragma once #include +#include namespace geos { namespace geom { @@ -47,6 +48,10 @@ class GEOS_DLL CircularString : public SimpleCurve { return SORTINDEX_LINESTRING; }; + double getLength() const override { + throw util::UnsupportedOperationException("Cannot calculate length of CircularString"); + } + }; diff --git a/include/geos/geom/CompoundCurve.h b/include/geos/geom/CompoundCurve.h index e3a49804ab..7228a144bf 100644 --- a/include/geos/geom/CompoundCurve.h +++ b/include/geos/geom/CompoundCurve.h @@ -15,6 +15,7 @@ #pragma once #include +#include #include namespace geos { @@ -48,10 +49,6 @@ class GEOS_DLL CompoundCurve : public Curve { const SimpleCurve* getCurveN(std::size_t) const; - std::size_t getNumGeometries() const override; - - const Geometry* getGeometryN(std::size_t) const override; - std::size_t getNumPoints() const override; std::unique_ptr getBoundary() const override; @@ -78,17 +75,14 @@ class GEOS_DLL CompoundCurve : public Curve { CompoundCurve* reverseImpl() const override; - void apply_rw(const CoordinateFilter* filter) override; - - void apply_ro(CoordinateFilter* filter) const override; - - void apply_rw(GeometryFilter* filter) override; + double getLength() const override; - void apply_ro(GeometryFilter* filter) const override; + using Curve::apply_ro; + using Curve::apply_rw; - void apply_rw(GeometryComponentFilter* filter) override; + void apply_rw(const CoordinateFilter* filter) override; - void apply_ro(GeometryComponentFilter* filter) const override; + void apply_ro(CoordinateFilter* filter) const override; void apply_rw(CoordinateSequenceFilter& filter) override; @@ -113,7 +107,7 @@ class GEOS_DLL CompoundCurve : public Curve { envelope = computeEnvelopeInternal(); } - Envelope computeEnvelopeInternal(); + Envelope computeEnvelopeInternal() const; private: std::vector> curves; diff --git a/include/geos/geom/Curve.h b/include/geos/geom/Curve.h index 10eea9580c..520a4c299f 100644 --- a/include/geos/geom/Curve.h +++ b/include/geos/geom/Curve.h @@ -36,6 +36,17 @@ class GEOS_DLL Curve : public Geometry { virtual bool isClosed() const = 0; + using Geometry::apply_ro; + using Geometry::apply_rw; + + 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; + protected: Curve(const GeometryFactory& factory) : Geometry(&factory) {} diff --git a/include/geos/geom/CurvePolygon.h b/include/geos/geom/CurvePolygon.h index 548a092eb6..9300df97ff 100644 --- a/include/geos/geom/CurvePolygon.h +++ b/include/geos/geom/CurvePolygon.h @@ -41,6 +41,8 @@ class GEOS_DLL CurvePolygon : public SurfaceImpl { void normalize() override; + double getArea() const override; + protected: using SurfaceImpl::SurfaceImpl; diff --git a/include/geos/geom/Geometry.h b/include/geos/geom/Geometry.h index cf6cdd5102..488332c1cd 100644 --- a/include/geos/geom/Geometry.h +++ b/include/geos/geom/Geometry.h @@ -309,6 +309,11 @@ class GEOS_DLL Geometry { /// Return a string representation of this Geometry type virtual std::string getGeometryType() const = 0; //Abstract + /// Returns whether the Geometry type _may_ contain curved elements + virtual bool isCurvedType() const; + + static bool isCurvedType(GeometryTypeId); + /// Return an integer representation of this Geometry type virtual GeometryTypeId getGeometryTypeId() const = 0; //Abstract diff --git a/include/geos/geom/Polygon.h b/include/geos/geom/Polygon.h index 95936b52f9..aabc4d8668 100644 --- a/include/geos/geom/Polygon.h +++ b/include/geos/geom/Polygon.h @@ -100,9 +100,6 @@ class GEOS_DLL Polygon: public SurfaceImpl { double getArea() const override; - /// Returns the perimeter of this Polygon - double getLength() const override; - bool isRectangle() const override; /** diff --git a/include/geos/geom/SimpleCurve.h b/include/geos/geom/SimpleCurve.h index 35e226ec82..240b54170f 100644 --- a/include/geos/geom/SimpleCurve.h +++ b/include/geos/geom/SimpleCurve.h @@ -85,7 +85,7 @@ class GEOS_DLL SimpleCurve : public Curve { bool isRing() const; - virtual bool isCoordinate(Coordinate& pt) const; + virtual bool isCoordinate(CoordinateXY& pt) const; bool equalsExact(const Geometry* other, double tolerance = 0) const override; @@ -99,18 +99,13 @@ class GEOS_DLL SimpleCurve : public Curve { return &envelope; } + using Curve::apply_ro; + using Curve::apply_rw; + 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; diff --git a/include/geos/geom/Surface.h b/include/geos/geom/Surface.h index f6d66c8ee2..46b4917489 100644 --- a/include/geos/geom/Surface.h +++ b/include/geos/geom/Surface.h @@ -45,7 +45,7 @@ class GEOS_DLL Surface : public Geometry { bool - equalsExact(const Geometry* other, double tolerance) const override; + equalsExact(const Geometry* other, double tolerance = 0.0) const override; bool equalsIdentical(const Geometry* other) const override; @@ -75,6 +75,9 @@ class GEOS_DLL Surface : public Geometry { bool isEmpty() const override; + /// Returns the perimeter of this Surface + double getLength() const override; + void apply_ro(CoordinateFilter* filter) const override; diff --git a/include/geos/geom/SurfaceImpl.h b/include/geos/geom/SurfaceImpl.h index 0fd917dbab..a69ecd0cd7 100644 --- a/include/geos/geom/SurfaceImpl.h +++ b/include/geos/geom/SurfaceImpl.h @@ -108,8 +108,8 @@ class SurfaceImpl : public Surface { /** * \brief - * Take ownership of this Polygon's exterior ring. - * After releasing the exterior ring, the Polygon should be + * 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 @@ -139,14 +139,13 @@ class SurfaceImpl : public Surface { /** * \brief - * Take ownership of this Polygon's interior rings. - * After releasing the rings, the Polygon should be + * 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() + std::vector> releaseInteriorRings() { return std::move(holes); } diff --git a/include/geos/operation/union/UnaryUnionOp.h b/include/geos/operation/union/UnaryUnionOp.h index 35354ba610..6c62127b98 100644 --- a/include/geos/operation/union/UnaryUnionOp.h +++ b/include/geos/operation/union/UnaryUnionOp.h @@ -29,6 +29,8 @@ #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 @@ -169,6 +171,8 @@ class GEOS_DLL UnaryUnionOp { void extract(const geom::Geometry& geom) { + util::ensureNotCurvedType(geom); + using namespace geom::util; if(! geomFact) { diff --git a/include/geos/util.h b/include/geos/util.h index 661dff4512..bd2ff674db 100644 --- a/include/geos/util.h +++ b/include/geos/util.h @@ -24,6 +24,7 @@ #include #include #include +#include // // Private macros definition @@ -59,6 +60,28 @@ template inline To down_cast(From* f) } } // namespace detail + +namespace util { + +template +void ensureNotCurvedType(const T* geom) +{ + if (geom->isCurvedType()) { + throw UnsupportedOperationException("Curved geometry types are not supported."); + } +} + +template +void ensureNotCurvedType(const T& geom) +{ + if (geom.isCurvedType()) { + throw UnsupportedOperationException("Curved geometry types are not supported."); + } +} + +} + + } // namespace geos #endif // GEOS_UTIL_H diff --git a/src/algorithm/Centroid.cpp b/src/algorithm/Centroid.cpp index fa9355421f..c99588da8f 100644 --- a/src/algorithm/Centroid.cpp +++ b/src/algorithm/Centroid.cpp @@ -28,6 +28,8 @@ #include // for std::abs +#include "geos/util.h" + using namespace geos::geom; namespace geos { @@ -68,6 +70,8 @@ Centroid::getCentroid(CoordinateXY& cent) const void Centroid::add(const Geometry& geom) { + util::ensureNotCurvedType(geom); + if(geom.isEmpty()) { return; } diff --git a/src/geom/CircularString.cpp b/src/geom/CircularString.cpp index fa39626231..a4376fcb36 100644 --- a/src/geom/CircularString.cpp +++ b/src/geom/CircularString.cpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace geos { namespace geom { diff --git a/src/geom/CompoundCurve.cpp b/src/geom/CompoundCurve.cpp index 40510dc7c1..a5aa225526 100644 --- a/src/geom/CompoundCurve.cpp +++ b/src/geom/CompoundCurve.cpp @@ -12,6 +12,7 @@ * **********************************************************************/ +#include #include #include #include @@ -23,7 +24,8 @@ namespace geom { CompoundCurve::CompoundCurve(std::vector>&& p_curves, const GeometryFactory& gf) : Curve(gf), - curves(std::move(p_curves)) {} + curves(std::move(p_curves)), + envelope(computeEnvelopeInternal()) {} CompoundCurve::CompoundCurve(const CompoundCurve& other) : Curve(other), @@ -130,24 +132,12 @@ CompoundCurve::getNumCurves() const return curves.size(); } -std::size_t -CompoundCurve::getNumGeometries() const -{ - return curves.size(); -} - const SimpleCurve* CompoundCurve::getCurveN(std::size_t i) const { return curves[i].get(); } -const Geometry* -CompoundCurve::getGeometryN(std::size_t i) const -{ - return curves[i].get(); -} - std::unique_ptr CompoundCurve::getBoundary() const { @@ -232,16 +222,23 @@ CompoundCurve* CompoundCurve::reverseImpl() const { std::vector> reversed(curves.size()); - std::transform(curves.rbegin(), curves.rend(), reversed.end(), [](const auto& curve) { + 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(); } +double CompoundCurve::getLength() const { + double sum = 0; + for (const auto& curve : curves) { + sum += curve->getLength(); + } + return sum; +} + Envelope -CompoundCurve::computeEnvelopeInternal() -{ +CompoundCurve::computeEnvelopeInternal() const { Envelope e; for (const auto& curve : curves) { e.expandToInclude(curve->getEnvelopeInternal()); @@ -259,55 +256,43 @@ CompoundCurve::compareToSameClass(const Geometry* g) const void CompoundCurve::normalize() { - throw std::runtime_error("Not implemented."); -} - -void -CompoundCurve::apply_ro(GeometryFilter*) const -{ - throw std::runtime_error("Not implemented."); + throw util::UnsupportedOperationException(); } void -CompoundCurve::apply_ro(GeometryComponentFilter*) const +CompoundCurve::apply_ro(CoordinateFilter* cf) const { - throw std::runtime_error("Not implemented."); -} - -void -CompoundCurve::apply_ro(CoordinateFilter*) const -{ - throw std::runtime_error("Not implemented."); -} - -void -CompoundCurve::apply_ro(CoordinateSequenceFilter&) const -{ - throw std::runtime_error("Not implemented."); -} - -void -CompoundCurve::apply_rw(GeometryFilter*) -{ - throw std::runtime_error("Not implemented."); + for (const auto& curve : curves) { + curve->apply_ro(cf); + } } void -CompoundCurve::apply_rw(GeometryComponentFilter*) +CompoundCurve::apply_ro(CoordinateSequenceFilter& csf) const { - throw std::runtime_error("Not implemented."); + 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*) +CompoundCurve::apply_rw(const CoordinateFilter* cf) { - throw std::runtime_error("Not implemented."); + for (auto& curve : curves) { + curve->apply_rw(cf); + } } void CompoundCurve::apply_rw(CoordinateSequenceFilter&) { - throw std::runtime_error("Not implemented."); + throw util::UnsupportedOperationException(); } diff --git a/src/geom/Curve.cpp b/src/geom/Curve.cpp new file mode 100644 index 0000000000..fd3013ec52 --- /dev/null +++ b/src/geom/Curve.cpp @@ -0,0 +1,52 @@ +/********************************************************************** + * + * 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_rw(GeometryFilter* filter) +{ + assert(filter); + filter->filter_rw(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_ro(GeometryComponentFilter* filter) const +{ + assert(filter); + filter->filter_ro(this); +} + +} +} \ No newline at end of file diff --git a/src/geom/CurvePolygon.cpp b/src/geom/CurvePolygon.cpp index bf542dd528..7afb1cb04e 100644 --- a/src/geom/CurvePolygon.cpp +++ b/src/geom/CurvePolygon.cpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace geos { namespace geom { @@ -24,13 +25,15 @@ namespace geom { { auto coordinates = shell->getCoordinates(); for (const auto& hole : holes) { - // FIXME remove unncessary copy - coordinates->add(*hole->getCoordinates()); + 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"; } @@ -41,22 +44,31 @@ namespace geom { std::unique_ptr CurvePolygon::getBoundary() const { - throw std::runtime_error("Not implemented."); + throw util::UnsupportedOperationException(); } void CurvePolygon::normalize() { - throw std::runtime_error("Not implemented."); + throw util::UnsupportedOperationException(); + } + + double CurvePolygon::getArea() const { + throw util::UnsupportedOperationException(); } Geometry* CurvePolygon::cloneImpl() const { - throw std::runtime_error("Not implemented."); + return new CurvePolygon(*this); } Geometry* CurvePolygon::reverseImpl() const { - throw std::runtime_error("Not implemented."); + 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/src/geom/Geometry.cpp b/src/geom/Geometry.cpp index eb15b01496..c6c6f766d0 100644 --- a/src/geom/Geometry.cpp +++ b/src/geom/Geometry.cpp @@ -796,6 +796,25 @@ Geometry::getPrecisionModel() const return _factory->getPrecisionModel(); } +bool +Geometry::isCurvedType(GeometryTypeId typ) { + switch(typ) { + case GEOS_CIRCULARSTRING: + case GEOS_COMPOUNDCURVE: + case GEOS_CURVEPOLYGON: + case GEOS_MULTICURVE: + case GEOS_MULTISURFACE: + return true; + default: + return false; + } +} + +bool +Geometry::isCurvedType() const { + return isCurvedType(getGeometryTypeId()); +} + } // namespace geos::geom } // namespace geos diff --git a/src/geom/GeometryFactory.cpp b/src/geom/GeometryFactory.cpp index a143da2995..4e97f85cd7 100644 --- a/src/geom/GeometryFactory.cpp +++ b/src/geom/GeometryFactory.cpp @@ -668,7 +668,8 @@ std::unique_ptr GeometryFactory::createCompoundCurve() const { - return std::unique_ptr(); + std::vector> curves; + return createCompoundCurve(std::move(curves)); } /*public*/ diff --git a/src/geom/Polygon.cpp b/src/geom/Polygon.cpp index 938a1786d0..3163e8543e 100644 --- a/src/geom/Polygon.cpp +++ b/src/geom/Polygon.cpp @@ -153,23 +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; -} - - GeometryTypeId Polygon::getGeometryTypeId() const { diff --git a/src/geom/SimpleCurve.cpp b/src/geom/SimpleCurve.cpp index 9fc75e1af0..471d48ff85 100644 --- a/src/geom/SimpleCurve.cpp +++ b/src/geom/SimpleCurve.cpp @@ -45,6 +45,16 @@ SimpleCurve::SimpleCurve(std::unique_ptr&& newCoords, { } +Envelope +SimpleCurve::computeEnvelopeInternal() const +{ + if(isEmpty()) { + return Envelope(); + } + + return points->getEnvelope(); +} + std::unique_ptr SimpleCurve::getCoordinates() const { @@ -169,7 +179,7 @@ SimpleCurve::getBoundary() const } bool -SimpleCurve::isCoordinate(Coordinate& pt) const +SimpleCurve::isCoordinate(CoordinateXY& pt) const { assert(points.get()); std::size_t npts = points->getSize(); @@ -181,17 +191,6 @@ SimpleCurve::isCoordinate(Coordinate& pt) const return false; } -/*protected*/ -Envelope -SimpleCurve::computeEnvelopeInternal() const -{ - if(isEmpty()) { - return Envelope(); - } - - return points->getEnvelope(); -} - const CoordinateXY* SimpleCurve::getCoordinate() const { @@ -293,6 +292,8 @@ SimpleCurve::normalizeClosed() void SimpleCurve::normalize() { + util::ensureNotCurvedType(*this); + if (isEmpty()) return; assert(points.get()); if (isClosed()) { @@ -327,33 +328,6 @@ SimpleCurve::apply_ro(CoordinateFilter* filter) const points->apply_ro(filter); } -void -SimpleCurve::apply_rw(GeometryFilter* filter) -{ - assert(filter); - filter->filter_rw(this); -} - -void -SimpleCurve::apply_ro(GeometryFilter* filter) const -{ - assert(filter); - filter->filter_ro(this); -} - -void -SimpleCurve::apply_rw(GeometryComponentFilter* filter) -{ - assert(filter); - filter->filter_rw(this); -} - -void -SimpleCurve::apply_ro(GeometryComponentFilter* filter) const -{ - assert(filter); - filter->filter_ro(this); -} void SimpleCurve::apply_rw(CoordinateSequenceFilter& filter) diff --git a/src/geom/Surface.cpp b/src/geom/Surface.cpp index c3ec61346c..343dfb1e8f 100644 --- a/src/geom/Surface.cpp +++ b/src/geom/Surface.cpp @@ -266,6 +266,15 @@ Surface::isEmpty() const return getExteriorRing()->isEmpty(); } +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; +} + std::unique_ptr Surface::createEmptyRing(const GeometryFactory& factory) { diff --git a/src/geom/prep/BasicPreparedGeometry.cpp b/src/geom/prep/BasicPreparedGeometry.cpp index efde4ce2ad..3dc08f5698 100644 --- a/src/geom/prep/BasicPreparedGeometry.cpp +++ b/src/geom/prep/BasicPreparedGeometry.cpp @@ -23,6 +23,8 @@ #include #include +#include "geos/util.h" + namespace geos { namespace geom { // geos.geom namespace prep { // geos.geom.prep diff --git a/src/geom/prep/PreparedGeometryFactory.cpp b/src/geom/prep/PreparedGeometryFactory.cpp index 899eff9a32..565e9e21eb 100644 --- a/src/geom/prep/PreparedGeometryFactory.cpp +++ b/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::ensureNotCurvedType(g); + std::unique_ptr pg; switch(g->getGeometryTypeId()) { diff --git a/src/operation/BoundaryOp.cpp b/src/operation/BoundaryOp.cpp index 5048617841..acc4a316a3 100644 --- a/src/operation/BoundaryOp.cpp +++ b/src/operation/BoundaryOp.cpp @@ -52,6 +52,8 @@ BoundaryOp::BoundaryOp(const geom::Geometry& geom, const algorithm::BoundaryNode std::unique_ptr BoundaryOp::getBoundary() { + util::ensureNotCurvedType(m_geom); + if (auto ls = dynamic_cast(&m_geom)) { return boundaryLineString(*ls); } diff --git a/src/operation/distance/DistanceOp.cpp b/src/operation/distance/DistanceOp.cpp index 952c68d96c..5ca7f941ee 100644 --- a/src/operation/distance/DistanceOp.cpp +++ b/src/operation/distance/DistanceOp.cpp @@ -104,6 +104,9 @@ DistanceOp::distance() { using geos::util::IllegalArgumentException; + util::ensureNotCurvedType(geom[0]); + util::ensureNotCurvedType(geom[1]); + if(geom[0] == nullptr || geom[1] == nullptr) { throw IllegalArgumentException("null geometries are not supported"); } diff --git a/src/operation/overlayng/OverlayNGRobust.cpp b/src/operation/overlayng/OverlayNGRobust.cpp index 016eeb3df6..2c7604014a 100644 --- a/src/operation/overlayng/OverlayNGRobust.cpp +++ b/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::ensureNotCurvedType(geom0); + geos::util::ensureNotCurvedType(geom1); + std::unique_ptr result; std::runtime_error exOriginal(""); diff --git a/src/operation/union/UnaryUnionOp.cpp b/src/operation/union/UnaryUnionOp.cpp index 8bd96b8479..a801e56582 100644 --- a/src/operation/union/UnaryUnionOp.cpp +++ b/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/src/operation/valid/IsSimpleOp.cpp b/src/operation/valid/IsSimpleOp.cpp index 61e966b4c7..065cd18d84 100644 --- a/src/operation/valid/IsSimpleOp.cpp +++ b/src/operation/valid/IsSimpleOp.cpp @@ -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 */ diff --git a/src/operation/valid/IsValidOp.cpp b/src/operation/valid/IsValidOp.cpp index 813ab84543..454d9a18cf 100644 --- a/src/operation/valid/IsValidOp.cpp +++ b/src/operation/valid/IsValidOp.cpp @@ -107,7 +107,7 @@ IsValidOp::isValidGeometry(const Geometry* g) case GEOS_CURVEPOLYGON: case GEOS_MULTICURVE: case GEOS_MULTISURFACE: - throw util::IllegalArgumentException("Curved types not supported in IsValidOp."); + throw util::UnsupportedOperationException("Curved types not supported in IsValidOp."); } // geometry type not known diff --git a/tests/unit/geom/CircularStringTest.cpp b/tests/unit/geom/CircularStringTest.cpp new file mode 100644 index 0000000000..6f5cf03ae3 --- /dev/null +++ b/tests/unit/geom/CircularStringTest.cpp @@ -0,0 +1,178 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +using geos::geom::CoordinateSequence; +using geos::geom::CircularString; +using XY = geos::geom::CoordinateXY; + +namespace tut { +// Common data used by tests +struct test_circularstring_data { + + geos::geom::GeometryFactory::Ptr factory_ = geos::geom::GeometryFactory::create(); + geos::io::WKTReader wktreader_; + + std::unique_ptr cs_; + + test_circularstring_data() + { + CoordinateSequence seq{ + XY(0, 0), + XY(1, 1), + XY(2, 0), + XY(3, -1), + XY(4, 0) + }; + + cs_ = factory_->createCircularString(seq); + } +}; + +typedef test_group group; +typedef group::object object; + +group test_circularstring_group("geos::geom::CircularString"); + +template<> +template<> +void object::test<1>() +{ + + auto cs = factory_->createCircularString(false, false); + + ensure(cs->isEmpty()); + ensure_equals(cs->getNumPoints(), 0u); + ensure(!cs->hasZ()); + ensure(!cs->hasM()); + ensure_equals(cs->getCoordinateDimension(), 2u); + + ensure(cs->getCoordinatesRO()->isEmpty()); + ensure(cs->getCoordinates()->isEmpty()); + ensure(cs->getCoordinate() == nullptr); + + ensure_equals(cs->getArea(), 0); + ensure_THROW(cs_->getLength(), geos::util::UnsupportedOperationException); +} + +// Basic Geometry API +template<> +template<> +void object::test<2>() +{ + // Geometry type functions + ensure_equals("getGeometryType", cs_->getGeometryType(), "CircularString"); + ensure_equals("getGeometryTypdId", cs_->getGeometryTypeId(), geos::geom::GEOS_CIRCULARSTRING); + ensure("isCollection", !cs_->isCollection()); + + // Geometry size functions + ensure("isEmpty", !cs_->isEmpty()); + ensure_equals("getArea", cs_->getArea(), 0); + ensure_THROW(cs_->getLength(), geos::util::UnsupportedOperationException); + ensure_equals("getNumGeometries", cs_->getNumGeometries(), 1u); + ensure_equals("getNumPoints", cs_->getNumPoints(), 5u); + ensure(!cs_->getEnvelopeInternal()->isNull()); + // FIXME calculate envelope correctly + + // Geometry dimension functions + ensure_equals("getDimension", cs_->getDimension(), geos::geom::Dimension::L); + ensure("isLineal", cs_->isLineal()); + ensure("isPuntal", !cs_->isPuntal()); + ensure("isPolygonal", !cs_->isPolygonal()); + ensure("hasDimension(L)", cs_->hasDimension(geos::geom::Dimension::L)); + ensure("hasDimension(P)", !cs_->hasDimension(geos::geom::Dimension::P)); + ensure("hasDimension(A)", !cs_->hasDimension(geos::geom::Dimension::A)); + ensure("isDimensionStrict", cs_->isDimensionStrict(geos::geom::Dimension::L)); + ensure("isMixedDimension", !cs_->isMixedDimension()); + ensure_equals("getBoundaryDimension", cs_->getBoundaryDimension(), geos::geom::Dimension::P); + + // Coordinate dimension functions + ensure("hasZ", !cs_->hasZ()); + ensure("hasM", !cs_->hasM()); + ensure_equals("getCoordinateDimension", cs_->getCoordinateDimension(), 2u); + + // Coordinate access functions + ensure("getCoordinates", cs_->getCoordinates()->getSize() == 5u); + ensure_equals("getCoordinate", *cs_->getCoordinate(), XY(0, 0)); +} + +// Operations +template<> +template<> +void object::test<3>() +{ + // Predicates + ensure_THROW(cs_->contains(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->coveredBy(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->covers(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->crosses(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->disjoint(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->equals(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->intersects(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->overlaps(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->relate(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->touches(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->within(cs_.get()), geos::util::UnsupportedOperationException); + + auto cs2 = cs_->clone(); + + ensure("equalsExact", cs_->equalsExact(cs2.get())); + ensure("equalsIdentical", cs_->equalsIdentical(cs2.get())); + + // Overlay + ensure_THROW(cs_->Union(), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->Union(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->difference(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->intersection(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->symDifference(cs_.get()), geos::util::UnsupportedOperationException); + + // Distance + ensure_THROW(cs_->distance(cs_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->isWithinDistance(cs_.get(), 1), geos::util::UnsupportedOperationException); + + // Valid / Simple + ensure_THROW(cs_->isSimple(), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->isValid(), geos::util::UnsupportedOperationException); + + // Operations + ensure_THROW(cs_->convexHull(), geos::util::UnsupportedOperationException); + ensure_THROW(cs_->buffer(1), geos::util::UnsupportedOperationException); + + ensure_THROW(cs_->getCentroid(), geos::util::UnsupportedOperationException); + + //auto expected_boundary = wktreader_.read("MULTIPOINT ((0 0), (1 1), (2 0), (3 -1), (4 0))"); + //ensure("getBoundary", cs_->getBoundary()->equalsIdentical(expected_boundary.get())); + ensure_THROW(cs_->getBoundary(), geos::util::UnsupportedOperationException); + + ensure("clone", cs_->equalsIdentical(cs_->clone().get())); + + ensure("reverse", cs_->reverse()->equalsIdentical(wktreader_.read("CIRCULARSTRING (4 0, 3 -1, 2 0, 1 1, 0 0)").get())); + + auto cs3 = cs_->reverse(); + ensure_THROW(cs3->normalize(), geos::util::UnsupportedOperationException); +} + +// SimpleCurve API +template<> +template<> +void object::test<4>() +{ + ensure("getCoordinateN", cs_->getCoordinateN(3).equals(XY(3, -1))); + ensure("getPointN", cs_->getPointN(1)->equalsIdentical(wktreader_.read("POINT (1 1)").get())); + + ensure("getStartPoint", cs_->getStartPoint()->equalsIdentical(wktreader_.read("POINT (0 0)").get())); + ensure("getEndPoint", cs_->getEndPoint()->equalsIdentical(wktreader_.read("POINT (4 0)").get())); + + ensure("getCoordinatesRO", cs_->getCoordinatesRO()->getSize() == 5u); + ensure("isClosed", !cs_->isClosed()); + XY pt(4, 0); + ensure("isCoordinate", cs_->isCoordinate(pt)); +} + +} diff --git a/tests/unit/geom/CompoundCurveTest.cpp b/tests/unit/geom/CompoundCurveTest.cpp new file mode 100644 index 0000000000..c394bf945c --- /dev/null +++ b/tests/unit/geom/CompoundCurveTest.cpp @@ -0,0 +1,318 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using geos::geom::CoordinateXY; +using geos::geom::CoordinateSequence; + +namespace tut { +// Common data used by tests +struct test_compoundcurve_data { + + geos::geom::GeometryFactory::Ptr factory_ = geos::geom::GeometryFactory::create(); + geos::io::WKTReader wktreader_; + + std::unique_ptr cc_; + + test_compoundcurve_data() + { + std::vector> curves; + + curves.emplace_back(factory_->createCircularString({ + CoordinateXY(0, 0), + CoordinateXY(1, 1), + CoordinateXY(2, 0) + })); + + curves.emplace_back(factory_->createLineString({ + CoordinateXY(2, 0), + CoordinateXY(2, 2) + })); + + cc_ = factory_->createCompoundCurve(std::move(curves)); + } + +}; + +typedef test_group group; +typedef group::object object; + +group test_compoundcurve_group("geos::geom::CompoundCurve"); + +template<> +template<> +void object::test<1>() +{ + auto cc = factory_->createCompoundCurve(); + + ensure("isEmpty", cc->isEmpty()); + ensure_equals("getNumPoints", cc->getNumPoints(), 0u); + ensure_equals("getNumCurves", cc->getNumCurves(), 0u); + ensure("hasZ", !cc->hasZ()); + ensure("hasM", !cc->hasM()); + ensure_equals("getCoordinateDimension", cc->getCoordinateDimension(), 2u); + + ensure("getCoordinates", cc->getCoordinates()->isEmpty()); + ensure("getCoordinate", cc->getCoordinate() == nullptr); + + ensure_equals("getArea", cc->getArea(), 0); + ensure_THROW(cc_->getLength(), geos::util::UnsupportedOperationException); +} + +// Basic Geometry API +template<> +template<> +void object::test<2>() +{ + // Geometry type functions + ensure_equals("getGeometryType", cc_->getGeometryType(), "CompoundCurve"); + ensure_equals("getGeometryTypdId", cc_->getGeometryTypeId(), geos::geom::GEOS_COMPOUNDCURVE); + ensure("isCollection", !cc_->isCollection()); + + // Geometry size functions + ensure("isEmpty", !cc_->isEmpty()); + ensure_equals("getArea", cc_->getArea(), 0); + ensure_THROW(cc_->getLength(), geos::util::UnsupportedOperationException); + ensure_equals("getNumGeometries", cc_->getNumGeometries(), 1u); + ensure_equals("getNumCurves", cc_->getNumCurves(), 2u); + ensure_equals("getNumPoints", cc_->getNumPoints(), 5u); // FIXME should this be 5 or 4? + ensure(!cc_->getEnvelopeInternal()->isNull()); + + // Geometry dimension functions + ensure_equals("getDimension", cc_->getDimension(), geos::geom::Dimension::L); + ensure("isLineal", cc_->isLineal()); + ensure("isPuntal", !cc_->isPuntal()); + ensure("isPolygonal", !cc_->isPolygonal()); + ensure("hasDimension(L)", cc_->hasDimension(geos::geom::Dimension::L)); + ensure("hasDimension(P)", !cc_->hasDimension(geos::geom::Dimension::P)); + ensure("hasDimension(A)", !cc_->hasDimension(geos::geom::Dimension::A)); + ensure("isDimensionStrict", cc_->isDimensionStrict(geos::geom::Dimension::L)); + ensure("isMixedDimension", !cc_->isMixedDimension()); + ensure_equals("getBoundaryDimension", cc_->getBoundaryDimension(), geos::geom::Dimension::P); + + // Coordinate dimension functions + ensure("hasZ", !cc_->hasZ()); + ensure("hasM", !cc_->hasM()); + ensure_equals("getCoordinateDimension", cc_->getCoordinateDimension(), 2u); + + // Coordinate access functions + ensure("getCoordinates", cc_->getCoordinates()->getSize() == 5u); + ensure_equals("getCoordinate", *cc_->getCoordinate(), CoordinateXY(0, 0)); +} + +// Operations +template<> +template<> +void object::test<3>() +{ + // Predicates + ensure_THROW(cc_->contains(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->coveredBy(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->covers(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->crosses(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->disjoint(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->equals(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->intersects(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->overlaps(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->relate(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->touches(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->within(cc_.get()), geos::util::UnsupportedOperationException); + + auto cc2 = cc_->clone(); + + ensure("equalsExact", cc_->equalsExact(cc2.get())); + ensure("equalsIdentical", cc_->equalsIdentical(cc2.get())); + + // Overlay + ensure_THROW(cc_->Union(), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->Union(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->difference(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->intersection(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->symDifference(cc_.get()), geos::util::UnsupportedOperationException); + + // Distance + ensure_THROW(cc_->distance(cc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->isWithinDistance(cc_.get(), 1), geos::util::UnsupportedOperationException); + + // Valid / Simple + ensure_THROW(cc_->isSimple(), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->isValid(), geos::util::UnsupportedOperationException); + + // Operations + ensure_THROW(cc_->convexHull(), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->buffer(1), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->getCentroid(), geos::util::UnsupportedOperationException); + ensure_THROW(cc_->getBoundary(), geos::util::UnsupportedOperationException); + + ensure("clone", cc_->equalsIdentical(cc_->clone().get())); + + ensure("reverse", cc_->reverse()->equalsIdentical(wktreader_.read("" + "COMPOUNDCURVE ((2 2, 2 0), CIRCULARSTRING (2 0, 1 1, 0 0))").get())); + auto cc3 = cc_->reverse(); + ensure_THROW(cc3->normalize(), geos::util::UnsupportedOperationException); +} + +// GeometryFilter +template<> +template<> +void object::test<4>() +{ + struct TestGeometryFilter : public geos::geom::GeometryFilter { + void filter_ro(const geos::geom::Geometry* g) override + { + calls++; + last_arg = g; + } + + std::size_t calls = 0; + const Geometry* last_arg = nullptr; + }; + + TestGeometryFilter tgf; + cc_->apply_ro(&tgf); + + ensure_equals(tgf.calls, 1u); + ensure_equals(tgf.last_arg, cc_.get()); +} + +// GeometryComponentFilter RO +template<> +template<> +void object::test<5>() +{ + struct TestGeometryComponentFilter : public geos::geom::GeometryComponentFilter { + void filter_ro(const geos::geom::Geometry* g) override + { + calls++; + last_arg = g; + } + + std::size_t calls = 0; + const Geometry* last_arg = nullptr; + }; + + TestGeometryComponentFilter tgf; + cc_->apply_ro(&tgf); + + ensure_equals(tgf.calls, 1u); + ensure_equals(tgf.last_arg, cc_.get()); +} + +// CoordinateFilter RO +template<> +template<> +void object::test<6>() +{ + struct TestCoordinateFilter : public geos::geom::CoordinateFilter { + void filter_ro(const geos::geom::Coordinate* x) override + { + coords.push_back(*x); + } + + bool isDone() const override + { + return coords.size() >= 4; + } + + std::vector coords; + }; + + TestCoordinateFilter tcf; + cc_->apply_ro(&tcf); + + ensure_equals(tcf.coords.size(), 4u); + ensure_equals(tcf.coords[0], CoordinateXY(0, 0)); + ensure_equals(tcf.coords[1], CoordinateXY(1, 1)); + ensure_equals(tcf.coords[2], CoordinateXY(2, 0)); + ensure_equals(tcf.coords[3], CoordinateXY(2, 0)); +} + +// CoordinateFilter RW +template<> +template<> +void object::test<7>() +{ + struct TestCoordinateFilter : public geos::geom::CoordinateFilter { + void filter_rw(geos::geom::Coordinate* c) const override + { + c->z = count; + count += 1.0; + } + + bool isDone() const override + { + return count >= 4; + } + + mutable double count = 0.0; + }; + + TestCoordinateFilter tcf; + cc_->apply_rw(&tcf); + + ensure_equals(tcf.count, 4.0); + auto newCoords = cc_->getCoordinates(); + + ensure_equals(newCoords->getOrdinate(0, CoordinateSequence::Z), 0.0); + ensure_equals(newCoords->getOrdinate(1, CoordinateSequence::Z), 1.0); + ensure_equals(newCoords->getOrdinate(2, CoordinateSequence::Z), 2.0); + ensure_equals(newCoords->getOrdinate(3, CoordinateSequence::Z), 3.0); + ensure_same(newCoords->getOrdinate(4, CoordinateSequence::Z), geos::DoubleNotANumber); +} + +// CoordinateSequenceFilter RO +template<> +template<> +void object::test<8>() +{ + struct TestCoordinateSequenceFilter : public geos::geom::CoordinateSequenceFilter { + void filter_ro(const CoordinateSequence& seq, std::size_t i) override + { + args.emplace_back(&seq, i); + } + + bool isDone() const override + { + return args.size() >= 4; + } + + bool isGeometryChanged() const override + { + return false; + } + + std::vector> args; + }; + + TestCoordinateSequenceFilter tcsf; + cc_->apply_ro(tcsf); + + ensure_equals(tcsf.args.size(), 4u); + + ensure_equals(tcsf.args[0].first, cc_->getCurveN(0)->getCoordinatesRO()); + ensure_equals(tcsf.args[0].second, 0u); + + ensure_equals(tcsf.args[1].first, cc_->getCurveN(0)->getCoordinatesRO()); + ensure_equals(tcsf.args[1].second, 1u); + + ensure_equals(tcsf.args[2].first, cc_->getCurveN(0)->getCoordinatesRO()); + ensure_equals(tcsf.args[2].second, 2u); + + ensure_equals(tcsf.args[3].first, cc_->getCurveN(1)->getCoordinatesRO()); + ensure_equals(tcsf.args[3].second, 0u); +} + + +} + + + diff --git a/tests/unit/geom/CurvePolygonTest.cpp b/tests/unit/geom/CurvePolygonTest.cpp new file mode 100644 index 0000000000..135f2dec35 --- /dev/null +++ b/tests/unit/geom/CurvePolygonTest.cpp @@ -0,0 +1,178 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +using geos::geom::CoordinateXY; + +namespace tut { + +// Common data used by tests +struct test_curvepolygon_data { + + geos::geom::GeometryFactory::Ptr factory_ = geos::geom::GeometryFactory::create(); + geos::io::WKTReader wktreader_; + + std::unique_ptr cp_; + + test_curvepolygon_data() { + std::vector> holes; + + std::vector> shell_sections; + shell_sections.emplace_back( + factory_->createCircularString({ + CoordinateXY(0, 0), + CoordinateXY(2, 0), + CoordinateXY(2, 1), + CoordinateXY(2, 3), + CoordinateXY(4, 3) + })); + shell_sections.emplace_back( + factory_->createLineString({ + CoordinateXY(4, 3), + CoordinateXY(4, 5), + CoordinateXY(1, 4), + CoordinateXY(0, 0) + })); + + auto shell = factory_->createCompoundCurve(std::move(shell_sections)); + + holes.emplace_back(factory_->createCircularString({ + CoordinateXY(1.7, 1), + CoordinateXY(1.4, 0.4), + CoordinateXY(1.6, 0.4), + CoordinateXY(1.6, 0.5), + CoordinateXY(1.7, 1) + })); + + cp_ = factory_->createCurvePolygon(std::move(shell), std::move(holes)); + } +}; + +typedef test_group group; +typedef group::object object; + +group test_curvepolygon_group("geos::geom::CurvePolygon"); + +template<> +template<> +void object::test<1>() +{ + auto cp = factory_->createCurvePolygon(false, false); + + ensure("isEmpty", cp->isEmpty()); + ensure_equals("getNumPoints", cp->getNumPoints(), 0u); + ensure("hasZ", !cp->hasZ()); + ensure("hasM", !cp->hasM()); + ensure_equals("getCoordinateDimension", cp->getCoordinateDimension(), 2u); + + ensure("getCoordinates", cp->getCoordinates()->isEmpty()); + ensure("getCoordinate", cp->getCoordinate() == nullptr); + + ensure_THROW(cp->getArea(), geos::util::UnsupportedOperationException); + ensure_equals("getLength", cp->getLength(), 0.0); +} + +// Basic Geometry API +template<> +template<> +void object::test<2>() +{ + // Geometry type functions + ensure_equals("getGeometryType", cp_->getGeometryType(), "CurvePolygon"); + ensure_equals("getGeometryTypdId", cp_->getGeometryTypeId(), geos::geom::GEOS_CURVEPOLYGON); + ensure("isCollection", !cp_->isCollection()); + + // Geometry size functions + ensure("isEmpty", !cp_->isEmpty()); + ensure_THROW(cp_->getArea(), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->getLength(), geos::util::UnsupportedOperationException); + ensure_equals("getNumGeometries", cp_->getNumGeometries(), 1u); + ensure_equals("getNumPoints", cp_->getNumPoints(), 14u); + ensure_equals("getNumInteriorRing", cp_->getNumInteriorRing(), 1u); + ensure(!cp_->getEnvelopeInternal()->isNull()); + + // Geometry dimension functions + ensure_equals("getDimension", cp_->getDimension(), geos::geom::Dimension::A); + ensure("isLineal", !cp_->isLineal()); + ensure("isPuntal", !cp_->isPuntal()); + ensure("isPolygonal", cp_->isPolygonal()); + ensure("hasDimension(L)", !cp_->hasDimension(geos::geom::Dimension::L)); + ensure("hasDimension(P)", !cp_->hasDimension(geos::geom::Dimension::P)); + ensure("hasDimension(A)", cp_->hasDimension(geos::geom::Dimension::A)); + ensure("isDimensionStrict", cp_->isDimensionStrict(geos::geom::Dimension::A)); + ensure("isMixedDimension", !cp_->isMixedDimension()); + ensure_equals("getBoundaryDimension", cp_->getBoundaryDimension(), geos::geom::Dimension::L); + + // Coordinate dimension functions + ensure("hasZ", !cp_->hasZ()); + ensure("hasM", !cp_->hasM()); + ensure_equals("getCoordinateDimension", cp_->getCoordinateDimension(), 2u); + + // Coordinate access functions + ensure("getCoordinates", cp_->getCoordinates()->getSize() == 14u); + ensure_equals("getCoordinate", *cp_->getCoordinate(), CoordinateXY(0, 0)); +} + +// Operations +template<> +template<> +void object::test<3>() +{ + // Predicates + ensure_THROW(cp_->contains(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->coveredBy(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->covers(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->crosses(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->disjoint(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->equals(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->intersects(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->overlaps(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->relate(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->touches(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->within(cp_.get()), geos::util::UnsupportedOperationException); + + auto cp2 = cp_->clone(); + + ensure("equalsExact", cp_->equalsExact(cp2.get())); + ensure("equalsIdentical", cp_->equalsIdentical(cp2.get())); + + // Overlay + ensure_THROW(cp_->Union(), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->Union(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->difference(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->intersection(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->symDifference(cp_.get()), geos::util::UnsupportedOperationException); + + // Distance + ensure_THROW(cp_->distance(cp_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->isWithinDistance(cp_.get(), 1), geos::util::UnsupportedOperationException); + + // Valid / Simple + ensure_THROW(cp_->isSimple(), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->isValid(), geos::util::UnsupportedOperationException); + + // Operations + ensure_THROW(cp_->convexHull(), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->buffer(1), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->getCentroid(), geos::util::UnsupportedOperationException); + ensure_THROW(cp_->getBoundary(), geos::util::UnsupportedOperationException); + + ensure("clone", cp_->equalsIdentical(cp_->clone().get())); + + // each element is reversed but the order of the elements remains the same + // this behavior is the same as for MultiLineString + ensure("reverse", cp_->reverse()->equalsIdentical(wktreader_.read( + "CURVEPOLYGON (" + "COMPOUNDCURVE ((0 0, 1 4, 4 5, 4 3), CIRCULARSTRING (4 3, 2 3, 2 1, 2 0, 0 0)), " + "CIRCULARSTRING (1.7 1, 1.6 0.5, 1.6 0.4, 1.4 0.4, 1.7 1))").get())); + auto cc3 = cp_->reverse(); + ensure_THROW(cc3->normalize(), geos::util::UnsupportedOperationException); +} + +} diff --git a/tests/unit/geom/MultiCurveTest.cpp b/tests/unit/geom/MultiCurveTest.cpp new file mode 100644 index 0000000000..1fec0ebc23 --- /dev/null +++ b/tests/unit/geom/MultiCurveTest.cpp @@ -0,0 +1,199 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +using geos::geom::CoordinateXY; + +namespace tut { + +// Common data used by tests +struct test_multicurve_data { + + geos::geom::GeometryFactory::Ptr factory_ = geos::geom::GeometryFactory::create(); + geos::io::WKTReader wktreader_; + + std::unique_ptr mc_; + + test_multicurve_data() { + std::vector> curves; + + // Add a CompoundCurve + std::vector> cc_sections; + cc_sections.emplace_back( + factory_->createCircularString({ + CoordinateXY(0, 0), + CoordinateXY(2, 0), + CoordinateXY(2, 1), + CoordinateXY(2, 3), + CoordinateXY(4, 3) + })); + cc_sections.emplace_back( + factory_->createLineString({ + CoordinateXY(4, 3), + CoordinateXY(4, 5), + CoordinateXY(1, 4), + CoordinateXY(0, 0) + })); + + curves.emplace_back(factory_->createCompoundCurve(std::move(cc_sections))); + + // Add a LineString + curves.emplace_back(factory_->createLineString({CoordinateXY(8, 9), CoordinateXY(10, 11)})); + + // Add a CircularString + curves.emplace_back(factory_->createCircularString({ + CoordinateXY(1.7, 1), + CoordinateXY(1.4, 0.4), + CoordinateXY(1.6, 0.4), + CoordinateXY(1.6, 0.5), + CoordinateXY(1.7, 1) + })); + + mc_ = factory_->createMultiCurve(std::move(curves)); + } + +}; + +typedef test_group group; +typedef group::object object; + +group test_multicurve_group("geos::geom::MultiCurve"); + +template<> +template<> +void object::test<1>() +{ + auto mc = factory_->createMultiCurve(); + + ensure("isEmpty", mc->isEmpty()); + ensure_equals("getNumPoints", mc->getNumPoints(), 0u); + ensure("hasZ", !mc->hasZ()); + ensure("hasM", !mc->hasM()); + ensure_equals("getCoordinateDimension", mc->getCoordinateDimension(), 2u); + + ensure("getCoordinates", mc->getCoordinates()->isEmpty()); + ensure("getCoordinate", mc->getCoordinate() == nullptr); + + ensure_equals("getArea", mc->getArea(), 0); + ensure_THROW(mc_->getLength(), geos::util::UnsupportedOperationException); +} + +// Basic Geometry API +template<> +template<> +void object::test<2>() +{ + // Geometry type functions + ensure_equals("getGeometryType", mc_->getGeometryType(), "MultiCurve"); + ensure_equals("getGeometryTypdId", mc_->getGeometryTypeId(), geos::geom::GEOS_MULTICURVE); + ensure("isCollection", !mc_->isCollection()); + + // Geometry size functions + ensure("isEmpty", !mc_->isEmpty()); + ensure_equals("getArea", mc_->getArea(), 0); + ensure_THROW(mc_->getLength(), geos::util::UnsupportedOperationException); + ensure_equals("getNumGeometries", mc_->getNumGeometries(), 3u); + ensure_equals("getNumPoints", mc_->getNumPoints(), 16u); + ensure(!mc_->getEnvelopeInternal()->isNull()); + + // Geometry dimension functions + ensure_equals("getDimension", mc_->getDimension(), geos::geom::Dimension::L); + ensure("isLineal", mc_->isLineal()); + ensure("isPuntal", !mc_->isPuntal()); + ensure("isPolygonal", !mc_->isPolygonal()); + ensure("hasDimension(L)", mc_->hasDimension(geos::geom::Dimension::L)); + ensure("hasDimension(P)", !mc_->hasDimension(geos::geom::Dimension::P)); + ensure("hasDimension(A)", !mc_->hasDimension(geos::geom::Dimension::A)); + ensure("isDimensionStrict", mc_->isDimensionStrict(geos::geom::Dimension::L)); + ensure("isMixedDimension", !mc_->isMixedDimension()); + ensure_equals("getBoundaryDimension", mc_->getBoundaryDimension(), geos::geom::Dimension::P); + + // Coordinate dimension functions + ensure("hasZ", !mc_->hasZ()); + ensure("hasM", !mc_->hasM()); + ensure_equals("getCoordinateDimension", mc_->getCoordinateDimension(), 2u); + + // Coordinate access functions + ensure("getCoordinates", mc_->getCoordinates()->getSize() == 16u); + ensure_equals("getCoordinate", *mc_->getCoordinate(), CoordinateXY(0, 0)); +} + +// Operations +template<> +template<> +void object::test<3>() +{ + // Predicates + ensure_THROW(mc_->contains(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->coveredBy(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->covers(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->crosses(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->disjoint(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->equals(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->intersects(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->overlaps(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->relate(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->touches(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->within(mc_.get()), geos::util::UnsupportedOperationException); + + auto cc2 = mc_->clone(); + + ensure("equalsExact", mc_->equalsExact(cc2.get())); + ensure("equalsIdentical", mc_->equalsIdentical(cc2.get())); + + // Overlay + ensure_THROW(mc_->Union(), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->Union(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->difference(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->intersection(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->symDifference(mc_.get()), geos::util::UnsupportedOperationException); + + // Distance + ensure_THROW(mc_->distance(mc_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->isWithinDistance(mc_.get(), 1), geos::util::UnsupportedOperationException); + + // Valid / Simple + ensure_THROW(mc_->isSimple(), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->isValid(), geos::util::UnsupportedOperationException); + + // Operations + ensure_THROW(mc_->convexHull(), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->buffer(1), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->getCentroid(), geos::util::UnsupportedOperationException); + ensure_THROW(mc_->getBoundary(), geos::util::UnsupportedOperationException); + + ensure("clone", mc_->equalsIdentical(mc_->clone().get())); + + // each element is reversed but the order of the elements remains the same + // this behavior is the same as for MultiLineString + ensure("reverse", mc_->reverse()->equalsIdentical(wktreader_.read("" + "MULTICURVE (" + " COMPOUNDCURVE ((0 0, 1 4, 4 5, 4 3), CIRCULARSTRING (4 3, 2 3, 2 1, 2 0, 0 0)), " + " (10 11, 8 9)," + " CIRCULARSTRING (1.7 1, 1.6 0.5, 1.6 0.4, 1.4 0.4, 1.7 1))").get())); + auto cc3 = mc_->reverse(); + ensure_THROW(cc3->normalize(), geos::util::UnsupportedOperationException); +} + +// isClosed +template<> +template<> +void object::test<4>() +{ + // union of elements is closed, but individual elements are not => MultiCurve is not closed + ensure(!wktreader_.read("MULTICURVE ((0 0, 1 0), (1 0, 1 1, 0 0))")->isClosed()); + + // all elements are closed => MulitCurve is closed + ensure(wktreader_.read("MULTICURVE ((0 0, 1 0, 1 1, 0 0), CIRCULARSTRING (3 3, 5 5, 3 3))")->isClosed()); + + // some elements are closed => MultiCurve is not closed + ensure(!wktreader_.read("MULTICURVE ((0 0, 1 0, 1 1, 0 0), CIRCULARSTRING (3 3, 4 4, 5 3))")->isClosed()); +} + +} diff --git a/tests/unit/geom/MultiSurfaceTest.cpp b/tests/unit/geom/MultiSurfaceTest.cpp new file mode 100644 index 0000000000..08c51f9e5f --- /dev/null +++ b/tests/unit/geom/MultiSurfaceTest.cpp @@ -0,0 +1,170 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +using geos::geom::CoordinateXY; + +namespace tut { + +// Common data used by tests +struct test_multisurface_data { + + geos::geom::GeometryFactory::Ptr factory_ = geos::geom::GeometryFactory::create(); + geos::io::WKTReader wktreader_; + + std::unique_ptr ms_; + + test_multisurface_data() { + std::vector> surfaces; + + surfaces.emplace_back( + factory_->createPolygon( + factory_->createLinearRing({ + CoordinateXY(0, 0), + CoordinateXY(1, 0), + CoordinateXY(1, 1), + CoordinateXY(0, 1), + CoordinateXY(0, 0) + }))); + + surfaces.emplace_back( + factory_->createCurvePolygon( + factory_->createCircularString({ + CoordinateXY(10, 10), + CoordinateXY(11, 11), + CoordinateXY(12, 10), + CoordinateXY(11, 9), + CoordinateXY(10, 10) + }))); + + ms_ = factory_->createMultiSurface(std::move(surfaces)); + } + +}; + +typedef test_group group; +typedef group::object object; + +group test_multisurface_group("geos::geom::MultiSurface"); + +template<> +template<> +void object::test<1>() +{ + auto ms = factory_->createMultiSurface(); + + ensure("isEmpty", ms->isEmpty()); + ensure_equals("getNumPoints", ms->getNumPoints(), 0u); + ensure("hasZ", !ms->hasZ()); + ensure("hasM", !ms->hasM()); + ensure_equals("getCoordinateDimension", ms->getCoordinateDimension(), 2u); + + ensure("getCoordinates", ms->getCoordinates()->isEmpty()); + ensure("getCoordinate", ms->getCoordinate() == nullptr); + + ensure_equals("getArea", ms->getArea(), 0); + ensure_equals("getLength", ms->getLength(), 0.0); +} + +// Basic Geometry API +template<> +template<> +void object::test<2>() +{ + // Geometry type functions + ensure_equals("getGeometryType", ms_->getGeometryType(), "MultiSurface"); + ensure_equals("getGeometryTypdId", ms_->getGeometryTypeId(), geos::geom::GEOS_MULTISURFACE); + ensure("isCollection", !ms_->isCollection()); + + // Geometry size functions + ensure("isEmpty", !ms_->isEmpty()); + ensure_THROW(ms_->getArea(), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->getLength(), geos::util::UnsupportedOperationException); + ensure_equals("getNumGeometries", ms_->getNumGeometries(), 2u); + ensure_equals("getNumPoints", ms_->getNumPoints(), 10u); + ensure(!ms_->getEnvelopeInternal()->isNull()); + + // Geometry dimension functions + ensure_equals("getDimension", ms_->getDimension(), geos::geom::Dimension::A); + ensure("isLineal", !ms_->isLineal()); + ensure("isPuntal", !ms_->isPuntal()); + ensure("isPolygonal", ms_->isPolygonal()); + ensure("hasDimension(L)", !ms_->hasDimension(geos::geom::Dimension::L)); + ensure("hasDimension(P)", !ms_->hasDimension(geos::geom::Dimension::P)); + ensure("hasDimension(A)", ms_->hasDimension(geos::geom::Dimension::A)); + ensure("isDimensionStrict", ms_->isDimensionStrict(geos::geom::Dimension::A)); + ensure("isMixedDimension", !ms_->isMixedDimension()); + ensure_equals("getBoundaryDimension", ms_->getBoundaryDimension(), geos::geom::Dimension::L); + + // Coordinate dimension functions + ensure("hasZ", !ms_->hasZ()); + ensure("hasM", !ms_->hasM()); + ensure_equals("getCoordinateDimension", ms_->getCoordinateDimension(), 2u); + + // Coordinate access functions + ensure("getCoordinates", ms_->getCoordinates()->getSize() == 10u); + ensure_equals("getCoordinate", *ms_->getCoordinate(), CoordinateXY(0, 0)); +} + +// Operations +template<> +template<> +void object::test<3>() +{ + // Predicates + ensure_THROW(ms_->contains(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->coveredBy(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->covers(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->crosses(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->disjoint(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->equals(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->intersects(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->overlaps(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->relate(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->touches(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->within(ms_.get()), geos::util::UnsupportedOperationException); + + auto cp2 = ms_->clone(); + + ensure("equalsExact", ms_->equalsExact(cp2.get())); + ensure("equalsIdentical", ms_->equalsIdentical(cp2.get())); + + // Overlay + ensure_THROW(ms_->Union(), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->Union(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->difference(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->intersection(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->symDifference(ms_.get()), geos::util::UnsupportedOperationException); + + // Distance + ensure_THROW(ms_->distance(ms_.get()), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->isWithinDistance(ms_.get(), 1), geos::util::UnsupportedOperationException); + + // Valid / Simple + ensure_THROW(ms_->isSimple(), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->isValid(), geos::util::UnsupportedOperationException); + + // Operations + ensure_THROW(ms_->convexHull(), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->buffer(1), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->getCentroid(), geos::util::UnsupportedOperationException); + ensure_THROW(ms_->getBoundary(), geos::util::UnsupportedOperationException); + + ensure("clone", ms_->equalsIdentical(ms_->clone().get())); + + // each element is reversed but the order of the elements remains the same + // this behavior is the same as for MultiLineString + ensure("reverse", ms_->reverse()->equalsIdentical(wktreader_.read( + "MULTISURFACE (((0 0, 0 1, 1 1, 1 0, 0 0)), " + "CURVEPOLYGON (CIRCULARSTRING (10 10, 11 9, 12 10, 11 11, 10 10)))").get())); + auto cc3 = ms_->reverse(); + ensure_THROW(cc3->normalize(), geos::util::UnsupportedOperationException); +} + +} diff --git a/tests/unit/io/WKTReaderTest.cpp b/tests/unit/io/WKTReaderTest.cpp index 7057582e4d..8873141f9e 100644 --- a/tests/unit/io/WKTReaderTest.cpp +++ b/tests/unit/io/WKTReaderTest.cpp @@ -496,8 +496,6 @@ void object::test<26> ensure_equals(geom->getGeometryTypeId(), geos::geom::GEOS_COMPOUNDCURVE); ensure_equals(geom->getNumPoints(), 5u); - ensure_equals(geom->getNumGeometries(), 2u); - // explicit form auto geom2 = wktreader.read("COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 1 0), LINESTRING (1 0, 0 1))"); ensure(geom->equalsIdentical(geom2.get()));