Skip to content

Versão 5.2.0 - #848

Merged
phborba merged 50 commits into
masterfrom
dev
Aug 11, 2026
Merged

phborba merged 50 commits into
masterfrom
dev

Conversation

@phborba

@phborba phborba commented Aug 11, 2026 •

Copy link
Copy Markdown
Collaborator

5.2.0 - 2026-08-11

Novas Funcionalidades:

  • Novo algoritmo de bounding boxes ótimos por tiles (Optimal Tile Bounding Boxes): decompõe uma camada de tiles em bounding boxes otimizados minimizando NoData, usando compressão de coordenadas, detecção de componentes conexas por BFS, decomposição por faixas horizontais e fusão greedy com threshold de cobertura configurável. Processamento paralelo via ThreadPoolExecutor;
  • Nova CLI (dsgtools_cli) para executar algoritmos de processamento do DSGTools via linha de comando, integrada ao qgis_process: validação local de parâmetros antes de rodar (nome inexistente com sugestão por proximidade, obrigatório ausente, índice de enum fora da faixa), describe compacto, modo --dry-run, cache de contrato em disco e doctor que diagnostica o provider (plugin ausente, desabilitado ou carregado sem nenhum algoritmo) e aponta o conserto;
  • Novo algoritmo de identificação de gaps e overlaps em cobertura por XOR de bordas (edge XOR);

Melhorias:

  • Refatoração do AbstractDb e remoção da dependência da QtQSql (que implicava em instalação de biblioteca adicional no linux);
  • Melhoria na gestão de conexões do PostgisDb;
  • Remoção de funcionalidades não utilizadas e de diversos códigos mortos (removida gestão de bancos no servidor, dsgtools_admindb, gestão e carregamento de estilo, etc);

Correções de bug:

  • Corrige estatística de regras (Rule Statistics) no fluxo do SAP Operador: contexto de processamento passa a usar o projeto ativo para resolver as camadas de entrada;
  • Corrige barra de revisão: campos de ranking/visitado tratados como vazios além de nulos, e trata falha ao salvar ao marcar feições como não visitadas;
  • Corrige erro ao mesclar mais de uma camada no processo de desconstruir polígonos (Unbuild Polygons);
  • Corrige erro ao montar estruturas auxiliares do grafo quando há trechos com geometria nula;
  • Corrige validação de depressão e de ponto cotado na verificação de terreno (leitura do atributo de depressão, sentido do filtro de cota e mesclagem de curvas com atribuição divergente);
  • Corrige geração de ponto cotado com valor múltiplo da equidistância no processo de extração de pontos cotados;
  • Corrige generalização de curvas de nível: segmenta curvas fechadas antes de medir e filtrar (o filtro de tamanho mínimo media só a primeira parte da geometria), corrige a medição do perímetro em CRS geográfico e a atributação de índice e depressão, que saíam com valor fixo;
  • Corrige validação do terreno recompondo curvas fechadas segmentadas em dois arcos antes de validar;
  • Corrige vulnerabilidade de XXE na leitura de metadados XML do preparo de pacotes raster para o BDGEx e injeção de SQL na definição do SRID de bancos Geopackage;
  • Corrige ícones do plugin não carregando (faltava registrar o módulo de recursos Qt) e menu "About DSGTools" desaparecendo no macOS;

Automação de release corrigida

.github/workflows/release.yml falhava desde a versão 4.20.0 (todo run, sem exceção — conferido no histórico do Actions): pedia Python 3.8, removido dos runners do GitHub Actions. Atualizado para Python 3.12 + actions atualizadas.

Validei o empacotamento localmente com qgis-plugin-ci package (sem subir nada) antes de publicar de verdade, e achei mais dois problemas reais que estavam escondidos atrás da falha do Python — nunca tinham aparecido em nenhum log porque o job sempre morria antes de chegar neles:

  • metadata.txt: about= é multi-linha (formato válido, é assim que o QGIS Desktop espera), mas o parser do qgis-plugin-ci só lê o que está na própria linha da chave — abortava com "Mandatory key is missing in metadata: about". Corrigido deixando a primeira frase já na linha do about=.
  • DsgTools/resources_rc.py: arquivo morto (não importado por nada no código-fonte), conflitava com o resource que o qgis-plugin-ci compila do zero durante o empacotamento. Removido e adicionado ao .gitignore.

Depois dessas duas correções, qgis-plugin-ci package 5.2.0 roda limpo até o fim localmente.

🤖 Generated with Claude Code

dinizime and others added 30 commits March 31, 2026 12:47
… dependencies and using in-place editing with AABB conflict detection
…g, group-priority dissolution and frame-aware generalization; consolidate reclassify pixels versions into single algorithm; add TrataRaster processing algorithm
  Decomposes a tile polygon layer into optimized bounding boxes using
  coordinate compression, BFS connected-component detection (4-connectivity),
  horizontal-strip decomposition, and greedy merging with a configurable
  coverage threshold. Parallel processing via ThreadPoolExecutor.

  - new file: optimalTileBBoxAlgorithm.py
  - registered in DSGToolsProcessingAlgorithmProvider
  - PT-BR translations added to DsgTools_pt.ts
dinizime and others added 20 commits June 14, 2026 19:58
O qgis_process 4.0 ainda resolve o perfil legado QGIS/QGIS3, enquanto o
QGIS 4 Desktop usa QGIS/QGIS4 — desde o upgrade o CLI listava 0 algoritmos
(provider invisivel). O CLI agora define QGIS_CUSTOM_CONFIG_PATH para o
diretorio do QGIS 4 quando ele existe (override manual continua valendo) e
o doctor mostra o caminho usado.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WaxprCH2LaCcG5uA8sPPR
prepareLayerList com mergeOutputs=True e is_child_algorithm=False
retornava um QgsVectorLayer direto quando havia mais de uma camada,
pois runMergeVectorLayers devolve o objeto de camada (nao uma string).
O wrapping so ocorria para str, entao o chamador quebrava ao aplicar
[0] (TypeError: QgsVectorLayer object is not subscriptable).
Agora converte string para camada quando necessario e sempre envolve
em lista.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s auxiliares

Trechos com geometria nula/vazia produzem vertex_pos NULL no "extract specific
vertices", o que corrompia o índice da hashDict. Passa a pular esses nós e avisar
quantos foram ignorados.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
O atributo de depressão era lido mas quase não era usado:

- O heightRange dos nós era lido com terrainGraph[node], que no networkx é a
  adjacência e não os atributos do nó, então a leitura sempre devolvia None e a
  propagação de faixas por banda nunca rodava.
- A validação de ponto cotado nunca consultava a depressão: numa calota aceitava
  [h-threshold, h+threshold], a união dos dois sentidos, de forma que uma cota de
  95 dentro de um topo fechado por 100 passava batido. Agora a atribuição de
  depressão define o sentido da faixa.
- O filtro de múltiplos do intervalo truncava a cota com to_int, flagando 100.4
  como múltiplo de 10.
- O merge de linhas mandava o campo de depressão para a blacklist, então curvas
  com atribuição divergente eram mescladas e o valor sobrevivente saía da ordem de
  iteração. Os campos referenciados pela expressão passam a compor a chave.

Corrige também um falso positivo: a região externa ao anel mais externo tem grau 1
e curva fechada, e era lida como calota. A distinção agora é por contenção no anel.

A verificação de atribuição passa a cobrir toda curva fechada cujo sentido do
terreno seja determinável, e não apenas a mais interna, de modo que curvas de
depressão aninhadas mais externas também são validadas.

Remove ainda 439 linhas inalcançáveis do spatialRelationsHandler
(SpatialRelationsHandler.validateTerrainModel e sua cadeia exclusiva), substituídas
pelo TerrainModel em 2024 e sem chamadores desde então.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s testes

Acrescenta duas camadas de teste para o modelo de terreno, que não tinha nenhuma
em funcionamento:

- tests/test_terrainHandler.py: 25 testes de lógica pura, com grafo e bandas
  montados à mão (o __post_init__ do TerrainModel roda o pipeline inteiro de
  processing, inviável em teste unitário).
- dsgtools_cli/tests/test_terrain_algorithms.py: 8 testes que executam o algoritmo
  de ponta a ponta, headless, via dsgtools_cli. Todos falham no código anterior.

As flags são verificadas por quantidade e pelo texto do reason, não por golden de
geometria. Cada fixture contém uma única classe de erro, porque validate() para na
primeira encontrada — um fixture com erro de banda nunca chega a validar depressão.

Remove os golden de identifyterrainmodelerrorsalgorithm e seu bloco de parâmetros.
Estavam congelados desde 2020 e descreviam a implementação anterior: as mensagens
que esperavam só existiam em SpatialRelationsHandler.validateTerrainModel, e o bloco
ainda passava TOPOLOGY_RADIUS, parâmetro que não existe mais, sem passar
GROUP_BY_SPATIAL_PARTITION, obrigatório — o teste não chegava a executar.

Acrescenta tests/run_local.py para rodar os testes dentro do QGIS sem Docker e sem
instalar nada, com código de saída utilizável em automação, e um tests/README.md
que registra as duas formas de execução e o estado conhecido dos módulos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…istância

Um ponto cotado com o mesmo valor de uma curva de nível é erro, e o
identifyterrainmodelerrorsalgorithm o acusa. O gerador tinha duas proteções, mas
nenhuma era garantia e as três fugas eram silenciosas:

- findNearbyNonMultiplePixel devolvia o próprio pixel múltiplo quando não achava
  vizinho utilizável dentro do raio de busca, como num platô plano em cota redonda.
- Pelo mesmo caminho, um platô maior que o raio devolvia o múltiplo mesmo havendo
  pixel válido mais adiante.
- maskContourIntervalMultiples devolve o raster sem máscara quando todo pixel é
  múltiplo, para não zerar a região inteira.

E não havia filtro na escrita do sink, de modo que qualquer uma dessas fugas
chegava à saída. Em MDE inteiro isso não é hipotético: cerca de um pixel em cada
dez é múltiplo de 10.

findNearbyNonMultiplePixel passa a devolver None quando não há pixel utilizável, e
quem chama descarta a feição. Como a garantia precisa valer para todos os caminhos
de geração, a única escrita no sink passa a descartar cotas múltiplas, avisando
quantas e por quê — inclusive as vindas da máscara que desiste, mantida porque
zerar a região quebraria o cálculo a jusante.

Acrescenta tests/test_extractElevationPoints.py, com 14 testes que falham no
código anterior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…butação

O filtro de curvas fechadas media o perímetro com QgsDistanceArea no CRS de
origem e comparava com um mínimo em metros. Sem elipsoide no contexto — o padrão
do qgis_process e de projeto novo —, a medida sai em unidades de mapa, então uma
entrada geográfica devolvia frações de grau para qualquer anel. Como todo valor
ficava abaixo do mínimo, toda curva fechada era descartada em silêncio: num teste
com dois anéis de 13,9 km e 7,0 km em SIRGAS 2000, a saída vinha vazia. Como o
dado EDGV é normalmente geográfico, o caso comum era o destrutivo. A geometria
passa a ser projetada antes de medir, aproveitando a transformação que a
generalização já fazia, e o comprimento sai em metros.

O tamanho mínimo passa a ser derivado da escala, 12 mm de perímetro: 300 m em
1:25.000, 600 m em 1:50.000, 1200 m em 1:100.000 e 3000 m em 1:250.000. O
parâmetro em metros foi substituído por um de escala, seguindo a convenção do
extractElevationPoints. É mudança incompatível, mas o default anterior de 200 m
não correspondia a escala alguma.

A atributação gravava indice e depressao fixos:

- depressao saía sempre 2, destruindo a atribuição da entrada, de modo que o
  identifyterrainmodelerrorsalgorithm depois acusava toda depressão como não
  marcada. Passa a vir de uma expressão, e o agrupamento que precede a mesclagem
  passa a considerá-la: agrupar só por cota juntaria uma curva de depressão com
  uma normal de mesma cota e perderia a atribuição de uma das duas.
- indice saía sempre 2, então nenhuma curva era mestra. Passa a ser calculado a
  partir da equidistância, uma a cada cinco curvas.

dentro_massa_dagua segue fixo em 2, por ora.

Acrescenta dsgtools_cli/tests/test_contour_prep_algorithms.py, com 8 testes que
executam o algoritmo de ponta a ponta sobre fixtures em CRS geográfico.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A preparação entrega os anéis segmentados em dois arcos abertos, que é o formato
do dado que chega à validação na produção. Topo de morro e fundo de depressão são
reconhecidos por is_closed, então sem o anel fechado não há topo a validar, e um
erro real de atribuição de depressão passava como "tudo certo" — a validação não
falhava, ela silenciava.

O runDSGToolsMergeLines já rodava antes da validação, mas não recompõe um anel a
partir de dois arcos: com dois arcos formando um quadrado, ele devolve as duas
feições separadas tanto com allowClosed=True quanto com False. O par
native:dissolve pelos atributos que importam e native:mergelines devolve o anel
fechado, e é o que passa a ser usado. O contourid é atribuído depois da mesclagem,
para identificar a curva já inteira, já que o dissolve descarta campos e todo o
resto do fluxo depende dele.

Os campos preservados no dissolve vêm do getFieldsToPreserveOnMerge, de modo que
a expressão de depressão continua determinando o agrupamento.

Os fixtures de terreno tinham anéis intactos e escondiam o problema. Os dois
fixtures segmentados acrescentados reproduzem o dado real: sem esta correção,
depressao_nao_marcada_com_aneis_segmentados não acusa o erro.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ltro por parte

Ao final da preparação, cada curva fechada é segmentada em dois arcos: um anel
começa e termina no mesmo ponto, o que é uma autointerseção. Cada arco sai como
feição própria, porque mantê-los no mesmo multipart deixaria a feição ainda
começando e terminando no mesmo ponto, que é o que a segmentação existe para
desfazer.

O _geometryToPolyline devolvia apenas a primeira parte de uma geometria
multiparte, então o fechamento era decidido por ela enquanto o comprimento media a
geometria inteira — o filtro de perímetro mínimo comparava coisas diferentes. Dá
lugar a _toPolylineParts e _dropSmallClosedRings, que avaliam e medem parte a
parte.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ega o provider morto

Traz o dsgtools_cli para o padrao agent-first da DGEO. Ele ja atendia os dois
principios mais dificeis (contrato ao vivo do `qgis_process help --json`, sem
catalogo pre-gerado; annotations.json com so o conhecimento de dominio que a
introspeccao nao alcanca). O que faltava era a camada de saida, validacao e
guardrail.

describe COMPACTO por padrao (uma linha por parametro, opcoes com o indice,
exemplo renderizado como linha de comando pronta). O JSON antigo fica em
--json, byte a byte igual. O `list` irmao ja era compacto: as duas politicas
agora batem. De quebra, o OUTPUT deixou de aparecer duas vezes.

VALIDACAO LOCAL antes do run: nome de parametro inexistente (com sugestao por
proximidade), obrigatorio ausente, indice de enum fora da faixa e rotulo no
lugar do indice (START_SCALE=250k responde "o indice de '250k' e 2"). O erro
imprime o contrato dos parametros citados e sai com codigo 2 SEM executar.

O modo de falha que motivou isso, confirmado experimentalmente: com TIPO_INDEX
no lugar de INDEX_TYPE, o qgis_process IGNORA a chave em silencio, aplica o
padrao e falha depois com "Invalid MI/MIR format", um erro de dominio que nao
aponta para a causa. Nome errado nao grava e ninguem reclama.

CACHE DE CONTRATO em disco, sem o qual a validacao nao valeria a pena: buscar
o contrato custa ~2,9 s, entao validar de forma ingenua custaria +84% em cada
run. Medido nesta maquina: run validado com cache quente 2,99 s contra 3,08 s
do --no-check (menos de 1%), e describe de 2,89 s para 0,12 s. A impressao
digital combina mtime e tamanho do qgis_process, da pasta do plugin e do
metadata.txt. Limite assumido e documentado: nao detecta edicao de um .py de
algoritmo dentro do plugin (varrer a arvore custaria mais que economiza); em
desenvolvimento, use --refresh-cache ou cache --clear.

--dry-run valida pelo cache e mostra o payload exato, cada parametro com o
rotulo por tras do indice, e os opcionais omitidos.

DOCTOR que pega o modo de falha real. Antes ele dizia que estava tudo bem
enquanto o CLI estava inutil. Agora distingue plugin ausente, instalado mas nao
habilitado, e carregado com zero algoritmos, cada um com o conserto exato, e
sai 1. O `list` com zero algoritmos tambem parou de mentir.

Armadilha encontrada e encodada: `qgis_process plugins enable DsgTools` colado
num terminal comum grava no perfil LEGADO (QGIS/QGIS3), enquanto o CLI
redireciona para o do QGIS 4. O enable "da certo" e o list continua vazio. Por
isso o doctor imprime o QGIS_CUSTOM_CONFIG_PATH junto e oferece --fix, que usa
o perfil certo por construcao.

Testes: 30 antes, 66 depois (36 novos, sem QGIS, 0,09 s), zero falhas.
Dependencia zero mantida (stdlib pura).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Adiciona [flake8] no setup.cfg: max-line-length=88, ignora W503/E203
  (conflitam com formatação do black) e F401 em __init__.py (re-export).
- Roda black em todos os módulos do plugin (115 arquivos). Só formatação,
  nenhuma mudança de lógica. Compilação verificada em todos os arquivos
  tocados.
- F401 (imports não usados, 1780 ocorrências) fica pendente: autoflake
  se mostrou inseguro nesse repo por causa do padrão de sondagem de
  dependência opcional (try: import X / except ImportError: raise ...),
  onde o import "não usado" é intencional. Precisa de passe filtrado
  ou manual, feito à parte.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Detecção via pyflakes, remoção via reescrita AST própria (não autoflake:
a ferramenta se mostrou insegura para este repo -- inseria `pass` espúrio
ao dividir imports com vírgula, e gutava o padrão de sondagem de
dependência opcional usado no código:

    try:
        from scipy.interpolate import splprep, splev
    except ImportError:
        raise QgsProcessingException(...)

onde o import "não usado" é intencional, só serve pra levantar ImportError
com mensagem amigável).

O fixer:
- protege qualquer Import/ImportFrom dentro de um bloco try (não mexe);
- respeita comentário `# noqa`;
- remove só os nomes de fato não usados, mantendo o resto do import;
- verifica compile() antes de gravar, nunca escreve arquivo quebrado;
- rodado em 3 passes até estabilizar (1º pass revela imports que só
  apareciam "usados" por causa de um import duplicado mais abaixo no
  arquivo, sombreando o primeiro -- ex: circle.py tinha `QtGui` importado
  duas vezes, pyflakes só reportava a redefinição).

Resultado: F401 1780 -> 10 (9 são os imports protegidos de sondagem
scipy/rasterio, propositalmente intocados; 1 é ui_create_inom_dialog.py,
arquivo gerado, fora do escopo). Nenhum outro código flake8 piorou
(conferido antes/depois). py_compile limpo em todos os 657 arquivos.
Rodado black depois para reformatar as linhas reescritas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- dsg_tools.py: faltava o import de fato do módulo de recursos Qt
  compilado (resources.py). Só existia o comentário
  "# Initialize Qt resources from file resources_rc.py" sem o import
  correspondente, então qInitResources() nunca era chamado e todo
  ícone carregado via ":/plugins/DsgTools/icons/..." saía em branco
  (sem erro, sem exceção). Bug pré-existente, não relacionado à
  migração para QGIS4 -- confirmado presente até no commit anterior
  a qualquer mudança desta sessão.

- aboutAndFurtherInfoGuiManager.py: no macOS, o Qt detecta automaticamente
  texto de QAction como "About ..." / "Options" / "Preferences" e move a
  ação pro menu de aplicativo nativo do Cocoa em vez de deixá-la no menu
  do plugin -- comportamento que não existe no Windows. Sem
  setMenuRole(NoRole) explícito, "About DSGTools" e "DSGTools' Options"
  simplesmente somem do menu DSGTools no Mac.

Testado e confirmado pelo usuário: ícones voltaram, menu About aparece
no macOS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- CHANGELOG.md: fecha o ciclo dev 5.1.2 como 5.2.0 (data do release),
  abre 5.3.0 - dev para o próximo ciclo.
- DsgTools/metadata.txt: version=5.2.0 e changelog= atualizado no
  mesmo formato/indentação já usado.
- README.md: master passa a apontar 5.2.0, dev passa a apontar 5.3.0.
- DsgTools/CLAUDE.md: documenta o padrão de preenchimento do changelog
  (usado para gerar as entradas acima).
- .github/workflows/release.yml: corrige o job de deploy, que falhava
  desde a versão 4.20.0 -- pedia Python 3.8, removido dos runners do
  GitHub Actions ("Version 3.8 with arch x64 not found"), então o
  qgis-plugin-ci nunca chegava a rodar em nenhum release publicado.
  Atualiza para Python 3.12 e actions/checkout@v4 + setup-python@v5.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ndia

Rodei qgis-plugin-ci localmente (package, sem upload) pra validar a release
5.2.0 antes de publicar de verdade, já que todo run do release.yml sempre
falhava antes de chegar nessa etapa. Achei duas falhas reais escondidas atrás
da falha do Python:

- metadata.txt: about= é multi-linha (formato válido, é assim que o QGIS
  Plugin Manager espera pra exibir texto longo), mas o parser do
  qgis-plugin-ci é ingênuo -- só lê o que está na própria linha da chave,
  então via about como vazio e abortava com "Mandatory key is missing in
  metadata: about". Corrigido colocando a primeira frase na própria linha
  de about= (o resto continua multi-linha, sem perda pro QGIS).

- DsgTools/resources_rc.py: arquivo morto, não importado por nada no código
  fonte (só sobrava em .pyc velho e no translation.pro). O qgis-plugin-ci
  compila o .qrc do zero durante o empacotamento e recusa sobrescrever um
  arquivo já versionado com esse nome exato -- removido.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Registra no CLAUDE.md as regras de versionamento que o usuário explicou
(Y par/ímpar = lançada/dev, Z incrementa por mudança, inclusive correção
de bug sobre versão já lançada) e as duas armadilhas reais achadas ao
validar o empacotamento da 5.2.0 localmente antes do release de verdade
(about= precisa da primeira frase na própria linha; resources_rc.py não
pode ficar versionado). .gitignore ganha entrada pros artefatos de build
do qgis-plugin-ci (resources_rc.py e *.zip da raiz).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@phborba
phborba merged commit 3bb7394 into master Aug 11, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants