Skip to content

Commit

Permalink
Merge pull request #4852 from nyalldawson/heatmap_source
Browse files Browse the repository at this point in the history
Use QgsFeatureSource instead of QgsVectorLayer for QgsKde
  • Loading branch information
nyalldawson committed Jul 14, 2017
2 parents 0d9945d + 4fa6964 commit 0639264
Show file tree
Hide file tree
Showing 19 changed files with 338 additions and 101 deletions.
4 changes: 2 additions & 2 deletions python/analysis/raster/qgskde.sip
Expand Up @@ -49,9 +49,9 @@ class QgsKernelDensityEstimation

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

double radius;
Expand Down
20 changes: 20 additions & 0 deletions python/core/processing/qgsprocessingutils.sip
Expand Up @@ -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
Expand Down Expand Up @@ -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;


};

Expand Down
22 changes: 22 additions & 0 deletions python/core/qgsfeaturesource.sip
Expand Up @@ -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.
Expand Down
6 changes: 4 additions & 2 deletions python/core/qgsvectorlayer.sip
Expand Up @@ -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
Expand All @@ -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
Expand Down
22 changes: 11 additions & 11 deletions python/plugins/processing/algs/qgis/Heatmap.py
Expand Up @@ -33,7 +33,7 @@
from qgis.core import (QgsFeatureRequest,
QgsProcessing,
QgsProcessingException,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterNumber,
QgsProcessingParameterField,
QgsProcessingParameterEnum,
Expand Down Expand Up @@ -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)'),
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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))

Expand Down
40 changes: 21 additions & 19 deletions python/plugins/processing/algs/qgis/ui/HeatmapWidgets.py
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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()

Expand All @@ -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())
Expand Down
21 changes: 11 additions & 10 deletions src/analysis/raster/qgskde.cpp
Expand Up @@ -14,7 +14,8 @@
***************************************************************************/

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

#define NO_DATA -9999
Expand All @@ -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 )
Expand All @@ -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()
Expand All @@ -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 ) )
Expand All @@ -79,7 +80,7 @@ QgsKernelDensityEstimation::Result QgsKernelDensityEstimation::prepare()
return DriverError;
}

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

mBounds = calculateBounds();
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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
{
Expand Down
8 changes: 4 additions & 4 deletions src/analysis/raster/qgskde.h
Expand Up @@ -25,7 +25,7 @@
#include <cpl_conv.h>
#include "qgis_analysis.h"

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

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -146,7 +146,7 @@ class ANALYSIS_EXPORT QgsKernelDensityEstimation

QgsRectangle calculateBounds() const;

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

QString mOutputFile;
QString mOutputFormat;
Expand Down

0 comments on commit 0639264

Please sign in to comment.