QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
qgskde.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgskde.cpp
3 ----------
4 Date : October 2016
5 Copyright : (C) 2016 by Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgskde.h"
17
18#include "qgsfeatureiterator.h"
19#include "qgsfeaturesource.h"
20#include "qgsgeometry.h"
21
22#define NO_DATA -9999
23
24QgsKernelDensityEstimation::QgsKernelDensityEstimation( const QgsKernelDensityEstimation::Parameters &parameters, const QString &outputFile, const QString &outputFormat )
25 : mSource( parameters.source )
26 , mOutputFile( outputFile )
27 , mOutputFormat( outputFormat )
28 , mRadius( parameters.radius )
29 , mPixelSize( parameters.pixelSize )
30 , mShape( parameters.shape )
31 , mDecay( parameters.decayRatio )
32 , mOutputValues( parameters.outputValues )
33 , mDatasetH( nullptr )
34{
35 if ( !parameters.radiusField.isEmpty() )
36 mRadiusField = mSource->fields().lookupField( parameters.radiusField );
37 if ( !parameters.weightField.isEmpty() )
38 mWeightField = mSource->fields().lookupField( parameters.weightField );
39}
40
42{
43 const Result result = prepare();
45 return result;
46
47 QgsAttributeList requiredAttributes;
48
49 if ( mRadiusField >= 0 )
50 requiredAttributes << mRadiusField;
51
52 if ( mWeightField >= 0 )
53 requiredAttributes << mWeightField;
54
55 QgsFeatureIterator fit = mSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( requiredAttributes ) );
56
57 QgsFeature f;
58 while ( fit.nextFeature( f ) )
59 {
60 addFeature( f );
61 }
62
63 return finalise();
64}
65
67{
68 GDALAllRegister();
69
70 GDALDriverH driver = GDALGetDriverByName( mOutputFormat.toUtf8() );
71 if ( !driver )
72 {
74 }
75
76 if ( !mSource )
78
79 mBounds = calculateBounds();
80 if ( mBounds.isNull() )
82
83 const int rows = std::max( std::ceil( mBounds.height() / mPixelSize ) + 1, 1.0 );
84 const int cols = std::max( std::ceil( mBounds.width() / mPixelSize ) + 1, 1.0 );
85
86 if ( !createEmptyLayer( driver, mBounds, rows, cols ) )
88
89 // open the raster in GA_Update mode
90 mDatasetH.reset( GDALOpen( mOutputFile.toUtf8().constData(), GA_Update ) );
91 if ( !mDatasetH )
93 mRasterBandH = GDALGetRasterBand( mDatasetH.get(), 1 );
94 if ( !mRasterBandH )
96
97 mBufferSize = -1;
98 if ( mRadiusField < 0 )
99 mBufferSize = radiusSizeInPixels( mRadius );
100
102}
103
105{
106 const QgsGeometry featureGeometry = feature.geometry();
107 if ( featureGeometry.isNull() )
108 {
110 }
111
112 // convert the geometry to multipoint
113 QgsMultiPointXY multiPoints;
114 if ( !featureGeometry.isMultipart() )
115 {
116 const QgsPointXY p = featureGeometry.asPoint();
117 // avoiding any empty points or out of extent points
118 if ( !mBounds.contains( p ) )
120
121 multiPoints << p;
122 }
123 else
124 {
125 multiPoints = featureGeometry.asMultiPoint();
126 }
127
128 // if radius is variable then fetch it and calculate new pixel buffer size
129 double radius = mRadius;
130 int buffer = mBufferSize;
131 if ( mRadiusField >= 0 )
132 {
133 radius = feature.attribute( mRadiusField ).toDouble();
134 buffer = radiusSizeInPixels( radius );
135 }
136 const int blockSize = 2 * buffer + 1; //Block SIDE would be more appropriate
137
138 // calculate weight
139 double weight = 1.0;
140 if ( mWeightField >= 0 )
141 {
142 weight = feature.attribute( mWeightField ).toDouble();
143 }
144
146
147 //loop through all points in multipoint
148 for ( QgsMultiPointXY::const_iterator pointIt = multiPoints.constBegin(); pointIt != multiPoints.constEnd(); ++pointIt )
149 {
150 // avoiding any empty points or out of extent points
151 if ( !mBounds.contains( *pointIt ) )
152 {
153 continue;
154 }
155
156 // calculate the pixel position
157 const unsigned int xPosition = ( ( ( *pointIt ).x() - mBounds.xMinimum() ) / mPixelSize ) - buffer;
158 const unsigned int yPosition = ( ( ( *pointIt ).y() - mBounds.yMinimum() ) / mPixelSize ) - buffer;
159 const unsigned int yPositionIO = ( ( mBounds.yMaximum() - ( *pointIt ).y() ) / mPixelSize ) - buffer;
160
161
162 // get the data
163 float *dataBuffer = ( float * ) CPLMalloc( sizeof( float ) * blockSize * blockSize );
164 if ( GDALRasterIO( mRasterBandH, GF_Read, xPosition, yPositionIO, blockSize, blockSize, dataBuffer, blockSize, blockSize, GDT_Float32, 0, 0 ) != CE_None )
165 {
167 }
168
169 for ( int xp = 0; xp < blockSize; xp++ )
170 {
171 for ( int yp = 0; yp < blockSize; yp++ )
172 {
173 const double pixelCentroidX = ( xPosition + xp + 0.5 ) * mPixelSize + mBounds.xMinimum();
174 const double pixelCentroidY = ( yPosition + yp + 0.5 ) * mPixelSize + mBounds.yMinimum();
175
176 const double distance = std::sqrt( std::pow( pixelCentroidX - ( *pointIt ).x(), 2.0 ) + std::pow( pixelCentroidY - ( *pointIt ).y(), 2.0 ) );
177
178 // is pixel outside search bandwidth of feature?
179 if ( distance > radius )
180 {
181 continue;
182 }
183
184 const double pixelValue = weight * calculateKernelValue( distance, radius, mShape, mOutputValues );
185 const int pos = xp + blockSize * yp;
186 if ( dataBuffer[pos] == NO_DATA )
187 {
188 dataBuffer[pos] = 0;
189 }
190 dataBuffer[pos] += pixelValue;
191 }
192 }
193 if ( GDALRasterIO( mRasterBandH, GF_Write, xPosition, yPositionIO, blockSize, blockSize, dataBuffer, blockSize, blockSize, GDT_Float32, 0, 0 ) != CE_None )
194 {
196 }
197 CPLFree( dataBuffer );
198 }
199
200 return result;
201}
202
204{
205 mDatasetH.reset();
206 mRasterBandH = nullptr;
208}
209
210int QgsKernelDensityEstimation::radiusSizeInPixels( double radius ) const
211{
212 int buffer = radius / mPixelSize;
213 if ( radius - ( mPixelSize * buffer ) > 0.5 )
214 {
215 ++buffer;
216 }
217 return buffer;
218}
219
220bool QgsKernelDensityEstimation::createEmptyLayer( GDALDriverH driver, const QgsRectangle &bounds, int rows, int columns ) const
221{
222 double geoTransform[6] = { bounds.xMinimum(), mPixelSize, 0, bounds.yMaximum(), 0, -mPixelSize };
223 const gdal::dataset_unique_ptr emptyDataset( GDALCreate( driver, mOutputFile.toUtf8(), columns, rows, 1, GDT_Float32, nullptr ) );
224 if ( !emptyDataset )
225 return false;
226
227 if ( GDALSetGeoTransform( emptyDataset.get(), geoTransform ) != CE_None )
228 return false;
229
230 // Set the projection on the raster destination to match the input layer
231 if ( GDALSetProjection( emptyDataset.get(), mSource->sourceCrs().toWkt().toLocal8Bit().data() ) != CE_None )
232 return false;
233
234 GDALRasterBandH poBand = GDALGetRasterBand( emptyDataset.get(), 1 );
235 if ( !poBand )
236 return false;
237
238 if ( GDALSetRasterNoDataValue( poBand, NO_DATA ) != CE_None )
239 return false;
240
241 float *line = static_cast<float *>( CPLMalloc( sizeof( float ) * columns ) );
242 for ( int i = 0; i < columns; i++ )
243 {
244 line[i] = NO_DATA;
245 }
246 // Write the empty raster
247 for ( int i = 0; i < rows; i++ )
248 {
249 if ( GDALRasterIO( poBand, GF_Write, 0, i, columns, 1, line, columns, 1, GDT_Float32, 0, 0 ) != CE_None )
250 {
251 return false;
252 }
253 }
254
255 CPLFree( line );
256 return true;
257}
258
259double QgsKernelDensityEstimation::calculateKernelValue( const double distance, const double bandwidth, const QgsKernelDensityEstimation::KernelShape shape, const QgsKernelDensityEstimation::OutputValues outputType ) const
260{
261 switch ( shape )
262 {
264 return triangularKernel( distance, bandwidth, outputType );
265
267 return uniformKernel( distance, bandwidth, outputType );
268
270 return quarticKernel( distance, bandwidth, outputType );
271
273 return triweightKernel( distance, bandwidth, outputType );
274
276 return epanechnikovKernel( distance, bandwidth, outputType );
277 }
278 return 0; //no warnings
279}
280
281/* The kernel functions below are taken from "Kernel Smoothing" by Wand and Jones (1995), p. 175
282 *
283 * Each kernel is multiplied by a normalizing constant "k", which normalizes the kernel area
284 * to 1 for a given bandwidth size.
285 *
286 * k is calculated by polar double integration of the kernel function
287 * between a radius of 0 to the specified bandwidth and equating the area to 1. */
288
289double QgsKernelDensityEstimation::uniformKernel( const double distance, const double bandwidth, const QgsKernelDensityEstimation::OutputValues outputType ) const
290{
291 Q_UNUSED( distance )
292 switch ( outputType )
293 {
295 {
296 // Normalizing constant
297 const double k = 2. / ( M_PI * bandwidth );
298
299 // Derived from Wand and Jones (1995), p. 175
300 return k * ( 0.5 / bandwidth );
301 }
303 return 1.0;
304 }
305 return 0.0; // NO warnings!!!!!
306}
307
308double QgsKernelDensityEstimation::quarticKernel( const double distance, const double bandwidth, const QgsKernelDensityEstimation::OutputValues outputType ) const
309{
310 switch ( outputType )
311 {
313 {
314 // Normalizing constant
315 const double k = 16. / ( 5. * M_PI * std::pow( bandwidth, 2 ) );
316
317 // Derived from Wand and Jones (1995), p. 175
318 return k * ( 15. / 16. ) * std::pow( 1. - std::pow( distance / bandwidth, 2 ), 2 );
319 }
321 return std::pow( 1. - std::pow( distance / bandwidth, 2 ), 2 );
322 }
323 return 0.0; //no, seriously, I told you NO WARNINGS!
324}
325
326double QgsKernelDensityEstimation::triweightKernel( const double distance, const double bandwidth, const QgsKernelDensityEstimation::OutputValues outputType ) const
327{
328 switch ( outputType )
329 {
331 {
332 // Normalizing constant
333 const double k = 128. / ( 35. * M_PI * std::pow( bandwidth, 2 ) );
334
335 // Derived from Wand and Jones (1995), p. 175
336 return k * ( 35. / 32. ) * std::pow( 1. - std::pow( distance / bandwidth, 2 ), 3 );
337 }
339 return std::pow( 1. - std::pow( distance / bandwidth, 2 ), 3 );
340 }
341 return 0.0; // this is getting ridiculous... don't you ever listen to a word I say?
342}
343
344double QgsKernelDensityEstimation::epanechnikovKernel( const double distance, const double bandwidth, const QgsKernelDensityEstimation::OutputValues outputType ) const
345{
346 switch ( outputType )
347 {
349 {
350 // Normalizing constant
351 const double k = 8. / ( 3. * M_PI * std::pow( bandwidth, 2 ) );
352
353 // Derived from Wand and Jones (1995), p. 175
354 return k * ( 3. / 4. ) * ( 1. - std::pow( distance / bandwidth, 2 ) );
355 }
357 return ( 1. - std::pow( distance / bandwidth, 2 ) );
358 }
359
360 return 0.0; // la la la i'm not listening
361}
362
363double QgsKernelDensityEstimation::triangularKernel( const double distance, const double bandwidth, const QgsKernelDensityEstimation::OutputValues outputType ) const
364{
365 switch ( outputType )
366 {
368 {
369 // Normalizing constant. In this case it's calculated a little different
370 // due to the inclusion of the non-standard "decay" parameter
371
372 if ( mDecay >= 0 )
373 {
374 const double k = 3. / ( ( 1. + 2. * mDecay ) * M_PI * std::pow( bandwidth, 2 ) );
375
376 // Derived from Wand and Jones (1995), p. 175 (with addition of decay parameter)
377 return k * ( 1. - ( 1. - mDecay ) * ( distance / bandwidth ) );
378 }
379 else
380 {
381 // Non-standard or mathematically valid negative decay ("coolmap")
382 return ( 1. - ( 1. - mDecay ) * ( distance / bandwidth ) );
383 }
384 }
386 return ( 1. - ( 1. - mDecay ) * ( distance / bandwidth ) );
387 }
388 return 0.0; // ....
389}
390
391QgsRectangle QgsKernelDensityEstimation::calculateBounds() const
392{
393 if ( !mSource )
394 return QgsRectangle();
395
396 QgsRectangle bbox = mSource->sourceExtent();
397
398 double radius = 0;
399 if ( mRadiusField >= 0 )
400 {
401 // if radius is using a field, find the max value
402 radius = mSource->maximumValue( mRadiusField ).toDouble();
403 }
404 else
405 {
406 radius = mRadius;
407 }
408 // expand the bounds by the maximum search radius
409 bbox.setXMinimum( bbox.xMinimum() - radius );
410 bbox.setYMinimum( bbox.yMinimum() - radius );
411 bbox.setXMaximum( bbox.xMaximum() + radius );
412 bbox.setYMaximum( bbox.yMaximum() + radius );
413 return bbox;
414}
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsGeometry geometry
Definition qgsfeature.h:69
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
A geometry is the spatial representation of a feature.
QgsMultiPointXY asMultiPoint() const
Returns the contents of the geometry as a multi-point.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
QgsKernelDensityEstimation(const Parameters &parameters, const QString &outputFile, const QString &outputFormat)
Constructor for QgsKernelDensityEstimation.
Definition qgskde.cpp:24
Result run()
Runs the KDE calculation across the whole layer at once.
Definition qgskde.cpp:41
Result addFeature(const QgsFeature &feature)
Adds a single feature to the KDE surface.
Definition qgskde.cpp:104
Result finalise()
Finalises the output file.
Definition qgskde.cpp:203
KernelShape
Kernel shape type.
Definition qgskde.h:44
@ Epanechnikov
Epanechnikov kernel.
Definition qgskde.h:49
@ Uniform
Uniform (flat) kernel.
Definition qgskde.h:47
OutputValues
Output values type.
Definition qgskde.h:54
@ Raw
Output the raw KDE values.
Definition qgskde.h:55
@ Scaled
Output mathematically correct scaled values.
Definition qgskde.h:56
Result prepare()
Prepares the output file for writing and setups up the surface calculation.
Definition qgskde.cpp:66
Result
Result of operation.
Definition qgskde.h:61
@ InvalidParameters
Input parameters were not valid.
Definition qgskde.h:64
@ Success
Operation completed successfully.
Definition qgskde.h:62
@ RasterIoError
Error writing to raster.
Definition qgskde.h:66
@ FileCreationError
Error creating output file.
Definition qgskde.h:65
@ DriverError
Could not open the driver for the specified format.
Definition qgskde.h:63
Represents a 2D point.
Definition qgspointxy.h:60
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
void setYMinimum(double y)
Set the minimum y value.
void setXMinimum(double x)
Set the minimum x value.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
double yMaximum
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
QList< int > QgsAttributeList
Definition qgsfield.h:28
QVector< QgsPointXY > QgsMultiPointXY
A collection of QgsPoints that share a common collection of attributes.
Definition qgsgeometry.h:96
#define NO_DATA
Definition qgskde.cpp:22
QString radiusField
Field for radius, or empty if using a fixed radius.
Definition qgskde.h:79
QString weightField
Field name for weighting field, or empty if not using weights.
Definition qgskde.h:82