diff --git a/NEWS.md b/NEWS.md index b378d9fca8..00a78583f9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,8 @@ - New things: - Add Angle::sinCosSnap to avoid small errors, e.g. with buffer operations (GH-978, Mike Taves) + - Add classes for curved geometry types: CircularString, CompoundCurve, CurvedPolygon, MultiCurve, + MultiSurface (GH-1046, Dan Baston) - Breaking Changes diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in index d5dd9c4257..246c54eee2 100644 --- a/capi/geos_c.h.in +++ b/capi/geos_c.h.in @@ -210,7 +210,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, }; /** diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp index 7109e0b722..6efc5f2f56 100644 --- a/capi/geos_ts_c.cpp +++ b/capi/geos_ts_c.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -41,12 +42,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -162,17 +165,21 @@ 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::SimpleCurve; +using geos::geom::Surface; using geos::io::WKTReader; using geos::io::WKTWriter; @@ -1048,8 +1055,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(); } @@ -1692,6 +1698,8 @@ extern "C" { 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."); } } @@ -1710,9 +1718,9 @@ extern "C" { 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()); }); @@ -1802,7 +1810,7 @@ extern "C" { GEOSisClosed_r(GEOSContextHandle_t extHandle, const Geometry* g1) { return execute(extHandle, 2, [&]() { - const LineString* ls = dynamic_cast(g1); + const Curve* ls = dynamic_cast(g1); if(ls) { return ls->isClosed(); } @@ -1812,7 +1820,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"); }); } @@ -1840,9 +1853,9 @@ extern "C" { GEOSGeomGetNumPoints_r(GEOSContextHandle_t extHandle, const Geometry* g1) { 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()); }); @@ -1926,9 +1939,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(); }); @@ -1942,9 +1955,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."); @@ -2813,7 +2826,7 @@ extern "C" { GEOSGeom_getCoordSeq_r(GEOSContextHandle_t extHandle, const Geometry* g) { return execute(extHandle, [&]() { - const LineString* ls = dynamic_cast(g); + const SimpleCurve* ls = dynamic_cast(g); if(ls) { return ls->getCoordinatesRO(); } diff --git a/include/geos/algorithm/CircularArcs.h b/include/geos/algorithm/CircularArcs.h new file mode 100644 index 0000000000..22293e5482 --- /dev/null +++ b/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 "geos/geom/Envelope.h" + +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/include/geos/algorithm/ConvexHull.h b/include/geos/algorithm/ConvexHull.h index 048d3aa715..bed2e0f3a8 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::ensureNoCurvedComponents(inputGeom); + }; ~ConvexHull() {}; diff --git a/include/geos/algorithm/hull/ConcaveHull.h b/include/geos/algorithm/hull/ConcaveHull.h index 1420e1f0a1..6201e21b5c 100644 --- a/include/geos/algorithm/hull/ConcaveHull.h +++ b/include/geos/algorithm/hull/ConcaveHull.h @@ -96,15 +96,7 @@ class GEOS_DLL ConcaveHull { public: - ConcaveHull(const Geometry* geom) - : inputGeometry(geom) - , maxEdgeLengthRatio(-1.0) - , alpha(-1) - , isHolesAllowed(false) - , criteriaType(PARAM_EDGE_LENGTH) - , maxSizeInHull(0.0) - , geomFactory(geom->getFactory()) - {}; + ConcaveHull(const Geometry* geom); /** * Computes the approximate edge length of diff --git a/include/geos/geom/CircularString.h b/include/geos/geom/CircularString.h new file mode 100644 index 0000000000..283cb636cb --- /dev/null +++ b/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 +#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 + { + throw util::UnsupportedOperationException("Cannot calculate length of CircularString"); + } + + bool hasCurvedComponents() 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/include/geos/geom/CompoundCurve.h b/include/geos/geom/CompoundCurve.h new file mode 100644 index 0000000000..7d82b8aa7c --- /dev/null +++ b/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; + + 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; + + 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/include/geos/geom/Curve.h b/include/geos/geom/Curve.h new file mode 100644 index 0000000000..9de1a627ae --- /dev/null +++ b/include/geos/geom/Curve.h @@ -0,0 +1,65 @@ +/********************************************************************** + * + * 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 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; + +protected: + Curve(const GeometryFactory& factory) : Geometry(&factory) {} + +}; + +} +} diff --git a/include/geos/geom/CurvePolygon.h b/include/geos/geom/CurvePolygon.h new file mode 100644 index 0000000000..1a12b48373 --- /dev/null +++ b/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/include/geos/geom/Geometry.h b/include/geos/geom/Geometry.h index cfa989a9a9..342d0cf6b1 100644 --- a/include/geos/geom/Geometry.h +++ b/include/geos/geom/Geometry.h @@ -86,7 +86,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 +102,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, }; /** @@ -299,6 +309,9 @@ 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 @@ -919,11 +932,34 @@ class GEOS_DLL Geometry { 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++; + } + + if(i < a.size()) { + return 1; + } - int compare(std::vector a, std::vector b) const; + if(j < b.size()) { + return -1; + } - int compare(const std::vector> & a, const std::vector> & b) const; + return 0; + } bool equal(const CoordinateXY& a, const CoordinateXY& b, double tolerance) const; diff --git a/include/geos/geom/GeometryCollection.h b/include/geos/geom/GeometryCollection.h index 7e0428c1f5..ae9a441aaf 100644 --- a/include/geos/geom/GeometryCollection.h +++ b/include/geos/geom/GeometryCollection.h @@ -246,6 +246,8 @@ class GEOS_DLL GeometryCollection : public Geometry { int compareToSameClass(const Geometry* gc) const override; + bool hasCurvedComponents() const override; + }; } // namespace geos::geom diff --git a/include/geos/geom/GeometryFactory.h b/include/geos/geom/GeometryFactory.h index ce2be87ae3..5737cdd81c 100644 --- a/include/geos/geom/GeometryFactory.h +++ b/include/geos/geom/GeometryFactory.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,8 @@ namespace geos { namespace geom { class Coordinate; +class CircularString; +class CompoundCurve; class CoordinateSequence; class Envelope; class Geometry; @@ -43,9 +46,11 @@ class GeometryCollection; class LineString; class LinearRing; class MultiLineString; +class MultiCurve; class MultiPoint; class MultiPolygon; -class Polygon; +class MultiSurface; +class CurvePolygon; } } @@ -142,6 +147,7 @@ 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 std::unique_ptr createPoint(const Coordinate& coordinate) const; @@ -159,7 +165,7 @@ class GEOS_DLL GeometryFactory { 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 template @@ -187,6 +193,16 @@ class GEOS_DLL GeometryFactory { 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; @@ -201,8 +217,19 @@ class GEOS_DLL GeometryFactory { 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(std::size_t coordinateDimension = 2) const; + std::unique_ptr createLinearRing(bool hasZ, bool hasM) const; /// Construct a LinearRing taking ownership of given arguments std::unique_ptr createLinearRing( @@ -244,6 +271,7 @@ class GEOS_DLL GeometryFactory { /// 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 std::unique_ptr createPolygon(std::unique_ptr && shell) const; @@ -258,8 +286,19 @@ class GEOS_DLL GeometryFactory { 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; @@ -272,6 +311,26 @@ class GEOS_DLL GeometryFactory { std::unique_ptr createLineString( const CoordinateSequence& coordinates) const; + /// 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}. diff --git a/include/geos/geom/LineString.h b/include/geos/geom/LineString.h index 7e70ca5e51..faf021f205 100644 --- a/include/geos/geom/LineString.h +++ b/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 @@ -62,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: @@ -85,109 +86,10 @@ 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; - - bool hasM() const override; - - bool hasZ() 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; - - bool equalsIdentical(const Geometry* other) 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 CoordinateXY* getCoordinate() const override; - double getLength() const override; /** @@ -198,10 +100,6 @@ class GEOS_DLL LineString: public Geometry { */ std::unique_ptr reverse() const { return std::unique_ptr(reverseImpl()); } - const Envelope* getEnvelopeInternal() const override { - return &envelope; - } - protected: LineString(const LineString& ls); @@ -216,28 +114,20 @@ class GEOS_DLL LineString: public Geometry { LineString* reverseImpl() const override; - Envelope computeEnvelopeInternal() const; - - CoordinateSequence::Ptr points; - - mutable Envelope envelope; - int getSortIndex() const override { return SORTINDEX_LINESTRING; }; - void geometryChangedAction() override { - envelope = computeEnvelopeInternal(); + void geometryChangedAction() override + { + envelope = computeEnvelopeInternal(true); } private: void validateConstruction(); - void normalizeClosed(); - - }; struct GEOS_DLL LineStringLT { diff --git a/include/geos/geom/MultiCurve.h b/include/geos/geom/MultiCurve.h new file mode 100644 index 0000000000..5505f5d967 --- /dev/null +++ b/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/include/geos/geom/MultiLineString.h b/include/geos/geom/MultiLineString.h index 47f2abaf9c..2b6cc76fdf 100644 --- a/include/geos/geom/MultiLineString.h +++ b/include/geos/geom/MultiLineString.h @@ -138,6 +138,11 @@ class GEOS_DLL MultiLineString: public GeometryCollection { return SORTINDEX_MULTILINESTRING; }; + bool hasCurvedComponents() const override + { + return false; + } + }; diff --git a/include/geos/geom/MultiPoint.h b/include/geos/geom/MultiPoint.h index 84863681e2..0e1d564d13 100644 --- a/include/geos/geom/MultiPoint.h +++ b/include/geos/geom/MultiPoint.h @@ -134,6 +134,11 @@ class GEOS_DLL MultiPoint: public GeometryCollection { return SORTINDEX_MULTIPOINT; }; + bool hasCurvedComponents() const override + { + return false; + } + }; #ifdef _MSC_VER diff --git a/include/geos/geom/MultiPolygon.h b/include/geos/geom/MultiPolygon.h index 391b71240f..f4cca3a384 100644 --- a/include/geos/geom/MultiPolygon.h +++ b/include/geos/geom/MultiPolygon.h @@ -26,7 +26,6 @@ #include // for inheritance #include // for inheritance #include // for Dimension::DimensionType -#include #include @@ -35,6 +34,7 @@ namespace geos { namespace geom { // geos::geom class Coordinate; class MultiPoint; +class Polygon; } } @@ -140,6 +140,11 @@ class GEOS_DLL MultiPolygon: public GeometryCollection { return SORTINDEX_MULTIPOLYGON; }; + bool hasCurvedComponents() const override + { + return false; + } + }; #ifdef _MSC_VER diff --git a/include/geos/geom/MultiSurface.h b/include/geos/geom/MultiSurface.h new file mode 100644 index 0000000000..73871e4f0b --- /dev/null +++ b/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/include/geos/geom/Polygon.h b/include/geos/geom/Polygon.h index abd5f100f6..aabc4d8668 100644 --- a/include/geos/geom/Polygon.h +++ b/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 @@ -57,7 +58,7 @@ namespace geom { // geos::geom * Specification for SQL . * */ -class GEOS_DLL Polygon: public Geometry { +class GEOS_DLL Polygon: public SurfaceImpl { public: @@ -68,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). @@ -79,23 +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; - - bool hasM() const override; - - bool hasZ() const override; - - /// Returns 1 (Polygon boundary is a MultiLineString) - int getBoundaryDimension() const override; - /** \brief * Computes the boundary of this geometry * @@ -104,121 +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; - bool equalsIdentical(const Geometry* other) 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; - /** - * \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); - std::unique_ptr reverse() const { return std::unique_ptr(reverseImpl()); } - const CoordinateXY* getCoordinate() const override; - double getArea() const override; - /// Returns the perimeter of this Polygon - double getLength() const override; - bool isRectangle() const override; - const Envelope* getEnvelopeInternal() const override { - return shell->getEnvelopeInternal(); - } + /** + * \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); 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(std::unique_ptr && newShell, - std::vector> && newHoles, - const GeometryFactory& newFactory); - - Polygon(std::unique_ptr && newShell, - 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; - - void geometryChangedAction() override {} - int getSortIndex() const override { diff --git a/include/geos/geom/SimpleCurve.h b/include/geos/geom/SimpleCurve.h new file mode 100644 index 0000000000..2d7037a0db --- /dev/null +++ b/include/geos/geom/SimpleCurve.h @@ -0,0 +1,136 @@ +/********************************************************************** + * + * 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; + + /// \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 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; + + 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/include/geos/geom/Surface.h b/include/geos/geom/Surface.h new file mode 100644 index 0000000000..7b1bb6ff38 --- /dev/null +++ b/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/include/geos/geom/SurfaceImpl.h b/include/geos/geom/SurfaceImpl.h new file mode 100644 index 0000000000..60da401ef8 --- /dev/null +++ b/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/include/geos/geomgraph/DirectedEdgeStar.h b/include/geos/geomgraph/DirectedEdgeStar.h index 3e70ada971..15880ae73f 100644 --- a/include/geos/geomgraph/DirectedEdgeStar.h +++ b/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 diff --git a/include/geos/geomgraph/EdgeEndStar.h b/include/geos/geomgraph/EdgeEndStar.h index 2982703a86..40b20ad31a 100644 --- a/include/geos/geomgraph/EdgeEndStar.h +++ b/include/geos/geomgraph/EdgeEndStar.h @@ -27,6 +27,7 @@ #include // for p0,p1 #include +#include #include #include #include @@ -115,7 +116,7 @@ class GEOS_DLL EdgeEndStar { virtual 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); @@ -148,8 +149,8 @@ class GEOS_DLL EdgeEndStar { private: virtual geom::Location getLocation(uint32_t geomIndex, - const geom::Coordinate& p, - std::vector* geom); + const geom::Coordinate&p, + const std::vector>&geom); /** \brief * The location of the point for this star in diff --git a/include/geos/io/WKTReader.h b/include/geos/io/WKTReader.h index 93249bd712..7294d64f55 100644 --- a/include/geos/io/WKTReader.h +++ b/include/geos/io/WKTReader.h @@ -40,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; } } @@ -118,7 +123,8 @@ class GEOS_DLL WKTReader { 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, OrdinateSet& ordinateFlags) 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; @@ -127,6 +133,17 @@ class GEOS_DLL WKTReader { 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; diff --git a/include/geos/io/WKTWriter.h b/include/geos/io/WKTWriter.h index a37973a872..97007dc175 100644 --- a/include/geos/io/WKTWriter.h +++ b/include/geos/io/WKTWriter.h @@ -39,6 +39,8 @@ class Coordinate; class CoordinateXY; class CoordinateXYZM; class CoordinateSequence; +class Curve; +class CompoundCurve; class Geometry; class GeometryCollection; class Point; @@ -49,6 +51,8 @@ class MultiPoint; class MultiLineString; class MultiPolygon; class PrecisionModel; +class SimpleCurve; +class Surface; } namespace io { class Writer; @@ -208,23 +212,28 @@ class GEOS_DLL WKTWriter { int level, Writer& writer) const; + void appendTag( + const geom::Geometry& geometry, + OrdinateSet outputOrdinates, + Writer& writer) const; + void appendPointTaggedText( const geom::Point& point, OrdinateSet outputOrdinates, int level, Writer& writer) const; - void appendLineStringTaggedText( - const geom::LineString& lineString, + void appendSimpleCurveTaggedText( + const geom::SimpleCurve& lineString, OrdinateSet outputOrdinates, int level, Writer& writer) const; - void appendLinearRingTaggedText( - const geom::LinearRing& lineString, + void appendCompoundCurveTaggedText( + const geom::CompoundCurve& lineString, OrdinateSet outputOrdinates, int level, Writer& writer) const; - void appendPolygonTaggedText( - const geom::Polygon& polygon, + void appendSurfaceTaggedText( + const geom::Surface& polygon, OrdinateSet outputOrdinates, int level, Writer& writer) const; @@ -233,13 +242,13 @@ class GEOS_DLL WKTWriter { OrdinateSet outputOrdinates, int level, Writer& writer) const; - void appendMultiLineStringTaggedText( - const geom::MultiLineString& multiLineString, + void appendMultiCurveTaggedText( + const geom::GeometryCollection& multiCurve, OrdinateSet outputOrdinates, int level, Writer& writer) const; - void appendMultiPolygonTaggedText( - const geom::MultiPolygon& multiPolygon, + void appendMultiSurfaceTaggedText( + const geom::GeometryCollection& multiSurface, OrdinateSet outputOrdinates, int level, Writer& writer) const; @@ -263,13 +272,18 @@ class GEOS_DLL WKTWriter { std::string writeNumber(double d) const; - void appendLineStringText( - const geom::LineString& lineString, + void appendCurveText( + const geom::Curve& lineString, OrdinateSet outputOrdinates, int level, bool doIndent, Writer& writer) const; - void appendPolygonText( - const geom::Polygon& polygon, + 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; @@ -278,13 +292,13 @@ class GEOS_DLL WKTWriter { OrdinateSet outputOrdinates, int level, Writer& writer) const; - void appendMultiLineStringText( - const geom::MultiLineString& multiLineString, + void appendMultiCurveText( + const geom::GeometryCollection& multiCurve, OrdinateSet outputOrdinates, int level, bool indentFirst, Writer& writer) const; - void appendMultiPolygonText( - const geom::MultiPolygon& multiPolygon, + void appendMultiSurfaceText( + const geom::GeometryCollection& multiSurface, OrdinateSet outputOrdinates, int level, Writer& writer) const; @@ -299,8 +313,6 @@ class GEOS_DLL WKTWriter { INDENT = 2 }; -// static const int INDENT = 2; - bool isFormatted; int roundingPrecision; @@ -309,8 +321,6 @@ class GEOS_DLL WKTWriter { bool removeEmptyDimensions = false; - int level; - static constexpr int coordsPerLine = 10; uint8_t defaultOutputDimension; diff --git a/include/geos/operation/GeometryGraphOperation.h b/include/geos/operation/GeometryGraphOperation.h index a088bccdff..0886e65b33 100644 --- a/include/geos/operation/GeometryGraphOperation.h +++ b/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/include/geos/operation/polygonize/Polygonizer.h b/include/geos/operation/polygonize/Polygonizer.h index 50fce1f7c8..1c21a3a343 100644 --- a/include/geos/operation/polygonize/Polygonizer.h +++ b/include/geos/operation/polygonize/Polygonizer.h @@ -192,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/include/geos/operation/relate/RelateComputer.h b/include/geos/operation/relate/RelateComputer.h index a232534d2e..6a9d9288d9 100644 --- a/include/geos/operation/relate/RelateComputer.h +++ b/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; diff --git a/include/geos/operation/union/UnaryUnionOp.h b/include/geos/operation/union/UnaryUnionOp.h index 35354ba610..3fed7e32bc 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::ensureNoCurvedComponents(geom); + using namespace geom::util; if(! geomFact) { diff --git a/include/geos/triangulate/polygon/ConstrainedDelaunayTriangulator.h b/include/geos/triangulate/polygon/ConstrainedDelaunayTriangulator.h index c8cc8ac338..23c385d21e 100644 --- a/include/geos/triangulate/polygon/ConstrainedDelaunayTriangulator.h +++ b/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/include/geos/util.h b/include/geos/util.h index 661dff4512..fe0825b851 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,27 @@ template inline To down_cast(From* f) } } // 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/include/geos/util/string.h b/include/geos/util/string.h index 32375e0cae..374dfa03b9 100644 --- a/include/geos/util/string.h +++ b/include/geos/util/string.h @@ -25,5 +25,7 @@ bool endsWith(const std::string & s, char suffix); bool startsWith(const std::string & s, const std::string & prefix); bool startsWith(const std::string & s, char prefix); +void toUpper(std::string& s); + +} } -} \ No newline at end of file diff --git a/src/algorithm/Centroid.cpp b/src/algorithm/Centroid.cpp index 168a0996e3..727bb282a1 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::ensureNoCurvedComponents(geom); + if(geom.isEmpty()) { return; } diff --git a/src/algorithm/CircularArcs.cpp b/src/algorithm/CircularArcs.cpp new file mode 100644 index 0000000000..abfd73f42c --- /dev/null +++ b/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 "geos/algorithm/Angle.h" +#include "geos/geom/Envelope.h" +#include "geos/geom/Quadrant.h" + +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/src/algorithm/distance/DiscreteFrechetDistance.cpp b/src/algorithm/distance/DiscreteFrechetDistance.cpp index a54dd81462..99a650f193 100644 --- a/src/algorithm/distance/DiscreteFrechetDistance.cpp +++ b/src/algorithm/distance/DiscreteFrechetDistance.cpp @@ -28,6 +28,8 @@ #include #include #include + +#include "geos/util.h" using namespace geos::geom; namespace geos { @@ -142,6 +144,9 @@ DiscreteFrechetDistance::compute( 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; diff --git a/src/algorithm/distance/DiscreteHausdorffDistance.cpp b/src/algorithm/distance/DiscreteHausdorffDistance.cpp index cffc7b6a71..3712b0a40b 100644 --- a/src/algorithm/distance/DiscreteHausdorffDistance.cpp +++ b/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/src/algorithm/hull/ConcaveHull.cpp b/src/algorithm/hull/ConcaveHull.cpp index bbee04badd..d70a80d46a 100644 --- a/src/algorithm/hull/ConcaveHull.cpp +++ b/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 diff --git a/src/algorithm/hull/ConcaveHullOfPolygons.cpp b/src/algorithm/hull/ConcaveHullOfPolygons.cpp index a774cf6f0f..d0ab8e4265 100644 --- a/src/algorithm/hull/ConcaveHullOfPolygons.cpp +++ b/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"); } diff --git a/src/coverage/CoverageRingEdges.cpp b/src/coverage/CoverageRingEdges.cpp index bc6c34e9b9..e6ec619b78 100644 --- a/src/coverage/CoverageRingEdges.cpp +++ b/src/coverage/CoverageRingEdges.cpp @@ -71,6 +71,8 @@ CoverageRingEdges::build() 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 diff --git a/src/coverage/CoverageValidator.cpp b/src/coverage/CoverageValidator.cpp index 719c7f134d..6445583342 100644 --- a/src/coverage/CoverageValidator.cpp +++ b/src/coverage/CoverageValidator.cpp @@ -74,6 +74,8 @@ CoverageValidator::validate() TemplateSTRtree index; std::vector> invalidLines; for (auto* geom : m_coverage) { + util::ensureNoCurvedComponents(geom); + index.insert(geom); invalidLines.emplace_back(nullptr); } diff --git a/src/geom/CircularString.cpp b/src/geom/CircularString.cpp new file mode 100644 index 0000000000..f5bb375c8d --- /dev/null +++ b/src/geom/CircularString.cpp @@ -0,0 +1,80 @@ +/********************************************************************** + * + * 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 + +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; +} + +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/src/geom/CompoundCurve.cpp b/src/geom/CompoundCurve.cpp new file mode 100644 index 0000000000..2175c7ed2b --- /dev/null +++ b/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/src/geom/Curve.cpp b/src/geom/Curve.cpp new file mode 100644 index 0000000000..91fd60da2b --- /dev/null +++ b/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/src/geom/CurvePolygon.cpp b/src/geom/CurvePolygon.cpp new file mode 100644 index 0000000000..74a5e943b7 --- /dev/null +++ b/src/geom/CurvePolygon.cpp @@ -0,0 +1,87 @@ +/********************************************************************** + * + * 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 + +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 { + throw util::UnsupportedOperationException(); + } + + 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/src/geom/Geometry.cpp b/src/geom/Geometry.cpp index dfd37113ea..8a18f2f933 100644 --- a/src/geom/Geometry.cpp +++ b/src/geom/Geometry.cpp @@ -205,6 +205,8 @@ Geometry::getCentroid(CoordinateXY& ret) const std::unique_ptr Geometry::getInteriorPoint() const { + geos::util::ensureNoCurvedComponents(this); + Coordinate interiorPt; int dim = getDimension(); if(dim == 0) { @@ -689,78 +691,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 @@ -868,6 +798,11 @@ Geometry::getPrecisionModel() const return _factory->getPrecisionModel(); } +bool +Geometry::hasCurvedComponents() const { + return false; +} + } // namespace geos::geom } // namespace geos diff --git a/src/geom/GeometryCollection.cpp b/src/geom/GeometryCollection.cpp index 107b3b8bdf..096ca35268 100644 --- a/src/geom/GeometryCollection.cpp +++ b/src/geom/GeometryCollection.cpp @@ -333,6 +333,15 @@ GeometryCollection::compareToSameClass(const Geometry* g) const return compare(geometries, gc->geometries); } +bool GeometryCollection::hasCurvedComponents() const { + for (const auto& g : geometries) { + if (g->hasCurvedComponents()) { + return true; + } + } + return false; +} + const CoordinateXY* GeometryCollection::getCoordinate() const { diff --git a/src/geom/GeometryFactory.cpp b/src/geom/GeometryFactory.cpp index 77a8a89957..4e97f85cd7 100644 --- a/src/geom/GeometryFactory.cpp +++ b/src/geom/GeometryFactory.cpp @@ -20,14 +20,19 @@ #include #include +#include +#include +#include #include #include #include #include #include +#include #include #include #include +#include #include #include #include @@ -220,6 +225,14 @@ GeometryFactory::createPoint(std::size_t coordinateDimension) const return std::unique_ptr(new Point(std::move(seq), this)); } +/*public*/ +std::unique_ptr +GeometryFactory::createPoint(bool hasZ, bool hasM) const +{ + CoordinateSequence seq(0u, hasZ, hasM); + return std::unique_ptr(new Point(std::move(seq), this)); +} + /*public*/ std::unique_ptr GeometryFactory::createPoint(std::unique_ptr&& coords) const @@ -303,6 +316,23 @@ 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 @@ -312,9 +342,26 @@ GeometryFactory::createGeometryCollection() const /*public*/ std::unique_ptr -GeometryFactory::createEmptyGeometry() const +GeometryFactory::createEmptyGeometry(GeometryTypeId type, bool hasZ, bool hasM) const { - return createGeometryCollection(); + 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*/ @@ -365,6 +412,27 @@ GeometryFactory::createMultiPolygon(const std::vector& fromPoly return createMultiPolygon(std::move(newGeoms)); } +std::unique_ptr +GeometryFactory::createMultiSurface() const +{ + // Can't use make_unique because constructor is protected + return std::unique_ptr(new MultiSurface(std::vector>(), *this)); +} + +std::unique_ptr +GeometryFactory::createMultiSurface(std::vector> && newSurfaces) const +{ + // Can't use make_unique because constructor is protected + return std::unique_ptr(new MultiSurface(std::move(newSurfaces), *this)); +} + +std::unique_ptr +GeometryFactory::createMultiSurface(std::vector> && newSurfaces) const +{ + // 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::size_t coordinateDimension) const @@ -374,6 +442,15 @@ GeometryFactory::createLinearRing(std::size_t coordinateDimension) const return std::unique_ptr(new LinearRing(std::move(cs), *this)); } +/*public*/ +std::unique_ptr +GeometryFactory::createLinearRing(bool hasZ, bool hasM) const +{ + // 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)); +} + std::unique_ptr GeometryFactory::createLinearRing(CoordinateSequence::Ptr && newCoords) const { @@ -444,6 +521,15 @@ GeometryFactory::createPolygon(std::size_t coordinateDimension) const return createPolygon(std::move(lr)); } +/*public*/ +std::unique_ptr +GeometryFactory::createPolygon(bool hasZ, bool hasM) const +{ + auto cs = detail::make_unique(0u, hasZ, hasM); + auto lr = createLinearRing(std::move(cs)); + return createPolygon(std::move(lr)); +} + std::unique_ptr GeometryFactory::createPolygon(std::unique_ptr && shell) const @@ -488,6 +574,33 @@ 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 @@ -496,6 +609,22 @@ GeometryFactory::createLineString(std::size_t coordinateDimension) const 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*/ std::unique_ptr GeometryFactory::createLineString(const LineString& ls) const @@ -504,6 +633,14 @@ GeometryFactory::createLineString(const LineString& ls) const return std::unique_ptr(new LineString(ls)); } +/*public*/ +std::unique_ptr +GeometryFactory::createCircularString(const CircularString& ls) const +{ + // Can't use make_unique with protected constructor + return std::unique_ptr(new CircularString(ls)); +} + /*public*/ std::unique_ptr GeometryFactory::createLineString(CoordinateSequence::Ptr && newCoords) @@ -515,6 +652,34 @@ const return std::unique_ptr(new LineString(std::move(newCoords), *this)); } +/*public*/ +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 CircularString(std::move(newCoords), *this)); +} + +/*public*/ +std::unique_ptr +GeometryFactory::createCompoundCurve() +const +{ + std::vector> curves; + return createCompoundCurve(std::move(curves)); +} + +/*public*/ +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) @@ -524,6 +689,15 @@ const 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*/ std::unique_ptr GeometryFactory::createEmpty(int dimension) const diff --git a/src/geom/LineString.cpp b/src/geom/LineString.cpp index e3c84c3dc8..90e37479f6 100644 --- a/src/geom/LineString.cpp +++ b/src/geom/LineString.cpp @@ -50,10 +50,7 @@ LineString::~LineString(){} /*protected*/ LineString::LineString(const LineString& ls) - : - Geometry(ls), - points(ls.points->clone()), - envelope(ls.envelope) + : SimpleCurve(ls) { } @@ -61,9 +58,7 @@ LineString::LineString(const LineString& ls) LineString::LineString(CoordinateSequence::Ptr && newCoords, const GeometryFactory& factory) : - Geometry(&factory), - points(newCoords ? std::move(newCoords) : detail::make_unique()), - envelope(computeEnvelopeInternal()) + SimpleCurve(std::move(newCoords), true, factory) { validateConstruction(); } @@ -88,7 +83,7 @@ void LineString::validateConstruction() { if(points.get() == nullptr) { - points = detail::make_unique(); + points = std::make_unique(); return; } @@ -97,126 +92,6 @@ LineString::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 newPts = detail::make_unique(0u, points->hasZ(), points->hasM()); - auto ret = std::move(points); - points = std::move(newPts); - 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(); -} - -bool -LineString::hasM() const -{ - return points->hasM(); -} - -bool -LineString::hasZ() const -{ - return points->hasZ(); -} - -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 getPointN(0); -} - -std::unique_ptr -LineString::getEndPoint() const -{ - if(isEmpty()) { - return nullptr; - } - return getPointN(getNumPoints() - 1); -} - -bool -LineString::isClosed() const -{ - if(isEmpty()) { - return false; - } - - return points->front().equals2D(points->back()); -} - -bool -LineString::isRing() const -{ - return isClosed() && isSimple(); -} std::string LineString::getGeometryType() const @@ -224,182 +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 -LineString::computeEnvelopeInternal() const -{ - if(isEmpty()) { - return Envelope(); - } - - return 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; -} - -bool -LineString::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()); -} - -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() -{ - 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); -} - -/*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) { - points->reverse(); - } - 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 CoordinateXY* -LineString::getCoordinate() const -{ - if(isEmpty()) { - return nullptr; - } - return &(points->getAt(0)); -} double LineString::getLength() const @@ -407,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/src/geom/MultiCurve.cpp b/src/geom/MultiCurve.cpp new file mode 100644 index 0000000000..31e13eb2c4 --- /dev/null +++ b/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/src/geom/MultiSurface.cpp b/src/geom/MultiSurface.cpp new file mode 100644 index 0000000000..b8e299f6a6 --- /dev/null +++ b/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/src/geom/Polygon.cpp b/src/geom/Polygon.cpp index 515275d514..3163e8543e 100644 --- a/src/geom/Polygon.cpp +++ b/src/geom/Polygon.cpp @@ -28,9 +28,6 @@ #include #include #include -#include -#include -#include #include #include @@ -48,57 +45,14 @@ 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]); - } -} - -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 detail::make_unique(0u, hasZ(), hasM()); + return std::make_unique(0u, hasZ(), hasM()); } - auto cl = detail::make_unique(0u, hasZ(), hasM()); + auto cl = std::make_unique(0u, hasZ(), hasM()); cl->reserve(getNumPoints()); // Add shell points @@ -112,110 +66,6 @@ Polygon::getCoordinates() const return 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()); - } - - return dimension; -} - -bool -Polygon::hasM() const { - if (shell->getCoordinatesRO()->hasM()) { - return true; - } - - for (const auto& hole : holes) { - if (hole->getCoordinatesRO()->hasM()) { - return true; - } - } - - return false; -} - -bool -Polygon::hasZ() const { - if (shell->getCoordinatesRO()->hasZ()) { - return true; - } - - for (const auto& hole : holes) { - if (hole->getCoordinatesRO()->hasZ()) { - return true; - } - } - - return false; -} - -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() -{ - 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); -} - std::string Polygon::getGeometryType() const { @@ -255,151 +105,6 @@ Polygon::getBoundary() const return getFactory()->createMultiLineString(std::move(rings)); } -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; -} - -bool -Polygon::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; -} - -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; - }); -} - -void -Polygon::orientRings(bool exteriorCW) -{ - shell->orient(exteriorCW); - for (auto& hole : holes) { - hole->orient(!exteriorCW); - } -} - -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 @@ -430,11 +135,6 @@ Polygon::normalize(LinearRing* ring, bool clockwise) ring->setPoints(&coords); } -const CoordinateXY* -Polygon::getCoordinate() const -{ - return shell->getCoordinate(); -} /* * Returns the area of this Polygon @@ -453,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 { @@ -572,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 { @@ -591,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/src/geom/SimpleCurve.cpp b/src/geom/SimpleCurve.cpp new file mode 100644 index 0000000000..def5aa1ea3 --- /dev/null +++ b/src/geom/SimpleCurve.cpp @@ -0,0 +1,359 @@ +/********************************************************************** + * + * 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(); +} + +std::unique_ptr +SimpleCurve::getEndPoint() const +{ + if (isEmpty()) { + return nullptr; + } + return getPointN(getNumPoints() - 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/src/geom/Surface.cpp b/src/geom/Surface.cpp new file mode 100644 index 0000000000..34bb5c6015 --- /dev/null +++ b/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/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..8445969357 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::ensureNoCurvedComponents(g); + std::unique_ptr pg; switch(g->getGeometryTypeId()) { diff --git a/src/geom/prep/PreparedLineString.cpp b/src/geom/prep/PreparedLineString.cpp index 819a42d6cb..4ae7f0ae85 100644 --- a/src/geom/prep/PreparedLineString.cpp +++ b/src/geom/prep/PreparedLineString.cpp @@ -57,6 +57,8 @@ PreparedLineString::getIntersectionFinder() bool PreparedLineString::intersects(const geom::Geometry* g) const { + geos::util::ensureNoCurvedComponents(g); + if(! envelopesIntersect(g)) { return false; } diff --git a/src/geom/prep/PreparedPoint.cpp b/src/geom/prep/PreparedPoint.cpp index 8fa3f94f55..5ddef66027 100644 --- a/src/geom/prep/PreparedPoint.cpp +++ b/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/src/geom/prep/PreparedPolygon.cpp b/src/geom/prep/PreparedPolygon.cpp index 332c4798e3..cf2243217e 100644 --- a/src/geom/prep/PreparedPolygon.cpp +++ b/src/geom/prep/PreparedPolygon.cpp @@ -136,6 +136,8 @@ bool PreparedPolygon:: intersects(const geom::Geometry* g) const { + geos::util::ensureNoCurvedComponents(g); + // envelope test if(!envelopesIntersect(g)) { return false; diff --git a/src/geom/util/GeometryEditor.cpp b/src/geom/util/GeometryEditor.cpp index b2d66b9476..acba507129 100644 --- a/src/geom/util/GeometryEditor.cpp +++ b/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(); diff --git a/src/geomgraph/DirectedEdgeStar.cpp b/src/geomgraph/DirectedEdgeStar.cpp index 118c98ebea..44b0ded2af 100644 --- a/src/geomgraph/DirectedEdgeStar.cpp +++ b/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/src/geomgraph/EdgeEndStar.cpp b/src/geomgraph/EdgeEndStar.cpp index da3df4a2b4..44fcc4f4bc 100644 --- a/src/geomgraph/EdgeEndStar.cpp +++ b/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/src/geomgraph/GeometryGraph.cpp b/src/geomgraph/GeometryGraph.cpp index b1b2e53f1a..48e5f6de2a 100644 --- a/src/geomgraph/GeometryGraph.cpp +++ b/src/geomgraph/GeometryGraph.cpp @@ -169,6 +169,8 @@ void GeometryGraph::add(const Geometry* g) //throw (UnsupportedOperationException *) { + util::ensureNoCurvedComponents(g); + if(g->isEmpty()) { return; } diff --git a/src/io/GeoJSONWriter.cpp b/src/io/GeoJSONWriter.cpp index 154b8266d2..0fd4bc297c 100644 --- a/src/io/GeoJSONWriter.cpp +++ b/src/io/GeoJSONWriter.cpp @@ -30,6 +30,8 @@ #include #include +#include "geos/util.h" + #define GEOS_COMPILATION using namespace geos::geom; @@ -174,6 +176,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); diff --git a/src/io/WKBWriter.cpp b/src/io/WKBWriter.cpp index 43ad801a00..0e622b0d49 100644 --- a/src/io/WKBWriter.cpp +++ b/src/io/WKBWriter.cpp @@ -36,6 +36,8 @@ #include #include +#include "geos/util.h" + #undef DEBUG_WKB_WRITER @@ -97,6 +99,8 @@ WKBWriter::writeHEX(const Geometry& g, std::ostream& os) void WKBWriter::write(const Geometry& g, std::ostream& os) { + util::ensureNoCurvedComponents(g); + OrdinateSet inputOrdinates = OrdinateSet::createXY(); inputOrdinates.setM(g.hasM()); inputOrdinates.setZ(g.hasZ()); diff --git a/src/io/WKTReader.cpp b/src/io/WKTReader.cpp index 3ed6406301..fc850d2201 100644 --- a/src/io/WKTReader.cpp +++ b/src/io/WKTReader.cpp @@ -22,14 +22,20 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include +#include #include +#include #include +#include #include #include @@ -259,14 +265,19 @@ WKTReader::getNextWord(StringTokenizer* tokenizer) } std::unique_ptr -WKTReader::readGeometryTaggedText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const +WKTReader::readGeometryTaggedText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags, const GeometryTypeId* emptyType) const { std::string type = getNextWord(tokenizer); std::unique_ptr geom; OrdinateSet origFlags = ordinateFlags; + OrdinateSet newFlags = OrdinateSet::createXY(); - readOrdinateFlags(type, newFlags); + if (type == "EMPTY") { + newFlags = origFlags; + } else { + readOrdinateFlags(type, newFlags); + } if(isTypeName(type, "POINT")) { geom = readPointText(tokenizer, newFlags); @@ -277,20 +288,37 @@ WKTReader::readGeometryTaggedText(StringTokenizer* tokenizer, OrdinateSet& ordin else if(isTypeName(type, "LINEARRING")) { geom = readLinearRingText(tokenizer, newFlags); } + else if(isTypeName(type, "CIRCULARSTRING")) { + geom = readCircularStringText(tokenizer, newFlags); + } + else if(isTypeName(type, "COMPOUNDCURVE")) { + geom = readCompoundCurveText(tokenizer, newFlags); + } else if(isTypeName(type, "POLYGON")) { geom = readPolygonText(tokenizer, newFlags); } + else if(isTypeName(type, "CURVEPOLYGON")) { + geom = readCurvePolygonText(tokenizer, newFlags); + } 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); } @@ -306,7 +334,7 @@ WKTReader::readGeometryTaggedText(StringTokenizer* tokenizer, OrdinateSet& ordin std::unique_ptr WKTReader::readPointText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) const { - auto coords = getCoordinates(tokenizer, ordinateFlags); + auto&& coords = getCoordinates(tokenizer, ordinateFlags); return geometryFactory->createPoint(std::move(coords)); } @@ -327,6 +355,69 @@ WKTReader::readLinearRingText(StringTokenizer* tokenizer, OrdinateSet& ordinateF 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, OrdinateSet& ordinateFlags) const { @@ -418,6 +509,27 @@ WKTReader::readPolygonText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlag 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, OrdinateSet& ordinateFlags) const { @@ -435,6 +547,23 @@ WKTReader::readMultiLineStringText(StringTokenizer* tokenizer, OrdinateSet& ordi 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, OrdinateSet& ordinateFlags) const { @@ -452,6 +581,23 @@ WKTReader::readMultiPolygonText(StringTokenizer* tokenizer, OrdinateSet& ordinat 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, OrdinateSet& ordinateFlags) const { diff --git a/src/io/WKTWriter.cpp b/src/io/WKTWriter.cpp index e4f62573fd..116db31783 100644 --- a/src/io/WKTWriter.cpp +++ b/src/io/WKTWriter.cpp @@ -22,8 +22,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -31,9 +33,12 @@ #include #include #include +#include +#include #include #include #include +#include #include #include @@ -58,7 +63,6 @@ WKTWriter::WKTWriter(): isFormatted(false), roundingPrecision(-1), trim(true), - level(0), defaultOutputDimension(4), old3D(false) { @@ -208,7 +212,7 @@ WKTWriter::writeFormatted(const Geometry* geometry, bool p_isFormatted, void WKTWriter::appendGeometryTaggedText(const Geometry& geometry, OrdinateSet checkOrdinates, - int p_level, + int level, Writer& writer) const { OrdinateSet outputOrdinates = OrdinateSet::createXY(); @@ -235,19 +239,33 @@ WKTWriter::appendGeometryTaggedText(const Geometry& geometry, } } - indent(p_level, &writer); + indent(level, &writer); switch(geometry.getGeometryTypeId()) { - case GEOS_POINT: appendPointTaggedText(static_cast(geometry), outputOrdinates, p_level, writer); break; - case GEOS_LINESTRING: appendLineStringTaggedText(static_cast(geometry), outputOrdinates, p_level, writer); break; - case GEOS_LINEARRING: appendLinearRingTaggedText(static_cast(geometry), outputOrdinates, p_level, writer); break; - case GEOS_POLYGON: appendPolygonTaggedText(static_cast(geometry), outputOrdinates, p_level, writer); break; - case GEOS_MULTIPOINT: appendMultiPointTaggedText(static_cast(geometry), outputOrdinates, p_level, writer); break; - case GEOS_MULTILINESTRING: appendMultiLineStringTaggedText(static_cast(geometry), outputOrdinates, p_level, writer); break; - case GEOS_MULTIPOLYGON: appendMultiPolygonTaggedText(static_cast(geometry), outputOrdinates, p_level, writer); break; - case GEOS_GEOMETRYCOLLECTION: appendGeometryCollectionTaggedText(static_cast(geometry), outputOrdinates, p_level, writer); break; + 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; } } +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 @@ -274,7 +292,7 @@ WKTWriter::appendOrdinateText(OrdinateSet outputOrdinates, Writer& writer) const } void -WKTWriter::appendPointTaggedText(const Point& point, OrdinateSet outputOrdinates, int p_level, +WKTWriter::appendPointTaggedText(const Point& point, OrdinateSet outputOrdinates, int level, Writer& writer) const { writer.write("POINT "); @@ -284,73 +302,103 @@ WKTWriter::appendPointTaggedText(const Point& point, OrdinateSet outputOrdinates if (coord == nullptr) { writer.write("EMPTY"); } else { - appendSequenceText(*point.getCoordinatesRO(), outputOrdinates, p_level, false, writer); + appendSequenceText(*point.getCoordinatesRO(), outputOrdinates, level, false, writer); } } void -WKTWriter::appendLineStringTaggedText(const LineString& lineString, OrdinateSet outputOrdinates, int p_level, - Writer& writer) const +WKTWriter::appendSimpleCurveTaggedText(const SimpleCurve& curve, OrdinateSet outputOrdinates, int level, Writer& writer) const { - writer.write("LINESTRING "); - appendOrdinateText(outputOrdinates, writer); - appendSequenceText(*lineString.getCoordinatesRO(), outputOrdinates, p_level, false, writer); + appendTag(curve, outputOrdinates, writer); + 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, OrdinateSet outputOrdinates, int p_level, Writer& writer) const +WKTWriter::appendCurveText(const Curve& curve, OrdinateSet outputOrdinates, int level, bool doIndent, Writer& writer) const { + if (doIndent) { + indent(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::appendSimpleCurveText(const SimpleCurve& curve, OrdinateSet outputOrdinates, int level, bool doIndent, Writer& writer) const { + if (doIndent) { + indent(level, &writer); + } + + if (curve.getGeometryTypeId() == GEOS_CIRCULARSTRING) { + appendSimpleCurveTaggedText(curve, outputOrdinates, level, writer); + } else { + appendSequenceText(*curve.getCoordinatesRO(), outputOrdinates, level, false, writer); + } +} + + +void +WKTWriter::appendCompoundCurveTaggedText(const CompoundCurve& curve, OrdinateSet outputOrdinates, int level, Writer& writer) const { - writer.write("LINEARRING "); + writer.write("COMPOUNDCURVE "); appendOrdinateText(outputOrdinates, writer); - appendSequenceText(*linearRing.getCoordinatesRO(), outputOrdinates, p_level, false, 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(")"); + } } void -WKTWriter::appendPolygonTaggedText(const Polygon& polygon, OrdinateSet outputOrdinates, int p_level, Writer& writer) const +WKTWriter::appendSurfaceTaggedText(const Surface& surface, OrdinateSet outputOrdinates, int level, Writer& writer) const { - writer.write("POLYGON "); - appendOrdinateText(outputOrdinates, writer); - appendPolygonText(polygon, outputOrdinates, p_level, false, writer); + appendTag(surface, outputOrdinates, writer); + appendSurfaceText(surface, outputOrdinates, level, false, writer); } void -WKTWriter::appendMultiPointTaggedText(const MultiPoint& multipoint, OrdinateSet outputOrdinates, int p_level, Writer& writer) const +WKTWriter::appendMultiPointTaggedText(const MultiPoint& multipoint, OrdinateSet outputOrdinates, int level, Writer& writer) const { writer.write("MULTIPOINT "); appendOrdinateText(outputOrdinates, writer); - appendMultiPointText(multipoint, outputOrdinates, p_level, writer); + appendMultiPointText(multipoint, outputOrdinates, level, writer); } void -WKTWriter::appendMultiLineStringTaggedText(const MultiLineString& multiLineString, OrdinateSet outputOrdinates, int p_level, Writer& writer) const +WKTWriter::appendMultiCurveTaggedText(const GeometryCollection& multiCurve, OrdinateSet outputOrdinates, int level, Writer& writer) const { - writer.write("MULTILINESTRING "); - appendOrdinateText(outputOrdinates, writer); - appendMultiLineStringText(multiLineString, outputOrdinates, p_level, false, writer); + appendTag(multiCurve, outputOrdinates, writer); + appendMultiCurveText(multiCurve, outputOrdinates, level, false, writer); } void -WKTWriter::appendMultiPolygonTaggedText(const MultiPolygon& multiPolygon, OrdinateSet outputOrdinates, int p_level, Writer& writer) const +WKTWriter::appendMultiSurfaceTaggedText(const GeometryCollection& multiPolygon, OrdinateSet outputOrdinates, int level, Writer& writer) const { - writer.write("MULTIPOLYGON "); - appendOrdinateText(outputOrdinates, writer); - appendMultiPolygonText(multiPolygon, outputOrdinates, p_level, writer); + appendTag(multiPolygon, outputOrdinates, writer); + appendMultiSurfaceText(multiPolygon, outputOrdinates, level, writer); } void -WKTWriter::appendGeometryCollectionTaggedText(const GeometryCollection& geometryCollection, OrdinateSet outputOrdinates, int p_level, +WKTWriter::appendGeometryCollectionTaggedText(const GeometryCollection& geometryCollection, OrdinateSet outputOrdinates, int level, Writer& writer) const { writer.write("GEOMETRYCOLLECTION "); appendOrdinateText(outputOrdinates, writer); - appendGeometryCollectionText(geometryCollection, outputOrdinates, p_level, writer); + appendGeometryCollectionText(geometryCollection, outputOrdinates, level, writer); } /* protected */ @@ -377,7 +425,7 @@ WKTWriter::appendCoordinate(const CoordinateXYZM& coordinate, void WKTWriter::appendSequenceText(const CoordinateSequence& seq, OrdinateSet outputOrdinates, - int p_level, + int level, bool doIndent, Writer& writer) const { @@ -386,7 +434,7 @@ WKTWriter::appendSequenceText(const CoordinateSequence& seq, } else { if(doIndent) { - indent(p_level, &writer); + indent(level, &writer); } writer.write("("); CoordinateXYZM c; @@ -394,7 +442,7 @@ WKTWriter::appendSequenceText(const CoordinateSequence& seq, if(i > 0) { writer.write(", "); if(coordsPerLine > 0 && i % coordsPerLine == 0) { - indent(p_level + 2, &writer); + indent(level + 2, &writer); } } seq.getAt(i, c); @@ -404,7 +452,7 @@ WKTWriter::appendSequenceText(const CoordinateSequence& seq, } } -int +int WKTWriter::writeTrimmedNumber(double d, uint32_t precision, char* buf) { const auto da = std::fabs(d); @@ -459,14 +507,7 @@ WKTWriter::writeNumber(double d) const } void -WKTWriter::appendLineStringText(const LineString& lineString, OrdinateSet outputOrdinates, int p_level, - bool doIndent, Writer& writer) const -{ - appendSequenceText(*lineString.getCoordinatesRO(), outputOrdinates, p_level, doIndent, writer); -} - -void -WKTWriter::appendPolygonText(const Polygon& polygon, OrdinateSet outputOrdinates, int /*level*/, +WKTWriter::appendSurfaceText(const Surface& polygon, OrdinateSet outputOrdinates, int level, bool indentFirst, Writer& writer) const { if(polygon.isEmpty()) { @@ -477,11 +518,15 @@ WKTWriter::appendPolygonText(const Polygon& polygon, OrdinateSet outputOrdinates indent(level, &writer); } writer.write("("); - appendLineStringText(*polygon.getExteriorRing(), outputOrdinates, level, false, writer); + + auto ring = polygon.getExteriorRing(); + appendCurveText(*ring, outputOrdinates, 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, outputOrdinates, level + 1, true, writer); + + auto hole = polygon.getInteriorRingN(i); + appendCurveText(*hole, outputOrdinates, level + 1, true, writer); } writer.write(")"); } @@ -518,49 +563,55 @@ WKTWriter::appendMultiPointText(const MultiPoint& multiPoint, OrdinateSet output } void -WKTWriter::appendMultiLineStringText(const MultiLineString& multiLineString, OrdinateSet outputOrdinates, int p_level, bool indentFirst, +WKTWriter::appendMultiCurveText(const GeometryCollection& multiCurve, OrdinateSet outputOrdinates, int level, bool indentFirst, Writer& writer) const { - const std::size_t n = multiLineString.getNumGeometries(); + 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; i < n; ++i) { if(i > 0) { writer.write(", "); - level2 = p_level + 1; + level2 = level + 1; doIndent = true; } - const LineString* ls = multiLineString.getGeometryN(i); - appendLineStringText(*ls, outputOrdinates, level2, doIndent, writer); + + const Curve* g = static_cast(multiCurve.getGeometryN(i)); + appendCurveText(*g, outputOrdinates, level2, doIndent, writer); } writer.write(")"); } } void -WKTWriter::appendMultiPolygonText(const MultiPolygon& multiPolygon, OrdinateSet outputOrdinates, int p_level, Writer& writer) const +WKTWriter::appendMultiSurfaceText(const GeometryCollection& multiSurface, OrdinateSet outputOrdinates, int level, Writer& writer) const { - const std::size_t n = multiPolygon.getNumGeometries(); + 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; i < n; ++i) { if(i > 0) { writer.write(", "); - level2 = p_level + 1; + level2 = level + 1; doIndent = true; } - const Polygon* p = multiPolygon.getGeometryN(i); - appendPolygonText(*p, outputOrdinates, 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(")"); } @@ -570,7 +621,7 @@ void WKTWriter::appendGeometryCollectionText( const GeometryCollection& geometryCollection, OrdinateSet outputOrdinates, - int p_level, + int level, Writer& writer) const { const std::size_t n = geometryCollection.getNumGeometries(); @@ -578,12 +629,12 @@ WKTWriter::appendGeometryCollectionText( writer.write("EMPTY"); } else { - int level2 = p_level; + int level2 = level; writer.write("("); for(std::size_t i = 0; i < n; ++i) { if(i > 0) { writer.write(", "); - level2 = p_level + 1; + level2 = level + 1; } appendGeometryTaggedText(*geometryCollection.getGeometryN(i), outputOrdinates, level2, writer); } @@ -592,13 +643,13 @@ WKTWriter::appendGeometryCollectionText( } 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/src/noding/GeometryNoder.cpp b/src/noding/GeometryNoder.cpp index 144d1f306f..71ad91ea9e 100644 --- a/src/noding/GeometryNoder.cpp +++ b/src/noding/GeometryNoder.cpp @@ -92,6 +92,7 @@ GeometryNoder::GeometryNoder(const geom::Geometry& g) : argGeom(g) { + util::ensureNoCurvedComponents(argGeom); } /* private */ diff --git a/src/operation/BoundaryOp.cpp b/src/operation/BoundaryOp.cpp index 5048617841..b407a2a41c 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::ensureNoCurvedComponents(m_geom); + if (auto ls = dynamic_cast(&m_geom)) { return boundaryLineString(*ls); } diff --git a/src/operation/GeometryGraphOperation.cpp b/src/operation/GeometryGraphOperation.cpp index 42d157bd0e..65b5c298c0 100644 --- a/src/operation/GeometryGraphOperation.cpp +++ b/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/src/operation/distance/DistanceOp.cpp b/src/operation/distance/DistanceOp.cpp index d587e755da..18ae46c359 100644 --- a/src/operation/distance/DistanceOp.cpp +++ b/src/operation/distance/DistanceOp.cpp @@ -104,6 +104,9 @@ 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"); } diff --git a/src/operation/linemerge/LineMerger.cpp b/src/operation/linemerge/LineMerger.cpp index 8163c9a62a..e26aab299a 100644 --- a/src/operation/linemerge/LineMerger.cpp +++ b/src/operation/linemerge/LineMerger.cpp @@ -94,6 +94,8 @@ struct LMGeometryComponentFilter: public GeometryComponentFilter { void LineMerger::add(const Geometry* geometry) { + util::ensureNoCurvedComponents(geometry); + LMGeometryComponentFilter lmgcf(this); geometry->apply_ro(&lmgcf); } diff --git a/src/operation/overlayng/OverlayNGRobust.cpp b/src/operation/overlayng/OverlayNGRobust.cpp index 016eeb3df6..96dda0f69f 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::ensureNoCurvedComponents(geom0); + geos::util::ensureNoCurvedComponents(geom1); + std::unique_ptr result; std::runtime_error exOriginal(""); diff --git a/src/operation/polygonize/Polygonizer.cpp b/src/operation/polygonize/Polygonizer.cpp index 1eb5241491..92eefdfc0c 100644 --- a/src/operation/polygonize/Polygonizer.cpp +++ b/src/operation/polygonize/Polygonizer.cpp @@ -103,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. @@ -128,6 +114,7 @@ Polygonizer::add(Geometry* g) void Polygonizer::add(const Geometry* g) { + util::ensureNoCurvedComponents(g); g->apply_ro(&lineStringAdder); } diff --git a/src/operation/relate/RelateComputer.cpp b/src/operation/relate/RelateComputer.cpp index bbdd255fec..53f1bc627c 100644 --- a/src/operation/relate/RelateComputer.cpp +++ b/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; - auto&& ee0 = eeBuilder.computeEdgeEnds((*arg)[0]->getEdges()); + auto&& ee0 = eeBuilder.computeEdgeEnds(arg[0]->getEdges()); insertEdgeEnds(ee0); - auto&& ee1 = eeBuilder.computeEdgeEnds((*arg)[1]->getEdges()); + auto&& ee1 = eeBuilder.computeEdgeEnds(arg[1]->getEdges()); #if GEOS_DEBUG std::cerr << "RelateComputer::computeIM: " @@ -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,7 +311,7 @@ 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.get(); Node* newNode = nodes.addNode(graphNode->getCoordinate()); @@ -333,7 +333,7 @@ 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); const EdgeIntersectionList& eiL = e->getEdgeIntersectionList(); @@ -361,7 +361,7 @@ RelateComputer::computeIntersectionNodes(uint8_t argIndex) void RelateComputer::labelIntersectionNodes(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(); @@ -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)); @@ -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); } } @@ -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/src/operation/relate/RelateOp.cpp b/src/operation/relate/RelateOp.cpp index 719cddb7f8..739b6d4ccb 100644 --- a/src/operation/relate/RelateOp.cpp +++ b/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/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 39571cc58e..454d9a18cf 100644 --- a/src/operation/valid/IsValidOp.cpp +++ b/src/operation/valid/IsValidOp.cpp @@ -102,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 diff --git a/src/triangulate/DelaunayTriangulationBuilder.cpp b/src/triangulate/DelaunayTriangulationBuilder.cpp index 424de2a131..cd80ecdcd7 100644 --- a/src/triangulate/DelaunayTriangulationBuilder.cpp +++ b/src/triangulate/DelaunayTriangulationBuilder.cpp @@ -77,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/src/triangulate/VoronoiDiagramBuilder.cpp b/src/triangulate/VoronoiDiagramBuilder.cpp index d4b09c8c5d..eeae7f0643 100644 --- a/src/triangulate/VoronoiDiagramBuilder.cpp +++ b/src/triangulate/VoronoiDiagramBuilder.cpp @@ -51,6 +51,7 @@ VoronoiDiagramBuilder::VoronoiDiagramBuilder() : void VoronoiDiagramBuilder::setSites(const geom::Geometry& geom) { + util::ensureNoCurvedComponents(geom); siteCoords = DelaunayTriangulationBuilder::extractUniqueCoordinates(geom); inputGeom = &geom; } diff --git a/src/triangulate/polygon/ConstrainedDelaunayTriangulator.cpp b/src/triangulate/polygon/ConstrainedDelaunayTriangulator.cpp index c52f855489..5f56b12c2a 100644 --- a/src/triangulate/polygon/ConstrainedDelaunayTriangulator.cpp +++ b/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/src/util/string.cpp b/src/util/string.cpp index b28575a15f..b61b147c12 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -12,6 +12,7 @@ * **********************************************************************/ +#include #include namespace geos { @@ -53,6 +54,11 @@ bool startsWith(const std::string & s, char prefix) { return s[0] == prefix; } +void toUpper(std::string& s) +{ + std::transform(s.begin(), s.end(), s.begin(), ::toupper); +} + } -} \ No newline at end of file +} diff --git a/tests/unit/algorithm/CircularArcsTest.cpp b/tests/unit/algorithm/CircularArcsTest.cpp new file mode 100644 index 0000000000..475261d4fb --- /dev/null +++ b/tests/unit/algorithm/CircularArcsTest.cpp @@ -0,0 +1,219 @@ +#include + +#include +#include + +using geos::geom::CoordinateXY; +using geos::algorithm::CircularArcs; +using geos::geom::Envelope; + +namespace tut { + +struct test_circulararcs_data { + const double eps = 1e-8; + + void checkEnvelope(const CoordinateXY& p0, const CoordinateXY& p1, const CoordinateXY& p2, + double xmin, double ymin, double xmax, double ymax) + { + { + Envelope e; + CircularArcs::expandEnvelope(e, p0, p1, p2); + + ensure_equals("p0-p1-p2 xmin", e.getMinX(), xmin, eps); + ensure_equals("p0-p1-p2 xmax", e.getMaxX(), xmax, eps); + ensure_equals("p0-p1-p2 ymin", e.getMinY(), ymin, eps); + ensure_equals("p0-p1-p2 ymax", e.getMaxY(), ymax, eps); + } + + { + Envelope e; + CircularArcs::expandEnvelope(e, p2, p1, p0); + + ensure_equals("p2-p1-p0 xmin", e.getMinX(), xmin, eps); + ensure_equals("p2-p1-p0 xmax", e.getMaxX(), xmax, eps); + ensure_equals("p2-p1-p0 ymin", e.getMinY(), ymin, eps); + ensure_equals("p2-p1-p0 ymax", e.getMaxY(), ymax, eps); + } + } +}; + +using group = test_group; +using object = group::object; + +group test_circulararcs_group("geos::algorithm::CircularArcs"); + +template<> +template<> +void object::test<1>() +{ + CoordinateXY p0{0, 10}; + CoordinateXY p1{100, 110}; + CoordinateXY p2{200, 10}; + + auto center = CircularArcs::getCenter(p0, p1, p2); + + ensure_equals(center, CoordinateXY{100, 10}); +} + +template<> +template<> +void object::test<2>() +{ + CoordinateXY p0{0, 0}; + CoordinateXY p1{1, 1}; + CoordinateXY p2{0, 2}; + + auto center = CircularArcs::getCenter(p0, p1, p2); + + ensure_equals(center, CoordinateXY{0, 1}); +} + +template<> +template<> +void object::test<3>() +{ + CoordinateXY p0{54.22, 31.8}; + CoordinateXY p1{16.07, 11.9}; + CoordinateXY p2{12.22, 3.99}; + + auto center = CircularArcs::getCenter(p0, p1, p2); + + ensure(center.distance(CoordinateXY{52.0123, -10.486}) < 1e-4); +} + +// complete circle +template<> +template<> +void object::test<4>() +{ + CoordinateXY p0{3, 4}; + CoordinateXY p1{7, 8}; + CoordinateXY p2{3, 4}; + + auto center = CircularArcs::getCenter(p0, p1, p2); + + ensure_equals(center, CoordinateXY{5, 6}); +} + +// collinear +template<> +template<> +void object::test<5>() +{ + CoordinateXY p0{1, 2}; + CoordinateXY p1{2, 3}; + CoordinateXY p2{3, 4}; + + auto center = CircularArcs::getCenter(p0, p1, p2); + + ensure(std::isnan(center.x)); + ensure(std::isnan(center.y)); +} + +// CCW quadrant 2 to quadrant 1 +template<> +template<> +void object::test<6>() +{ + CoordinateXY p0{-std::sqrt(2), -std::sqrt(2)}; + CoordinateXY p1{2, 0}; + CoordinateXY p2{-std::sqrt(2), std::sqrt(2)}; + + checkEnvelope(p0, p1, p2, + -std::sqrt(2), -2, 2, 2); +} + +// quadrant 0 to quadrant 0, crossing all axes +template<> +template<> +void object::test<7>() +{ + CoordinateXY p0{std::sqrt(2), std::sqrt(2)}; + CoordinateXY p1{2, 0}; + CoordinateXY p2{std::sqrt(3), 1}; + + checkEnvelope(p0, p1, p2, -2, -2, 2, 2); +} + + +// quadrant 0 to quadrant 0, crossing no axes +template<> +template<> +void object::test<8>() +{ + CoordinateXY p0{1, std::sqrt(3)}; + CoordinateXY p1{std::sqrt(2), std::sqrt(2)}; + CoordinateXY p2{std::sqrt(3), 1}; + + checkEnvelope(p0, p1, p2, + 1, 1, std::sqrt(3), std::sqrt(3)); +} + +// half circle with start points on -/+ x axis +template<> +template<> +void object::test<9>() +{ + CoordinateXY p0{-1, 0}; + CoordinateXY p1{0, 1}; + CoordinateXY p2{1, 0}; + + checkEnvelope(p0, p1, p2, + -1, 0, 1, 1); +} + +// CCW quadrant 0 to quadrant 3 +template<> +template<> +void object::test<10>() +{ + CoordinateXY p0{std::sqrt(2), std::sqrt(2)}; + CoordinateXY p1{-2, 0}; + CoordinateXY p2{std::sqrt(2), -std::sqrt(2)}; + + checkEnvelope(p0, p1, p2, + -2, -2, std::sqrt(2), 2); +} + +// collinear +template<> +template<> +void object::test<11>() +{ + CoordinateXY p0{-1, -1}; + CoordinateXY p1{1, 1}; + CoordinateXY p2{2, 2}; + + checkEnvelope(p0, p1, p2, + -1, -1, 2, 2); +} + +// collinear +template<> +template<> +void object::test<12>() +{ + + CoordinateXY p0{1, 2}; + CoordinateXY p1{2, 3}; + CoordinateXY p2{3, 4}; + + checkEnvelope(p0, p1, p2, + 1, 2, 3, 4); +} + +// repeated +template<> +template<> +void object::test<13>() +{ + CoordinateXY p0{3, 4}; + CoordinateXY p1{3, 4}; + CoordinateXY p2{3, 4}; + + checkEnvelope(p0, p1, p2, + 3, 4, 3, 4); +} + +} + diff --git a/tests/unit/capi/GEOSBoundaryTest.cpp b/tests/unit/capi/GEOSBoundaryTest.cpp index 005ab388e4..d24bd558f3 100644 --- a/tests/unit/capi/GEOSBoundaryTest.cpp +++ b/tests/unit/capi/GEOSBoundaryTest.cpp @@ -30,6 +30,16 @@ void object::test<1>() ensure_equals(GEOSGetSRID(input_), GEOSGetSRID(result_)); } +template<> +template<> +void object::test<2>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_ != nullptr); + + result_ = GEOSBoundary(input_); + ensure(result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSBufferTest.cpp b/tests/unit/capi/GEOSBufferTest.cpp index e44d342e72..badf30684f 100644 --- a/tests/unit/capi/GEOSBufferTest.cpp +++ b/tests/unit/capi/GEOSBufferTest.cpp @@ -460,4 +460,15 @@ void object::test<25>() ensure_geometry_equals(geom3_, geom2_, 0.001); } +template<> +template<> +void object::test<26>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_ != nullptr); + + result_ = GEOSBuffer(input_, 1, 8); + ensure(result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSBuildAreaTest.cpp b/tests/unit/capi/GEOSBuildAreaTest.cpp index c7f23ac4bc..012de41e40 100644 --- a/tests/unit/capi/GEOSBuildAreaTest.cpp +++ b/tests/unit/capi/GEOSBuildAreaTest.cpp @@ -38,4 +38,15 @@ void object::test<1>() ensure_geometry_equals(result_, expected_, 0); } +template<> +template<> +void object::test<2>() +{ + input_ = fromWKT("MULTICURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0) )"); + ensure(input_ != nullptr); + + result_ = GEOSBuildArea(input_); + ensure(result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSClipByRectTest.cpp b/tests/unit/capi/GEOSClipByRectTest.cpp index 0fcdb530df..13fde6f732 100644 --- a/tests/unit/capi/GEOSClipByRectTest.cpp +++ b/tests/unit/capi/GEOSClipByRectTest.cpp @@ -271,6 +271,17 @@ void object::test<16> } } +template<> +template<> +void object::test<17>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_ != nullptr); + + result_ = GEOSClipByRect(input_, 0, 0, 1, 1); + ensure(result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSConcaveHullOfPolygonsTest.cpp b/tests/unit/capi/GEOSConcaveHullOfPolygonsTest.cpp index 8de6baf542..57c6a0b9c1 100644 --- a/tests/unit/capi/GEOSConcaveHullOfPolygonsTest.cpp +++ b/tests/unit/capi/GEOSConcaveHullOfPolygonsTest.cpp @@ -52,6 +52,17 @@ void object::test<2>() ensure_geometry_equals(geom1_, expected_); } +template<> +template<> +void object::test<3>() +{ + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0) ))"); + ensure(input_ != nullptr); + + result_ = GEOSConcaveHullOfPolygons(input_, 0.7, false, false); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSConcaveHullTest.cpp b/tests/unit/capi/GEOSConcaveHullTest.cpp index 690ce54252..243161d31a 100644 --- a/tests/unit/capi/GEOSConcaveHullTest.cpp +++ b/tests/unit/capi/GEOSConcaveHullTest.cpp @@ -53,4 +53,15 @@ void object::test<2>() ensure_geometry_equals(result_, expected_); } +template<> +template<> +void object::test<3>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_ != nullptr); + + result_ = GEOSConcaveHull(input_, 0, 0); + ensure(result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSConstrainedDelaunayTriangulationTest.cpp b/tests/unit/capi/GEOSConstrainedDelaunayTriangulationTest.cpp index 24687a6b86..2022b7f905 100644 --- a/tests/unit/capi/GEOSConstrainedDelaunayTriangulationTest.cpp +++ b/tests/unit/capi/GEOSConstrainedDelaunayTriangulationTest.cpp @@ -71,6 +71,17 @@ void object::test<3> ensure_geometry_equals(geom2_, geom3_); } +// CurvePolygon +template<> +template<> +void object::test<4>() +{ + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); + ensure(input_ != nullptr); + + result_ = GEOSConstrainedDelaunayTriangulation(input_); + ensure("curved geometry not supported", result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSContainsTest.cpp b/tests/unit/capi/GEOSContainsTest.cpp index 7dd017ff7e..2e570424d8 100644 --- a/tests/unit/capi/GEOSContainsTest.cpp +++ b/tests/unit/capi/GEOSContainsTest.cpp @@ -191,5 +191,19 @@ void object::test<5> } } +template<> +template<> +void object::test<6>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 0)"); + + ensure(geom1_); + ensure(geom2_); + + ensure_equals("curved geometry not supported", GEOSContains(geom1_, geom2_), 2); +} + + } // namespace tut diff --git a/tests/unit/capi/GEOSConvexHullTest.cpp b/tests/unit/capi/GEOSConvexHullTest.cpp index 9b99c8cad4..e7c20ac0bd 100644 --- a/tests/unit/capi/GEOSConvexHullTest.cpp +++ b/tests/unit/capi/GEOSConvexHullTest.cpp @@ -42,5 +42,16 @@ void object::test<1> ensure_geometry_equals(result_, expected_); } +template<> +template<> +void object::test<2>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_ != nullptr); + + result_ = GEOSConvexHull(input_); + ensure(result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSCoverageIsValidTest.cpp b/tests/unit/capi/GEOSCoverageIsValidTest.cpp index 79d54d88aa..93868959db 100644 --- a/tests/unit/capi/GEOSCoverageIsValidTest.cpp +++ b/tests/unit/capi/GEOSCoverageIsValidTest.cpp @@ -88,6 +88,16 @@ template<> void object::test<2> ensure_geometry_equals(result_, expected_, 0.01); } +template<> +template<> void object::test<3> +() +{ + input_ = fromWKT("GEOMETRYCOLLECTION ( " + "CURVEPOLYGON (COMPOUNDCURVE ( CIRCULARSTRING (2 0, 1 1, 2 2), (2 2, 0 2, 0 0, 2 0))), " + "CURVEPOLYGON (COMPOUNDCURVE ( CIRCULARSTRING (2 2, 1 1, 2 0), (2 0, 4 0, 4 2, 2 2))))"); + ensure(input_); + ensure_equals("curved geometry not supported", GEOSCoverageIsValid(input_, 0, nullptr), 2); +} } // namespace tut diff --git a/tests/unit/capi/GEOSCoverageSimplifyTest.cpp b/tests/unit/capi/GEOSCoverageSimplifyTest.cpp index 830a58c476..812db7cc13 100644 --- a/tests/unit/capi/GEOSCoverageSimplifyTest.cpp +++ b/tests/unit/capi/GEOSCoverageSimplifyTest.cpp @@ -100,6 +100,18 @@ template<> void object::test<3> ensure_geometry_equals(result_, expected_, 0.1); } +template<> +template<> void object::test<4> +() +{ + input_ = fromWKT("GEOMETRYCOLLECTION ( " + "CURVEPOLYGON (COMPOUNDCURVE ( CIRCULARSTRING (2 0, 1 1, 2 2), (2 2, 0 2, 0 0, 2 0))), " + "CURVEPOLYGON (COMPOUNDCURVE ( CIRCULARSTRING (2 2, 1 1, 2 0), (2 0, 4 0, 4 2, 2 2))))"); + ensure(input_); + + result_ = GEOSCoverageSimplifyVW(input_, 0.1, false); + ensure("curved geometry not supported", result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSCoverageUnionTest.cpp b/tests/unit/capi/GEOSCoverageUnionTest.cpp index 6b8a71e7a3..a9808c1042 100644 --- a/tests/unit/capi/GEOSCoverageUnionTest.cpp +++ b/tests/unit/capi/GEOSCoverageUnionTest.cpp @@ -10,13 +10,15 @@ #include #include +#include "capi_test_utils.h" + namespace tut { // // Test Group // // Common data used in test cases. -struct test_capicoverageunion_data { +struct test_capicoverageunion_data : public capitest::utility { static void notice(const char *fmt, ...) { std::fprintf(stdout, "NOTICE: "); @@ -103,6 +105,18 @@ void object::test<2> GEOSGeom_destroy_r(m_context, result); } +template<> +template<> void object::test<4> +() +{ + input_ = fromWKT("GEOMETRYCOLLECTION ( " + "CURVEPOLYGON (COMPOUNDCURVE ( CIRCULARSTRING (2 0, 1 1, 2 2), (2 2, 0 2, 0 0, 2 0))), " + "CURVEPOLYGON (COMPOUNDCURVE ( CIRCULARSTRING (2 2, 1 1, 2 0), (2 0, 4 0, 4 2, 2 2))))"); + ensure(input_); + + result_ = GEOSCoverageSimplifyVW(input_, 0.1, false); + ensure("curved geometry not supported", result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSCoveredByTest.cpp b/tests/unit/capi/GEOSCoveredByTest.cpp index 6f9bfc947e..6982fb4966 100644 --- a/tests/unit/capi/GEOSCoveredByTest.cpp +++ b/tests/unit/capi/GEOSCoveredByTest.cpp @@ -32,5 +32,18 @@ void object::test<1>() ensure_equals(0, GEOSCoveredBy(geom3_, geom2_)); } +template<> +template<> +void object::test<2>() +{ + geom1_ = fromWKT("LINESTRING (5 3, 5 4)"); + geom2_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 10 10, 20 0), (20 0, 0 0)))"); + + ensure(geom1_); + ensure(geom2_); + + ensure_equals("curved geometry not supported", GEOSCoveredBy(geom1_, geom2_), 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSCoversTest.cpp b/tests/unit/capi/GEOSCoversTest.cpp index ee976743a8..bdf5f3b087 100644 --- a/tests/unit/capi/GEOSCoversTest.cpp +++ b/tests/unit/capi/GEOSCoversTest.cpp @@ -32,5 +32,18 @@ void object::test<1>() ensure_equals(0, GEOSCovers(geom3_, geom2_)); } +template<> +template<> +void object::test<2>() +{ + geom1_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 10 10, 20 0), (20 0, 0 0)))"); + geom2_ = fromWKT("LINESTRING (5 3, 5 4)"); + + ensure(geom1_); + ensure(geom2_); + + ensure_equals("curved geometry not supported", GEOSCovers(geom1_, geom2_), 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSCrossesTest.cpp b/tests/unit/capi/GEOSCrossesTest.cpp index ab19caa9fc..12ba5f161a 100644 --- a/tests/unit/capi/GEOSCrossesTest.cpp +++ b/tests/unit/capi/GEOSCrossesTest.cpp @@ -32,5 +32,19 @@ void object::test<1>() ensure_equals(0, GEOSCrosses(geom3_, geom2_)); } +template<> +template<> +void object::test<2>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + ensure_equals("curved geometry not supported", GEOSCrosses(geom1_, geom2_), 2); + ensure_equals("curved geometry not supported", GEOSCrosses(geom2_, geom1_), 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSDelaunayTriangulationTest.cpp b/tests/unit/capi/GEOSDelaunayTriangulationTest.cpp index a5568e59ea..0064d8073d 100644 --- a/tests/unit/capi/GEOSDelaunayTriangulationTest.cpp +++ b/tests/unit/capi/GEOSDelaunayTriangulationTest.cpp @@ -128,5 +128,16 @@ void object::test<6> "MULTILINESTRING ((10 0, 10 10), (0 0, 10 10), (0 0, 10 0))"); } +template<> +template<> +void object::test<7>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0)"); + ensure(input_); + + result_ = GEOSDelaunayTriangulation(input_, 0, 0); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSDensifyTest.cpp b/tests/unit/capi/GEOSDensifyTest.cpp index cd11e1cea3..97ede1605e 100644 --- a/tests/unit/capi/GEOSDensifyTest.cpp +++ b/tests/unit/capi/GEOSDensifyTest.cpp @@ -159,4 +159,14 @@ void object::test<9>() ensure("result expected to be NULL", result_ == nullptr); } +template<> +template<> +void object::test<10>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_); + + result_ = GEOSDensify(input_, 0.1); + ensure("curved geometries not supported", result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSDifferenceTest.cpp b/tests/unit/capi/GEOSDifferenceTest.cpp index 5cdf79604a..d739b09361 100644 --- a/tests/unit/capi/GEOSDifferenceTest.cpp +++ b/tests/unit/capi/GEOSDifferenceTest.cpp @@ -37,8 +37,7 @@ void object::test<1>() */ template<> template<> -void object::test<2>() -{ +void object::test<2>() { GEOSGeometry* a = GEOSGeomFromWKT("GEOMETRYCOLLECTION (POINT (51 -1), LINESTRING (52 -1, 49 2))"); GEOSGeometry* b = GEOSGeomFromWKT("POINT (2 3)"); @@ -60,5 +59,19 @@ void object::test<2>() GEOSGeom_destroy(ba); } +template<> +template<> +void object::test<3>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + result_ = GEOSDifference(geom1_, geom2_); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSDisjointSubsetUnionTest.cpp b/tests/unit/capi/GEOSDisjointSubsetUnionTest.cpp index 456b39ee9d..76e2a39923 100644 --- a/tests/unit/capi/GEOSDisjointSubsetUnionTest.cpp +++ b/tests/unit/capi/GEOSDisjointSubsetUnionTest.cpp @@ -46,6 +46,15 @@ void object::test<2>() ensure_geometry_equals(result_, expected_); } +template <> +template <> +void object::test<3>() { + input_ = fromWKT("MULTICURVE ((0 0, 1 1), CIRCULARSTRING (1 1, 2 0, 3 1), (5 5, 8 8))"); + ensure(input_); + + result_ = GEOSDisjointSubsetUnion(input_); + ensure("curved geometry not supported", result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSDisjointTest.cpp b/tests/unit/capi/GEOSDisjointTest.cpp index cb8fde2a2e..f17faca3aa 100644 --- a/tests/unit/capi/GEOSDisjointTest.cpp +++ b/tests/unit/capi/GEOSDisjointTest.cpp @@ -28,5 +28,19 @@ void object::test<1>() ensure_equals(0, GEOSDisjoint(geom1_, geom3_)); } +template<> +template<> +void object::test<2>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + ensure_equals("curved geometry not supported", GEOSDisjoint(geom1_, geom2_), 2); + ensure_equals("curved geometry not supported", GEOSDisjoint(geom2_, geom1_), 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSDistanceTest.cpp b/tests/unit/capi/GEOSDistanceTest.cpp index 5bf03c90ba..0416532ef4 100644 --- a/tests/unit/capi/GEOSDistanceTest.cpp +++ b/tests/unit/capi/GEOSDistanceTest.cpp @@ -157,5 +157,20 @@ void object::test<5> ensure_equals(raised & FE_OVERFLOW, 0); } +template<> +template<> +void object::test<6>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 1.0001, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + double dist; + int ret = GEOSDistance(geom1_, geom2_, &dist); + ensure_equals("curved geometry not supported", ret, 0); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSDistanceWithinTest.cpp b/tests/unit/capi/GEOSDistanceWithinTest.cpp index a76aa05e98..567e431925 100644 --- a/tests/unit/capi/GEOSDistanceWithinTest.cpp +++ b/tests/unit/capi/GEOSDistanceWithinTest.cpp @@ -415,6 +415,19 @@ void object::test<31>() { ); } +template<> +template<> +void object::test<32>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 1.0001, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + char ret = GEOSDistanceWithin(geom1_, geom2_, 0.1); + ensure_equals("curved geometry not supported", ret, 2); +} diff --git a/tests/unit/capi/GEOSEqualsIdenticalTest.cpp b/tests/unit/capi/GEOSEqualsIdenticalTest.cpp index 4b0da52993..ea2b2a799e 100644 --- a/tests/unit/capi/GEOSEqualsIdenticalTest.cpp +++ b/tests/unit/capi/GEOSEqualsIdenticalTest.cpp @@ -212,5 +212,16 @@ void object::test<16> ensure_equals(GEOSEqualsIdentical(geom1_, geom2_), 1); } +template<> +template<> +void object::test<17> +() +{ + geom1_ = GEOSGeomFromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = GEOSGeomFromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + + ensure_equals(GEOSEqualsIdentical(geom1_, geom2_), 1); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSEqualsTest.cpp b/tests/unit/capi/GEOSEqualsTest.cpp index 793207f496..0164a3cede 100644 --- a/tests/unit/capi/GEOSEqualsTest.cpp +++ b/tests/unit/capi/GEOSEqualsTest.cpp @@ -134,5 +134,19 @@ void object::test<6> #endif +template<> +template<> +void object::test<7>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + + ensure(geom1_); + ensure(geom2_); + + ensure_equals("curved geometry not supported", GEOSEquals(geom1_, geom2_), 2); + ensure_equals("curved geometry not supported", GEOSEquals(geom2_, geom1_), 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSFrechetDistanceTest.cpp b/tests/unit/capi/GEOSFrechetDistanceTest.cpp index 93c0f657e1..ad6215a284 100644 --- a/tests/unit/capi/GEOSFrechetDistanceTest.cpp +++ b/tests/unit/capi/GEOSFrechetDistanceTest.cpp @@ -92,4 +92,19 @@ void object::test<4> ensure(dist >= 0); // no crash } +template<> +template<> +void object::test<5>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 2, 2 2)"); + + ensure(geom1_); + ensure(geom2_); + + double dist; + ensure_equals("curved geometry not supported", GEOSFrechetDistance(geom1_, geom2_, &dist), 0); + ensure_equals("curved geometry not supported", GEOSFrechetDistance(geom2_, geom1_, &dist), 0); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSGeomGeoJSONWriteTest.cpp b/tests/unit/capi/GEOSGeomGeoJSONWriteTest.cpp index 939ec6e5fb..23b2d68d1e 100644 --- a/tests/unit/capi/GEOSGeomGeoJSONWriteTest.cpp +++ b/tests/unit/capi/GEOSGeomGeoJSONWriteTest.cpp @@ -100,4 +100,17 @@ void object::test<3> "}"); test_geojson(wkt, expected, 4); } + +template<> +template<> +void object::test<4> +() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_); + + char* geojson = GEOSGeoJSONWriter_writeGeometry(writer_, input_, 0); + ensure("curved geometry not supported", geojson == nullptr); +} + } diff --git a/tests/unit/capi/GEOSGeomGetNumPointsTest.cpp b/tests/unit/capi/GEOSGeomGetNumPointsTest.cpp index 7c9db7ae2b..80b7d24566 100644 --- a/tests/unit/capi/GEOSGeomGetNumPointsTest.cpp +++ b/tests/unit/capi/GEOSGeomGetNumPointsTest.cpp @@ -40,5 +40,13 @@ void object::test<3>() ensure_equals(GEOSGeomGetNumPoints(input_), -1); } +template<> +template<> +void object::test<4>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure_equals(GEOSGeomGetNumPoints(input_), 3); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSGeomToHEX_bufTest.cpp b/tests/unit/capi/GEOSGeomToHEX_bufTest.cpp index fbd43b0a0f..f6d6c4e7c5 100644 --- a/tests/unit/capi/GEOSGeomToHEX_bufTest.cpp +++ b/tests/unit/capi/GEOSGeomToHEX_bufTest.cpp @@ -78,5 +78,7 @@ void object::test<3>() GEOS_finish_r(handle); } + + } // namespace tut diff --git a/tests/unit/capi/GEOSGeomTypeIdTest.cpp b/tests/unit/capi/GEOSGeomTypeIdTest.cpp index 65eed116a1..326796a253 100644 --- a/tests/unit/capi/GEOSGeomTypeIdTest.cpp +++ b/tests/unit/capi/GEOSGeomTypeIdTest.cpp @@ -24,9 +24,12 @@ void object::test<1>() ensure(nullptr != geom1_); geom2_ = fromWKT("LINESTRING (1 2, 3 4)"); ensure(nullptr != geom2_); + geom3_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(nullptr != geom3_); - ensure_equals(0, GEOSGeomTypeId(geom1_)); - ensure_equals(1, GEOSGeomTypeId(geom2_)); + ensure_equals(GEOS_POINT, GEOSGeomTypeId(geom1_)); + ensure_equals(GEOS_LINESTRING, GEOSGeomTypeId(geom2_)); + ensure_equals(GEOS_CIRCULARSTRING, GEOSGeomTypeId(geom3_)); } } // namespace tut diff --git a/tests/unit/capi/GEOSGeomTypeTest.cpp b/tests/unit/capi/GEOSGeomTypeTest.cpp index 14ba449f67..3f88c9431f 100644 --- a/tests/unit/capi/GEOSGeomTypeTest.cpp +++ b/tests/unit/capi/GEOSGeomTypeTest.cpp @@ -29,6 +29,11 @@ void object::test<1>() char* type2 = GEOSGeomType(geom2_); ensure_equals(std::string(type2), "LineString"); GEOSFree(type2); + + geom3_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 2)"); + char* type3 = GEOSGeomType(geom3_); + ensure_equals(std::string(type3), "CircularString"); + GEOSFree(type3); } } // namespace tut diff --git a/tests/unit/capi/GEOSGeom_cloneTest.cpp b/tests/unit/capi/GEOSGeom_cloneTest.cpp index 9948d5a0f4..3466a1b958 100644 --- a/tests/unit/capi/GEOSGeom_cloneTest.cpp +++ b/tests/unit/capi/GEOSGeom_cloneTest.cpp @@ -34,6 +34,16 @@ void object::test<1> ensure(GEOSGeom_getUserData(geom2_) == nullptr); // userData not transferred } +template<> +template<> +void object::test<2> +() +{ + input_ = GEOSGeomFromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + result_ = GEOSGeom_clone(input_); + + ensure_equals(toWKT(result_), "CIRCULARSTRING (0 0, 1 1, 2 0)"); +} } // namespace tut diff --git a/tests/unit/capi/GEOSGeom_extractUniquePointsTest.cpp b/tests/unit/capi/GEOSGeom_extractUniquePointsTest.cpp index b2b531b36f..3d5c986141 100644 --- a/tests/unit/capi/GEOSGeom_extractUniquePointsTest.cpp +++ b/tests/unit/capi/GEOSGeom_extractUniquePointsTest.cpp @@ -65,6 +65,17 @@ void object::test<3> ensure_geometry_equals(geom3_, geom2_); } +template <> +template <> +void object::test<4>() { + input_ = GEOSGeomFromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_); + + result_ = GEOSGeom_extractUniquePoints(input_); + + ensure_geometry_equals(result_, "MULTIPOINT ((0 0), (1 1), (2 0))"); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSGeom_getCoordSeqTest.cpp b/tests/unit/capi/GEOSGeom_getCoordSeqTest.cpp index bd84597881..e976d8ef39 100644 --- a/tests/unit/capi/GEOSGeom_getCoordSeqTest.cpp +++ b/tests/unit/capi/GEOSGeom_getCoordSeqTest.cpp @@ -57,5 +57,19 @@ void object::test<3>() ensure_equals(y, 8); } +template<> +template<> +void object::test<4>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + const GEOSCoordSequence* seq = GEOSGeom_getCoordSeq(input_); + + ensure(seq); + + unsigned int size; + ensure(GEOSCoordSeq_getSize(seq, &size)); + ensure_equals(size, 3u); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSGeom_getCoordinateDimensionTest.cpp b/tests/unit/capi/GEOSGeom_getCoordinateDimensionTest.cpp index 8d0b0ce6fd..c3f41676be 100644 --- a/tests/unit/capi/GEOSGeom_getCoordinateDimensionTest.cpp +++ b/tests/unit/capi/GEOSGeom_getCoordinateDimensionTest.cpp @@ -21,14 +21,35 @@ template<> void object::test<1> () { - geom1_ = fromWKT("POLYGON ((0 0, 1 0, 1 1, 0 0))"); - ensure_equals(GEOSGeom_getCoordinateDimension(geom1_), 2); + input_ = fromWKT("POLYGON ((0 0, 1 0, 1 1, 0 0))"); + ensure_equals(GEOSGeom_getCoordinateDimension(input_), 2); +} + +template<> +template<> +void object::test<2> +() +{ + input_ = fromWKT("POINT (4 2 7)"); + ensure_equals(GEOSGeom_getCoordinateDimension(input_), 3); +} - geom2_ = fromWKT("POINT (4 2 7)"); - ensure_equals(GEOSGeom_getCoordinateDimension(geom2_), 3); +template<> +template<> +void object::test<3> +() +{ + input_ = fromWKT("LINESTRING (4 2 7 1, 8 2 9 5)"); + ensure_equals(GEOSGeom_getCoordinateDimension(input_), 4); +} - geom3_ = fromWKT("LINESTRING (4 2 7 1, 8 2 9 5)"); - ensure_equals(GEOSGeom_getCoordinateDimension(geom3_), 4); +template<> +template<> +void object::test<4> +() +{ + input_ = fromWKT("CIRCULARSTRING Z (0 0 0, 1 1 1, 2 0 2)"); + ensure_equals(GEOSGeom_getCoordinateDimension(input_), 3); } } // namespace tut diff --git a/tests/unit/capi/GEOSGeom_getDimensionsTest.cpp b/tests/unit/capi/GEOSGeom_getDimensionsTest.cpp index c90e3e0ae6..5050f5d691 100644 --- a/tests/unit/capi/GEOSGeom_getDimensionsTest.cpp +++ b/tests/unit/capi/GEOSGeom_getDimensionsTest.cpp @@ -31,5 +31,13 @@ void object::test<1> ensure_equals(GEOSGeom_getDimensions(geom2_), 0); } +template<> +template<> +void object::test<2>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure_equals(GEOSGeom_getDimensions(input_), 1); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSGeom_transformXYTest.cpp b/tests/unit/capi/GEOSGeom_transformXYTest.cpp index f220a47f28..b2112ea36a 100644 --- a/tests/unit/capi/GEOSGeom_transformXYTest.cpp +++ b/tests/unit/capi/GEOSGeom_transformXYTest.cpp @@ -294,4 +294,15 @@ void object::test<13>() { GEOSGeom_destroy(out); } +template <> +template <> +void object::test<14>() { + input_ = GEOSGeomFromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_); + + result_ = GEOSGeom_transformXY(input_, SCALE_2_3, nullptr); + + ensure_equals(toWKT(result_), "CIRCULARSTRING (0 0, 2 3, 4 0)"); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSGetCentroidTest.cpp b/tests/unit/capi/GEOSGetCentroidTest.cpp index 96b6334be0..f99c78f120 100644 --- a/tests/unit/capi/GEOSGetCentroidTest.cpp +++ b/tests/unit/capi/GEOSGetCentroidTest.cpp @@ -139,5 +139,16 @@ void object::test<5> } +template<> +template<> +void object::test<6>() +{ + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); + ensure(input_ != nullptr); + + result_ = GEOSGetCentroid(input_); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSGetExteriorRingTest.cpp b/tests/unit/capi/GEOSGetExteriorRingTest.cpp index 0905b2e55f..c8012f90a7 100644 --- a/tests/unit/capi/GEOSGetExteriorRingTest.cpp +++ b/tests/unit/capi/GEOSGetExteriorRingTest.cpp @@ -41,4 +41,17 @@ namespace tut ensure(GEOSGetExteriorRing(geom1_) == nullptr); } + template<> + template<> + void object::test<3>() + { + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); + ensure(input_ != nullptr); + + const GEOSGeometry* extring = GEOSGetExteriorRing(input_); + ensure(extring); + + ensure_equals(toWKT(extring), "COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0))"); + } + } // namespace tut diff --git a/tests/unit/capi/GEOSGetGeometryNTest.cpp b/tests/unit/capi/GEOSGetGeometryNTest.cpp index 92665cbc16..bb8ac8d358 100644 --- a/tests/unit/capi/GEOSGetGeometryNTest.cpp +++ b/tests/unit/capi/GEOSGetGeometryNTest.cpp @@ -23,16 +23,27 @@ void object::test<1>() geom1_ = fromWKT("MULTIPOINT ((1 1), (2 2), (3 3))"); ensure(nullptr != geom1_); - GEOSGeometry* result = const_cast(GEOSGetGeometryN(geom1_, 0)); + const GEOSGeometry* result = GEOSGetGeometryN(geom1_, 0); ensure(nullptr != result); ensure_equals("POINT (1 1)", toWKT(result)); - result = const_cast(GEOSGetGeometryN(geom1_, 2)); + result = GEOSGetGeometryN(geom1_, 2); ensure(nullptr != result); ensure_equals("POINT (3 3)", toWKT(result)); ensure(GEOSGetGeometryN(geom1_, -1) == nullptr); } +template<> +template<> +void object::test<2>() +{ + input_ = fromWKT("MULTICURVE ((0 0, 1 1), CIRCULARSTRING (1 1, 2 0, 3 1))"); + ensure(input_); + + ensure_equals(toWKT(GEOSGetGeometryN(input_, 0)), "LINESTRING (0 0, 1 1)"); + ensure_equals(toWKT(GEOSGetGeometryN(input_, 1)), "CIRCULARSTRING (1 1, 2 0, 3 1)"); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSGetInteriorRingNTest.cpp b/tests/unit/capi/GEOSGetInteriorRingNTest.cpp index d54664d909..e9b50d60e9 100644 --- a/tests/unit/capi/GEOSGetInteriorRingNTest.cpp +++ b/tests/unit/capi/GEOSGetInteriorRingNTest.cpp @@ -42,5 +42,17 @@ namespace tut ensure(GEOSGetInteriorRingN(geom1_, 0) == nullptr); } + template<> + template<> + void object::test<3>() + { + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 10 10, 20 0), (20 0, 0 0)), (8 8, 9 9, 9 8, 8 8))"); + ensure(input_ != nullptr); + + const GEOSGeometry* intring = GEOSGetInteriorRingN(input_, 0); + ensure(intring); + + ensure_equals(toWKT(intring), "LINESTRING (8 8, 9 9, 9 8, 8 8)"); + } } // namespace tut diff --git a/tests/unit/capi/GEOSGetNumInteriorRingsTest.cpp b/tests/unit/capi/GEOSGetNumInteriorRingsTest.cpp index 968868ea4f..70e647990b 100644 --- a/tests/unit/capi/GEOSGetNumInteriorRingsTest.cpp +++ b/tests/unit/capi/GEOSGetNumInteriorRingsTest.cpp @@ -38,5 +38,15 @@ void object::test<2>() GEOSGeom_destroy(input); } +template<> +template<> +void object::test<3>() +{ + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 10 10, 20 0), (20 0, 0 0)), (8 8, 9 9, 9 8, 8 8))"); + ensure(input_ != nullptr); + + ensure_equals(GEOSGetNumInteriorRings(input_), 1); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSHasZMTest.cpp b/tests/unit/capi/GEOSHasZMTest.cpp index 346ca97d8d..13ea525b96 100644 --- a/tests/unit/capi/GEOSHasZMTest.cpp +++ b/tests/unit/capi/GEOSHasZMTest.cpp @@ -106,6 +106,15 @@ void object::test<9>() ensure_equals(GEOSHasM(input_), 1); } +template<> +template<> +void object::test<10>() +{ + input_ = fromWKT("CIRCULARSTRING M (0 0 0, 1 1 1, 2 0 2)"); + + ensure_equals(GEOSHasZ(input_), 0); + ensure_equals(GEOSHasM(input_), 1); +} } // namespace tut diff --git a/tests/unit/capi/GEOSHausdorffDistanceTest.cpp b/tests/unit/capi/GEOSHausdorffDistanceTest.cpp index 3cddf2424b..ead81cd93b 100644 --- a/tests/unit/capi/GEOSHausdorffDistanceTest.cpp +++ b/tests/unit/capi/GEOSHausdorffDistanceTest.cpp @@ -63,4 +63,19 @@ void object::test<2> ensure_distance(dist, 70., 1e-12); } +template<> +template<> +void object::test<3>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 2, 2 2)"); + + ensure(geom1_); + ensure(geom2_); + + double dist; + ensure_equals("curved geometry not supported", GEOSHausdorffDistance(geom1_, geom2_, &dist), 0); + ensure_equals("curved geometry not supported", GEOSHausdorffDistance(geom2_, geom1_, &dist), 0); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSIntersectionTest.cpp b/tests/unit/capi/GEOSIntersectionTest.cpp index 94d209e267..2377c609f4 100644 --- a/tests/unit/capi/GEOSIntersectionTest.cpp +++ b/tests/unit/capi/GEOSIntersectionTest.cpp @@ -143,6 +143,19 @@ void object::test<7> ensure(!std::fetestexcept(FE_INVALID)); } +template<> +template<> +void object::test<8>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + result_ = GEOSIntersection(geom1_, geom2_); + ensure("curved geometry not supported", result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSIntersectsTest.cpp b/tests/unit/capi/GEOSIntersectsTest.cpp index bba2534458..395d2698ca 100644 --- a/tests/unit/capi/GEOSIntersectsTest.cpp +++ b/tests/unit/capi/GEOSIntersectsTest.cpp @@ -220,5 +220,19 @@ void object::test<10> ensure_equals(r2, 1); } +template<> +template<> +void object::test<11>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + ensure_equals("curved geometry not supported", GEOSIntersects(geom1_, geom2_), 2); + ensure_equals("curved geometry not supported", GEOSIntersects(geom2_, geom1_), 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSLargestEmptyCircleTest.cpp b/tests/unit/capi/GEOSLargestEmptyCircleTest.cpp index aff84e2ec9..6b0e69a869 100644 --- a/tests/unit/capi/GEOSLargestEmptyCircleTest.cpp +++ b/tests/unit/capi/GEOSLargestEmptyCircleTest.cpp @@ -65,5 +65,32 @@ void object::test<2> ensure_geometry_equals_exact(result_, expected_, 0.0001); } +template<> +template<> +void object::test<3>() +{ + input_ = fromWKT("MULTICURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (0 3, 2 3))"); + ensure(input_); + + result_ = GEOSLargestEmptyCircle(input_, nullptr, 0.001); + + ensure("curved geometries not supported", result_ == nullptr); +} + +template<> +template<> +void object::test<4>() +{ + input_ = GEOSGeomFromWKT("MULTILINESTRING ((40 90, 90 60), (90 40, 40 10))"); + geom2_ = GEOSGeomFromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING(0 100, 50 150, 100 100), (100 100, 100 0, 0 0, 0 100)))"); + ensure(input_); + ensure(geom2_); + + result_ = GEOSLargestEmptyCircle(input_, geom2_, 0.001); + + ensure("curved geometries not supported", result_ == nullptr); +} + + } // namespace tut diff --git a/tests/unit/capi/GEOSLengthTest.cpp b/tests/unit/capi/GEOSLengthTest.cpp index fa22e2a7a4..66c2872d13 100644 --- a/tests/unit/capi/GEOSLengthTest.cpp +++ b/tests/unit/capi/GEOSLengthTest.cpp @@ -58,5 +58,17 @@ void object::test<3>() ensure_equals(length, 0); } +template<> +template<> +void object::test<4>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_ != nullptr); + + double length = -1; + int ret = GEOSLength(input_, &length); + ensure_equals("error raised on curved geometry", ret, 0); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSLineMergeDirectedTest.cpp b/tests/unit/capi/GEOSLineMergeDirectedTest.cpp index 39a172ad17..bd5537d084 100644 --- a/tests/unit/capi/GEOSLineMergeDirectedTest.cpp +++ b/tests/unit/capi/GEOSLineMergeDirectedTest.cpp @@ -60,5 +60,18 @@ void object::test<2> GEOSGeom_destroy(expected); } + +template<> +template<> +void object::test<3>() +{ + input_ = fromWKT("MULTICURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 3 0))"); + ensure(input_); + + result_ = GEOSLineMergeDirected(input_); + + ensure("curved geometries not supported", result_ == nullptr); +} + } // namesplace tut diff --git a/tests/unit/capi/GEOSLineMergeTest.cpp b/tests/unit/capi/GEOSLineMergeTest.cpp index e744ed513d..d8c40c4e07 100644 --- a/tests/unit/capi/GEOSLineMergeTest.cpp +++ b/tests/unit/capi/GEOSLineMergeTest.cpp @@ -44,5 +44,17 @@ void object::test<1> GEOSGeom_destroy(expected); } +template<> +template<> +void object::test<2>() +{ + input_ = fromWKT("MULTICURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 3 0))"); + ensure(input_); + + result_ = GEOSLineMerge(input_); + + ensure("curved geometries not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSLineSubstringTest.cpp b/tests/unit/capi/GEOSLineSubstringTest.cpp index c759307929..24078c085c 100644 --- a/tests/unit/capi/GEOSLineSubstringTest.cpp +++ b/tests/unit/capi/GEOSLineSubstringTest.cpp @@ -119,6 +119,17 @@ void object::test<6> ensure_geometry_equals(result_, expected_); } +template<> +template<> +void object::test<7>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_); + + result_ = GEOSLineSubstring(input_, 0.5, 0); + + ensure("curved geometries not supported", result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSMakeValidTest.cpp b/tests/unit/capi/GEOSMakeValidTest.cpp index c28d14606c..1c6071e94f 100644 --- a/tests/unit/capi/GEOSMakeValidTest.cpp +++ b/tests/unit/capi/GEOSMakeValidTest.cpp @@ -94,5 +94,16 @@ void object::test<4> ensure(GEOSEqualsExact(geom2_, expected_, 0.01)); } +template<> +template<> +void object::test<5>() +{ + // ring outside shell + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 10 10, 20 0), (20 0, 0 0)), (10 10, 30 10, 30 30, 10 10))"); + ensure(input_); + + result_ = GEOSMakeValid(input_); + ensure("curved geometry not supported", result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSMaximumInscribedCircleTest.cpp b/tests/unit/capi/GEOSMaximumInscribedCircleTest.cpp index 60ef3f10bd..1ca8a79363 100644 --- a/tests/unit/capi/GEOSMaximumInscribedCircleTest.cpp +++ b/tests/unit/capi/GEOSMaximumInscribedCircleTest.cpp @@ -75,7 +75,16 @@ void object::test<2> // no crash } +template<> +template<> +void object::test<3>() +{ + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 10 10, 20 0), (20 0, 0 0)))"); + ensure(input_); + result_ = GEOSMaximumInscribedCircle(input_, 1); + ensure("curved geometry not supported", result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp b/tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp index 3bfd6326aa..5b7c1dff48 100644 --- a/tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp +++ b/tests/unit/capi/GEOSMinimumBoundingCircleTest.cpp @@ -116,5 +116,19 @@ void object::test<4> ensure(fabs(radius) - 5.0 < 0.001); } +template<> +template<> +void object::test<5> +() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_); + + GEOSGeometry* center; + double radius; + result_ = GEOSMinimumBoundingCircle(input_, &radius, ¢er); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSMinimumRotatedRectangleTest.cpp b/tests/unit/capi/GEOSMinimumRotatedRectangleTest.cpp index a71462d860..6782d20c2c 100644 --- a/tests/unit/capi/GEOSMinimumRotatedRectangleTest.cpp +++ b/tests/unit/capi/GEOSMinimumRotatedRectangleTest.cpp @@ -122,5 +122,16 @@ void object::test<7> "POLYGON ((1 2, 3 8, 9 6, 7 0, 1 2))"); } +template<> +template<> +void object::test<8> +() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_); + + result_ = GEOSMinimumRotatedRectangle(input_); + ensure("curved geometry not supported", result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSMinimumWidthTest.cpp b/tests/unit/capi/GEOSMinimumWidthTest.cpp index bad070c101..57f935da8f 100644 --- a/tests/unit/capi/GEOSMinimumWidthTest.cpp +++ b/tests/unit/capi/GEOSMinimumWidthTest.cpp @@ -67,4 +67,16 @@ void object::test<2> GEOSGeom_destroy(output); } +template<> +template<> +void object::test<3> +() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_); + + result_ = GEOSMinimumWidth(input_); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSNodeTest.cpp b/tests/unit/capi/GEOSNodeTest.cpp index abda612bb9..9b11e1a15b 100644 --- a/tests/unit/capi/GEOSNodeTest.cpp +++ b/tests/unit/capi/GEOSNodeTest.cpp @@ -201,5 +201,17 @@ void object::test<8> GEOSFree(wkt_expected); } +template<> +template<> +void object::test<9>() +{ + input_ = fromWKT("MULTICURVE (CIRCULARSTRING (0 0, 1 1, 2 0), CIRCULARSTRING (0 1, 1 0, 2 1))"); + ensure(input_); + + result_ = GEOSNode(input_); + + ensure("curved geometries not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSOffsetCurveTest.cpp b/tests/unit/capi/GEOSOffsetCurveTest.cpp index d5ddfb8f67..b3414dc159 100644 --- a/tests/unit/capi/GEOSOffsetCurveTest.cpp +++ b/tests/unit/capi/GEOSOffsetCurveTest.cpp @@ -259,5 +259,17 @@ void object::test<12>() ); } +template<> +template<> +void object::test<13>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_); + + result_ = GEOSOffsetCurve(input_, 1, 8, GEOSBUF_JOIN_ROUND, 0); + + ensure("curved geometries not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSOrientPolygonsTest.cpp b/tests/unit/capi/GEOSOrientPolygonsTest.cpp index 745ac1bc06..1b9a7a3d1d 100644 --- a/tests/unit/capi/GEOSOrientPolygonsTest.cpp +++ b/tests/unit/capi/GEOSOrientPolygonsTest.cpp @@ -90,5 +90,16 @@ void object::test<5>() ensure_equals(toWKT(geom1_), "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (MULTIPOLYGON (((0 0, 0 10, 10 10, 10 0, 0 0)))))"); } +template<> +template<> +void object::test<6>() +{ + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); + ensure(input_); + + auto ret = GEOSOrientPolygons(input_, 0); + ensure_equals("curved geometries not supported", ret, -1); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSOverlapsTest.cpp b/tests/unit/capi/GEOSOverlapsTest.cpp index 59cfa073ec..253bd762f3 100644 --- a/tests/unit/capi/GEOSOverlapsTest.cpp +++ b/tests/unit/capi/GEOSOverlapsTest.cpp @@ -35,5 +35,19 @@ void object::test<1>() ensure_equals(0, GEOSOverlaps(geom3_, geom2_)); } +template<> +template<> +void object::test<2>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + ensure_equals("curved geometry not supported", GEOSOverlaps(geom1_, geom2_), 2); + ensure_equals("curved geometry not supported", GEOSOverlaps(geom2_, geom1_), 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSPointOnSurfaceTest.cpp b/tests/unit/capi/GEOSPointOnSurfaceTest.cpp index 65f885e3dc..2675a3a7c6 100644 --- a/tests/unit/capi/GEOSPointOnSurfaceTest.cpp +++ b/tests/unit/capi/GEOSPointOnSurfaceTest.cpp @@ -224,5 +224,17 @@ void object::test<9> ensure_equals(std::string(wkt_), std::string("POINT (182755.892 141812.8789)")); } +template<> +template<> +void object::test<10> +() +{ + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE(CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0)))"); + ensure(input_); + + result_ = GEOSPointOnSurface(input_); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSPolygonHullSimplifyTest.cpp b/tests/unit/capi/GEOSPolygonHullSimplifyTest.cpp index af98ebb300..43f426e046 100644 --- a/tests/unit/capi/GEOSPolygonHullSimplifyTest.cpp +++ b/tests/unit/capi/GEOSPolygonHullSimplifyTest.cpp @@ -93,6 +93,16 @@ void object::test<5>() ensure_geometry_equals(geom1_, expected_); } +template<> +template<> +void object::test<6>() +{ + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 1 0.0001, 0 0)))"); + ensure(input_); + + result_ = GEOSPolygonHullSimplify(input_, 1, 0.8); + ensure("curved geometries not supported", result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSPolygonizeTest.cpp b/tests/unit/capi/GEOSPolygonizeTest.cpp index 695db3757c..c0c2eb5764 100644 --- a/tests/unit/capi/GEOSPolygonizeTest.cpp +++ b/tests/unit/capi/GEOSPolygonizeTest.cpp @@ -187,5 +187,28 @@ void object::test<6> GEOSGeom_destroy(expected_invalidRings); } +template<> +template<> +void object::test<7> +() +{ + constexpr int size = 2; + GEOSGeometry* geoms[size]; + geoms[0] = GEOSGeomFromWKT("LINESTRING (0 0, 2 0)"); + geoms[1] = GEOSGeomFromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + + for (auto& geom : geoms) { + ensure(geom != nullptr); + } + + GEOSGeometry* g = GEOSPolygonize(geoms, size); + + ensure("curved geometries not supported", g == nullptr); + + for(auto& input : geoms) { + GEOSGeom_destroy(input); + } +} + } // namespace tut diff --git a/tests/unit/capi/GEOSPreparedGeometryTest.cpp b/tests/unit/capi/GEOSPreparedGeometryTest.cpp index 5d324d55c5..4ee90cea51 100644 --- a/tests/unit/capi/GEOSPreparedGeometryTest.cpp +++ b/tests/unit/capi/GEOSPreparedGeometryTest.cpp @@ -441,5 +441,17 @@ void object::test<15> } +template<> +template<> +void object::test<16>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + + ensure(geom1_); + + prepGeom1_ = GEOSPrepare(geom1_); + ensure("curved geometries not supported", prepGeom1_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSRelatePatternTest.cpp b/tests/unit/capi/GEOSRelatePatternTest.cpp index ef8e24f6ec..1b6068749f 100644 --- a/tests/unit/capi/GEOSRelatePatternTest.cpp +++ b/tests/unit/capi/GEOSRelatePatternTest.cpp @@ -31,5 +31,19 @@ void object::test<1>() ensure_equals(1, GEOSRelatePattern(geom1_, geom3_, "*FF*FF212")); } +template<> +template<> +void object::test<11>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + ensure_equals("curved geometry not supported", GEOSRelatePattern(geom1_, geom2_, "0********"), 2); + ensure_equals("curved geometry not supported", GEOSRelatePattern(geom2_, geom1_, "0********"), 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSRelateTest.cpp b/tests/unit/capi/GEOSRelateTest.cpp index 336c41f1e9..f531ee4ab3 100644 --- a/tests/unit/capi/GEOSRelateTest.cpp +++ b/tests/unit/capi/GEOSRelateTest.cpp @@ -30,5 +30,19 @@ void object::test<1>() GEOSFree(pattern); } +template<> +template<> +void object::test<2>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + ensure("curved geometry not supported", GEOSRelate(geom1_, geom2_) == nullptr); + ensure("curved geometry not supported", GEOSRelate(geom2_, geom1_) == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSRemoveRepeatedPointsTest.cpp b/tests/unit/capi/GEOSRemoveRepeatedPointsTest.cpp index 8d6c9c2c6a..1a0780b575 100644 --- a/tests/unit/capi/GEOSRemoveRepeatedPointsTest.cpp +++ b/tests/unit/capi/GEOSRemoveRepeatedPointsTest.cpp @@ -55,5 +55,16 @@ void object::test<2> ensure(result_ == nullptr); } +template<> +template<> +void object::test<3>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0, 2 0, 2 0)"); + ensure(input_); + + result_ = GEOSRemoveRepeatedPoints(input_, 0); + + ensure("curved geometry not supported", result_ == nullptr); +} } // namespace tut diff --git a/tests/unit/capi/GEOSReverseTest.cpp b/tests/unit/capi/GEOSReverseTest.cpp index 5f5b8020d1..fae65d95f7 100644 --- a/tests/unit/capi/GEOSReverseTest.cpp +++ b/tests/unit/capi/GEOSReverseTest.cpp @@ -123,4 +123,12 @@ void object::test<8> testReverse("GEOMETRYCOLLECTION EMPTY", "GEOMETRYCOLLECTION EMPTY"); } +template<> +template<> +void object::test<9>() +{ + testReverse("CIRCULARSTRING (0 0, 1 1, 2 0)", + "CIRCULARSTRING (2 0, 1 1, 0 0)"); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSSharedPathsTest.cpp b/tests/unit/capi/GEOSSharedPathsTest.cpp index 0c16640bc8..db3b24fb9c 100644 --- a/tests/unit/capi/GEOSSharedPathsTest.cpp +++ b/tests/unit/capi/GEOSSharedPathsTest.cpp @@ -88,5 +88,19 @@ void object::test<3> ensure(!geom3_); } +template<> +template<> +void object::test<4>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + result_ = GEOSSharedPaths(geom1_, geom2_); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSSimplifyTest.cpp b/tests/unit/capi/GEOSSimplifyTest.cpp index 1067bbb770..407589b8bb 100644 --- a/tests/unit/capi/GEOSSimplifyTest.cpp +++ b/tests/unit/capi/GEOSSimplifyTest.cpp @@ -44,5 +44,16 @@ void object::test<1> ensure(0 != GEOSisEmpty(geom2_)); } +template<> +template<> +void object::test<2>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_ != nullptr); + + result_ = GEOSSimplify(input_, 2); + ensure(result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSSnapTest.cpp b/tests/unit/capi/GEOSSnapTest.cpp index 17d9da8f26..fba18f8e8c 100644 --- a/tests/unit/capi/GEOSSnapTest.cpp +++ b/tests/unit/capi/GEOSSnapTest.cpp @@ -209,5 +209,19 @@ void object::test<10> ensure_equals(out, "LINESTRING (-71.1257 42.2703, -71.1261 42.2703, -71.1261 42.2702, -71.1317 42.2509)"); } +template<> +template<> +void object::test<11>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 1.0001, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + result_ = GEOSSnap(geom1_, geom2_, 0.1); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSSymDifferenceTest.cpp b/tests/unit/capi/GEOSSymDifferenceTest.cpp index 34d2c07947..def092b684 100644 --- a/tests/unit/capi/GEOSSymDifferenceTest.cpp +++ b/tests/unit/capi/GEOSSymDifferenceTest.cpp @@ -29,5 +29,19 @@ void object::test<1>() ensure_equals("MULTILINESTRING ((50 150, 50 200), (50 50, 50 100))", toWKT(geom3_)); } +template<> +template<> +void object::test<2>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + result_ = GEOSSymDifference(geom1_, geom2_); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSTopologyPreserveSimplifyTest.cpp b/tests/unit/capi/GEOSTopologyPreserveSimplifyTest.cpp index 2df27d1a9b..ca84ed8992 100644 --- a/tests/unit/capi/GEOSTopologyPreserveSimplifyTest.cpp +++ b/tests/unit/capi/GEOSTopologyPreserveSimplifyTest.cpp @@ -52,5 +52,16 @@ void object::test<2> ensure_geometry_equals(result_, expected_); } +template<> +template<> +void object::test<3>() +{ + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 10 10, 20 0), (20 0, 10 0, 0 0)), (10 1, 11 1, 11 2, 10 1))"); + ensure(input_); + + result_ = GEOSTopologyPreserveSimplify(input_, 0.2); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSTouchesTest.cpp b/tests/unit/capi/GEOSTouchesTest.cpp index 79de79f629..df6722d5fb 100644 --- a/tests/unit/capi/GEOSTouchesTest.cpp +++ b/tests/unit/capi/GEOSTouchesTest.cpp @@ -35,5 +35,19 @@ void object::test<1>() ensure_equals(0, GEOSTouches(geom3_, geom2_)); } +template<> +template<> +void object::test<2>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + ensure_equals("curved geometry not supported", GEOSTouches(geom1_, geom2_), 2); + ensure_equals("curved geometry not supported", GEOSTouches(geom2_, geom1_), 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSUnaryUnionTest.cpp b/tests/unit/capi/GEOSUnaryUnionTest.cpp index e164bbd39e..b33b07a2f3 100644 --- a/tests/unit/capi/GEOSUnaryUnionTest.cpp +++ b/tests/unit/capi/GEOSUnaryUnionTest.cpp @@ -218,6 +218,18 @@ void object::test<11> ensure_geometry_equals(result_, expected_); } +template<> +template<> +void object::test<12>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + + ensure(input_); + + result_ = GEOSUnaryUnion(input_); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSUnionTest.cpp b/tests/unit/capi/GEOSUnionTest.cpp index 1053ec7a6d..68c90867b9 100644 --- a/tests/unit/capi/GEOSUnionTest.cpp +++ b/tests/unit/capi/GEOSUnionTest.cpp @@ -62,5 +62,19 @@ void object::test<2>() (void) result; // no crash } +template<> +template<> +void object::test<3>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 1)"); + + ensure(geom1_); + ensure(geom2_); + + result_ = GEOSUnion(geom1_, geom2_); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSVoronoiDiagramTest.cpp b/tests/unit/capi/GEOSVoronoiDiagramTest.cpp index c8843ac230..b379a49d04 100644 --- a/tests/unit/capi/GEOSVoronoiDiagramTest.cpp +++ b/tests/unit/capi/GEOSVoronoiDiagramTest.cpp @@ -175,4 +175,16 @@ void object::test<8> check_voronoi_ordered("LINESTRING (1 1, 2 2, 3 3)"); } +template<> +template<> +void object::test<9> +() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_); + + result_ = GEOSVoronoiDiagram(input_, nullptr, 0, 0); + ensure("curved geometry not supported", result_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSWKBWriterTest.cpp b/tests/unit/capi/GEOSWKBWriterTest.cpp index 2f3e041d66..1bb95c8081 100644 --- a/tests/unit/capi/GEOSWKBWriterTest.cpp +++ b/tests/unit/capi/GEOSWKBWriterTest.cpp @@ -186,5 +186,18 @@ void object::test<9>() ensure_equals(hexstr, "010100008000000000000008400000000000002040000000000000F03F"); } +template<> +template<> +void object::test<10> +() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_); + + std::size_t hex_size = 0; + buf_ = GEOSWKBWriter_writeHEX(wkbwriter_, input_, &hex_size); + ensure("curved geometry not supported", buf_ == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSWithinTest.cpp b/tests/unit/capi/GEOSWithinTest.cpp index c6ee8ede01..703a9b86e6 100644 --- a/tests/unit/capi/GEOSWithinTest.cpp +++ b/tests/unit/capi/GEOSWithinTest.cpp @@ -88,5 +88,18 @@ void object::test<3> ensure_equals(int(r2), 1); } +template<> +template<> +void object::test<4>() +{ + geom1_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + geom2_ = fromWKT("LINESTRING (1 0, 2 0)"); + + ensure(geom1_); + ensure(geom2_); + + ensure_equals("curved geometry not supported", GEOSWithin(geom2_, geom1_), 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSisClosedTest.cpp b/tests/unit/capi/GEOSisClosedTest.cpp index c4becfdd79..656ec1ab15 100644 --- a/tests/unit/capi/GEOSisClosedTest.cpp +++ b/tests/unit/capi/GEOSisClosedTest.cpp @@ -36,7 +36,7 @@ void object::test<1> () { geom1_ = GEOSGeomFromWKT("LINESTRING(0 0, 1 0, 1 1)"); - int r = GEOSisClosed(geom1_); + char r = GEOSisClosed(geom1_); ensure_equals(r, 0); } @@ -46,7 +46,7 @@ void object::test<2> () { geom1_ = GEOSGeomFromWKT("LINESTRING(0 0, 0 1, 1 1, 0 0)"); - int r = GEOSisClosed(geom1_); + char r = GEOSisClosed(geom1_); ensure_equals(r, 1); } @@ -56,7 +56,7 @@ void object::test<3> () { geom1_ = GEOSGeomFromWKT("MULTILINESTRING ((1 1, 1 2, 2 2, 1 1), (0 0, 0 1, 1 1))"); - int r = GEOSisClosed(geom1_); + char r = GEOSisClosed(geom1_); ensure_equals(r, 0); } @@ -66,7 +66,7 @@ void object::test<4> () { geom1_ = GEOSGeomFromWKT("MULTILINESTRING ((1 1, 1 2, 2 2, 1 1), (0 0, 0 1, 1 1, 0 0))"); - int r = GEOSisClosed(geom1_); + char r = GEOSisClosed(geom1_); ensure_equals(r, 1); } @@ -76,8 +76,20 @@ void object::test<5> () { geom1_ = GEOSGeomFromWKT("POINT (1 1)"); - int r = GEOSisClosed(geom1_); + char r = GEOSisClosed(geom1_); ensure_equals(r, 2); } +template<> +template<> +void object::test<6> +() +{ + geom1_ = GEOSGeomFromWKT("COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0))"); + ensure(geom1_); + + char r = GEOSisClosed(geom1_); + ensure_equals(r, 1); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSisRingTest.cpp b/tests/unit/capi/GEOSisRingTest.cpp index 7337cd83eb..1d0b6da476 100644 --- a/tests/unit/capi/GEOSisRingTest.cpp +++ b/tests/unit/capi/GEOSisRingTest.cpp @@ -36,7 +36,7 @@ void object::test<1> () { geom1_ = GEOSGeomFromWKT("LINESTRING(0 0, 1 0, 1 1)"); - int r = GEOSisRing(geom1_); + char r = GEOSisRing(geom1_); ensure_equals(r, 0); } @@ -46,7 +46,7 @@ void object::test<2> () { geom1_ = GEOSGeomFromWKT("LINESTRING (0 0, 1 0, 1 1, 0 0)"); - int r = GEOSisRing(geom1_); + char r = GEOSisRing(geom1_); ensure_equals(r, 1); } @@ -56,7 +56,7 @@ void object::test<3> () { geom1_ = GEOSGeomFromWKT("POINT (1 1)"); - int r = GEOSisRing(geom1_); + char r = GEOSisRing(geom1_); ensure_equals(r, 0); } @@ -66,7 +66,7 @@ void object::test<4> () { geom1_ = GEOSGeomFromWKT("MULTILINESTRING ((0 0, 1 0, 1 1, 0 0))"); - int r = GEOSisRing(geom1_); + char r = GEOSisRing(geom1_); ensure_equals(r, 0); } @@ -76,9 +76,21 @@ void object::test<5> () { geom1_ = GEOSGeomFromWKT("LINESTRING EMPTY"); - int r = GEOSisRing(geom1_); + char r = GEOSisRing(geom1_); ensure_equals(r, 0); } +template<> +template<> +void object::test<6> +() +{ + geom1_ = GEOSGeomFromWKT("COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 0 0))"); + ensure(geom1_); + + char r = GEOSisRing(geom1_); + ensure_equals("curved geometetries not supported", r, 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSisSimpleTest.cpp b/tests/unit/capi/GEOSisSimpleTest.cpp index b25930dd95..762bacdb8b 100644 --- a/tests/unit/capi/GEOSisSimpleTest.cpp +++ b/tests/unit/capi/GEOSisSimpleTest.cpp @@ -32,4 +32,15 @@ void object::test<2>() ensure_equals(0, GEOSisSimple(input_)); } +template<> +template<> +void object::test<3>() +{ + input_ = fromWKT("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure(input_ != nullptr); + + char ret = GEOSisSimple(input_); + ensure_equals("error raised on curved geometry", ret, 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSisValidDetailTest.cpp b/tests/unit/capi/GEOSisValidDetailTest.cpp index cafa342f60..56d005268e 100644 --- a/tests/unit/capi/GEOSisValidDetailTest.cpp +++ b/tests/unit/capi/GEOSisValidDetailTest.cpp @@ -144,5 +144,16 @@ void object::test<6> ensure_equals(r, 0); // invalid } +template<> +template<> +void object::test<7>() +{ + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 1 1)))"); + ensure(input_ != nullptr); + + char ret = GEOSisValidDetail(input_, 0, nullptr, nullptr); + ensure_equals("error raised on curved geometry", ret, 2); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSisValidReasonTest.cpp b/tests/unit/capi/GEOSisValidReasonTest.cpp index d253b7bfb9..fbd988e59a 100644 --- a/tests/unit/capi/GEOSisValidReasonTest.cpp +++ b/tests/unit/capi/GEOSisValidReasonTest.cpp @@ -57,6 +57,17 @@ void object::test<3>() GEOSFree(reason); } +template<> +template<> +void object::test<7>() +{ + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 1 1)))"); + ensure(input_ != nullptr); + + char* reason = GEOSisValidReason(input_); + ensure(reason == nullptr); +} + } // namespace tut diff --git a/tests/unit/capi/GEOSisValidTest.cpp b/tests/unit/capi/GEOSisValidTest.cpp index a99684b79f..a71fc51501 100644 --- a/tests/unit/capi/GEOSisValidTest.cpp +++ b/tests/unit/capi/GEOSisValidTest.cpp @@ -64,4 +64,15 @@ void object::test<3>() ensure_equals(2, isvalid); } +template<> +template<> +void object::test<5>() +{ + input_ = fromWKT("CURVEPOLYGON (COMPOUNDCURVE( CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 1 1)))"); + ensure(input_ != nullptr); + + char ret = GEOSisValid(input_); + ensure_equals("error raised on curved geometry", ret, 2); +} + } // namespace tut diff --git a/tests/unit/capi/capi_test_utils.h b/tests/unit/capi/capi_test_utils.h index d6a9dc668e..ef6f6c07bb 100644 --- a/tests/unit/capi/capi_test_utils.h +++ b/tests/unit/capi/capi_test_utils.h @@ -82,7 +82,7 @@ namespace capitest { } std::string - toWKT(GEOSGeometry* g) + toWKT(const GEOSGeometry* g) { char* wkt = GEOSWKTWriter_write(wktw_, g); std::string ret(wkt); diff --git a/tests/unit/geom/CircularStringTest.cpp b/tests/unit/geom/CircularStringTest.cpp new file mode 100644 index 0000000000..8e1eaf4f3e --- /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); + geos::geom::Envelope expected(0, 4, -1, 1); + ensure("getEnvelopeInternal", cs_->getEnvelopeInternal()->equals(&expected)); + + // 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..ae232f5caa --- /dev/null +++ b/tests/unit/geom/CompoundCurveTest.cpp @@ -0,0 +1,319 @@ +#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? + geos::geom::Envelope expected(0, 2, 0, 2); + ensure("getEnvelopeInternal", cc_->getEnvelopeInternal()->equals(&expected)); + + // 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..0025235d9d --- /dev/null +++ b/tests/unit/geom/CurvePolygonTest.cpp @@ -0,0 +1,186 @@ +#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); + { + geos::geom::Envelope expected(0, 4, -0.618033988749895, 5); + const geos::geom::Envelope& actual = *cp_->getEnvelopeInternal(); + + ensure_equals("getEnvelopeInternal MinX", actual.getMinX(), expected.getMinX()); + ensure_equals("getEnvelopeInternal MinY", actual.getMinY(), expected.getMinY()); + ensure_equals("getEnvelopeInternal MaxX", actual.getMaxX(), expected.getMaxX()); + ensure_equals("getEnvelopeInternal MaxY", actual.getMaxY(), expected.getMaxY()); + } + + // 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 59e5f1b252..8873141f9e 100644 --- a/tests/unit/io/WKTReaderTest.cpp +++ b/tests/unit/io/WKTReaderTest.cpp @@ -473,4 +473,96 @@ void object::test<24> ensure_equals(geom->getNumGeometries(), 3u); } +// Read a CircularString +template<> +template<> +void object::test<25> +() +{ + auto geom = wktreader.read("CIRCULARSTRING (0 0, 1 1, 2 0)"); + + ensure_equals(geom->getGeometryTypeId(), geos::geom::GEOS_CIRCULARSTRING); + ensure_equals(geom->getNumPoints(), 3u); +} + +// Read a CompoundCurve +template<> +template<> +void object::test<26> +() +{ + auto geom = wktreader.read("COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 1 0), (1 0, 0 1))"); + + ensure_equals(geom->getGeometryTypeId(), geos::geom::GEOS_COMPOUNDCURVE); + ensure_equals(geom->getNumPoints(), 5u); + + // explicit form + auto geom2 = wktreader.read("COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 1 0), LINESTRING (1 0, 0 1))"); + ensure(geom->equalsIdentical(geom2.get())); +} + +// Read a CurvePolygon whose components are simple curves +template<> +template<> +void object::test<27> +() +{ + auto geom = wktreader.read("CURVEPOLYGON( CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 3 3, 3 1, 1 1) )"); + + ensure_equals(geom->getGeometryTypeId(), geos::geom::GEOS_CURVEPOLYGON); +} + +// Read a CurvePolygon whose components include compound curves +template<> +template<> +void object::test<28> +() +{ + auto geom = wktreader.read("CURVEPOLYGON( COMPOUNDCURVE( CIRCULARSTRING(0 0,2 0, 2 1, 2 3, 4 3), (4 3, 4 5, 1 4, 0 0)), CIRCULARSTRING(1.7 1, 1.4 0.4, 1.6 0.4, 1.6 0.5, 1.7 1) )"); + + ensure_equals(geom->getGeometryTypeId(), geos::geom::GEOS_CURVEPOLYGON); +} + +// Read a MultiCurve +template<> +template<> +void object::test<29> +() +{ + auto geom = wktreader.read("MULTICURVE( (0 0, 5 5), CIRCULARSTRING(4 0, 4 4, 8 4))"); + + ensure_equals(geom->getGeometryTypeId(), geos::geom::GEOS_MULTICURVE); + + // explicit form + auto geom2 = wktreader.read("MULTICURVE( LINESTRING(0 0, 5 5), CIRCULARSTRING(4 0, 4 4, 8 4))"); + ensure(geom->equalsIdentical(geom2.get())); +} + +// Read a MultiCurve whose elements contain CompoundCurves +template<> +template<> +void object::test<30> +() +{ + auto geom = wktreader.read("MULTICURVE( (0 0, 5 5), COMPOUNDCURVE( (0 0, 1 1), CIRCULARSTRING (0 0, 1 1, 2 0)), CIRCULARSTRING(4 0, 4 4, 8 4))"); + + ensure_equals(geom->getGeometryTypeId(), geos::geom::GEOS_MULTICURVE); +} + +// Read a MultiSurface +template<> +template<> +void object::test<31> +() +{ + auto geom = wktreader.read("MULTISURFACE( CURVEPOLYGON( CIRCULARSTRING( 0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 3 3, 3 1, 1 1)), ((10 10, 14 12, 11 10, 10 10), (11 11, 11.5 11, 11 11.5, 11 11)))"); + + ensure_equals(geom->getGeometryTypeId(), geos::geom::GEOS_MULTISURFACE); + + // explicit form + auto geom2 = wktreader.read("MULTISURFACE( CURVEPOLYGON( CIRCULARSTRING( 0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 3 3, 3 1, 1 1)), POLYGON ((10 10, 14 12, 11 10, 10 10), (11 11, 11.5 11, 11 11.5, 11 11)))"); + ensure(geom->equalsIdentical(geom2.get())); +} + + } // namespace tut diff --git a/tests/unit/io/WKTWriterTest.cpp b/tests/unit/io/WKTWriterTest.cpp index 7dbb7379d5..8618f8be03 100644 --- a/tests/unit/io/WKTWriterTest.cpp +++ b/tests/unit/io/WKTWriterTest.cpp @@ -6,6 +6,11 @@ // geos #include #include +#include +#include +#include +#include +#include #include #include #include @@ -416,7 +421,8 @@ void object::test<15> // https://github.com/libgeos/geos/issues/888 std::vector variants0{ "MULTIPOINT EMPTY", "MULTILINESTRING EMPTY", - "MULTIPOLYGON EMPTY", "GEOMETRYCOLLECTION EMPTY" + "MULTIPOLYGON EMPTY", "GEOMETRYCOLLECTION EMPTY", + "MULTICURVE EMPTY", "MULTISURFACE EMPTY" }; for (const auto& wkt : variants0) { const auto g = wktreader.read(wkt); @@ -435,7 +441,15 @@ void object::test<15> "GEOMETRYCOLLECTION (MULTIPOINT EMPTY)", "GEOMETRYCOLLECTION Z (POINT Z EMPTY)", "GEOMETRYCOLLECTION M (LINESTRING M EMPTY)", - "GEOMETRYCOLLECTION ZM (POLYGON ZM EMPTY)" + "GEOMETRYCOLLECTION ZM (POLYGON ZM EMPTY)", + "MULTICURVE (EMPTY)", "MULTICURVE Z (EMPTY)", + "MULTICURVE M (EMPTY)", "MULTICURVE ZM (EMPTY)", + "MULTICURVE (CIRCULARSTRING EMPTY)", "MULTICURVE Z (CIRCULARSTRING Z EMPTY)", + "MULTICURVE M (CIRCULARSTRING M EMPTY)", "MULTICURVE ZM (CIRCULARSTRING ZM EMPTY)", + "MULTISURFACE (EMPTY)", "MULTISURFACE Z (EMPTY)", + "MULTISURFACE M (EMPTY)", "MULTISURFACE ZM (EMPTY)", + "MULTISURFACE (EMPTY)", "MULTISURFACE Z (CURVEPOLYGON Z EMPTY)", + "MULTISURFACE M (CURVEPOLYGON M EMPTY)", "MULTISURFACE ZM (CURVEPOLYGON ZM EMPTY)", }; for (const auto& wkt : variants1) { const auto g = wktreader.read(wkt); @@ -454,7 +468,11 @@ void object::test<15> "GEOMETRYCOLLECTION (POLYGON EMPTY, LINESTRING EMPTY)", "GEOMETRYCOLLECTION Z (LINESTRING Z EMPTY, POINT Z EMPTY)", "GEOMETRYCOLLECTION M (POINT M EMPTY, LINESTRING M EMPTY)", - "GEOMETRYCOLLECTION ZM (POINT ZM EMPTY, LINESTRING ZM EMPTY)" + "GEOMETRYCOLLECTION ZM (POINT ZM EMPTY, LINESTRING ZM EMPTY)", + "MULTICURVE (EMPTY, CIRCULARSTRING EMPTY)", "MULTICURVE Z (EMPTY, CIRCULARSTRING Z EMPTY)", + "MULTICURVE M (EMPTY, CIRCULARSTRING M EMPTY)", "MULTICURVE ZM (EMPTY, CIRCULARSTRING ZM EMPTY)", + "MULTISURFACE (EMPTY, EMPTY)", "MULTISURFACE Z (EMPTY, CURVEPOLYGON Z EMPTY)", + "MULTISURFACE M (EMPTY, CURVEPOLYGON M EMPTY)", "MULTISURFACE ZM (EMPTY, CURVEPOLYGON ZM EMPTY)", }; for (const auto& wkt : variants2) { const auto g = wktreader.read(wkt); @@ -575,4 +593,227 @@ void object::test<16> } +// test CircularString +template<> +template<> +void object::test<17>() +{ + CoordinateSequence seq{ + CoordinateXY(0, 0), + CoordinateXY(1, 1), + CoordinateXY(2, 0) + }; + auto geom = gf->createCircularString(std::move(seq)); + + ensure_equals(wktwriter.write(*geom), "CIRCULARSTRING (0 0, 1 1, 2 0)"); +} + +// test CompoundCurve +template<> +template<> +void object::test<18>() +{ + std::vector> curves; + + curves.emplace_back(gf->createCircularString({ + CoordinateXY(0, 0), + CoordinateXY(1, 1), + CoordinateXY(2, 0) + })); + + curves.emplace_back(gf->createLineString({ + CoordinateXY(2, 0), + CoordinateXY(2, 2) + })); + + auto geom = gf->createCompoundCurve(std::move(curves)); + + ensure_equals(wktwriter.write(*geom), "COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 2 2))"); +} + +// test CurvePolygon +template<> +template<> +void object::test<19>() +{ + std::vector> holes; + + std::vector> shell_sections; + shell_sections.emplace_back( + gf->createCircularString({ + CoordinateXY(0, 0), + CoordinateXY(2, 0), + CoordinateXY(2, 1), + CoordinateXY(2, 3), + CoordinateXY(4, 3) + })); + shell_sections.emplace_back( + gf->createLineString({ + CoordinateXY(4, 3), + CoordinateXY(4, 5), + CoordinateXY(1, 4), + CoordinateXY(0, 0) + })); + + auto shell = gf->createCompoundCurve(std::move(shell_sections)); + + holes.emplace_back(gf->createCircularString({ + CoordinateXY(1.7, 1), + CoordinateXY(1.4, 0.4), + CoordinateXY(1.6, 0.4), + CoordinateXY(1.6, 0.5), + CoordinateXY(1.7, 1) + })); + + auto geom = gf->createCurvePolygon(std::move(shell), std::move(holes)); + + ensure_equals(wktwriter.write(*geom), "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 2 0, 2 1, 2 3, 4 3), (4 3, 4 5, 1 4, 0 0)), CIRCULARSTRING (1.7 1, 1.4 0.4, 1.6 0.4, 1.6 0.5, 1.7 1))"); +} + +// test MultiCurve +template<> +template<> +void object::test<20>() +{ + std::vector> curves; + + // Add a CompoundCurve + std::vector> cc_sections; + cc_sections.emplace_back( + gf->createCircularString({ + CoordinateXY(0, 0), + CoordinateXY(2, 0), + CoordinateXY(2, 1), + CoordinateXY(2, 3), + CoordinateXY(4, 3) + })); + cc_sections.emplace_back( + gf->createLineString({ + CoordinateXY(4, 3), + CoordinateXY(4, 5), + CoordinateXY(1, 4), + CoordinateXY(0, 0) + })); + + curves.emplace_back(gf->createCompoundCurve(std::move(cc_sections))); + + // Add a LineString + curves.emplace_back(gf->createLineString({CoordinateXY(8, 9), CoordinateXY(10, 11)})); + + // Add a CircularString + curves.emplace_back(gf->createCircularString({ + CoordinateXY(1.7, 1), + CoordinateXY(1.4, 0.4), + CoordinateXY(1.6, 0.4), + CoordinateXY(1.6, 0.5), + CoordinateXY(1.7, 1) + })); + + auto geom = gf->createMultiCurve(std::move(curves)); + + ensure_equals(wktwriter.write(*geom), "MULTICURVE (COMPOUNDCURVE (CIRCULARSTRING (0 0, 2 0, 2 1, 2 3, 4 3), (4 3, 4 5, 1 4, 0 0)), (8 9, 10 11), CIRCULARSTRING (1.7 1, 1.4 0.4, 1.6 0.4, 1.6 0.5, 1.7 1))"); +} + +// test MultiSurface +template<> +template<> +void object::test<21>() +{ + std::vector> surfaces; + + surfaces.emplace_back( + gf->createPolygon( + gf->createLinearRing({ + CoordinateXY(0, 0), + CoordinateXY(1, 0), + CoordinateXY(1, 1), + CoordinateXY(0, 1), + CoordinateXY(0, 0) + }))); + + surfaces.emplace_back( + gf->createCurvePolygon( + gf->createCircularString({ + CoordinateXY(10, 10), + CoordinateXY(11, 11), + CoordinateXY(12, 10), + CoordinateXY(11, 9), + CoordinateXY(10, 10) + }))); + + auto geom = gf->createMultiSurface(std::move(surfaces)); + + ensure_equals(wktwriter.write(*geom), "MULTISURFACE (((0 0, 1 0, 1 1, 0 1, 0 0)), CURVEPOLYGON (CIRCULARSTRING (10 10, 11 11, 12 10, 11 9, 10 10)))"); +} + +// test formatted output +template<> +template<> +void object::test<22>() +{ + auto geom = wktreader.read("POINT (1 1)"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "POINT (1 1)"); + + geom = wktreader.read("LINESTRING (1 2, 3 4)"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "LINESTRING (1 2, 3 4)"); + + geom = wktreader.read("LINEARRING (0 0, 1 0, 1 1, 0 0)"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "LINEARRING (0 0, 1 0, 1 1, 0 0)"); + + geom = wktreader.read("CIRCULARSTRING (0 0, 1 1, 2 0)"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "CIRCULARSTRING (0 0, 1 1, 2 0)"); + + geom = wktreader.read("COMPOUNDCURVE((0 10, 0 5), CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 3 0))"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "COMPOUNDCURVE ((0 10, 0 5), \n" + " CIRCULARSTRING (0 0, 1 1, 2 0), \n" + " (2 0, 3 0))"); + + geom = wktreader.read("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1), (3 3, 3 4, 4 4, 4 3, 3 3))"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), \n" + " (1 1, 1 2, 2 2, 2 1, 1 1), \n" + " (3 3, 3 4, 4 4, 4 3, 3 3))"); + + geom = wktreader.read("CURVEPOLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1), CIRCULARSTRING (3 3, 3 4, 5 3, 3 2, 3 3))"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "CURVEPOLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), \n" + " (1 1, 1 2, 2 2, 2 1, 1 1), \n" + " CIRCULARSTRING (3 3, 3 4, 5 3, 3 2, 3 3))"); + + geom = wktreader.read("MULTIPOINT ((0 0), (1 1), (2 2))"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "MULTIPOINT ((0 0), (1 1), (2 2))"); + + geom = wktreader.read("MULTILINESTRING ((0 0, 1 1), (2 2, 3 3), (4 4, 5 5))"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "MULTILINESTRING ((0 0, 1 1), \n" + " (2 2, 3 3), \n" + " (4 4, 5 5))"); + + geom = wktreader.read("MULTICURVE ((0 0, 1 1), COMPOUNDCURVE ((2 2, 3 3), CIRCULARSTRING (4 4, 5 5, 6 4), (6 4, 7 4)), (100 100, 200 200))"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "MULTICURVE ((0 0, 1 1), \n" + " COMPOUNDCURVE ((2 2, 3 3), \n" + " CIRCULARSTRING (4 4, 5 5, 6 4), \n" + " (6 4, 7 4)), \n" + " (100 100, 200 200))"); + + geom = wktreader.read("MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1), (3 3, 3 4, 4 4, 4 3, 3 3)), ((100 100, 200 100, 200 200, 100 100)))"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), \n" + " (1 1, 1 2, 2 2, 2 1, 1 1), \n" + " (3 3, 3 4, 4 4, 4 3, 3 3)), \n" + " ((100 100, 200 100, 200 200, 100 100)))"); + + geom = wktreader.read("MULTISURFACE (CURVEPOLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1), CIRCULARSTRING (3 3, 3 4, 5 3, 3 2, 3 3)), ((100 100, 200 100, 200 200, 100 100)))"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "MULTISURFACE (CURVEPOLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), \n" + " (1 1, 1 2, 2 2, 2 1, 1 1), \n" + " CIRCULARSTRING (3 3, 3 4, 5 3, 3 2, 3 3)), \n" + " ((100 100, 200 100, 200 200, 100 100)))"); + + geom = wktreader.read("GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1), (3 3, 3 4, 4 4, 4 3, 3 3)), ((100 100, 200 100, 200 200, 100 100))), POINT (2 2))"); + ensure_equals(wktwriter.writeFormatted(geom.get()), "GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), \n" + " MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), \n" + " (1 1, 1 2, 2 2, 2 1, 1 1), \n" + " (3 3, 3 4, 4 4, 4 3, 3 3)), \n" + " ((100 100, 200 100, 200 200, 100 100))), \n" + " POINT (2 2))"); +} + + + } // namespace tut