4 votos

Usar programáticamente la capa de plugin de Openlayers en Print Composer

He creado algunos PyQGIS funciones para configurar una impresión compositor, modificar el diseño (la adición de mapas, leyenda, título...) e imprimir el resultado como un archivo de imagen. Esto funciona bien con una o varias capas de vector - todos ellos se muestran en el archivo de imagen resultante.

Sin embargo, cuando trato de crear una impresión compositor de diseño que incluye una capa de Openlayers plugin, yo no conseguir el resultado esperado - la mayoría del tiempo la capa del mapa base que falta en todos y, a veces, algunos de los artefactos que se muestran.

El ejemplo de código siguiente debe ser inmediatamente ejecutable después de establecer el plotdir parámetro en la parte superior para un deseado de la carpeta de salida. Es

  • crea un nuevo proyecto de QGIS
  • crea dos capas de vector en la memoria, cada uno con un punto de la geometría en algún lugar
  • carga el OpenStreetMap mapa base de la OpenLayers plugin (haciendo clic en el QGIS menú Gui como no sé cómo cargar de otra manera)
  • pasa las dos capas de la memoria y la Openlayers capa a un recién creado impresión compositor, establece algunos diseños cosas (la leyenda, título...) y guarda el resultado en un archivo de imagen.

Cuando ejecuto este, la leyenda muestra los tres deseado capas, pero el lienzo está en blanco (yo esperaría a ver, al menos, el mapa base como el punto de geometrías son bastante pequeñas). Alguna sugerencia de cómo correctamente incluir un mapa base en una impresión compositor con PyQGIS? Lo probé en Windows 7 con QGIS 2.4 y 2.6.

El código:

# imports
from qgis.core import *
from qgis.utils import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *

plotdir = r'F:\\'

###################################
# function to start a new qgis project
###################################
def startNewProject():
    print('starting new project')
    iface.newProject(thePromptToSaveFlag = False)
    return


###################################
# function to create a memory layer and add it to the map layer registry
###################################
def createMemoryLayer(layerType, layerName, x, y):

    print 'create memory layer'

    myMemoryLayer = QgsVectorLayer(layerType, layerName, "memory")
    QgsMapLayerRegistry.instance().addMapLayer(myMemoryLayer, True)

    feat = QgsFeature()
    feat.setGeometry(QgsGeometry.fromPoint(QgsPoint(x,y)))
    (res, outFeats) = myMemoryLayer.dataProvider().addFeatures( [ feat ] )

    return myMemoryLayer


###################################
# function to load OpenLayers plugin layers and move it to the bottom of the layer tree
###################################
def loadOpenLayersPluginMap(mapProvider, openLayersMap):

    print 'loading openlayers plugin layer'

    webMenu = iface.webMenu() #get object of the Web menu of QGIS

    for webMenuItem in webMenu.actions(): #open the Web menu of QGIS and loop through the list of web plugins
        if 'OpenLayers plugin' in webMenuItem.text(): #look for OpenLayers plugin entry in the Web menu
            openLayersMenu = webMenuItem #and open it

            for openLayersMenuItem in openLayersMenu.menu().actions(): #open the OpenLayers plugin menu entry and loop through the list of map providers
                if mapProvider in openLayersMenuItem.text(): #if the desired mapProvider is found
                    mapProviderMenu = openLayersMenuItem #open its menu entry

                    for map in mapProviderMenu.menu().actions(): #loop through the list of maps for the opened provider
                        if openLayersMap in map.text(): #if the desired map entry is found
                            map.trigger() #click the entry to load the map as a layer

    # move the layer to the bottom of the TOC by putting all other layers above it (openlayers layer cannot be moved directly)
    root = QgsProject.instance().layerTreeRoot()
    for child in root.children():
        if isinstance(child, QgsLayerTreeLayer):
            childClone = child.clone()
            root.insertChildNode(0, childClone)
            root.removeChildNode(child)

    return


###################################
# function to zoom to the extent of a layer
###################################
def zoomToLayerExtent(layer):

    print 'zooming to layer '

    # set the desired layer as active and zoom to it
    iface.setActiveLayer(layer)
    iface.zoomToActiveLayer()

    return


###################################
# function to save a map layout as image file with map composer
###################################
def saveImagesWithPrintComposer(layers, extentLayer, plottitle, plotdir):

    print 'saving images with map composer'

    #######
    # set up layer set, extent and create a map renderer
    #######
    mapRenderer = QgsMapSettings() # new in QGIS 2.4 - replaces QgsMapRenderer()
    layerSet = [layer.id() for layer in layers] # create a list of all layer IDs that shall be part of the composition
    mapRenderer.setLayers(layerSet) # when using QgsMapRenderer, replace this with setLayerSet(layerset)
    mapRectangle = extentLayer.extent() # set extent to the extent of the desired layer
    mapRectangle.scale(2) # set scale as desired
    mapRenderer.setExtent(mapRectangle)
    mapRenderer.setOutputSize(QSize(1600,1200)) #when using QgsMapRenderer(), setOutputSize needs a second argument '[int] dpi'

    #######
    # create a composition and pass the renderer
    #######
    composition = QgsComposition(mapRenderer)
    composition.setPlotStyle(QgsComposition.Print)
    dpi = composition.printResolution()
    dpmm = dpi / 25.4
    width = int(dpmm * composition.paperWidth())
    height = int(dpmm * composition.paperHeight())

    #######
    # add map to the composition
    #######
    x, y = 0, 0
    w, h = composition.paperWidth(), composition.paperHeight()
    composerMap = QgsComposerMap(composition, x, y, w, h)
    composition.addItem(composerMap)

    #######
    # create title label
    #######
    composerLabel = QgsComposerLabel(composition)
    composerLabel.setText(plottitle)
    composerLabel.setFont(QFont("Arial",8))
    composerLabel.adjustSizeToText()
    composerLabel.setBackgroundEnabled(False)
    composition.addItem(composerLabel)
    composerLabel.setItemPosition(20,10)

    #######
    # create legend
    #######
    # create composer legend object
    composerLegend = QgsComposerLegend(composition)

    # set legend layers set (as the same as the map layerset)
    composerLegend.model().setLayerSet(mapRenderer.layers()) #when using QgsMapRenderer, use layerSet() instead of layers()

    # set titles
    composerLegend.setTitle('')
    newFont = QFont("Arial", 8)
    composerLegend.setStyleFont(QgsComposerLegendStyle.Title, newFont)
    composerLegend.setStyleFont(QgsComposerLegendStyle.Subgroup, newFont)
    composerLegend.setStyleFont(QgsComposerLegendStyle.SymbolLabel, newFont)

    # refresh legend
    composerLegend.updateLegend()

    # set feature count activated for all layers of the current map composition
    composerLayerItem = QgsComposerLayerItem()

    def activateFeatureCount(layer):
        composerLayerItem.setLayerID(layer.id())
        composerLayerItem.setShowFeatureCount(True)
        return

    [activateFeatureCount(layer) for layer in layers]

    # set legend background
    composerLegend.setBackgroundEnabled(False)

    # set legend position
    composerLegend.setItemPosition(20,20)

    # add legend to composition
    composition.addItem(composerLegend)

    #######
    # create image and initialize
    #######
    image = QImage(QSize(width, height), QImage.Format_ARGB32)
    image.setDotsPerMeterX(dpmm * 1000)
    image.setDotsPerMeterY(dpmm * 1000)
    image.fill(0)

    #######
    # Render composition
    #######
    painter = QPainter(image)
    sourceArea = QRectF(0, 0, composition.paperWidth(), composition.paperHeight())
    targetArea = QRectF(0, 0, width, height)
    composition.render(painter, targetArea, sourceArea)
    painter.end()

    #######
    # Save composition to image file (other extensions possible)
    #######
    image.save(plotdir + plottitle + '.png', 'png')

    return


###################################
# main function
###################################
def main():

    # start a new qgis project
    startNewProject()

    # create memory layers
    memoryLayer1 = createMemoryLayer('Linestring', 'MyFirstLayer', 2, 47) # some random coordinates
    memoryLayer2 = createMemoryLayer('Linestring', 'MySecondLayer', 9, 48) # some random coordinates

    # add openlayers plugin layer
    loadOpenLayersPluginMap('OpenStreetMap', 'OpenStreetMap')

    # zoom to first layer
    zoomToLayerExtent(memoryLayer1)

    # fetch all layers from the canvas
    allLayers = QgsMapLayerRegistry.instance().mapLayers().values()

    # save to file with print composer
    saveImagesWithPrintComposer(allLayers, memoryLayer1, 'MyPlot', plotdir)

    return

main()
print 'finished'

4voto

Como directamente a través de un Openlayers plugin en una capa a través de programación creado la impresión de la composición no es posible (ver la aceptación respuesta), este enfoque puede al menos ser utilizado para crear una imagen fija de archivo (jpg/png) a partir de una deseada Openlayers extensión del mapa. Tal vez esto es útil para alguien de alguna manera...

Se basa en la basemap2Image script que he modificado ligeramente para que pueda llamar desde dentro de un PyQGIS script que se ejecuta en la consola de Python. Para usarlo, necesitas las siguientes funciones auxiliares.

Llegar al borde de las coordenadas de un vector de la capa de medida:

###################################
# function to get coordinates of a layer extent
###################################
def getCoordinatesOfLayerExtent(layer):

    print 'getting coordinates of layer extent'
    layerRectangle = layer.extent()
    coordinates = [layerRectangle.xMinimum(), layerRectangle.yMinimum(), layerRectangle.xMaximum(), layerRectangle.yMaximum()]

    return coordinates

Llame al brindar una QgsVectorLayer objeto, por ejemplo,

vectorLayerCoordinates = getCoordinatesOfLayerExtent(myFavoriteVectorLayer)

Las coordenadas para pasar a la secuencia de comandos en EPSG:3857, de modo que si su vector de la capa de no uso que CRS de forma predeterminada, utilice la siguiente función para transformar las coordenadas en primer lugar:

###################################
# function to transform a set of coordinates from one CRS to another
###################################
def transformCoordinates(coordinates, fromCRS, toCRS):

    print 'transforming coordinates between crs'

    crsSrc = QgsCoordinateReferenceSystem(fromCRS)
    crsDest = QgsCoordinateReferenceSystem(toCRS)
    xform = QgsCoordinateTransform(crsSrc, crsDest)

    # convert list of coordinates to QgsPoint objects
    coordinatesAsPoints = [QgsPoint(coordinates[0], coordinates[1]), QgsPoint(coordinates[2], coordinates[3])]

    # do transformation for each point
    transformedCoordinatesAsPoints = [xform.transform(point) for point in coordinatesAsPoints]

    # transform the QgsPoint objects back to a list of coordinates
    transformedCoordinates = [transformedCoordinatesAsPoints[0].x(), transformedCoordinatesAsPoints[0].y(), transformedCoordinatesAsPoints[1].x(), transformedCoordinatesAsPoints[1].y()]

    return transformedCoordinates

Llame al brindar su lista de coordenadas, el origen de CRS y el destino deseado CRS, por ejemplo:

transformedCoordinates = transformCoordinates(coordinates, 4326, 3857)

Ahora, cree un archivo vacío llamado __init__.py junto a su script (esto es necesario para importar las funciones desde otros scripts dentro de la misma carpeta) y otro, en busca de esta (esta es la versión modificada de la basemap2Image secuencia de comandos):

#!/usr/bin/env python
# Nov 30, 2012
# Angel Joyce Torres Ramirez
# joys.tower@gmail.com
# I am not responsible for any use you give to this program, I did self-study and study purposes
# License:
# BaseMap2Image 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.

import sys
import signal
from qgis.core import *
from qgis.utils import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *
from functools import partial

def main(htmlDirectory, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8):

    print arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8

    def onLoadFinished(result):

        global repaintEnd
        global xMin
        global yMin
        global xMax
        global yMax
        global width
        global height
        global fileOut
        global fileFormat

        if not result:
            print "Request failed"
            web.close()

        action = "map.zoomToExtent(new OpenLayers.Bounds("+xMin+", "+yMin+", "+xMax+", "+yMax+"), true);"
        web.page().mainFrame().evaluateJavaScript(action)

        repaintEnd = None
        pauseReq()

        img = QImage(web.page().mainFrame().contentsSize(), QImage.Format_ARGB32_Premultiplied)
        imgPainter = QPainter(img)
        web.page().mainFrame().render(imgPainter)
        imgPainter.end()
        img = img.scaled(width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation )
        img.save( fileOut, fileFormat)
        print 'saving basemap'
        web.close()

    def pauseReq():
        global repaintEnd
        timerMax.start()
        while not repaintEnd:
            qApp.processEvents()
        timerMax.stop()


    def endTimer():
        global repaintEnd
        repaintEnd = True

    def resolutions():
        if olResolutions == None:
          resVariant = web.page().mainFrame().evaluateJavaScript("map.layers[0].resolutions")
          olResolutions = []
          for res in resVariant.toList():
            olResolutions.append(res.toDouble()[0])
        return olResolutions

    global layer
    layer = arg1
    global xMin
    xMin = arg2
    global yMin
    yMin = arg3
    global xMax
    xMax = arg4
    global yMax
    yMax = arg5

    global fileOut
    fileOut = arg6

    global fileFormat
    fileFormat="png"
    if fileOut.find(".") != -1:
        fileFormat = fileOut.split('.')[1]
    if fileFormat == "jpg":
        fileFormat = "jpeg"

    timeWait = 2000
    global width
    width = float(arg7)
    global height
    height= float(arg8)

    web = QWebView()
    timerMax = QTimer()

    global repaintEnd
    repaintEnd = None
    olResolutions = None

    timerMax.setSingleShot(True)
    timerMax.setInterval(int(timeWait))
    QObject.connect(timerMax, SIGNAL("timeout()"), endTimer)

    web.setFixedSize(width,height)
    pathUrl = "file:///%s/%s.html" % (htmlDirectory, layer)

    web.connect(web, SIGNAL("loadFinished(bool)"), onLoadFinished)

    web.load(QUrl(pathUrl))
    web.show()

Para llamar la función principal de esta secuencia de comandos, usted tiene que pasar a los siguientes argumentos:

  1. htmlDirectory: ruta de acceso a la html directorio, que era parte del github paquete (clonar el repositorio o descargar como zip, y luego copiar el código html del directorio de una conveniente ubicación)

  2. mapProvider: elija una de la html directorio donde corresponda .html el archivo existe, por ejemplo, 'google_streets' o 'osm'

  3. xMin: coordenadas de la capa de medida en EPSG:3857

  4. yMin: coordenadas de la capa de medida en EPSG:3857

  5. xMax: coordenadas de la capa de medida en EPSG:3857

  6. yMax: coordenadas de la capa de medida en EPSG:3857

  7. nombre de archivo: ruta de acceso y nombre de archivo del archivo de imagen de salida

  8. anchura: la anchura de la salida de archivo de imagen

  9. altura: la altura de la salida de archivo de imagen

Asegúrese de pasar todos los argumentos como cadenas desde el guión fue desarrollado originalmente para ser usado desde la línea de comandos, donde no hay otros tipos que existen. Un ejemplos de la llamada podría ser algo como esto:

basemap2Image.main(r'c:\PyQGIS\html', 'osm', str(transformedCoordinates[0]), str(transformedCoordinates[1]), str(transformedCoordinates[2]), str(transformedCoordinates[3]), r'c:\PyQGIS\basemap.png', '1600', '1200')

Por último, pero no menos importante, con el fin de ejecutar otras secuencias de comandos de Python desde un ejecutando la consola de Python script, tienes que decirle a la consola de dónde buscarlo. La forma más fácil y más permanente en la manera de hacer esto era la búsqueda para el archivo console.py en mi QGIS carpeta y añadir la línea sys.path.append('/path/to/basemap2Image script') en la sección importar (reiniciar el QGIS para hacer conciencia de que el cambio).

Una gran cantidad de trabajo para una función simple... sin embargo, cuando se ejecuta esta como se describió anteriormente, una ventana pop-up, mostrando un mapa del mundo en la que desea Openlayers proveedor, entonces el zoom a la medida, guardar el contenido del nombre de archivo y, a continuación, cierre la ventana de nuevo. Este archivo de imagen podría ser utilizado como un mapa base o cualquier otra cosa que en una impresión de la composición.

3voto

John Feminella Puntos 123

El complemento de openlayers no funciona con compositores de mapas, solo funciona en el lienzo del mapa principal. Consulte http://hub.qgis.org/issues/5827 , http://hub.qgis.org/issues/8824 o http://hub.qgis.org/issues/10992 , entre muchos otros ...

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X