Skip to content

Commit

Permalink
Merge pull request #4998 from marioba/rasterize_provider
Browse files Browse the repository at this point in the history
[FEATURE] Added processing algorithm to convert map to raster
  • Loading branch information
m-kuhn committed Aug 8, 2017
2 parents 425aa30 + d9f71de commit a12346c
Show file tree
Hide file tree
Showing 4 changed files with 386 additions and 0 deletions.
2 changes: 2 additions & 0 deletions python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
Expand Up @@ -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
Expand Down Expand Up @@ -275,6 +276,7 @@ def getAlgs(self):
RandomPointsPolygons(),
RandomSelection(),
RandomSelectionWithinSubsets(),
RasterizeAlgorithm(),
RasterLayerStatistics(),
RegularPoints(),
ReverseLineDirection(),
Expand Down
325 changes: 325 additions & 0 deletions python/plugins/processing/algs/qgis/Rasterize.py
@@ -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)
7 changes: 7 additions & 0 deletions python/plugins/processing/gui/wrappers.py
Expand Up @@ -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]'
Expand Down Expand Up @@ -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.