Merge pull request #5199 from nyalldawson/extract_by_extent2

[processing] Extent handling improvements
This commit is contained in:
Nyall Dawson 2017-09-15 15:52:00 +10:00 committed by GitHub
commit c5ae3a0c98
22 changed files with 635 additions and 21 deletions

View File

@ -662,12 +662,40 @@ class QgsProcessingAlgorithm
:rtype: QgsCoordinateReferenceSystem
%End
QgsRectangle parameterAsExtent( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context ) const;
QgsRectangle parameterAsExtent( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context,
const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() ) const;
%Docstring
Evaluates the parameter with matching ``name`` to a rectangular extent.
If ``crs`` is set, and the original coordinate reference system of the parameter can be determined, then the extent will be automatically
reprojected so that it is in the specified ``crs``. In this case the extent of the reproject rectangle will be returned.
.. seealso:: parameterAsExtentGeometry()
:rtype: QgsRectangle
%End
QgsGeometry parameterAsExtentGeometry( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context,
const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() );
%Docstring
Evaluates the parameter with matching ``name`` to a rectangular extent, and returns a geometry covering this extent.
If ``crs`` is set, and the original coordinate reference system of the parameter can be determined, then the extent will be automatically
reprojected so that it is in the specified ``crs``. Unlike parameterAsExtent(), the reprojected rectangle returned by this function
will no longer be a rectangle itself (i.e. this method returns the geometry of the actual reprojected rectangle, while parameterAsExtent() returns
just the extent of the reprojected rectangle).
.. seealso:: parameterAsExtent()
:rtype: QgsGeometry
%End
QgsCoordinateReferenceSystem parameterAsExtentCrs( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context );
%Docstring
Returns the coordinate reference system associated with an extent parameter value.
.. seealso:: parameterAsExtent()
:rtype: QgsCoordinateReferenceSystem
%End
QgsPointXY parameterAsPoint( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context ) const;
%Docstring
Evaluates the parameter with matching ``name`` to a point.

View File

@ -534,12 +534,42 @@ class QgsProcessingParameters
:rtype: QgsCoordinateReferenceSystem
%End
static QgsRectangle parameterAsExtent( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context );
static QgsRectangle parameterAsExtent( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context,
const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() );
%Docstring
Evaluates the parameter with matching ``definition`` to a rectangular extent.
If ``crs`` is set, and the original coordinate reference system of the parameter can be determined, then the extent will be automatically
reprojected so that it is in the specified ``crs``. In this case the extent of the reproject rectangle will be returned.
.. seealso:: parameterAsExtentGeometry()
.. seealso:: parameterAsExtentCrs()
:rtype: QgsRectangle
%End
static QgsGeometry parameterAsExtentGeometry( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context,
const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() );
%Docstring
Evaluates the parameter with matching ``definition`` to a rectangular extent, and returns a geometry covering this extent.
If ``crs`` is set, and the original coordinate reference system of the parameter can be determined, then the extent will be automatically
reprojected so that it is in the specified ``crs``. Unlike parameterAsExtent(), the reprojected rectangle returned by this function
will no longer be a rectangle itself (i.e. this method returns the geometry of the actual reprojected rectangle, while parameterAsExtent() returns
just the extent of the reprojected rectangle).
.. seealso:: parameterAsExtent()
.. seealso:: parameterAsExtentCrs()
:rtype: QgsGeometry
%End
static QgsCoordinateReferenceSystem parameterAsExtentCrs( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context );
%Docstring
Returns the coordinate reference system associated with an extent parameter value.
.. seealso:: parameterAsExtent()
:rtype: QgsCoordinateReferenceSystem
%End
static QgsPointXY parameterAsPoint( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context );
%Docstring
Evaluates the parameter with matching ``definition`` to a point.

View File

@ -76,8 +76,8 @@ class CreateConstantRaster(QgisAlgorithm):
return self.tr('Create constant raster layer')
def processAlgorithm(self, parameters, context, feedback):
extent = self.parameterAsExtent(parameters, self.EXTENT, context)
crs = self.parameterAsCrs(parameters, self.TARGET_CRS, context)
extent = self.parameterAsExtent(parameters, self.EXTENT, context, crs)
value = self.parameterAsDouble(parameters, self.NUMBER, context)
pixelSize = self.parameterAsDouble(parameters, self.PIXEL_SIZE, context)

View File

@ -105,8 +105,8 @@ class GridLine(QgisAlgorithm):
hOverlay = self.parameterAsDouble(parameters, self.HOVERLAY, context)
vOverlay = self.parameterAsDouble(parameters, self.VOVERLAY, context)
bbox = self.parameterAsExtent(parameters, self.EXTENT, context)
crs = self.parameterAsCrs(parameters, self.CRS, context)
bbox = self.parameterAsExtent(parameters, self.EXTENT, context, crs)
width = bbox.width()
height = bbox.height()

View File

@ -113,8 +113,8 @@ class GridPolygon(QgisAlgorithm):
hOverlay = self.parameterAsDouble(parameters, self.HOVERLAY, context)
vOverlay = self.parameterAsDouble(parameters, self.VOVERLAY, context)
bbox = self.parameterAsExtent(parameters, self.EXTENT, context)
crs = self.parameterAsCrs(parameters, self.CRS, context)
bbox = self.parameterAsExtent(parameters, self.EXTENT, context, crs)
width = bbox.width()
height = bbox.height()

View File

@ -94,10 +94,10 @@ class RandomPointsExtent(QgisAlgorithm):
return self.tr('Random points in extent')
def processAlgorithm(self, parameters, context, feedback):
bbox = self.parameterAsExtent(parameters, self.EXTENT, context)
pointCount = self.parameterAsDouble(parameters, self.POINTS_NUMBER, context)
minDistance = self.parameterAsDouble(parameters, self.MIN_DISTANCE, context)
crs = self.parameterAsCrs(parameters, self.TARGET_CRS, context)
bbox = self.parameterAsExtent(parameters, self.EXTENT, context, crs)
extent = QgsGeometry().fromRect(bbox)

View File

@ -93,13 +93,12 @@ class RegularPoints(QgisAlgorithm):
return self.tr('Regular points')
def processAlgorithm(self, parameters, context, feedback):
extent = self.parameterAsExtent(parameters, self.EXTENT, context)
spacing = self.parameterAsDouble(parameters, self.SPACING, context)
inset = self.parameterAsDouble(parameters, self.INSET, context)
randomize = self.parameterAsBool(parameters, self.RANDOMIZE, context)
isSpacing = self.parameterAsBool(parameters, self.IS_SPACING, context)
crs = self.parameterAsCrs(parameters, self.CRS, context)
extent = self.parameterAsExtent(parameters, self.EXTENT, context, crs)
fields = QgsFields()
fields.append(QgsField('id', QVariant.Int, '', 10, 0))

View File

@ -216,7 +216,10 @@ class AlgorithmDialog(AlgorithmDialogBase):
self.tr('<b>Algorithm \'{0}\' starting...</b>').format(self.alg.displayName()), escape_html=False)
feedback.pushInfo(self.tr('Input parameters:'))
feedback.pushCommandInfo(pformat(parameters))
display_params = []
for k, v in parameters.items():
display_params.append("'" + k + "' : " + self.alg.parameterDefinition(k).valueAsPythonString(v, context))
feedback.pushCommandInfo('{ ' + ', '.join(display_params) + ' }')
feedback.pushInfo('')
start_time = time.time()

22
python/plugins/processing/gui/ExtentSelectionPanel.py Normal file → Executable file
View File

@ -36,7 +36,11 @@ from qgis.gui import QgsMessageBar
from qgis.utils import iface
from qgis.core import (QgsProcessingUtils,
QgsProcessingParameterDefinition,
QgsProject)
QgsProcessingParameters,
QgsProject,
QgsCoordinateReferenceSystem,
QgsRectangle,
QgsReferencedRectangle)
from processing.gui.RectangleMapTool import RectangleMapTool
from processing.core.ProcessingConfig import ProcessingConfig
from processing.tools.dataobjects import createContext
@ -54,6 +58,8 @@ class ExtentSelectionPanel(BASE, WIDGET):
self.dialog = dialog
self.param = param
self.crs = QgsProject.instance().crs()
if self.param.flags() & QgsProcessingParameterDefinition.FlagOptional:
if hasattr(self.leText, 'setPlaceholderText'):
self.leText.setPlaceholderText(
@ -126,7 +132,7 @@ class ExtentSelectionPanel(BASE, WIDGET):
(item, ok) = QInputDialog.getItem(self, self.tr('Select extent'),
self.tr('Use extent from'), extents, False)
if ok:
self.setValueFromRect(extentsDict[item]["extent"])
self.setValueFromRect(QgsReferencedRectangle(extentsDict[item]["extent"], QgsCoordinateReferenceSystem(extentsDict[item]["authid"])))
if extentsDict[item]["authid"] != iface.mapCanvas().mapSettings().destinationCrs().authid():
iface.messageBar().pushMessage(self.tr("Warning"),
self.tr("The projection of the chosen layer is not the same as canvas projection! The selected extent might not be what was intended."),
@ -146,6 +152,10 @@ class ExtentSelectionPanel(BASE, WIDGET):
r.xMinimum(), r.xMaximum(), r.yMinimum(), r.yMaximum())
self.leText.setText(s)
try:
self.crs = r.crs()
except:
self.crs = QgsProject.instance().crs()
self.tool.reset()
canvas = iface.mapCanvas()
canvas.setMapTool(self.prevMapTool)
@ -155,7 +165,13 @@ class ExtentSelectionPanel(BASE, WIDGET):
def getValue(self):
if str(self.leText.text()).strip() != '':
return str(self.leText.text())
try:
parts = self.leText.text().split(',')
parts = [float(p) for p in parts]
r = QgsReferencedRectangle(QgsRectangle(parts[0], parts[2], parts[1], parts[3]), self.crs)
return r
except:
return str(self.leText.text())
else:
return None

View File

@ -0,0 +1,32 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>extract_by_extent</Name>
<ElementPath>extract_by_extent</ElementPath>
<!--POLYGON-->
<GeometryType>3</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>2</FeatureCount>
<ExtentXMin>-1.00000</ExtentXMin>
<ExtentXMax>6.00000</ExtentXMax>
<ExtentYMin>-3.00000</ExtentYMin>
<ExtentYMax>3.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>5</Width>
</PropertyDefn>
<PropertyDefn>
<Name>intval</Name>
<ElementPath>intval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>floatval</Name>
<ElementPath>floatval</ElementPath>
<Type>Real</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
<gml:coord><gml:X>6</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:extract_by_extent fid="polys.0">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-1,-1 -1,3 3,3 3,2 2,2 2,-1 -1,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>aaaaa</ogr:name>
<ogr:intval>33</ogr:intval>
<ogr:floatval>44.123456</ogr:floatval>
</ogr:extract_by_extent>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_by_extent fid="polys.5">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3,2 6,1 6,-3 2,-1 2,2 3,2</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>elim</ogr:name>
<ogr:intval>2</ogr:intval>
<ogr:floatval>3.33</ogr:floatval>
</ogr:extract_by_extent>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,32 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>extract_by_extent_clip</Name>
<ElementPath>extract_by_extent_clip</ElementPath>
<!--MULTIPOLYGON-->
<GeometryType>6</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>2</FeatureCount>
<ExtentXMin>-1.00000</ExtentXMin>
<ExtentXMax>4.77500</ExtentXMax>
<ExtentYMin>-2.38750</ExtentYMin>
<ExtentYMax>3.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>5</Width>
</PropertyDefn>
<PropertyDefn>
<Name>intval</Name>
<ElementPath>intval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>floatval</Name>
<ElementPath>floatval</ElementPath>
<Type>Real</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>-1</gml:X><gml:Y>-2.3875</gml:Y></gml:coord>
<gml:coord><gml:X>4.775</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:extract_by_extent_clip fid="polys.0">
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-1,-1 -1,3 3,3 3,2 2,2 2,-1 -1,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
<ogr:name>aaaaa</ogr:name>
<ogr:intval>33</ogr:intval>
<ogr:floatval>44.123456</ogr:floatval>
</ogr:extract_by_extent_clip>
</gml:featureMember>
<gml:featureMember>
<ogr:extract_by_extent_clip fid="polys.5">
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3,2 4.775,1.40833333333333 4.775,-2.3875 2,-1 2,2 3,2</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
<ogr:name>elim</ogr:name>
<ogr:intval>2</ogr:intval>
<ogr:floatval>3.33</ogr:floatval>
</ogr:extract_by_extent_clip>
</gml:featureMember>
</ogr:FeatureCollection>

28
python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml vendored Normal file → Executable file
View File

@ -3937,4 +3937,30 @@ tests:
- name
compare:
fields:
fid: skip
fid: skip
- algorithm: native:extractbyextent
name: Extract by extent
params:
CLIP: false
EXTENT: -1.1650000000000003,4.775,-2.444285714285715,3.4171428571428573
INPUT:
name: polys.gml
type: vector
results:
OUTPUT:
name: expected/extract_by_extent.gml
type: vector
- algorithm: native:extractbyextent
name: Extract by extent (clipped)
params:
CLIP: true
EXTENT: -1.1650000000000003,4.775,-2.444285714285715,3.4171428571428573
INPUT:
name: polys.gml
type: vector
results:
OUTPUT:
name: expected/extract_by_extent_clip.gml
type: vector

View File

@ -82,6 +82,8 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsMergeLinesAlgorithm() );
addAlgorithm( new QgsSmoothAlgorithm() );
addAlgorithm( new QgsSimplifyAlgorithm() );
addAlgorithm( new QgsExtractByExtentAlgorithm() );
addAlgorithm( new QgsExtentToLayerAlgorithm() );
}
void QgsCentroidAlgorithm::initAlgorithm( const QVariantMap & )
@ -1811,7 +1813,121 @@ QgsFeature QgsSimplifyAlgorithm::processFeature( const QgsFeature &feature, QgsP
return f;
}
void QgsExtractByExtentAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
addParameter( new QgsProcessingParameterExtent( QStringLiteral( "EXTENT" ), QObject::tr( "Extent" ) ) );
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "CLIP" ), QObject::tr( "Clip features to extent" ), false ) );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extracted" ) ) );
}
QString QgsExtractByExtentAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm creates a new vector layer that only contains features which fall within a specified extent. "
"Any features which intersect the extent will be included.\n\n"
"Optionally, feature geometries can also be clipped to the extent. If this option is selected, then the output "
"geometries will automatically be converted to multi geometries to ensure uniform output geometry types." );
}
QgsExtractByExtentAlgorithm *QgsExtractByExtentAlgorithm::createInstance() const
{
return new QgsExtractByExtentAlgorithm();
}
QVariantMap QgsExtractByExtentAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
std::unique_ptr< QgsFeatureSource > featureSource( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
if ( !featureSource )
return QVariantMap();
QgsRectangle extent = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, featureSource->sourceCrs() );
bool clip = parameterAsBool( parameters, QStringLiteral( "CLIP" ), context );
// if clipping, we force multi output
QgsWkbTypes::Type outType = clip ? QgsWkbTypes::multiType( featureSource->wkbType() ) : featureSource->wkbType();
QString dest;
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, featureSource->fields(), outType, featureSource->sourceCrs() ) );
if ( !sink )
return QVariantMap();
QgsGeometry clipGeom = parameterAsExtentGeometry( parameters, QStringLiteral( "EXTENT" ), context, featureSource->sourceCrs() );
double step = featureSource->featureCount() > 0 ? 100.0 / featureSource->featureCount() : 1;
QgsFeatureIterator inputIt = featureSource->getFeatures( QgsFeatureRequest().setFilterRect( extent ).setFlags( QgsFeatureRequest::ExactIntersect ) );
QgsFeature f;
int i = -1;
while ( inputIt.nextFeature( f ) )
{
i++;
if ( feedback->isCanceled() )
{
break;
}
if ( clip )
{
QgsGeometry g = f.geometry().intersection( clipGeom );
g.convertToMultiType();
f.setGeometry( g );
}
sink->addFeature( f, QgsFeatureSink::FastInsert );
feedback->setProgress( i * step );
}
QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
return outputs;
}
void QgsExtentToLayerAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterExtent( QStringLiteral( "INPUT" ), QObject::tr( "Extent" ) ) );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extent" ), QgsProcessing::TypeVectorPolygon ) );
}
QString QgsExtentToLayerAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm creates a new vector layer that contains a single feature with geometry matching an extent parameter.\n\n"
"It can be used in models to convert an extent into a layer which can be used for other algorithms which require "
"a layer based input." );
}
QgsExtentToLayerAlgorithm *QgsExtentToLayerAlgorithm::createInstance() const
{
return new QgsExtentToLayerAlgorithm();
}
QVariantMap QgsExtentToLayerAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
QgsCoordinateReferenceSystem crs = parameterAsExtentCrs( parameters, QStringLiteral( "INPUT" ), context );
QgsGeometry geom = parameterAsExtentGeometry( parameters, QStringLiteral( "INPUT" ), context );
QgsFields fields;
fields.append( QgsField( QStringLiteral( "id" ), QVariant::Int ) );
QString dest;
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, QgsWkbTypes::Polygon, crs ) );
if ( !sink )
return QVariantMap();
QgsFeature f;
f.setAttributes( QgsAttributes() << 1 );
f.setGeometry( geom );
sink->addFeature( f, QgsFeatureSink::FastInsert );
feedback->setProgress( 100 );
QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
return outputs;
}
///@endcond

View File

@ -662,6 +662,56 @@ class QgsSimplifyAlgorithm : public QgsProcessingFeatureBasedAlgorithm
std::unique_ptr< QgsMapToPixelSimplifier > mSimplifier;
};
/**
* Native extract/clip by extent algorithm.
*/
class QgsExtractByExtentAlgorithm : public QgsProcessingAlgorithm
{
public:
QgsExtractByExtentAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override { return QStringLiteral( "extractbyextent" ); }
QString displayName() const override { return QObject::tr( "Extract/clip by extent" ); }
virtual QStringList tags() const override { return QObject::tr( "clip,extract,intersect,intersection,mask,extent" ).split( ',' ); }
QString group() const override { return QObject::tr( "Vector overlay" ); }
QString shortHelpString() const override;
QgsExtractByExtentAlgorithm *createInstance() const override SIP_FACTORY;
protected:
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
};
/**
* Native extent to layer algorithm.
*/
class QgsExtentToLayerAlgorithm : public QgsProcessingAlgorithm
{
public:
QgsExtentToLayerAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override { return QStringLiteral( "extenttolayer" ); }
QString displayName() const override { return QObject::tr( "Create layer from extent" ); }
virtual QStringList tags() const override { return QObject::tr( "extent,layer,polygon,create,new" ).split( ',' ); }
QString group() const override { return QObject::tr( "Vector geometry" ); }
QString shortHelpString() const override;
QgsExtentToLayerAlgorithm *createInstance() const override SIP_FACTORY;
protected:
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
};
///@endcond PRIVATE
#endif // QGSNATIVEALGORITHMS_H

View File

@ -550,9 +550,19 @@ QgsCoordinateReferenceSystem QgsProcessingAlgorithm::parameterAsCrs( const QVari
return QgsProcessingParameters::parameterAsCrs( parameterDefinition( name ), parameters, context );
}
QgsRectangle QgsProcessingAlgorithm::parameterAsExtent( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context ) const
QgsCoordinateReferenceSystem QgsProcessingAlgorithm::parameterAsExtentCrs( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context )
{
return QgsProcessingParameters::parameterAsExtent( parameterDefinition( name ), parameters, context );
return QgsProcessingParameters::parameterAsCrs( parameterDefinition( name ), parameters, context );
}
QgsRectangle QgsProcessingAlgorithm::parameterAsExtent( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context, const QgsCoordinateReferenceSystem &crs ) const
{
return QgsProcessingParameters::parameterAsExtent( parameterDefinition( name ), parameters, context, crs );
}
QgsGeometry QgsProcessingAlgorithm::parameterAsExtentGeometry( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context, const QgsCoordinateReferenceSystem &crs )
{
return QgsProcessingParameters::parameterAsExtentGeometry( parameterDefinition( name ), parameters, context, crs );
}
QgsPointXY QgsProcessingAlgorithm::parameterAsPoint( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context ) const

View File

@ -648,8 +648,34 @@ class CORE_EXPORT QgsProcessingAlgorithm
/**
* Evaluates the parameter with matching \a name to a rectangular extent.
*
* If \a crs is set, and the original coordinate reference system of the parameter can be determined, then the extent will be automatically
* reprojected so that it is in the specified \a crs. In this case the extent of the reproject rectangle will be returned.
*
* \see parameterAsExtentGeometry()
*/
QgsRectangle parameterAsExtent( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context ) const;
QgsRectangle parameterAsExtent( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context,
const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() ) const;
/**
* Evaluates the parameter with matching \a name to a rectangular extent, and returns a geometry covering this extent.
*
* If \a crs is set, and the original coordinate reference system of the parameter can be determined, then the extent will be automatically
* reprojected so that it is in the specified \a crs. Unlike parameterAsExtent(), the reprojected rectangle returned by this function
* will no longer be a rectangle itself (i.e. this method returns the geometry of the actual reprojected rectangle, while parameterAsExtent() returns
* just the extent of the reprojected rectangle).
*
* \see parameterAsExtent()
*/
QgsGeometry parameterAsExtentGeometry( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context,
const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() );
/**
* Returns the coordinate reference system associated with an extent parameter value.
*
* \see parameterAsExtent()
*/
QgsCoordinateReferenceSystem parameterAsExtentCrs( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context );
/**
* Evaluates the parameter with matching \a name to a point.

View File

@ -22,6 +22,7 @@
#include "qgsprocessingoutputs.h"
#include "qgssettings.h"
#include "qgsvectorfilewriter.h"
#include "qgsreferencedgeometry.h"
#include <functional>
bool QgsProcessingParameters::isDynamic( const QVariantMap &parameters, const QString &name )
@ -484,12 +485,29 @@ QgsCoordinateReferenceSystem QgsProcessingParameters::parameterAsCrs( const QgsP
return crs;
}
QgsRectangle QgsProcessingParameters::parameterAsExtent( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context )
QgsRectangle QgsProcessingParameters::parameterAsExtent( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context,
const QgsCoordinateReferenceSystem &crs )
{
if ( !definition )
return QgsRectangle();
QVariant val = parameters.value( definition->name() );
if ( val.canConvert< QgsRectangle >() )
{
return val.value<QgsRectangle>();
}
if ( val.canConvert< QgsReferencedRectangle >() )
{
QgsReferencedRectangle rr = val.value<QgsReferencedRectangle>();
if ( crs.isValid() && rr.crs().isValid() && crs != rr.crs() )
{
QgsCoordinateTransform ct( rr.crs(), crs );
return ct.transformBoundingBox( rr );
}
return rr;
}
QString rectText;
if ( val.canConvert<QgsProperty>() )
rectText = val.value< QgsProperty >().valueAsString( context.expressionContext(), definition->defaultValue().toString() );
@ -521,6 +539,55 @@ QgsRectangle QgsProcessingParameters::parameterAsExtent( const QgsProcessingPara
return QgsRectangle();
}
QgsGeometry QgsProcessingParameters::parameterAsExtentGeometry( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context, const QgsCoordinateReferenceSystem &crs )
{
if ( !definition )
return QgsGeometry();
QVariant val = parameters.value( definition->name() );
if ( val.canConvert< QgsReferencedRectangle >() )
{
QgsReferencedRectangle rr = val.value<QgsReferencedRectangle>();
QgsGeometry g = QgsGeometry::fromRect( rr );
if ( crs.isValid() && rr.crs().isValid() && crs != rr.crs() )
{
g = g.densifyByCount( 20 );
QgsCoordinateTransform ct( rr.crs(), crs );
try
{
g.transform( ct );
}
catch ( QgsCsException & )
{
QgsMessageLog::logMessage( QObject::tr( "Error transforming extent geometry" ) );
}
return g;
}
}
return QgsGeometry::fromRect( parameterAsExtent( definition, parameters, context, crs ) );
}
QgsCoordinateReferenceSystem QgsProcessingParameters::parameterAsExtentCrs( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context )
{
QVariant val = parameters.value( definition->name() );
if ( val.canConvert< QgsReferencedRectangle >() )
{
QgsReferencedRectangle rr = val.value<QgsReferencedRectangle>();
if ( rr.crs().isValid() )
{
return rr.crs();
}
}
if ( context.project() )
return context.project()->crs();
else
return QgsCoordinateReferenceSystem();
}
QgsPointXY QgsProcessingParameters::parameterAsPoint( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context )
{
if ( !definition )
@ -1133,6 +1200,17 @@ bool QgsProcessingParameterExtent::checkValueIsAcceptable( const QVariant &input
return true;
}
if ( input.canConvert< QgsRectangle >() )
{
QgsRectangle r = input.value<QgsRectangle>();
return !r.isNull();
}
if ( input.canConvert< QgsReferencedRectangle >() )
{
QgsReferencedRectangle r = input.value<QgsReferencedRectangle>();
return !r.isNull();
}
if ( input.type() != QVariant::String || input.toString().isEmpty() )
return mFlags & FlagOptional;
@ -1166,6 +1244,23 @@ QString QgsProcessingParameterExtent::valueAsPythonString( const QVariant &value
if ( value.canConvert<QgsProperty>() )
return QStringLiteral( "QgsProperty.fromExpression('%1')" ).arg( value.value< QgsProperty >().asExpression() );
if ( value.canConvert< QgsRectangle >() )
{
QgsRectangle r = value.value<QgsRectangle>();
return QStringLiteral( "QgsRectangle( %1, %2, %3, %4 )" ).arg( qgsDoubleToString( r.xMinimum() ),
qgsDoubleToString( r.yMinimum() ),
qgsDoubleToString( r.xMaximum() ),
qgsDoubleToString( r.yMaximum() ) );
}
if ( value.canConvert< QgsReferencedRectangle >() )
{
QgsReferencedRectangle r = value.value<QgsReferencedRectangle>();
return QStringLiteral( "QgsReferencedRectangle( QgsRectangle( %1, %2, %3, %4 ), QgsCoordinateReferenceSystem( '%5' ) )" ).arg( qgsDoubleToString( r.xMinimum() ),
qgsDoubleToString( r.yMinimum() ),
qgsDoubleToString( r.xMaximum() ),
qgsDoubleToString( r.yMaximum() ), r.crs().authid() );
}
QVariantMap p;
p.insert( name(), value );
QgsMapLayer *layer = QgsProcessingParameters::parameterAsLayer( this, p, context );

View File

@ -565,8 +565,36 @@ class CORE_EXPORT QgsProcessingParameters
/**
* Evaluates the parameter with matching \a definition to a rectangular extent.
*
* If \a crs is set, and the original coordinate reference system of the parameter can be determined, then the extent will be automatically
* reprojected so that it is in the specified \a crs. In this case the extent of the reproject rectangle will be returned.
*
* \see parameterAsExtentGeometry()
* \see parameterAsExtentCrs()
*/
static QgsRectangle parameterAsExtent( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context );
static QgsRectangle parameterAsExtent( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context,
const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() );
/**
* Evaluates the parameter with matching \a definition to a rectangular extent, and returns a geometry covering this extent.
*
* If \a crs is set, and the original coordinate reference system of the parameter can be determined, then the extent will be automatically
* reprojected so that it is in the specified \a crs. Unlike parameterAsExtent(), the reprojected rectangle returned by this function
* will no longer be a rectangle itself (i.e. this method returns the geometry of the actual reprojected rectangle, while parameterAsExtent() returns
* just the extent of the reprojected rectangle).
*
* \see parameterAsExtent()
* \see parameterAsExtentCrs()
*/
static QgsGeometry parameterAsExtentGeometry( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context,
const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() );
/**
* Returns the coordinate reference system associated with an extent parameter value.
*
* \see parameterAsExtent()
*/
static QgsCoordinateReferenceSystem parameterAsExtentCrs( const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context );
/**
* Evaluates the parameter with matching \a definition to a point.

View File

@ -41,6 +41,7 @@
#include "qgsunittypes.h"
#include "qgsuserprofile.h"
#include "qgsuserprofilemanager.h"
#include "qgsreferencedgeometry.h"
#include "gps/qgsgpsconnectionregistry.h"
#include "processing/qgsprocessingregistry.h"
@ -149,6 +150,8 @@ void QgsApplication::init( QString profileFolder )
qRegisterMetaType<QgsUnitTypes::LayoutUnit>( "QgsUnitTypes::LayoutUnit" );
qRegisterMetaType<QgsFeatureIds>( "QgsFeatureIds" );
qRegisterMetaType<QgsMessageLog::MessageLevel>( "QgsMessageLog::MessageLevel" );
qRegisterMetaType<QgsReferencedRectangle>( "QgsReferencedRectangle" );
qRegisterMetaType<QgsReferencedRectangle>( "QgsReferencedPoint" );
QString prefixPath( getenv( "QGIS_PREFIX_PATH" ) ? getenv( "QGIS_PREFIX_PATH" ) : applicationDirPath() );
// QgsDebugMsg( QString( "prefixPath(): %1" ).arg( prefixPath ) );

View File

@ -32,6 +32,7 @@
#include "qgsvectorfilewriter.h"
#include "qgsexpressioncontext.h"
#include "qgsxmlutils.h"
#include "qgsreferencedgeometry.h"
class DummyAlgorithm : public QgsProcessingAlgorithm
{
@ -1844,6 +1845,10 @@ void TestQgsProcessing::parameterExtent()
QVERIFY( def->checkValueIsAcceptable( "1,2,3,4" ) );
QVERIFY( !def->checkValueIsAcceptable( "" ) );
QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
QVERIFY( def->checkValueIsAcceptable( QgsRectangle( 1, 2, 3, 4 ) ) );
QVERIFY( !def->checkValueIsAcceptable( QgsRectangle() ) );
QVERIFY( def->checkValueIsAcceptable( QgsReferencedRectangle( QgsRectangle( 1, 2, 3, 4 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ) );
QVERIFY( !def->checkValueIsAcceptable( QgsReferencedRectangle( QgsRectangle(), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ) );
// these checks require a context - otherwise we could potentially be referring to a layer source
QVERIFY( def->checkValueIsAcceptable( "1,2,3" ) );
@ -1881,15 +1886,70 @@ void TestQgsProcessing::parameterExtent()
QGSCOMPARENEAR( ext.yMinimum(), 3.3, 0.001 );
QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 );
// with target CRS - should make no difference, because source CRS is unknown
ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
QGSCOMPARENEAR( ext.xMinimum(), 1.1, 0.001 );
QGSCOMPARENEAR( ext.xMaximum(), 2.2, 0.001 );
QGSCOMPARENEAR( ext.yMinimum(), 3.3, 0.001 );
QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 );
// nonsense string
params.insert( "non_optional", QString( "i'm not a crs, and nothing you can do will make me one" ) );
QVERIFY( QgsProcessingParameters::parameterAsExtent( def.get(), params, context ).isNull() );
// QgsRectangle
params.insert( "non_optional", QgsRectangle( 11.1, 12.2, 13.3, 14.4 ) );
ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context );
QGSCOMPARENEAR( ext.xMinimum(), 11.1, 0.001 );
QGSCOMPARENEAR( ext.xMaximum(), 13.3, 0.001 );
QGSCOMPARENEAR( ext.yMinimum(), 12.2, 0.001 );
QGSCOMPARENEAR( ext.yMaximum(), 14.4, 0.001 );
// with target CRS - should make no difference, because source CRS is unknown
ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
QGSCOMPARENEAR( ext.xMinimum(), 11.1, 0.001 );
QGSCOMPARENEAR( ext.xMaximum(), 13.3, 0.001 );
QGSCOMPARENEAR( ext.yMinimum(), 12.2, 0.001 );
QGSCOMPARENEAR( ext.yMaximum(), 14.4, 0.001 );
QgsGeometry gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
QCOMPARE( gExt.exportToWkt( 1 ), QStringLiteral( "Polygon ((11.1 12.2, 13.3 12.2, 13.3 14.4, 11.1 14.4, 11.1 12.2))" ) );
p.setCrs( QgsCoordinateReferenceSystem( "EPSG:3785" ) );
QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:3785" ) );
// QgsReferencedRectangle
params.insert( "non_optional", QgsReferencedRectangle( QgsRectangle( 1.1, 2.2, 3.3, 4.4 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) );
ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context );
QGSCOMPARENEAR( ext.xMinimum(), 1.1, 0.001 );
QGSCOMPARENEAR( ext.xMaximum(), 3.3, 0.001 );
QGSCOMPARENEAR( ext.yMinimum(), 2.2, 0.001 );
QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 );
QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) );
// with target CRS
ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 );
QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 );
QGSCOMPARENEAR( ext.yMinimum(), 244963, 100 );
QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 );
// as reprojected geometry
gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) );
QCOMPARE( gExt.geometry()->vertexCount(), 85 );
ext = gExt.boundingBox();
QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 );
QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 );
QGSCOMPARENEAR( ext.yMinimum(), 244963, 100 );
QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 );
QCOMPARE( def->valueAsPythonString( "1,2,3,4", context ), QStringLiteral( "'1,2,3,4'" ) );
QCOMPARE( def->valueAsPythonString( r1->id(), context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) );
QCOMPARE( def->valueAsPythonString( QVariant::fromValue( r1 ), context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) );
QCOMPARE( def->valueAsPythonString( raster2, context ), QString( "'" ) + testDataDir + QStringLiteral( "landsat.tif'" ) );
QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
QCOMPARE( def->valueAsPythonString( QgsRectangle( 11, 12, 13, 14 ), context ), QStringLiteral( "QgsRectangle( 11, 12, 13, 14 )" ) );
QCOMPARE( def->valueAsPythonString( QgsReferencedRectangle( QgsRectangle( 11, 12, 13, 14 ), QgsCoordinateReferenceSystem( "epsg:4326" ) ), context ), QStringLiteral( "QgsReferencedRectangle( QgsRectangle( 11, 12, 13, 14 ), QgsCoordinateReferenceSystem( 'EPSG:4326' ) )" ) );
QString code = def->asScriptCode();
QCOMPARE( code, QStringLiteral( "##non_optional=extent 1,2,3,4" ) );