Skip to content
Permalink
Browse files

Merge pull request #4852 from nyalldawson/heatmap_source

Use QgsFeatureSource instead of QgsVectorLayer for QgsKde
  • Loading branch information
nyalldawson committed Jul 14, 2017
2 parents 0d9945d + 4fa6964 commit 0639264a4aebdd3c6eb555d4787cc17ed01b09d1
@@ -49,9 +49,9 @@ class QgsKernelDensityEstimation

struct Parameters
{
QgsVectorLayer *vectorLayer;
QgsFeatureSource *source;
%Docstring
Vector point layer
Point feature source
%End

double radius;
@@ -79,6 +79,20 @@ class QgsProcessingUtils
:rtype: QgsMapLayer
%End

static QgsProcessingFeatureSource *variantToSource( const QVariant &value, QgsProcessingContext &context, const QVariant &fallbackValue = QVariant() ) /Factory/;
%Docstring
Converts a variant ``value`` to a new feature source.

Sources will either be taken from ``context``'s active project, or loaded from external
sources and stored temporarily in the ``context``.

The optional ``fallbackValue`` can be used to specify a "default" value which is used
if ``value`` cannot be successfully converted to a source.

This function creates a new object and the caller takes responsibility for deleting the returned object.
:rtype: QgsProcessingFeatureSource
%End

static QString normalizeLayerSource( const QString &source );
%Docstring
Normalizes a layer ``source`` string for safe comparison across different
@@ -232,6 +246,12 @@ class QgsProcessingFeatureSource : QgsFeatureSource

virtual QString sourceName() const;

virtual QSet<QVariant> uniqueValues( int fieldIndex, int limit = -1 ) const;

virtual QVariant minimumValue( int fieldIndex ) const;

virtual QVariant maximumValue( int fieldIndex ) const;


};

@@ -81,9 +81,31 @@ class QgsFeatureSource
If specified, the ``limit`` option can be used to limit the number of returned values.
The base class implementation uses a non-optimised approach of looping through
all features in the source.
.. seealso:: minimumValue()
.. seealso:: maximumValue()
:rtype: set of QVariant
%End

virtual QVariant minimumValue( int fieldIndex ) const;
%Docstring
Returns the minimum value for an attribute column or an invalid variant in case of error.
The base class implementation uses a non-optimised approach of looping through
all features in the source.
.. seealso:: maximumValue()
.. seealso:: uniqueValues()
:rtype: QVariant
%End

virtual QVariant maximumValue( int fieldIndex ) const;
%Docstring
Returns the maximum value for an attribute column or an invalid variant in case of error.
The base class implementation uses a non-optimised approach of looping through
all features in the source.
.. seealso:: minimumValue()
.. seealso:: uniqueValues()
:rtype: QVariant
%End

virtual QgsRectangle sourceExtent() const;
%Docstring
Returns the extent of all geometries from the source.
@@ -1537,7 +1537,8 @@ Assembles mUpdatedFields considering provider fields, joined fields and added fi
:rtype: list of str
%End

QVariant minimumValue( int index ) const;
virtual QVariant minimumValue( int index ) const;

%Docstring
Returns the minimum value for an attribute column or an invalid variant in case of error.
Note that in some circumstances when unsaved changes are present for the layer then the
@@ -1548,7 +1549,8 @@ Assembles mUpdatedFields considering provider fields, joined fields and added fi
:rtype: QVariant
%End

QVariant maximumValue( int index ) const;
virtual QVariant maximumValue( int index ) const;

%Docstring
Returns the maximum value for an attribute column or an invalid variant in case of error.
Note that in some circumstances when unsaved changes are present for the layer then the
@@ -33,7 +33,7 @@
from qgis.core import (QgsFeatureRequest,
QgsProcessing,
QgsProcessingException,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterNumber,
QgsProcessingParameterField,
QgsProcessingParameterEnum,
@@ -88,9 +88,9 @@ def initAlgorithm(self, config=None):
self.OUTPUT_VALUES = OrderedDict([(self.tr('Raw'), QgsKernelDensityEstimation.OutputRaw),
(self.tr('Scaled'), QgsKernelDensityEstimation.OutputScaled)])

self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT_LAYER,
self.tr('Point layer'),
[QgsProcessing.TypeVectorPoint]))
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER,
self.tr('Point layer'),
[QgsProcessing.TypeVectorPoint]))

self.addParameter(QgsProcessingParameterNumber(self.RADIUS,
self.tr('Radius (layer units)'),
@@ -167,7 +167,7 @@ def __init__(self, name='', description='', parent_layer=None, radius_param=None
self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT_LAYER, self.tr('Heatmap')))

def processAlgorithm(self, parameters, context, feedback):
layer = self.parameterAsVectorLayer(parameters, self.INPUT_LAYER, context)
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)

radius = self.parameterAsDouble(parameters, self.RADIUS, context)
kernel_shape = self.parameterAsEnum(parameters, self.KERNEL, context)
@@ -182,17 +182,17 @@ def processAlgorithm(self, parameters, context, feedback):
attrs = []

kde_params = QgsKernelDensityEstimation.Parameters()
kde_params.vectorLayer = layer
kde_params.source = source
kde_params.radius = radius
kde_params.pixelSize = pixel_size
# radius field
if radius_field:
kde_params.radiusField = radius_field
attrs.append(layer.fields().lookupField(radius_field))
attrs.append(source.fields().lookupField(radius_field))
# weight field
if weight_field:
kde_params.weightField = weight_field
attrs.append(layer.fields().lookupField(weight_field))
attrs.append(source.fields().lookupField(weight_field))

kde_params.shape = kernel_shape
kde_params.decayRatio = decay
@@ -206,14 +206,14 @@ def processAlgorithm(self, parameters, context, feedback):

request = QgsFeatureRequest()
request.setSubsetOfAttributes(attrs)
features = layer.getFeatures(request)
total = 100.0 / layer.featureCount() if layer.featureCount() else 0
features = source.getFeatures(request)
total = 100.0 / source.featureCount() if source.featureCount() else 0
for current, f in enumerate(features):
if feedback.isCanceled():
break

if kde.addFeature(f) != QgsKernelDensityEstimation.Success:
feedback.reportError(self.tr('Error adding feature with ID {} to heatmap').format(f.id()), self.tr('Processing'), QgsMessageLog.CRITICAL)
feedback.reportError(self.tr('Error adding feature with ID {} to heatmap').format(f.id()))

feedback.setProgress(int(current * total))

@@ -42,7 +42,7 @@ def __init__(self):
self.setupUi(self)

self.layer_bounds = QgsRectangle()
self.layer = None
self.source = None
self.raster_bounds = QgsRectangle()
self.radius = 100
self.radius_field = None
@@ -65,28 +65,28 @@ def setRadiusField(self, radius_field):
self.radius_field = radius_field
self.recalculate_bounds()

def setLayer(self, layer):
if not layer:
def setSource(self, source):
if not source:
return
bounds = layer.extent()
bounds = source.sourceExtent()
if bounds.isNull():
return

self.layer = layer
self.source = source
self.layer_bounds = bounds
self.recalculate_bounds()

def recalculate_bounds(self):
self.raster_bounds = QgsRectangle(self.layer_bounds)

if not self.layer:
if not self.source:
return

max_radius = self.radius
if self.radius_field:
idx = self.layer.fields().lookupField(self.radius_field)
idx = self.source.fields().lookupField(self.radius_field)
try:
max_radius = float(self.layer.maximumValue(idx))
max_radius = float(self.source.maximumValue(idx))
except:
pass

@@ -157,6 +157,10 @@ def value(self):

class HeatmapPixelSizeWidgetWrapper(WidgetWrapper):

def __init__(self, param, dialog, row=0, col=0, **kwargs):
super().__init__(param, dialog, row, col, **kwargs)
self.context = dataobjects.createContext()

def _panel(self):
return HeatmapPixelSizeWidget()

@@ -177,24 +181,22 @@ def postInitialize(self, wrappers):
return

for wrapper in wrappers:
if wrapper.param.name == self.param.parent_layer:
self.setLayer(wrapper.value())
if wrapper.param.name() == self.param.parent_layer:
self.setSource(wrapper.value())
wrapper.widgetValueHasChanged.connect(self.parentLayerChanged)
elif wrapper.param.name == self.param.radius_param:
elif wrapper.param.name() == self.param.radius_param:
self.setRadius(wrapper.value())
wrapper.widgetValueHasChanged.connect(self.radiusChanged)
elif wrapper.param.name == self.param.radius_field_param:
self.setLayer(wrapper.value())
elif wrapper.param.name() == self.param.radius_field_param:
self.setSource(wrapper.value())
wrapper.widgetValueHasChanged.connect(self.radiusFieldChanged)

def parentLayerChanged(self, wrapper):
self.setLayer(wrapper.value())
self.setSource(wrapper.value())

def setLayer(self, layer):
context = dataobjects.createContext()
if isinstance(layer, str):
layer = QgsProcessingUtils.mapLayerFromString(layer, context)
self.widget.setLayer(layer)
def setSource(self, source):
source = QgsProcessingUtils.variantToSource(source, self.context)
self.widget.setSource(source)

def radiusChanged(self, wrapper):
self.setRadius(wrapper.value())
@@ -14,7 +14,8 @@
***************************************************************************/

#include "qgskde.h"
#include "qgsvectorlayer.h"
#include "qgsfeaturesource.h"
#include "qgsfeatureiterator.h"
#include "qgsgeometry.h"

#define NO_DATA -9999
@@ -24,7 +25,7 @@
#endif

QgsKernelDensityEstimation::QgsKernelDensityEstimation( const QgsKernelDensityEstimation::Parameters &parameters, const QString &outputFile, const QString &outputFormat )
: mInputLayer( parameters.vectorLayer )
: mSource( parameters.source )
, mOutputFile( outputFile )
, mOutputFormat( outputFormat )
, mRadiusField( -1 )
@@ -39,9 +40,9 @@ QgsKernelDensityEstimation::QgsKernelDensityEstimation( const QgsKernelDensityEs
, mRasterBandH( nullptr )
{
if ( !parameters.radiusField.isEmpty() )
mRadiusField = mInputLayer->fields().lookupField( parameters.radiusField );
mRadiusField = mSource->fields().lookupField( parameters.radiusField );
if ( !parameters.weightField.isEmpty() )
mWeightField = mInputLayer->fields().lookupField( parameters.weightField );
mWeightField = mSource->fields().lookupField( parameters.weightField );
}

QgsKernelDensityEstimation::Result QgsKernelDensityEstimation::run()
@@ -58,7 +59,7 @@ QgsKernelDensityEstimation::Result QgsKernelDensityEstimation::run()
if ( mWeightField >= 0 )
requiredAttributes << mWeightField;

QgsFeatureIterator fit = mInputLayer->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( requiredAttributes ) );
QgsFeatureIterator fit = mSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( requiredAttributes ) );

QgsFeature f;
while ( fit.nextFeature( f ) )
@@ -79,7 +80,7 @@ QgsKernelDensityEstimation::Result QgsKernelDensityEstimation::prepare()
return DriverError;
}

if ( !mInputLayer )
if ( !mSource )
return InvalidParameters;

mBounds = calculateBounds();
@@ -235,7 +236,7 @@ bool QgsKernelDensityEstimation::createEmptyLayer( GDALDriverH driver, const Qgs
return false;

// Set the projection on the raster destination to match the input layer
if ( GDALSetProjection( emptyDataset, mInputLayer->crs().toWkt().toLocal8Bit().data() ) != CE_None )
if ( GDALSetProjection( emptyDataset, mSource->sourceCrs().toWkt().toLocal8Bit().data() ) != CE_None )
return false;

GDALRasterBandH poBand = GDALGetRasterBand( emptyDataset, 1 );
@@ -399,16 +400,16 @@ double QgsKernelDensityEstimation::triangularKernel( const double distance, cons

QgsRectangle QgsKernelDensityEstimation::calculateBounds() const
{
if ( !mInputLayer )
if ( !mSource )
return QgsRectangle();

QgsRectangle bbox = mInputLayer->extent();
QgsRectangle bbox = mSource->sourceExtent();

double radius = 0;
if ( mRadiusField >= 0 )
{
// if radius is using a field, find the max value
radius = mInputLayer->maximumValue( mRadiusField ).toDouble();
radius = mSource->maximumValue( mRadiusField ).toDouble();
}
else
{
@@ -25,7 +25,7 @@
#include <cpl_conv.h>
#include "qgis_analysis.h"

class QgsVectorLayer;
class QgsFeatureSource;
class QProgressDialog;
class QgsFeature;

@@ -70,8 +70,8 @@ class ANALYSIS_EXPORT QgsKernelDensityEstimation
//! KDE parameters
struct Parameters
{
//! Vector point layer
QgsVectorLayer *vectorLayer = nullptr;
//! Point feature source
QgsFeatureSource *source = nullptr;

//! Fixed radius, in map units
double radius;
@@ -146,7 +146,7 @@ class ANALYSIS_EXPORT QgsKernelDensityEstimation

QgsRectangle calculateBounds() const;

QgsVectorLayer *mInputLayer = nullptr;
QgsFeatureSource *mSource = nullptr;

QString mOutputFile;
QString mOutputFormat;

0 comments on commit 0639264

Please sign in to comment.
You can’t perform that action at this time.