2017-08-08 09:45:59 +02:00

276 lines
10 KiB
Python

# -*- coding: utf-8 -*-
"""
/***************************************************************************
QFieldSync processing provider
-------------------
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.core.outputs import OutputRaster
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,
QgsProcessingParameterRasterLayer,
QgsProcessingOutputRasterLayer,
QgsProcessingParameterRasterDestination
)
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):
"""
"""
# 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_LAYER = 'OUTPUT_LAYER'
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(
QgsProcessingParameterString(self.MAP_THEME,
description=self.tr(
'Map theme to render.'),
defaultValue=None, optional=True))
self.addParameter(
QgsProcessingParameterRasterLayer(self.LAYER, description=self.tr(
'Layer to render. Will only be used if the map theme is not '
'set. '
'If both, map theme and layer are not '
'set, the current map content will be rendered.'),
optional=True))
self.addParameter(
QgsProcessingParameterExtent(self.EXTENT, description=self.tr(
'The minimum extent to render. Will internally be extended to '
'be '
'a multiple of the tile sizes.')))
self.addParameter(
QgsProcessingParameterNumber(self.TILE_SIZE, self.tr('Tile size'),
defaultValue=1024))
self.addParameter(QgsProcessingParameterNumber(self.MAP_UNITS_PER_PIXEL,
self.tr(
'Map units per '
'pixel'),
defaultValue=100))
# We add a raster layer as output
self.addParameter(QgsProcessingParameterRasterDestination(
self.OUTPUT_LAYER,
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 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.parameterAsString(parameters, self.LAYER, context)
extent = self.parameterAsExtent(parameters, self.EXTENT,
context)
tile_size = self.parameterAsInt(parameters, self.TILE_SIZE, context)
mupp = self.parameterAsInt(parameters, self.MAP_UNITS_PER_PIXEL, context)
output_layer = self.parameterAsOutputLayer(parameters, self.OUTPUT_LAYER,
context)
# This probably affects the whole system but it's a lot nicer
osgeo.gdal.UseExceptions()
tile_set = TileSet(map_theme, layer, extent, tile_size, mupp, output_layer,
qgis.utils.iface.mapCanvas().mapSettings())
tile_set.render()
return {self.OUTPUT_LAYER: output_layer}
class TileSet():
"""
A set of tiles /home/mario/Dropbox/workspace/marioba/QGIS/python/plugins/processing/tests/testdata/
"""
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
# TODO: Check if file exists and update instead?
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_RGB32)
self.settings = QgsMapSettings()
self.settings.setOutputDpi(self.image.logicalDpiX())
self.settings.setOutputImageFormat(QImage.Format_RGB32)
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):
for x in range(self.x_tile_count):
for y in range(self.y_tile_count):
cur_tile = x * self.y_tile_count + y
num_tiles = self.x_tile_count * self.y_tile_count
self.renderTile(x, y)
def renderTile(self, x, y):
"""
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))
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'
for i in range(osgeo.gdal.GetDriverCount()):
driver = osgeo.gdal.GetDriver(i)
if driver.GetMetadataItem('DMD_EXTENSION') == extension[1:]:
return driver