Skip to content

Commit

Permalink
[API] Add an adapter class QgsBatchGeocodeAlgorithm which creates
Browse files Browse the repository at this point in the history
a batch geocoding algorithm from a QgsGeocoderInterface

Example usage:

  # create a class which implements the QgsGeocoderInterface interface:
  class MyGeocoder(QgsGeocoderInterface):

     def geocodeString(self, string, context, feedback):
        # calculate and return results...

  my_geocoder = MyGeocoder()

  # create an algorithm which allows for batch geocoding operations using the custom geocoder interface
  # and implement the few required pure virtual methods
  class MyGeocoderAlgorithm(QgsBatchGeocodeAlgorithm):

      def __init__(self):
          super().__init__(my_geocoder)

      def displayName(self):
          return "My Geocoder"

      def name(self):
          return "my_geocoder_alg"

      def createInstance(self):
          return MyGeocoderAlgorithm()

      # optionally, the group(), groupId(), tags(), shortHelpString() and other metadata style methods can be overridden and customized:
      def tags(self):
          return 'geocode,my service,batch'
  • Loading branch information
nyalldawson committed Dec 14, 2020
1 parent 55f5259 commit 6ae7a17
Show file tree
Hide file tree
Showing 7 changed files with 588 additions and 0 deletions.
1 change: 1 addition & 0 deletions python/analysis/analysis_auto.sip
Expand Up @@ -15,6 +15,7 @@
%Include auto_generated/network/qgsnetworkspeedstrategy.sip
%Include auto_generated/network/qgsnetworkstrategy.sip
%Include auto_generated/network/qgsvectorlayerdirector.sip
%Include auto_generated/processing/qgsalgorithmbatchgeocode.sip
%Include auto_generated/processing/qgsnativealgorithms.sip
%Include auto_generated/raster/qgsalignraster.sip
%Include auto_generated/raster/qgsaspectfilter.sip
Expand Down
@@ -0,0 +1,106 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/analysis/processing/qgsalgorithmbatchgeocode.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/





class QgsBatchGeocodeAlgorithm : QgsProcessingFeatureBasedAlgorithm
{
%Docstring

A base class for batch geocoder algorithms, which takes a QgsGeocoderInterface object and exposes it as
a Processing algorithm for batch geocoding operations.

Example
-------

.. code-block:: python

# create a class which implements the QgsGeocoderInterface interface:
class MyGeocoder(QgsGeocoderInterface):

def geocodeString(self, string, context, feedback):
# calculate and return results...

my_geocoder = MyGeocoder()

# create an algorithm which allows for batch geocoding operations using the custom geocoder interface
# and implement the few required pure virtual methods
class MyGeocoderAlgorithm(QgsBatchGeocodeAlgorithm):

def __init__(self):
super().__init__(my_geocoder)

def displayName(self):
return "My Geocoder"

def name(self):
return "my_geocoder_alg"

def createInstance(self):
return MyGeocoderAlgorithm()

# optionally, the group(), groupId(), tags(), shortHelpString() and other metadata style methods can be overridden and customized:
def tags(self):
return 'geocode,my service,batch'

.. versionadded:: 3.18
%End

%TypeHeaderCode
#include "qgsalgorithmbatchgeocode.h"
%End
public:

QgsBatchGeocodeAlgorithm( QgsGeocoderInterface *geocoder );
%Docstring
Constructor for QgsBatchGeocodeAlgorithm.

The ``geocoder`` must specify an instance of a class which implements the :py:class:`QgsGeocoderInterface`
interface. Ownership of ``geocoder`` is not transferred, and the caller must ensure that ``geocoder``
exists for the lifetime of this algorithm.
%End

virtual void initParameters( const QVariantMap &configuration = QVariantMap() );

virtual QStringList tags() const;

virtual QString group() const;

virtual QString groupId() const;

virtual QList<int> inputLayerTypes() const;


protected:
virtual QString outputName() const;

virtual bool prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback );

virtual QgsFeatureList processFeature( const QgsFeature &feature, QgsProcessingContext &, QgsProcessingFeedback *feedback );

virtual QgsCoordinateReferenceSystem outputCrs( const QgsCoordinateReferenceSystem &inputCrs ) const;

virtual QgsFields outputFields( const QgsFields &inputFields ) const;

virtual QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const;


};




/************************************************************************
* This file has been generated automatically from *
* *
* src/analysis/processing/qgsalgorithmbatchgeocode.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
2 changes: 2 additions & 0 deletions src/analysis/CMakeLists.txt
Expand Up @@ -32,6 +32,7 @@ set(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmaspect.cpp
processing/qgsalgorithmassignprojection.cpp
processing/qgsalgorithmattributeindex.cpp
processing/qgsalgorithmbatchgeocode.cpp
processing/qgsalgorithmboundary.cpp
processing/qgsalgorithmboundingbox.cpp
processing/qgsalgorithmbuffer.cpp
Expand Down Expand Up @@ -311,6 +312,7 @@ set(QGIS_ANALYSIS_HDRS
network/qgsnetworkstrategy.h
network/qgsvectorlayerdirector.h

processing/qgsalgorithmbatchgeocode.h
processing/qgsalgorithmfiledownloader.h
processing/qgsalgorithmimportphotos.h
processing/qgsnativealgorithms.h
Expand Down
148 changes: 148 additions & 0 deletions src/analysis/processing/qgsalgorithmbatchgeocode.cpp
@@ -0,0 +1,148 @@
/***************************************************************************
qgsalgorithmbatchgeocode.cpp
------------------
begin : August 2020
copyright : (C) 2020 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsalgorithmbatchgeocode.h"
#include "qgsgeocoder.h"
#include "qgsgeocoderresult.h"
#include "qgsgeocodercontext.h"

QgsBatchGeocodeAlgorithm::QgsBatchGeocodeAlgorithm( QgsGeocoderInterface *geocoder )
: QgsProcessingFeatureBasedAlgorithm()
, mGeocoder( geocoder )
{

}

QStringList QgsBatchGeocodeAlgorithm::tags() const
{
return QObject::tr( "geocode" ).split( ',' );
}

QString QgsBatchGeocodeAlgorithm::group() const
{
return QObject::tr( "Vector general" );
}

QString QgsBatchGeocodeAlgorithm::groupId() const
{
return QStringLiteral( "vectorgeneral" );
}

void QgsBatchGeocodeAlgorithm::initParameters( const QVariantMap & )
{
addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Address field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::String ) );
}

QList<int> QgsBatchGeocodeAlgorithm::inputLayerTypes() const
{
return QList<int>() << QgsProcessing::TypeVector;
}

QString QgsBatchGeocodeAlgorithm::outputName() const
{
return QObject::tr( "Geocoded" );
}

bool QgsBatchGeocodeAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
{
mAddressField = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
return true;
}

QgsWkbTypes::Type QgsBatchGeocodeAlgorithm::outputWkbType( QgsWkbTypes::Type ) const
{
return QgsWkbTypes::Point;
}

QgsCoordinateReferenceSystem QgsBatchGeocodeAlgorithm::outputCrs( const QgsCoordinateReferenceSystem &inputCrs ) const
{
mOutputCrs = inputCrs;
return mOutputCrs;
}

QgsFields QgsBatchGeocodeAlgorithm::outputFields( const QgsFields &inputFields ) const
{
// append any additional fields created by the geocoder
QgsFields res = inputFields;
const QgsFields newFields = mGeocoder->appendedFields();
mAdditionalFields = newFields.names();
res.extend( newFields );
return res;
}

QgsFeatureList QgsBatchGeocodeAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
QgsFeature f = feature;

QgsAttributes attr = f.attributes();

const QString address = f.attribute( mAddressField ).toString();
if ( address.isEmpty() )
{
// append null attributes
for ( int i = 0; i < mAdditionalFields.count(); ++ i )
attr.append( QVariant() );
f.setAttributes( attr );
feedback->reportError( QObject::tr( "Empty address field for feature %1" ).arg( feature.id() ) );
return QgsFeatureList() << f;
}

QgsGeocoderContext geocodeContext( context.transformContext() );
const QList< QgsGeocoderResult > results = mGeocoder->geocodeString( address, geocodeContext, feedback );
if ( results.empty() )
{
// append null attributes
for ( int i = 0; i < mAdditionalFields.count(); ++ i )
attr.append( QVariant() );
f.setAttributes( attr );
feedback->reportError( QObject::tr( "No result for %1" ).arg( address ) );
return QgsFeatureList() << f;
}

if ( !results.at( 0 ).isValid() )
{
// append null attributes
for ( int i = 0; i < mAdditionalFields.count(); ++ i )
attr.append( QVariant() );
f.setAttributes( attr );

feedback->reportError( QObject::tr( "Error geocoding %1: %2" ).arg( address, results.at( 0 ).error() ) );
return QgsFeatureList() << f;
}

const QVariantMap additionalAttributes = results.at( 0 ).additionalAttributes();
for ( const QString &additionalField : mAdditionalFields )
{
attr.append( additionalAttributes.value( additionalField ) );
}
f.setAttributes( attr );

QgsCoordinateTransform transform = QgsCoordinateTransform( results.at( 0 ).crs(), mOutputCrs, context.transformContext() );
QgsGeometry g = results.at( 0 ).geometry();
try
{
g.transform( transform );
}
catch ( QgsCsException & )
{
feedback->reportError( QObject::tr( "Error transforming %1 to layer CRS" ).arg( address ) );
return QgsFeatureList() << f;
}

f.setGeometry( g );
return QgsFeatureList() << f;
}
109 changes: 109 additions & 0 deletions src/analysis/processing/qgsalgorithmbatchgeocode.h
@@ -0,0 +1,109 @@
/***************************************************************************
qgsalgorithmbatchgeocode.h
------------------
begin : August 2020
copyright : (C) 2020 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSALGORITHMBATCHGEOCODE_H
#define QGSALGORITHMBATCHGEOCODE_H

#include "qgis_sip.h"
#include "qgis_analysis.h"
#include "qgsprocessingalgorithm.h"

class QgsGeocoderInterface;

/**
* \ingroup analysis
*
* A base class for batch geocoder algorithms, which takes a QgsGeocoderInterface object and exposes it as
* a Processing algorithm for batch geocoding operations.
*
* ### Example
*
* \code{.py}
* # create a class which implements the QgsGeocoderInterface interface:
* class MyGeocoder(QgsGeocoderInterface):
*
* def geocodeString(self, string, context, feedback):
* # calculate and return results...
*
* my_geocoder = MyGeocoder()
*
* # create an algorithm which allows for batch geocoding operations using the custom geocoder interface
* # and implement the few required pure virtual methods
* class MyGeocoderAlgorithm(QgsBatchGeocodeAlgorithm):
*
* def __init__(self):
* super().__init__(my_geocoder)
*
* def displayName(self):
* return "My Geocoder"
*
* def name(self):
* return "my_geocoder_alg"
*
* def createInstance(self):
* return MyGeocoderAlgorithm()
*
* # optionally, the group(), groupId(), tags(), shortHelpString() and other metadata style methods can be overridden and customized:
* def tags(self):
* return 'geocode,my service,batch'
*
* \endcode
*
* \since QGIS 3.18
*/
class ANALYSIS_EXPORT QgsBatchGeocodeAlgorithm : public QgsProcessingFeatureBasedAlgorithm
{

public:

/**
* Constructor for QgsBatchGeocodeAlgorithm.
*
* The \a geocoder must specify an instance of a class which implements the QgsGeocoderInterface
* interface. Ownership of \a geocoder is not transferred, and the caller must ensure that \a geocoder
* exists for the lifetime of this algorithm.
*/
QgsBatchGeocodeAlgorithm( QgsGeocoderInterface *geocoder );

void initParameters( const QVariantMap &configuration = QVariantMap() ) override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QList<int> inputLayerTypes() const override;

protected:
QString outputName() const override;
bool prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
QgsFeatureList processFeature( const QgsFeature &feature, QgsProcessingContext &, QgsProcessingFeedback *feedback ) override;
QgsCoordinateReferenceSystem outputCrs( const QgsCoordinateReferenceSystem &inputCrs ) const override;
QgsFields outputFields( const QgsFields &inputFields ) const override;
QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const override;

private:

QString mAddressField;
QgsGeocoderInterface *mGeocoder = nullptr;
mutable QgsCoordinateReferenceSystem mOutputCrs;
mutable QStringList mAdditionalFields;

};

///@endcond PRIVATE

#endif // QGSALGORITHMBATCHGEOCODE_H


1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -29,6 +29,7 @@ ADD_PYTHON_TEST(PyQgsAttributeTableConfig test_qgsattributetableconfig.py)
ADD_PYTHON_TEST(PyQgsAttributeTableModel test_qgsattributetablemodel.py)
ADD_PYTHON_TEST(PyQgsAuthenticationSystem test_qgsauthsystem.py)
ADD_PYTHON_TEST(PyQgsAuthBasicMethod test_qgsauthbasicmethod.py)
ADD_PYTHON_TEST(PyQgsBatchGeocodeAlgorithm test_qgsgeocoderalgorithm.py)
ADD_PYTHON_TEST(PyQgsBearingUtils test_qgsbearingutils.py)
ADD_PYTHON_TEST(PyQgsBinaryWidget test_qgsbinarywidget.py)
ADD_PYTHON_TEST(PyQgsBlendModes test_qgsblendmodes.py)
Expand Down

0 comments on commit 6ae7a17

Please sign in to comment.