gdal/ogr/ogrgeometrycollection.cpp

1329 lines
43 KiB
C++

/******************************************************************************
*
* Project: OpenGIS Simple Features Reference Implementation
* Purpose: The OGRGeometryCollection class.
* Author: Frank Warmerdam, warmerdam@pobox.com
*
******************************************************************************
* Copyright (c) 1999, Frank Warmerdam
* Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
****************************************************************************/
#include "cpl_port.h"
#include "ogr_geometry.h"
#include <cstddef>
#include <cstring>
#include <new>
#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_string.h"
#include "cpl_vsi.h"
#include "ogr_api.h"
#include "ogr_core.h"
#include "ogr_p.h"
#include "ogr_spatialref.h"
/************************************************************************/
/* OGRGeometryCollection() */
/************************************************************************/
/**
* \brief Create an empty geometry collection.
*/
OGRGeometryCollection::OGRGeometryCollection() = default;
/************************************************************************/
/* OGRGeometryCollection( const OGRGeometryCollection& ) */
/************************************************************************/
/**
* \brief Copy constructor.
*
* Note: before GDAL 2.1, only the default implementation of the constructor
* existed, which could be unsafe to use.
*
* @since GDAL 2.1
*/
OGRGeometryCollection::OGRGeometryCollection(const OGRGeometryCollection &other)
: OGRGeometry(other)
{
// Do not use addGeometry() as it is virtual.
papoGeoms = static_cast<OGRGeometry **>(
VSI_CALLOC_VERBOSE(sizeof(void *), other.nGeomCount));
if (papoGeoms)
{
nGeomCount = other.nGeomCount;
for (int i = 0; i < other.nGeomCount; i++)
{
papoGeoms[i] = other.papoGeoms[i]->clone();
}
}
}
/************************************************************************/
/* ~OGRGeometryCollection() */
/************************************************************************/
OGRGeometryCollection::~OGRGeometryCollection()
{
OGRGeometryCollection::empty();
}
/************************************************************************/
/* operator=( const OGRGeometryCollection&) */
/************************************************************************/
/**
* \brief Assignment operator.
*
* Note: before GDAL 2.1, only the default implementation of the operator
* existed, which could be unsafe to use.
*
* @since GDAL 2.1
*/
OGRGeometryCollection &
OGRGeometryCollection::operator=(const OGRGeometryCollection &other)
{
if (this != &other)
{
empty();
OGRGeometry::operator=(other);
for (int i = 0; i < other.nGeomCount; i++)
{
addGeometry(other.papoGeoms[i]);
}
}
return *this;
}
/************************************************************************/
/* empty() */
/************************************************************************/
void OGRGeometryCollection::empty()
{
if (papoGeoms != nullptr)
{
for (auto &&poSubGeom : *this)
{
delete poSubGeom;
}
CPLFree(papoGeoms);
}
nGeomCount = 0;
papoGeoms = nullptr;
}
/************************************************************************/
/* clone() */
/************************************************************************/
OGRGeometryCollection *OGRGeometryCollection::clone() const
{
return new (std::nothrow) OGRGeometryCollection(*this);
}
/************************************************************************/
/* getGeometryType() */
/************************************************************************/
OGRwkbGeometryType OGRGeometryCollection::getGeometryType() const
{
if ((flags & OGR_G_3D) && (flags & OGR_G_MEASURED))
return wkbGeometryCollectionZM;
else if (flags & OGR_G_MEASURED)
return wkbGeometryCollectionM;
else if (flags & OGR_G_3D)
return wkbGeometryCollection25D;
else
return wkbGeometryCollection;
}
/************************************************************************/
/* getDimension() */
/************************************************************************/
int OGRGeometryCollection::getDimension() const
{
int nDimension = 0;
// FIXME? Not sure if it is really appropriate to take the max in case
// of geometries of different dimension.
for (auto &&poSubGeom : *this)
{
int nSubGeomDimension = poSubGeom->getDimension();
if (nSubGeomDimension > nDimension)
{
nDimension = nSubGeomDimension;
if (nDimension == 2)
break;
}
}
return nDimension;
}
/************************************************************************/
/* flattenTo2D() */
/************************************************************************/
void OGRGeometryCollection::flattenTo2D()
{
for (auto &&poSubGeom : *this)
{
poSubGeom->flattenTo2D();
}
flags &= ~OGR_G_3D;
flags &= ~OGR_G_MEASURED;
}
/************************************************************************/
/* getGeometryName() */
/************************************************************************/
const char *OGRGeometryCollection::getGeometryName() const
{
return "GEOMETRYCOLLECTION";
}
/************************************************************************/
/* getNumGeometries() */
/************************************************************************/
/**
* \brief Fetch number of geometries in container.
*
* This method relates to the SFCOM IGeometryCollect::get_NumGeometries()
* method.
*
* @return count of children geometries. May be zero.
*/
int OGRGeometryCollection::getNumGeometries() const
{
return nGeomCount;
}
/************************************************************************/
/* getGeometryRef() */
/************************************************************************/
/**
* \brief Fetch geometry from container.
*
* This method returns a pointer to a geometry within the container. The
* returned geometry remains owned by the container, and should not be
* modified. The pointer is only valid until the next change to the
* geometry container. Use IGeometry::clone() to make a copy.
*
* This method relates to the SFCOM IGeometryCollection::get_Geometry() method.
*
* @param i the index of the geometry to fetch, between 0 and
* getNumGeometries() - 1.
* @return pointer to requested geometry.
*/
OGRGeometry *OGRGeometryCollection::getGeometryRef(int i)
{
if (i < 0 || i >= nGeomCount)
return nullptr;
return papoGeoms[i];
}
/**
* \brief Fetch geometry from container.
*
* This method returns a pointer to a geometry within the container. The
* returned geometry remains owned by the container, and should not be
* modified. The pointer is only valid until the next change to the
* geometry container. Use IGeometry::clone() to make a copy.
*
* This method relates to the SFCOM IGeometryCollection::get_Geometry() method.
*
* @param i the index of the geometry to fetch, between 0 and
* getNumGeometries() - 1.
* @return pointer to requested geometry.
*/
const OGRGeometry *OGRGeometryCollection::getGeometryRef(int i) const
{
if (i < 0 || i >= nGeomCount)
return nullptr;
return papoGeoms[i];
}
/************************************************************************/
/* addGeometry() */
/* */
/* Add a new geometry to a collection. Subclasses should */
/* override this to verify the type of the new geometry, and */
/* then call this method to actually add it. */
/************************************************************************/
/**
* \brief Add a geometry to the container.
*
* Some subclasses of OGRGeometryCollection restrict the types of geometry
* that can be added, and may return an error. The passed geometry is cloned
* to make an internal copy.
*
* There is no SFCOM analog to this method.
*
* This method is the same as the C function OGR_G_AddGeometry().
*
* @param poNewGeom geometry to add to the container.
*
* @return OGRERR_NONE if successful, or OGRERR_UNSUPPORTED_GEOMETRY_TYPE if
* the geometry type is illegal for the type of geometry container.
*/
OGRErr OGRGeometryCollection::addGeometry(const OGRGeometry *poNewGeom)
{
OGRGeometry *poClone = poNewGeom->clone();
if (poClone == nullptr)
return OGRERR_FAILURE;
const OGRErr eErr = addGeometryDirectly(poClone);
if (eErr != OGRERR_NONE)
delete poClone;
return eErr;
}
/************************************************************************/
/* addGeometryDirectly() */
/* */
/* Add a new geometry to a collection. Subclasses should */
/* override this to verify the type of the new geometry, and */
/* then call this method to actually add it. */
/************************************************************************/
/**
* \brief Add a geometry directly to the container.
*
* Some subclasses of OGRGeometryCollection restrict the types of geometry
* that can be added, and may return an error. Ownership of the passed
* geometry is taken by the container rather than cloning as addGeometry()
* does, but only if the method is successful. If the method fails, ownership
* still belongs to the caller.
*
* This method is the same as the C function OGR_G_AddGeometryDirectly().
*
* There is no SFCOM analog to this method.
*
* @param poNewGeom geometry to add to the container.
*
* @return OGRERR_NONE if successful, or OGRERR_UNSUPPORTED_GEOMETRY_TYPE if
* the geometry type is illegal for the type of geometry container.
*/
OGRErr OGRGeometryCollection::addGeometryDirectly(OGRGeometry *poNewGeom)
{
if (!isCompatibleSubType(poNewGeom->getGeometryType()))
return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
HomogenizeDimensionalityWith(poNewGeom);
OGRGeometry **papoNewGeoms = static_cast<OGRGeometry **>(
VSI_REALLOC_VERBOSE(papoGeoms, sizeof(void *) * (nGeomCount + 1)));
if (papoNewGeoms == nullptr)
return OGRERR_FAILURE;
papoGeoms = papoNewGeoms;
papoGeoms[nGeomCount] = poNewGeom;
nGeomCount++;
return OGRERR_NONE;
}
/************************************************************************/
/* addGeometry() */
/************************************************************************/
/**
* \brief Add a geometry directly to the container.
*
* Some subclasses of OGRGeometryCollection restrict the types of geometry
* that can be added, and may return an error.
*
* There is no SFCOM analog to this method.
*
* @param geom geometry to add to the container.
*
* @return OGRERR_NONE if successful, or OGRERR_UNSUPPORTED_GEOMETRY_TYPE if
* the geometry type is illegal for the type of geometry container.
*/
OGRErr OGRGeometryCollection::addGeometry(std::unique_ptr<OGRGeometry> geom)
{
OGRGeometry *poGeom = geom.release();
OGRErr eErr = addGeometryDirectly(poGeom);
if (eErr != OGRERR_NONE)
delete poGeom;
return eErr;
}
/************************************************************************/
/* removeGeometry() */
/************************************************************************/
/**
* \brief Remove a geometry from the container.
*
* Removing a geometry will cause the geometry count to drop by one, and all
* "higher" geometries will shuffle down one in index.
*
* There is no SFCOM analog to this method.
*
* This method is the same as the C function OGR_G_RemoveGeometry().
*
* @param iGeom the index of the geometry to delete. A value of -1 is a
* special flag meaning that all geometries should be removed.
*
* @param bDelete if TRUE the geometry will be deallocated, otherwise it will
* not. The default is TRUE as the container is considered to own the
* geometries in it.
*
* @return OGRERR_NONE if successful, or OGRERR_FAILURE if the index is
* out of range.
*/
OGRErr OGRGeometryCollection::removeGeometry(int iGeom, int bDelete)
{
if (iGeom < -1 || iGeom >= nGeomCount)
return OGRERR_FAILURE;
// Special case.
if (iGeom == -1)
{
while (nGeomCount > 0)
removeGeometry(nGeomCount - 1, bDelete);
return OGRERR_NONE;
}
if (bDelete)
delete papoGeoms[iGeom];
memmove(papoGeoms + iGeom, papoGeoms + iGeom + 1,
sizeof(void *) * (nGeomCount - iGeom - 1));
nGeomCount--;
return OGRERR_NONE;
}
/************************************************************************/
/* WkbSize() */
/* */
/* Return the size of this object in well known binary */
/* representation including the byte order, and type information. */
/************************************************************************/
size_t OGRGeometryCollection::WkbSize() const
{
size_t nSize = 9;
for (const auto &poGeom : *this)
{
nSize += poGeom->WkbSize();
}
return nSize;
}
/************************************************************************/
/* importFromWkbInternal() */
/************************************************************************/
//! @cond Doxygen_Suppress
OGRErr OGRGeometryCollection::importFromWkbInternal(
const unsigned char *pabyData, size_t nSize, int nRecLevel,
OGRwkbVariant eWkbVariant, size_t &nBytesConsumedOut)
{
nBytesConsumedOut = 0;
// Arbitrary value, but certainly large enough for reasonable use cases.
if (nRecLevel == 32)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Too many recursion levels (%d) while parsing WKB geometry.",
nRecLevel);
return OGRERR_CORRUPT_DATA;
}
OGRwkbByteOrder eByteOrder = wkbXDR;
size_t nDataOffset = 0;
int nGeomCountNew = 0;
OGRErr eErr = importPreambleOfCollectionFromWkb(pabyData, nSize,
nDataOffset, eByteOrder, 9,
nGeomCountNew, eWkbVariant);
if (eErr != OGRERR_NONE)
return eErr;
CPLAssert(nGeomCount == 0);
nGeomCount = nGeomCountNew;
// coverity[tainted_data]
papoGeoms = static_cast<OGRGeometry **>(
VSI_CALLOC_VERBOSE(sizeof(void *), nGeomCount));
if (nGeomCount != 0 && papoGeoms == nullptr)
{
nGeomCount = 0;
return OGRERR_NOT_ENOUGH_MEMORY;
}
/* -------------------------------------------------------------------- */
/* Get the Geoms. */
/* -------------------------------------------------------------------- */
for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
{
// Parses sub-geometry.
const unsigned char *pabySubData = pabyData + nDataOffset;
if (nSize < 9 && nSize != static_cast<size_t>(-1))
return OGRERR_NOT_ENOUGH_DATA;
OGRwkbGeometryType eSubGeomType = wkbUnknown;
eErr = OGRReadWKBGeometryType(pabySubData, eWkbVariant, &eSubGeomType);
if (eErr != OGRERR_NONE)
return eErr;
if (!isCompatibleSubType(eSubGeomType))
{
nGeomCount = iGeom;
CPLDebug(
"OGR",
"Cannot add geometry of type (%d) to geometry of type (%d)",
eSubGeomType, getGeometryType());
return OGRERR_CORRUPT_DATA;
}
OGRGeometry *poSubGeom = nullptr;
size_t nSubGeomBytesConsumed = 0;
if (OGR_GT_IsSubClassOf(eSubGeomType, wkbGeometryCollection))
{
poSubGeom = OGRGeometryFactory::createGeometry(eSubGeomType);
if (poSubGeom == nullptr)
eErr = OGRERR_FAILURE;
else
eErr = poSubGeom->toGeometryCollection()->importFromWkbInternal(
pabySubData, nSize, nRecLevel + 1, eWkbVariant,
nSubGeomBytesConsumed);
}
else
{
eErr = OGRGeometryFactory::createFromWkb(
pabySubData, nullptr, &poSubGeom, nSize, eWkbVariant,
nSubGeomBytesConsumed);
}
if (eErr != OGRERR_NONE)
{
nGeomCount = iGeom;
delete poSubGeom;
return eErr;
}
papoGeoms[iGeom] = poSubGeom;
if (papoGeoms[iGeom]->Is3D())
flags |= OGR_G_3D;
if (papoGeoms[iGeom]->IsMeasured())
flags |= OGR_G_MEASURED;
CPLAssert(nSubGeomBytesConsumed > 0);
if (nSize != static_cast<size_t>(-1))
{
CPLAssert(nSize >= nSubGeomBytesConsumed);
nSize -= nSubGeomBytesConsumed;
}
nDataOffset += nSubGeomBytesConsumed;
}
nBytesConsumedOut = nDataOffset;
return OGRERR_NONE;
}
//! @endcond
/************************************************************************/
/* importFromWkb() */
/* */
/* Initialize from serialized stream in well known binary */
/* format. */
/************************************************************************/
OGRErr OGRGeometryCollection::importFromWkb(const unsigned char *pabyData,
size_t nSize,
OGRwkbVariant eWkbVariant,
size_t &nBytesConsumedOut)
{
return importFromWkbInternal(pabyData, nSize, 0, eWkbVariant,
nBytesConsumedOut);
}
/************************************************************************/
/* exportToWkb() */
/* */
/* Build a well known binary representation of this object. */
/************************************************************************/
OGRErr OGRGeometryCollection::exportToWkb(OGRwkbByteOrder eByteOrder,
unsigned char *pabyData,
OGRwkbVariant eWkbVariant) const
{
if (eWkbVariant == wkbVariantOldOgc &&
(wkbFlatten(getGeometryType()) == wkbMultiCurve ||
wkbFlatten(getGeometryType()) == wkbMultiSurface))
{
// Does not make sense for new geometries, so patch it.
eWkbVariant = wkbVariantIso;
}
/* -------------------------------------------------------------------- */
/* Set the byte order. */
/* -------------------------------------------------------------------- */
pabyData[0] =
DB2_V72_UNFIX_BYTE_ORDER(static_cast<unsigned char>(eByteOrder));
/* -------------------------------------------------------------------- */
/* Set the geometry feature type, ensuring that 3D flag is */
/* preserved. */
/* -------------------------------------------------------------------- */
GUInt32 nGType = getGeometryType();
if (eWkbVariant == wkbVariantIso)
nGType = getIsoGeometryType();
else if (eWkbVariant == wkbVariantPostGIS1)
{
const bool bIs3D = wkbHasZ(static_cast<OGRwkbGeometryType>(nGType));
nGType = wkbFlatten(nGType);
if (nGType == wkbMultiCurve)
nGType = POSTGIS15_MULTICURVE;
else if (nGType == wkbMultiSurface)
nGType = POSTGIS15_MULTISURFACE;
if (bIs3D)
// Yes, explicitly set wkb25DBit.
nGType =
static_cast<OGRwkbGeometryType>(nGType | wkb25DBitInternalUse);
}
if (OGR_SWAP(eByteOrder))
{
nGType = CPL_SWAP32(nGType);
}
memcpy(pabyData + 1, &nGType, 4);
/* -------------------------------------------------------------------- */
/* Copy in the raw data. */
/* -------------------------------------------------------------------- */
if (OGR_SWAP(eByteOrder))
{
int nCount = CPL_SWAP32(nGeomCount);
memcpy(pabyData + 5, &nCount, 4);
}
else
{
memcpy(pabyData + 5, &nGeomCount, 4);
}
size_t nOffset = 9;
/* ==================================================================== */
/* Serialize each of the Geoms. */
/* ==================================================================== */
int iGeom = 0;
for (auto &&poSubGeom : *this)
{
poSubGeom->exportToWkb(eByteOrder, pabyData + nOffset, eWkbVariant);
// Should normally not happen if everyone else does its job,
// but has happened sometimes. (#6332)
if (poSubGeom->getCoordinateDimension() != getCoordinateDimension())
{
CPLError(CE_Warning, CPLE_AppDefined,
"Sub-geometry %d has coordinate dimension %d, "
"but container has %d",
iGeom, poSubGeom->getCoordinateDimension(),
getCoordinateDimension());
}
nOffset += poSubGeom->WkbSize();
iGeom++;
}
return OGRERR_NONE;
}
/************************************************************************/
/* importFromWktInternal() */
/************************************************************************/
OGRErr OGRGeometryCollection::importFromWktInternal(const char **ppszInput,
int nRecLevel)
{
// Arbitrary value, but certainly large enough for reasonable usages.
if (nRecLevel == 32)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Too many recursion levels (%d) while parsing WKT geometry.",
nRecLevel);
return OGRERR_CORRUPT_DATA;
}
int bHasZ = FALSE;
int bHasM = FALSE;
bool bIsEmpty = false;
OGRErr eErr = importPreambleFromWkt(ppszInput, &bHasZ, &bHasM, &bIsEmpty);
if (eErr != OGRERR_NONE)
return eErr;
if (bHasZ)
flags |= OGR_G_3D;
if (bHasM)
flags |= OGR_G_MEASURED;
if (bIsEmpty)
return OGRERR_NONE;
char szToken[OGR_WKT_TOKEN_MAX] = {};
const char *pszInput = *ppszInput;
// Skip first '('.
pszInput = OGRWktReadToken(pszInput, szToken);
/* ==================================================================== */
/* Read each subgeometry in turn. */
/* ==================================================================== */
do
{
OGRGeometry *poGeom = nullptr;
/* --------------------------------------------------------------------
*/
/* Get the first token, which should be the geometry type. */
/* --------------------------------------------------------------------
*/
OGRWktReadToken(pszInput, szToken);
/* --------------------------------------------------------------------
*/
/* Do the import. */
/* --------------------------------------------------------------------
*/
if (STARTS_WITH_CI(szToken, "GEOMETRYCOLLECTION"))
{
OGRGeometryCollection *poGC = new OGRGeometryCollection();
poGeom = poGC;
eErr = poGC->importFromWktInternal(&pszInput, nRecLevel + 1);
}
else
eErr =
OGRGeometryFactory::createFromWkt(&pszInput, nullptr, &poGeom);
if (eErr == OGRERR_NONE)
{
// If this has M, but not Z, it is an error if poGeom does
// not have M.
if (!Is3D() && IsMeasured() && !poGeom->IsMeasured())
eErr = OGRERR_CORRUPT_DATA;
else
eErr = addGeometryDirectly(poGeom);
}
if (eErr != OGRERR_NONE)
{
delete poGeom;
return eErr;
}
/* --------------------------------------------------------------------
*/
/* Read the delimiter following the ring. */
/* --------------------------------------------------------------------
*/
pszInput = OGRWktReadToken(pszInput, szToken);
} while (szToken[0] == ',');
/* -------------------------------------------------------------------- */
/* freak if we don't get a closing bracket. */
/* -------------------------------------------------------------------- */
if (szToken[0] != ')')
return OGRERR_CORRUPT_DATA;
*ppszInput = pszInput;
return OGRERR_NONE;
}
/************************************************************************/
/* importFromWkt() */
/************************************************************************/
OGRErr OGRGeometryCollection::importFromWkt(const char **ppszInput)
{
return importFromWktInternal(ppszInput, 0);
}
/************************************************************************/
/* exportToWkt() */
/* */
/* Translate this structure into its well known text format */
/* equivalent. */
/************************************************************************/
std::string OGRGeometryCollection::exportToWkt(const OGRWktOptions &opts,
OGRErr *err) const
{
return exportToWktInternal(opts, err);
}
//! @cond Doxygen_Suppress
std::string OGRGeometryCollection::exportToWktInternal(
const OGRWktOptions &opts, OGRErr *err, const std::string &exclude) const
{
bool first = true;
const size_t excludeSize = exclude.size();
std::string wkt(getGeometryName());
wkt += wktTypeString(opts.variant);
try
{
for (int i = 0; i < nGeomCount; ++i)
{
OGRGeometry *geom = papoGeoms[i];
OGRErr subgeomErr = OGRERR_NONE;
std::string tempWkt = geom->exportToWkt(opts, &subgeomErr);
if (subgeomErr != OGRERR_NONE)
{
if (err)
*err = subgeomErr;
return std::string();
}
// For some strange reason we exclude the typename leader when using
// some geometries as part of a collection.
if (excludeSize && (tempWkt.compare(0, excludeSize, exclude) == 0))
{
auto pos = tempWkt.find('(');
// We won't have an opening paren if the geom is empty.
if (pos == std::string::npos)
continue;
tempWkt = tempWkt.substr(pos);
}
// Also strange, we allow the inclusion of ISO-only geometries (see
// OGRPolyhedralSurface) in a non-iso geometry collection. In order
// to facilitate this, we need to rip the ISO bit from the string.
if (opts.variant != wkbVariantIso)
{
std::string::size_type pos;
if ((pos = tempWkt.find(" Z ")) != std::string::npos)
tempWkt.erase(pos + 1, 2);
else if ((pos = tempWkt.find(" M ")) != std::string::npos)
tempWkt.erase(pos + 1, 2);
else if ((pos = tempWkt.find(" ZM ")) != std::string::npos)
tempWkt.erase(pos + 1, 3);
}
if (first)
wkt += '(';
else
wkt += ',';
first = false;
wkt += tempWkt;
}
if (err)
*err = OGRERR_NONE;
if (first)
wkt += "EMPTY";
else
wkt += ')';
return wkt;
}
catch (const std::bad_alloc &e)
{
CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
if (err)
*err = OGRERR_FAILURE;
return std::string();
}
}
//! @endcond
/************************************************************************/
/* getEnvelope() */
/************************************************************************/
void OGRGeometryCollection::getEnvelope(OGREnvelope *psEnvelope) const
{
OGREnvelope3D oEnv3D;
getEnvelope(&oEnv3D);
psEnvelope->MinX = oEnv3D.MinX;
psEnvelope->MinY = oEnv3D.MinY;
psEnvelope->MaxX = oEnv3D.MaxX;
psEnvelope->MaxY = oEnv3D.MaxY;
}
/************************************************************************/
/* getEnvelope() */
/************************************************************************/
void OGRGeometryCollection::getEnvelope(OGREnvelope3D *psEnvelope) const
{
OGREnvelope3D oGeomEnv;
bool bExtentSet = false;
*psEnvelope = OGREnvelope3D();
for (auto &&poSubGeom : *this)
{
if (!poSubGeom->IsEmpty())
{
bExtentSet = true;
poSubGeom->getEnvelope(&oGeomEnv);
psEnvelope->Merge(oGeomEnv);
}
}
if (!bExtentSet)
{
// To be backward compatible when called on empty geom
psEnvelope->MinX = 0.0;
psEnvelope->MinY = 0.0;
psEnvelope->MinZ = 0.0;
psEnvelope->MaxX = 0.0;
psEnvelope->MaxY = 0.0;
psEnvelope->MaxZ = 0.0;
}
}
/************************************************************************/
/* Equals() */
/************************************************************************/
OGRBoolean OGRGeometryCollection::Equals(const OGRGeometry *poOther) const
{
if (poOther == this)
return TRUE;
if (poOther->getGeometryType() != getGeometryType())
return FALSE;
if (IsEmpty() && poOther->IsEmpty())
return TRUE;
auto poOGC = poOther->toGeometryCollection();
if (getNumGeometries() != poOGC->getNumGeometries())
return FALSE;
// TODO(schwehr): Should test the SRS.
for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
{
if (!getGeometryRef(iGeom)->Equals(poOGC->getGeometryRef(iGeom)))
return FALSE;
}
return TRUE;
}
/************************************************************************/
/* transform() */
/************************************************************************/
OGRErr OGRGeometryCollection::transform(OGRCoordinateTransformation *poCT)
{
int iGeom = 0;
for (auto &&poSubGeom : *this)
{
const OGRErr eErr = poSubGeom->transform(poCT);
if (eErr != OGRERR_NONE)
{
if (iGeom != 0)
{
CPLDebug("OGR",
"OGRGeometryCollection::transform() failed for a "
"geometry other than the first, meaning some "
"geometries are transformed and some are not.");
return OGRERR_FAILURE;
}
return eErr;
}
iGeom++;
}
assignSpatialReference(poCT->GetTargetCS());
return OGRERR_NONE;
}
/************************************************************************/
/* closeRings() */
/************************************************************************/
void OGRGeometryCollection::closeRings()
{
for (auto &&poSubGeom : *this)
{
if (OGR_GT_IsSubClassOf(wkbFlatten(poSubGeom->getGeometryType()),
wkbCurvePolygon))
{
OGRCurvePolygon *poPoly = poSubGeom->toCurvePolygon();
poPoly->closeRings();
}
}
}
/************************************************************************/
/* setCoordinateDimension() */
/************************************************************************/
void OGRGeometryCollection::setCoordinateDimension(int nNewDimension)
{
for (auto &&poSubGeom : *this)
{
poSubGeom->setCoordinateDimension(nNewDimension);
}
OGRGeometry::setCoordinateDimension(nNewDimension);
}
void OGRGeometryCollection::set3D(OGRBoolean bIs3D)
{
for (auto &&poSubGeom : *this)
{
poSubGeom->set3D(bIs3D);
}
OGRGeometry::set3D(bIs3D);
}
void OGRGeometryCollection::setMeasured(OGRBoolean bIsMeasured)
{
for (auto &&poSubGeom : *this)
{
poSubGeom->setMeasured(bIsMeasured);
}
OGRGeometry::setMeasured(bIsMeasured);
}
/************************************************************************/
/* get_Length() */
/************************************************************************/
/**
* \brief Compute the length of a multicurve.
*
* The length is computed as the sum of the length of all members
* in this collection.
*
* @note No warning will be issued if a member of the collection does not
* support the get_Length method.
*
* @return computed length.
*/
double OGRGeometryCollection::get_Length() const
{
double dfLength = 0.0;
for (auto &&poSubGeom : *this)
{
const OGRwkbGeometryType eType =
wkbFlatten(poSubGeom->getGeometryType());
if (OGR_GT_IsCurve(eType))
{
const OGRCurve *poCurve = poSubGeom->toCurve();
dfLength += poCurve->get_Length();
}
else if (OGR_GT_IsSubClassOf(eType, wkbMultiCurve) ||
eType == wkbGeometryCollection)
{
const OGRGeometryCollection *poColl =
poSubGeom->toGeometryCollection();
dfLength += poColl->get_Length();
}
}
return dfLength;
}
/************************************************************************/
/* get_Area() */
/************************************************************************/
/**
* \brief Compute area of geometry collection.
*
* The area is computed as the sum of the areas of all members
* in this collection.
*
* @note No warning will be issued if a member of the collection does not
* support the get_Area method.
*
* @return computed area.
*/
double OGRGeometryCollection::get_Area() const
{
double dfArea = 0.0;
for (auto &&poSubGeom : *this)
{
OGRwkbGeometryType eType = wkbFlatten(poSubGeom->getGeometryType());
if (OGR_GT_IsSurface(eType))
{
const OGRSurface *poSurface = poSubGeom->toSurface();
dfArea += poSurface->get_Area();
}
else if (OGR_GT_IsCurve(eType))
{
const OGRCurve *poCurve = poSubGeom->toCurve();
dfArea += poCurve->get_Area();
}
else if (OGR_GT_IsSubClassOf(eType, wkbMultiSurface) ||
eType == wkbGeometryCollection)
{
dfArea += poSubGeom->toGeometryCollection()->get_Area();
}
}
return dfArea;
}
/************************************************************************/
/* IsEmpty() */
/************************************************************************/
OGRBoolean OGRGeometryCollection::IsEmpty() const
{
for (auto &&poSubGeom : *this)
{
if (poSubGeom->IsEmpty() == FALSE)
return FALSE;
}
return TRUE;
}
/************************************************************************/
/* assignSpatialReference() */
/************************************************************************/
void OGRGeometryCollection::assignSpatialReference(
const OGRSpatialReference *poSR)
{
OGRGeometry::assignSpatialReference(poSR);
for (auto &&poSubGeom : *this)
{
poSubGeom->assignSpatialReference(poSR);
}
}
/************************************************************************/
/* OGRGeometryCollection::segmentize() */
/************************************************************************/
void OGRGeometryCollection::segmentize(double dfMaxLength)
{
for (auto &&poSubGeom : *this)
{
poSubGeom->segmentize(dfMaxLength);
}
}
/************************************************************************/
/* swapXY() */
/************************************************************************/
void OGRGeometryCollection::swapXY()
{
for (auto &&poSubGeom : *this)
{
poSubGeom->swapXY();
}
}
/************************************************************************/
/* isCompatibleSubType() */
/************************************************************************/
/** Returns whether a geometry of the specified geometry type can be a
* member of this collection.
*
* @param eSubType type of the potential member
* @return TRUE or FALSE
*/
OGRBoolean OGRGeometryCollection::isCompatibleSubType(
CPL_UNUSED OGRwkbGeometryType eSubType) const
{
// Accept all geometries as sub-geometries.
return TRUE;
}
/************************************************************************/
/* hasCurveGeometry() */
/************************************************************************/
OGRBoolean OGRGeometryCollection::hasCurveGeometry(int bLookForNonLinear) const
{
for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
{
if (papoGeoms[iGeom]->hasCurveGeometry(bLookForNonLinear))
return TRUE;
}
return FALSE;
}
/************************************************************************/
/* getLinearGeometry() */
/************************************************************************/
OGRGeometry *
OGRGeometryCollection::getLinearGeometry(double dfMaxAngleStepSizeDegrees,
const char *const *papszOptions) const
{
OGRGeometryCollection *poGC =
OGRGeometryFactory::createGeometry(OGR_GT_GetLinear(getGeometryType()))
->toGeometryCollection();
if (poGC == nullptr)
return nullptr;
poGC->assignSpatialReference(getSpatialReference());
for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
{
OGRGeometry *poSubGeom = papoGeoms[iGeom]->getLinearGeometry(
dfMaxAngleStepSizeDegrees, papszOptions);
poGC->addGeometryDirectly(poSubGeom);
}
return poGC;
}
/************************************************************************/
/* getCurveGeometry() */
/************************************************************************/
OGRGeometry *
OGRGeometryCollection::getCurveGeometry(const char *const *papszOptions) const
{
OGRGeometryCollection *poGC =
OGRGeometryFactory::createGeometry(OGR_GT_GetCurve(getGeometryType()))
->toGeometryCollection();
if (poGC == nullptr)
return nullptr;
poGC->assignSpatialReference(getSpatialReference());
bool bHasCurveGeometry = false;
for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
{
OGRGeometry *poSubGeom =
papoGeoms[iGeom]->getCurveGeometry(papszOptions);
if (poSubGeom->hasCurveGeometry())
bHasCurveGeometry = true;
poGC->addGeometryDirectly(poSubGeom);
}
if (!bHasCurveGeometry)
{
delete poGC;
return clone();
}
return poGC;
}
/************************************************************************/
/* TransferMembersAndDestroy() */
/************************************************************************/
//! @cond Doxygen_Suppress
OGRGeometryCollection *
OGRGeometryCollection::TransferMembersAndDestroy(OGRGeometryCollection *poSrc,
OGRGeometryCollection *poDst)
{
poDst->assignSpatialReference(poSrc->getSpatialReference());
poDst->set3D(poSrc->Is3D());
poDst->setMeasured(poSrc->IsMeasured());
poDst->nGeomCount = poSrc->nGeomCount;
poDst->papoGeoms = poSrc->papoGeoms;
poSrc->nGeomCount = 0;
poSrc->papoGeoms = nullptr;
delete poSrc;
return poDst;
}
//! @endcond
/************************************************************************/
/* CastToGeometryCollection() */
/************************************************************************/
/**
* \brief Cast to geometry collection.
*
* This methods cast a derived class of geometry collection to a plain
* geometry collection.
*
* The passed in geometry is consumed and a new one returned (or NULL in case
* of failure).
*
* @param poSrc the input geometry - ownership is passed to the method.
* @return new geometry.
* @since GDAL 2.2
*/
OGRGeometryCollection *
OGRGeometryCollection::CastToGeometryCollection(OGRGeometryCollection *poSrc)
{
if (wkbFlatten(poSrc->getGeometryType()) == wkbGeometryCollection)
return poSrc;
return TransferMembersAndDestroy(poSrc, new OGRGeometryCollection());
}