5  Parte III - Desenvolvimento intermédio

5.1 8. Trabalhar com projectos e camadas

5.1.1 8.1 QgsProject

A instância actual:

project = QgsProject.instance()

Adicionar camada:

layer = QgsVectorLayer(path, layer_name, "ogr")
if layer.isValid():
    project.addMapLayer(layer)

Nunca assuma que a camada carregou. Valide e registe a mensagem do fornecedor quando disponível.

5.1.2 8.2 Camada de memória

GeoClick Capture cria uma camada temporária quando não existe destino:

layer = QgsVectorLayer(
    "Point?crs=EPSG:4326",
    "Captured Points Log",
    "memory",
)
provider = layer.dataProvider()
provider.addAttributes([
    QgsField("id", QVariant.Int),
    QgsField("captured_at", QVariant.String),
    QgsField("lat", QVariant.Double),
    QgsField("lon", QVariant.Double),
])
layer.updateFields()
QgsProject.instance().addMapLayer(layer)

Uma camada de memória é útil para sessões temporárias. Para persistência imediata, prefira GeoPackage.

5.1.3 8.3 Adicionar feições

feature = QgsFeature(layer.fields())
feature.setGeometry(QgsGeometry.fromPointXY(point))
feature["id"] = next_id
feature["captured_at"] = timestamp
feature["lat"] = latitude
feature["lon"] = longitude

ok, created = layer.dataProvider().addFeatures([feature])
if not ok:
    raise RuntimeError("The point could not be added.")

Considere edição transaccional e undo/redo quando altera camadas existentes do utilizador. Para camadas geridas pelo plugin, a escrita directa pelo provider pode ser suficiente, desde que os IDs e campos sejam controlados.

5.2 9. Sistemas de referência e coordenadas

5.2.1 9.1 Nunca confundir coordenadas do mapa com latitude/longitude

O clique é devolvido no CRS do mapa. Se o projecto estiver em UTM, os valores podem ser Este/Norte em metros, não longitude/latitude.

project_crs = canvas.mapSettings().destinationCrs()
wgs84 = QgsCoordinateReferenceSystem("EPSG:4326")
transform = QgsCoordinateTransform(
    project_crs,
    wgs84,
    QgsProject.instance(),
)
wgs84_point = transform.transform(map_point)
lon = float(wgs84_point.x())
lat = float(wgs84_point.y())

5.2.2 9.2 Transformar para a camada de destino

Se a camada de destino tem outro CRS, transforme a geometria antes de gravar:

layer_transform = QgsCoordinateTransform(
    project_crs,
    layer.crs(),
    QgsProject.instance(),
)
layer_point = layer_transform.transform(map_point)

5.2.3 9.3 Guardar ambos os sistemas

Para auditoria, é útil guardar:

  • latitude e longitude em WGS 84;
  • X e Y no CRS do projecto;
  • identificador ou descrição do CRS;
  • geometria no CRS da camada de destino.

Esta estratégia foi adoptada pelo GeoClick Capture e facilita comparação com mapas, GPS e bases institucionais.

5.3 10. Leitura de GPX e escrita de formatos GIS

Um ficheiro GPX pode expor subcamadas:

  • waypoints;
  • routes;
  • route_points;
  • tracks;
  • track_points.

Nem todos os ficheiros possuem todas as subcamadas. Ausência não é, por si só, erro.

5.3.1 10.1 Abrir subcamada GPX

uri = f"{gpx_path}?type={layer_name}"
source = QgsVectorLayer(uri, layer_name, "gpx")
if not source.isValid() or source.featureCount() == 0:
    # Registar como missing/empty, não como falha fatal.
    return

5.3.2 10.2 Escolher formatos de saída

Uma tabela de configuração reduz condicionais:

OUTPUT_FORMATS = {
    "ESRI Shapefile": {
        "driver": "ESRI Shapefile",
        "extension": ".shp",
        "layer_creation_options": ["ENCODING=UTF-8"],
    },
    "GeoPackage": {
        "driver": "GPKG",
        "extension": ".gpkg",
        "layer_creation_options": [],
    },
    "GeoJSON": {
        "driver": "GeoJSON",
        "extension": ".geojson",
        "layer_creation_options": [],
    },
}

5.3.3 10.3 Limitações do Shapefile

O Shapefile é amplamente suportado, mas possui limitações:

  • nomes de campos curtos;
  • conjunto de ficheiros relacionados;
  • tipos de dados limitados;
  • dificuldades de encoding;
  • um tipo de geometria por camada.

Para dados modernos, GeoPackage é muitas vezes a melhor saída padrão. Contudo, mantenha Shapefile quando a interoperabilidade institucional exigir.

5.3.4 10.4 Proveniência

Ao fundir dados, adicione campos que identifiquem a origem:

source_file
source_path
source_layer

Sem proveniência, a fusão reduz rastreabilidade. O GPX Batch Converter inclui estes campos nos resultados fundidos.

5.4 11. Tarefas em segundo plano

Operações longas não devem executar no thread da interface. Um ciclo com centenas de ficheiros pode congelar o QGIS e levar o utilizador a encerrar a aplicação.

5.4.1 11.1 QgsTask

class ConversionTask(QgsTask):
    def __init__(self, files, callback):
        super().__init__("Convert files", task_can_cancel_flag())
        self.files = files
        self.callback = callback
        self.results = []
        self.error = None

    def run(self):
        try:
            for index, path in enumerate(self.files, start=1):
                if self.isCanceled():
                    return False
                self.convert_one(path)
                self.setProgress(index / len(self.files) * 100)
            return True
        except Exception as exc:
            self.error = exc
            return False

    def finished(self, result):
        self.callback(self, result)

Adicionar ao gestor:

QgsApplication.taskManager().addTask(task)

5.4.2 11.2 Regra do thread principal

No método run() de uma tarefa:

  • não altere widgets;
  • não adicione camadas ao projecto;
  • não aceda a objectos Qt que pertençam ao thread principal;
  • trabalhe com dados independentes, caminhos e APIs thread-safe.

No finished() ou callback:

  • actualize a interface;
  • adicione saídas ao projecto;
  • apresente o resumo.

5.4.3 11.3 Cancelamento

Cancelamento deve ser cooperativo. Verifique isCanceled() em intervalos curtos. Se existe processo externo activo, termine-o com timeout e depois force a paragem, registando falhas.

No GPX Batch Converter, a tarefa mantém referência ao processo GDAL actual, chama terminate(), aguarda e usa kill() apenas quando necessário.

5.5 12. Resultados, progresso e logs

Uma barra de progresso isolada não explica o que aconteceu. Para lotes, mantenha uma lista de resultados com:

source_file
layer
status
feature_count
output_path
message

Estados recomendados:

  • converted;
  • merged;
  • included;
  • missing_or_empty;
  • skipped_existing;
  • failed;
  • cancelled.

O resumo deve separar falhas reais de camadas ausentes. Isto evita que um GPX sem waypoints seja apresentado como erro.

5.5.1 12.1 Mensagens no QGIS

self.iface.messageBar().pushMessage(
    "Plugin",
    "Conversion completed.",
    level=success_level,
    duration=5,
)

Para diagnóstico técnico:

QgsMessageLog.logMessage(
    detailed_message,
    "My Plugin",
    level=Qgis.MessageLevel.Warning,
)

Não mostre dados sensíveis, tokens ou caminhos confidenciais em logs públicos.

5.6 13. Ferramentas de mapa

Para responder a cliques:

from qgis.gui import QgsMapToolEmitPoint

self.tool = QgsMapToolEmitPoint(self.canvas)
self.tool.canvasClicked.connect(self.handle_map_click)
self.canvas.setMapTool(self.tool)

Ao desactivar:

if self.canvas.mapTool() is self.tool:
    self.canvas.unsetMapTool(self.tool)

5.6.1 13.1 Identificar a feição sob o clique

QgsMapToolIdentify pode devolver a camada e a feição. Restrinja a pesquisa a camadas relevantes e visíveis quando possível. Guarde nome, ID da camada e ID da feição para auditoria.

5.6.2 13.2 Estado visual

A acção de captura deve ser verificável. O painel e a toolbar devem mostrar o mesmo estado. Mostre uma mensagem curta quando o modo é activado.

5.7 14. Snapping a vértices e segmentos

Snapping melhora precisão e evita pontos quase coincidentes.

Estratégia robusta:

  1. usar a configuração de snapping do projecto;
  2. se não existir correspondência, procurar camadas visíveis de linha e polígono;
  3. dar prioridade ao vértice mais próximo;
  4. usar o segmento mais próximo apenas quando nenhum vértice está dentro da tolerância;
  5. registar se houve snapping, o tipo e a distância.

Fluxo de captura do GeoClick Capture

5.7.1 14.1 Tolerância em pixels

A tolerância em pixels oferece experiência consistente em diferentes escalas. Converta para unidades do mapa:

tolerance_map = tolerance_pixels * canvas.mapUnitsPerPixel()

Quando a camada tem CRS diferente, transforme o ponto e uma distância de referência para estimar a tolerância na unidade da camada.

5.7.2 14.2 Auditoria do snapping

Campos úteis:

snapped        boolean
snap_type      vertex | segment | project snapping
snap_distance  distância em unidades do mapa

5.8 15. Pedidos de rede e geocodificação reversa

Plugins QGIS devem utilizar QgsNetworkAccessManager para respeitar proxy, autenticação e definições de rede da aplicação.

manager = QgsNetworkAccessManager.instance()
request = QNetworkRequest(QUrl(url))
reply = manager.get(request)
reply.finished.connect(lambda: self.handle_reply(reply))

5.8.1 15.1 Regras de um cliente responsável

  • identificar a aplicação no User-Agent;
  • respeitar limites do fornecedor;
  • adicionar timeout;
  • tratar redireccionamentos com segurança;
  • validar HTTP, rede, SSL e JSON;
  • armazenar cache;
  • evitar pedidos duplicados;
  • permitir desligar a funcionalidade;
  • manter a função principal independente da rede.

No GeoClick Capture, o ponto é sempre guardado primeiro. A geocodificação actualiza o campo de localização depois. Assim, falha de Internet não causa perda do registo.

5.8.2 15.2 Sem resultado não é falha de rede

Um serviço pode responder que não encontrou endereço. Trate separadamente:

  • falha de ligação;
  • limite HTTP 429;
  • rejeição 403;
  • erro do servidor;
  • resposta válida sem endereço.

Uma estratégia de fallback pode pedir níveis mais amplos: endereço, assentamento, cidade, província e país. Respeite o intervalo mínimo entre pedidos.

5.9 16. Preferências persistentes

QgsSettings guarda opções por utilizador:

settings = QgsSettings()
settings.setValue("my_plugin/operator", operator_name)
operator_name = settings.value("my_plugin/operator", "", type=str)

Use um prefixo exclusivo. Guarde apenas preferências, não dados sensíveis.

Exemplos:

  • última pasta de saída;
  • formato preferido;
  • geocodificação ligada/desligada;
  • nome do operador;
  • tolerância de snapping;
  • camada seleccionada, quando a referência puder ser restaurada com segurança.