QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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 <QString>
33#include <QThread>
34
35using namespace Qt::StringLiterals;
36
37//
38// QgsRasterLayerProfileResults
39//
40
42{
43 return u"raster"_s;
44}
45
46QVector<QgsProfileIdentifyResults> QgsRasterLayerProfileResults::identify( const QgsProfilePoint &point, const QgsProfileIdentifyContext &context )
47{
48 const QVector<QgsProfileIdentifyResults> noLayerResults = QgsAbstractProfileSurfaceResults::identify( point, context );
49
50 // we have to make a new list, with the correct layer reference set
51 QVector<QgsProfileIdentifyResults> res;
52 res.reserve( noLayerResults.size() );
53 for ( const QgsProfileIdentifyResults &result : noLayerResults )
54 {
55 res.append( QgsProfileIdentifyResults( mLayer, result.results() ) );
56 }
57 return res;
58}
59
60
61//
62// QgsRasterLayerProfileGenerator
63//
64
67 , mId( layer->id() )
68 , mFeedback( std::make_unique< QgsRasterBlockFeedback >() )
69 , mProfileCurve( request.profileCurve() ? request.profileCurve()->clone() : nullptr )
70 , mSourceCrs( layer->crs() )
71 , mTargetCrs( request.crs() )
72 , mTransformContext( request.transformContext() )
73 , mOffset( layer->elevationProperties()->zOffset() )
74 , mScale( layer->elevationProperties()->zScale() )
75 , mLayer( layer )
76 , mBand( qgis::down_cast< QgsRasterLayerElevationProperties * >( layer->elevationProperties() )->bandNumber() )
77 , mRasterUnitsPerPixelX( layer->rasterUnitsPerPixelX() )
78 , mRasterUnitsPerPixelY( layer->rasterUnitsPerPixelY() )
79 , mStepDistance( request.stepDistance() )
80{
81 mRasterProvider.reset( layer->dataProvider()->clone() );
82 mRasterProvider->moveToThread( nullptr );
83
84 mSymbology = qgis::down_cast< QgsRasterLayerElevationProperties * >( layer->elevationProperties() )->profileSymbology();
85 mElevationLimit = qgis::down_cast< QgsRasterLayerElevationProperties * >( layer->elevationProperties() )->elevationLimit();
86 mLineSymbol.reset( qgis::down_cast< QgsRasterLayerElevationProperties * >( layer->elevationProperties() )->profileLineSymbol()->clone() );
87 mFillSymbol.reset( qgis::down_cast< QgsRasterLayerElevationProperties * >( layer->elevationProperties() )->profileFillSymbol()->clone() );
88}
89
91{
92 return mId;
93}
94
96{
97 return u"raster"_s;
98}
99
104
106
108{
109 if ( !mProfileCurve || mFeedback->isCanceled() )
110 return false;
111
112 QgsScopedAssignObjectToCurrentThread assignProviderToCurrentThread( mRasterProvider.get() );
113
114 const double startDistanceOffset = std::max( !context.distanceRange().isInfinite() ? context.distanceRange().lower() : 0, 0.0 );
115 const double endDistance = context.distanceRange().upper();
116
117 std::unique_ptr< QgsCurve > trimmedCurve;
118 QgsCurve *sourceCurve = nullptr;
119 if ( startDistanceOffset > 0 || endDistance < mProfileCurve->length() )
120 {
121 trimmedCurve.reset( mProfileCurve->curveSubstring( startDistanceOffset, endDistance ) );
122 sourceCurve = trimmedCurve.get();
123 }
124 else
125 {
126 sourceCurve = mProfileCurve.get();
127 }
128
129 // we need to transform the profile curve to the raster's CRS
130 std::unique_ptr< QgsCurve > transformedCurve( sourceCurve->clone() );
131 const QgsCoordinateTransform rasterToTargetTransform( mSourceCrs, mTargetCrs, mTransformContext );
132 try
133 {
134 transformedCurve->transform( rasterToTargetTransform, Qgis::TransformDirection::Reverse );
135 }
136 catch ( QgsCsException & )
137 {
138 QgsDebugError( u"Error transforming profile line to raster CRS"_s );
139 return false;
140 }
141
142 if ( mFeedback->isCanceled() )
143 return false;
144
145 const QgsRectangle profileCurveBoundingBox = transformedCurve->boundingBox();
146 if ( !profileCurveBoundingBox.intersects( mRasterProvider->extent() ) )
147 return false;
148
149 if ( mFeedback->isCanceled() )
150 return false;
151
152 mResults = std::make_unique< QgsRasterLayerProfileResults >();
153 mResults->mLayer = mLayer;
154 mResults->mId = mId;
155 mResults->copyPropertiesFromGenerator( this );
156
157 std::unique_ptr< QgsGeometryEngine > curveEngine( QgsGeometry::createGeometryEngine( transformedCurve.get() ) );
158 curveEngine->prepareGeometry();
159
160 if ( mFeedback->isCanceled() )
161 return false;
162
163 double stepDistance = mStepDistance;
164 if ( !std::isnan( context.maximumErrorMapUnits() ) )
165 {
166 // convert the maximum error in curve units to a step distance
167 // TODO -- there's no point in this being << pixel size!
168 if ( std::isnan( stepDistance ) || context.maximumErrorMapUnits() > stepDistance )
169 {
170 stepDistance = context.maximumErrorMapUnits();
171 }
172 }
173
174 QSet< QgsPointXY > profilePoints;
175 if ( !std::isnan( stepDistance ) )
176 {
177 // if specific step distance specified, use this to generate points along the curve
178 QgsGeometry densifiedCurve( sourceCurve->clone() );
179 densifiedCurve = densifiedCurve.densifyByDistance( stepDistance );
180 densifiedCurve.transform( rasterToTargetTransform, Qgis::TransformDirection::Reverse );
181 profilePoints.reserve( densifiedCurve.constGet()->nCoordinates() );
182 for ( auto it = densifiedCurve.vertices_begin(); it != densifiedCurve.vertices_end(); ++it )
183 {
184 profilePoints.insert( *it );
185 }
186 }
187
188 if ( mFeedback->isCanceled() )
189 return false;
190
191 // calculate the portion of the raster which actually covers the curve
192 int subRegionWidth = 0;
193 int subRegionHeight = 0;
194 int subRegionLeft = 0;
195 int subRegionTop = 0;
196 QgsRectangle rasterSubRegion
197 = mRasterProvider->xSize() > 0 && mRasterProvider->ySize() > 0
198 ? QgsRasterIterator::subRegion( mRasterProvider->extent(), mRasterProvider->xSize(), mRasterProvider->ySize(), transformedCurve->boundingBox(), subRegionWidth, subRegionHeight, subRegionLeft, subRegionTop )
199 : transformedCurve->boundingBox();
200
201 const bool zeroXYSize = mRasterProvider->xSize() == 0 || mRasterProvider->ySize() == 0;
202 if ( zeroXYSize )
203 {
204 const double curveLengthInPixels = sourceCurve->length() / context.mapUnitsPerDistancePixel();
205 const double conversionFactor = curveLengthInPixels / transformedCurve->length();
206 subRegionWidth = rasterSubRegion.width() * conversionFactor;
207 subRegionHeight = rasterSubRegion.height() * conversionFactor;
208
209 // ensure we fetch at least 1 pixel wide/high blocks, otherwise exactly vertical/horizontal profile lines will result in zero size blocks
210 // see https://github.com/qgis/QGIS/issues/51196
211 if ( subRegionWidth == 0 )
212 {
213 subRegionWidth = 1;
214 rasterSubRegion.setXMaximum( rasterSubRegion.xMinimum() + 1 / conversionFactor );
215 }
216 if ( subRegionHeight == 0 )
217 {
218 subRegionHeight = 1;
219 rasterSubRegion.setYMaximum( rasterSubRegion.yMinimum() + 1 / conversionFactor );
220 }
221 }
222
223 // iterate over the raster blocks, throwing away any which don't intersect the profile curve
224 QgsRasterIterator it( mRasterProvider.get() );
225 // we use smaller tile sizes vs the default, as we will be skipping over tiles which don't intersect the curve at all,
226 // and we expect that to be the VAST majority of the tiles in the raster.
227 // => Smaller tile sizes = more regions we can shortcut over = less pixels to iterate over = faster runtime
228 it.setMaximumTileHeight( 64 );
229 it.setMaximumTileWidth( 64 );
230
231 it.startRasterRead( mBand, subRegionWidth, subRegionHeight, rasterSubRegion );
232
233 const double halfPixelSizeX = mRasterUnitsPerPixelX / 2.0;
234 const double halfPixelSizeY = mRasterUnitsPerPixelY / 2.0;
235 int blockColumns = 0;
236 int blockRows = 0;
237 int blockTopLeftColumn = 0;
238 int blockTopLeftRow = 0;
239 QgsRectangle blockExtent;
240
241 while ( it.next( mBand, blockColumns, blockRows, blockTopLeftColumn, blockTopLeftRow, blockExtent ) )
242 {
243 if ( mFeedback->isCanceled() )
244 return false;
245
246 const QgsGeometry blockExtentGeom = QgsGeometry::fromRect( blockExtent );
247 if ( !curveEngine->intersects( blockExtentGeom.constGet() ) )
248 continue;
249
250 std::unique_ptr< QgsRasterBlock > block( mRasterProvider->block( mBand, blockExtent, blockColumns, blockRows, mFeedback.get() ) );
251 if ( mFeedback->isCanceled() )
252 return false;
253
254 if ( !block )
255 continue;
256
257 bool isNoData = false;
258
259 // 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
260 // sample at specific points
261 if ( !std::isnan( stepDistance ) )
262 {
263 auto it = profilePoints.begin();
264 while ( it != profilePoints.end() )
265 {
266 if ( mFeedback->isCanceled() )
267 return false;
268
269 // convert point to a pixel and sample, if it's in this block
270 if ( blockExtent.contains( *it ) )
271 {
272 int row, col;
273 if ( zeroXYSize )
274 {
275 row = std::clamp( static_cast< int >( std::round( ( blockExtent.yMaximum() - it->y() ) * blockRows / blockExtent.height() ) ), 0, blockRows - 1 );
276 col = std::clamp( static_cast< int >( std::round( ( it->x() - blockExtent.xMinimum() ) * blockColumns / blockExtent.width() ) ), 0, blockColumns - 1 );
277 }
278 else
279 {
280 row = std::clamp( static_cast< int >( std::round( ( blockExtent.yMaximum() - it->y() ) / mRasterUnitsPerPixelY ) ), 0, blockRows - 1 );
281 col = std::clamp( static_cast< int >( std::round( ( it->x() - blockExtent.xMinimum() ) / mRasterUnitsPerPixelX ) ), 0, blockColumns - 1 );
282 }
283 double val = block->valueAndNoData( row, col, isNoData );
284 if ( !isNoData )
285 {
286 val = val * mScale + mOffset;
287 }
288 else
289 {
290 val = std::numeric_limits<double>::quiet_NaN();
291 }
292
293 QgsPoint pixel( it->x(), it->y(), val );
294 try
295 {
296 pixel.transform( rasterToTargetTransform );
297 }
298 catch ( QgsCsException & )
299 {
300 continue;
301 }
302 mResults->mRawPoints.append( pixel );
303
304 it = profilePoints.erase( it );
305 }
306 else
307 {
308 it++;
309 }
310 }
311
312 if ( profilePoints.isEmpty() )
313 break; // all done!
314 }
315 else
316 {
317 double currentY = blockExtent.yMaximum() - 0.5 * mRasterUnitsPerPixelY;
318 for ( int row = 0; row < blockRows; ++row )
319 {
320 if ( mFeedback->isCanceled() )
321 return false;
322
323 double currentX = blockExtent.xMinimum() + 0.5 * mRasterUnitsPerPixelX;
324 for ( int col = 0; col < blockColumns; ++col, currentX += mRasterUnitsPerPixelX )
325 {
326 const double val = block->valueAndNoData( row, col, isNoData );
327
328 // does pixel intersect curve?
329 QgsGeometry pixelRectGeometry = QgsGeometry::fromRect( QgsRectangle( currentX - halfPixelSizeX, currentY - halfPixelSizeY, currentX + halfPixelSizeX, currentY + halfPixelSizeY ) );
330 if ( !curveEngine->intersects( pixelRectGeometry.constGet() ) )
331 continue;
332
333 QgsPoint pixel( currentX, currentY, isNoData ? std::numeric_limits<double>::quiet_NaN() : val * mScale + mOffset );
334 try
335 {
336 pixel.transform( rasterToTargetTransform );
337 }
338 catch ( QgsCsException & )
339 {
340 continue;
341 }
342 mResults->mRawPoints.append( pixel );
343 }
344 currentY -= mRasterUnitsPerPixelY;
345 }
346 }
347 }
348
349 if ( mFeedback->isCanceled() )
350 return false;
351
352 // convert x/y values back to distance/height values
353 QgsGeos originalCurveGeos( sourceCurve );
354 originalCurveGeos.prepareGeometry();
355 QString lastError;
356 for ( const QgsPoint &pixel : std::as_const( mResults->mRawPoints ) )
357 {
358 if ( mFeedback->isCanceled() )
359 return false;
360
361 const double distance = originalCurveGeos.lineLocatePoint( pixel, &lastError ) + startDistanceOffset;
362
363 if ( !std::isnan( pixel.z() ) )
364 {
365 mResults->minZ = std::min( pixel.z(), mResults->minZ );
366 mResults->maxZ = std::max( pixel.z(), mResults->maxZ );
367 }
368 mResults->mDistanceToHeightMap.insert( distance, pixel.z() );
369 }
370
371 return true;
372}
373
378
380{
381 return mFeedback.get();
382}
@ RespectsMaximumErrorMapUnit
Generated profile respects the QgsProfileGenerationContext::maximumErrorMapUnits() property.
Definition qgis.h:4360
@ RespectsDistanceRange
Generated profile respects the QgsProfileGenerationContext::distanceRange() property.
Definition qgis.h:4361
QFlags< ProfileGeneratorFlag > ProfileGeneratorFlags
Definition qgis.h:4365
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2766
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:266
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:139
void prepareGeometry() override
Prepares the geometry, so that subsequent calls to spatial relation methods are much faster.
Definition qgsgeos.cpp:286
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:3179
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
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:414
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:79
T upper() const
Returns the upper bound of the range.
Definition qgsrange.h:86
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.
QString type() const override
Returns the unique string identifier for the results type.
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:59