Mapas ENC en QGis con Postgis(1)

Este tema comprende dos artículos. El primero trata de la importación de ficheros ENC S57 en una base de datos Postgresql/postgis. El segundo trata de la creación de una simbología automática equivalente a las cartas náuticas. Puede descargar todos los scripts de este artículo en https://www.nasca.ovh/downloads/fichiers_enc_postgis1.7z o vaya directamente a GItHub: https://github.com/SigEtTerritoires/enc_postgis

Le recomendamos que descomprima todos los archivos sql (.sql) y batch (.bat) en un directorio de su elección, teniendo en cuenta que para utilizarlos deberá cambiar el directorio utilizado en los scripts por el nombre de su directorio.
Para la simbología, como verás más adelante, podrás elegir entre crear un directorio por defecto :C:/nautical ((recomendado), o un directorio de tu elección.

El formato S57 y el SIG

Si va a empezar a trabajar con archivos S57 en un SIG, hay algunas cosas que debe tener en cuenta para navegar sin problemas.

En primer lugar, la estructura de los ficheros S57 no se corresponde con las estructuras adoptadas en los SIG.

En un SIG tienes un objeto geográfico que está representado por una tabla con dos tipos de información: la geometría de las entidades del objeto y los atributos de estas entidades.

Si tiene otros objetos con geometrías idénticas, la información geométrica se duplica, una vez en cada tabla.

En el formato S57, el objetivo principal es optimizar el almacenamiento de la información y, por tanto, no duplicarla. Si un objeto tiene una entidad puntual, se creará un punto. Si otros objetos tienen entidades situadas en este punto, se utilizará la referencia del punto ya creado. De este modo, un punto sólo se describe una vez en el fichero. Lo mismo ocurre con las polilíneas y las superficies. Por tanto, un fichero S57 tendrá una serie de tablas con información geométrica, denominadas «primitivas»:

  • IsolatedNode (puntos)
  • ConnectedNode (puntos)
  • Edge (polilíneas)
  • Face (polígonos)

La tabla de atributos de los distintos objetos S57 sólo contiene los atributos de los objetos.

Tipos de geometría

Las «capas» S57 son clases de objetos. Por ejemplo, las diferentes zonas terrestres se codifican en la clase de objeto LNDARE.

La definición de esta clase de objeto es :

Geo objeto: Superficie terrestre (LNDARE) (P,L,A)
Atributos: CONDTN OBJNAM NOBJNM STATUS INFORM NINFOM

Aunque todos los objetos LNDARE tienen los mismos atributos, no ocurre lo mismo con el tipo de geometría. La información (P, L, A) indica que en esta clase de objetos se pueden encontrar puntos, líneas y polígonos. A diferencia de las normas SIG, los tres tipos de geometría coexisten dentro de la misma clase de objetos.

Para pasar del formato S57 a una base de datos Postgresql/Postgis, utilizaremos la librería GDAL, incluida con QGis.

Las operaciones con GDAL se realizarán en la línea de comandos en una ventana de OSGeo4W.

La sintaxis básica para procesar un archivo S57 (extensión .000) e importarlo a una base de datos Postgtresql/Postgis es la siguiente:

ogr2ogr -f PostgreSQL PG: «dbname=“postgis_34_sample” host=“localhost” port=“5434” user=“postgres” password=“psw” active_schema=“schema”» Archivo .000

Veremos las diferentes opciones a utilizar y su función, pero por ahora añadiremos las opciones -skipfailures -append -update.

ogr2ogr -skipfailures -append –update  -f PostgreSQL PG:"dbname='postgis_34_sample' host='localhost' port='5434' user='postgres' password='psw' active_schema='schema'" fichier.000

La primera permite que el procesamiento continúe aunque se detecten errores. Las dos siguientes nos permiten no sobrescribir las tablas postgresql de salida si existen, sino actualizarlas añadiendo los datos del fichero S57 de entrada.

Sea cual sea el formato elegido para la integración en QGis, tendremos que crear una capa para cada tipo de geometría. Si no lo hacemos, GDAL creará el tipo de capa basándose en la primera entidad encontrada durante la conversión. Si es de tipo punto, la capa creada será de tipo punto y las entidades líneas y polígonos serán ignoradas. Del mismo modo, si la primera entidad es de tipo línea, se ignorarán los puntos y polígonos.

También hay que tener en cuenta que el formato S57 no tiene restricciones sobre las clases de objetos: si no hay ninguna clase de objeto en el área cubierta por el fichero S57, no habrá nada sobre él en el fichero (ninguna capa vacía). Del mismo modo, si sólo hay un tipo de geometría presente, aunque los tres tipos sean posibles, no habrá rastro de los otros tipos de geometría.

Por consiguiente, el tratamiento de un fichero S57 con ogr2ogr debe dividirse en tres etapas, una para cada tipo de geometría. Las siguientes opciones permiten procesar cada clase de objeto S57 seleccionando un solo tipo de geometría::

-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 controlador, GDAL permite crear prefijos en las tablas de salida. En este caso podrías crear todas las tablas (puntos, líneas, polígonos) en un único esquema prefijándolas con pt_, li_ y pl_, por ejemplo. El problema es que el controlador S57 de GDAL no permite esta opción. Por tanto, tendremos que crear tres esquemas distintos, en los que se crearán las tablas en función del tipo de geometría. Cada esquema contendrá una tabla con el mismo nombre pero con una geometría diferente.

Aquí utilizaremos tres esquemas de importación para los comandos ogr2ogr, en los que importaremos tablas de un fichero S57. Los llamaremos pointsenc, linesenc y polysenc. También crearemos un esquema llamado ENC para la base de datos completa. Para cada mapa ENC importaremos su contenido en los tres esquemas de importación con ogr2ogr y luego ejecutaremos consultas SQL para actualizar la base de datos del esquema ENC con las nuevas tablas importadas. Tanto si tiene que cargar un único archivo S57 como un lote de archivos S57 simultáneamente, el proceso es el siguiente:

  1. Cargar clases de objetos Point en el esquema pointsENC con ogr2ogr
  2. Carga de clases de objetos Lines en el esquema LinesENC con ogr2ogr
  3. Carga de clases de objetos Polygon en el esquema PolysENC con ogr2ogr
  4. Eliminar tablas vacías de los tres esquemas
  5. Actualización de tablas existentes en el esquema ENC a partir de los tres esquemas de importación
  6. Clonación de tablas de los tres esquemas de importación que no estaban presentes en el esquema ENC
  7. Supresión de tablas de los tres esquemas de importación

Tratamiento de las sondas batimétricas

Las sondas batimétricas plantean varios problemas durante la conversión del formato. El primero es que el valor de profundidad no está contenido en un atributo, sino como Z en la geometría (XYZ). El segundo es que las sondas no son de tipo Punto, sino Multipunto. Para obtener el valor de las sondas directamente en un campo de atributo, es necesario añadir dos parámetros a la línea de comandos de ogr2ogr:

-oo SPLIT_MULTIPOINT=ON -oo ADD_SOUNDG_DEPTH=ON

El primero convierte las entidades multipunto en puntos individuales, y el segundo añade un campo de atributo «DEPTH» a la tabla de salida con el valor Z de la geometría.

Recuperación de identificadores «padre

Algunos objetos pueden agruparse por un objeto padre no geográfico. Por ejemplo, los sectores de incendio se codifican con un sector por registro en la tabla «LUCES». Los distintos sectores de un mismo incendio no tienen ningún atributo particular que pueda indicar que corresponden a la misma entidad. Esta información figura en un registro «padre». Para recuperar este identificador como atributo de la tabla «LIGTHS», es necesario añadir una opción a la línea de comandos:

-oo RETURN_LINKAGES=O

Esta opción crea un atributo name_rcid común a todos los sectores de un mismo semáforo, pero también crea una serie de campos (name_rcnm,ORNT,USAG,MASK).

Tratamiento de tipo “lista”

Además de los tipos de campo tradicionales (entero, real, texto), el formato S57 utiliza un tipo especial: las listas de cadenas de caracteres o enteros. Estos tipos de campo se encuentran en PostgreSQL, pero no en shapefiles y geopackage. Por lo tanto, no es necesario transformar las listas S57 en cadenas de caracteres de la misma manera que para estos formatos. De hecho, se desaconseja totalmente, ya que complicaría el procesamiento de los atributos de tipo lista.

Gestión de la información duplicada

Cuando carga varios mapas ENC en la misma base de datos, puede encontrarse con una duplicación de información cuando la cobertura de dos o más mapas se solapa.

Estos duplicados pueden ser de dos tipos diferentes:

Duplicados «verdaderos», en los que todos los atributos son iguales. Son poco frecuentes porque resultan de la superposición de dos superposiciones cartográficas de escala muy similar. Sin embargo, también pueden ser el resultado de un error al recargar un archivo S57 duplicado. Estos duplicados pueden eliminarse.

Duplicados «falsos», en los que la información de la capa está duplicada pero los identificadores del registro no son necesariamente idénticos. Se producen cuando se cargan en la misma base de datos zonas cartografiadas a diferentes escalas. Además, dependiendo de la escala, la misma información (por ejemplo, un obstáculo) puede tener una geometría diferente (punto en el mapa, área en otro). Este tipo de duplicaciones no deben suprimirse, sino gestionarse.

Para gestionar estas duplicaciones, y también para garantizar que la información se muestra correctamente, es necesario añadir atributos de mapa a las capas. Una vez integradas en una capa, las entidades procedentes de archivos diferentes no tienen ningún atributo que permita rastrearlas hasta su fuente.

Hemos incluido el código necesario en el procedimiento de importación SQL para añadir el nombre del fichero, la escala de compilación de datos y la finalidad del gráfico a todas las tablas del esquema.

En cuanto al nombre, puede ser útil en el mantenimiento posterior de la base de datos para poder seleccionar las entidades correspondientes a un fichero fuente S57.

Si bien el nombre del mapa tiene una utilidad relativa, no puede decirse lo mismo de la escala, ya que se trata de un criterio esencial en la búsqueda de duplicados. Cuando se mezclan entidades procedentes de varios ficheros S57, los datos de diferentes escalas coexistirán más o menos felizmente.

Además de la escala, hay un dato que puede ser muy útil: la finalidad del mapa. Se trata de un valor comprendido entre 1 y 6 y corresponde al objetivo principal del mapa:

  • 1: Visión general
  • 2: General
  • 3: Costero
  • 4: Aproximación
  • 5: Puerto
  • 6 : Atraque

Hemos importado 4500 archivos S57 en la base de datos del proyecto utilizando PostgreSQL/Postgis.
Los valores mínimo y máximo de la escala de todos los mapas para el objetivo 5 son 3000 y 60000. Los valores mínimo y máximo de escala para el objetivo 6 son 2500 y 15000. Está claro que los valores de escala de los mapas más detallados se encuentran dentro de los mapas de tipo 5.

He aquí el resultado para todos los objetivos:

Tabla de objetivos y escalas
Purpose min_scale max_scale
1 325000 10000000
2 100000 1534076
3 50000 600000
4 12500 150000
5 3000 60000
6 2500 15000

La tabla DSID para archivos S57 contiene dos atributos: el nombre del archivo S57 y la escala de compilación de datos, así como el atributo «propósito». Este atributo se encuentra en la misma tabla DSID utilizada para recuperar la escala con el nombre DSID_INTU.

Como los comandos ogr2ogr sólo cargan tablas espaciales por defecto, necesitaremos ejecutar un comando especial solicitando que se cargue la tabla DSID:

ogr2ogr -skipfailures -append -update -f PostgreSQL PG: «dbname… « DSID

Importación de «primitivas”

Un fichero S57 tiene, por tanto, una serie de tablas que contienen información geométrica, denominadas «primitivas»:

  • IsolatedNode (puntos)
  • ConnectedNode (puntos)
  • Edge (polilíneas)
  • Face(polígonos)

La tabla de atributos de los distintos objetos S57 sólo contiene los atributos de los objetos.

Lo que complica la tarea es que hay dos atributos que se refieren a las geometrías: posacc (la precisión estimada de la posición, un valor cuantitativo) y quapos (calidad de la posición, una variable cualitativa).

Estos dos atributos se encuentran en las tablas primitivas.

Para pasar de la estructura S57 a una estructura SIG (shapefile, geopackage, postgis) utilizamos la biblioteca GDAL y su comando ogr2ogr.

Este comando crea tablas SIG a partir de la estructura S57, creando una tabla por cada objeto S57, asignando la geometría correspondiente de las tablas de primitivas a cada entidad y añadiendo los atributos del objeto S57 a cada entidad. La traza de las primitivas utilizadas para la geometría de cada entidad puede encontrarse en el campo NAME_RCID de las tablas SIG, siempre que se hayan añadido las opciones -oo «RETURN_LINKAGES=ON» -oo «LNAM_REFS=ON» a la línea de comandos de ogr2ogr.

La siguiente figura muestra una capa de tipo punto. El valor indicado en el campo NAME_RCID es el RCID del punto utilizado en la tabla IsolatedNode.

table de type point

La siguiente figura muestra un ejemplo de capa de tipo lineal. Los valores indicados en el campo NAME_RCID son los de los RCID de las polilíneas utilizadas en la tabla Edge.

table de type polyligne

La siguiente figura muestra un ejemplo de capa de tipo poligonal. Los valores indicados en el campo NAME_RCID son los de las polilíneas utilizadas en la tabla Edge.

table de type polygone

Para recuperar los atributos QUAPOS y POSACC de cada entidad en las tablas de tipo punto, necesitamos recuperar los valores del punto IsolatedNode y asignarlos a las tablas de los distintos objetos ENC.

Si los identificadores fueran directamente los RCID de las tablas ENC, podríamos hacer un join entre cada tabla (soundg, obstrn,…) y IsolatedNode. Pero como se puede ver en las imágenes anteriores, el atributo NAME_RCID es de tipo stringlist, lo que bloquea esta solución. Por lo tanto, hemos desarrollado una consulta SQL que realiza este trabajo al cargar los datos de los esquemas de importación en el esquema ENC.

Esquemas PostgreSQL

Para crear tablas de importación en un esquema específico, es necesario utilizar la opción active_schema de ogr2ogr. Con esta opción, PostgreSQL permite crear tablas con el mismo nombre en varios esquemas diferentes. Sólo hay una excepción, el esquema Público. Si se utiliza un nombre de tabla en este esquema, se ignora la opción active_schema. Por lo tanto, debe tener cuidado de no crear tablas S57 en el esquema Público. Si esto ocurre, tendrá que eliminarlas manualmente una a una.

Creación con una consulta SQL

La siguiente consulta crea los cuatro esquemas necesarios:

createschemas.sql

CREATE SCHEMA IF NOT EXISTS encm
AUTHORIZATION pg_database_owner;
GRANT ALL ON SCHEMA encm TO pg_database_owner;
CREATE SCHEMA IF NOT EXISTS pointsenc
AUTHORIZATION pg_database_owner;
GRANT ALL ON SCHEMA pointsenc TO pg_database_owner;
CREATE SCHEMA IF NOT EXISTS linesenc
AUTHORIZATION pg_database_owner;
GRANT ALL ON SCHEMA linesenc TO pg_database_owner;
CREATE SCHEMA IF NOT EXISTS polysenc
AUTHORIZATION pg_database_owner;
GRANT ALL ON SCHEMA polysenc TO pg_database_owner;

Crear con pgAdmin

Para crear esquemas, abra pgAdmin4, y en Esquemas abra el menú contextual->Crear-Esquema

ctreate schema pgadmin4

Introduzca el nombre deseado para el esquema:

create schema general

Repita esta operación para crear los cuatro esquemas necesarios para nuestra base de datos ENC.

postgresql schemas

ATENCIÓN: Los dos métodos de creación no dan el mismo resultado. Cuando se utiliza pgAdmin, los nombres de los esquemas distinguen entre mayúsculas y minúsculas y van entre comillas («ENCpoints», por ejemplo). Cuando usas SQL, los nombres siempre estarán en minúsculas y no habrá comillas (puntosenc, por ejemplo). Para evitar esto, no uses mayúsculas en pgAdmin.

En el resto de este artículo utilizaremos los nombres de la consulta SQL. Si decide utilizar mayúsculas, tendrá que modificar los nombres de los esquemas en el código proporcionado..

Preparación de los esquemas de importación

La fase de importación de tablas con ogr2ogr no se limita a la simple ejecución del comando de transcodificación S57->Postgresql.

Una vez poblado el esquema con las tablas de clases del fichero S57, añadiremos la tabla DSID, que servirá para recuperar el nombre del fichero S57, la escala de compilación de datos y la finalidad del mapa.

Antes debemos añadir estos atributos a todas las tablas no vacías resultantes del comando ogr2ogr, aprovechando para crear también los atributos QUAPOS y POSACC que se rellenarán más adelante.

El principio de este paso es el siguiente:

  1. En el fichero batch (.bat), ejecute el comando para cargar un tipo de geometría.
  2. Una vez ejecutada esta línea de código, se carga la tabla DSID en el mismo esquema.
  3. Un trigger AFTER INSERT sobre la tabla dsid en el esquema comienza borrando las tablas vacías, luego crea los atributos enc_chart, scale, purpose, posacc y quapos en todas las tablas. Añade los valores de enc_chart, scale y purpose a todos los registros de las tablas.

Este procedimiento se realiza para cada tipo de geometría: punto, línea y polígono.

Para que este procedimiento funcione, necesitamos configurar algunos elementos en la base de datos Postgresql.

Tablas DSID para esquemas de importación

Como utilizaremos un disparador en estas tablas, es necesario crearlas antes de la primera carga. Así, en cada esquema de importación, crearemos una tabla

  • Pointsdsid en el esquema pointsenc
  • Linesdsid en el esquema linesenc
  • Polysdsid en el esquema polysenc

create_DSID_tables.sql

-- Création de la séquence pointsdsid_ogc_fid_seq
CREATE SEQUENCE IF NOT EXISTS pointsenc.pointsdsid_ogc_fid_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;

-- Création de la séquence polysdsid_ogc_fid_seq

CREATE SEQUENCE IF NOT EXISTS polysenc.polysdsid_ogc_fid_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;

    -- Création de la séquence linesdsid_ogc_fid_seq

CREATE SEQUENCE IF NOT EXISTS linesenc.linesdsid_ogc_fid_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
-- Table: pointsenc.pointsdsid

-- DROP TABLE IF EXISTS pointsenc.pointsdsid;

CREATE TABLE IF NOT EXISTS pointsenc.pointsdsid
(
ogc_fid integer NOT NULL DEFAULT nextval('pointsenc.pointsdsid_ogc_fid_seq'::regclass),
dsid_expp numeric(3,0),
dsid_intu numeric(3,0),
dsid_dsnm character varying COLLATE pg_catalog."default",
dsid_edtn character varying COLLATE pg_catalog."default",
dsid_updn character varying COLLATE pg_catalog."default",
dsid_uadt character varying(8) COLLATE pg_catalog."default",
dsid_isdt character varying(8) COLLATE pg_catalog."default",
dsid_sted numeric(11,6),
dsid_prsp numeric(3,0),
dsid_psdn character varying COLLATE pg_catalog."default",
dsid_pred character varying COLLATE pg_catalog."default",
dsid_prof numeric(3,0),
dsid_agen numeric(5,0),
dsid_comt character varying COLLATE pg_catalog."default",
dssi_dstr numeric(3,0),
dssi_aall numeric(3,0),
dssi_nall numeric(3,0),
dssi_nomr numeric(10,0),
dssi_nocr numeric(10,0),
dssi_nogr numeric(10,0),
dssi_nolr numeric(10,0),
dssi_noin numeric(10,0),
dssi_nocn numeric(10,0),
dssi_noed numeric(10,0),
dssi_nofa numeric(10,0),
dspm_hdat numeric(3,0),
dspm_vdat numeric(3,0),
dspm_sdat numeric(3,0),
dspm_cscl numeric(10,0),
dspm_duni numeric(3,0),
dspm_huni numeric(3,0),
dspm_puni numeric(3,0),
dspm_coun numeric(3,0),
dspm_comf numeric(10,0),
dspm_somf numeric(10,0),
dspm_comt character varying COLLATE pg_catalog."default",
CONSTRAINT pointsdsid_pkey PRIMARY KEY (ogc_fid)
)

TABLESPACE pg_default;

ALTER TABLE IF EXISTS pointsenc.pointsdsid
OWNER to postgres;
-- Table: polysenc.polysdsid

-- DROP TABLE IF EXISTS polysenc.polysdsid;

CREATE TABLE IF NOT EXISTS polysenc.polysdsid
(
ogc_fid integer NOT NULL DEFAULT nextval('polysenc.polysdsid_ogc_fid_seq'::regclass),
dsid_expp numeric(3,0),
dsid_intu numeric(3,0),
dsid_dsnm character varying COLLATE pg_catalog."default",
dsid_edtn character varying COLLATE pg_catalog."default",
dsid_updn character varying COLLATE pg_catalog."default",
dsid_uadt character varying(8) COLLATE pg_catalog."default",
dsid_isdt character varying(8) COLLATE pg_catalog."default",
dsid_sted numeric(11,6),
dsid_prsp numeric(3,0),
dsid_psdn character varying COLLATE pg_catalog."default",
dsid_pred character varying COLLATE pg_catalog."default",
dsid_prof numeric(3,0),
dsid_agen numeric(5,0),
dsid_comt character varying COLLATE pg_catalog."default",
dssi_dstr numeric(3,0),
dssi_aall numeric(3,0),
dssi_nall numeric(3,0),
dssi_nomr numeric(10,0),
dssi_nocr numeric(10,0),
dssi_nogr numeric(10,0),
dssi_nolr numeric(10,0),
dssi_noin numeric(10,0),
dssi_nocn numeric(10,0),
dssi_noed numeric(10,0),
dssi_nofa numeric(10,0),
dspm_hdat numeric(3,0),
dspm_vdat numeric(3,0),
dspm_sdat numeric(3,0),
dspm_cscl numeric(10,0),
dspm_duni numeric(3,0),
dspm_huni numeric(3,0),
dspm_puni numeric(3,0),
dspm_coun numeric(3,0),
dspm_comf numeric(10,0),
dspm_somf numeric(10,0),
dspm_comt character varying COLLATE pg_catalog."default",
CONSTRAINT polysdsid_pkey PRIMARY KEY (ogc_fid)
)

TABLESPACE pg_default;

ALTER TABLE IF EXISTS polysenc.polysdsid
OWNER to postgres;
-- Table: linesenc.linesdsid

-- DROP TABLE IF EXISTS linesenc.linesdsid;

CREATE TABLE IF NOT EXISTS linesenc.linesdsid
(
ogc_fid integer NOT NULL DEFAULT nextval('linesenc.linesdsid_ogc_fid_seq'::regclass),
dsid_expp numeric(3,0),
dsid_intu numeric(3,0),
dsid_dsnm character varying COLLATE pg_catalog."default",
dsid_edtn character varying COLLATE pg_catalog."default",
dsid_updn character varying COLLATE pg_catalog."default",
dsid_uadt character varying(8) COLLATE pg_catalog."default",
dsid_isdt character varying(8) COLLATE pg_catalog."default",
dsid_sted numeric(11,6),
dsid_prsp numeric(3,0),
dsid_psdn character varying COLLATE pg_catalog."default",
dsid_pred character varying COLLATE pg_catalog."default",
dsid_prof numeric(3,0),
dsid_agen numeric(5,0),
dsid_comt character varying COLLATE pg_catalog."default",
dssi_dstr numeric(3,0),
dssi_aall numeric(3,0),
dssi_nall numeric(3,0),
dssi_nomr numeric(10,0),
dssi_nocr numeric(10,0),
dssi_nogr numeric(10,0),
dssi_nolr numeric(10,0),
dssi_noin numeric(10,0),
dssi_nocn numeric(10,0),
dssi_noed numeric(10,0),
dssi_nofa numeric(10,0),
dspm_hdat numeric(3,0),
dspm_vdat numeric(3,0),
dspm_sdat numeric(3,0),
dspm_cscl numeric(10,0),
dspm_duni numeric(3,0),
dspm_huni numeric(3,0),
dspm_puni numeric(3,0),
dspm_coun numeric(3,0),
dspm_comf numeric(10,0),
dspm_somf numeric(10,0),
dspm_comt character varying COLLATE pg_catalog."default",
CONSTRAINT linesdsid_pkey PRIMARY KEY (ogc_fid)
)

TABLESPACE pg_default;

ALTER TABLE IF EXISTS linesenc.linesdsid
OWNER to postgres;

Configuración de activadores AFTER INSERT

Después de insertar una fila en la tabla DSID del esquema, estos activadores

  • eliminarán cualquier tabla vacía del esquema
  • comprobarán si los campos adicionales ya existen y, en caso contrario, crearán los 5 atributos ‘(enc_chart, scale, purpose,posacc y quapos),
  • añada los valores del registro DSID a los atributos enc_chart, scale y purpose,
  • y finalmente borrar el registro de la tabla DSID.

Existe una función para cada tipo de geometría,

  • create_fields_and_update_values_pointsenc()
  • create_fields_and_update_values_linesenc()
  • create_fields_and_update_values_polysenc()

a continuación, una consulta SQL para configurar desencadenadores en cada una de las tablas DSID (pointsDSID,linesDSID,polysDSID)

create_fields_and_update_values_pointsenc()

CREATE OR REPLACE FUNCTION create_fields_and_update_values_pointsenc()

RETURNS TRIGGER AS
$$
DECLARE
table_record RECORD;
tables_import RECORD;
enc_chart_value TEXT;
scale_value NUMERIC;
purpose_value NUMERIC;
empty_tables int;
BEGIN
--Le code suivant permet de supprimer les tables vides d'un schéma d'import:
FOR table_record IN SELECT table_name FROM information_schema.tables WHERE table_schema = 'pointsenc' AND table_name != 'pointsdsid' LOOP
-- Composer une requête dynamique pour vérifier si la table est vide
EXECUTE format('SELECT COUNT(*) FROM pointsenc.%I', table_record.table_name) INTO empty_tables;

-- Si le nombre de lignes est égal à zéro, supprimer la table
    IF empty_tables = 0 THEN
        EXECUTE format('DROP TABLE IF EXISTS pointsenc.%I CASCADE',  table_record.table_name);
        RAISE NOTICE 'Table pointsenc.%I supprimée car elle est vide.',  table_record.table_name;
    END IF;
END LOOP;

-- Parcours de toutes les tables du schéma pointsenc 
FOR table_record IN SELECT table_name FROM information_schema.tables WHERE table_schema = 'pointsenc' AND table_name != 'pointsdsid' LOOP
    -- Vérifie si les champs enc_chart et scale n'existent pas dans la table actuelle
    IF NOT EXISTS (
        SELECT column_name FROM information_schema.columns
        WHERE table_schema = 'pointsenc' AND table_name = table_record.table_name AND column_name IN ('enc_chart', 'scale','purpose')
    ) THEN
        -- Crée le champ enc_chart de type texte
        EXECUTE format('ALTER TABLE pointsenc.%I ADD COLUMN enc_chart TEXT', table_record.table_name);
        -- Crée le champ scale de type numérique
        EXECUTE format('ALTER TABLE pointsenc.%I ADD COLUMN scale NUMERIC', table_record.table_name);
        -- Crée le champ purpose de type numérique
        EXECUTE format('ALTER TABLE pointsenc.%I ADD COLUMN purpose NUMERIC', table_record.table_name);
            IF NOT EXISTS (
                SELECT column_name FROM information_schema.columns
                WHERE table_schema = 'pointsenc' AND table_name = table_record.table_name AND column_name IN ('posacc','quapos')
                ) THEN
                -- Crée le champ POSACC de type numérique
                EXECUTE format('ALTER TABLE pointsenc.%I ADD COLUMN posacc NUMERIC(10,0)', table_record.table_name);
                -- Crée le champ QUAPOS de type numérique
                EXECUTE format('ALTER TABLE pointsenc.%I ADD COLUMN quapos INTEGER', table_record.table_name);
            END IF;
        RAISE NOTICE 'Champs enc_chart, scale , purpose, POSACC et QUIAPOS créés dans la table %', table_record.table_name;

    END IF;
        -- Obtient la valeur de enc_chart à partir de la table DSID pour DSID_DSNM
        SELECT DSID_DSNM INTO enc_chart_value FROM pointsdsid LIMIT 1;
        -- Obtient la valeur de scale à partir de la table DSID pour DSPM_CSCL
        SELECT DSPM_CSCL INTO scale_value FROM pointsdsid LIMIT 1;
        -- Obtient la valeur de purpose à partir de la table DSID pour DSID_INTU
        SELECT DSID_INTU INTO purpose_value FROM pointsdsid LIMIT 1;

        -- Met à jour les enregistrements avec les valeurs trouvées dans la table DSID
    EXECUTE format('UPDATE pointsenc.%I SET enc_chart = $1 WHERE enc_chart IS NULL', table_record.table_name) USING enc_chart_value;
    EXECUTE format('UPDATE pointsenc.%I SET scale = $1 WHERE scale IS NULL', table_record.table_name) USING scale_value;
    EXECUTE format('UPDATE pointsenc.%I SET purpose = $1 WHERE purpose IS NULL', table_record.table_name) USING purpose_value;


END LOOP;

-- Efface l'enregistrement de la table DSID
DELETE FROM pointsenc.pointsdsid;

RETURN NULL;

END;
$$
LANGUAGE plpgsql;

create_fields_and_update_values_linesenc()



CREATE OR REPLACE FUNCTION create_fields_and_update_values_linesenc()
RETURNS TRIGGER AS
$$
DECLARE
table_record RECORD;
tables_import RECORD;
enc_chart_value TEXT;
scale_value NUMERIC;
purpose_value NUMERIC;
empty_tables int;
BEGIN
--Le code suivant permet de supprimer les tables vides d'un schéma d'import:
FOR table_record IN SELECT table_name FROM information_schema.tables WHERE table_schema = 'linesenc' AND table_name != 'linesdsid' LOOP
-- Composer une requête dynamique pour vérifier si la table est vide
EXECUTE format('SELECT COUNT(*) FROM linesenc.%I', table_record.table_name) INTO empty_tables;

-- Si le nombre de lignes est égal à zéro, supprimer la table
    IF empty_tables = 0 THEN
        EXECUTE format('DROP TABLE IF EXISTS linesenc.%I CASCADE',  table_record.table_name);
        RAISE NOTICE 'Table linesenc.%I supprimée car elle est vide.',  table_record.table_name;
    END IF;
END LOOP;

-- Parcours de toutes les tables du schéma linesenc 
FOR table_record IN SELECT table_name FROM information_schema.tables WHERE table_schema = 'linesenc' AND table_name != 'linesdsid' LOOP
    -- Vérifie si les champs enc_chart et scale n'existent pas dans la table actuelle
    IF NOT EXISTS (
        SELECT column_name FROM information_schema.columns
        WHERE table_schema = 'linesenc' AND table_name = table_record.table_name AND column_name IN ('enc_chart', 'scale','purpose')
    ) THEN
        -- Crée le champ enc_chart de type texte
        EXECUTE format('ALTER TABLE linesenc.%I ADD COLUMN enc_chart TEXT', table_record.table_name);
        -- Crée le champ scale de type numérique
        EXECUTE format('ALTER TABLE linesenc.%I ADD COLUMN scale NUMERIC', table_record.table_name);
        -- Crée le champ purpose de type numérique
        EXECUTE format('ALTER TABLE linesenc.%I ADD COLUMN purpose NUMERIC', table_record.table_name);
        RAISE NOTICE 'Champs enc_chart, scale et purpose créés dans la table %', table_record.table_name;
            IF NOT EXISTS (
            SELECT column_name FROM information_schema.columns
            WHERE table_schema = 'linesenc' AND table_name = table_record.table_name AND column_name IN ('posacc','quapos')
            ) THEN
            -- Crée le champ POSACC de type numérique
            EXECUTE format('ALTER TABLE linesenc.%I ADD COLUMN POSACC NUMERIC(10,0)', table_record.table_name);
            -- Crée le champ QUAPOS de type numérique
            EXECUTE format('ALTER TABLE linesenc.%I ADD COLUMN QUAPOS INTEGER', table_record.table_name);
        END IF;
    RAISE NOTICE 'Champs enc_chart, scale et purpose créés dans la table %', table_record.table_name;

    END IF;
        -- Obtient la valeur de enc_chart à partir de la table DSID pour DSID_DSNM
        SELECT DSID_DSNM INTO enc_chart_value FROM linesenc.linesdsid LIMIT 1;
        -- Obtient la valeur de scale à partir de la table DSID pour DSPM_CSCL
        SELECT DSPM_CSCL INTO scale_value FROM linesenc.linesdsid LIMIT 1;
        -- Obtient la valeur de purpose à partir de la table DSID pour DSID_INTU
        SELECT DSID_INTU INTO purpose_value FROM linesenc.linesdsid LIMIT 1;

        -- Met à jour les enregistrements avec les valeurs trouvées dans la table DSID
    EXECUTE format('UPDATE linesenc.%I SET enc_chart = $1 WHERE enc_chart IS NULL', table_record.table_name) USING enc_chart_value;
    EXECUTE format('UPDATE linesenc.%I SET scale = $1 WHERE scale IS NULL', table_record.table_name) USING scale_value;
    EXECUTE format('UPDATE linesenc.%I SET purpose = $1 WHERE purpose IS NULL', table_record.table_name) USING purpose_value;
END LOOP;

-- Efface l'enregistrement de la table DSID
DELETE FROM linesenc.linesdsid;

RETURN NULL;

END;
$$
LANGUAGE plpgsql;

create_fields_and_update_values_polysenc()

CREATE OR REPLACE FUNCTION create_fields_and_update_values_polysenc()
RETURNS TRIGGER AS
$$
DECLARE
table_record RECORD;
tables_import RECORD;
enc_chart_value TEXT;
scale_value NUMERIC;
purpose_value NUMERIC;
empty_tables int;
BEGIN
--Le code suivant permet de supprimer les tables vides d'un schéma d'import:
FOR table_record IN SELECT table_name FROM information_schema.tables WHERE table_schema = 'polysenc' AND table_name != 'polysdsid' LOOP
-- Composer une requête dynamique pour vérifier si la table est vide
EXECUTE format('SELECT COUNT(*) FROM polysenc.%I', table_record.table_name) INTO empty_tables;

-- Si le nombre de lignes est égal à zéro, supprimer la table
    IF empty_tables = 0 THEN
        EXECUTE format('DROP TABLE IF EXISTS polysenc.%I CASCADE',  table_record.table_name);
        RAISE NOTICE 'Table polysenc.%I supprimée car elle est vide.',  table_record.table_name;
    END IF;
END LOOP;

-- Parcours de toutes les tables du schéma polysenc 
FOR table_record IN SELECT table_name FROM information_schema.tables WHERE table_schema = 'polysenc' AND table_name != 'polysdsid' LOOP
    -- Vérifie si les champs enc_chart et scale n'existent pas dans la table actuelle
    IF NOT EXISTS (
        SELECT column_name FROM information_schema.columns
        WHERE table_schema = 'polysenc' AND table_name = table_record.table_name AND column_name IN ('enc_chart', 'scale','purpose')
    ) THEN
        -- Crée le champ enc_chart de type texte
        EXECUTE format('ALTER TABLE polysenc.%I ADD COLUMN enc_chart TEXT', table_record.table_name);
        -- Crée le champ scale de type numérique
        EXECUTE format('ALTER TABLE polysenc.%I ADD COLUMN scale NUMERIC', table_record.table_name);
        -- Crée le champ purpose de type numérique
        EXECUTE format('ALTER TABLE polysenc.%I ADD COLUMN purpose NUMERIC', table_record.table_name);
                    IF NOT EXISTS (
                SELECT column_name FROM information_schema.columns
                WHERE table_schema = 'polysenc' AND table_name = table_record.table_name AND column_name IN ('posacc','quapos')
                ) THEN
                -- Crée le champ POSACC de type numérique
                EXECUTE format('ALTER TABLE polysenc.%I ADD COLUMN POSACC NUMERIC(10,0)', table_record.table_name);
                -- Crée le champ QUAPOS de type numérique
                EXECUTE format('ALTER TABLE polysenc.%I ADD COLUMN QUAPOS INTEGER', table_record.table_name);
            END IF;
        RAISE NOTICE 'Champs enc_chart, scale et purpose créés dans la table %', table_record.table_name;

    END IF;
        -- Obtient la valeur de enc_chart à partir de la table DSID pour DSID_DSNM
        SELECT DSID_DSNM INTO enc_chart_value FROM polysenc.polysdsid LIMIT 1;
        -- Obtient la valeur de scale à partir de la table DSID pour DSPM_CSCL
        SELECT DSPM_CSCL INTO scale_value FROM polysenc.polysdsid LIMIT 1;
        -- Obtient la valeur de purpose à partir de la table DSID pour DSID_INTU
        SELECT DSID_INTU INTO purpose_value FROM polysenc.polysdsid LIMIT 1;

        -- Met à jour les enregistrements avec les valeurs trouvées dans la table DSID
    EXECUTE format('UPDATE polysenc.%I SET enc_chart = $1 WHERE enc_chart IS NULL', table_record.table_name) USING enc_chart_value;
    EXECUTE format('UPDATE polysenc.%I SET scale = $1 WHERE scale IS NULL', table_record.table_name) USING scale_value;
    EXECUTE format('UPDATE polysenc.%I SET purpose = $1 WHERE purpose IS NULL', table_record.table_name) USING purpose_value;
END LOOP;

-- Efface l'enregistrement de la table DSID
DELETE FROM polysenc.polysdsid;

RETURN NULL;

END;
$$
LANGUAGE plpgsql;

createtriggersfields()

CREATE OR REPLACE TRIGGER check_update
AFTER INSERT ON linesenc.linesdsid
FOR EACH ROW
EXECUTE PROCEDURE create_fields_and_update_values_linesenc();

CREATE OR REPLACE TRIGGER check_update
AFTER INSERT ON polysenc.polysdsid
FOR EACH ROW
EXECUTE PROCEDURE create_fields_and_update_values_polysenc();

CREATE OR REPLACE TRIGGER check_update
AFTER INSERT ON pointsenc.pointsdsid
FOR EACH ROW
EXECUTE PROCEDURE create_fields_and_update_values_pointsenc();

Flujo de trabajo para cargar archivos S57

Comandos ogr2ogr para crear tablas Postgis

Con todos estos elementos en la mano, aquí está el archivo .bat con las líneas de comando ogr2ogr para crear las tablas en los esquemas de importación:

Commandes ogr2ogr



@echo off
setlocal enabledelayedexpansion

REM Vérifie qu’un répertoire a été fourni
if “%~1″==”” (
echo Usage: %0 directory000
exit /b 1
)

REM Récupère l’argument
set “directory=%~1”

REM Itère sur tous les fichiers .000 dans le répertoire
for /r “%directory%” %%i in (*.000) do (
echo Traitement du fichier: %%i

ogr2ogr -skipfailures -append -update -s_srs EPSG:4326 -t_srs EPSG:4326 ^
    -where "OGR_GEOMETRY='POINT' or OGR_GEOMETRY='MULTIPOINT'" ^
    -oo RETURN_PRIMITIVES=ON -oo SPLIT_MULTIPOINT=ON -oo RETURN_LINKAGES=ON -oo LNAM_REFS=ON -oo ADD_SOUNDG_DEPTH=ON ^
    -nlt MULTIPOINT -f PostgreSQL ^
    PG:"dbname=postgis_34_sample host=localhost port=5432 user=postgres password=1touria+ active_schema=pointsenc" %%i

ogr2ogr -skipfailures -append -update -nln pointsDSID -f PostgreSQL  PG:"dbname=postgis_34_sample host=localhost port=5432 user=postgres password=1touria+ active_schema=pointsenc" %%i DSID

ogr2ogr -skipfailures -append -update -s_srs EPSG:4326 -t_srs EPSG:4326 ^
    -where "OGR_GEOMETRY='LINESTRING' or OGR_GEOMETRY='MULTILINESTRING'" ^
    -oo RETURN_PRIMITIVES=ON -oo SPLIT_MULTIPOINT=ON -oo RETURN_LINKAGES=ON -oo LNAM_REFS=ON ^
    -f PostgreSQL PG:"dbname=postgis_34_sample host=localhost port=5432 user=postgres password=1touria+ active_schema=linesenc" %%i

ogr2ogr -skipfailures -append -update -nln linesDSID -f PostgreSQL ^
    PG:"dbname=postgis_34_sample host=localhost port=5432 user=postgres password=1touria+ active_schema=linesenc" %%i DSID

ogr2ogr -skipfailures -append -update -s_srs EPSG:4326 -t_srs EPSG:4326 ^
    -where "OGR_GEOMETRY='POLYGON' or OGR_GEOMETRY='MULTIPOLYGON'" ^
    -oo RETURN_PRIMITIVES=ON -oo SPLIT_MULTIPOINT=ON -oo RETURN_LINKAGES=ON -oo LNAM_REFS=ON ^
    -f PostgreSQL PG:"dbname=postgis_34_sample host=localhost port=5432 user=postgres password=1touria+ active_schema=polysenc" %%i

ogr2ogr -skipfailures -append -update -nln polysDSID -f PostgreSQL ^
    PG:"dbname=postgis_34_sample host=localhost port=5432 user=postgres password=1touria+ active_schema=polysenc" %%i DSID

)

echo Traitement terminé.
pause

Necesita cambiar la información de conexión para su base de datos PostgreSQL/POstgis:

PostgreSQL PG: «dbname=“postgis_34_sample” host=“localhost” port=“5434” user=“postgres” password=“xxxxxx”

Para ejecutar estas líneas de comando, simplemente abra la ventana shell de OSGeo4W:

OSGeo4W shell

Se abre la ventana Shell

osgeo4W shell window

Introduzca la siguiente línea de comandos:

.\Path/ mass_load_s57_postgis.bat repertorio_enc

Repertoire_enc es el directorio que contiene los archivos .000 para las tarjetas enc. Todos los archivos .000 de este directorio se cargarán en los esquemas de importación.

El resultado será una serie de tablas creadas en cada esquema de importación. Sin embargo, algunas tablas estarán completamente vacías. Si el tipo de geometría es posible para una clase de objeto, se creará la tabla, pero si no hay ocurrencias en el fichero S57 que se está procesando, la tabla no tendrá registros.

Carga de la base de datos a partir de los esquemas de importación

La función clone_tables_with_prefix carga la base de datos ENC con las tablas de los tres esquemas de importación.

  • En primer lugar, elimina de cada esquema de importación las tablas vacías creadas por ogr2ogr si en el archivo .000 no se ha utilizado un tipo de geometría esperado.
  • En segundo lugar, comprueba para cada esquema de importación si la tabla correspondiente ya existe en la base de datos ENC. Si no existe, crea la tabla con el prefijo correspondiente al tipo de geometría (pt_, li_, pl_).
  • La función añade los registros de la tabla de importación a la tabla ENC.
  • Para las tablas de tipo punto, actualiza los atributos posacc y quapos de la tabla utilizando los valores de la tabla IsolatedNode.
  • Para las tablas de líneas y polígonos, actualiza los atributos posacc y quapos de la tabla utilizando los valores de la tabla Edge.
  • Finalmente, la función vacía todas las tablas de los esquemas de importación, dejándolas listas para ser cargadas de nuevo con mis comandos ogr2ogr.

Antes de usar esta función necesitas instalar la función delete_all_records_in_schema:

Copia la consulta en una ventana SQL de pgAdmin y ejecútala.

delete_all_records_in_schema 

-- FUNCTION: public.delete_all_records_in_schema(text)

-- DROP FUNCTION IF EXISTS public.delete_all_records_in_schema(text);

CREATE OR REPLACE FUNCTION public.delete_all_records_in_schema(
schema_name text)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE PARALLEL UNSAFE
AS $BODY$
DECLARE
table_record RECORD;
BEGIN
-- Récupérer toutes les tables du schéma spécifié
FOR table_record IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = schema_name AND table_type = 'BASE TABLE'
LOOP
-- Construction de la requête DELETE pour chaque table
EXECUTE format('DELETE FROM %I.%I', schema_name, table_record.table_name);
END LOOP;
END;
$BODY$;

ALTER FUNCTION public.delete_all_records_in_schema(text)
OWNER TO postgres;

La base de datos ENC se actualiza ejecutando el comando

SELECT clone_table_with_prefix()

No olvide sustituir el nombre del esquema (enc2) por el nombre de su esquema enc..

clone_table_with_prefix

CREATE OR REPLACE FUNCTION clone_tables_with_prefix()
RETURNS void AS
$$
DECLARE
table_nom text;
BEGIN
-- Clonage des tables dans le schéma ENC et mise à jour des tables existantes

-- Boucle sur les tables du schéma pointsENC
FOR table_nom IN (SELECT table_name as table_nom FROM information_schema.tables WHERE table_schema = 'pointsenc' AND table_name NOT IN ('pointsdsid','isolatednode','connectednode'))
LOOP
    -- Construction de la requête dynamique pour créer ou mettre à jour la table dans ENC
    EXECUTE format('CREATE TABLE IF NOT EXISTS enc2.pt_%I AS SELECT * FROM pointsenc.%I', table_nom, table_nom);
    EXECUTE format('INSERT INTO enc2.pt_%I SELECT * FROM pointsenc.%I ON CONFLICT DO NOTHING', table_nom, table_nom);
    EXECUTE format('UPDATE enc2.pt_%I SET posacc = isolatednode.posacc,  quapos = isolatednode.quapos FROM pointsenc.IsolatedNode isolatednode WHERE enc2.pt_%I.NAME_RCID[1] = isolatednode.RCID   AND enc2.pt_%I.enc_chart = isolatednode.enc_chart;', table_nom, table_nom,table_nom);

END LOOP;

-- Boucle sur les tables du schéma LinesENC
FOR table_nom IN (SELECT table_name as table_nom FROM information_schema.tables WHERE table_schema = 'linesenc' AND table_name NOT IN ('linesdsid','edge'))
LOOP
    -- Construction de la requête dynamique pour créer ou mettre à jour la table dans ENC
    EXECUTE format('CREATE TABLE IF NOT EXISTS enc2.li_%I AS SELECT * FROM linesenc.%I', table_nom, table_nom);
    EXECUTE format('INSERT INTO enc2.li_%I SELECT * FROM linesenc.%I ON CONFLICT DO NOTHING', table_nom, table_nom);
    EXECUTE format('UPDATE enc2.li_%I SET posacc = edge.posacc,  quapos = edge.quapos FROM linesenc.edge edge WHERE enc2.li_%I.NAME_RCID[1] = edge.RCID   AND enc2.li_%I.enc_chart = edge.enc_chart;', table_nom, table_nom,table_nom);

END LOOP;

-- Boucle sur les tables du schéma PolysENC
FOR table_nom IN (SELECT table_name as table_nom FROM information_schema.tables WHERE table_schema = 'polysenc' AND table_name NOT IN ( 'polysdsid','m_qual','m_srel'))
LOOP
    -- Construction de la requête dynamique pour créer ou mettre à jour la table dans ENC
    EXECUTE format('CREATE TABLE IF NOT EXISTS enc2.pl_%I AS SELECT * FROM polysenc.%I', table_nom, table_nom);
    EXECUTE format('INSERT INTO enc2.pl_%I SELECT * FROM polysenc.%I ON CONFLICT DO NOTHING', table_nom, table_nom);
    EXECUTE format('UPDATE enc2.pl_%I SET posacc = edge.posacc,  quapos = edge.quapos FROM linesenc.edge edge WHERE enc2.pl_%I.NAME_RCID[1] = edge.RCID   AND enc2.pl_%I.enc_chart = edge.enc_chart;', table_nom, table_nom,table_nom);

END LOOP;
EXECUTE (SELECT delete_all_records_in_schema('pointsenc'));
EXECUTE (SELECT delete_all_records_in_schema('linesenc'));
EXECUTE (SELECT delete_all_records_in_schema('polysenc'));

EXECUTE (SELECT update_sbdare());
END;
$$ LANGUAGE plpgsql;

Tratamiento especial de la tabla pt_SBDARE

Después de la primera ejecución del script anterior, si los ficheros cargados tenían valores para las naturalezas de fondo, deberías tener una tabla pt_sbdare en el esquema ENC. Esta es la única tabla que requiere un procesamiento especial para la simbología por defecto que proporcionamos. De hecho, el procesamiento de los valores de los dos atributos necesarios para crear la etiqueta de naturaleza de fondo (NATSUR y NATQUA) es muy complejo debido a la naturaleza de los atributos (StringLists) y a las innumerables combinaciones posibles.

La solución aplicada en este caso es la siguiente:

  • Una tabla natsurf lista las posibles combinaciones de valores de las tuplas natqua y natsur con la etiqueta correspondiente.
  • Una función update_sbdare procesa las dos listas de cadenas para componer las tuplas de valores y concatena adecuadamente las etiquetas de la tabla natsurf.
  • La función actualiza los registros de la tabla pt_sbdare cuyo valor ‘label’ es cero.

Cuando se cargan por primera vez los esquemas de importación, la tabla pt_sbdare no existe. Una vez creada por una importación, se puede definir un trigger que ejecute la función automáticamente a continuación.

Por lo tanto, es necesario :

  • Importar la tabla natsurf en el esquema ENC
  • Crear la función update_sbdare
  • Ejecutar la función manualmente por primera vez

.

Importar la tabla natsurf

Cargue la siguiente consulta en una ventana SQL de pgAdmin y ejecútela. Se supone que ha creado un esquema ‘enc’ para su base de datos. Si no es el caso, edite la tabla, sustituyendo ‘enc.natsurf’ por ‘su_esquema.natsurf’.

natsurf.sql 

SET standard_conforming_strings = ON;
DROP TABLE IF EXISTS enc.natsurf CASCADE;
BEGIN;
CREATE TABLE enc.natsurf();
ALTER TABLE enc.natsurf ADD COLUMN "ogc_fid" SERIAL CONSTRAINT "natsurf_pk" PRIMARY KEY;
ALTER TABLE enc.natsurf ADD COLUMN "fid" NUMERIC(20,0);
ALTER TABLE enc.natsurf ADD COLUMN "natsurt" VARCHAR;
ALTER TABLE enc.natsurf ADD COLUMN "natquat" VARCHAR;
ALTER TABLE enc.natsurf ADD COLUMN "etiq" VARCHAR;
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (1, '1', '0', 'M');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (2, '1', '1', 'fM');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (3, '1', '2', 'mM');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (4, '1', '3', 'cM');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (5, '1', '4', 'bkM');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (6, '1', '5', 'syM');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (7, '1', '6', 'soM');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (8, '1', '7', 'sfM');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (9, '1', '8', 'vM');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (10, '1', '9', 'caM');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (11, '1', '10', 'hM');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (12, '2', '0', 'Cy');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (13, '2', '1', 'fCy');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (14, '2', '2', 'mCy');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (15, '2', '3', 'cCy');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (16, '2', '4', 'bkCy');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (17, '2', '5', 'syCy');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (18, '2', '6', 'soCy');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (19, '2', '7', 'sfCy');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (20, '2', '8', 'vCy');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (21, '2', '9', 'caCy');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (22, '2', '10', 'hCy');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (23, '3', '0', 'Si');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (24, '3', '1', 'fSi');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (25, '3', '2', 'mSi');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (26, '3', '3', 'cSi');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (27, '3', '4', 'bkSi');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (28, '3', '5', 'sySi');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (29, '3', '6', 'soSi');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (30, '3', '7', 'sfSi');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (31, '3', '8', 'vSi');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (32, '3', '9', 'caSi');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (33, '3', '10', 'hSi');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (34, '4', '0', 'S');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (35, '4', '1', 'fS');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (36, '4', '2', 'mS');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (37, '4', '3', 'cS');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (38, '4', '4', 'bkS');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (39, '4', '5', 'syS');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (40, '4', '6', 'soS');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (41, '4', '7', 'sfS');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (42, '4', '8', 'vS');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (43, '4', '9', 'caS');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (44, '4', '10', 'hS');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (45, '5', '0', 'St');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (46, '5', '1', 'fSt');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (47, '5', '2', 'mSt');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (48, '5', '3', 'cSt');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (49, '5', '4', 'bkSt');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (50, '5', '5', 'sySt');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (51, '5', '6', 'soSt');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (52, '5', '7', 'sfSt');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (53, '5', '8', 'vSt');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (54, '5', '9', 'caSt');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (55, '5', '10', 'hSt');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (56, '6', '0', 'G');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (57, '6', '1', 'fG');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (58, '6', '2', 'mG');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (59, '6', '3', 'cG');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (60, '6', '4', 'bkG');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (61, '6', '5', 'syG');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (62, '6', '6', 'soG');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (63, '6', '7', 'sfG');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (64, '6', '8', 'vG');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (65, '6', '9', 'caG');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (66, '6', '10', 'hG');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (67, '7', '0', 'P');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (68, '7', '1', 'fP');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (69, '7', '2', 'mP');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (70, '7', '3', 'cP');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (71, '7', '4', 'bkP');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (72, '7', '5', 'syP');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (73, '7', '6', 'soP');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (74, '7', '7', 'sfP');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (75, '7', '8', 'vP');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (76, '7', '9', 'caP');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (77, '7', '10', 'hP');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (78, '8', '0', 'Cb');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (79, '8', '1', 'fCb');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (80, '8', '2', 'mCb');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (81, '8', '3', 'cCb');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (82, '8', '4', 'bkCb');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (83, '8', '5', 'syCb');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (84, '8', '6', 'soCb');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (85, '8', '7', 'sfCb');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (86, '8', '8', 'vCb');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (87, '8', '9', 'caCb');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (88, '8', '10', 'hCb');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (89, '9', '0', 'R');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (90, '9', '1', 'fR');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (91, '9', '2', 'mR');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (92, '9', '3', 'cR');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (93, '9', '4', 'bkR');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (94, '9', '5', 'syR');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (95, '9', '6', 'soR');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (96, '9', '7', 'sfR');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (97, '9', '8', 'vR');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (98, '9', '9', 'caR');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (99, '9', '10', 'hR');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (100, '11', '0', 'L');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (101, '11', '1', 'fL');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (102, '11', '2', 'mL');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (103, '11', '3', 'cL');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (104, '11', '4', 'bkL');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (105, '11', '5', 'syL');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (106, '11', '6', 'soL');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (107, '11', '7', 'sfL');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (108, '11', '8', 'vL');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (109, '11', '9', 'caL');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (110, '11', '10', 'hL');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (111, '14', '0', 'Co');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (112, '14', '1', 'fCo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (113, '14', '2', 'mCo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (114, '14', '3', 'cCo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (115, '14', '4', 'bkCo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (116, '14', '5', 'syCo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (117, '14', '6', 'soCo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (118, '14', '7', 'sfCo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (119, '14', '8', 'vCo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (120, '14', '9', 'caCo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (121, '14', '10', 'hCo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (122, '17', '0', 'Sh');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (123, '17', '1', 'fSh');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (124, '17', '2', 'mSh');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (125, '17', '3', 'cSh');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (126, '17', '4', 'bkSh');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (127, '17', '5', 'sySh');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (128, '17', '6', 'soSh');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (129, '17', '7', 'sfSh');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (130, '17', '8', 'vSh');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (131, '17', '9', 'caSh');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (132, '17', '10', 'hSh');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (133, '18', '0', 'Bo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (134, '18', '1', 'fBo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (135, '18', '2', 'mBo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (136, '18', '3', 'cBo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (137, '18', '4', 'bkBo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (138, '18', '5', 'syBo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (139, '18', '6', 'soBo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (140, '18', '7', 'sfBo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (141, '18', '8', 'vBo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (142, '18', '9', 'caBo');
INSERT INTO enc.natsurf ("fid", "natsurt", "natquat", "etiq") VALUES (143, '18', '10', 'hBo');
COMMIT;

Creación de la función update_sbdare

Cargue la siguiente consulta en una ventana SQL de pgAdmin y ejecútela. Se supone que ha creado un esquema ‘enc’ para su base de datos. Si no es así, edite la tabla sustituyendo ‘enc.’ por ‘su_esquema’.

update_sbdare.sql



CREATE OR REPLACE FUNCTION update_sbdare()
RETURNS VOID AS
$$
DECLARE
rec_row RECORD;
natsurt VARCHAR[4];
natquat VARCHAR[4];
S VARCHAR[3];
flag INT := 0;
etiq VARCHAR(25);
etiquet VARCHAR(25);
BEGIN
-- Vérifier si le champ 'Label' existe déjà
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'enc2' AND table_name = 'pt_sbdare' AND column_name = 'label'
) THEN

-- Parcourir les lignes de la table enc.pt_sbdare
FOR rec_row IN SELECT * FROM enc2.pt_sbdare WHERE label IS NULL LOOP
    -- Réinitialiser les variables à chaque itération de la boucle
    natsurt := ARRAY['','','',''];
    S := ARRAY[',',',',','];
    flag :=0;

    -- Extraire les parties de natsur
    FOR i IN 1..4 LOOP
        --sortie anticipée
        IF i=4 AND flag=1 THEN
            EXIT;
        END IF;

        -- Vérifier si rec_row.natsur[i] est NULL
        IF rec_row.natsur[i] IS NULL THEN
            IF flag=0 THEN
                natsurt[i] := '0';
            ELSE
                natsurt[i+1] := '0';
            END IF;
        -- Vérifier si rec_row.natsur[i] contient un '/'
        ELSIF strpos(rec_row.natsur[i], '/') = 0 THEN
            IF flag=0 THEN
                natsurt[i] := rec_row.natsur[i];
                IF rec_row.natqua[i] IS NOT NULL THEN
                    natquat[i] := rec_row.natqua[i];
                ELSE
                    natquat[i] := '0';
                END IF;
            ELSE
                natsurt[i+1] := rec_row.natsur[i];
                IF rec_row.natqua[i] IS NOT NULL THEN
                    natquat[i+1] := rec_row.natqua[i];
                ELSE
                    natquat[i] := '0';
                END IF;
            END IF;

        ELSE
            -- Extraire les parties avant et après le '/'
            IF i < 5 THEN
                natsurt[i] := split_part(rec_row.natsur[i], '/', 1);
                natsurt[i+1] := split_part(rec_row.natsur[i], '/', 2);
                IF rec_row.natqua[i] IS NOT NULL THEN
                    natquat[i] := rec_row.natqua[i];
                ELSE
                    natquat[i] := '0';
                END IF;
                S[i] := '/';
                flag :=1; -- nous avons trouvé une valeur avec un '/'
            ELSE
                natsurt[i] := split_part(rec_row.natsur[i], '/', 1);
            END IF;
        END IF;
    END LOOP;

    etiquet :='';
    etiq :='';

    FOR i IN 1..4 LOOP
        IF natsurt[i] <> '0' THEN
            -- Exécutez la requête SQL pour récupérer le label en fonction de natsurt[i] et natquat[i]
            EXECUTE 'SELECT etiq FROM enc2.natsurf WHERE NATSURT = $1 AND NATQUAT = $2' INTO etiq USING natsurt[i], natquat[i];

            IF i =1 THEN
                etiquet := etiq ;
            ELSE
                etiquet := etiquet || S[i-1] || etiq;
            END IF;
        END IF;
    END LOOP;

    -- Mettre à jour la ligne avec les valeurs extraites
    UPDATE enc2.pt_sbdare
    SET
        label = etiquet
    WHERE pt_sbdare.ogc_fid = rec_row.ogc_fid;
END LOOP;

END IF;
END;
$$
LANGUAGE plpgsql;

Ejecución manual de la función para la primera actualización

En una ventana SQL de pgAdmin, introduzca la consulta

ALTER TABLE enc.pt_sbdare ADD COLUMN label VARCHAR(25);

SELECT update_sbdare()

El resultado será el atributo ‘label’ en la tabla pt_sbdare. Posteriormente, al ejecutar clone_tables_with_prefix(), la actualización se realizará automáticamente.

Si cet article vous a intéressé et que vous pensez qu'il pourrait bénéficier à d'autres personnes, n'hésitez pas à le partager sur vos réseaux sociaux en utilisant les boutons ci-dessous. Votre partage est apprécié !

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *