QGIS API Documentation 3.99.0-Master (752b475928d)
Loading...
Searching...
No Matches
qgsrasterlayerprofilegenerator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsrasterlayerprofilegenerator.cpp
3 ---------------
4 begin : March 2022
5 copyright : (C) 2022 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
18
19#include "qgscurve.h"
20#include "qgsfillsymbol.h"
21#include "qgsgeometryengine.h"
22#include "qgsgeos.h"
23#include "qgslinesymbol.h"
24#include "qgsprofilepoint.h"
25#include "qgsprofilerequest.h"
26#include "qgsrasteriterator.h"
27#include "qgsrasterlayer.h"
29#include "qgsthreadingutils.h"
30
31#include <QPolygonF>
32#include <QThread>
33
34//
35// QgsRasterLayerProfileResults
36//
37
39{
40 return QStringLiteral( "raster" );
41}
42
43QVector<QgsProfileIdentifyResults> QgsRasterLayerProfileResults::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
44{
45 const QVector<QgsProfileIdentifyResults> noLayerResults = QgsAbstractProfileSurfaceResults::identify( point, context );
46
47 // we have to make a new list, with the correct layer reference set
48 QVector<QgsProfileIdentifyResults> res;
49 res.reserve( noLayerResults.size() );
50 for ( const QgsProfileIdentifyResults &result : noLayerResults )
51 {
52 res.append( QgsProfileIdentifyResults( mLayer, result.results() ) );
53 }
54 return res;
55}
56
57
58
59//
60// QgsRasterLayerProfileGenerator
61//
62
65 , mId( layer->id() )
66 , mFeedback( std::make_unique< QgsRasterBlockFeedback >() )
67 , mProfileCurve( request.profileCurve() ? request.profileCurve()->clone() : nullptr )
68 , mSourceCrs( layer->crs() )
69 , mTargetCrs( request.crs() )
70 , mTransformContext( request.transformContext() )
71 , mOffset( layer->elevationProperties()->zOffset() )
72 , mScale( layer->elevationProperties()->zScale() )
73 , mLayer( layer )
74 , mBand( qgis::down_cast< QgsRasterLayerElevationProperties * >( layer->elevationProperties() )->bandNumber() )
75 , mRasterUnitsPerPixelX( layer->rasterUnitsPerPixelX() )
76 , mRasterUnitsPerPixelY( layer->rasterUnitsPerPixelY() )
77 , mStepDistance( request.stepDistance() )
78{
79 mRasterProvider.reset( layer->dataProvider()->clone() );
80 mRasterProvider->moveToThread( nullptr );
81
82 mSymbology = qgis::down_cast< QgsRasterLayerElevationProperties * >( layer->elevationProperties() )->profileSymbology();
83 mElevationLimit = qgis::down_cast< QgsRasterLayerElevationProperties * >( layer->elevationProperties() )->elevationLimit();
84 mLineSymbol.reset( qgis::down_cast< QgsRasterLayerElevationProperties * >( layer->elevationProperties() )->profileLineSymbol()->clone() );
85 mFillSymbol.reset( qgis::down_cast< QgsRasterLayerElevationProperties * >( layer->elevationProperties() )->profileFillSymbol()->clone() );
86}
87
89{
90 return mId;
91}
92
97
99
101{
102 if ( !mProfileCurve || mFeedback->isCanceled() )
103 return false;
104
105 QgsScopedAssignObjectToCurrentThread assignProviderToCurrentThread( mRasterProvider.get() );
106
107 const double startDistanceOffset = std::max( !context.distanceRange().isInfinite() ? context.distanceRange().lower() : 0, 0.0 );
108 const double endDistance = context.distanceRange().upper();
109
110 std::unique_ptr< QgsCurve > trimmedCurve;
111 QgsCurve *sourceCurve = nullptr;
112 if ( startDistanceOffset > 0 || endDistance < mProfileCurve->length() )
113 {
114 trimmedCurve.reset( mProfileCurve->curveSubstring( startDistanceOffset, endDistance ) );
115 sourceCurve = trimmedCurve.get();
116 }
117 else
118 {
119 sourceCurve = mProfileCurve.get();
120 }
121
122 // we need to transform the profile curve to the raster's CRS
123 std::unique_ptr< QgsCurve > transformedCurve( sourceCurve->clone() );
124 const QgsCoordinateTransform rasterToTargetTransform( mSourceCrs, mTargetCrs, mTransformContext );
125 try
126 {
127 transformedCurve->transform( rasterToTargetTransform, Qgis::TransformDirection::Reverse );
128 }
129 catch ( QgsCsException & )
130 {
131 QgsDebugError( QStringLiteral( "Error transforming profile line to raster CRS" ) );
132 return false;
133 }
134
135 if ( mFeedback->isCanceled() )
136 return false;
137
138 const QgsRectangle profileCurveBoundingBox = transformedCurve->boundingBox();
139 if ( !profileCurveBoundingBox.intersects( mRasterProvider->extent() ) )
140 return false;
141
142 if ( mFeedback->isCanceled() )
143 return false;
144
145 mResults = std::make_unique< QgsRasterLayerProfileResults >();
146 mResults->mLayer = mLayer;
147 mResults->mId = mId;
148 mResults->copyPropertiesFromGenerator( this );
149
150 std::unique_ptr< QgsGeometryEngine > curveEngine( QgsGeometry::createGeometryEngine( transformedCurve.get() ) );
151 curveEngine->prepareGeometry();
152
153 if ( mFeedback->isCanceled() )
154 return false;
155
156 double stepDistance = mStepDistance;
157 if ( !std::isnan( context.maximumErrorMapUnits() ) )
158 {
159 // convert the maximum error in curve units to a step distance
160 // TODO -- there's no point in this being << pixel size!
161 if ( std::isnan( stepDistance ) || context.maximumErrorMapUnits() > stepDistance )
162 {
163 stepDistance = context.maximumErrorMapUnits();
164 }
165 }
166
167 QSet< QgsPointXY > profilePoints;
168 if ( !std::isnan( stepDistance ) )
169 {
170 // if specific step distance specified, use this to generate points along the curve
171 QgsGeometry densifiedCurve( sourceCurve->clone() );
172 densifiedCurve = densifiedCurve.densifyByDistance( stepDistance );
173 densifiedCurve.transform( rasterToTargetTransform, Qgis::TransformDirection::Reverse );
174 profilePoints.reserve( densifiedCurve.constGet()->nCoordinates() );
175 for ( auto it = densifiedCurve.vertices_begin(); it != densifiedCurve.vertices_end(); ++it )
176 {
177 profilePoints.insert( *it );
178 }
179 }
180
181 if ( mFeedback->isCanceled() )
182 return false;
183
184 // calculate the portion of the raster which actually covers the curve
185 int subRegionWidth = 0;
186 int subRegionHeight = 0;
187 int subRegionLeft = 0;
188 int subRegionTop = 0;
189 QgsRectangle rasterSubRegion = mRasterProvider->xSize() > 0 && mRasterProvider->ySize() > 0 ?
191 mRasterProvider->extent(),
192 mRasterProvider->xSize(),
193 mRasterProvider->ySize(),
194 transformedCurve->boundingBox(),
195 subRegionWidth,
196 subRegionHeight,
197 subRegionLeft,
198 subRegionTop ) : transformedCurve->boundingBox();
199
200 const bool zeroXYSize = mRasterProvider->xSize() == 0 || mRasterProvider->ySize() == 0;
201 if ( zeroXYSize )
202 {
203 const double curveLengthInPixels = sourceCurve->length() / context.mapUnitsPerDistancePixel();
204 const double conversionFactor = curveLengthInPixels / transformedCurve->length();
205 subRegionWidth = rasterSubRegion.width() * conversionFactor;
206 subRegionHeight = rasterSubRegion.height() * conversionFactor;
207
208 // ensure we fetch at least 1 pixel wide/high blocks, otherwise exactly vertical/horizontal profile lines will result in zero size blocks
209 // see https://github.com/qgis/QGIS/issues/51196
210 if ( subRegionWidth == 0 )
211 {
212 subRegionWidth = 1;
213 rasterSubRegion.setXMaximum( rasterSubRegion.xMinimum() + 1 / conversionFactor );
214 }
215 if ( subRegionHeight == 0 )
216 {
217 subRegionHeight = 1;
218 rasterSubRegion.setYMaximum( rasterSubRegion.yMinimum() + 1 / conversionFactor );
219 }
220 }
221
222 // iterate over the raster blocks, throwing away any which don't intersect the profile curve
223 QgsRasterIterator it( mRasterProvider.get() );
224 // we use smaller tile sizes vs the default, as we will be skipping over tiles which don't intersect the curve at all,
225 // and we expect that to be the VAST majority of the tiles in the raster.
226 // => Smaller tile sizes = more regions we can shortcut over = less pixels to iterate over = faster runtime
227 it.setMaximumTileHeight( 64 );
228 it.setMaximumTileWidth( 64 );
229
230 it.startRasterRead( mBand, subRegionWidth, subRegionHeight, rasterSubRegion );
231
232 const double halfPixelSizeX = mRasterUnitsPerPixelX / 2.0;
233 const double halfPixelSizeY = mRasterUnitsPerPixelY / 2.0;
234 int blockColumns = 0;
235 int blockRows = 0;
236 int blockTopLeftColumn = 0;
237 int blockTopLeftRow = 0;
238 QgsRectangle blockExtent;
239
240 while ( it.next( mBand, blockColumns, blockRows, blockTopLeftColumn, blockTopLeftRow, blockExtent ) )
241 {
242 if ( mFeedback->isCanceled() )
243 return false;
244
245 const QgsGeometry blockExtentGeom = QgsGeometry::fromRect( blockExtent );
246 if ( !curveEngine->intersects( blockExtentGeom.constGet() ) )
247 continue;
248
249 std::unique_ptr< QgsRasterBlock > block( mRasterProvider->block( mBand, blockExtent, blockColumns, blockRows, mFeedback.get() ) );
250 if ( mFeedback->isCanceled() )
251 return false;
252
253 if ( !block )
254 continue;
255
256 bool isNoData = false;
257
258 // there's two potential code paths we use here, depending on if we want to sample at every pixel, or if we only want to
259 // sample at specific points
260 if ( !std::isnan( stepDistance ) )
261 {
262 auto it = profilePoints.begin();
263 while ( it != profilePoints.end() )
264 {
265 if ( mFeedback->isCanceled() )
266 return false;
267
268 // convert point to a pixel and sample, if it's in this block
269 if ( blockExtent.contains( *it ) )
270 {
271 int row, col;
272 if ( zeroXYSize )
273 {
274 row = std::clamp( static_cast< int >( std::round( ( blockExtent.yMaximum() - it->y() ) * blockRows / blockExtent.height() ) ), 0, blockRows - 1 );
275 col = std::clamp( static_cast< int >( std::round( ( it->x() - blockExtent.xMinimum() ) * blockColumns / blockExtent.width() ) ), 0, blockColumns - 1 );
276 }
277 else
278 {
279 row = std::clamp( static_cast< int >( std::round( ( blockExtent.yMaximum() - it->y() ) / mRasterUnitsPerPixelY ) ), 0, blockRows - 1 );
280 col = std::clamp( static_cast< int >( std::round( ( it->x() - blockExtent.xMinimum() ) / mRasterUnitsPerPixelX ) ), 0, blockColumns - 1 );
281 }
282 double val = block->valueAndNoData( row, col, isNoData );
283 if ( !isNoData )
284 {
285 val = val * mScale + mOffset;
286 }
287 else
288 {
289 val = std::numeric_limits<double>::quiet_NaN();
290 }
291
292 QgsPoint pixel( it->x(), it->y(), val );
293 try
294 {
295 pixel.transform( rasterToTargetTransform );
296 }
297 catch ( QgsCsException & )
298 {
299 continue;
300 }
301 mResults->mRawPoints.append( pixel );
302
303 it = profilePoints.erase( it );
304 }
305 else
306 {
307 it++;
308 }
309 }
310
311 if ( profilePoints.isEmpty() )
312 break; // all done!
313 }
314 else
315 {
316 double currentY = blockExtent.yMaximum() - 0.5 * mRasterUnitsPerPixelY;
317 for ( int row = 0; row < blockRows; ++row )
318 {
319 if ( mFeedback->isCanceled() )
320 return false;
321
322 double currentX = blockExtent.xMinimum() + 0.5 * mRasterUnitsPerPixelX;
323 for ( int col = 0; col < blockColumns; ++col, currentX += mRasterUnitsPerPixelX )
324 {
325 const double val = block->valueAndNoData( row, col, isNoData );
326
327 // does pixel intersect curve?
328 QgsGeometry pixelRectGeometry = QgsGeometry::fromRect( QgsRectangle( currentX - halfPixelSizeX,
329 currentY - halfPixelSizeY,
330 currentX + halfPixelSizeX,
331 currentY + halfPixelSizeY ) );
332 if ( !curveEngine->intersects( pixelRectGeometry.constGet() ) )
333 continue;
334
335 QgsPoint pixel( currentX, currentY, isNoData ? std::numeric_limits<double>::quiet_NaN() : val * mScale + mOffset );
336 try
337 {
338 pixel.transform( rasterToTargetTransform );
339 }
340 catch ( QgsCsException & )
341 {
342 continue;
343 }
344 mResults->mRawPoints.append( pixel );
345 }
346 currentY -= mRasterUnitsPerPixelY;
347 }
348 }
349 }
350
351 if ( mFeedback->isCanceled() )
352 return false;
353
354 // convert x/y values back to distance/height values
355 QgsGeos originalCurveGeos( sourceCurve );
356 originalCurveGeos.prepareGeometry();
357 QString lastError;
358 for ( const QgsPoint &pixel : std::as_const( mResults->mRawPoints ) )
359 {
360 if ( mFeedback->isCanceled() )
361 return false;
362
363 const double distance = originalCurveGeos.lineLocatePoint( pixel, &lastError ) + startDistanceOffset;
364
365 if ( !std::isnan( pixel.z() ) )
366 {
367 mResults->minZ = std::min( pixel.z(), mResults->minZ );
368 mResults->maxZ = std::max( pixel.z(), mResults->maxZ );
369 }
370 mResults->mDistanceToHeightMap.insert( distance, pixel.z() );
371 }
372
373 return true;
374}
375
380
382{
383 return mFeedback.get();
384}
@ RespectsMaximumErrorMapUnit
Generated profile respects the QgsProfileGenerationContext::maximumErrorMapUnits() property.
Definition qgis.h:4219
@ RespectsDistanceRange
Generated profile respects the QgsProfileGenerationContext::distanceRange() property.
Definition qgis.h:4220
QFlags< ProfileGeneratorFlag > ProfileGeneratorFlags
Definition qgis.h:4224
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2673
virtual int nCoordinates() const
Returns the number of nodes contained in the geometry.
virtual double length() const
Returns the planar, 2-dimensional length of the geometry.
Abstract base class for storage of elevation profiles.
QgsAbstractProfileSurfaceGenerator(const QgsProfileRequest &request)
Constructor for QgsAbstractProfileSurfaceGenerator.
QVector< QgsProfileIdentifyResults > identify(const QgsProfilePoint &point, const QgsProfileIdentifyContext &context) override
Identify results visible at the specified profile point.
Handles coordinate transforms between two coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Abstract base class for curved geometry type.
Definition qgscurve.h:36
QgsCurve * clone() const override=0
Clones the geometry by performing a deep copy.
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition qgsrange.h:287
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
A geometry is the spatial representation of a feature.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
QgsGeometry densifyByDistance(double distance) const
Densifies the geometry by adding regularly placed extra nodes inside each segment so that the maximum...
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsAbstractGeometry::vertex_iterator vertices_begin() const
Returns STL-style iterator pointing to the first vertex of the geometry.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry, double precision=0.0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlag::SkipEmptyInteriorRings)
Creates and returns a new geometry engine representing the specified geometry using precision on a gr...
QgsAbstractGeometry::vertex_iterator vertices_end() const
Returns STL-style iterator pointing to the imaginary vertex after the last vertex of the geometry.
Does vector analysis using the GEOS library and handles import, export, and exception handling.
Definition qgsgeos.h:141
void prepareGeometry() override
Prepares the geometry, so that subsequent calls to spatial relation methods are much faster.
Definition qgsgeos.cpp:292
double lineLocatePoint(const QgsPoint &point, QString *errorMsg=nullptr) const
Returns a distance representing the location along this linestring of the closest point on this lines...
Definition qgsgeos.cpp:3185
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
void transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection d=Qgis::TransformDirection::Forward, bool transformZ=false) override
Transforms the geometry using a coordinate transform.
Definition qgspoint.cpp:387
Encapsulates the context in which an elevation profile is to be generated.
double maximumErrorMapUnits() const
Returns the maximum allowed error in the generated result, in profile curve map units.
double mapUnitsPerDistancePixel() const
Returns the number of map units per pixel in the distance dimension.
QgsDoubleRange distanceRange() const
Returns the range of distances to include in the generation.
Encapsulates the context of identifying profile results.
Stores identify results generated by a QgsAbstractProfileResults object.
Encapsulates a point on a distance-elevation profile.
Encapsulates properties and constraints relating to fetching elevation profiles from different source...
T lower() const
Returns the lower bound of the range.
Definition qgsrange.h:78
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:85
Feedback object tailored for raster block reading.
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
Iterator for sequentially processing raster cells.
static QgsRectangle subRegion(const QgsRectangle &rasterExtent, int rasterWidth, int rasterHeight, const QgsRectangle &subRegion, int &subRegionWidth, int &subRegionHeight, int &subRegionLeft, int &subRegionTop, int resamplingFactor=1)
Given an overall raster extent and width and height in pixels, calculates the sub region of the raste...
bool next(int bandNumber, int &columns, int &rows, int &topLeftColumn, int &topLeftRow, QgsRectangle &blockExtent)
Fetches details of the next part of the raster data.
void setMaximumTileWidth(int w)
Sets the maximum tile width returned during iteration.
void startRasterRead(int bandNumber, qgssize nCols, qgssize nRows, const QgsRectangle &extent, QgsRasterBlockFeedback *feedback=nullptr)
Start reading of raster band.
void setMaximumTileHeight(int h)
Sets the minimum tile height returned during iteration.
Raster layer specific subclass of QgsMapLayerElevationProperties.
QgsAbstractProfileResults * takeResults() override
Takes results from the generator.
QgsRasterLayerProfileGenerator(QgsRasterLayer *layer, const QgsProfileRequest &request)
Constructor for QgsRasterLayerProfileGenerator.
QgsFeedback * feedback() const override
Access to feedback object of the generator (may be nullptr).
bool generateProfile(const QgsProfileGenerationContext &context=QgsProfileGenerationContext()) override
Generate the profile (based on data stored in the class).
Qgis::ProfileGeneratorFlags flags() const override
Returns flags which reflect how the profile generator operates.
QString sourceId() const override
Returns a unique identifier representing the source of the profile.
~QgsRasterLayerProfileGenerator() override
QVector< QgsProfileIdentifyResults > identify(const QgsProfilePoint &point, const QgsProfileIdentifyContext &context) override
Identify results visible at the specified profile point.
QString type() const override
Returns the unique string identifier for the results type.
Represents a raster layer.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
QgsMapLayerElevationProperties * elevationProperties() override
Returns the layer's elevation properties.
A rectangle specified with double values.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
double xMinimum
double yMinimum
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
double yMaximum
Temporarily moves a QObject to the current thread, then resets it back to nullptr thread on destructi...
#define QgsDebugError(str)
Definition qgslogger.h:57