mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-10-31 00:06:02 -04: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)
 |