//! Simplify the WKB-geometry using the specified tolerance QgsGeometry QgsMapToPixelSimplifier::simplifyGeometry( int simplifyFlags, SimplifyAlgorithm simplifyAlgorithm, QgsWkbTypes::Type wkbType, const QgsAbstractGeometry& geometry, const QgsRectangle &envelope, double map2pixelTol, bool isaLinearRing ) { bool isGeneralizable = true; // Can replace the geometry by its BBOX ? if (( simplifyFlags & QgsMapToPixelSimplifier::SimplifyEnvelope ) && isGeneralizableByMapBoundingBox( envelope, map2pixelTol ) ) { return generalizeWkbGeometryByBoundingBox( wkbType, geometry, envelope ); } if ( !( simplifyFlags & QgsMapToPixelSimplifier::SimplifyGeometry ) ) isGeneralizable = false; const QgsWkbTypes::Type flatType = QgsWkbTypes::flatType( wkbType ); // Write the geometry if ( flatType == QgsWkbTypes::LineString || flatType == QgsWkbTypes::CircularString ) { const QgsCurve& srcCurve = dynamic_cast<const QgsCurve&>( geometry ); QScopedPointer<QgsCurve> output( createEmptySameTypeGeom( srcCurve ) ); double x = 0.0, y = 0.0, lastX = 0.0, lastY = 0.0; QgsRectangle r; r.setMinimal(); const int numPoints = srcCurve.numPoints(); if ( numPoints <= ( isaLinearRing ? 4 : 2 ) ) isGeneralizable = false; bool isLongSegment; bool hasLongSegments = false; //-> To avoid replace the simplified geometry by its BBOX when there are 'long' segments. // Check whether the LinearRing is really closed. if ( isaLinearRing ) { isaLinearRing = qgsDoubleNear( srcCurve.xAt( 0 ), srcCurve.xAt( numPoints - 1 ) ) && qgsDoubleNear( srcCurve.yAt( 0 ), srcCurve.yAt( numPoints - 1 ) ); } // Process each vertex... switch ( simplifyAlgorithm ) { case SnapToGrid: { double gridOriginX = envelope.xMinimum(); double gridOriginY = envelope.yMinimum(); // Use a factor for the maximum displacement distance for simplification, similar as GeoServer does float gridInverseSizeXY = map2pixelTol != 0 ? ( float )( 1.0f / ( 0.8 * map2pixelTol ) ) : 0.0f; for ( int i = 0; i < numPoints; ++i ) { x = srcCurve.xAt( i ); y = srcCurve.yAt( i ); if ( i == 0 || !isGeneralizable || !equalSnapToGrid( x, y, lastX, lastY, gridOriginX, gridOriginY, gridInverseSizeXY ) || ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) ) { output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPointV2( x, y ) ); lastX = x; lastY = y; } r.combineExtentWith( x, y ); } break; } case Visvalingam: { map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'Area' calculations. EFFECTIVE_AREAS ea( srcCurve ); int set_area = 0; ptarray_calc_areas( &ea, isaLinearRing ? 4 : 2, set_area, map2pixelTol ); for ( int i = 0; i < numPoints; ++i ) { if ( ea.res_arealist[ i ] > map2pixelTol ) { output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), ea.inpts.at( i ) ); } } break; } case Distance: { map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'LengthSquare' calculations. for ( int i = 0; i < numPoints; ++i ) { x = srcCurve.xAt( i ); y = srcCurve.yAt( i ); isLongSegment = false; if ( i == 0 || !isGeneralizable || ( isLongSegment = ( calculateLengthSquared2D( x, y, lastX, lastY ) > map2pixelTol ) ) || ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) ) { output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPointV2( x, y ) ); lastX = x; lastY = y; hasLongSegments |= isLongSegment; } r.combineExtentWith( x, y ); } } } if ( output->numPoints() < ( isaLinearRing ? 4 : 2 ) ) { // we simplified the geometry too much! if ( !hasLongSegments ) { // approximate the geometry's shape by its bounding box // (rect for linear ring / one segment for line string) return generalizeWkbGeometryByBoundingBox( wkbType, geometry, r ); } else { // Bad luck! The simplified geometry is invalid and approximation by bounding box // would create artifacts due to long segments. // We will return the original geometry return QgsGeometry( geometry.clone() ); } } if ( isaLinearRing ) { // make sure we keep the linear ring closed if ( !qgsDoubleNear( lastX, output->xAt( 0 ) ) || !qgsDoubleNear( lastY, output->yAt( 0 ) ) ) { output->insertVertex( QgsVertexId( 0, 0, output->numPoints() ), QgsPointV2( output->xAt( 0 ), output->yAt( 0 ) ) ); } } return QgsGeometry( output.take() ); } else if ( flatType == QgsWkbTypes::Polygon ) { const QgsPolygonV2& srcPolygon = dynamic_cast<const QgsPolygonV2&>( geometry ); QScopedPointer<QgsPolygonV2> polygon( new QgsPolygonV2() ); polygon->setExteriorRing( dynamic_cast<QgsCurve*>( simplifyGeometry( simplifyFlags, simplifyAlgorithm, srcPolygon.exteriorRing()->wkbType(), *srcPolygon.exteriorRing(), envelope, map2pixelTol, true ).geometry()->clone() ) ); for ( int i = 0; i < srcPolygon.numInteriorRings(); ++i ) { const QgsCurve* sub = srcPolygon.interiorRing( i ); polygon->addInteriorRing( dynamic_cast<QgsCurve*>( simplifyGeometry( simplifyFlags, simplifyAlgorithm, sub->wkbType(), *sub, envelope, map2pixelTol, true ).geometry()->clone() ) ); } return QgsGeometry( polygon.take() ); } else if ( QgsWkbTypes::isMultiType( flatType ) ) { const QgsGeometryCollection& srcCollection = dynamic_cast<const QgsGeometryCollection&>( geometry ); QScopedPointer<QgsGeometryCollection> collection( createEmptySameTypeGeom( srcCollection ) ); const int numGeoms = srcCollection.numGeometries(); for ( int i = 0; i < numGeoms; ++i ) { const QgsAbstractGeometry* sub = srcCollection.geometryN( i ); collection->addGeometry( simplifyGeometry( simplifyFlags, simplifyAlgorithm, sub->wkbType(), *sub, envelope, map2pixelTol, false ).geometry()->clone() ); } return QgsGeometry( collection.take() ); } return QgsGeometry( geometry.clone() ); }
//! Simplify the WKB-geometry using the specified tolerance bool QgsMapToPixelSimplifier::simplifyWkbGeometry( int simplifyFlags, SimplifyAlgorithm simplifyAlgorithm, QGis::WkbType wkbType, QgsConstWkbPtr sourceWkbPtr, QgsWkbPtr targetWkbPtr, int &targetWkbSize, const QgsRectangle &envelope, double map2pixelTol, bool writeHeader, bool isaLinearRing ) { bool isGeneralizable = true; bool result = false; // Save initial WKB settings to use when the simplification creates invalid geometries QgsConstWkbPtr sourcePrevWkbPtr( sourceWkbPtr ); QgsWkbPtr targetPrevWkbPtr( targetWkbPtr ); int targetWkbPrevSize = targetWkbSize; // Can replace the geometry by its BBOX ? if (( simplifyFlags & QgsMapToPixelSimplifier::SimplifyEnvelope ) && isGeneralizableByMapBoundingBox( envelope, map2pixelTol ) ) { isGeneralizable = generalizeWkbGeometryByBoundingBox( wkbType, sourceWkbPtr, targetWkbPtr, targetWkbSize, envelope, writeHeader ); if ( isGeneralizable ) return true; } if ( !( simplifyFlags & QgsMapToPixelSimplifier::SimplifyGeometry ) ) isGeneralizable = false; // Write the main header of the geometry if ( writeHeader ) { QgsWKBTypes::Type geometryType = sourceWkbPtr.readHeader(); targetWkbPtr << ( char ) QgsApplication::endian() << QgsWKBTypes::flatType( geometryType ); targetWkbSize += targetWkbPtr - targetPrevWkbPtr; } unsigned int flatType = QGis::flatType( wkbType ); // Write the geometry if ( flatType == QGis::WKBLineString || isaLinearRing ) { QgsWkbPtr savedTargetWkbPtr( targetWkbPtr ); double x = 0.0, y = 0.0, lastX = 0, lastY = 0; QgsRectangle r; r.setMinimal(); int skipZM = ( QGis::wkbDimensions( wkbType ) - 2 ) * sizeof( double ); Q_ASSERT( skipZM >= 0 ); int numPoints; sourceWkbPtr >> numPoints; if ( numPoints <= ( isaLinearRing ? 5 : 2 ) ) isGeneralizable = false; QgsWkbPtr numPtr( targetWkbPtr ); int numTargetPoints = 0; targetWkbPtr << numTargetPoints; targetWkbSize += 4; bool isLongSegment; bool hasLongSegments = false; //-> To avoid replace the simplified geometry by its BBOX when there are 'long' segments. bool badLuck = false; // Check whether the LinearRing is really closed. if ( isaLinearRing ) { QgsConstWkbPtr checkPtr( sourceWkbPtr ); double x1, y1, x2, y2; checkPtr >> x1 >> y1; checkPtr += skipZM + ( numPoints - 2 ) * ( 2 * sizeof( double ) + skipZM ); checkPtr >> x2 >> y2; isaLinearRing = qgsDoubleNear( x1, x2 ) && qgsDoubleNear( y1, y2 ); } // Process each vertex... if ( simplifyAlgorithm == SnapToGrid ) { double gridOriginX = envelope.xMinimum(); double gridOriginY = envelope.yMinimum(); // Use a factor for the maximum displacement distance for simplification, similar as GeoServer does float gridInverseSizeXY = map2pixelTol != 0 ? ( float )( 1.0f / ( 0.8 * map2pixelTol ) ) : 0.0f; for ( int i = 0; i < numPoints; ++i ) { sourceWkbPtr >> x >> y; sourceWkbPtr += skipZM; if ( i == 0 || !isGeneralizable || !equalSnapToGrid( x, y, lastX, lastY, gridOriginX, gridOriginY, gridInverseSizeXY ) || ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) ) { targetWkbPtr << x << y; lastX = x; lastY = y; numTargetPoints++; } r.combineExtentWith( x, y ); } }
//! Simplify the WKB-geometry using the specified tolerance bool QgsMapToPixelSimplifier::simplifyWkbGeometry( int simplifyFlags, QGis::WkbType wkbType, const unsigned char* sourceWkb, size_t sourceWkbSize, unsigned char* targetWkb, size_t& targetWkbSize, const QgsRectangle& envelope, double map2pixelTol, bool writeHeader, bool isaLinearRing ) { bool isGeneralizable = true; bool hasZValue = QGis::wkbDimensions( wkbType ) == 3; bool result = false; // Save initial WKB settings to use when the simplification creates invalid geometries const unsigned char* sourcePrevWkb = sourceWkb; unsigned char* targetPrevWkb = targetWkb; size_t targetWkbPrevSize = targetWkbSize; // Can replace the geometry by its BBOX ? if (( simplifyFlags & QgsMapToPixelSimplifier::SimplifyEnvelope ) && isGeneralizableByMapBoundingBox( envelope, map2pixelTol ) ) { isGeneralizable = generalizeWkbGeometryByBoundingBox( wkbType, sourceWkb, sourceWkbSize, targetWkb, targetWkbSize, envelope, writeHeader ); if ( isGeneralizable ) return true; } if ( !( simplifyFlags & QgsMapToPixelSimplifier::SimplifyGeometry ) ) isGeneralizable = false; // Write the main header of the geometry if ( writeHeader ) { targetWkb[0] = sourceWkb[0]; // byteOrder sourceWkb += 1; targetWkb += 1; int geometryType; memcpy( &geometryType, sourceWkb, 4 ); int flatType = QGis::flatType(( QGis::WkbType )geometryType ); memcpy( targetWkb, &flatType, 4 ); // type sourceWkb += 4; targetWkb += 4; targetWkbSize += 5; } const unsigned char* wkb1 = sourceWkb; unsigned char* wkb2 = targetWkb; unsigned int flatType = QGis::flatType( wkbType ); // Write the geometry if ( flatType == QGis::WKBLineString || isaLinearRing ) { double x, y, lastX = 0, lastY = 0; QgsRectangle r; r.setMinimal(); int sizeOfDoubleX = sizeof( double ); int sizeOfDoubleY = QGis::wkbDimensions( wkbType ) == 3 /*hasZValue*/ ? 2 * sizeof( double ) : sizeof( double ); int numPoints; memcpy( &numPoints, sourceWkb, 4 ); sourceWkb += 4; if ( numPoints <= ( isaLinearRing ? 5 : 2 ) ) isGeneralizable = false; int numTargetPoints = 0; memcpy( targetWkb, &numTargetPoints, 4 ); targetWkb += 4; targetWkbSize += 4; double* ptr = ( double* )targetWkb; map2pixelTol *= map2pixelTol; //-> Use mappixelTol for 'LengthSquare' calculations. bool isLongSegment; bool hasLongSegments = false; //-> To avoid replace the simplified geometry by its BBOX when there are 'long' segments. // Check whether the LinearRing is really closed. if ( isaLinearRing ) { double x1, y1, x2, y2; const unsigned char* startWkbX = sourceWkb; const unsigned char* startWkbY = startWkbX + sizeOfDoubleX; const unsigned char* finalWkbX = sourceWkb + ( numPoints - 1 ) * ( sizeOfDoubleX + sizeOfDoubleY ); const unsigned char* finalWkbY = finalWkbX + sizeOfDoubleX; memcpy( &x1, startWkbX, sizeof( double ) ); memcpy( &y1, startWkbY, sizeof( double ) ); memcpy( &x2, finalWkbX, sizeof( double ) ); memcpy( &y2, finalWkbY, sizeof( double ) ); isaLinearRing = ( x1 == x2 ) && ( y1 == y2 ); } // Process each vertex... for ( int i = 0; i < numPoints; ++i ) { memcpy( &x, sourceWkb, sizeof( double ) ); sourceWkb += sizeOfDoubleX; memcpy( &y, sourceWkb, sizeof( double ) ); sourceWkb += sizeOfDoubleY; isLongSegment = false; if ( i == 0 || !isGeneralizable || ( isLongSegment = ( calculateLengthSquared2D( x, y, lastX, lastY ) > map2pixelTol ) ) || ( !isaLinearRing && ( i == 1 || i >= numPoints - 2 ) ) ) { memcpy( ptr, &x, sizeof( double ) ); lastX = x; ptr++; memcpy( ptr, &y, sizeof( double ) ); lastY = y; ptr++; numTargetPoints++; hasLongSegments |= isLongSegment; } r.combineExtentWith( x, y ); } targetWkb = wkb2 + 4; if ( numTargetPoints < ( isaLinearRing ? 4 : 2 ) ) { // we simplified the geometry too much! if ( !hasLongSegments ) { // approximate the geometry's shape by its bounding box // (rect for linear ring / one segment for line string) unsigned char* targetTempWkb = targetWkb; size_t targetWkbTempSize = targetWkbSize; sourceWkb = sourcePrevWkb; targetWkb = targetPrevWkb; targetWkbSize = targetWkbPrevSize; if ( generalizeWkbGeometryByBoundingBox( wkbType, sourceWkb, sourceWkbSize, targetWkb, targetWkbSize, r, writeHeader ) ) return true; targetWkb = targetTempWkb; targetWkbSize = targetWkbTempSize; } else { // Bad luck! The simplified geometry is invalid and approximation by bounding box // would create artifacts due to long segments. Worst of all, we may have overwritten // the original coordinates by the simplified ones (source and target WKB ptr can be the same) // so we cannot even undo the changes here. We will return invalid geometry and hope that // other pieces of QGIS will survive that :-/ } } if ( isaLinearRing ) { // make sure we keep the linear ring closed memcpy( &x, targetWkb + 0, sizeof( double ) ); memcpy( &y, targetWkb + sizeof( double ), sizeof( double ) ); if ( lastX != x || lastY != y ) { memcpy( ptr, &x, sizeof( double ) ); ptr++; memcpy( ptr, &y, sizeof( double ) ); ptr++; numTargetPoints++; } } targetWkbSize += numTargetPoints * sizeof( double ) * 2; targetWkb = wkb2; memcpy( targetWkb, &numTargetPoints, 4 ); result = numPoints != numTargetPoints; } else if ( flatType == QGis::WKBPolygon ) { int numRings; memcpy( &numRings, sourceWkb, 4 ); sourceWkb += 4; memcpy( targetWkb, &numRings, 4 ); targetWkb += 4; targetWkbSize += 4; for ( int i = 0; i < numRings; ++i ) { int numPoints_i; memcpy( &numPoints_i, sourceWkb, 4 ); QgsRectangle envelope_i = numRings == 1 ? envelope : calculateBoundingBox( wkbType, sourceWkb + 4, numPoints_i ); size_t sourceWkbSize_i = 4 + numPoints_i * ( hasZValue ? 3 : 2 ) * sizeof( double ); size_t targetWkbSize_i = 0; result |= simplifyWkbGeometry( simplifyFlags, wkbType, sourceWkb, sourceWkbSize_i, targetWkb, targetWkbSize_i, envelope_i, map2pixelTol, false, true ); sourceWkb += sourceWkbSize_i; targetWkb += targetWkbSize_i; targetWkbSize += targetWkbSize_i; } } else if ( flatType == QGis::WKBMultiLineString || flatType == QGis::WKBMultiPolygon ) { int numGeoms; memcpy( &numGeoms, sourceWkb, 4 ); sourceWkb += 4; wkb1 += 4; memcpy( targetWkb, &numGeoms, 4 ); targetWkb += 4; targetWkbSize += 4; for ( int i = 0; i < numGeoms; ++i ) { size_t sourceWkbSize_i = 0; size_t targetWkbSize_i = 0; // ... calculate the wkb-size of the current child complex geometry if ( flatType == QGis::WKBMultiLineString ) { int numPoints_i; memcpy( &numPoints_i, wkb1 + 5, 4 ); int wkbSize_i = 4 + numPoints_i * ( hasZValue ? 3 : 2 ) * sizeof( double ); sourceWkbSize_i += 5 + wkbSize_i; wkb1 += 5 + wkbSize_i; } else { int numPrings_i; memcpy( &numPrings_i, wkb1 + 5, 4 ); sourceWkbSize_i = 9; wkb1 += 9; for ( int j = 0; j < numPrings_i; ++j ) { int numPoints_i; memcpy( &numPoints_i, wkb1, 4 ); int wkbSize_i = 4 + numPoints_i * ( hasZValue ? 3 : 2 ) * sizeof( double ); sourceWkbSize_i += wkbSize_i; wkb1 += wkbSize_i; } } result |= simplifyWkbGeometry( simplifyFlags, QGis::singleType( wkbType ), sourceWkb, sourceWkbSize_i, targetWkb, targetWkbSize_i, envelope, map2pixelTol, true, false ); sourceWkb += sourceWkbSize_i; targetWkb += targetWkbSize_i; targetWkbSize += targetWkbSize_i; } } return result; }