From 61152e4b065b6267882c1ee6a39f180187a92a27 Mon Sep 17 00:00:00 2001 From: Daniel Baston Date: Sun, 18 Feb 2024 21:51:45 -0500 Subject: [PATCH] WKTWriter: Support curved types --- include/geos/geom/GeometryFactory.h | 11 +- include/geos/io/WKTReader.h | 7 +- include/geos/io/WKTWriter.h | 57 ++++--- include/geos/util/string.h | 4 +- src/geom/GeometryFactory.cpp | 67 +++++++- src/io/WKTReader.cpp | 21 ++- src/io/WKTWriter.cpp | 204 ++++++++++++++--------- src/util/string.cpp | 8 +- tests/unit/io/WKTWriterTest.cpp | 247 +++++++++++++++++++++++++++- 9 files changed, 508 insertions(+), 118 deletions(-) diff --git a/include/geos/geom/GeometryFactory.h b/include/geos/geom/GeometryFactory.h index f5ae08cc4e..e0da353308 100644 --- a/include/geos/geom/GeometryFactory.h +++ b/include/geos/geom/GeometryFactory.h @@ -147,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; @@ -164,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 @@ -228,6 +229,7 @@ class GEOS_DLL GeometryFactory { /// 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( @@ -269,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; @@ -284,6 +287,9 @@ class GEOS_DLL GeometryFactory { 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; @@ -292,6 +298,7 @@ class GEOS_DLL GeometryFactory { /// 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; @@ -305,7 +312,7 @@ class GEOS_DLL GeometryFactory { const CoordinateSequence& coordinates) const; /// Construct an EMPTY CircularString - std::unique_ptr createCircularString(bool hasZ = false, bool hasM = false) const; + std::unique_ptr createCircularString(bool hasZ, bool hasM) const; /// Copy a CircularString std::unique_ptr createCircularString(const CircularString& ls) const; diff --git a/include/geos/io/WKTReader.h b/include/geos/io/WKTReader.h index 6f9afe2162..7294d64f55 100644 --- a/include/geos/io/WKTReader.h +++ b/include/geos/io/WKTReader.h @@ -123,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; @@ -132,14 +133,16 @@ 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; diff --git a/include/geos/io/WKTWriter.h b/include/geos/io/WKTWriter.h index a37973a872..e58c01c9c6 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,25 @@ class GEOS_DLL WKTWriter { std::string writeNumber(double d) const; + void appendCurveText( + const geom::Curve& lineString, + OrdinateSet outputOrdinates, + int level, bool doIndent, Writer& writer) const; + + void appendSimpleCurveText( + const geom::SimpleCurve& lineString, + OrdinateSet outputOrdinates, + int level, bool doIndent, Writer& writer) const; + +#if 0 void appendLineStringText( const geom::LineString& lineString, OrdinateSet outputOrdinates, int level, bool doIndent, Writer& writer) const; +#endif - void appendPolygonText( - const geom::Polygon& polygon, + void appendSurfaceText( + const geom::Surface& polygon, OrdinateSet outputOrdinates, int level, bool indentFirst, Writer& writer) const; @@ -278,13 +299,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 +320,6 @@ class GEOS_DLL WKTWriter { INDENT = 2 }; -// static const int INDENT = 2; - bool isFormatted; int roundingPrecision; @@ -309,8 +328,6 @@ class GEOS_DLL WKTWriter { bool removeEmptyDimensions = false; - int level; - static constexpr int coordsPerLine = 10; uint8_t defaultOutputDimension; 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/geom/GeometryFactory.cpp b/src/geom/GeometryFactory.cpp index a34e7ddd23..a143da2995 100644 --- a/src/geom/GeometryFactory.cpp +++ b/src/geom/GeometryFactory.cpp @@ -225,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 @@ -334,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*/ @@ -417,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 { @@ -487,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 @@ -531,12 +574,20 @@ 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 { - //auto shellCurve = std::unique_ptr(detail::down_cast(shell.release())); // Can't use make_unique with protected constructor return std::unique_ptr(new CurvePolygon(std::move(shell), *this)); } @@ -558,6 +609,14 @@ 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 @@ -599,7 +658,7 @@ GeometryFactory::createCircularString(CoordinateSequence::Ptr && newCoords) const { if (!newCoords) - return createCircularString(); + return createCircularString(false, false); // Can't use make_unique with protected constructor return std::unique_ptr(new CircularString(std::move(newCoords), *this)); } diff --git a/src/io/WKTReader.cpp b/src/io/WKTReader.cpp index f72bedede8..fc850d2201 100644 --- a/src/io/WKTReader.cpp +++ b/src/io/WKTReader.cpp @@ -265,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); @@ -312,6 +317,8 @@ WKTReader::readGeometryTaggedText(StringTokenizer* tokenizer, OrdinateSet& ordin } 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); } @@ -327,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)); } @@ -363,7 +370,8 @@ WKTReader::readCurveText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlags) return readLineStringText(tokenizer, ordinateFlags); } - auto component = readGeometryTaggedText(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())); } @@ -379,8 +387,9 @@ WKTReader::readSurfaceText(StringTokenizer* tokenizer, OrdinateSet& ordinateFlag return readPolygonText(tokenizer, ordinateFlags); } - auto component = readGeometryTaggedText(tokenizer, ordinateFlags); - if (dynamic_cast(component.get())) { + GeometryTypeId defaultType = GEOS_POLYGON; + auto component = readGeometryTaggedText(tokenizer, ordinateFlags, &defaultType); + if (dynamic_cast(component.get())) { return component; } diff --git a/src/io/WKTWriter.cpp b/src/io/WKTWriter.cpp index 86c12cdf8e..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,24 +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_CIRCULARSTRING: + 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_COMPOUNDCURVE: throw std::runtime_error("Not handled yet"); + 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 @@ -279,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 "); @@ -289,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 */ @@ -382,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 { @@ -391,7 +434,7 @@ WKTWriter::appendSequenceText(const CoordinateSequence& seq, } else { if(doIndent) { - indent(p_level, &writer); + indent(level, &writer); } writer.write("("); CoordinateXYZM c; @@ -399,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); @@ -409,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); @@ -464,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()) { @@ -482,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(")"); } @@ -523,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(")"); } @@ -575,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(); @@ -583,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); } @@ -597,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/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/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