QGIS/src/core/geometry/qgsgeos.cpp

2781 lines
76 KiB
C++

/***************************************************************************
qgsgeos.cpp
-------------------------------------------------------------------
Date : 22 Sept 2014
Copyright : (C) 2014 by Marco Hugentobler
email : marco.hugentobler at sourcepole dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsgeos.h"
#include "qgsabstractgeometry.h"
#include "qgsgeometrycollection.h"
#include "qgsgeometryfactory.h"
#include "qgslinestring.h"
#include "qgsmulticurve.h"
#include "qgsmultilinestring.h"
#include "qgsmultipoint.h"
#include "qgsmultipolygon.h"
#include "qgslogger.h"
#include "qgspolygon.h"
#include "qgsgeometryeditutils.h"
#include <limits>
#include <cstdio>
#define DEFAULT_QUADRANT_SEGMENTS 8
#define CATCH_GEOS(r) \
catch (GEOSException &) \
{ \
return r; \
}
#define CATCH_GEOS_WITH_ERRMSG(r) \
catch (GEOSException &e) \
{ \
if ( errorMsg ) \
{ \
*errorMsg = e.what(); \
} \
return r; \
}
/// @cond PRIVATE
static void throwGEOSException( const char *fmt, ... )
{
va_list ap;
char buffer[1024];
va_start( ap, fmt );
vsnprintf( buffer, sizeof buffer, fmt, ap );
va_end( ap );
qWarning( "GEOS exception: %s", buffer );
QString message = QString::fromUtf8( buffer );
#ifdef _MSC_VER
// stupid stupid MSVC, *SOMETIMES* raises it's own exception if we throw GEOSException, resulting in a crash!
// see https://issues.qgis.org/issues/14752
// if you want to test alternative fixes for this, run the testqgsexpression.cpp test suite - that will crash
// and burn on the "line_interpolate_point point" test if a GEOSException is thrown.
// TODO - find a real fix for the underlying issue
try
{
throw GEOSException( message );
}
catch ( ... )
{
// oops, msvc threw an exception when we tried to throw the exception!
// just throw nothing instead (except your mouse at your monitor)
}
#else
throw GEOSException( message );
#endif
}
static void printGEOSNotice( const char *fmt, ... )
{
#if defined(QGISDEBUG)
va_list ap;
char buffer[1024];
va_start( ap, fmt );
vsnprintf( buffer, sizeof buffer, fmt, ap );
va_end( ap );
QgsDebugMsg( QStringLiteral( "GEOS notice: %1" ).arg( QString::fromUtf8( buffer ) ) );
#else
Q_UNUSED( fmt );
#endif
}
class GEOSInit
{
public:
GEOSContextHandle_t ctxt;
GEOSInit()
{
ctxt = initGEOS_r( printGEOSNotice, throwGEOSException );
}
~GEOSInit()
{
finishGEOS_r( ctxt );
}
GEOSInit( const GEOSInit &rh ) = delete;
GEOSInit &operator=( const GEOSInit &rh ) = delete;
};
static GEOSInit geosinit;
void geos::GeosDeleter::operator()( GEOSGeometry *geom )
{
GEOSGeom_destroy_r( geosinit.ctxt, geom );
}
void geos::GeosDeleter::operator()( const GEOSPreparedGeometry *geom )
{
GEOSPreparedGeom_destroy_r( geosinit.ctxt, geom );
}
void geos::GeosDeleter::operator()( GEOSBufferParams *params )
{
GEOSBufferParams_destroy_r( geosinit.ctxt, params );
}
void geos::GeosDeleter::operator()( GEOSCoordSequence *sequence )
{
GEOSCoordSeq_destroy_r( geosinit.ctxt, sequence );
}
///@endcond
QgsGeos::QgsGeos( const QgsAbstractGeometry *geometry, double precision )
: QgsGeometryEngine( geometry )
, mGeos( nullptr )
, mPrecision( precision )
{
cacheGeos();
}
QgsGeometry QgsGeos::geometryFromGeos( GEOSGeometry *geos )
{
QgsGeometry g( QgsGeos::fromGeos( geos ) );
GEOSGeom_destroy_r( QgsGeos::getGEOSHandler(), geos );
return g;
}
QgsGeometry QgsGeos::geometryFromGeos( const geos::unique_ptr &geos )
{
QgsGeometry g( QgsGeos::fromGeos( geos.get() ) );
return g;
}
geos::unique_ptr QgsGeos::asGeos( const QgsGeometry &geometry, double precision )
{
if ( geometry.isNull() )
{
return nullptr;
}
return asGeos( geometry.constGet(), precision );
}
QgsGeometry::OperationResult QgsGeos::addPart( QgsGeometry &geometry, GEOSGeometry *newPart )
{
if ( geometry.isNull() )
{
return QgsGeometry::InvalidBaseGeometry;
}
if ( !newPart )
{
return QgsGeometry::AddPartNotMultiGeometry;
}
std::unique_ptr< QgsAbstractGeometry > geom = fromGeos( newPart );
return QgsGeometryEditUtils::addPart( geometry.get(), std::move( geom ) );
}
void QgsGeos::geometryChanged()
{
mGeos.reset();
mGeosPrepared.reset();
cacheGeos();
}
void QgsGeos::prepareGeometry()
{
mGeosPrepared.reset();
if ( mGeos )
{
mGeosPrepared.reset( GEOSPrepare_r( geosinit.ctxt, mGeos.get() ) );
}
}
void QgsGeos::cacheGeos() const
{
if ( !mGeometry )
{
return;
}
mGeos = asGeos( mGeometry, mPrecision );
}
QgsAbstractGeometry *QgsGeos::intersection( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
return overlay( geom, OverlayIntersection, errorMsg ).release();
}
QgsAbstractGeometry *QgsGeos::difference( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
return overlay( geom, OverlayDifference, errorMsg ).release();
}
std::unique_ptr<QgsAbstractGeometry> QgsGeos::clip( const QgsRectangle &rect, QString *errorMsg ) const
{
if ( !mGeos || rect.isNull() || rect.isEmpty() )
{
return nullptr;
}
try
{
geos::unique_ptr opGeom( GEOSClipByRect_r( geosinit.ctxt, mGeos.get(), rect.xMinimum(), rect.yMinimum(), rect.xMaximum(), rect.yMaximum() ) );
return fromGeos( opGeom.get() );
}
catch ( GEOSException &e )
{
if ( errorMsg )
{
*errorMsg = e.what();
}
return nullptr;
}
}
void QgsGeos::subdivideRecursive( const GEOSGeometry *currentPart, int maxNodes, int depth, QgsGeometryCollection *parts, const QgsRectangle &clipRect ) const
{
int partType = GEOSGeomTypeId_r( geosinit.ctxt, currentPart );
if ( qgsDoubleNear( clipRect.width(), 0.0 ) && qgsDoubleNear( clipRect.height(), 0.0 ) )
{
if ( partType == GEOS_POINT )
{
parts->addGeometry( fromGeos( currentPart ).release() );
return;
}
else
{
return;
}
}
if ( partType == GEOS_MULTILINESTRING || partType == GEOS_MULTIPOLYGON || partType == GEOS_GEOMETRYCOLLECTION )
{
int partCount = GEOSGetNumGeometries_r( geosinit.ctxt, currentPart );
for ( int i = 0; i < partCount; ++i )
{
subdivideRecursive( GEOSGetGeometryN_r( geosinit.ctxt, currentPart, i ), maxNodes, depth, parts, clipRect );
}
return;
}
if ( depth > 50 )
{
parts->addGeometry( fromGeos( currentPart ).release() );
return;
}
int vertexCount = GEOSGetNumCoordinates_r( geosinit.ctxt, currentPart );
if ( vertexCount == 0 )
{
return;
}
else if ( vertexCount < maxNodes )
{
parts->addGeometry( fromGeos( currentPart ).release() );
return;
}
// chop clipping rect in half by longest side
double width = clipRect.width();
double height = clipRect.height();
QgsRectangle halfClipRect1 = clipRect;
QgsRectangle halfClipRect2 = clipRect;
if ( width > height )
{
halfClipRect1.setXMaximum( clipRect.xMinimum() + width / 2.0 );
halfClipRect2.setXMinimum( halfClipRect1.xMaximum() );
}
else
{
halfClipRect1.setYMaximum( clipRect.yMinimum() + height / 2.0 );
halfClipRect2.setYMinimum( halfClipRect1.yMaximum() );
}
if ( height <= 0 )
{
halfClipRect1.setYMinimum( halfClipRect1.yMinimum() - std::numeric_limits<double>::epsilon() );
halfClipRect2.setYMinimum( halfClipRect2.yMinimum() - std::numeric_limits<double>::epsilon() );
halfClipRect1.setYMaximum( halfClipRect1.yMaximum() + std::numeric_limits<double>::epsilon() );
halfClipRect2.setYMaximum( halfClipRect2.yMaximum() + std::numeric_limits<double>::epsilon() );
}
if ( width <= 0 )
{
halfClipRect1.setXMinimum( halfClipRect1.xMinimum() - std::numeric_limits<double>::epsilon() );
halfClipRect2.setXMinimum( halfClipRect2.xMinimum() - std::numeric_limits<double>::epsilon() );
halfClipRect1.setXMaximum( halfClipRect1.xMaximum() + std::numeric_limits<double>::epsilon() );
halfClipRect2.setXMaximum( halfClipRect2.xMaximum() + std::numeric_limits<double>::epsilon() );
}
geos::unique_ptr clipPart1( GEOSClipByRect_r( geosinit.ctxt, currentPart, halfClipRect1.xMinimum(), halfClipRect1.yMinimum(), halfClipRect1.xMaximum(), halfClipRect1.yMaximum() ) );
geos::unique_ptr clipPart2( GEOSClipByRect_r( geosinit.ctxt, currentPart, halfClipRect2.xMinimum(), halfClipRect2.yMinimum(), halfClipRect2.xMaximum(), halfClipRect2.yMaximum() ) );
++depth;
if ( clipPart1 )
{
subdivideRecursive( clipPart1.get(), maxNodes, depth, parts, halfClipRect1 );
}
if ( clipPart2 )
{
subdivideRecursive( clipPart2.get(), maxNodes, depth, parts, halfClipRect2 );
}
}
std::unique_ptr<QgsAbstractGeometry> QgsGeos::subdivide( int maxNodes, QString *errorMsg ) const
{
if ( !mGeos )
{
return nullptr;
}
// minimum allowed max is 8
maxNodes = std::max( maxNodes, 8 );
std::unique_ptr< QgsGeometryCollection > parts = QgsGeometryFactory::createCollectionOfType( mGeometry->wkbType() );
try
{
subdivideRecursive( mGeos.get(), maxNodes, 0, parts.get(), mGeometry->boundingBox() );
}
CATCH_GEOS_WITH_ERRMSG( nullptr )
return std::move( parts );
}
QgsAbstractGeometry *QgsGeos::combine( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
return overlay( geom, OverlayUnion, errorMsg ).release();
}
QgsAbstractGeometry *QgsGeos::combine( const QVector<QgsAbstractGeometry *> &geomList, QString *errorMsg ) const
{
QVector< GEOSGeometry * > geosGeometries;
geosGeometries.reserve( geomList.size() );
for ( const QgsAbstractGeometry *g : geomList )
{
if ( !g )
continue;
geosGeometries << asGeos( g, mPrecision ).release();
}
geos::unique_ptr geomUnion;
try
{
geos::unique_ptr geomCollection = createGeosCollection( GEOS_GEOMETRYCOLLECTION, geosGeometries );
geomUnion.reset( GEOSUnaryUnion_r( geosinit.ctxt, geomCollection.get() ) );
}
CATCH_GEOS_WITH_ERRMSG( nullptr )
std::unique_ptr< QgsAbstractGeometry > result = fromGeos( geomUnion.get() );
return result.release();
}
QgsAbstractGeometry *QgsGeos::combine( const QVector<QgsGeometry> &geomList, QString *errorMsg ) const
{
QVector< GEOSGeometry * > geosGeometries;
geosGeometries.reserve( geomList.size() );
for ( const QgsGeometry &g : geomList )
{
if ( g.isNull() )
continue;
geosGeometries << asGeos( g.constGet(), mPrecision ).release();
}
geos::unique_ptr geomUnion;
try
{
geos::unique_ptr geomCollection = createGeosCollection( GEOS_GEOMETRYCOLLECTION, geosGeometries );
geomUnion.reset( GEOSUnaryUnion_r( geosinit.ctxt, geomCollection.get() ) );
}
CATCH_GEOS_WITH_ERRMSG( nullptr )
std::unique_ptr< QgsAbstractGeometry > result = fromGeos( geomUnion.get() );
return result.release();
}
QgsAbstractGeometry *QgsGeos::symDifference( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
return overlay( geom, OverlaySymDifference, errorMsg ).release();
}
double QgsGeos::distance( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
double distance = -1.0;
if ( !mGeos )
{
return distance;
}
geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
if ( !otherGeosGeom )
{
return distance;
}
try
{
GEOSDistance_r( geosinit.ctxt, mGeos.get(), otherGeosGeom.get(), &distance );
}
CATCH_GEOS_WITH_ERRMSG( -1.0 )
return distance;
}
double QgsGeos::hausdorffDistance( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
double distance = -1.0;
if ( !mGeos )
{
return distance;
}
geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
if ( !otherGeosGeom )
{
return distance;
}
try
{
GEOSHausdorffDistance_r( geosinit.ctxt, mGeos.get(), otherGeosGeom.get(), &distance );
}
CATCH_GEOS_WITH_ERRMSG( -1.0 )
return distance;
}
double QgsGeos::hausdorffDistanceDensify( const QgsAbstractGeometry *geom, double densifyFraction, QString *errorMsg ) const
{
double distance = -1.0;
if ( !mGeos )
{
return distance;
}
geos::unique_ptr otherGeosGeom( asGeos( geom, mPrecision ) );
if ( !otherGeosGeom )
{
return distance;
}
try
{
GEOSHausdorffDistanceDensify_r( geosinit.ctxt, mGeos.get(), otherGeosGeom.get(), densifyFraction, &distance );
}
CATCH_GEOS_WITH_ERRMSG( -1.0 )
return distance;
}
bool QgsGeos::intersects( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
return relation( geom, RelationIntersects, errorMsg );
}
bool QgsGeos::touches( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
return relation( geom, RelationTouches, errorMsg );
}
bool QgsGeos::crosses( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
return relation( geom, RelationCrosses, errorMsg );
}
bool QgsGeos::within( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
return relation( geom, RelationWithin, errorMsg );
}
bool QgsGeos::overlaps( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
return relation( geom, RelationOverlaps, errorMsg );
}
bool QgsGeos::contains( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
return relation( geom, RelationContains, errorMsg );
}
bool QgsGeos::disjoint( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
return relation( geom, RelationDisjoint, errorMsg );
}
QString QgsGeos::relate( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
if ( !mGeos )
{
return QString();
}
geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
if ( !geosGeom )
{
return QString();
}
QString result;
try
{
char *r = GEOSRelate_r( geosinit.ctxt, mGeos.get(), geosGeom.get() );
if ( r )
{
result = QString( r );
GEOSFree_r( geosinit.ctxt, r );
}
}
catch ( GEOSException &e )
{
if ( errorMsg )
{
*errorMsg = e.what();
}
}
return result;
}
bool QgsGeos::relatePattern( const QgsAbstractGeometry *geom, const QString &pattern, QString *errorMsg ) const
{
if ( !mGeos || !geom )
{
return false;
}
geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
if ( !geosGeom )
{
return false;
}
bool result = false;
try
{
result = ( GEOSRelatePattern_r( geosinit.ctxt, mGeos.get(), geosGeom.get(), pattern.toLocal8Bit().constData() ) == 1 );
}
catch ( GEOSException &e )
{
if ( errorMsg )
{
*errorMsg = e.what();
}
}
return result;
}
double QgsGeos::area( QString *errorMsg ) const
{
double area = -1.0;
if ( !mGeos )
{
return area;
}
try
{
if ( GEOSArea_r( geosinit.ctxt, mGeos.get(), &area ) != 1 )
return -1.0;
}
CATCH_GEOS_WITH_ERRMSG( -1.0 );
return area;
}
double QgsGeos::length( QString *errorMsg ) const
{
double length = -1.0;
if ( !mGeos )
{
return length;
}
try
{
if ( GEOSLength_r( geosinit.ctxt, mGeos.get(), &length ) != 1 )
return -1.0;
}
CATCH_GEOS_WITH_ERRMSG( -1.0 )
return length;
}
QgsGeometryEngine::EngineOperationResult QgsGeos::splitGeometry( const QgsLineString &splitLine,
QVector<QgsGeometry> &newGeometries,
bool topological,
QgsPointSequence &topologyTestPoints,
QString *errorMsg ) const
{
EngineOperationResult returnCode = Success;
if ( !mGeos || !mGeometry )
{
return InvalidBaseGeometry;
}
//return if this type is point/multipoint
if ( mGeometry->dimension() == 0 )
{
return SplitCannotSplitPoint; //cannot split points
}
if ( !GEOSisValid_r( geosinit.ctxt, mGeos.get() ) )
return InvalidBaseGeometry;
//make sure splitLine is valid
if ( ( mGeometry->dimension() == 1 && splitLine.numPoints() < 1 ) ||
( mGeometry->dimension() == 2 && splitLine.numPoints() < 2 ) )
return InvalidInput;
newGeometries.clear();
geos::unique_ptr splitLineGeos;
try
{
if ( splitLine.numPoints() > 1 )
{
splitLineGeos = createGeosLinestring( &splitLine, mPrecision );
}
else if ( splitLine.numPoints() == 1 )
{
splitLineGeos = createGeosPointXY( splitLine.xAt( 0 ), splitLine.yAt( 0 ), false, 0, false, 0, 2, mPrecision );
}
else
{
return InvalidInput;
}
if ( !GEOSisValid_r( geosinit.ctxt, splitLineGeos.get() ) || !GEOSisSimple_r( geosinit.ctxt, splitLineGeos.get() ) )
{
return InvalidInput;
}
if ( topological )
{
//find out candidate points for topological corrections
if ( !topologicalTestPointsSplit( splitLineGeos.get(), topologyTestPoints ) )
{
return InvalidInput; // TODO: is it really an invalid input?
}
}
//call split function depending on geometry type
if ( mGeometry->dimension() == 1 )
{
returnCode = splitLinearGeometry( splitLineGeos.get(), newGeometries );
}
else if ( mGeometry->dimension() == 2 )
{
returnCode = splitPolygonGeometry( splitLineGeos.get(), newGeometries );
}
else
{
return InvalidInput;
}
}
CATCH_GEOS_WITH_ERRMSG( EngineError )
return returnCode;
}
bool QgsGeos::topologicalTestPointsSplit( const GEOSGeometry *splitLine, QgsPointSequence &testPoints, QString *errorMsg ) const
{
//Find out the intersection points between splitLineGeos and this geometry.
//These points need to be tested for topological correctness by the calling function
//if topological editing is enabled
if ( !mGeos )
{
return false;
}
try
{
testPoints.clear();
geos::unique_ptr intersectionGeom( GEOSIntersection_r( geosinit.ctxt, mGeos.get(), splitLine ) );
if ( !intersectionGeom )
return false;
bool simple = false;
int nIntersectGeoms = 1;
if ( GEOSGeomTypeId_r( geosinit.ctxt, intersectionGeom.get() ) == GEOS_LINESTRING
|| GEOSGeomTypeId_r( geosinit.ctxt, intersectionGeom.get() ) == GEOS_POINT )
simple = true;
if ( !simple )
nIntersectGeoms = GEOSGetNumGeometries_r( geosinit.ctxt, intersectionGeom.get() );
for ( int i = 0; i < nIntersectGeoms; ++i )
{
const GEOSGeometry *currentIntersectGeom = nullptr;
if ( simple )
currentIntersectGeom = intersectionGeom.get();
else
currentIntersectGeom = GEOSGetGeometryN_r( geosinit.ctxt, intersectionGeom.get(), i );
const GEOSCoordSequence *lineSequence = GEOSGeom_getCoordSeq_r( geosinit.ctxt, currentIntersectGeom );
unsigned int sequenceSize = 0;
double x, y;
if ( GEOSCoordSeq_getSize_r( geosinit.ctxt, lineSequence, &sequenceSize ) != 0 )
{
for ( unsigned int i = 0; i < sequenceSize; ++i )
{
if ( GEOSCoordSeq_getX_r( geosinit.ctxt, lineSequence, i, &x ) != 0 )
{
if ( GEOSCoordSeq_getY_r( geosinit.ctxt, lineSequence, i, &y ) != 0 )
{
testPoints.push_back( QgsPoint( x, y ) );
}
}
}
}
}
}
CATCH_GEOS_WITH_ERRMSG( true )
return true;
}
geos::unique_ptr QgsGeos::linePointDifference( GEOSGeometry *GEOSsplitPoint ) const
{
int type = GEOSGeomTypeId_r( geosinit.ctxt, mGeos.get() );
std::unique_ptr< QgsMultiCurve > multiCurve;
if ( type == GEOS_MULTILINESTRING )
{
multiCurve.reset( qgsgeometry_cast<QgsMultiCurve *>( mGeometry->clone() ) );
}
else if ( type == GEOS_LINESTRING )
{
multiCurve.reset( new QgsMultiCurve() );
multiCurve->addGeometry( mGeometry->clone() );
}
else
{
return nullptr;
}
if ( !multiCurve )
{
return nullptr;
}
std::unique_ptr< QgsAbstractGeometry > splitGeom( fromGeos( GEOSsplitPoint ) );
QgsPoint *splitPoint = qgsgeometry_cast<QgsPoint *>( splitGeom.get() );
if ( !splitPoint )
{
return nullptr;
}
QgsMultiCurve lines;
//For each part
for ( int i = 0; i < multiCurve->numGeometries(); ++i )
{
const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( multiCurve->geometryN( i ) );
if ( line )
{
//For each segment
QgsLineString newLine;
newLine.addVertex( line->pointN( 0 ) );
int nVertices = line->numPoints();
for ( int j = 1; j < ( nVertices - 1 ); ++j )
{
QgsPoint currentPoint = line->pointN( j );
newLine.addVertex( currentPoint );
if ( currentPoint == *splitPoint )
{
lines.addGeometry( newLine.clone() );
newLine = QgsLineString();
newLine.addVertex( currentPoint );
}
}
newLine.addVertex( line->pointN( nVertices - 1 ) );
lines.addGeometry( newLine.clone() );
}
}
return asGeos( &lines, mPrecision );
}
QgsGeometryEngine::EngineOperationResult QgsGeos::splitLinearGeometry( GEOSGeometry *splitLine, QVector<QgsGeometry> &newGeometries ) const
{
if ( !splitLine )
return InvalidInput;
if ( !mGeos )
return InvalidBaseGeometry;
//first test if linestring intersects geometry. If not, return straight away
if ( !GEOSIntersects_r( geosinit.ctxt, splitLine, mGeos.get() ) )
return NothingHappened;
//check that split line has no linear intersection
int linearIntersect = GEOSRelatePattern_r( geosinit.ctxt, mGeos.get(), splitLine, "1********" );
if ( linearIntersect > 0 )
return InvalidInput;
int splitGeomType = GEOSGeomTypeId_r( geosinit.ctxt, splitLine );
geos::unique_ptr splitGeom;
if ( splitGeomType == GEOS_POINT )
{
splitGeom = linePointDifference( splitLine );
}
else
{
splitGeom.reset( GEOSDifference_r( geosinit.ctxt, mGeos.get(), splitLine ) );
}
QVector<GEOSGeometry *> lineGeoms;
int splitType = GEOSGeomTypeId_r( geosinit.ctxt, splitGeom.get() );
if ( splitType == GEOS_MULTILINESTRING )
{
int nGeoms = GEOSGetNumGeometries_r( geosinit.ctxt, splitGeom.get() );
lineGeoms.reserve( nGeoms );
for ( int i = 0; i < nGeoms; ++i )
lineGeoms << GEOSGeom_clone_r( geosinit.ctxt, GEOSGetGeometryN_r( geosinit.ctxt, splitGeom.get(), i ) );
}
else
{
lineGeoms << GEOSGeom_clone_r( geosinit.ctxt, splitGeom.get() );
}
mergeGeometriesMultiTypeSplit( lineGeoms );
for ( int i = 0; i < lineGeoms.size(); ++i )
{
newGeometries << QgsGeometry( fromGeos( lineGeoms[i] ) );
GEOSGeom_destroy_r( geosinit.ctxt, lineGeoms[i] );
}
return Success;
}
QgsGeometryEngine::EngineOperationResult QgsGeos::splitPolygonGeometry( GEOSGeometry *splitLine, QVector<QgsGeometry> &newGeometries ) const
{
if ( !splitLine )
return InvalidInput;
if ( !mGeos )
return InvalidBaseGeometry;
//first test if linestring intersects geometry. If not, return straight away
if ( !GEOSIntersects_r( geosinit.ctxt, splitLine, mGeos.get() ) )
return NothingHappened;
//first union all the polygon rings together (to get them noded, see JTS developer guide)
geos::unique_ptr nodedGeometry = nodeGeometries( splitLine, mGeos.get() );
if ( !nodedGeometry )
return NodedGeometryError; //an error occurred during noding
const GEOSGeometry *noded = nodedGeometry.get();
geos::unique_ptr polygons( GEOSPolygonize_r( geosinit.ctxt, &noded, 1 ) );
if ( !polygons || numberOfGeometries( polygons.get() ) == 0 )
{
return InvalidBaseGeometry;
}
//test every polygon if contained in original geometry
//include in result if yes
QVector<GEOSGeometry *> testedGeometries;
geos::unique_ptr intersectGeometry;
//ratio intersect geometry / geometry. This should be close to 1
//if the polygon belongs to the input geometry
for ( int i = 0; i < numberOfGeometries( polygons.get() ); i++ )
{
const GEOSGeometry *polygon = GEOSGetGeometryN_r( geosinit.ctxt, polygons.get(), i );
intersectGeometry.reset( GEOSIntersection_r( geosinit.ctxt, mGeos.get(), polygon ) );
if ( !intersectGeometry )
{
QgsDebugMsg( QStringLiteral( "intersectGeometry is nullptr" ) );
continue;
}
double intersectionArea;
GEOSArea_r( geosinit.ctxt, intersectGeometry.get(), &intersectionArea );
double polygonArea;
GEOSArea_r( geosinit.ctxt, polygon, &polygonArea );
const double areaRatio = intersectionArea / polygonArea;
if ( areaRatio > 0.99 && areaRatio < 1.01 )
testedGeometries << GEOSGeom_clone_r( geosinit.ctxt, polygon );
}
int nGeometriesThis = numberOfGeometries( mGeos.get() ); //original number of geometries
if ( testedGeometries.empty() || testedGeometries.size() == nGeometriesThis )
{
//no split done, preserve original geometry
for ( int i = 0; i < testedGeometries.size(); ++i )
{
GEOSGeom_destroy_r( geosinit.ctxt, testedGeometries[i] );
}
return NothingHappened;
}
mergeGeometriesMultiTypeSplit( testedGeometries );
int i;
for ( i = 0; i < testedGeometries.size() && GEOSisValid_r( geosinit.ctxt, testedGeometries[i] ); ++i )
;
if ( i < testedGeometries.size() )
{
for ( i = 0; i < testedGeometries.size(); ++i )
GEOSGeom_destroy_r( geosinit.ctxt, testedGeometries[i] );
return InvalidBaseGeometry;
}
for ( i = 0; i < testedGeometries.size(); ++i )
{
newGeometries << QgsGeometry( fromGeos( testedGeometries[i] ) );
GEOSGeom_destroy_r( geosinit.ctxt, testedGeometries[i] );
}
return Success;
}
geos::unique_ptr QgsGeos::nodeGeometries( const GEOSGeometry *splitLine, const GEOSGeometry *geom )
{
if ( !splitLine || !geom )
return nullptr;
geos::unique_ptr geometryBoundary;
if ( GEOSGeomTypeId_r( geosinit.ctxt, geom ) == GEOS_POLYGON || GEOSGeomTypeId_r( geosinit.ctxt, geom ) == GEOS_MULTIPOLYGON )
geometryBoundary.reset( GEOSBoundary_r( geosinit.ctxt, geom ) );
else
geometryBoundary.reset( GEOSGeom_clone_r( geosinit.ctxt, geom ) );
geos::unique_ptr splitLineClone( GEOSGeom_clone_r( geosinit.ctxt, splitLine ) );
geos::unique_ptr unionGeometry( GEOSUnion_r( geosinit.ctxt, splitLineClone.get(), geometryBoundary.get() ) );
return unionGeometry;
}
int QgsGeos::mergeGeometriesMultiTypeSplit( QVector<GEOSGeometry *> &splitResult ) const
{
if ( !mGeos )
return 1;
//convert mGeos to geometry collection
int type = GEOSGeomTypeId_r( geosinit.ctxt, mGeos.get() );
if ( type != GEOS_GEOMETRYCOLLECTION &&
type != GEOS_MULTILINESTRING &&
type != GEOS_MULTIPOLYGON &&
type != GEOS_MULTIPOINT )
return 0;
QVector<GEOSGeometry *> copyList = splitResult;
splitResult.clear();
//collect all the geometries that belong to the initial multifeature
QVector<GEOSGeometry *> unionGeom;
for ( int i = 0; i < copyList.size(); ++i )
{
//is this geometry a part of the original multitype?
bool isPart = false;
for ( int j = 0; j < GEOSGetNumGeometries_r( geosinit.ctxt, mGeos.get() ); j++ )
{
if ( GEOSEquals_r( geosinit.ctxt, copyList[i], GEOSGetGeometryN_r( geosinit.ctxt, mGeos.get(), j ) ) )
{
isPart = true;
break;
}
}
if ( isPart )
{
unionGeom << copyList[i];
}
else
{
QVector<GEOSGeometry *> geomVector;
geomVector << copyList[i];
if ( type == GEOS_MULTILINESTRING )
splitResult << createGeosCollection( GEOS_MULTILINESTRING, geomVector ).release();
else if ( type == GEOS_MULTIPOLYGON )
splitResult << createGeosCollection( GEOS_MULTIPOLYGON, geomVector ).release();
else
GEOSGeom_destroy_r( geosinit.ctxt, copyList[i] );
}
}
//make multifeature out of unionGeom
if ( !unionGeom.isEmpty() )
{
if ( type == GEOS_MULTILINESTRING )
splitResult << createGeosCollection( GEOS_MULTILINESTRING, unionGeom ).release();
else if ( type == GEOS_MULTIPOLYGON )
splitResult << createGeosCollection( GEOS_MULTIPOLYGON, unionGeom ).release();
}
else
{
unionGeom.clear();
}
return 0;
}
geos::unique_ptr QgsGeos::createGeosCollection( int typeId, const QVector<GEOSGeometry *> &geoms )
{
int nNullGeoms = geoms.count( nullptr );
int nNotNullGeoms = geoms.size() - nNullGeoms;
GEOSGeometry **geomarr = new GEOSGeometry*[ nNotNullGeoms ];
if ( !geomarr )
{
return nullptr;
}
int i = 0;
QVector<GEOSGeometry *>::const_iterator geomIt = geoms.constBegin();
for ( ; geomIt != geoms.constEnd(); ++geomIt )
{
if ( *geomIt )
{
geomarr[i] = *geomIt;
++i;
}
}
geos::unique_ptr geom;
try
{
geom.reset( GEOSGeom_createCollection_r( geosinit.ctxt, typeId, geomarr, nNotNullGeoms ) );
}
catch ( GEOSException & )
{
}
delete [] geomarr;
return geom;
}
std::unique_ptr<QgsAbstractGeometry> QgsGeos::fromGeos( const GEOSGeometry *geos )
{
if ( !geos )
{
return nullptr;
}
int nCoordDims = GEOSGeom_getCoordinateDimension_r( geosinit.ctxt, geos );
int nDims = GEOSGeom_getDimensions_r( geosinit.ctxt, geos );
bool hasZ = ( nCoordDims == 3 );
bool hasM = ( ( nDims - nCoordDims ) == 1 );
switch ( GEOSGeomTypeId_r( geosinit.ctxt, geos ) )
{
case GEOS_POINT: // a point
{
const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosinit.ctxt, geos );
return std::unique_ptr<QgsAbstractGeometry>( coordSeqPoint( cs, 0, hasZ, hasM ).clone() );
}
case GEOS_LINESTRING:
{
return sequenceToLinestring( geos, hasZ, hasM );
}
case GEOS_POLYGON:
{
return fromGeosPolygon( geos );
}
case GEOS_MULTIPOINT:
{
std::unique_ptr< QgsMultiPoint > multiPoint( new QgsMultiPoint() );
int nParts = GEOSGetNumGeometries_r( geosinit.ctxt, geos );
for ( int i = 0; i < nParts; ++i )
{
const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosinit.ctxt, GEOSGetGeometryN_r( geosinit.ctxt, geos, i ) );
if ( cs )
{
multiPoint->addGeometry( coordSeqPoint( cs, 0, hasZ, hasM ).clone() );
}
}
return std::move( multiPoint );
}
case GEOS_MULTILINESTRING:
{
std::unique_ptr< QgsMultiLineString > multiLineString( new QgsMultiLineString() );
int nParts = GEOSGetNumGeometries_r( geosinit.ctxt, geos );
for ( int i = 0; i < nParts; ++i )
{
std::unique_ptr< QgsLineString >line( sequenceToLinestring( GEOSGetGeometryN_r( geosinit.ctxt, geos, i ), hasZ, hasM ) );
if ( line )
{
multiLineString->addGeometry( line.release() );
}
}
return std::move( multiLineString );
}
case GEOS_MULTIPOLYGON:
{
std::unique_ptr< QgsMultiPolygon > multiPolygon( new QgsMultiPolygon() );
int nParts = GEOSGetNumGeometries_r( geosinit.ctxt, geos );
for ( int i = 0; i < nParts; ++i )
{
std::unique_ptr< QgsPolygon > poly = fromGeosPolygon( GEOSGetGeometryN_r( geosinit.ctxt, geos, i ) );
if ( poly )
{
multiPolygon->addGeometry( poly.release() );
}
}
return std::move( multiPolygon );
}
case GEOS_GEOMETRYCOLLECTION:
{
std::unique_ptr< QgsGeometryCollection > geomCollection( new QgsGeometryCollection() );
int nParts = GEOSGetNumGeometries_r( geosinit.ctxt, geos );
for ( int i = 0; i < nParts; ++i )
{
std::unique_ptr< QgsAbstractGeometry > geom( fromGeos( GEOSGetGeometryN_r( geosinit.ctxt, geos, i ) ) );
if ( geom )
{
geomCollection->addGeometry( geom.release() );
}
}
return std::move( geomCollection );
}
}
return nullptr;
}
std::unique_ptr<QgsPolygon> QgsGeos::fromGeosPolygon( const GEOSGeometry *geos )
{
if ( GEOSGeomTypeId_r( geosinit.ctxt, geos ) != GEOS_POLYGON )
{
return nullptr;
}
int nCoordDims = GEOSGeom_getCoordinateDimension_r( geosinit.ctxt, geos );
int nDims = GEOSGeom_getDimensions_r( geosinit.ctxt, geos );
bool hasZ = ( nCoordDims == 3 );
bool hasM = ( ( nDims - nCoordDims ) == 1 );
std::unique_ptr< QgsPolygon > polygon( new QgsPolygon() );
const GEOSGeometry *ring = GEOSGetExteriorRing_r( geosinit.ctxt, geos );
if ( ring )
{
polygon->setExteriorRing( sequenceToLinestring( ring, hasZ, hasM ).release() );
}
QVector<QgsCurve *> interiorRings;
const int ringCount = GEOSGetNumInteriorRings_r( geosinit.ctxt, geos );
interiorRings.reserve( ringCount );
for ( int i = 0; i < ringCount; ++i )
{
ring = GEOSGetInteriorRingN_r( geosinit.ctxt, geos, i );
if ( ring )
{
interiorRings.push_back( sequenceToLinestring( ring, hasZ, hasM ).release() );
}
}
polygon->setInteriorRings( interiorRings );
return polygon;
}
std::unique_ptr<QgsLineString> QgsGeos::sequenceToLinestring( const GEOSGeometry *geos, bool hasZ, bool hasM )
{
const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosinit.ctxt, geos );
unsigned int nPoints;
GEOSCoordSeq_getSize_r( geosinit.ctxt, cs, &nPoints );
QVector< double > xOut( nPoints );
QVector< double > yOut( nPoints );
QVector< double > zOut;
if ( hasZ )
zOut.resize( nPoints );
QVector< double > mOut;
if ( hasM )
mOut.resize( nPoints );
double *x = xOut.data();
double *y = yOut.data();
double *z = zOut.data();
double *m = mOut.data();
for ( unsigned int i = 0; i < nPoints; ++i )
{
GEOSCoordSeq_getX_r( geosinit.ctxt, cs, i, x++ );
GEOSCoordSeq_getY_r( geosinit.ctxt, cs, i, y++ );
if ( hasZ )
{
GEOSCoordSeq_getZ_r( geosinit.ctxt, cs, i, z++ );
}
if ( hasM )
{
GEOSCoordSeq_getOrdinate_r( geosinit.ctxt, cs, i, 3, m++ );
}
}
std::unique_ptr< QgsLineString > line( new QgsLineString( xOut, yOut, zOut, mOut ) );
return line;
}
int QgsGeos::numberOfGeometries( GEOSGeometry *g )
{
if ( !g )
return 0;
int geometryType = GEOSGeomTypeId_r( geosinit.ctxt, g );
if ( geometryType == GEOS_POINT || geometryType == GEOS_LINESTRING || geometryType == GEOS_LINEARRING
|| geometryType == GEOS_POLYGON )
return 1;
//calling GEOSGetNumGeometries is save for multi types and collections also in geos2
return GEOSGetNumGeometries_r( geosinit.ctxt, g );
}
QgsPoint QgsGeos::coordSeqPoint( const GEOSCoordSequence *cs, int i, bool hasZ, bool hasM )
{
if ( !cs )
{
return QgsPoint();
}
double x, y;
double z = 0;
double m = 0;
GEOSCoordSeq_getX_r( geosinit.ctxt, cs, i, &x );
GEOSCoordSeq_getY_r( geosinit.ctxt, cs, i, &y );
if ( hasZ )
{
GEOSCoordSeq_getZ_r( geosinit.ctxt, cs, i, &z );
}
if ( hasM )
{
GEOSCoordSeq_getOrdinate_r( geosinit.ctxt, cs, i, 3, &m );
}
QgsWkbTypes::Type t = QgsWkbTypes::Point;
if ( hasZ && hasM )
{
t = QgsWkbTypes::PointZM;
}
else if ( hasZ )
{
t = QgsWkbTypes::PointZ;
}
else if ( hasM )
{
t = QgsWkbTypes::PointM;
}
return QgsPoint( t, x, y, z, m );
}
geos::unique_ptr QgsGeos::asGeos( const QgsAbstractGeometry *geom, double precision )
{
if ( !geom )
return nullptr;
int coordDims = 2;
if ( geom->is3D() )
{
++coordDims;
}
if ( geom->isMeasure() )
{
++coordDims;
}
if ( QgsWkbTypes::isMultiType( geom->wkbType() ) || QgsWkbTypes::flatType( geom->wkbType() ) == QgsWkbTypes::GeometryCollection )
{
int geosType = GEOS_GEOMETRYCOLLECTION;
if ( QgsWkbTypes::flatType( geom->wkbType() ) != QgsWkbTypes::GeometryCollection )
{
switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
{
case QgsWkbTypes::PointGeometry:
geosType = GEOS_MULTIPOINT;
break;
case QgsWkbTypes::LineGeometry:
geosType = GEOS_MULTILINESTRING;
break;
case QgsWkbTypes::PolygonGeometry:
geosType = GEOS_MULTIPOLYGON;
break;
case QgsWkbTypes::UnknownGeometry:
case QgsWkbTypes::NullGeometry:
return nullptr;
break;
}
}
const QgsGeometryCollection *c = qgsgeometry_cast<const QgsGeometryCollection *>( geom );
if ( !c )
return nullptr;
QVector< GEOSGeometry * > geomVector( c->numGeometries() );
for ( int i = 0; i < c->numGeometries(); ++i )
{
geomVector[i] = asGeos( c->geometryN( i ), precision ).release();
}
return createGeosCollection( geosType, geomVector );
}
else
{
switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
{
case QgsWkbTypes::PointGeometry:
return createGeosPoint( static_cast<const QgsPoint *>( geom ), coordDims, precision );
break;
case QgsWkbTypes::LineGeometry:
return createGeosLinestring( static_cast<const QgsLineString *>( geom ), precision );
break;
case QgsWkbTypes::PolygonGeometry:
return createGeosPolygon( static_cast<const QgsPolygon *>( geom ), precision );
break;
case QgsWkbTypes::UnknownGeometry:
case QgsWkbTypes::NullGeometry:
return nullptr;
break;
}
}
return nullptr;
}
std::unique_ptr<QgsAbstractGeometry> QgsGeos::overlay( const QgsAbstractGeometry *geom, Overlay op, QString *errorMsg ) const
{
if ( !mGeos || !geom )
{
return nullptr;
}
geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
if ( !geosGeom )
{
return nullptr;
}
try
{
geos::unique_ptr opGeom;
switch ( op )
{
case OverlayIntersection:
opGeom.reset( GEOSIntersection_r( geosinit.ctxt, mGeos.get(), geosGeom.get() ) );
break;
case OverlayDifference:
opGeom.reset( GEOSDifference_r( geosinit.ctxt, mGeos.get(), geosGeom.get() ) );
break;
case OverlayUnion:
{
geos::unique_ptr unionGeometry( GEOSUnion_r( geosinit.ctxt, mGeos.get(), geosGeom.get() ) );
if ( unionGeometry && GEOSGeomTypeId_r( geosinit.ctxt, unionGeometry.get() ) == GEOS_MULTILINESTRING )
{
geos::unique_ptr mergedLines( GEOSLineMerge_r( geosinit.ctxt, unionGeometry.get() ) );
if ( mergedLines )
{
unionGeometry = std::move( mergedLines );
}
}
opGeom = std::move( unionGeometry );
}
break;
case OverlaySymDifference:
opGeom.reset( GEOSSymDifference_r( geosinit.ctxt, mGeos.get(), geosGeom.get() ) );
break;
default: //unknown op
return nullptr;
}
return fromGeos( opGeom.get() );
}
catch ( GEOSException &e )
{
if ( errorMsg )
{
*errorMsg = e.what();
}
return nullptr;
}
}
bool QgsGeos::relation( const QgsAbstractGeometry *geom, Relation r, QString *errorMsg ) const
{
if ( !mGeos || !geom )
{
return false;
}
geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
if ( !geosGeom )
{
return false;
}
bool result = false;
try
{
if ( mGeosPrepared ) //use faster version with prepared geometry
{
switch ( r )
{
case RelationIntersects:
result = ( GEOSPreparedIntersects_r( geosinit.ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
break;
case RelationTouches:
result = ( GEOSPreparedTouches_r( geosinit.ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
break;
case RelationCrosses:
result = ( GEOSPreparedCrosses_r( geosinit.ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
break;
case RelationWithin:
result = ( GEOSPreparedWithin_r( geosinit.ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
break;
case RelationContains:
result = ( GEOSPreparedContains_r( geosinit.ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
break;
case RelationDisjoint:
result = ( GEOSPreparedDisjoint_r( geosinit.ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
break;
case RelationOverlaps:
result = ( GEOSPreparedOverlaps_r( geosinit.ctxt, mGeosPrepared.get(), geosGeom.get() ) == 1 );
break;
default:
return false;
}
return result;
}
switch ( r )
{
case RelationIntersects:
result = ( GEOSIntersects_r( geosinit.ctxt, mGeos.get(), geosGeom.get() ) == 1 );
break;
case RelationTouches:
result = ( GEOSTouches_r( geosinit.ctxt, mGeos.get(), geosGeom.get() ) == 1 );
break;
case RelationCrosses:
result = ( GEOSCrosses_r( geosinit.ctxt, mGeos.get(), geosGeom.get() ) == 1 );
break;
case RelationWithin:
result = ( GEOSWithin_r( geosinit.ctxt, mGeos.get(), geosGeom.get() ) == 1 );
break;
case RelationContains:
result = ( GEOSContains_r( geosinit.ctxt, mGeos.get(), geosGeom.get() ) == 1 );
break;
case RelationDisjoint:
result = ( GEOSDisjoint_r( geosinit.ctxt, mGeos.get(), geosGeom.get() ) == 1 );
break;
case RelationOverlaps:
result = ( GEOSOverlaps_r( geosinit.ctxt, mGeos.get(), geosGeom.get() ) == 1 );
break;
default:
return false;
}
}
catch ( GEOSException &e )
{
if ( errorMsg )
{
*errorMsg = e.what();
}
return false;
}
return result;
}
QgsAbstractGeometry *QgsGeos::buffer( double distance, int segments, QString *errorMsg ) const
{
if ( !mGeos )
{
return nullptr;
}
geos::unique_ptr geos;
try
{
geos.reset( GEOSBuffer_r( geosinit.ctxt, mGeos.get(), distance, segments ) );
}
CATCH_GEOS_WITH_ERRMSG( nullptr );
return fromGeos( geos.get() ).release();
}
QgsAbstractGeometry *QgsGeos::buffer( double distance, int segments, int endCapStyle, int joinStyle, double miterLimit, QString *errorMsg ) const
{
if ( !mGeos )
{
return nullptr;
}
geos::unique_ptr geos;
try
{
geos.reset( GEOSBufferWithStyle_r( geosinit.ctxt, mGeos.get(), distance, segments, endCapStyle, joinStyle, miterLimit ) );
}
CATCH_GEOS_WITH_ERRMSG( nullptr );
return fromGeos( geos.get() ).release();
}
QgsAbstractGeometry *QgsGeos::simplify( double tolerance, QString *errorMsg ) const
{
if ( !mGeos )
{
return nullptr;
}
geos::unique_ptr geos;
try
{
geos.reset( GEOSTopologyPreserveSimplify_r( geosinit.ctxt, mGeos.get(), tolerance ) );
}
CATCH_GEOS_WITH_ERRMSG( nullptr );
return fromGeos( geos.get() ).release();
}
QgsAbstractGeometry *QgsGeos::interpolate( double distance, QString *errorMsg ) const
{
if ( !mGeos )
{
return nullptr;
}
geos::unique_ptr geos;
try
{
geos.reset( GEOSInterpolate_r( geosinit.ctxt, mGeos.get(), distance ) );
}
CATCH_GEOS_WITH_ERRMSG( nullptr );
return fromGeos( geos.get() ).release();
}
QgsPoint *QgsGeos::centroid( QString *errorMsg ) const
{
if ( !mGeos )
{
return nullptr;
}
geos::unique_ptr geos;
double x;
double y;
try
{
geos.reset( GEOSGetCentroid_r( geosinit.ctxt, mGeos.get() ) );
if ( !geos )
return nullptr;
GEOSGeomGetX_r( geosinit.ctxt, geos.get(), &x );
GEOSGeomGetY_r( geosinit.ctxt, geos.get(), &y );
}
CATCH_GEOS_WITH_ERRMSG( nullptr );
return new QgsPoint( x, y );
}
QgsAbstractGeometry *QgsGeos::envelope( QString *errorMsg ) const
{
if ( !mGeos )
{
return nullptr;
}
geos::unique_ptr geos;
try
{
geos.reset( GEOSEnvelope_r( geosinit.ctxt, mGeos.get() ) );
}
CATCH_GEOS_WITH_ERRMSG( nullptr );
return fromGeos( geos.get() ).release();
}
QgsPoint *QgsGeos::pointOnSurface( QString *errorMsg ) const
{
if ( !mGeos )
{
return nullptr;
}
double x;
double y;
geos::unique_ptr geos;
try
{
geos.reset( GEOSPointOnSurface_r( geosinit.ctxt, mGeos.get() ) );
if ( !geos || GEOSisEmpty_r( geosinit.ctxt, geos.get() ) != 0 )
{
return nullptr;
}
GEOSGeomGetX_r( geosinit.ctxt, geos.get(), &x );
GEOSGeomGetY_r( geosinit.ctxt, geos.get(), &y );
}
CATCH_GEOS_WITH_ERRMSG( nullptr );
return new QgsPoint( x, y );
}
QgsAbstractGeometry *QgsGeos::convexHull( QString *errorMsg ) const
{
if ( !mGeos )
{
return nullptr;
}
try
{
geos::unique_ptr cHull( GEOSConvexHull_r( geosinit.ctxt, mGeos.get() ) );
std::unique_ptr< QgsAbstractGeometry > cHullGeom = fromGeos( cHull.get() );
return cHullGeom.release();
}
CATCH_GEOS_WITH_ERRMSG( nullptr );
}
bool QgsGeos::isValid( QString *errorMsg ) const
{
if ( !mGeos )
{
return false;
}
try
{
return GEOSisValid_r( geosinit.ctxt, mGeos.get() );
}
CATCH_GEOS_WITH_ERRMSG( false );
}
bool QgsGeos::isEqual( const QgsAbstractGeometry *geom, QString *errorMsg ) const
{
if ( !mGeos || !geom )
{
return false;
}
try
{
geos::unique_ptr geosGeom( asGeos( geom, mPrecision ) );
if ( !geosGeom )
{
return false;
}
bool equal = GEOSEquals_r( geosinit.ctxt, mGeos.get(), geosGeom.get() );
return equal;
}
CATCH_GEOS_WITH_ERRMSG( false );
}
bool QgsGeos::isEmpty( QString *errorMsg ) const
{
if ( !mGeos )
{
return false;
}
try
{
return GEOSisEmpty_r( geosinit.ctxt, mGeos.get() );
}
CATCH_GEOS_WITH_ERRMSG( false );
}
bool QgsGeos::isSimple( QString *errorMsg ) const
{
if ( !mGeos )
{
return false;
}
try
{
return GEOSisSimple_r( geosinit.ctxt, mGeos.get() );
}
CATCH_GEOS_WITH_ERRMSG( false );
}
GEOSCoordSequence *QgsGeos::createCoordinateSequence( const QgsCurve *curve, double precision, bool forceClose )
{
std::unique_ptr< QgsLineString > segmentized;
const QgsLineString *line = qgsgeometry_cast<const QgsLineString *>( curve );
if ( !line )
{
segmentized.reset( curve->curveToLine() );
line = segmentized.get();
}
if ( !line )
{
return nullptr;
}
bool hasZ = line->is3D();
bool hasM = false; //line->isMeasure(); //disabled until geos supports m-coordinates
int coordDims = 2;
if ( hasZ )
{
++coordDims;
}
if ( hasM )
{
++coordDims;
}
int numPoints = line->numPoints();
int numOutPoints = numPoints;
if ( forceClose && ( line->pointN( 0 ) != line->pointN( numPoints - 1 ) ) )
{
++numOutPoints;
}
GEOSCoordSequence *coordSeq = nullptr;
try
{
coordSeq = GEOSCoordSeq_create_r( geosinit.ctxt, numOutPoints, coordDims );
if ( !coordSeq )
{
QgsDebugMsg( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for %1 points in %2 dimensions" ).arg( numPoints ).arg( coordDims ) );
return nullptr;
}
const double *xData = line->xData();
const double *yData = line->yData();
const double *zData = hasZ ? line->zData() : nullptr;
const double *mData = hasM ? line->mData() : nullptr;
if ( precision > 0. )
{
for ( int i = 0; i < numOutPoints; ++i )
{
if ( i >= numPoints )
{
// start reading back from start of line
xData = line->xData();
yData = line->yData();
zData = hasZ ? line->zData() : nullptr;
mData = hasM ? line->mData() : nullptr;
}
GEOSCoordSeq_setX_r( geosinit.ctxt, coordSeq, i, std::round( *xData++ / precision ) * precision );
GEOSCoordSeq_setY_r( geosinit.ctxt, coordSeq, i, std::round( *yData++ / precision ) * precision );
if ( hasZ )
{
GEOSCoordSeq_setOrdinate_r( geosinit.ctxt, coordSeq, i, 2, std::round( *zData++ / precision ) * precision );
}
if ( hasM )
{
GEOSCoordSeq_setOrdinate_r( geosinit.ctxt, coordSeq, i, 3, line->mAt( *mData++ ) );
}
}
}
else
{
for ( int i = 0; i < numOutPoints; ++i )
{
if ( i >= numPoints )
{
// start reading back from start of line
xData = line->xData();
yData = line->yData();
zData = hasZ ? line->zData() : nullptr;
mData = hasM ? line->mData() : nullptr;
}
GEOSCoordSeq_setX_r( geosinit.ctxt, coordSeq, i, *xData++ );
GEOSCoordSeq_setY_r( geosinit.ctxt, coordSeq, i, *yData++ );
if ( hasZ )
{
GEOSCoordSeq_setOrdinate_r( geosinit.ctxt, coordSeq, i, 2, *zData++ );
}
if ( hasM )
{
GEOSCoordSeq_setOrdinate_r( geosinit.ctxt, coordSeq, i, 3, *mData++ );
}
}
}
}
CATCH_GEOS( nullptr )
return coordSeq;
}
geos::unique_ptr QgsGeos::createGeosPoint( const QgsAbstractGeometry *point, int coordDims, double precision )
{
const QgsPoint *pt = qgsgeometry_cast<const QgsPoint *>( point );
if ( !pt )
return nullptr;
return createGeosPointXY( pt->x(), pt->y(), pt->is3D(), pt->z(), pt->isMeasure(), pt->m(), coordDims, precision );
}
geos::unique_ptr QgsGeos::createGeosPointXY( double x, double y, bool hasZ, double z, bool hasM, double m, int coordDims, double precision )
{
Q_UNUSED( hasM );
Q_UNUSED( m );
geos::unique_ptr geosPoint;
try
{
GEOSCoordSequence *coordSeq = GEOSCoordSeq_create_r( geosinit.ctxt, 1, coordDims );
if ( !coordSeq )
{
QgsDebugMsg( QStringLiteral( "GEOS Exception: Could not create coordinate sequence for point with %1 dimensions" ).arg( coordDims ) );
return nullptr;
}
if ( precision > 0. )
{
GEOSCoordSeq_setX_r( geosinit.ctxt, coordSeq, 0, std::round( x / precision ) * precision );
GEOSCoordSeq_setY_r( geosinit.ctxt, coordSeq, 0, std::round( y / precision ) * precision );
if ( hasZ )
{
GEOSCoordSeq_setOrdinate_r( geosinit.ctxt, coordSeq, 0, 2, std::round( z / precision ) * precision );
}
}
else
{
GEOSCoordSeq_setX_r( geosinit.ctxt, coordSeq, 0, x );
GEOSCoordSeq_setY_r( geosinit.ctxt, coordSeq, 0, y );
if ( hasZ )
{
GEOSCoordSeq_setOrdinate_r( geosinit.ctxt, coordSeq, 0, 2, z );
}
}
#if 0 //disabled until geos supports m-coordinates
if ( hasM )
{
GEOSCoordSeq_setOrdinate_r( geosinit.ctxt, coordSeq, 0, 3, m );
}
#endif
geosPoint.reset( GEOSGeom_createPoint_r( geosinit.ctxt, coordSeq ) );
}
CATCH_GEOS( nullptr )
return geosPoint;
}
geos::unique_ptr QgsGeos::createGeosLinestring( const QgsAbstractGeometry *curve, double precision )
{
const QgsCurve *c = qgsgeometry_cast<const QgsCurve *>( curve );
if ( !c )
return nullptr;
GEOSCoordSequence *coordSeq = createCoordinateSequence( c, precision );
if ( !coordSeq )
return nullptr;
geos::unique_ptr geosGeom;
try
{
geosGeom.reset( GEOSGeom_createLineString_r( geosinit.ctxt, coordSeq ) );
}
CATCH_GEOS( nullptr )
return geosGeom;
}
geos::unique_ptr QgsGeos::createGeosPolygon( const QgsAbstractGeometry *poly, double precision )
{
const QgsCurvePolygon *polygon = qgsgeometry_cast<const QgsCurvePolygon *>( poly );
if ( !polygon )
return nullptr;
const QgsCurve *exteriorRing = polygon->exteriorRing();
if ( !exteriorRing )
{
return nullptr;
}
geos::unique_ptr geosPolygon;
try
{
geos::unique_ptr exteriorRingGeos( GEOSGeom_createLinearRing_r( geosinit.ctxt, createCoordinateSequence( exteriorRing, precision, true ) ) );
int nHoles = polygon->numInteriorRings();
GEOSGeometry **holes = nullptr;
if ( nHoles > 0 )
{
holes = new GEOSGeometry*[ nHoles ];
}
for ( int i = 0; i < nHoles; ++i )
{
const QgsCurve *interiorRing = polygon->interiorRing( i );
holes[i] = GEOSGeom_createLinearRing_r( geosinit.ctxt, createCoordinateSequence( interiorRing, precision, true ) );
}
geosPolygon.reset( GEOSGeom_createPolygon_r( geosinit.ctxt, exteriorRingGeos.release(), holes, nHoles ) );
delete[] holes;
}
CATCH_GEOS( nullptr )
return geosPolygon;
}
QgsAbstractGeometry *QgsGeos::offsetCurve( double distance, int segments, int joinStyle, double miterLimit, QString *errorMsg ) const
{
if ( !mGeos )
return nullptr;
geos::unique_ptr offset;
try
{
offset.reset( GEOSOffsetCurve_r( geosinit.ctxt, mGeos.get(), distance, segments, joinStyle, miterLimit ) );
}
CATCH_GEOS_WITH_ERRMSG( nullptr )
std::unique_ptr< QgsAbstractGeometry > offsetGeom = fromGeos( offset.get() );
return offsetGeom.release();
}
std::unique_ptr<QgsAbstractGeometry> QgsGeos::singleSidedBuffer( double distance, int segments, int side, int joinStyle, double miterLimit, QString *errorMsg ) const
{
if ( !mGeos )
{
return nullptr;
}
geos::unique_ptr geos;
try
{
geos::buffer_params_unique_ptr bp( GEOSBufferParams_create_r( geosinit.ctxt ) );
GEOSBufferParams_setSingleSided_r( geosinit.ctxt, bp.get(), 1 );
GEOSBufferParams_setQuadrantSegments_r( geosinit.ctxt, bp.get(), segments );
GEOSBufferParams_setJoinStyle_r( geosinit.ctxt, bp.get(), joinStyle );
GEOSBufferParams_setMitreLimit_r( geosinit.ctxt, bp.get(), miterLimit ); //#spellok
if ( side == 1 )
{
distance = -distance;
}
geos.reset( GEOSBufferWithParams_r( geosinit.ctxt, mGeos.get(), bp.get(), distance ) );
}
CATCH_GEOS_WITH_ERRMSG( nullptr );
return fromGeos( geos.get() );
}
std::unique_ptr<QgsAbstractGeometry> QgsGeos::reshapeGeometry( const QgsLineString &reshapeWithLine, EngineOperationResult *errorCode, QString *errorMsg ) const
{
if ( !mGeos || mGeometry->dimension() == 0 )
{
if ( errorCode ) { *errorCode = InvalidBaseGeometry; }
return nullptr;
}
if ( reshapeWithLine.numPoints() < 2 )
{
if ( errorCode ) { *errorCode = InvalidInput; }
return nullptr;
}
geos::unique_ptr reshapeLineGeos = createGeosLinestring( &reshapeWithLine, mPrecision );
//single or multi?
int numGeoms = GEOSGetNumGeometries_r( geosinit.ctxt, mGeos.get() );
if ( numGeoms == -1 )
{
if ( errorCode )
{
*errorCode = InvalidBaseGeometry;
}
return nullptr;
}
bool isMultiGeom = false;
int geosTypeId = GEOSGeomTypeId_r( geosinit.ctxt, mGeos.get() );
if ( geosTypeId == GEOS_MULTILINESTRING || geosTypeId == GEOS_MULTIPOLYGON )
isMultiGeom = true;
bool isLine = ( mGeometry->dimension() == 1 );
if ( !isMultiGeom )
{
geos::unique_ptr reshapedGeometry;
if ( isLine )
{
reshapedGeometry = reshapeLine( mGeos.get(), reshapeLineGeos.get(), mPrecision );
}
else
{
reshapedGeometry = reshapePolygon( mGeos.get(), reshapeLineGeos.get(), mPrecision );
}
if ( errorCode )
*errorCode = Success;
std::unique_ptr< QgsAbstractGeometry > reshapeResult = fromGeos( reshapedGeometry.get() );
return reshapeResult;
}
else
{
try
{
//call reshape for each geometry part and replace mGeos with new geometry if reshape took place
bool reshapeTookPlace = false;
geos::unique_ptr currentReshapeGeometry;
GEOSGeometry **newGeoms = new GEOSGeometry*[numGeoms];
for ( int i = 0; i < numGeoms; ++i )
{
if ( isLine )
currentReshapeGeometry = reshapeLine( GEOSGetGeometryN_r( geosinit.ctxt, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
else
currentReshapeGeometry = reshapePolygon( GEOSGetGeometryN_r( geosinit.ctxt, mGeos.get(), i ), reshapeLineGeos.get(), mPrecision );
if ( currentReshapeGeometry )
{
newGeoms[i] = currentReshapeGeometry.release();
reshapeTookPlace = true;
}
else
{
newGeoms[i] = GEOSGeom_clone_r( geosinit.ctxt, GEOSGetGeometryN_r( geosinit.ctxt, mGeos.get(), i ) );
}
}
geos::unique_ptr newMultiGeom;
if ( isLine )
{
newMultiGeom.reset( GEOSGeom_createCollection_r( geosinit.ctxt, GEOS_MULTILINESTRING, newGeoms, numGeoms ) );
}
else //multipolygon
{
newMultiGeom.reset( GEOSGeom_createCollection_r( geosinit.ctxt, GEOS_MULTIPOLYGON, newGeoms, numGeoms ) );
}
delete[] newGeoms;
if ( !newMultiGeom )
{
if ( errorCode ) { *errorCode = EngineError; }
return nullptr;
}
if ( reshapeTookPlace )
{
if ( errorCode )
*errorCode = Success;
std::unique_ptr< QgsAbstractGeometry > reshapedMultiGeom = fromGeos( newMultiGeom.get() );
return reshapedMultiGeom;
}
else
{
if ( errorCode )
{
*errorCode = NothingHappened;
}
return nullptr;
}
}
CATCH_GEOS_WITH_ERRMSG( nullptr )
}
}
QgsGeometry QgsGeos::mergeLines( QString *errorMsg ) const
{
if ( !mGeos )
{
return QgsGeometry();
}
if ( GEOSGeomTypeId_r( geosinit.ctxt, mGeos.get() ) != GEOS_MULTILINESTRING )
return QgsGeometry();
geos::unique_ptr geos;
try
{
geos.reset( GEOSLineMerge_r( geosinit.ctxt, mGeos.get() ) );
}
CATCH_GEOS_WITH_ERRMSG( QgsGeometry() );
return QgsGeometry( fromGeos( geos.get() ) );
}
QgsGeometry QgsGeos::closestPoint( const QgsGeometry &other, QString *errorMsg ) const
{
if ( !mGeos || other.isNull() )
{
return QgsGeometry();
}
geos::unique_ptr otherGeom( asGeos( other.constGet(), mPrecision ) );
if ( !otherGeom )
{
return QgsGeometry();
}
double nx = 0.0;
double ny = 0.0;
try
{
geos::coord_sequence_unique_ptr nearestCoord( GEOSNearestPoints_r( geosinit.ctxt, mGeos.get(), otherGeom.get() ) );
( void )GEOSCoordSeq_getX_r( geosinit.ctxt, nearestCoord.get(), 0, &nx );
( void )GEOSCoordSeq_getY_r( geosinit.ctxt, nearestCoord.get(), 0, &ny );
}
catch ( GEOSException &e )
{
if ( errorMsg )
{
*errorMsg = e.what();
}
return QgsGeometry();
}
return QgsGeometry( new QgsPoint( nx, ny ) );
}
QgsGeometry QgsGeos::shortestLine( const QgsGeometry &other, QString *errorMsg ) const
{
if ( !mGeos || other.isNull() )
{
return QgsGeometry();
}
geos::unique_ptr otherGeom( asGeos( other.constGet(), mPrecision ) );
if ( !otherGeom )
{
return QgsGeometry();
}
double nx1 = 0.0;
double ny1 = 0.0;
double nx2 = 0.0;
double ny2 = 0.0;
try
{
geos::coord_sequence_unique_ptr nearestCoord( GEOSNearestPoints_r( geosinit.ctxt, mGeos.get(), otherGeom.get() ) );
( void )GEOSCoordSeq_getX_r( geosinit.ctxt, nearestCoord.get(), 0, &nx1 );
( void )GEOSCoordSeq_getY_r( geosinit.ctxt, nearestCoord.get(), 0, &ny1 );
( void )GEOSCoordSeq_getX_r( geosinit.ctxt, nearestCoord.get(), 1, &nx2 );
( void )GEOSCoordSeq_getY_r( geosinit.ctxt, nearestCoord.get(), 1, &ny2 );
}
catch ( GEOSException &e )
{
if ( errorMsg )
{
*errorMsg = e.what();
}
return QgsGeometry();
}
QgsLineString *line = new QgsLineString();
line->addVertex( QgsPoint( nx1, ny1 ) );
line->addVertex( QgsPoint( nx2, ny2 ) );
return QgsGeometry( line );
}
double QgsGeos::lineLocatePoint( const QgsPoint &point, QString *errorMsg ) const
{
if ( !mGeos )
{
return -1;
}
geos::unique_ptr otherGeom( asGeos( &point, mPrecision ) );
if ( !otherGeom )
{
return -1;
}
double distance = -1;
try
{
distance = GEOSProject_r( geosinit.ctxt, mGeos.get(), otherGeom.get() );
}
catch ( GEOSException &e )
{
if ( errorMsg )
{
*errorMsg = e.what();
}
return -1;
}
return distance;
}
QgsGeometry QgsGeos::polygonize( const QVector<const QgsAbstractGeometry *> &geometries, QString *errorMsg )
{
GEOSGeometry **const lineGeosGeometries = new GEOSGeometry*[ geometries.size()];
int validLines = 0;
for ( const QgsAbstractGeometry *g : geometries )
{
geos::unique_ptr l = asGeos( g );
if ( l )
{
lineGeosGeometries[validLines] = l.release();
validLines++;
}
}
try
{
geos::unique_ptr result( GEOSPolygonize_r( geosinit.ctxt, lineGeosGeometries, validLines ) );
for ( int i = 0; i < validLines; ++i )
{
GEOSGeom_destroy_r( geosinit.ctxt, lineGeosGeometries[i] );
}
delete[] lineGeosGeometries;
return QgsGeometry( fromGeos( result.get() ) );
}
catch ( GEOSException &e )
{
if ( errorMsg )
{
*errorMsg = e.what();
}
for ( int i = 0; i < validLines; ++i )
{
GEOSGeom_destroy_r( geosinit.ctxt, lineGeosGeometries[i] );
}
delete[] lineGeosGeometries;
return QgsGeometry();
}
}
QgsGeometry QgsGeos::voronoiDiagram( const QgsAbstractGeometry *extent, double tolerance, bool edgesOnly, QString *errorMsg ) const
{
if ( !mGeos )
{
return QgsGeometry();
}
geos::unique_ptr extentGeosGeom;
if ( extent )
{
extentGeosGeom = asGeos( extent, mPrecision );
if ( !extentGeosGeom )
{
return QgsGeometry();
}
}
geos::unique_ptr geos;
try
{
geos.reset( GEOSVoronoiDiagram_r( geosinit.ctxt, mGeos.get(), extentGeosGeom.get(), tolerance, edgesOnly ) );
if ( !geos || GEOSisEmpty_r( geosinit.ctxt, geos.get() ) != 0 )
{
return QgsGeometry();
}
return QgsGeometry( fromGeos( geos.get() ) );
}
CATCH_GEOS_WITH_ERRMSG( QgsGeometry() );
}
QgsGeometry QgsGeos::delaunayTriangulation( double tolerance, bool edgesOnly, QString *errorMsg ) const
{
if ( !mGeos )
{
return QgsGeometry();
}
geos::unique_ptr geos;
try
{
geos.reset( GEOSDelaunayTriangulation_r( geosinit.ctxt, mGeos.get(), tolerance, edgesOnly ) );
if ( !geos || GEOSisEmpty_r( geosinit.ctxt, geos.get() ) != 0 )
{
return QgsGeometry();
}
return QgsGeometry( fromGeos( geos.get() ) );
}
CATCH_GEOS_WITH_ERRMSG( QgsGeometry() );
}
//! Extract coordinates of linestring's endpoints. Returns false on error.
static bool _linestringEndpoints( const GEOSGeometry *linestring, double &x1, double &y1, double &x2, double &y2 )
{
const GEOSCoordSequence *coordSeq = GEOSGeom_getCoordSeq_r( geosinit.ctxt, linestring );
if ( !coordSeq )
return false;
unsigned int coordSeqSize;
if ( GEOSCoordSeq_getSize_r( geosinit.ctxt, coordSeq, &coordSeqSize ) == 0 )
return false;
if ( coordSeqSize < 2 )
return false;
GEOSCoordSeq_getX_r( geosinit.ctxt, coordSeq, 0, &x1 );
GEOSCoordSeq_getY_r( geosinit.ctxt, coordSeq, 0, &y1 );
GEOSCoordSeq_getX_r( geosinit.ctxt, coordSeq, coordSeqSize - 1, &x2 );
GEOSCoordSeq_getY_r( geosinit.ctxt, coordSeq, coordSeqSize - 1, &y2 );
return true;
}
//! Merge two linestrings if they meet at the given intersection point, return new geometry or null on error.
static geos::unique_ptr _mergeLinestrings( const GEOSGeometry *line1, const GEOSGeometry *line2, const QgsPointXY &intersectionPoint )
{
double x1, y1, x2, y2;
if ( !_linestringEndpoints( line1, x1, y1, x2, y2 ) )
return nullptr;
double rx1, ry1, rx2, ry2;
if ( !_linestringEndpoints( line2, rx1, ry1, rx2, ry2 ) )
return nullptr;
bool intersectionAtOrigLineEndpoint =
( intersectionPoint.x() == x1 && intersectionPoint.y() == y1 ) !=
( intersectionPoint.x() == x2 && intersectionPoint.y() == y2 );
bool intersectionAtReshapeLineEndpoint =
( intersectionPoint.x() == rx1 && intersectionPoint.y() == ry1 ) ||
( intersectionPoint.x() == rx2 && intersectionPoint.y() == ry2 );
// the intersection must be at the begin/end of both lines
if ( intersectionAtOrigLineEndpoint && intersectionAtReshapeLineEndpoint )
{
geos::unique_ptr g1( GEOSGeom_clone_r( geosinit.ctxt, line1 ) );
geos::unique_ptr g2( GEOSGeom_clone_r( geosinit.ctxt, line2 ) );
GEOSGeometry *geoms[2] = { g1.release(), g2.release() };
geos::unique_ptr multiGeom( GEOSGeom_createCollection_r( geosinit.ctxt, GEOS_MULTILINESTRING, geoms, 2 ) );
geos::unique_ptr res( GEOSLineMerge_r( geosinit.ctxt, multiGeom.get() ) );
return res;
}
else
return nullptr;
}
geos::unique_ptr QgsGeos::reshapeLine( const GEOSGeometry *line, const GEOSGeometry *reshapeLineGeos, double precision )
{
if ( !line || !reshapeLineGeos )
return nullptr;
bool atLeastTwoIntersections = false;
bool oneIntersection = false;
QgsPointXY oneIntersectionPoint;
try
{
//make sure there are at least two intersection between line and reshape geometry
geos::unique_ptr intersectGeom( GEOSIntersection_r( geosinit.ctxt, line, reshapeLineGeos ) );
if ( intersectGeom )
{
atLeastTwoIntersections = ( GEOSGeomTypeId_r( geosinit.ctxt, intersectGeom.get() ) == GEOS_MULTIPOINT
&& GEOSGetNumGeometries_r( geosinit.ctxt, intersectGeom.get() ) > 1 );
// one point is enough when extending line at its endpoint
if ( GEOSGeomTypeId_r( geosinit.ctxt, intersectGeom.get() ) == GEOS_POINT )
{
const GEOSCoordSequence *intersectionCoordSeq = GEOSGeom_getCoordSeq_r( geosinit.ctxt, intersectGeom.get() );
double xi, yi;
GEOSCoordSeq_getX_r( geosinit.ctxt, intersectionCoordSeq, 0, &xi );
GEOSCoordSeq_getY_r( geosinit.ctxt, intersectionCoordSeq, 0, &yi );
oneIntersection = true;
oneIntersectionPoint = QgsPointXY( xi, yi );
}
}
}
catch ( GEOSException & )
{
atLeastTwoIntersections = false;
}
// special case when extending line at its endpoint
if ( oneIntersection )
return _mergeLinestrings( line, reshapeLineGeos, oneIntersectionPoint );
if ( !atLeastTwoIntersections )
return nullptr;
//begin and end point of original line
double x1, y1, x2, y2;
if ( !_linestringEndpoints( line, x1, y1, x2, y2 ) )
return nullptr;
geos::unique_ptr beginLineVertex = createGeosPointXY( x1, y1, false, 0, false, 0, 2, precision );
geos::unique_ptr endLineVertex = createGeosPointXY( x2, y2, false, 0, false, 0, 2, precision );
bool isRing = false;
if ( GEOSGeomTypeId_r( geosinit.ctxt, line ) == GEOS_LINEARRING
|| GEOSEquals_r( geosinit.ctxt, beginLineVertex.get(), endLineVertex.get() ) == 1 )
isRing = true;
//node line and reshape line
geos::unique_ptr nodedGeometry = nodeGeometries( reshapeLineGeos, line );
if ( !nodedGeometry )
{
return nullptr;
}
//and merge them together
geos::unique_ptr mergedLines( GEOSLineMerge_r( geosinit.ctxt, nodedGeometry.get() ) );
if ( !mergedLines )
{
return nullptr;
}
int numMergedLines = GEOSGetNumGeometries_r( geosinit.ctxt, mergedLines.get() );
if ( numMergedLines < 2 ) //some special cases. Normally it is >2
{
if ( numMergedLines == 1 ) //reshape line is from begin to endpoint. So we keep the reshapeline
{
geos::unique_ptr result( GEOSGeom_clone_r( geosinit.ctxt, reshapeLineGeos ) );
return result;
}
else
return nullptr;
}
QVector<GEOSGeometry *> resultLineParts; //collection with the line segments that will be contained in result
QVector<GEOSGeometry *> probableParts; //parts where we can decide on inclusion only after going through all the candidates
for ( int i = 0; i < numMergedLines; ++i )
{
const GEOSGeometry *currentGeom = nullptr;
currentGeom = GEOSGetGeometryN_r( geosinit.ctxt, mergedLines.get(), i );
const GEOSCoordSequence *currentCoordSeq = GEOSGeom_getCoordSeq_r( geosinit.ctxt, currentGeom );
unsigned int currentCoordSeqSize;
GEOSCoordSeq_getSize_r( geosinit.ctxt, currentCoordSeq, &currentCoordSeqSize );
if ( currentCoordSeqSize < 2 )
continue;
//get the two endpoints of the current line merge result
double xBegin, xEnd, yBegin, yEnd;
GEOSCoordSeq_getX_r( geosinit.ctxt, currentCoordSeq, 0, &xBegin );
GEOSCoordSeq_getY_r( geosinit.ctxt, currentCoordSeq, 0, &yBegin );
GEOSCoordSeq_getX_r( geosinit.ctxt, currentCoordSeq, currentCoordSeqSize - 1, &xEnd );
GEOSCoordSeq_getY_r( geosinit.ctxt, currentCoordSeq, currentCoordSeqSize - 1, &yEnd );
geos::unique_ptr beginCurrentGeomVertex = createGeosPointXY( xBegin, yBegin, false, 0, false, 0, 2, precision );
geos::unique_ptr endCurrentGeomVertex = createGeosPointXY( xEnd, yEnd, false, 0, false, 0, 2, precision );
//check how many endpoints of the line merge result are on the (original) line
int nEndpointsOnOriginalLine = 0;
if ( pointContainedInLine( beginCurrentGeomVertex.get(), line ) == 1 )
nEndpointsOnOriginalLine += 1;
if ( pointContainedInLine( endCurrentGeomVertex.get(), line ) == 1 )
nEndpointsOnOriginalLine += 1;
//check how many endpoints equal the endpoints of the original line
int nEndpointsSameAsOriginalLine = 0;
if ( GEOSEquals_r( geosinit.ctxt, beginCurrentGeomVertex.get(), beginLineVertex.get() ) == 1
|| GEOSEquals_r( geosinit.ctxt, beginCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
nEndpointsSameAsOriginalLine += 1;
if ( GEOSEquals_r( geosinit.ctxt, endCurrentGeomVertex.get(), beginLineVertex.get() ) == 1
|| GEOSEquals_r( geosinit.ctxt, endCurrentGeomVertex.get(), endLineVertex.get() ) == 1 )
nEndpointsSameAsOriginalLine += 1;
//check if the current geometry overlaps the original geometry (GEOSOverlap does not seem to work with linestrings)
bool currentGeomOverlapsOriginalGeom = false;
bool currentGeomOverlapsReshapeLine = false;
if ( lineContainedInLine( currentGeom, line ) == 1 )
currentGeomOverlapsOriginalGeom = true;
if ( lineContainedInLine( currentGeom, reshapeLineGeos ) == 1 )
currentGeomOverlapsReshapeLine = true;
//logic to decide if this part belongs to the result
if ( !isRing && nEndpointsSameAsOriginalLine == 1 && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
{
resultLineParts.push_back( GEOSGeom_clone_r( geosinit.ctxt, currentGeom ) );
}
//for closed rings, we take one segment from the candidate list
else if ( isRing && nEndpointsOnOriginalLine == 2 && currentGeomOverlapsOriginalGeom )
{
probableParts.push_back( GEOSGeom_clone_r( geosinit.ctxt, currentGeom ) );
}
else if ( nEndpointsOnOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
{
resultLineParts.push_back( GEOSGeom_clone_r( geosinit.ctxt, currentGeom ) );
}
else if ( nEndpointsSameAsOriginalLine == 2 && !currentGeomOverlapsOriginalGeom )
{
resultLineParts.push_back( GEOSGeom_clone_r( geosinit.ctxt, currentGeom ) );
}
else if ( currentGeomOverlapsOriginalGeom && currentGeomOverlapsReshapeLine )
{
resultLineParts.push_back( GEOSGeom_clone_r( geosinit.ctxt, currentGeom ) );
}
}
//add the longest segment from the probable list for rings (only used for polygon rings)
if ( isRing && !probableParts.isEmpty() )
{
geos::unique_ptr maxGeom; //the longest geometry in the probabla list
GEOSGeometry *currentGeom = nullptr;
double maxLength = -std::numeric_limits<double>::max();
double currentLength = 0;
for ( int i = 0; i < probableParts.size(); ++i )
{
currentGeom = probableParts.at( i );
GEOSLength_r( geosinit.ctxt, currentGeom, &currentLength );
if ( currentLength > maxLength )
{
maxLength = currentLength;
maxGeom.reset( currentGeom );
}
else
{
GEOSGeom_destroy_r( geosinit.ctxt, currentGeom );
}
}
resultLineParts.push_back( maxGeom.release() );
}
geos::unique_ptr result;
if ( resultLineParts.empty() )
return nullptr;
if ( resultLineParts.size() == 1 ) //the whole result was reshaped
{
result.reset( resultLineParts[0] );
}
else //>1
{
GEOSGeometry **lineArray = new GEOSGeometry*[resultLineParts.size()];
for ( int i = 0; i < resultLineParts.size(); ++i )
{
lineArray[i] = resultLineParts[i];
}
//create multiline from resultLineParts
geos::unique_ptr multiLineGeom( GEOSGeom_createCollection_r( geosinit.ctxt, GEOS_MULTILINESTRING, lineArray, resultLineParts.size() ) );
delete [] lineArray;
//then do a linemerge with the newly combined partstrings
result.reset( GEOSLineMerge_r( geosinit.ctxt, multiLineGeom.get() ) );
}
//now test if the result is a linestring. Otherwise something went wrong
if ( GEOSGeomTypeId_r( geosinit.ctxt, result.get() ) != GEOS_LINESTRING )
{
return nullptr;
}
return result;
}
geos::unique_ptr QgsGeos::reshapePolygon( const GEOSGeometry *polygon, const GEOSGeometry *reshapeLineGeos, double precision )
{
//go through outer shell and all inner rings and check if there is exactly one intersection of a ring and the reshape line
int nIntersections = 0;
int lastIntersectingRing = -2;
const GEOSGeometry *lastIntersectingGeom = nullptr;
int nRings = GEOSGetNumInteriorRings_r( geosinit.ctxt, polygon );
if ( nRings < 0 )
return nullptr;
//does outer ring intersect?
const GEOSGeometry *outerRing = GEOSGetExteriorRing_r( geosinit.ctxt, polygon );
if ( GEOSIntersects_r( geosinit.ctxt, outerRing, reshapeLineGeos ) == 1 )
{
++nIntersections;
lastIntersectingRing = -1;
lastIntersectingGeom = outerRing;
}
//do inner rings intersect?
const GEOSGeometry **innerRings = new const GEOSGeometry*[nRings];
try
{
for ( int i = 0; i < nRings; ++i )
{
innerRings[i] = GEOSGetInteriorRingN_r( geosinit.ctxt, polygon, i );
if ( GEOSIntersects_r( geosinit.ctxt, innerRings[i], reshapeLineGeos ) == 1 )
{
++nIntersections;
lastIntersectingRing = i;
lastIntersectingGeom = innerRings[i];
}
}
}
catch ( GEOSException & )
{
nIntersections = 0;
}
if ( nIntersections != 1 ) //reshape line is only allowed to intersect one ring
{
delete [] innerRings;
return nullptr;
}
//we have one intersecting ring, let's try to reshape it
geos::unique_ptr reshapeResult = reshapeLine( lastIntersectingGeom, reshapeLineGeos, precision );
if ( !reshapeResult )
{
delete [] innerRings;
return nullptr;
}
//if reshaping took place, we need to reassemble the polygon and its rings
GEOSGeometry *newRing = nullptr;
const GEOSCoordSequence *reshapeSequence = GEOSGeom_getCoordSeq_r( geosinit.ctxt, reshapeResult.get() );
GEOSCoordSequence *newCoordSequence = GEOSCoordSeq_clone_r( geosinit.ctxt, reshapeSequence );
reshapeResult.reset();
newRing = GEOSGeom_createLinearRing_r( geosinit.ctxt, newCoordSequence );
if ( !newRing )
{
delete [] innerRings;
return nullptr;
}
GEOSGeometry *newOuterRing = nullptr;
if ( lastIntersectingRing == -1 )
newOuterRing = newRing;
else
newOuterRing = GEOSGeom_clone_r( geosinit.ctxt, outerRing );
//check if all the rings are still inside the outer boundary
QVector<GEOSGeometry *> ringList;
if ( nRings > 0 )
{
GEOSGeometry *outerRingPoly = GEOSGeom_createPolygon_r( geosinit.ctxt, GEOSGeom_clone_r( geosinit.ctxt, newOuterRing ), nullptr, 0 );
if ( outerRingPoly )
{
GEOSGeometry *currentRing = nullptr;
for ( int i = 0; i < nRings; ++i )
{
if ( lastIntersectingRing == i )
currentRing = newRing;
else
currentRing = GEOSGeom_clone_r( geosinit.ctxt, innerRings[i] );
//possibly a ring is no longer contained in the result polygon after reshape
if ( GEOSContains_r( geosinit.ctxt, outerRingPoly, currentRing ) == 1 )
ringList.push_back( currentRing );
else
GEOSGeom_destroy_r( geosinit.ctxt, currentRing );
}
}
GEOSGeom_destroy_r( geosinit.ctxt, outerRingPoly );
}
GEOSGeometry **newInnerRings = new GEOSGeometry*[ringList.size()];
for ( int i = 0; i < ringList.size(); ++i )
newInnerRings[i] = ringList.at( i );
delete [] innerRings;
geos::unique_ptr reshapedPolygon( GEOSGeom_createPolygon_r( geosinit.ctxt, newOuterRing, newInnerRings, ringList.size() ) );
delete[] newInnerRings;
return reshapedPolygon;
}
int QgsGeos::lineContainedInLine( const GEOSGeometry *line1, const GEOSGeometry *line2 )
{
if ( !line1 || !line2 )
{
return -1;
}
double bufferDistance = std::pow( 10.0L, geomDigits( line2 ) - 11 );
geos::unique_ptr bufferGeom( GEOSBuffer_r( geosinit.ctxt, line2, bufferDistance, DEFAULT_QUADRANT_SEGMENTS ) );
if ( !bufferGeom )
return -2;
geos::unique_ptr intersectionGeom( GEOSIntersection_r( geosinit.ctxt, bufferGeom.get(), line1 ) );
//compare ratio between line1Length and intersectGeomLength (usually close to 1 if line1 is contained in line2)
double intersectGeomLength;
double line1Length;
GEOSLength_r( geosinit.ctxt, intersectionGeom.get(), &intersectGeomLength );
GEOSLength_r( geosinit.ctxt, line1, &line1Length );
double intersectRatio = line1Length / intersectGeomLength;
if ( intersectRatio > 0.9 && intersectRatio < 1.1 )
return 1;
return 0;
}
int QgsGeos::pointContainedInLine( const GEOSGeometry *point, const GEOSGeometry *line )
{
if ( !point || !line )
return -1;
double bufferDistance = std::pow( 10.0L, geomDigits( line ) - 11 );
geos::unique_ptr lineBuffer( GEOSBuffer_r( geosinit.ctxt, line, bufferDistance, 8 ) );
if ( !lineBuffer )
return -2;
bool contained = false;
if ( GEOSContains_r( geosinit.ctxt, lineBuffer.get(), point ) == 1 )
contained = true;
return contained;
}
int QgsGeos::geomDigits( const GEOSGeometry *geom )
{
geos::unique_ptr bbox( GEOSEnvelope_r( geosinit.ctxt, geom ) );
if ( !bbox.get() )
return -1;
const GEOSGeometry *bBoxRing = GEOSGetExteriorRing_r( geosinit.ctxt, bbox.get() );
if ( !bBoxRing )
return -1;
const GEOSCoordSequence *bBoxCoordSeq = GEOSGeom_getCoordSeq_r( geosinit.ctxt, bBoxRing );
if ( !bBoxCoordSeq )
return -1;
unsigned int nCoords = 0;
if ( !GEOSCoordSeq_getSize_r( geosinit.ctxt, bBoxCoordSeq, &nCoords ) )
return -1;
int maxDigits = -1;
for ( unsigned int i = 0; i < nCoords - 1; ++i )
{
double t;
GEOSCoordSeq_getX_r( geosinit.ctxt, bBoxCoordSeq, i, &t );
int digits;
digits = std::ceil( std::log10( std::fabs( t ) ) );
if ( digits > maxDigits )
maxDigits = digits;
GEOSCoordSeq_getY_r( geosinit.ctxt, bBoxCoordSeq, i, &t );
digits = std::ceil( std::log10( std::fabs( t ) ) );
if ( digits > maxDigits )
maxDigits = digits;
}
return maxDigits;
}
GEOSContextHandle_t QgsGeos::getGEOSHandler()
{
return geosinit.ctxt;
}