Skip to content
Permalink
Browse files

Merge pull request #4998 from marioba/rasterize_provider

[FEATURE] Added processing algorithm to convert map to raster
  • Loading branch information
m-kuhn committed Aug 8, 2017
2 parents 425aa30 + d9f71de commit a12346cca4d09f01e55c05a3a238ebd65babc25c
@@ -109,6 +109,7 @@
from .RandomPointsPolygons import RandomPointsPolygons
from .RandomSelection import RandomSelection
from .RandomSelectionWithinSubsets import RandomSelectionWithinSubsets
from .Rasterize import RasterizeAlgorithm
from .RasterLayerStatistics import RasterLayerStatistics
from .RegularPoints import RegularPoints
from .ReverseLineDirection import ReverseLineDirection
@@ -275,6 +276,7 @@ def getAlgs(self):
RandomPointsPolygons(),
RandomSelection(),
RandomSelectionWithinSubsets(),
RasterizeAlgorithm(),
RasterLayerStatistics(),
RegularPoints(),
ReverseLineDirection(),
@@ -0,0 +1,325 @@
# -*- 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.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,
QgsProcessingParameterMapLayer,
QgsProcessingParameterRasterDestination,
QgsMessageLog,
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
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(
# TODO use QgsProcessingParameterMapLayer when
# the LayerWidgetWrapper class will be implemented
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,
minValue=0,
type=QgsProcessingParameterNumber.Double
))

# 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').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_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, 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)
@@ -754,6 +754,11 @@ def value(self):
return self.widget.currentData()


class LayerWidgetWrapper(WidgetWrapper):
def __init__(self):
raise NotImplementedError('Layer widget wrapper is not implemented yet')


class VectorWidgetWrapper(WidgetWrapper):

NOT_SELECTED = '[Not selected]'
@@ -1388,6 +1393,8 @@ def create_wrapper_from_class(param, dialog, row=0, col=0):
wrapper = VectorWidgetWrapper
elif param.type() == 'band':
wrapper = BandWidgetWrapper
elif param.type() == 'layer':
wrapper = LayerWidgetWrapper
else:
assert False, param.type()
return wrapper(param, dialog, row, col)

0 comments on commit a12346c

Please sign in to comment.
You can’t perform that action at this time.