Dans le cadre d’un de mes projets personnels, j’ai eu besoin d’exploiter les données issues du MNT à 5 m de l’IGN (disponible gratuitement depuis début 2021 ici). Je fais donc ici un récapitulatif des différentes méthodes que j’ai pu essayer pour gérer au mieux ces données et exploiter tout leur potentiel en lien avec une base PostgreSQL.
Cet article est orienté pour des fichiers raster de type modèle d’élévation avec une seule bande. Il est cependant possible d’appliquer ces méthodes à tout type de rasters.
Ces méthodes se basent sur la librairie GDAL (notamment disponible avec Qgis et dont l’article suivant présente une rapide prise en main) et l’extension PostGIS.
Cet article aborde un sujet très largement détaillé et plus approfondi dans mon livre à découvrir ci dessous
PostGIS – Tous les ingrédients pour concocter un SIG sur de bonnes bases
Stockez, manipulez et faites parler vos données géolocalisées, vecteur ou raster, avec PostGIS.
Ce livre vise à fournir au lecteur tous les éléments nécessaires pour mettre en place un système d’information géographique (SIG) fondé sur l’utilisation conjointe de PostgreSQL et de PostGIS.
Stockage des rasters
Avant de démarrer, vous pouvez faire un petit tour sur cet article pour en savoir plus sur la configuration de PostGIS pour le stockage des données raster.
Fichier plat
Méthode la plus classique : les rasters sont stockés dans des répertoires sur le disque.
Petit bonus, si le raster est composé de nombreuses dalles (ou tuiles), l’utilisation d’un raster virtuel permet de se simplifier la tâche : il suffira d’appeler cet unique fichier pour accéder à toutes les tuiles.
Les dalles à intégrer dans le raster virtuel peuvent être spécifiées de deux façons différentes :
- En indiquant directement la liste des fichiers à utiliser :
/mon_fichier_1.ext /mon_fichier_2.ext
- En indiquant directement la liste des fichiers à utiliser mais en ajoutant le caractère joker
*
:/mon_fichier*.ext
- En utilisant un fichier de configuration listant les fichiers raster à incorporer.
gdalbuildvrt -resolution average -r bilinear -a_srs EPSG:2154 /emplacement/du/fichier/mon_vrt.vrt /emplacement/des/tuiles/tuile_1.asc /emplacement/des/tuiles/tuile_2.asc /emplacement/des/tuiles/ensemble_de_tuiles_supplementaire_*.asc
gdalbuildvrt -resolution average -r bilinear -a_srs EPSG:2154 -input_file_list /emplacement/du/fichier/ma_liste.list /emplacement/du/fichier/mon_vrt.vrt
/emplacement/du/raster/tuile_1.asc /emplacement/du/raster/dalle_2.asc /emplacement/du/raster/dalle_3.asc /emplacement/du/raster/dalle_4.asc
Voici les options principales :
-resolution
:highest|lowest|average|user
Permet de définir la résolution à utiliser si les différentes tuiles n’ont pas la même résolution.-b
: Permet d’indiquer la bande à utiliser, on peut utiliser plusieurs fois cet argument pour utiliser plusieurs bandes.-a_srs
: permet d’indiquer les système de projection à utiliser.-r
:nearest (default)|bilinear|cubic|cubicspline|lanczos|average|mode
méthode de ré-échantillonnage.-input_file_list
: permet de spécifier un fichier contenant la liste des tuiles à utiliser dans le raster virtuel.
Plus d’info sur l’article dédié à GDAL et la documentation officielle de GDAL.
En base : In-DB
Il est possible de stocker les rasters directement dans PostgreSQL. La plupart des utilitaires disponibles avec GDAL sont présent également dans l’extension PostGIS (qui contient en fait un partie de la librairie GDAL).
L’intérêt d’un tel stockage est de pouvoir accéder aux données du raster via des requêtes SQL.
L’affichage des données dans un logiciel type QGIS est légèrement plus lent qu’a partir d’un fichier plat mais la différence reste minime d’après mes tests. Cependant, les traitements opérés par GDAL à partir de raster In-DB sont souvent un peu plus long qu’a partir de fichiers plats.
L’intégration d’un raster en base se fait avec l’utilitaire raster2pgsql (plus d’information sur l’article dédié), disponible avec l’extension PostGIS.
Voici deux exemples de requête d’intégration :
raster2pgsql -C -I -F -M -s 2154 -t 256x256 /emplacement/des/fichiers/mnt_5m.vrt mnt.mnt_5m | psql -h 127.0.0.1 -p 5432 -U postgres -d ma_bdd
raster2pgsql -C -I -F -M -s 2154 -t 256x256 /emplacement/fichier_1.asc /emplacement/fichier_2.asc /emplacement/fichier_3.asc mnt.mnt_5m | psql -h 127.0.0.1 -p 5432 -U postgres -d ma_bdd
Voici quelques options intéressantes :
-C
: création des contraintes raster.-I
: Création d’un index GIST.-F
: Création d’une colonne avec le nom du fichier d’origine.-M
: Vacuum + Analyse sur la table.-s 2154
: Projection du raster source.-t 256x256
: dimension des tuiles.
Utilisez toujours l’option -t nxn
afin de découper le raster en tuiles. Ceci ralenti le processus d’intégration mais accélère grandement vos traitements par la suite.
Attention, l’utilisation de VRT est parfois capricieux, sur mon poste par exemple cela ne fonctionne qu’une fois sur deux (dès que j’aurai compris l’origine du problème je le détaillerai ici). Pensez cependant aux éléments suivants :
- Les droits de lecture sur les fichiers rasters : le processus « raster2pgsql » doit pouvoir lire les fichiers et le VRT.
- Le formatage de l’emplacement des fichiers dans le VRT : chemin relatif ou absolu. Pour moi, seul le chemin relatif fonctionne malgré l’option
relativeToVRT="0"
. - L’emplacement des fichiers raster par rapport au fichier VRT. Pour moi, ils doivent toujours se situer dans le même répertoire.
N’hésitez donc pas à consulter cet article pour en apprendre plus sur l’utilitaire raster2pgsql.
Fichier plat et base : Out-DB
Cette méthode regroupe tout simplement les deux précédentes :
- Les rasters sont stockés sous forme de fichiers plats dans un répertoire.
- Les rasters sont référencés dans la BDD et peuvent donc être interrogés par les fonction PostGIS.
La contrainte est qu’ici, il n’est pas possible de modifier le raster avec les fonction PostGIS, seule la lecture est autorisée.
Voici deux exemples de requête d’intégration :
raster2pgsql -R -C -I -F -M -s 2154 -t 256x256 /emplacement/des/fichiers/mnt_5m.vrt mnt.mnt_5m | psql -h 127.0.0.1 -p 5432 -U postgres -d ma_bdd
raster2pgsql -R -C -I -F -M -s 2154 -t 256x256 /emplacement/fichier_1.asc /emplacement/fichier_2.asc /emplacement/fichier_3.asc mnt.mnt_5m | psql -h 127.0.0.1 -p 5432 -U postgres -d ma_bdd
Les options restent identiques à l’intégration « In-DB », seule la suivante est ajoutée :
-R
: Importer uniquement les métadonnées et l’emplacement des fichiers raster.
Utilisez toujours l’option -t nxn
afin de découper le raster en tuiles. Ceci ralenti le processus d’intégration mais accélère grandement vos traitements par la suite.
Attention, l’utilisation de VRT est parfois capricieux, sur mon poste par exemple cela ne fonctionne qu’une fois sur deux (dès que j’aurai compris l’origine du problème je le détaillerai ici). Pensez cependant aux éléments suivants :
- Les droits de lecture sur les fichiers rasters : le processus « raster2pgsql » doit pouvoir lire les fichiers et le VRT.
- Le formatage de l’emplacement des fichiers dans le VRT : chemin relatif ou absolu. Pour moi, seul le chemin relatif fonctionne malgré l’option
relativeToVRT="0"
. - L’emplacement des fichiers raster par rapport au fichier VRT. Pour moi, ils doivent toujours se situer dans le même répertoire.
N’hésitez donc pas à consulter cet article pour en apprendre plus sur l’utilitaire raster2pgsql.
Traitement des rasters
Il est possible d’extraire tout un tas de données depuis ces fichiers. Voici quelques traitements que j’ai pu tester.
Contours
GDAL
GDAL permet de générer facilement des contours grâce à l’utilitaire gdal_contour.
Pour ma part je stocke mes données en base PostgreSQL mais vous pouvez utiliser un autre stockage.
Fichiers plats
Voici un exemple d’utilisation à partir de fichiers plats :
gdal_contour -b 1 -a altitude -i 10 -nln mon_schema.ma_table_contour -lco GEOMETRY_NAME=geom -f "PostgreSQL" /emplacement/des/fichiers/mnt_5m.vrt PG:"host='127.0.0.1' port='5432' dbname='ma_bdd' user='postgres' password='postgres'"
Voici quelques options intéressantes :
-b 1
: numéro de bande à utiliser.-a altitude
: nom de l’attribut qui stockera la valeur des lignes de contour.-i 10
: le pas entre chaque contour (ici 10 mètre).-nln mon_schema.ma_table_contour
: nom de la couche vecteur en sortie (ici préfixée par le schéma de stockage).-lco GEOMETRY_NAME=geom
: option de création de la couche (vous pouvez utiliser plusieurs fois ce switch pour spécifier plusieurs options). L’option dépend du format de sortie.-f "PostgreSQL"
: format de sortie.
Quelques informations supplémentaire sur les drivers GDAL qui permettent de lire et d’écrire dans différents formats (dont ceux utilisés dans la ligne de commande) :
- L’article dédié à GDAL.
- Liste des drivers rasters
- Liste des drivers vecteurs
- Driver PostgreSQL/PostGIS
- Driver Shapefile
In-DB
Voici un exemple à partir de fichiers stockés In-DB :
gdal_contour -b 1 -a altitude -i 10 -nln mon_schema.ma_table_contour -f "PostgreSQL" PG:"host='127.0.0.1' port='5432' dbname='ma_bdd' user='postgres' password='postgres' schema='mon_schema' table='ma_table_raster_mnt' column='rast' mode=2" PG:"host='127.0.0.1' port='5432' dbname='ma_bdd' user='postgres' password='postgres'"
L’utilisation est similaire mais attention à bien spécifier l’option mode=2
pour la table en entrée. Ceci permet de lire toutes les lignes de la table comme un seul et unique raster et non chaque ligne comme un raster indépendant.
Quelques informations supplémentaire sur les drivers GDAL qui permettent de lire et d’écrire dans différents formats (dont ceux utilisés dans la ligne de commande) :
Post-traitement SQL
Si le traitement est fait sur une emprise importante, il peut en résulter des objets de taille particulièrement importante. Par exemple, j’ai traité d’un seul coup trois départements français (environ 45 min de traitement), le résultat contenait alors des contours pouvant comporter jusqu’à 150 000 sommets. Il devenait alors compliqué d’utiliser ces données, même avec des index.
Voici une requête qui permet de découper les lignes dont la longueur est supérieur à 10 000 sommets :
DO $corps$ DECLARE schema_exe TEXT := 'mon_schema'; -- Table source, contenant les contours de grande taille contour_table_source TEXT := 'table_contour_source_lourd'; -- Table de derstination qui contiendra les contour allégés contour_table_dest TEXT := 'table_contour_destination_leger'; -- Nombre maximum de sommet par objet nb_max_vertex integer := 10000; nb_total_light integer; nb_total_heavy integer; comp_object record; id_tour_prec integer := null; n_tour integer :=1; n_obj integer :=1; n_obj_obj integer :=1; BEGIN RAISE NOTICE USING MESSAGE = 'Création table'; -- Création de la table avec les contours light EXECUTE $a$CREATE TABLE IF NOT EXISTS $a$ || quote_ident(schema_exe) || $b$.$b$ || quote_ident(contour_table_dest) || $c$ ( id serial PRIMARY KEY, old_id integer, altitude float8, geom geometry(LINESTRING, 2154) )$c$ ; -- Insertion des objets simples -- Comptage EXECUTE $a$SELECT count(id) FROM $a$ || quote_ident(schema_exe) || $b$.$b$ || quote_ident(contour_table_source) || $c$ WHERE ST_numpoints(geom) < $c$ || nb_max_vertex INTO nb_total_light ; RAISE NOTICE USING MESSAGE = 'Insertion des objets simples - ' || nb_total_light || ' objets'; -- Insertion proprement dite EXECUTE $a$INSERT INTO $a$ || quote_ident(schema_exe) || $b$.$b$ || quote_ident(contour_table_dest) || $c$ ( old_id, altitude, geom ) SELECT id, altitude, geom FROM $c$ || quote_ident(schema_exe) || $d$.$d$ || quote_ident(contour_table_source) || $e$ WHERE ST_numpoints(geom) < $e$ || nb_max_vertex ; -- Insertion des objets complexes -- Comptage EXECUTE $a$SELECT count(id) FROM $a$ || quote_ident(schema_exe) || $b$.$b$ || quote_ident(contour_table_source) || $c$ WHERE ST_numpoints(geom) >= $c$ || nb_max_vertex INTO nb_total_heavy ; RAISE NOTICE USING MESSAGE = 'Insertion des objets complexes - ' || nb_total_heavy || ' objets'; -- Découpage et insertion -- On boucle sur chaque morceau d'objet "lourd". La découpe est effectuée avec ST_Subdivide FOR comp_object IN EXECUTE $a$SELECT id AS old_id, altitude, ST_Subdivide(geom, $a$ || nb_max_vertex || $b$) AS geom FROM $b$ || quote_ident(schema_exe) || $c$.$c$ || quote_ident(contour_table_source) || $d$ WHERE ST_numpoints(geom) >= $d$ || nb_max_vertex LOOP IF id_tour_prec <> comp_object.old_id THEN n_tour = n_tour + 1; n_obj_obj = 1; END IF; -- Information sur l'avancement du processus RAISE NOTICE USING MESSAGE = ' Traitement objet : '|| n_tour::TEXT || '/' || nb_total_heavy::TEXT || ' (' || round((n_tour::real*100/nb_total_heavy::real)::numeric,1) || '%)' || ' => Nombre d''objet total : ' || n_obj::text; -- Insertion du morceau EXECUTE $a$INSERT INTO $a$ || quote_ident(schema_exe) || $b$.$b$ || quote_ident(contour_table_dest) || $c$ ( old_id, altitude, geom ) SELECT $1, $2, $3 $c$ USING comp_object.old_id, comp_object.altitude, comp_object.geom ; id_tour_prec = comp_object.old_id; n_obj = n_obj+1; n_obj_obj = n_obj_obj+1; END LOOP; RAISE NOTICE USING MESSAGE = 'Création de l''index géométrique '; -- Création d'un index spatial EXECUTE $a$CREATE INDEX $a$ || quote_ident(contour_table_dest || '_geom_idx') || $b$ ON $b$ || quote_ident(schema_exe) || $c$.$c$ || quote_ident(contour_table_dest) || $d$ USING gist (geom) $d$; END $corps$ ;
Ombrage
Très intéressant pour les rendus cartographiques, voici comment générer un raster d’ombrage à partir de votre MNT.
GDAL
GDAL permet de générer facilement des ombrages grâce à l’utilitaire gdaldem.
Pour ma part je stocke mes données raster sous forme de fichiers geotiff mais vous pouvez utiliser un autre stockage.
Fichiers plats
Voici un exemple d’utilisation à partir de fichiers plats :
gdaldem hillshade /emplacement/des/fichiers/mnt_5m.vrt /emplacement/des/fichiers/mnt_5m_hillshade.tiff -az 315 -alt 45 -of "GTiff"
Voici les options intéressantes :
-az 315
: orientation du soleil-alt 45
: inclinaison verticale du soleil-of "GTiff"
: format d’export
Quelques informations supplémentaire sur les drivers GDAL qui permettent de lire et d’écrire dans différents formats (dont ceux utilisés dans la ligne de commande) :
In-DB
Voici un exemple d’utilisation à partir de rasters stockés In-DB :
gdaldem hillshade PG:"host='127.0.0.1' port='5432' dbname='ma_bdd' user='postgres' password='postgres' schema='mon_schema' table='mnt_5m' column='rast' mode=2" /emplacement/des/fichiers/mnt_5m_hillshade.tiff -az 315 -alt 45 -of "GTiff"
L’utilisation est similaire mais attention à bien spécifier l’option mode=2
pour la table en entrée. Ceci permet de lire toutes les lignes de la table comme un seul et unique raster et non chaque ligne comme un raster indépendant.
Quelques informations supplémentaire sur les drivers GDAL qui permettent de lire et d’écrire dans différents formats (dont ceux utilisés dans la ligne de commande) :
PostGIS
Il est possible de créer un raster d’ombrage avec l’aide de PostGIS et de la fonction ST_HillShade.
La table créée est forcément de type raster In-DB, mais la table source, peut être In-DB ou Out-DB :
-- Création du raster d'ombrage CREATE TABLE mon_schema.mnt_5m_hillshade AS SELECT rid, filename, ST_HillShade(rast, 1, '32BF', 315, 45, 255, 1, FALSE) AS rast FROM mon_schema.mnt_5m; COMMENT ON TABLE mon_schema.mnt_5m_hillshade IS 'azimuth=315, altitude=45, max_bright=255, scale=1.0, interpolate_nodata=FALSE'; -- Ajout d'une clé primaire ALTER TABLE mon_schema.mnt_5m_hillshade ADD CONSTRAINT mnt_5m_hillshade_pkey PRIMARY KEY (rid); -- Ajout des contraintes raster SELECT AddRasterConstraints('mon_schema'::name, 'mnt_5m_lucinges_hillshade'::name, 'rast'::name);
La fonction ST_HillShade admet les arguments suivants : ST_HillShade(raster, band, pixeltype, azimuth, altitude, max_bright, scale, interpolate_nodata)
:
raster
: colonne raster sourceband
: numéro de bande à utiliser.pixeltype
: type de pixel du raster final (plus d’information ici).azimuth
: orientation du soleil.altitude
: inclinaison verticale du soleil.max_bright
: Luminosité maximale (de 0 à 255).scale
: facteur multiplicateur verticalinterpolate_nodata
: interpoler les données absente ?
Pente
Très intéressant pour l’analyse cartographique, voici comment générer un raster de pentes à partir de votre MNT.
GDAL
GDAL permet de calculer facilement des pentes grâce à l’utilitaire gdaldem.
Pour ma part je stocke mes données raster sous forme de fichiers geotiff mais vous pouvez utiliser un autre stockage.
Fichiers plats
Voici un exemple d’utilisation à partir de fichiers plats :
gdaldem slope /emplacement/des/fichiers/mnt_5m.vrt /emplacement/des/fichiers/mnt_5m_slope.tiff -of "GTiff"
Voici les options intéressantes :
-p
: pour exprimer la pente en pourcent (par défaut en degrés)-of "GTiff"
: format d’export
Quelques informations supplémentaire sur les drivers GDAL qui permettent de lire et d’écrire dans différents formats (dont ceux utilisés dans la ligne de commande) :
In-DB
Voici un exemple d’utilisation à partir de rasters stockés In-DB :
gdaldem slope PG:"host='127.0.0.1' port='5432' dbname='ma_bdd' user='postgres' password='postgres' schema='mon_schema' table='mnt_5m' column='rast' mode=2" /emplacement/des/fichiers/mnt_5m_slope.tiff -of "GTiff"
L’utilisation est similaire mais attention à bien spécifier l’option mode=2
pour la table en entrée. Ceci permet de lire toutes les lignes de la table comme un seul et unique raster et non chaque ligne comme un raster indépendant.
Quelques informations supplémentaire sur les drivers GDAL qui permettent de lire et d’écrire dans différents formats (dont ceux utilisés dans la ligne de commande) :
PostGIS
Il est possible de créer un raster de pente avec l’aide de PostGIS et de la fonction ST_Slope.
La table créée est forcément de type raster In-DB, mais la table source, peut être In-DB ou Out-DB :
-- Création du raster de pente CREATE TABLE mon_schema.mnt_5m_slope AS SELECT rid, filename, ST_Slope(rast, 1, '32BF', 'DEGREES', 1, FALSE) AS rast FROM mon_schema.mnt_5m; COMMENT ON TABLE mon_schema.mnt_5m_slope IS 'unit=degrees, scale=1.0, interpolate_nodata=FALSE'; -- Ajout d'une clé primaire ALTER TABLE mon_schema.mnt_5m_slope ADD CONSTRAINT mnt_5m_slope_pkey PRIMARY KEY (rid); -- Ajout des contraintes raster SELECT AddRasterConstraints('mon_schema'::name, 'mnt_5m_slope'::name, 'rast'::name);
La fonction ST_Slope admet les arguments suivants : ST_Slope(raster, band, pixeltype, unit, scale, interpolate_nodata)
:
raster
: colonne raster sourceband
: numéro de bande à utiliser.pixeltype
: type de pixel du raster final (plus d’information ici).unit
: unité de la pente : DEGREES, RADIANS ou PERCENT.scale
: facteur multiplicateur verticalinterpolate_nodata
: interpoler les données absente ?
Orientation
Très intéressant pour l’analyse cartographique, voici comment générer un raster d’orientation des pentes à partir de votre MNT.
GDAL
GDAL permet de calculer facilement des orientations grâce à l’utilitaire gdaldem.
Pour ma part je stocke mes données raster sous forme de fichiers geotiff mais vous pouvez utiliser un autre stockage.
Fichiers plats
Voici un exemple d’utilisation à partir de fichiers plats :
gdaldem aspect /emplacement/des/fichiers/mnt_5m.vrt /emplacement/des/fichiers/mnt_5m_aspect.tiff -of "GTiff"
Voici les options intéressantes :
-zero_for_flat
: pour utiliser un angle de 0 degrés pour les surfaces plates au lieu de -9999.-trigonometric
: pour utiliser des angles trigonométriques au lieu d’un azimute.-of "GTiff"
: format d’export
Quelques informations supplémentaire sur les drivers GDAL qui permettent de lire et d’écrire dans différents formats (dont ceux utilisés dans la ligne de commande) :
In-DB
Voici un exemple d’utilisation à partir de rasters stockés In-DB :
gdaldem aspect PG:"host='127.0.0.1' port='5432' dbname='ma_bdd' user='postgres' password='postgres' schema='mon_schema' table='mnt_5m' column='rast' mode=2" /emplacement/des/fichiers/mnt_5m_aspect.tiff -of "GTiff"
L’utilisation est similaire mais attention à bien spécifier l’option mode=2
pour la table en entrée. Ceci permet de lire toutes les lignes de la table comme un seul et unique raster et non chaque ligne comme un raster indépendant.
Quelques informations supplémentaire sur les drivers GDAL qui permettent de lire et d’écrire dans différents formats (dont ceux utilisés dans la ligne de commande) :
PostGIS
Il est possible de créer un raster de pente avec l’aide de PostGIS et de la fonction ST_Aspect.
La table créée est forcément de type raster In-DB, mais la table source, peut être In-DB ou Out-DB :
-- Création du raster d'orientation CREATE TABLE mon_schema.mnt_5m_aspect AS SELECT rid, filename, ST_Aspect(rast, 1, '32BF', 'DEGREES', FALSE) AS rast FROM mon_schema.mnt_5m; COMMENT ON TABLE mon_schema.mnt_5m_aspect IS 'unit=degrees, scale=1.0, interpolate_nodata=FALSE'; -- Ajout d'une clé primaire ALTER TABLE mon_schema.mnt_5m_aspect ADD CONSTRAINT mnt_5m_aspect_pkey PRIMARY KEY (rid); -- Ajout des contraintes raster SELECT AddRasterConstraints('mon_schema'::name, 'mnt_5m_aspect'::name, 'rast'::name);
La fonction ST_Slope admet les arguments suivants : ST_Aspect(raster, band, pixeltype, unit, interpolate_nodata)
:
raster
: colonne raster sourceband
: numéro de bande à utiliser.pixeltype
: type de pixel du raster final (plus d’information ici).unit
: unité de l’orientation : DEGREES ou RADIANS.interpolate_nodata
: interpoler les données absente ?
Conclusions
Après avoir fait beaucoup de tests, j’ai opté pour un stockage Out-DB : les rasters sont sous forme de fichiers plats mais ils sont référencés dans la BDD.
Ceci me permet d’utiliser les fichiers rasters directement dans QGIS mais également de les traiter avec GDAL directement. Cependant, le fait qu’ils soient référencés dans la BDD me permet d’analyser les données raster à partir de mes requêtes SQL. Par exemple pour tracer un profil en long ou calculer le dénivelé d’un tracé.
Cet article vous a plu ?
N'hésitez pas à le partager, il interessera surement certains de vos contacts.
Les thèmes suivants contiennent des articles en lien avec celui-ci, allez faire un tour :
GDAL/OGRPostGISSIG demgdalin-dbmntout-dbpostgisraster