mirror of
https://github.com/qgis/QGIS.git
synced 2025-11-22 00:14:55 -05:00
1153 lines
29 KiB
C++
1153 lines
29 KiB
C++
/***************************************************************************
|
|
qgscurvepolygon.cpp
|
|
---------------------
|
|
begin : September 2014
|
|
copyright : (C) 2014 by Marco Hugentobler
|
|
email : marco at sourcepole dot ch
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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 "qgscurvepolygon.h"
|
|
#include "qgsapplication.h"
|
|
#include "qgscircularstring.h"
|
|
#include "qgscompoundcurve.h"
|
|
#include "qgsgeometryutils.h"
|
|
#include "qgslinestring.h"
|
|
#include "qgspolygon.h"
|
|
#include "qgswkbptr.h"
|
|
#include "qgsmulticurve.h"
|
|
#include <QPainter>
|
|
#include <QPainterPath>
|
|
#include <memory>
|
|
|
|
QgsCurvePolygon::QgsCurvePolygon()
|
|
{
|
|
mWkbType = QgsWkbTypes::CurvePolygon;
|
|
}
|
|
|
|
QgsCurvePolygon::~QgsCurvePolygon()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
QgsCurvePolygon *QgsCurvePolygon::createEmptyWithSameType() const
|
|
{
|
|
auto result = new QgsCurvePolygon();
|
|
result->mWkbType = mWkbType;
|
|
return result;
|
|
}
|
|
|
|
QString QgsCurvePolygon::geometryType() const
|
|
{
|
|
return QStringLiteral( "CurvePolygon" );
|
|
}
|
|
|
|
int QgsCurvePolygon::dimension() const
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
QgsCurvePolygon::QgsCurvePolygon( const QgsCurvePolygon &p )
|
|
: QgsSurface( p )
|
|
|
|
{
|
|
mWkbType = p.mWkbType;
|
|
if ( p.mExteriorRing )
|
|
{
|
|
mExteriorRing.reset( static_cast<QgsCurve *>( p.mExteriorRing->clone() ) );
|
|
}
|
|
|
|
for ( const QgsCurve *ring : p.mInteriorRings )
|
|
{
|
|
mInteriorRings.push_back( static_cast<QgsCurve *>( ring->clone() ) );
|
|
}
|
|
}
|
|
|
|
QgsCurvePolygon &QgsCurvePolygon::operator=( const QgsCurvePolygon &p )
|
|
{
|
|
if ( &p != this )
|
|
{
|
|
clearCache();
|
|
QgsSurface::operator=( p );
|
|
if ( p.mExteriorRing )
|
|
{
|
|
mExteriorRing.reset( static_cast<QgsCurve *>( p.mExteriorRing->clone() ) );
|
|
}
|
|
|
|
for ( const QgsCurve *ring : p.mInteriorRings )
|
|
{
|
|
mInteriorRings.push_back( static_cast<QgsCurve *>( ring->clone() ) );
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
bool QgsCurvePolygon::operator==( const QgsCurvePolygon &other ) const
|
|
{
|
|
//run cheap checks first
|
|
if ( mWkbType != other.mWkbType )
|
|
return false;
|
|
|
|
if ( ( !mExteriorRing && other.mExteriorRing ) || ( mExteriorRing && !other.mExteriorRing ) )
|
|
return false;
|
|
|
|
if ( mInteriorRings.count() != other.mInteriorRings.count() )
|
|
return false;
|
|
|
|
// compare rings
|
|
if ( mExteriorRing && other.mExteriorRing )
|
|
{
|
|
if ( *mExteriorRing != *other.mExteriorRing )
|
|
return false;
|
|
}
|
|
|
|
for ( int i = 0; i < mInteriorRings.count(); ++i )
|
|
{
|
|
if ( ( !mInteriorRings.at( i ) && other.mInteriorRings.at( i ) ) ||
|
|
( mInteriorRings.at( i ) && !other.mInteriorRings.at( i ) ) )
|
|
return false;
|
|
|
|
if ( mInteriorRings.at( i ) && other.mInteriorRings.at( i ) &&
|
|
*mInteriorRings.at( i ) != *other.mInteriorRings.at( i ) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsCurvePolygon::operator!=( const QgsCurvePolygon &other ) const
|
|
{
|
|
return !operator==( other );
|
|
}
|
|
|
|
QgsCurvePolygon *QgsCurvePolygon::clone() const
|
|
{
|
|
return new QgsCurvePolygon( *this );
|
|
}
|
|
|
|
void QgsCurvePolygon::clear()
|
|
{
|
|
mWkbType = QgsWkbTypes::CurvePolygon;
|
|
mExteriorRing.reset();
|
|
qDeleteAll( mInteriorRings );
|
|
mInteriorRings.clear();
|
|
clearCache();
|
|
}
|
|
|
|
|
|
bool QgsCurvePolygon::fromWkb( QgsConstWkbPtr &wkbPtr )
|
|
{
|
|
clear();
|
|
if ( !wkbPtr )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QgsWkbTypes::Type type = wkbPtr.readHeader();
|
|
if ( QgsWkbTypes::flatType( type ) != QgsWkbTypes::CurvePolygon )
|
|
{
|
|
return false;
|
|
}
|
|
mWkbType = type;
|
|
|
|
int nRings;
|
|
wkbPtr >> nRings;
|
|
std::unique_ptr< QgsCurve > currentCurve;
|
|
for ( int i = 0; i < nRings; ++i )
|
|
{
|
|
QgsWkbTypes::Type curveType = wkbPtr.readHeader();
|
|
wkbPtr -= 1 + sizeof( int );
|
|
QgsWkbTypes::Type flatCurveType = QgsWkbTypes::flatType( curveType );
|
|
if ( flatCurveType == QgsWkbTypes::LineString )
|
|
{
|
|
currentCurve.reset( new QgsLineString() );
|
|
}
|
|
else if ( flatCurveType == QgsWkbTypes::CircularString )
|
|
{
|
|
currentCurve.reset( new QgsCircularString() );
|
|
}
|
|
else if ( flatCurveType == QgsWkbTypes::CompoundCurve )
|
|
{
|
|
currentCurve.reset( new QgsCompoundCurve() );
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
currentCurve->fromWkb( wkbPtr ); // also updates wkbPtr
|
|
if ( i == 0 )
|
|
{
|
|
mExteriorRing = std::move( currentCurve );
|
|
}
|
|
else
|
|
{
|
|
mInteriorRings.append( currentCurve.release() );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsCurvePolygon::fromWkt( const QString &wkt )
|
|
{
|
|
clear();
|
|
|
|
QPair<QgsWkbTypes::Type, QString> parts = QgsGeometryUtils::wktReadBlock( wkt );
|
|
|
|
if ( QgsWkbTypes::geometryType( parts.first ) != QgsWkbTypes::PolygonGeometry )
|
|
return false;
|
|
|
|
mWkbType = parts.first;
|
|
|
|
QString defaultChildWkbType = QStringLiteral( "LineString%1%2" ).arg( is3D() ? QStringLiteral( "Z" ) : QString(), isMeasure() ? QStringLiteral( "M" ) : QString() );
|
|
|
|
const QStringList blocks = QgsGeometryUtils::wktGetChildBlocks( parts.second, defaultChildWkbType );
|
|
for ( const QString &childWkt : blocks )
|
|
{
|
|
QPair<QgsWkbTypes::Type, QString> childParts = QgsGeometryUtils::wktReadBlock( childWkt );
|
|
|
|
QgsWkbTypes::Type flatCurveType = QgsWkbTypes::flatType( childParts.first );
|
|
if ( flatCurveType == QgsWkbTypes::LineString )
|
|
mInteriorRings.append( new QgsLineString() );
|
|
else if ( flatCurveType == QgsWkbTypes::CircularString )
|
|
mInteriorRings.append( new QgsCircularString() );
|
|
else if ( flatCurveType == QgsWkbTypes::CompoundCurve )
|
|
mInteriorRings.append( new QgsCompoundCurve() );
|
|
else
|
|
{
|
|
clear();
|
|
return false;
|
|
}
|
|
if ( !mInteriorRings.back()->fromWkt( childWkt ) )
|
|
{
|
|
clear();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( mInteriorRings.isEmpty() )
|
|
{
|
|
clear();
|
|
return false;
|
|
}
|
|
|
|
mExteriorRing.reset( mInteriorRings.takeFirst() );
|
|
|
|
//scan through rings and check if dimensionality of rings is different to CurvePolygon.
|
|
//if so, update the type dimensionality of the CurvePolygon to match
|
|
bool hasZ = false;
|
|
bool hasM = false;
|
|
if ( mExteriorRing )
|
|
{
|
|
hasZ = hasZ || mExteriorRing->is3D();
|
|
hasM = hasM || mExteriorRing->isMeasure();
|
|
}
|
|
for ( const QgsCurve *curve : qgis::as_const( mInteriorRings ) )
|
|
{
|
|
hasZ = hasZ || curve->is3D();
|
|
hasM = hasM || curve->isMeasure();
|
|
if ( hasZ && hasM )
|
|
break;
|
|
}
|
|
if ( hasZ )
|
|
addZValue( 0 );
|
|
if ( hasM )
|
|
addMValue( 0 );
|
|
|
|
return true;
|
|
}
|
|
|
|
QgsRectangle QgsCurvePolygon::calculateBoundingBox() const
|
|
{
|
|
if ( mExteriorRing )
|
|
{
|
|
return mExteriorRing->boundingBox();
|
|
}
|
|
return QgsRectangle();
|
|
}
|
|
|
|
QByteArray QgsCurvePolygon::asWkb() const
|
|
{
|
|
int binarySize = sizeof( char ) + sizeof( quint32 ) + sizeof( quint32 );
|
|
QList<QByteArray> wkbForRings;
|
|
if ( mExteriorRing )
|
|
{
|
|
QByteArray wkb( mExteriorRing->asWkb() );
|
|
binarySize += wkb.length();
|
|
wkbForRings << wkb;
|
|
}
|
|
for ( const QgsCurve *curve : mInteriorRings )
|
|
{
|
|
QByteArray wkb( curve->asWkb() );
|
|
binarySize += wkb.length();
|
|
wkbForRings << wkb;
|
|
}
|
|
|
|
QByteArray wkbArray;
|
|
wkbArray.resize( binarySize );
|
|
QgsWkbPtr wkbPtr( wkbArray );
|
|
wkbPtr << static_cast<char>( QgsApplication::endian() );
|
|
wkbPtr << static_cast<quint32>( wkbType() );
|
|
wkbPtr << static_cast<quint32>( wkbForRings.count() );
|
|
for ( const QByteArray &wkb : qgis::as_const( wkbForRings ) )
|
|
{
|
|
wkbPtr << wkb;
|
|
}
|
|
return wkbArray;
|
|
}
|
|
|
|
QString QgsCurvePolygon::asWkt( int precision ) const
|
|
{
|
|
QString wkt = wktTypeStr() + " (";
|
|
if ( mExteriorRing )
|
|
{
|
|
QString childWkt = mExteriorRing->asWkt( precision );
|
|
if ( qgsgeometry_cast<QgsLineString *>( mExteriorRing.get() ) )
|
|
{
|
|
// Type names of linear geometries are omitted
|
|
childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
|
|
}
|
|
wkt += childWkt + ',';
|
|
}
|
|
for ( const QgsCurve *curve : mInteriorRings )
|
|
{
|
|
QString childWkt = curve->asWkt( precision );
|
|
if ( qgsgeometry_cast<const QgsLineString *>( curve ) )
|
|
{
|
|
// Type names of linear geometries are omitted
|
|
childWkt = childWkt.mid( childWkt.indexOf( '(' ) );
|
|
}
|
|
wkt += childWkt + ',';
|
|
}
|
|
if ( wkt.endsWith( ',' ) )
|
|
{
|
|
wkt.chop( 1 ); // Remove last ','
|
|
}
|
|
wkt += ')';
|
|
return wkt;
|
|
}
|
|
|
|
QDomElement QgsCurvePolygon::asGML2( QDomDocument &doc, int precision, const QString &ns ) const
|
|
{
|
|
// GML2 does not support curves
|
|
QDomElement elemPolygon = doc.createElementNS( ns, QStringLiteral( "Polygon" ) );
|
|
|
|
if ( isEmpty() )
|
|
return elemPolygon;
|
|
|
|
QDomElement elemOuterBoundaryIs = doc.createElementNS( ns, QStringLiteral( "outerBoundaryIs" ) );
|
|
std::unique_ptr< QgsLineString > exteriorLineString( exteriorRing()->curveToLine() );
|
|
QDomElement outerRing = exteriorLineString->asGML2( doc, precision, ns );
|
|
outerRing.toElement().setTagName( QStringLiteral( "LinearRing" ) );
|
|
elemOuterBoundaryIs.appendChild( outerRing );
|
|
elemPolygon.appendChild( elemOuterBoundaryIs );
|
|
std::unique_ptr< QgsLineString > interiorLineString;
|
|
for ( int i = 0, n = numInteriorRings(); i < n; ++i )
|
|
{
|
|
QDomElement elemInnerBoundaryIs = doc.createElementNS( ns, QStringLiteral( "innerBoundaryIs" ) );
|
|
interiorLineString.reset( interiorRing( i )->curveToLine() );
|
|
QDomElement innerRing = interiorLineString->asGML2( doc, precision, ns );
|
|
innerRing.toElement().setTagName( QStringLiteral( "LinearRing" ) );
|
|
elemInnerBoundaryIs.appendChild( innerRing );
|
|
elemPolygon.appendChild( elemInnerBoundaryIs );
|
|
}
|
|
return elemPolygon;
|
|
}
|
|
|
|
QDomElement QgsCurvePolygon::asGML3( QDomDocument &doc, int precision, const QString &ns ) const
|
|
{
|
|
QDomElement elemCurvePolygon = doc.createElementNS( ns, QStringLiteral( "Polygon" ) );
|
|
|
|
if ( isEmpty() )
|
|
return elemCurvePolygon;
|
|
|
|
QDomElement elemExterior = doc.createElementNS( ns, QStringLiteral( "exterior" ) );
|
|
QDomElement curveElem = exteriorRing()->asGML3( doc, precision, ns );
|
|
if ( curveElem.tagName() == QLatin1String( "LineString" ) )
|
|
{
|
|
curveElem.setTagName( QStringLiteral( "LinearRing" ) );
|
|
}
|
|
elemExterior.appendChild( curveElem );
|
|
elemCurvePolygon.appendChild( elemExterior );
|
|
|
|
for ( int i = 0, n = numInteriorRings(); i < n; ++i )
|
|
{
|
|
QDomElement elemInterior = doc.createElementNS( ns, QStringLiteral( "interior" ) );
|
|
QDomElement innerRing = interiorRing( i )->asGML3( doc, precision, ns );
|
|
if ( innerRing.tagName() == QLatin1String( "LineString" ) )
|
|
{
|
|
innerRing.setTagName( QStringLiteral( "LinearRing" ) );
|
|
}
|
|
elemInterior.appendChild( innerRing );
|
|
elemCurvePolygon.appendChild( elemInterior );
|
|
}
|
|
return elemCurvePolygon;
|
|
}
|
|
|
|
QString QgsCurvePolygon::asJSON( int precision ) const
|
|
{
|
|
// GeoJSON does not support curves
|
|
QString json = QStringLiteral( "{\"type\": \"Polygon\", \"coordinates\": [" );
|
|
|
|
std::unique_ptr< QgsLineString > exteriorLineString( exteriorRing()->curveToLine() );
|
|
QgsPointSequence exteriorPts;
|
|
exteriorLineString->points( exteriorPts );
|
|
json += QgsGeometryUtils::pointsToJSON( exteriorPts, precision ) + ", ";
|
|
|
|
std::unique_ptr< QgsLineString > interiorLineString;
|
|
for ( int i = 0, n = numInteriorRings(); i < n; ++i )
|
|
{
|
|
interiorLineString.reset( interiorRing( i )->curveToLine() );
|
|
QgsPointSequence interiorPts;
|
|
interiorLineString->points( interiorPts );
|
|
json += QgsGeometryUtils::pointsToJSON( interiorPts, precision ) + ", ";
|
|
}
|
|
if ( json.endsWith( QLatin1String( ", " ) ) )
|
|
{
|
|
json.chop( 2 ); // Remove last ", "
|
|
}
|
|
json += QLatin1String( "] }" );
|
|
return json;
|
|
}
|
|
|
|
double QgsCurvePolygon::area() const
|
|
{
|
|
if ( !mExteriorRing )
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
double totalArea = 0.0;
|
|
|
|
if ( mExteriorRing->isRing() )
|
|
{
|
|
double area = 0.0;
|
|
mExteriorRing->sumUpArea( area );
|
|
totalArea += std::fabs( area );
|
|
}
|
|
|
|
QList<QgsCurve *>::const_iterator ringIt = mInteriorRings.constBegin();
|
|
for ( ; ringIt != mInteriorRings.constEnd(); ++ringIt )
|
|
{
|
|
double area = 0.0;
|
|
if ( ( *ringIt )->isRing() )
|
|
{
|
|
( *ringIt )->sumUpArea( area );
|
|
totalArea -= std::fabs( area );
|
|
}
|
|
}
|
|
return totalArea;
|
|
}
|
|
|
|
double QgsCurvePolygon::perimeter() const
|
|
{
|
|
if ( !mExteriorRing )
|
|
return 0.0;
|
|
|
|
//sum perimeter of rings
|
|
double perimeter = mExteriorRing->length();
|
|
QList<QgsCurve *>::const_iterator ringIt = mInteriorRings.constBegin();
|
|
for ( ; ringIt != mInteriorRings.constEnd(); ++ringIt )
|
|
{
|
|
perimeter += ( *ringIt )->length();
|
|
}
|
|
return perimeter;
|
|
}
|
|
|
|
QgsPolygonV2 *QgsCurvePolygon::surfaceToPolygon() const
|
|
{
|
|
std::unique_ptr< QgsPolygonV2 > polygon( new QgsPolygonV2() );
|
|
if ( !mExteriorRing )
|
|
return polygon.release();
|
|
|
|
polygon->setExteriorRing( exteriorRing()->curveToLine() );
|
|
QList<QgsCurve *> interiors;
|
|
int n = numInteriorRings();
|
|
interiors.reserve( n );
|
|
for ( int i = 0; i < n; ++i )
|
|
{
|
|
interiors.append( interiorRing( i )->curveToLine() );
|
|
}
|
|
polygon->setInteriorRings( interiors );
|
|
return polygon.release();
|
|
}
|
|
|
|
QgsAbstractGeometry *QgsCurvePolygon::boundary() const
|
|
{
|
|
if ( !mExteriorRing )
|
|
return nullptr;
|
|
|
|
if ( mInteriorRings.isEmpty() )
|
|
{
|
|
return mExteriorRing->clone();
|
|
}
|
|
else
|
|
{
|
|
QgsMultiCurve *multiCurve = new QgsMultiCurve();
|
|
multiCurve->addGeometry( mExteriorRing->clone() );
|
|
int nInteriorRings = mInteriorRings.size();
|
|
for ( int i = 0; i < nInteriorRings; ++i )
|
|
{
|
|
multiCurve->addGeometry( mInteriorRings.at( i )->clone() );
|
|
}
|
|
return multiCurve;
|
|
}
|
|
}
|
|
|
|
QgsCurvePolygon *QgsCurvePolygon::snappedToGrid( double hSpacing, double vSpacing, double dSpacing, double mSpacing,
|
|
double tolerance, SegmentationToleranceType toleranceType ) const
|
|
{
|
|
if ( !mExteriorRing )
|
|
return nullptr;
|
|
|
|
std::unique_ptr<QgsPolygonV2> polygon;
|
|
if ( QgsWkbTypes::flatType( mWkbType ) == QgsWkbTypes::Triangle || QgsWkbTypes::flatType( mWkbType ) == QgsWkbTypes::Polygon )
|
|
{
|
|
polygon = std::unique_ptr<QgsPolygonV2> { static_cast< QgsPolygonV2 const *>( this )->createEmptyWithSameType() };
|
|
}
|
|
else
|
|
{
|
|
polygon = std::unique_ptr<QgsPolygonV2> { new QgsPolygonV2() };
|
|
polygon->mWkbType = QgsWkbTypes::zmType( QgsWkbTypes::Polygon, QgsWkbTypes::hasZ( mWkbType ), QgsWkbTypes::hasM( mWkbType ) );
|
|
}
|
|
|
|
// exterior ring
|
|
auto exterior = std::unique_ptr<QgsCurve> { mExteriorRing->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, tolerance, toleranceType ) };
|
|
|
|
if ( !exterior )
|
|
return nullptr;
|
|
|
|
polygon->mExteriorRing = exterior.release();
|
|
|
|
//interior rings
|
|
for ( auto interior : mInteriorRings )
|
|
{
|
|
if ( !interior )
|
|
continue;
|
|
|
|
QgsCurve *gridifiedInterior = interior->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing, tolerance, toleranceType );
|
|
|
|
if ( !gridifiedInterior )
|
|
continue;
|
|
|
|
polygon->mInteriorRings.append( gridifiedInterior );
|
|
}
|
|
|
|
return polygon.release();
|
|
|
|
}
|
|
|
|
QgsPolygonV2 *QgsCurvePolygon::toPolygon( double tolerance, SegmentationToleranceType toleranceType ) const
|
|
{
|
|
std::unique_ptr< QgsPolygonV2 > poly( new QgsPolygonV2() );
|
|
if ( !mExteriorRing )
|
|
{
|
|
return poly.release();
|
|
}
|
|
|
|
poly->setExteriorRing( mExteriorRing->curveToLine( tolerance, toleranceType ) );
|
|
|
|
QList<QgsCurve *> rings;
|
|
QList<QgsCurve *>::const_iterator it = mInteriorRings.constBegin();
|
|
for ( ; it != mInteriorRings.constEnd(); ++it )
|
|
{
|
|
rings.push_back( ( *it )->curveToLine( tolerance, toleranceType ) );
|
|
}
|
|
poly->setInteriorRings( rings );
|
|
return poly.release();
|
|
}
|
|
|
|
int QgsCurvePolygon::numInteriorRings() const
|
|
{
|
|
return mInteriorRings.size();
|
|
}
|
|
|
|
const QgsCurve *QgsCurvePolygon::exteriorRing() const
|
|
{
|
|
return mExteriorRing.get();
|
|
}
|
|
|
|
const QgsCurve *QgsCurvePolygon::interiorRing( int i ) const
|
|
{
|
|
if ( i < 0 || i >= mInteriorRings.size() )
|
|
{
|
|
return nullptr;
|
|
}
|
|
return mInteriorRings.at( i );
|
|
}
|
|
|
|
void QgsCurvePolygon::setExteriorRing( QgsCurve *ring )
|
|
{
|
|
if ( !ring )
|
|
{
|
|
return;
|
|
}
|
|
mExteriorRing.reset( ring );
|
|
|
|
//set proper wkb type
|
|
if ( QgsWkbTypes::flatType( wkbType() ) == QgsWkbTypes::Polygon )
|
|
{
|
|
setZMTypeFromSubGeometry( ring, QgsWkbTypes::Polygon );
|
|
}
|
|
else if ( QgsWkbTypes::flatType( wkbType() ) == QgsWkbTypes::CurvePolygon )
|
|
{
|
|
setZMTypeFromSubGeometry( ring, QgsWkbTypes::CurvePolygon );
|
|
}
|
|
|
|
//match dimensionality for rings
|
|
for ( QgsCurve *ring : qgis::as_const( mInteriorRings ) )
|
|
{
|
|
if ( is3D() )
|
|
ring->addZValue();
|
|
else
|
|
ring->dropZValue();
|
|
|
|
if ( isMeasure() )
|
|
ring->addMValue();
|
|
else
|
|
ring->dropMValue();
|
|
}
|
|
clearCache();
|
|
}
|
|
|
|
void QgsCurvePolygon::setInteriorRings( const QList<QgsCurve *> &rings )
|
|
{
|
|
qDeleteAll( mInteriorRings );
|
|
mInteriorRings.clear();
|
|
|
|
//add rings one-by-one, so that they can each be converted to the correct type for the CurvePolygon
|
|
for ( QgsCurve *ring : rings )
|
|
{
|
|
addInteriorRing( ring );
|
|
}
|
|
clearCache();
|
|
}
|
|
|
|
void QgsCurvePolygon::addInteriorRing( QgsCurve *ring )
|
|
{
|
|
if ( !ring )
|
|
return;
|
|
|
|
//ensure dimensionality of ring matches curve polygon
|
|
if ( !is3D() )
|
|
ring->dropZValue();
|
|
else if ( !ring->is3D() )
|
|
ring->addZValue();
|
|
|
|
if ( !isMeasure() )
|
|
ring->dropMValue();
|
|
else if ( !ring->isMeasure() )
|
|
ring->addMValue();
|
|
|
|
mInteriorRings.append( ring );
|
|
clearCache();
|
|
}
|
|
|
|
bool QgsCurvePolygon::removeInteriorRing( int nr )
|
|
{
|
|
if ( nr < 0 || nr >= mInteriorRings.size() )
|
|
{
|
|
return false;
|
|
}
|
|
delete mInteriorRings.takeAt( nr );
|
|
clearCache();
|
|
return true;
|
|
}
|
|
|
|
void QgsCurvePolygon::removeInteriorRings( double minimumAllowedArea )
|
|
{
|
|
for ( int ringIndex = mInteriorRings.size() - 1; ringIndex >= 0; --ringIndex )
|
|
{
|
|
if ( minimumAllowedArea < 0 )
|
|
delete mInteriorRings.takeAt( ringIndex );
|
|
else
|
|
{
|
|
double area = 0.0;
|
|
mInteriorRings.at( ringIndex )->sumUpArea( area );
|
|
if ( area < minimumAllowedArea )
|
|
delete mInteriorRings.takeAt( ringIndex );
|
|
}
|
|
}
|
|
|
|
clearCache();
|
|
}
|
|
|
|
void QgsCurvePolygon::draw( QPainter &p ) const
|
|
{
|
|
if ( !mExteriorRing )
|
|
return;
|
|
|
|
if ( mInteriorRings.empty() )
|
|
{
|
|
mExteriorRing->drawAsPolygon( p );
|
|
}
|
|
else
|
|
{
|
|
QPainterPath path;
|
|
mExteriorRing->addToPainterPath( path );
|
|
|
|
QList<QgsCurve *>::const_iterator it = mInteriorRings.constBegin();
|
|
for ( ; it != mInteriorRings.constEnd(); ++it )
|
|
{
|
|
( *it )->addToPainterPath( path );
|
|
}
|
|
p.drawPath( path );
|
|
}
|
|
}
|
|
|
|
void QgsCurvePolygon::transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d, bool transformZ )
|
|
{
|
|
if ( mExteriorRing )
|
|
{
|
|
mExteriorRing->transform( ct, d, transformZ );
|
|
}
|
|
|
|
for ( QgsCurve *curve : qgis::as_const( mInteriorRings ) )
|
|
{
|
|
curve->transform( ct, d, transformZ );
|
|
}
|
|
clearCache();
|
|
}
|
|
|
|
void QgsCurvePolygon::transform( const QTransform &t )
|
|
{
|
|
if ( mExteriorRing )
|
|
{
|
|
mExteriorRing->transform( t );
|
|
}
|
|
|
|
for ( QgsCurve *curve : qgis::as_const( mInteriorRings ) )
|
|
{
|
|
curve->transform( t );
|
|
}
|
|
clearCache();
|
|
}
|
|
|
|
QgsCoordinateSequence QgsCurvePolygon::coordinateSequence() const
|
|
{
|
|
if ( !mCoordinateSequence.isEmpty() )
|
|
return mCoordinateSequence;
|
|
|
|
mCoordinateSequence.append( QgsRingSequence() );
|
|
|
|
if ( mExteriorRing )
|
|
{
|
|
mCoordinateSequence.back().append( QgsPointSequence() );
|
|
mExteriorRing->points( mCoordinateSequence.back().back() );
|
|
}
|
|
|
|
QList<QgsCurve *>::const_iterator it = mInteriorRings.constBegin();
|
|
for ( ; it != mInteriorRings.constEnd(); ++it )
|
|
{
|
|
mCoordinateSequence.back().append( QgsPointSequence() );
|
|
( *it )->points( mCoordinateSequence.back().back() );
|
|
}
|
|
|
|
return mCoordinateSequence;
|
|
}
|
|
|
|
int QgsCurvePolygon::nCoordinates() const
|
|
{
|
|
if ( !mCoordinateSequence.isEmpty() )
|
|
return QgsAbstractGeometry::nCoordinates();
|
|
|
|
int count = 0;
|
|
|
|
if ( mExteriorRing )
|
|
{
|
|
count += mExteriorRing->nCoordinates();
|
|
}
|
|
|
|
QList<QgsCurve *>::const_iterator it = mInteriorRings.constBegin();
|
|
for ( ; it != mInteriorRings.constEnd(); ++it )
|
|
{
|
|
count += ( *it )->nCoordinates();
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
int QgsCurvePolygon::vertexNumberFromVertexId( QgsVertexId id ) const
|
|
{
|
|
if ( id.part != 0 )
|
|
return -1;
|
|
|
|
if ( id.ring < 0 || id.ring >= ringCount() )
|
|
return -1;
|
|
|
|
int number = 0;
|
|
if ( id.ring == 0 && mExteriorRing )
|
|
{
|
|
return mExteriorRing->vertexNumberFromVertexId( QgsVertexId( 0, 0, id.vertex ) );
|
|
}
|
|
else
|
|
{
|
|
number += mExteriorRing->numPoints();
|
|
}
|
|
|
|
for ( int i = 0; i < mInteriorRings.count(); ++i )
|
|
{
|
|
if ( id.ring == i + 1 )
|
|
{
|
|
int partNumber = mInteriorRings.at( i )->vertexNumberFromVertexId( QgsVertexId( 0, 0, id.vertex ) );
|
|
if ( partNumber == -1 )
|
|
return -1;
|
|
return number + partNumber;
|
|
}
|
|
else
|
|
{
|
|
number += mInteriorRings.at( i )->numPoints();
|
|
}
|
|
}
|
|
return -1; // should not happen
|
|
}
|
|
|
|
bool QgsCurvePolygon::isEmpty() const
|
|
{
|
|
if ( !mExteriorRing )
|
|
return true;
|
|
|
|
return mExteriorRing->isEmpty();
|
|
}
|
|
|
|
double QgsCurvePolygon::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, bool *leftOf, double epsilon ) const
|
|
{
|
|
if ( !mExteriorRing )
|
|
{
|
|
return -1;
|
|
}
|
|
QList<QgsCurve *> segmentList;
|
|
segmentList.append( mExteriorRing.get() );
|
|
segmentList.append( mInteriorRings );
|
|
return QgsGeometryUtils::closestSegmentFromComponents( segmentList, QgsGeometryUtils::Ring, pt, segmentPt, vertexAfter, leftOf, epsilon );
|
|
}
|
|
|
|
bool QgsCurvePolygon::nextVertex( QgsVertexId &vId, QgsPoint &vertex ) const
|
|
{
|
|
if ( !mExteriorRing || vId.ring >= 1 + mInteriorRings.size() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( vId.ring < 0 )
|
|
{
|
|
vId.ring = 0;
|
|
vId.vertex = -1;
|
|
if ( vId.part < 0 )
|
|
{
|
|
vId.part = 0;
|
|
}
|
|
return mExteriorRing->nextVertex( vId, vertex );
|
|
}
|
|
else
|
|
{
|
|
QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings[vId.ring - 1];
|
|
|
|
if ( ring->nextVertex( vId, vertex ) )
|
|
{
|
|
return true;
|
|
}
|
|
++vId.ring;
|
|
vId.vertex = -1;
|
|
if ( vId.ring >= 1 + mInteriorRings.size() )
|
|
{
|
|
return false;
|
|
}
|
|
ring = mInteriorRings[ vId.ring - 1 ];
|
|
return ring->nextVertex( vId, vertex );
|
|
}
|
|
}
|
|
|
|
void ringAdjacentVertices( const QgsCurve *curve, QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex )
|
|
{
|
|
int n = curve->numPoints();
|
|
if ( vertex.vertex < 0 || vertex.vertex >= n )
|
|
{
|
|
previousVertex = QgsVertexId();
|
|
nextVertex = QgsVertexId();
|
|
return;
|
|
}
|
|
|
|
if ( vertex.vertex == 0 && n < 3 )
|
|
{
|
|
previousVertex = QgsVertexId();
|
|
}
|
|
else if ( vertex.vertex == 0 )
|
|
{
|
|
previousVertex = QgsVertexId( vertex.part, vertex.ring, n - 2 );
|
|
}
|
|
else
|
|
{
|
|
previousVertex = QgsVertexId( vertex.part, vertex.ring, vertex.vertex - 1 );
|
|
}
|
|
if ( vertex.vertex == n - 1 && n < 3 )
|
|
{
|
|
nextVertex = QgsVertexId();
|
|
}
|
|
else if ( vertex.vertex == n - 1 )
|
|
{
|
|
nextVertex = QgsVertexId( vertex.part, vertex.ring, 1 );
|
|
}
|
|
else
|
|
{
|
|
nextVertex = QgsVertexId( vertex.part, vertex.ring, vertex.vertex + 1 );
|
|
}
|
|
}
|
|
|
|
void QgsCurvePolygon::adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex, QgsVertexId &nextVertex ) const
|
|
{
|
|
if ( !mExteriorRing || vertex.ring < 0 || vertex.ring >= 1 + mInteriorRings.size() )
|
|
{
|
|
previousVertex = QgsVertexId();
|
|
nextVertex = QgsVertexId();
|
|
return;
|
|
}
|
|
|
|
if ( vertex.ring == 0 )
|
|
{
|
|
ringAdjacentVertices( mExteriorRing.get(), vertex, previousVertex, nextVertex );
|
|
}
|
|
else
|
|
{
|
|
ringAdjacentVertices( mInteriorRings.at( vertex.ring - 1 ), vertex, previousVertex, nextVertex );
|
|
}
|
|
}
|
|
|
|
bool QgsCurvePolygon::insertVertex( QgsVertexId vId, const QgsPoint &vertex )
|
|
{
|
|
if ( !mExteriorRing || vId.ring < 0 || vId.ring >= 1 + mInteriorRings.size() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( vId.ring - 1 );
|
|
int n = ring->numPoints();
|
|
bool success = ring->insertVertex( QgsVertexId( 0, 0, vId.vertex ), vertex );
|
|
if ( !success )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If first or last vertex is inserted, re-sync the last/first vertex
|
|
if ( vId.vertex == 0 )
|
|
ring->moveVertex( QgsVertexId( 0, 0, n ), vertex );
|
|
else if ( vId.vertex == n )
|
|
ring->moveVertex( QgsVertexId( 0, 0, 0 ), vertex );
|
|
|
|
clearCache();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QgsCurvePolygon::moveVertex( QgsVertexId vId, const QgsPoint &newPos )
|
|
{
|
|
if ( !mExteriorRing || vId.ring < 0 || vId.ring >= 1 + mInteriorRings.size() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( vId.ring - 1 );
|
|
int n = ring->numPoints();
|
|
bool success = ring->moveVertex( vId, newPos );
|
|
if ( success )
|
|
{
|
|
// If first or last vertex is moved, also move the last/first vertex
|
|
if ( vId.vertex == 0 )
|
|
ring->moveVertex( QgsVertexId( vId.part, vId.ring, n - 1 ), newPos );
|
|
else if ( vId.vertex == n - 1 )
|
|
ring->moveVertex( QgsVertexId( vId.part, vId.ring, 0 ), newPos );
|
|
clearCache();
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool QgsCurvePolygon::deleteVertex( QgsVertexId vId )
|
|
{
|
|
if ( !mExteriorRing || vId.ring < 0 || vId.ring >= 1 + mInteriorRings.size() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QgsCurve *ring = vId.ring == 0 ? mExteriorRing.get() : mInteriorRings.at( vId.ring - 1 );
|
|
int n = ring->numPoints();
|
|
if ( n <= 4 )
|
|
{
|
|
//no points will be left in ring, so remove whole ring
|
|
if ( vId.ring == 0 )
|
|
{
|
|
mExteriorRing.reset();
|
|
if ( !mInteriorRings.isEmpty() )
|
|
{
|
|
mExteriorRing.reset( mInteriorRings.takeFirst() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
removeInteriorRing( vId.ring - 1 );
|
|
}
|
|
clearCache();
|
|
return true;
|
|
}
|
|
|
|
bool success = ring->deleteVertex( vId );
|
|
if ( success )
|
|
{
|
|
// If first or last vertex is removed, re-sync the last/first vertex
|
|
// Do not use "n - 2", but "ring->numPoints() - 1" as more than one vertex
|
|
// may have been deleted (e.g. with CircularString)
|
|
if ( vId.vertex == 0 )
|
|
ring->moveVertex( QgsVertexId( 0, 0, ring->numPoints() - 1 ), ring->vertexAt( QgsVertexId( 0, 0, 0 ) ) );
|
|
else if ( vId.vertex == n - 1 )
|
|
ring->moveVertex( QgsVertexId( 0, 0, 0 ), ring->vertexAt( QgsVertexId( 0, 0, ring->numPoints() - 1 ) ) );
|
|
clearCache();
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool QgsCurvePolygon::hasCurvedSegments() const
|
|
{
|
|
if ( mExteriorRing && mExteriorRing->hasCurvedSegments() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
QList<QgsCurve *>::const_iterator it = mInteriorRings.constBegin();
|
|
for ( ; it != mInteriorRings.constEnd(); ++it )
|
|
{
|
|
if ( ( *it )->hasCurvedSegments() )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QgsAbstractGeometry *QgsCurvePolygon::segmentize( double tolerance, SegmentationToleranceType toleranceType ) const
|
|
{
|
|
return toPolygon( tolerance, toleranceType );
|
|
}
|
|
|
|
double QgsCurvePolygon::vertexAngle( QgsVertexId vertex ) const
|
|
{
|
|
if ( !mExteriorRing || vertex.ring < 0 || vertex.ring >= 1 + mInteriorRings.size() )
|
|
{
|
|
//makes no sense - conversion of false to double!
|
|
return false;
|
|
}
|
|
|
|
QgsCurve *ring = vertex.ring == 0 ? mExteriorRing.get() : mInteriorRings[vertex.ring - 1];
|
|
return ring->vertexAngle( vertex );
|
|
}
|
|
|
|
int QgsCurvePolygon::vertexCount( int /*part*/, int ring ) const
|
|
{
|
|
return ring == 0 ? mExteriorRing->vertexCount() : mInteriorRings[ring - 1]->vertexCount();
|
|
}
|
|
|
|
int QgsCurvePolygon::ringCount( int ) const
|
|
{
|
|
return ( nullptr != mExteriorRing ) + mInteriorRings.size();
|
|
}
|
|
|
|
int QgsCurvePolygon::partCount() const
|
|
{
|
|
return ringCount() > 0 ? 1 : 0;
|
|
}
|
|
|
|
QgsPoint QgsCurvePolygon::vertexAt( QgsVertexId id ) const
|
|
{
|
|
return id.ring == 0 ? mExteriorRing->vertexAt( id ) : mInteriorRings[id.ring - 1]->vertexAt( id );
|
|
}
|
|
|
|
bool QgsCurvePolygon::addZValue( double zValue )
|
|
{
|
|
if ( QgsWkbTypes::hasZ( mWkbType ) )
|
|
return false;
|
|
|
|
mWkbType = QgsWkbTypes::addZ( mWkbType );
|
|
|
|
if ( mExteriorRing )
|
|
mExteriorRing->addZValue( zValue );
|
|
for ( QgsCurve *curve : qgis::as_const( mInteriorRings ) )
|
|
{
|
|
curve->addZValue( zValue );
|
|
}
|
|
clearCache();
|
|
return true;
|
|
}
|
|
|
|
bool QgsCurvePolygon::addMValue( double mValue )
|
|
{
|
|
if ( QgsWkbTypes::hasM( mWkbType ) )
|
|
return false;
|
|
|
|
mWkbType = QgsWkbTypes::addM( mWkbType );
|
|
|
|
if ( mExteriorRing )
|
|
mExteriorRing->addMValue( mValue );
|
|
for ( QgsCurve *curve : qgis::as_const( mInteriorRings ) )
|
|
{
|
|
curve->addMValue( mValue );
|
|
}
|
|
clearCache();
|
|
return true;
|
|
}
|
|
|
|
bool QgsCurvePolygon::dropZValue()
|
|
{
|
|
if ( !is3D() )
|
|
return false;
|
|
|
|
mWkbType = QgsWkbTypes::dropZ( mWkbType );
|
|
if ( mExteriorRing )
|
|
mExteriorRing->dropZValue();
|
|
for ( QgsCurve *curve : qgis::as_const( mInteriorRings ) )
|
|
{
|
|
curve->dropZValue();
|
|
}
|
|
clearCache();
|
|
return true;
|
|
}
|
|
|
|
bool QgsCurvePolygon::dropMValue()
|
|
{
|
|
if ( !isMeasure() )
|
|
return false;
|
|
|
|
mWkbType = QgsWkbTypes::dropM( mWkbType );
|
|
if ( mExteriorRing )
|
|
mExteriorRing->dropMValue();
|
|
for ( QgsCurve *curve : qgis::as_const( mInteriorRings ) )
|
|
{
|
|
curve->dropMValue();
|
|
}
|
|
clearCache();
|
|
return true;
|
|
}
|
|
|
|
QgsCurvePolygon *QgsCurvePolygon::toCurveType() const
|
|
{
|
|
return clone();
|
|
}
|
|
|
|
int QgsCurvePolygon::childCount() const
|
|
{
|
|
return 1 + mInteriorRings.count();
|
|
}
|
|
|
|
QgsAbstractGeometry *QgsCurvePolygon::childGeometry( int index ) const
|
|
{
|
|
if ( index == 0 )
|
|
return mExteriorRing.get();
|
|
else
|
|
return mInteriorRings.at( index - 1 );
|
|
}
|