QgsRectangle QgsMapSettings::fullExtent() const { // reset the map canvas extent since the extent may now be smaller // We can't use a constructor since QgsRectangle normalizes the rectangle upon construction QgsRectangle fullExtent; fullExtent.setMinimal(); // iterate through the map layers and test each layers extent // against the current min and max values QgsDebugMsgLevel( QStringLiteral( "Layer count: %1" ).arg( mLayers.count() ), 5 ); const auto constMLayers = mLayers; for ( const QgsWeakMapLayerPointer &layerPtr : constMLayers ) { if ( QgsMapLayer *lyr = layerPtr.data() ) { QgsDebugMsgLevel( "Updating extent using " + lyr->name(), 5 ); QgsDebugMsgLevel( "Input extent: " + lyr->extent().toString(), 5 ); if ( lyr->extent().isNull() ) continue; // Layer extents are stored in the coordinate system (CS) of the // layer. The extent must be projected to the canvas CS QgsRectangle extent = layerExtentToOutputExtent( lyr, lyr->extent() ); QgsDebugMsgLevel( "Output extent: " + extent.toString(), 5 ); fullExtent.combineExtentWith( extent ); } } if ( fullExtent.width() == 0.0 || fullExtent.height() == 0.0 ) { // If all of the features are at the one point, buffer the // rectangle a bit. If they are all at zero, do something a bit // more crude. if ( fullExtent.xMinimum() == 0.0 && fullExtent.xMaximum() == 0.0 && fullExtent.yMinimum() == 0.0 && fullExtent.yMaximum() == 0.0 ) { fullExtent.set( -1.0, -1.0, 1.0, 1.0 ); } else { const double padFactor = 1e-8; double widthPad = fullExtent.xMinimum() * padFactor; double heightPad = fullExtent.yMinimum() * padFactor; double xmin = fullExtent.xMinimum() - widthPad; double xmax = fullExtent.xMaximum() + widthPad; double ymin = fullExtent.yMinimum() - heightPad; double ymax = fullExtent.yMaximum() + heightPad; fullExtent.set( xmin, ymin, xmax, ymax ); } } QgsDebugMsgLevel( "Full extent: " + fullExtent.toString(), 5 ); return fullExtent; }
QgsRectangle QgsMeshLayer::extent() const { if ( mDataProvider ) return mDataProvider->extent(); else { QgsRectangle rec; rec.setMinimal(); return rec; } }
//! Returns whether the device-geometry can be replaced by its BBOX when is applied the specified tolerance bool QgsAbstractGeometrySimplifier::isGeneralizableByDeviceBoundingBox( const QVector<QPointF>& points, float mapToPixelTol ) { QgsRectangle r; r.setMinimal(); for ( int i = 0, numPoints = points.size(); i < numPoints; ++i ) { r.combineExtentWith( points[i].x(), points[i].y() ); } return isGeneralizableByDeviceBoundingBox( r, mapToPixelTol ); }
void QgsExpressionSelectionDialog::mButtonZoomToFeatures_clicked() { if ( mExpressionBuilder->expressionText().isEmpty() || !mMapCanvas ) return; QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) ); QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( mExpressionBuilder->expressionText() ) .setExpressionContext( context ) .setNoAttributes(); QgsFeatureIterator features = mLayer->getFeatures( request ); QgsRectangle bbox; bbox.setMinimal(); QgsFeature feat; int featureCount = 0; while ( features.nextFeature( feat ) ) { QgsGeometry geom = feat.geometry(); if ( geom.isNull() || geom.constGet()->isEmpty() ) continue; QgsRectangle r = mMapCanvas->mapSettings().layerExtentToOutputExtent( mLayer, geom.boundingBox() ); bbox.combineExtentWith( r ); featureCount++; } features.close(); QgsSettings settings; int timeout = settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt(); if ( featureCount > 0 ) { mMapCanvas->zoomToFeatureExtent( bbox ); if ( mMessageBar ) { mMessageBar->pushMessage( QString(), tr( "Zoomed to %n matching feature(s)", "number of matching features", featureCount ), Qgis::Info, timeout ); } } else if ( mMessageBar ) { mMessageBar->pushMessage( QString(), tr( "No matching features found" ), Qgis::Info, timeout ); } saveRecent(); }
void QgsSelectByFormDialog::zoomToFeatures( const QString &filter ) { QgsFeatureIds ids; QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) ); QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( filter ) .setExpressionContext( context ) .setSubsetOfAttributes( QgsAttributeList() ); QgsFeatureIterator features = mLayer->getFeatures( request ); QgsRectangle bbox; bbox.setMinimal(); QgsFeature feat; int featureCount = 0; while ( features.nextFeature( feat ) ) { QgsGeometry geom = feat.geometry(); if ( geom.isNull() || geom.geometry()->isEmpty() ) continue; QgsRectangle r = mMapCanvas->mapSettings().layerExtentToOutputExtent( mLayer, geom.boundingBox() ); bbox.combineExtentWith( r ); featureCount++; } features.close(); QgsSettings settings; int timeout = settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt(); if ( featureCount > 0 ) { mMapCanvas->zoomToFeatureExtent( bbox ); if ( mMessageBar ) { mMessageBar->pushMessage( QString(), tr( "Zoomed to %n matching feature(s)", "number of matching features", featureCount ), QgsMessageBar::INFO, timeout ); } } else if ( mMessageBar ) { mMessageBar->pushMessage( QString(), tr( "No matching features found" ), QgsMessageBar::INFO, timeout ); } }
QgsRectangle QgsMeshDataProvider::extent() const { QgsRectangle rec; rec.setMinimal(); for ( int i = 0; i < vertexCount(); ++i ) { QgsMeshVertex v = vertex( i ); rec.setXMinimum( std::min( rec.xMinimum(), v.x() ) ); rec.setYMinimum( std::min( rec.yMinimum(), v.y() ) ); rec.setXMaximum( std::max( rec.xMaximum(), v.x() ) ); rec.setYMaximum( std::max( rec.yMaximum(), v.y() ) ); } return rec; }
//! Returns the BBOX of the specified WKB-point stream inline static QgsRectangle calculateBoundingBox( QGis::WkbType wkbType, QgsConstWkbPtr wkbPtr, int numPoints ) { QgsRectangle r; r.setMinimal(); int skipZM = ( QGis::wkbDimensions( wkbType ) - 2 ) * sizeof( double ); Q_ASSERT( skipZM >= 0 ); for ( int i = 0; i < numPoints; ++i ) { double x, y; wkbPtr >> x >> y; wkbPtr += skipZM; r.combineExtentWith( x, y ); } return r; }
//! Returns the BBOX of the specified WKB-point stream inline static QgsRectangle calculateBoundingBox( QGis::WkbType wkbType, const unsigned char* wkb, size_t numPoints ) { double x, y; QgsRectangle r; r.setMinimal(); int sizeOfDoubleX = sizeof( double ); int sizeOfDoubleY = QGis::wkbDimensions( wkbType ) == 3 /*hasZValue*/ ? 2 * sizeof( double ) : sizeof( double ); for ( size_t i = 0; i < numPoints; ++i ) { memcpy( &x, wkb, sizeof( double ) ); wkb += sizeOfDoubleX; memcpy( &y, wkb, sizeof( double ) ); wkb += sizeOfDoubleY; r.combineExtentWith( x, y ); } return r; }
QgsRectangle QgsMapSettings::fullExtent() const { QgsDebugMsg( "called." ); QgsMapLayerRegistry* registry = QgsMapLayerRegistry::instance(); // reset the map canvas extent since the extent may now be smaller // We can't use a constructor since QgsRectangle normalizes the rectangle upon construction QgsRectangle fullExtent; fullExtent.setMinimal(); // iterate through the map layers and test each layers extent // against the current min and max values QStringList::const_iterator it = mLayers.begin(); QgsDebugMsg( QString( "Layer count: %1" ).arg( mLayers.count() ) ); while ( it != mLayers.end() ) { QgsMapLayer * lyr = registry->mapLayer( *it ); if ( !lyr ) { QgsDebugMsg( QString( "WARNING: layer '%1' not found in map layer registry!" ).arg( *it ) ); } else { QgsDebugMsg( "Updating extent using " + lyr->name() ); QgsDebugMsg( "Input extent: " + lyr->extent().toString() ); if ( lyr->extent().isNull() ) { ++it; continue; } // Layer extents are stored in the coordinate system (CS) of the // layer. The extent must be projected to the canvas CS QgsRectangle extent = layerExtentToOutputExtent( lyr, lyr->extent() ); QgsDebugMsg( "Output extent: " + extent.toString() ); fullExtent.unionRect( extent ); } ++it; } if ( fullExtent.width() == 0.0 || fullExtent.height() == 0.0 ) { // If all of the features are at the one point, buffer the // rectangle a bit. If they are all at zero, do something a bit // more crude. if ( fullExtent.xMinimum() == 0.0 && fullExtent.xMaximum() == 0.0 && fullExtent.yMinimum() == 0.0 && fullExtent.yMaximum() == 0.0 ) { fullExtent.set( -1.0, -1.0, 1.0, 1.0 ); } else { const double padFactor = 1e-8; double widthPad = fullExtent.xMinimum() * padFactor; double heightPad = fullExtent.yMinimum() * padFactor; double xmin = fullExtent.xMinimum() - widthPad; double xmax = fullExtent.xMaximum() + widthPad; double ymin = fullExtent.yMinimum() - heightPad; double ymax = fullExtent.yMaximum() + heightPad; fullExtent.set( xmin, ymin, xmax, ymax ); } } QgsDebugMsg( "Full extent: " + fullExtent.toString() ); return fullExtent; }
//! 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 ); } }
QgsRasterLayerRenderer::QgsRasterLayerRenderer( QgsRasterLayer* layer, QgsRenderContext& rendererContext ) : QgsMapLayerRenderer( layer->id() ) , mRasterViewPort( nullptr ) , mPipe( nullptr ) { mPainter = rendererContext.painter(); const QgsMapToPixel& theQgsMapToPixel = rendererContext.mapToPixel(); mMapToPixel = &theQgsMapToPixel; QgsMapToPixel mapToPixel = theQgsMapToPixel; if ( mapToPixel.mapRotation() ) { // unset rotation for the sake of local computations. // Rotation will be handled by QPainter later // TODO: provide a method of QgsMapToPixel to fetch map center // in geographical units QgsPoint center = mapToPixel.toMapCoordinates( mapToPixel.mapWidth() / 2.0, mapToPixel.mapHeight() / 2.0 ); mapToPixel.setMapRotation( 0, center.x(), center.y() ); } QgsRectangle myProjectedViewExtent; QgsRectangle myProjectedLayerExtent; if ( rendererContext.coordinateTransform() ) { QgsDebugMsg( "coordinateTransform set -> project extents." ); try { myProjectedViewExtent = rendererContext.coordinateTransform()->transformBoundingBox( rendererContext.extent() ); } catch ( QgsCsException &cs ) { QgsMessageLog::logMessage( QObject::tr( "Could not reproject view extent: %1" ).arg( cs.what() ), QObject::tr( "Raster" ) ); myProjectedViewExtent.setMinimal(); } try { myProjectedLayerExtent = rendererContext.coordinateTransform()->transformBoundingBox( layer->extent() ); } catch ( QgsCsException &cs ) { QgsMessageLog::logMessage( QObject::tr( "Could not reproject layer extent: %1" ).arg( cs.what() ), QObject::tr( "Raster" ) ); myProjectedLayerExtent.setMinimal(); } } else { QgsDebugMsg( "coordinateTransform not set" ); myProjectedViewExtent = rendererContext.extent(); myProjectedLayerExtent = layer->extent(); } // clip raster extent to view extent QgsRectangle myRasterExtent = myProjectedViewExtent.intersect( &myProjectedLayerExtent ); if ( myRasterExtent.isEmpty() ) { QgsDebugMsg( "draw request outside view extent." ); // nothing to do return; } QgsDebugMsg( "theViewExtent is " + rendererContext.extent().toString() ); QgsDebugMsg( "myProjectedViewExtent is " + myProjectedViewExtent.toString() ); QgsDebugMsg( "myProjectedLayerExtent is " + myProjectedLayerExtent.toString() ); QgsDebugMsg( "myRasterExtent is " + myRasterExtent.toString() ); // // The first thing we do is set up the QgsRasterViewPort. This struct stores all the settings // relating to the size (in pixels and coordinate system units) of the raster part that is // in view in the map window. It also stores the origin. // //this is not a class level member because every time the user pans or zooms //the contents of the rasterViewPort will change mRasterViewPort = new QgsRasterViewPort(); mRasterViewPort->mDrawnExtent = myRasterExtent; if ( rendererContext.coordinateTransform() ) { mRasterViewPort->mSrcCRS = layer->crs(); mRasterViewPort->mDestCRS = rendererContext.coordinateTransform()->destCRS(); mRasterViewPort->mSrcDatumTransform = rendererContext.coordinateTransform()->sourceDatumTransform(); mRasterViewPort->mDestDatumTransform = rendererContext.coordinateTransform()->destinationDatumTransform(); } else { mRasterViewPort->mSrcCRS = QgsCoordinateReferenceSystem(); // will be invalid mRasterViewPort->mDestCRS = QgsCoordinateReferenceSystem(); // will be invalid mRasterViewPort->mSrcDatumTransform = -1; mRasterViewPort->mDestDatumTransform = -1; } // get dimensions of clipped raster image in device coordinate space (this is the size of the viewport) mRasterViewPort->mTopLeftPoint = mapToPixel.transform( myRasterExtent.xMinimum(), myRasterExtent.yMaximum() ); mRasterViewPort->mBottomRightPoint = mapToPixel.transform( myRasterExtent.xMaximum(), myRasterExtent.yMinimum() ); // align to output device grid, i.e. floor/ceil to integers // TODO: this should only be done if paint device is raster - screen, image // for other devices (pdf) it can have floating point origin // we could use floating point for raster devices as well, but respecting the // output device grid should make it more effective as the resampling is done in // the provider anyway mRasterViewPort->mTopLeftPoint.setX( floor( mRasterViewPort->mTopLeftPoint.x() ) ); mRasterViewPort->mTopLeftPoint.setY( floor( mRasterViewPort->mTopLeftPoint.y() ) ); mRasterViewPort->mBottomRightPoint.setX( ceil( mRasterViewPort->mBottomRightPoint.x() ) ); mRasterViewPort->mBottomRightPoint.setY( ceil( mRasterViewPort->mBottomRightPoint.y() ) ); // recalc myRasterExtent to aligned values myRasterExtent.set( mapToPixel.toMapCoordinatesF( mRasterViewPort->mTopLeftPoint.x(), mRasterViewPort->mBottomRightPoint.y() ), mapToPixel.toMapCoordinatesF( mRasterViewPort->mBottomRightPoint.x(), mRasterViewPort->mTopLeftPoint.y() ) ); //raster viewport top left / bottom right are already rounded to int mRasterViewPort->mWidth = static_cast<int>( mRasterViewPort->mBottomRightPoint.x() - mRasterViewPort->mTopLeftPoint.x() ); mRasterViewPort->mHeight = static_cast<int>( mRasterViewPort->mBottomRightPoint.y() - mRasterViewPort->mTopLeftPoint.y() ); //the drawable area can start to get very very large when you get down displaying 2x2 or smaller, this is becasue //mapToPixel.mapUnitsPerPixel() is less then 1, //so we will just get the pixel data and then render these special cases differently in paintImageToCanvas() QgsDebugMsgLevel( QString( "mapUnitsPerPixel = %1" ).arg( mapToPixel.mapUnitsPerPixel() ), 3 ); QgsDebugMsgLevel( QString( "mWidth = %1" ).arg( layer->width() ), 3 ); QgsDebugMsgLevel( QString( "mHeight = %1" ).arg( layer->height() ), 3 ); QgsDebugMsgLevel( QString( "myRasterExtent.xMinimum() = %1" ).arg( myRasterExtent.xMinimum() ), 3 ); QgsDebugMsgLevel( QString( "myRasterExtent.xMaximum() = %1" ).arg( myRasterExtent.xMaximum() ), 3 ); QgsDebugMsgLevel( QString( "myRasterExtent.yMinimum() = %1" ).arg( myRasterExtent.yMinimum() ), 3 ); QgsDebugMsgLevel( QString( "myRasterExtent.yMaximum() = %1" ).arg( myRasterExtent.yMaximum() ), 3 ); QgsDebugMsgLevel( QString( "mTopLeftPoint.x() = %1" ).arg( mRasterViewPort->mTopLeftPoint.x() ), 3 ); QgsDebugMsgLevel( QString( "mBottomRightPoint.x() = %1" ).arg( mRasterViewPort->mBottomRightPoint.x() ), 3 ); QgsDebugMsgLevel( QString( "mTopLeftPoint.y() = %1" ).arg( mRasterViewPort->mTopLeftPoint.y() ), 3 ); QgsDebugMsgLevel( QString( "mBottomRightPoint.y() = %1" ).arg( mRasterViewPort->mBottomRightPoint.y() ), 3 ); QgsDebugMsgLevel( QString( "mWidth = %1" ).arg( mRasterViewPort->mWidth ), 3 ); QgsDebugMsgLevel( QString( "mHeight = %1" ).arg( mRasterViewPort->mHeight ), 3 ); // /\/\/\ - added to handle zoomed-in rasters // TODO R->mLastViewPort = *mRasterViewPort; // TODO: is it necessary? Probably WMS only? layer->dataProvider()->setDpi( rendererContext.rasterScaleFactor() * 25.4 * rendererContext.scaleFactor() ); // copy the whole raster pipe! mPipe = new QgsRasterPipe( *layer->pipe() ); }
//! 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, QGis::WkbType wkbType, 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 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 = generalizeWkbGeometry( 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; } 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. // Check whether the LinearRing is really closed. if ( isaLinearRing ) { double x1, y1, x2, y2; unsigned char* startWkbX = sourceWkb; unsigned char* startWkbY = startWkbX + sizeOfDoubleX; unsigned char* finalWkbX = sourceWkb + ( numPoints - 1 ) * ( sizeOfDoubleX + sizeOfDoubleY ); 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; if ( i == 0 || !isGeneralizable || 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++; } r.combineExtentWith( x, y ); } targetWkb = wkb2 + 4; // Fix the topology of the geometry if ( numTargetPoints <= ( isaLinearRing ? 2 : 1 ) ) { unsigned char* targetTempWkb = targetWkb; int targetWkbTempSize = targetWkbSize; sourceWkb = sourcePrevWkb; targetWkb = targetPrevWkb; targetWkbSize = targetWkbPrevSize; if ( generalizeWkbGeometry( wkbType, sourceWkb, sourceWkbSize, targetWkb, targetWkbSize, r, writeHeader ) ) return true; targetWkb = targetTempWkb; targetWkbSize = targetWkbTempSize; } if ( isaLinearRing ) { 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; }
//! 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; }