EN | PT
O desafio
Em tempos na lista de discussão do qgis-pt alguém perguntou como dispor as coordenadas dos cantos do mapa no QGIS. Não estando (ainda) disponível tal funcionalidade, tentei chegar sem sucesso a uma solução que fosse de certa forma automática. Depois de remoer a ideia, e de ler um artigo do Nathan Woodrow, achei que a solução poderia passar por criar uma função para o construtor de expressões que pudesse ser usada em etiquetas no mapa.
A solução
Seguindo as indicações do referido artigo, comecei por criar um ficheiro userfunctions.py, que gravei na pasta .qgis2/python e, com uma ajuda do Nyall Dawson, escrevi o seguinte código.
from qgis.utils import qgsfunction, iface from qgis.core import QGis @qgsfunction(2,"python") def map_x_min(values, feature, parent): """ Returns the minimum x coordinate of a map from a specific composer. """ composer_title = values[0] map_id = values[1] composers = iface.activeComposers() for composer_view in composers(): composer_window = composer_view.composerWindow() window_title = composer_window.windowTitle() if window_title == composer_title: composition = composer_view.composition() map = composition.getComposerMapById(map_id) if map: extent = map.currentMapExtent() break result = extent.xMinimum() return result
Depois de correr o comando import userfunctions na consola python (Módulos > Consola python), já conseguia usar a função map_x_min() (disponível na categoria python) numa expressão para obter o valor mínimo em X.
Bastava então criar as restantes funções map_x_max(), map_y_min() e map_y_max(). Como parte do código seria repetida, decidi encapsulá-lo na função map_bound() que recebesse como argumentos o título do compositor de impressão e o id do mapa e me devolvesse a extensão do mesmo (sob a forma de um QgsRectangle).
from qgis.utils import qgsfunction, iface from qgis.core import QGis def map_bounds(composer_title, map_id): """ Returns a rectangle with the bounds of a map from a specific composer """ composers = iface.activeComposers() for composer_view in composers: composer_window = composer_view.composerWindow() window_title = composer_window.windowTitle() if window_title == composer_title: composition = composer_view.composition() map = composition.getComposerMapById(map_id) if map: extent = map.currentMapExtent() break else: extent = None return extent
Com essa função disponível podia usá-la internamente nas funções para devolver cada um dos mínimos e máximos em X e Y, tornando o código mais compacto e fácil de manter. Adicionei ainda ao código original alguns mecanismos para evitar erros.
@qgsfunction(2,"python") def map_x_min(values, feature, parent): """ Returns the minimum x coordinate of a map from a specific composer. Calculations are in the Spatial Reference System of the project. <h2>Syntax</h2> map_x_min(composer_title, map_id) <h2>Arguments</h2> composer_title - is string. The title of the composer where the map is. map_id - integer. The id of the map. <h2>Example</h2> map_x_min('my pretty map', 0) -> -12345.679 """ composer_title = values[0] map_id = values[1] map_extent = map_bounds(composer_title, map_id) if map_extent: result = map_extent.xMinimum() else: result = None return result @qgsfunction(2,"python") def map_x_max(values, feature, parent): """ Returns the maximum x coordinate of a map from a specific composer. Calculations are in the Spatial Reference System of the project. <h2>Syntax</h2> map_x_max(composer_title, map_id) <h2>Arguments</h2> composer_title - is string. The title of the composer where the map is. map_id - integer. The id of the map. <h2>Example</h2> map_x_max('my pretty map', 0) -> 12345.679 """ composer_title = values[0] map_id = values[1] map_extent = map_bounds(composer_title, map_id) if map_extent: result = map_extent.xMaximum() else: result = None return result @qgsfunction(2,"python") def map_y_min(values, feature, parent): """ Returns the minimum y coordinate of a map from a specific composer. Calculations are in the Spatial Reference System of the project. <h2>Syntax</h2> map_y_min(composer_title, map_id) <h2>Arguments</h2> composer_title - is string. The title of the composer where the map is. map_id - integer. The id of the map. <h2>Example</h2> map_y_min('my pretty map', 0) -> -12345.679 """ composer_title = values[0] map_id = values[1] map_extent = map_bounds(composer_title, map_id) if map_extent: result = map_extent.yMinimum() else: result = None return result @qgsfunction(2,"python") def map_y_max(values, feature, parent): """ Returns the maximum y coordinate of a map from a specific composer. Calculations are in the Spatial Reference System of the project. <h2>Syntax</h2> map_y_max(composer_title, map_id) <h2>Arguments</h2> composer_title - is string. The title of the composer where the map is. map_id - integer. The id of the map. <h2>Example</h2> map_y_max('my pretty map', 0) -> 12345.679 """ composer_title = values[0] map_id = values[1] map_extent = map_bounds(composer_title, map_id) if map_extent: result = map_extent.yMaximum() else: result = None return result
As funções ficaram disponíveis no construtor de expressões na categoria “Python” (podia ter-lhe dado outro nome qualquer) e as descrições das funções são transformadas em textos de ajuda para fornecer ao utilizador informação de como utilizar as funções.
Usando as funções recentemente criadas, foi fácil posicionar etiquetas junto dos cantos do mapa com as coordenadas dos mesmos. Qualquer alteração à extensão do mapa, reflecte-se nas etiquetas, podendo por isso ser usadas convenientemente com a funcionalidade de atlas.
O resultado destas funções pode ser usado com outras. Na imagem seguinte apresenta-se uma expressão para apresentar as coordenadas de forma mais compacta.
Havia um senão… Para as funções ficarem disponíveis, seria necessário importá-las manualmente em cada utilização do QGIS. Algo que não era prático. Novamente com a ajuda do Nathan, fiquei a saber que podemos importar módulos Python no arranque do QGIS colocando na pasta .qgis2/python um ficheiro com o nome startup.py com os comandos de importação. Para o meu caso bastou o seguinte.
import userfunctions
Conclusões
Fiquei bastante satisfeito com o resultado. A possibilidade do utilizador criar as usas próprias funções para usar em expressões vem mais uma vez demonstrar como é fácil personalizar e criar as minhas próprias ferramentas para QGIS. Já estou a matutar em mais aplicações para estar fantástica funcionalidade.
Os ficheiros Python com as funções criadas podem ser descarregados AQUI. Basta descompactar os dois ficheiros para a pasta .qgis2/python e reiniciar o QGIS, e as funções devem ficar disponíveis.
Caro Alexandre, muito legal!
Valeu pela dica desta possibilidade potencial de customização.
Saudações!
This excellent website really has all the information I needed about this
subject and didn’t know who to ask.
It works with QGis 3.0?
I haven’t tried, but I think it does.
I have had trying run it in QGis3 but I think it is not possible.
The answer is here…
https://raw.githubusercontent.com/klakar/QGIS_resources/master/collections/Geosupportsystem/python/expressions/userfunctions.py