diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml index 0eb07954e2b..e2afbfd3174 100755 --- a/python/plugins/processing/algs/help/qgis.yaml +++ b/python/plugins/processing/algs/help/qgis.yaml @@ -402,12 +402,14 @@ qgis:polygoncentroids: > NOTE: This algorithm is deprecated and the generic "centroids" algorithm (which works for line and multi geometry layers) should be used instead. - qgis:polygonfromlayerextent: > This algorithm takes a vector layer and generates a new one with the minimum bounding box (rectangle with N-S orientation) that covers all the input features. As an alternative, the output layer can contain not just a single bounding box, but one for each input feature, representing the bounding box of each of them. +qgis:polygonfromrasterextent: > + This algorithm takes a raster layer and generates a vector layer containing a feature with the minimum bounding box that covers the raster layer's extent. + qgis:polygonize: > This algorithm takes a lines layer and creates a polygon layer, with polygons generated from the lines in the input layer. diff --git a/python/plugins/processing/algs/qgis/ExtentFromLayer.py b/python/plugins/processing/algs/qgis/ExtentFromLayer.py index d0bdd4cbd4d..61a21426d53 100644 --- a/python/plugins/processing/algs/qgis/ExtentFromLayer.py +++ b/python/plugins/processing/algs/qgis/ExtentFromLayer.py @@ -49,7 +49,7 @@ pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] class ExtentFromLayer(QgisAlgorithm): - INPUT_LAYER = 'INPUT_LAYER' + INPUT = 'INPUT' BY_FEATURE = 'BY_FEATURE' OUTPUT = 'OUTPUT' @@ -67,7 +67,7 @@ class ExtentFromLayer(QgisAlgorithm): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER, self.tr('Input layer'))) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'))) self.addParameter(QgsProcessingParameterBoolean(self.BY_FEATURE, self.tr('Calculate extent for each feature separately'), False)) @@ -77,10 +77,10 @@ class ExtentFromLayer(QgisAlgorithm): return 'polygonfromlayerextent' def displayName(self): - return self.tr('Polygon from layer extent') + return self.tr('Polygon from vector extent') def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + source = self.parameterAsSource(parameters, self.INPUT, context) byFeature = self.parameterAsBool(parameters, self.BY_FEATURE, context) fields = QgsFields() diff --git a/python/plugins/processing/algs/qgis/ExtentFromRasterLayer.py b/python/plugins/processing/algs/qgis/ExtentFromRasterLayer.py new file mode 100755 index 00000000000..63efd366aef --- /dev/null +++ b/python/plugins/processing/algs/qgis/ExtentFromRasterLayer.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + ExtentFromRasterLayer.py + --------------------- + Date : August 2017 + Copyright : (C) 2017 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. * +* * +*************************************************************************** +""" + +__author__ = 'Nyall Dawson' +__date__ = 'August 2017' +__copyright__ = '(C) 2017, Nyall Dawson' + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = '$Format:%H$' + +import os + +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtCore import QVariant + +from qgis.core import (QgsField, + QgsFeatureSink, + QgsPointXY, + QgsGeometry, + QgsFeature, + QgsWkbTypes, + QgsProcessing, + QgsProcessingParameterRasterLayer, + QgsProcessingParameterFeatureSink, + QgsFields) + +from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm + +pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] + + +class ExtentFromRasterLayer(QgisAlgorithm): + + INPUT = 'INPUT' + OUTPUT = 'OUTPUT' + + def icon(self): + return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'layer_extent.png')) + + def tags(self): + return self.tr('extent,envelope,bounds,bounding,boundary,layer').split(',') + + def group(self): + return self.tr('Raster tools') + + def __init__(self): + super().__init__() + + def initAlgorithm(self, config=None): + self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT, self.tr('Input layer'))) + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Extent'), type=QgsProcessing.TypeVectorPolygon)) + + def name(self): + return 'polygonfromrasterextent' + + def displayName(self): + return self.tr('Polygon from raster extent') + + def processAlgorithm(self, parameters, context, feedback): + raster = self.parameterAsRasterLayer(parameters, self.INPUT, context) + + fields = QgsFields() + fields.append(QgsField('MINX', QVariant.Double)) + fields.append(QgsField('MINY', QVariant.Double)) + fields.append(QgsField('MAXX', QVariant.Double)) + fields.append(QgsField('MAXY', QVariant.Double)) + fields.append(QgsField('CNTX', QVariant.Double)) + fields.append(QgsField('CNTY', QVariant.Double)) + fields.append(QgsField('AREA', QVariant.Double)) + fields.append(QgsField('PERIM', QVariant.Double)) + fields.append(QgsField('HEIGHT', QVariant.Double)) + fields.append(QgsField('WIDTH', QVariant.Double)) + + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, QgsWkbTypes.Polygon, raster.crs()) + + self.layerExtent(raster, sink, feedback) + + return {self.OUTPUT: dest_id} + + def layerExtent(self, raster, sink, feedback): + rect = raster.extent() + geometry = QgsGeometry.fromRect(rect) + minx = rect.xMinimum() + miny = rect.yMinimum() + maxx = rect.xMaximum() + maxy = rect.yMaximum() + height = rect.height() + width = rect.width() + cntx = minx + width / 2.0 + cnty = miny + height / 2.0 + area = width * height + perim = 2 * width + 2 * height + + feat = QgsFeature() + feat.setGeometry(geometry) + attrs = [ + minx, + miny, + maxx, + maxy, + cntx, + cnty, + area, + perim, + height, + width, + ] + feat.setAttributes(attrs) + sink.addFeature(feat, QgsFeatureSink.FastInsert) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 8e062e8f664..9a8539cd641 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -70,6 +70,7 @@ from .Explode import Explode from .ExportGeometryInfo import ExportGeometryInfo from .ExtendLines import ExtendLines from .ExtentFromLayer import ExtentFromLayer +from .ExtentFromRasterLayer import ExtentFromRasterLayer from .ExtractNodes import ExtractNodes from .ExtractSpecificNodes import ExtractSpecificNodes from .FieldPyculator import FieldsPyculator @@ -222,6 +223,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider): ExportGeometryInfo(), ExtendLines(), ExtentFromLayer(), + ExtentFromRasterLayer(), ExtractNodes(), ExtractSpecificNodes(), FieldsCalculator(), diff --git a/python/plugins/processing/gui/TestTools.py b/python/plugins/processing/gui/TestTools.py index 1d8bf7cdd10..ca54882ef47 100644 --- a/python/plugins/processing/gui/TestTools.py +++ b/python/plugins/processing/gui/TestTools.py @@ -27,6 +27,7 @@ __copyright__ = '(C) 2013, Victor Olaya' __revision__ = '$Format:%H$' import os +import posixpath import re import yaml import hashlib @@ -81,7 +82,8 @@ def extractSchemaPath(filepath): if part == 'testdata' and not localpath: localparts = parts localparts.reverse() - localpath = os.path.join(*localparts) + # we always want posix style paths here + localpath = posixpath.join(*localparts) parts.append(part) diff --git a/python/plugins/processing/tests/testdata/expected/raster_extent.gfs b/python/plugins/processing/tests/testdata/expected/raster_extent.gfs new file mode 100755 index 00000000000..c8cd5ab33c1 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/raster_extent.gfs @@ -0,0 +1,66 @@ + + + raster_extent + raster_extent + + 3 + EPSG:4326 + + 1 + 18.66630 + 18.70360 + 45.77670 + 45.81170 + + + MINX + MINX + Real + + + MINY + MINY + Real + + + MAXX + MAXX + Real + + + MAXY + MAXY + Real + + + CNTX + CNTX + Real + + + CNTY + CNTY + Real + + + AREA + AREA + Real + + + PERIM + PERIM + Real + + + HEIGHT + HEIGHT + Real + + + WIDTH + WIDTH + Real + + + diff --git a/python/plugins/processing/tests/testdata/expected/raster_extent.gml b/python/plugins/processing/tests/testdata/expected/raster_extent.gml new file mode 100755 index 00000000000..1b84c0ed0ff --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/raster_extent.gml @@ -0,0 +1,29 @@ + + + + + 18.666297944245.7767014376 + 18.703597944245.8117014376 + + + + + + 18.6662979442,45.7767014376 18.7035979442,45.7767014376 18.7035979442,45.8117014376 18.6662979442,45.8117014376 18.6662979442,45.7767014376 + 18.6662979442 + 45.7767014376 + 18.7035979442 + 45.8117014376 + 18.6849479442 + 45.7942014376 + 0.00130549999999981 + 0.14459999999999 + 0.0349999999999966 + 0.0372999999999983 + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 39ebd6d0a99..570ba2d95a3 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -2845,7 +2845,7 @@ tests: name: Standard polygon from layer extent params: BY_FEATURE: false - INPUT_LAYER: + INPUT: name: polys.gml type: vector results: @@ -3228,3 +3228,14 @@ tests: OUTPUT: name: expected/execute_sql.gml type: vector + + - algorithm: qgis:polygonfromrasterextent + name: Polygon from raster extent + params: + INPUT: + name: dem.tif + type: raster + results: + OUTPUT: + name: expected/raster_extent.gml + type: vector