mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-27 00:33:48 -05:00
320 lines
10 KiB
Python
320 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
/***************************************************************************
|
|
Rasterize.py
|
|
-------------------
|
|
begin : 2016-10-05
|
|
copyright : (C) 2016 by OPENGIS.ch
|
|
email : matthias@opengis.ch
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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. *
|
|
* *
|
|
***************************************************************************/
|
|
"""
|
|
|
|
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
|
|
|
|
from qgis.PyQt.QtGui import QImage, QPainter
|
|
from qgis.PyQt.QtCore import QSize
|
|
from qgis.core import (
|
|
QgsMapSettings,
|
|
QgsMapRendererCustomPainterJob,
|
|
QgsRectangle,
|
|
QgsProject,
|
|
QgsProcessingException,
|
|
QgsProcessingParameterExtent,
|
|
QgsProcessingParameterString,
|
|
QgsProcessingParameterNumber,
|
|
QgsProcessingParameterMapLayer,
|
|
QgsProcessingParameterRasterDestination,
|
|
QgsRasterFileWriter
|
|
)
|
|
|
|
import qgis
|
|
import osgeo.gdal
|
|
import os
|
|
import tempfile
|
|
import math
|
|
|
|
__author__ = 'Matthias Kuhn'
|
|
__date__ = '2016-10-05'
|
|
__copyright__ = '(C) 2016 by OPENGIS.ch'
|
|
|
|
# This will get replaced with a git SHA1 when you do a git archive
|
|
|
|
__revision__ = '$Format:%H$'
|
|
|
|
|
|
class RasterizeAlgorithm(QgisAlgorithm):
|
|
|
|
"""Processing algorithm renders map canvas to a raster file.
|
|
It's possible to choose the following parameters:
|
|
- Map theme to render
|
|
- Layer to render
|
|
- The minimum extent to render
|
|
- The tile size
|
|
- Map unit per pixel
|
|
- The output (can be saved to a file or to a temporary file and
|
|
automatically opened as layer in qgis)
|
|
"""
|
|
|
|
# Constants used to refer to parameters and outputs. They will be
|
|
# used when calling the algorithm from another algorithm, or when
|
|
# calling from the QGIS console.
|
|
|
|
OUTPUT = 'OUTPUT'
|
|
MAP_THEME = 'MAP_THEME'
|
|
LAYER = 'LAYER'
|
|
EXTENT = 'EXTENT'
|
|
TILE_SIZE = 'TILE_SIZE'
|
|
MAP_UNITS_PER_PIXEL = 'MAP_UNITS_PER_PIXEL'
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
def initAlgorithm(self, config=None):
|
|
"""Here we define the inputs and output of the algorithm, along
|
|
with some other properties.
|
|
"""
|
|
# The parameters
|
|
self.addParameter(
|
|
QgsProcessingParameterExtent(self.EXTENT, description=self.tr(
|
|
'Minimum extent to render')))
|
|
|
|
self.addParameter(
|
|
QgsProcessingParameterNumber(
|
|
self.TILE_SIZE,
|
|
self.tr('Tile size'),
|
|
defaultValue=1024, minValue=64))
|
|
|
|
self.addParameter(QgsProcessingParameterNumber(
|
|
self.MAP_UNITS_PER_PIXEL,
|
|
self.tr(
|
|
'Map units per '
|
|
'pixel'),
|
|
defaultValue=100,
|
|
minValue=0,
|
|
type=QgsProcessingParameterNumber.Double
|
|
))
|
|
|
|
map_theme_param = QgsProcessingParameterString(
|
|
self.MAP_THEME,
|
|
description=self.tr(
|
|
'Map theme to render'),
|
|
defaultValue=None, optional=True)
|
|
|
|
map_theme_param.setMetadata(
|
|
{'widget_wrapper': {
|
|
'class':
|
|
'processing.gui.wrappers_map_theme.MapThemeWrapper'}})
|
|
self.addParameter(map_theme_param)
|
|
|
|
self.addParameter(
|
|
QgsProcessingParameterMapLayer(
|
|
self.LAYER,
|
|
description=self.tr(
|
|
'Single layer to render'),
|
|
optional=True))
|
|
|
|
# We add a raster layer as output
|
|
self.addParameter(QgsProcessingParameterRasterDestination(
|
|
self.OUTPUT,
|
|
self.tr(
|
|
'Output layer')))
|
|
|
|
def name(self):
|
|
# Unique (non-user visible) name of algorithm
|
|
return 'rasterize'
|
|
|
|
def displayName(self):
|
|
# The name that the user will see in the toolbox
|
|
return self.tr('Convert map to raster')
|
|
|
|
def group(self):
|
|
return self.tr('Raster tools')
|
|
|
|
def tags(self):
|
|
return self.tr('layer,raster,convert,file,map themes,tiles,render').split(',')
|
|
|
|
# def processAlgorithm(self, progress):
|
|
def processAlgorithm(self, parameters, context, feedback):
|
|
"""Here is where the processing itself takes place."""
|
|
|
|
# The first thing to do is retrieve the values of the parameters
|
|
# entered by the user
|
|
map_theme = self.parameterAsString(
|
|
parameters,
|
|
self.MAP_THEME,
|
|
context)
|
|
|
|
layer = self.parameterAsLayer(
|
|
parameters,
|
|
self.LAYER,
|
|
context)
|
|
|
|
extent = self.parameterAsExtent(
|
|
parameters,
|
|
self.EXTENT,
|
|
context)
|
|
|
|
tile_size = self.parameterAsInt(
|
|
parameters,
|
|
self.TILE_SIZE,
|
|
context)
|
|
|
|
mupp = self.parameterAsDouble(
|
|
parameters,
|
|
self.MAP_UNITS_PER_PIXEL,
|
|
context)
|
|
|
|
output_layer = self.parameterAsOutputLayer(
|
|
parameters,
|
|
self.OUTPUT,
|
|
context)
|
|
|
|
tile_set = TileSet(map_theme, layer, extent, tile_size, mupp,
|
|
output_layer,
|
|
qgis.utils.iface.mapCanvas().mapSettings())
|
|
tile_set.render(feedback)
|
|
|
|
return {self.OUTPUT: output_layer}
|
|
|
|
|
|
class TileSet():
|
|
|
|
"""
|
|
A set of tiles
|
|
"""
|
|
|
|
def __init__(self, map_theme, layer, extent, tile_size, mupp, output,
|
|
map_settings):
|
|
"""
|
|
|
|
:param map_theme:
|
|
:param extent:
|
|
:param layer:
|
|
:param tile_size:
|
|
:param mupp:
|
|
:param output:
|
|
:param map_settings: Map canvas map settings used for some fallback
|
|
values and CRS
|
|
"""
|
|
|
|
self.extent = extent
|
|
self.mupp = mupp
|
|
self.tile_size = tile_size
|
|
|
|
driver = self.getDriverForFile(output)
|
|
|
|
if not driver:
|
|
raise QgsProcessingException(
|
|
u'Could not load GDAL driver for file {}'.format(output))
|
|
|
|
crs = map_settings.destinationCrs()
|
|
|
|
self.x_tile_count = math.ceil(extent.width() / mupp / tile_size)
|
|
self.y_tile_count = math.ceil(extent.height() / mupp / tile_size)
|
|
|
|
xsize = self.x_tile_count * tile_size
|
|
ysize = self.y_tile_count * tile_size
|
|
|
|
self.dataset = driver.Create(output, xsize, ysize, 3) # 3 bands
|
|
self.dataset.SetProjection(str(crs.toWkt()))
|
|
self.dataset.SetGeoTransform(
|
|
[extent.xMinimum(), mupp, 0, extent.yMaximum(), 0, -mupp])
|
|
|
|
self.image = QImage(QSize(tile_size, tile_size), QImage.Format_ARGB32)
|
|
|
|
self.settings = QgsMapSettings()
|
|
self.settings.setOutputDpi(self.image.logicalDpiX())
|
|
self.settings.setOutputImageFormat(QImage.Format_ARGB32)
|
|
self.settings.setDestinationCrs(crs)
|
|
self.settings.setOutputSize(self.image.size())
|
|
self.settings.setFlag(QgsMapSettings.Antialiasing, True)
|
|
self.settings.setFlag(QgsMapSettings.RenderMapTile, True)
|
|
|
|
if QgsProject.instance().mapThemeCollection().hasMapTheme(map_theme):
|
|
self.settings.setLayers(
|
|
QgsProject.instance().mapThemeCollection(
|
|
|
|
).mapThemeVisibleLayers(
|
|
map_theme))
|
|
self.settings.setLayerStyleOverrides(
|
|
QgsProject.instance().mapThemeCollection(
|
|
|
|
).mapThemeStyleOverrides(
|
|
map_theme))
|
|
elif layer:
|
|
self.settings.setLayers([layer])
|
|
else:
|
|
self.settings.setLayers(map_settings.layers())
|
|
|
|
def render(self, feedback):
|
|
for x in range(self.x_tile_count):
|
|
for y in range(self.y_tile_count):
|
|
if feedback.isCanceled():
|
|
return
|
|
cur_tile = x * self.y_tile_count + y
|
|
num_tiles = self.x_tile_count * self.y_tile_count
|
|
self.renderTile(x, y, feedback)
|
|
|
|
feedback.setProgress(int((cur_tile / num_tiles) * 100))
|
|
|
|
def renderTile(self, x, y, feedback):
|
|
"""
|
|
Render one tile
|
|
|
|
:param x: The x index of the current tile
|
|
:param y: The y index of the current tile
|
|
"""
|
|
painter = QPainter(self.image)
|
|
|
|
self.settings.setExtent(QgsRectangle(
|
|
self.extent.xMinimum() + x * self.mupp * self.tile_size,
|
|
self.extent.yMaximum() - (y + 1) * self.mupp * self.tile_size,
|
|
self.extent.xMinimum() + (x + 1) * self.mupp * self.tile_size,
|
|
self.extent.yMaximum() - y * self.mupp * self.tile_size))
|
|
|
|
job = QgsMapRendererCustomPainterJob(self.settings, painter)
|
|
job.renderSynchronously()
|
|
painter.end()
|
|
|
|
# Needs not to be deleted or Windows will kill it too early...
|
|
tmpfile = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
|
|
try:
|
|
self.image.save(tmpfile.name)
|
|
|
|
src_ds = osgeo.gdal.Open(tmpfile.name)
|
|
|
|
self.dataset.WriteRaster(x * self.tile_size, y * self.tile_size,
|
|
self.tile_size, self.tile_size,
|
|
src_ds.ReadRaster(0, 0, self.tile_size,
|
|
self.tile_size))
|
|
except Exception as e:
|
|
feedback.reportError(str(e))
|
|
finally:
|
|
del src_ds
|
|
tmpfile.close()
|
|
os.unlink(tmpfile.name)
|
|
|
|
def getDriverForFile(self, filename):
|
|
"""
|
|
Get the GDAL driver for a filename, based on its extension. (.gpkg,
|
|
.mbtiles...)
|
|
"""
|
|
_, extension = os.path.splitext(filename)
|
|
|
|
# If no extension is set, use .tif as default
|
|
if extension == '':
|
|
extension = '.tif'
|
|
|
|
driver_name = QgsRasterFileWriter.driverForExtension(extension[1:])
|
|
return osgeo.gdal.GetDriverByName(driver_name)
|