A primeira parte do projeto Financiamento colaborativo para a integração de dados marinhos no QGIS chegou ao fim, graças às contribuições de ORANGE Marine, Geoconceptos Uruguay, Janez Avzec e Gaetan Mas. Nossos mais sinceros agradecimentos a eles. Portanto, estamos publicando o conteúdo final dessa parte do trabalho.
Devido ao volume do resultado, estamos publicando-o em duas partes: a primeira diz respeito à criação e ao gerenciamento do banco de dados, a segunda à simbologia S57 no QGis.
O formato S57 é complexo e não está totalmente em conformidade com os padrões de formato GIS. Sua conversão com o GDAL é, portanto, complexa ou incompleta. Neste artigo, entraremos em detalhes sobre a transformação completa do S57 em tabelas de arquivos do Geopackage.
Tipos de geometria
As “camadas” do S57 são classes de objetos. Por exemplo, as diferentes áreas de terra são codificadas na classe de objeto LNDARE.
A definição dessa classe de objeto é:
Geo object: Land area (LNDARE) (P,L,A)
Attributes: CONDTN OBJNAM NOBJNM STATUS INFORM NINFOM
Embora todos os objetos LNDARE tenham os mesmos atributos, o mesmo não pode ser dito sobre o tipo de geometria. A informação (P,L,A) indica que pontos, linhas e polígonos podem ser encontrados nessa classe de objeto. Ao contrário dos padrões do GIS, os três tipos de geometria coexistem na mesma classe de objeto.
Seja qual for o formato que escolhermos para integrar ao QGis, precisaremos criar uma camada para cada tipo de geometria. Se não fizermos isso, o GDAL criará o tipo de camada com base na primeira entidade encontrada durante a conversão. Se ela for do tipo ponto, a camada criada será do tipo ponto e as entidades linhas e polígonos serão ignoradas. Da mesma forma, se a primeira entidade for do tipo linha, os pontos e polígonos serão ignorados.
Também deve ser observado que o formato S57 não tem restrições sobre classes de objetos: se não houver nenhuma classe de objeto na área coberta pelo arquivo S57, não haverá nada sobre ela no arquivo (nenhuma camada vazia). Da mesma forma, se houver apenas um tipo de geometria presente, mesmo que todos os três tipos sejam possíveis, não haverá nenhum vestígio dos outros tipos de geometria.
Portanto, o processamento de um arquivo S57 com ogr2ogr deve ser dividido em três estágios, um para cada tipo de geometria. As opções a seguir permitem que você processe cada classe de objeto S57 selecionando apenas um tipo de geometria:
-where « OGR_GEOMETRY=’POINT’ or OGR_GEOMETRY=’MULTIPOINT’ »
-where « OGR_GEOMETRY=’LINESTRING’ or OGR_GEOMETRY=’MULTILINESTRING’ »
-where « OGR_GEOMETRY=’POLYGON’ or OGR_GEOMETRY=’MULTIPOLYGON’ »
Para determinados tipos de driver, o GDAL permite que você crie prefixos nas tabelas de saída. Nesse caso, você poderia criar todas as tabelas (pontos, linhas, polígonos) em um único geopackage, prefixando-as com pt_, li_ e pl_, por exemplo. O problema é que o driver S57 do GDAL não permite essa opção. Portanto, precisamos criar três geopacotes separados, nos quais as tabelas serão criadas de acordo com o tipo de geometria. Cada geopacote conterá uma tabela com o mesmo nome, mas com uma geometria diferente. Embora essa seja a solução mais simples, não é a mais elegante nem a mais prática. Aqui, usaremos três geopacotes de importação para os comandos ogr2ogr nos quais importaremos tabelas de um arquivo S57. Nós os chamaremos de PointsENC, LinesENC e PolysENC. Também criaremos um único geopackage chamado ENC para todo o banco de dados. Para cada mapa ENC, importaremos seu conteúdo para os três geopacotes de importação usando ogr2ogr e, em seguida, executaremos scripts Python para atualizar o banco de dados do geopacote ENC com as novas tabelas importadas. Temos de usar scripts Python em vez de consultas e funções SQL porque a versão SQL do SQLite, usada para geopacotes, é muito limitada e não permite a criação de procedimentos ou funções.
Se você tiver um único arquivo S57 para importar, o processo é o seguinte:
- Criação do geopacote ENC vazio.
- Carregar classes de objetos de pontos no geopacote pointsENC usando ogr2ogr
- Carregar as classes de objeto Lines no geopackage LinesENC usando ogr2ogr
- Carregar as classes de objeto Polygon no geopackage PolysENC usando ogr2ogr
- Remoção de tabelas vazias dos três geopacotes e adição de atributos map_enc e scale…
- Clonagem das tabelas dos três geopacotes de importação para o geopacote ENC, prefixando as tabelas pointsENC com pt_, as tabelas LinesENC com li_ e as tabelas PolysENC com pl_.
Se você tiver um lote de arquivos S57 para carregar simultaneamente, o processo é o seguinte:
- Carregue as classes de objeto Point no geopackage pointsENC com ogr2ogr, excluindo tabelas vazias e adicionando o nome do arquivo enc e a escala aos atributos de todas as tabelas.
- Carregue as classes de objeto Lines no geopackage LinesENC com ogr2ogr, excluindo tabelas vazias e adicionando o nome do arquivo enc e a escala aos atributos de todas as tabelas.
- Carregar classes de objeto Polygon no geopackage PolysENC usando ogr2ogr, excluindo tabelas vazias e adicionando o nome do arquivo enc e a escala aos atributos de todas as tabelas.
- Atualização de tabelas existentes no geopacote ENC a partir dos três geopacotes de importação ET.
- Clonagem de tabelas dos três geopacotes de importação que não estavam presentes no geopacote ENC,
- Detecção e exclusão de quaisquer duplicatas resultantes da atualização de tabelas no geopacote ENC (opcional)
Processamento de sondagens batimétricas
As sondas batimétricas apresentam vários problemas na conversão de formatos. O primeiro é que o valor da profundidade não está contido em um atributo, mas como Z na geometria (XYZ). O segundo é que as sondas não são do tipo Ponto, mas do tipo Multiponto. Para obter o valor das sondas diretamente em um campo de atributo, é necessário adicionar dois parâmetros à linha de comando do ogr2ogr:
Recuperação de identificadores “pai”
Alguns objetos podem ser agrupados por um objeto pai não geográfico. Por exemplo, os setores de incêndio são codificados com um setor por registro na tabela “LIGHTS”. Os diferentes setores do mesmo incêndio não têm nenhum atributo específico que possa indicar que eles correspondem à mesma entidade. Essa informação está contida em um registro “pai”. Para recuperar esse identificador como um atributo da tabela LIGTHS, é necessário adicionar uma opção à linha de comando:
Essa opção cria um atributo name_rcid que é comum a todos os setores no mesmo semáforo, mas também cria uma série de campos (name_rcnm,ORNT,USAG,MASK).
Processamento do tipo “Lista”
Além dos tipos de campo tradicionais (inteiro, real, texto), o formato S57 usa um tipo especial: listas de cadeias de caracteres ou inteiros. Esses tipos de campo são encontrados no PostgreSQL, mas não nos shapefiles e no geopackage.
Na ausência de opções específicas, haverá mensagens de aviso indicando que o formato não é compatível e o conteúdo desses campos será ignorado. Para evitar a perda do conteúdo, você precisa adicionar a extensão :
Essa opção usa uma lista {…,…} e gera uma string de texto com um primeiro número indicando o número de elementos na lista, dois pontos e, em seguida, os elementos da lista separados por vírgulas:
{3} -> ‘(1:3)’
{3,1} -> ‘(2:3,1)’
Isso inevitavelmente complica um pouco o processamento desses campos, mas as informações não são perdidas. Por outro lado, o driver GDAL apresenta um problema para gerenciar o formato String resultante.
Em primeiro lugar, não há problema com os atributos dos objetos S57. O campo na tabela de geopacotes é do tipo “Text”, que permite a criação de uma cadeia de caracteres de qualquer comprimento.
No entanto, para “parent identifiers”, a definição usada pelo driver é “Text()”, ou seja, uma cadeia de caracteres com um comprimento máximo. Esse comprimento é determinado a priori pelo conteúdo do primeiro registro processado. Em outras palavras, é apenas por acaso que você tem um campo com o comprimento necessário para incluir todos os valores no arquivo.
Em seguida, você verá mensagens do tipo:
WARNING : Value of field ‘NAME_RCID’ has 371 characters, whereas maximum allowed is 30
A solução não é complicada, mas envolve uma boa quantidade de trabalho. Comece com a premissa de que, para a exibição clássica de cartões ENC, isso não representa um problema e você pode simplesmente ignorar essas mensagens de aviso.
Entretanto, se você precisar de um processamento mais avançado que inclua essas informações, essa é a única maneira que encontrei até o momento:
Fluxo de trabalho para fazer upload de um único arquivo S57
Criação do geopacote ENC
Vamos criar um geopackage vazio que receberá as tabelas importadas e, em seguida, atualizações sucessivas quando outros arquivos S57 forem carregados.
No painel QGis Explorer, clique com o botão direito do mouse em Geopackages e selecione Create a database (Criar um banco de dados)
Na janela que se abre, digite o nome do geopacote, deixe a opção padrão para o nome da tabela e selecione No geometry (Sem geometria) em Geometry type (Tipo de geometria).
Haga clic en Aceptar para que el nuevo geopackage vacío se añada a las conexiones del geopackage de QGis. Es aconsejable borrar la tabla ENC vacía creada al crear el geopackage. Para ello, abra el gestor de bases de datos de QGis desde el menú principal, sitúese sobre la tabla ENC del geopackage ENC.gpkg y haga clic con el botón derecho del ratón para seleccionar Borrar…
comandos ogr2ogr para crear geopaquetes de importación
Con todos estos elementos en mente, he aquí las tres líneas de comandos ogr2ogr para crear las tablas de los geopaquetes de importación:
Para executar essas linhas de comando, basta abrir a janela do shell do OSGeo4W:
A janela Shell é aberta
Digite as linhas de comando, tomando cuidado para inserir o caminho e o nome do arquivo com a extensão .000.
O resultado será uma série de tabelas criadas em cada geopackage de importação. Entretanto, algumas tabelas ficarão completamente vazias. Se o tipo de geometria for possível para uma classe de objeto, a tabela será criada, mas se não houver ocorrências no arquivo S57 processado, a tabela não terá registros.
Uso de scripts Python para gerenciar o banco de dados
Usaremos o console QGis Python para todas as operações de gerenciamento do banco de dados.
Para abri-lo, acesse o menu Extensões -> Console Python. Os painéis do console Python serão abertos:
Para executar um script Python, carregue o código no painel Editor. Você pode copiar e colar ou usar o ícone Diretório para apontar para o arquivo .py. A indentação é crucial para scripts Python, portanto, escolha a segunda opção.
Remova as tabelas vazias e adicione o nome e a escala do mapa
Como vimos anteriormente, ao executar comandos ogr2ogr, são criadas tabelas vazias para camadas em que não há geometria solicitada na linha de comando. Portanto, removeremos essas tabelas vazias e aproveitaremos o script Python para adicionar dois atributos a todas as tabelas: o nome do mapa ENC e sua escala.
No que diz respeito ao nome, pode ser útil na manutenção futura do banco de dados poder selecionar as entidades correspondentes a um arquivo de origem S57. Essa informação não está contida em nenhum atributo do arquivo S57. Portanto, temos de inseri-la manualmente.
O mesmo se aplica à escala do mapa. Embora um atributo seja fornecido no S57 para inserir a escala do mapa “em papel”, o atributo CSCALE na tabela M_CSCL, é muito raro que essa tabela e seu atributo estejam presentes nos arquivos S57.
Embora o nome do mapa seja de uso relativo, o mesmo não pode ser dito da escala, pois esse é um critério essencial na busca por duplicatas. De fato, quando entidades de vários arquivos S57 são misturadas, os dados de diferentes escalas coexistirão de forma mais ou menos feliz.
O código Python a seguir exclui tabelas vazias de um esquema de importação e adiciona dois atributos (enc_chart e scale) a todas as tabelas:
Tenha o cuidado de respeitar a indentação do código Python. Para simplificar a tarefa, baixe o arquivo .py diretamente deste link: delete_empty_tables_in_geopackages.py.
Para executá-lo, certifique-se de alterar os parâmetros de entrada do script:
Você só precisará inserir os geopackage_paths na primeira vez que executar o script. Por outro lado, o nome do mapa ENC e a escala precisarão ser alterados quando cada novo arquivo S57 for importado.
No final dessa execução, você terá os novos atributos em cada tabela
Fluxo de trabalho para carregar vários arquivos S57 ao mesmo tempo
As linhas de comando nos parágrafos anteriores pressupõem que você tenha um único arquivo S57 para carregar. Mas você pode carregar vários arquivos simultaneamente. Ao usar as opções -append -update, você pode processar todos os arquivos .000 em um diretório em um loop. Em seguida, você terá todos os arquivos carregados nos geopacotes de importação.
Diferentemente do carregamento de um único arquivo, ao carregar um lote de arquivos, precisamos incluir a adição do nome do arquivo e da escala no loop de transcodificação S57->geopackage. Para fazer isso, usaremos um script python que adiciona os atributos enc_chart e scale às tabelas criadas pelo comando ogr2ogr e as preenche com o nome do arquivo correspondente e uma escala definida pelo produtor. O script também exclui todas as tabelas vazias no geopackage de importação.
Agradecemos a Janez AVSEC pela ideia de usar a tabela DSID para recuperar a escala de compilação de dados.
A chamada para cada arquivo .bat tem o seguinte formato:
.\fichier.bat “diretório que contém os arquivos S57 a serem carregados” “nome do geopacote de importação”
Por exemplo:
.\mass_load_s57_polys.bat “c:/enc_db/enc000” “c:/enc_db/polys.gpkg”
Os arquivos .bat para essa tarefa são os seguintes:
Para importar tabelas de tipos de pontos:
Você perceberá que, além do comando ogr2ogr para importar camadas de pontos, importamos duas tabelas sem geometria: DSID e C_AGGR. A tabela DSID contém dois atributos com o nome do arquivo S57 e a escala de compilação de dados. Essa tabela será usada pelo script Python (update_geopackage_dsid.py) para inserir o arquivo S57 original e a escala de compilação em cada tabela do geopackage.
Para importar tabelas do tipo linha:
Para importar tabelas de polígonos:
Os arquivos .bat contêm o comando ogr2ogr e uma linha para execução de um script Python, update_geopackage_dsid.py. Certifique-se de alterar o caminho desse script no arquivo .bat para o caminho de sua escolha. Esse script adiciona os dois atributos enc_chart e scale a todas as tabelas do geopackage.
O código python usado é o seguinte:
Você poderia criar um único arquivo .bat para todos os três processos, mas precisaria verificar os resultados para cada tipo de geometria.
Você pode baixar os arquivos .bat e o código Python clicando aqui.
Continua, independentemente do número de arquivos carregados
Clonagem e/ou adição de tabelas importadas ao geopacote ENC
Para o primeiro carregamento do S57, o script Python a seguir cria todas as tabelas presentes nos três geopacotes de importação. Para cargas subsequentes, se a tabela já existir no geopacote ENC, os registros da tabela no geopacote de importação serão adicionados aos registros já presentes no ENC. Se, por outro lado, a tabela ainda não existir, ela será criada e preenchida com registros do geopacote de importação. No arquivo do geopacote ENC, as tabelas são prefixadas com pt_, li_ e pl_ para indicar seu tipo de geometria.
A clonagem é realizada usando o seguinte código:
Você pode fazer o download do arquivo .py aqui: clone_or_append_tables_with_prefix.py.
Gerenciamento de duplicatas
Quando dois mapas ENC são sobrepostos, as entidades presentes na zona de sobreposição serão inseridas tantas vezes quantas forem as sobreposições. Para evitar duplicatas e mais, o banco de dados deve ser limpo após cada importação de um arquivo S57.
Mas há dois tipos diferentes de duplicação. Por um lado, as áreas em que dois mapas de escala semelhante se sobrepõem produzirão duplicatas “reais” que precisam ser limpas. Por outro lado, a sobreposição de mapas de escalas muito diferentes precisa ser tratada de forma diferente. O mapa menos detalhado (escala pequena) terá uma seleção de dados do mapa mais detalhado (escala grande). Nesse segundo caso, não é aconselhável remover essas informações.
O código Python abaixo removerá as duplicatas “reais”. Para outras duplicatas, recomendamos o uso de filtros QGis. Dependendo de suas necessidades, você pode usar o atributo “scale” (escala) que adicionamos ao excluir as tabelas vazias para definir quais dados são exibidos no seu mapa e quais não são.
Nesse exemplo, somente os recursos correspondentes a uma escala entre 12500 e 45000 serão exibidos em seu mapa.
Essa operação corresponde à filtragem de uma camada, mas… o que você faz quando tem 130 camadas exibidas? Para evitar que você tenha de executar essa operação 130 vezes, aqui está um código Python que permite filtrar todas as camadas do projeto:
E outro para remover o filtro de todas as camadas exibidas:
Faça o download dos códigos python aqui.
O código a seguir cria uma tabela temporária (temp_table) para cada tabela no geopackage ENC. Em seguida, ele preenche essa tabela com os registros separados com base no LNAM. Em seguida, ele exclui a tabela original e renomeia a tabela temporária com o nome da tabela original.
Esse tratamento se aplica a todas as tabelas S57, exceto
- as tabelas de luzes (LIGHTS) e sondagens batimétricas (SOUNDG), em que o atributo LNAM não é útil. Na verdade, cada setor de incêndio tem o mesmo LNAM e name_rcid, mas corresponde a uma cor e a um setor geográfico diferentes. Da mesma forma, o LNAM e o name_rcid das sondagens batimétricas são os mesmos para todos os pontos de sondagem da entidade multiponto original. No primeiro caso, precisamos procurar cores e setores duplicados para o mesmo name_rcid e, para as sondagens, precisamos encontrar as sondagens com o mesmo XY em sua geometria.
- Tabelas SEAARE, LNDRGN e ADMARE que cobrem vastas áreas e que são divididas de acordo com a área de cobertura do mapa, mas mantendo o mesmo LNAM. Nesse caso, mesmo que o LNAM seja o mesmo, os polígonos não têm a mesma forma e área.
- las tablas DSID y C_AGGR, que no tienen LNAM y que, por principio, no pueden tener duplicados.
Você pode fazer download do arquivo .py neste link: supprime_doublons.py. Não se esqueça de alterar os caminhos e os nomes dos arquivos para que correspondam aos seus. Observe também a linha :
if table_name not in [“pl_SEAARE”, “pl_LNDRGN”, “pl_ADMARE”, “layer_styles”, “natsurf”,”DSID”,”C_AGGR”]:
Em princípio, você terá apenas as tabelas prefixadas e as tabelas layer_styles e natsurf. Essa linha impede que pl_SEAARE, pl_LNDRGN e pl_ADMARE sejam processadas, mas, mais importante, impede que as tabelas layer_styles e natsurf que usamos para simbologias sejam esvaziadas. Se você precisar criar tabelas diferentes daquelas do arquivo s57 no geopackage, será necessário levar em conta os nomes dessas tabelas nesse script, adicionando-os a essa linha de exceções.