[processing] Adapt algorithm 'Clip raster by mask' to use QgsProcessingRasterLayerDefinition, and thus, support clipping WMS layers by extent, setting a reference scale and DPI for the WMS (e.g., to preserve symbology/labeling).

This commit is contained in:
Germán Carrillo 2025-08-22 17:33:05 +02:00 committed by Nyall Dawson
parent 66d16fb836
commit 4eb52e461d
3 changed files with 74 additions and 5 deletions

View File

@ -194,6 +194,7 @@ class ClipRasterByExtent(GdalAlgorithm):
arguments = [] arguments = []
# If it's a WMS layer and scale/DPI were given, craft a XML description file
if inLayer.providerType() == "wms" and isinstance( if inLayer.providerType() == "wms" and isinstance(
parameters[self.INPUT], QgsProcessingRasterLayerDefinition parameters[self.INPUT], QgsProcessingRasterLayerDefinition
): ):
@ -206,7 +207,7 @@ class ClipRasterByExtent(GdalAlgorithm):
if inLayer.crs().isGeographic(): if inLayer.crs().isGeographic():
distanceArea = QgsDistanceArea() distanceArea = QgsDistanceArea()
distanceArea.setSourceCrs(inLayer.crs(), context.transformContext()) distanceArea.setSourceCrs(inLayer.crs(), context.transformContext())
distanceArea.setEllipsoid(context.ellipsoid()) distanceArea.setEllipsoid(inLayer.crs().ellipsoidAcronym())
width, height = GdalUtils._wms_dimensions_for_scale( width, height = GdalUtils._wms_dimensions_for_scale(
bbox, inLayer.crs(), scale, dpi, distanceArea bbox, inLayer.crs(), scale, dpi, distanceArea

View File

@ -20,10 +20,12 @@ __date__ = "September 2013"
__copyright__ = "(C) 2013, Alexander Bruy" __copyright__ = "(C) 2013, Alexander Bruy"
import os import os
import tempfile
from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtGui import QIcon
from qgis.core import ( from qgis.core import (
QgsDistanceArea,
QgsRasterFileWriter, QgsRasterFileWriter,
QgsProcessing, QgsProcessing,
QgsProcessingException, QgsProcessingException,
@ -37,9 +39,10 @@ from qgis.core import (
QgsProcessingParameterNumber, QgsProcessingParameterNumber,
QgsProcessingParameterBoolean, QgsProcessingParameterBoolean,
QgsProcessingParameterRasterDestination, QgsProcessingParameterRasterDestination,
QgsProcessingRasterLayerDefinition,
) )
from processing.algs.gdal.GdalAlgorithm import GdalAlgorithm from processing.algs.gdal.GdalAlgorithm import GdalAlgorithm
from processing.algs.gdal.GdalUtils import GdalUtils from processing.algs.gdal.GdalUtils import GdalConnectionDetails, GdalUtils
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
@ -261,7 +264,6 @@ class ClipRasterByMask(GdalAlgorithm):
raise QgsProcessingException( raise QgsProcessingException(
self.invalidRasterError(parameters, self.INPUT) self.invalidRasterError(parameters, self.INPUT)
) )
input_details = GdalUtils.gdal_connection_details_from_layer(inLayer)
mask_details = self.getOgrCompatibleSource( mask_details = self.getOgrCompatibleSource(
self.MASK, parameters, context, feedback, executing self.MASK, parameters, context, feedback, executing
@ -281,6 +283,72 @@ class ClipRasterByMask(GdalAlgorithm):
out = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) out = self.parameterAsOutputLayer(parameters, self.OUTPUT, context)
self.setOutputValue(self.OUTPUT, out) self.setOutputValue(self.OUTPUT, out)
# If it's a WMS layer and scale/DPI were given, craft a XML description file
if inLayer.providerType() == "wms" and isinstance(
parameters[self.INPUT], QgsProcessingRasterLayerDefinition
):
# If the scale is greater than 0, we'll have a QgsProcessingRasterLayerDefinition
param_def = parameters[self.INPUT]
scale = param_def.referenceScale
dpi = param_def.dpi
# For the input WMS's BBOX, if -te is given, then use -te, projected to input's
# crs. Otherwise, use mask layer's extent, projected to input's crs.
_bbox = None
if not bbox.isNull() and not bbox.isEmpty():
if bboxCrs != inLayer.crs():
_bbox = self.parameterAsExtent(
parameters, self.EXTENT, context, inLayer.crs()
)
else:
_bbox = bbox
else:
_bbox = self.parameterAsExtent(
parameters, self.MASK, context, inLayer.crs()
)
if _bbox.isNull() or _bbox.isEmpty():
raise QgsProcessingException(
"Invalid extent in mask layer ({}) and in target extent ({}).".format(
parameters[self.INPUT],
(
parameters[self.EXTENT]
if self.EXTENT in parameters
else "EXTENT"
),
)
)
distanceArea = None
if inLayer.crs().isGeographic():
distanceArea = QgsDistanceArea()
distanceArea.setSourceCrs(inLayer.crs(), context.transformContext())
distanceArea.setEllipsoid(inLayer.crs().ellipsoidAcronym())
width, height = GdalUtils._wms_dimensions_for_scale(
_bbox, inLayer.crs(), scale, dpi, distanceArea
)
wms_description_file_path = tempfile.mktemp("_wms_description_file.xml")
res_xml_wms, xml_wms_error = GdalUtils.gdal_wms_xml_description_file(
inLayer.publicSource(),
GdalUtils._get_wms_version(inLayer),
_bbox,
width,
height,
wms_description_file_path,
)
if not res_xml_wms:
raise QgsProcessingException(
"Cannot create XML description file for WMS layer. Details: {}".format(
xml_wms_error
)
)
input_details = GdalConnectionDetails(
connection_string=wms_description_file_path
)
else:
input_details = GdalUtils.gdal_connection_details_from_layer(inLayer)
arguments = ["-overwrite"] arguments = ["-overwrite"]
if sourceCrs.isValid(): if sourceCrs.isValid():
@ -291,7 +359,7 @@ class ClipRasterByMask(GdalAlgorithm):
arguments.append("-t_srs") arguments.append("-t_srs")
arguments.append(GdalUtils.gdal_crs_string(targetCrs)) arguments.append(GdalUtils.gdal_crs_string(targetCrs))
if not bbox.isNull(): if not bbox.isNull() and not bbox.isEmpty():
arguments.append("-te") arguments.append("-te")
arguments.append(str(bbox.xMinimum())) arguments.append(str(bbox.xMinimum()))
arguments.append(str(bbox.yMinimum())) arguments.append(str(bbox.yMinimum()))

View File

@ -732,7 +732,7 @@ class GdalUtils:
string = ( string = (
"<td>" "<td>"
+ QCoreApplication.translate("QgsWmsProvider", "WMS Version") + QCoreApplication.translate("QgsWmsProvider", "WMS Version")
+ "</td><td>(.+?)</td>" + "</td><td>(\\d\\.\\d(\\.\\d)?)</td>"
) )
result = re.search(string, provider.htmlMetadata()) result = re.search(string, provider.htmlMetadata())
if result: if result: