La raison d'être des fonctionnalités spatiales des base de données est de vous permettre d'effectuer des requêtes qui requièrent habituellement des outils SIG bureautiques. Utiliser PostGIS de manière efficace nécessite de connaitre qu'elles sont les fonctions spatiales disponibles et de s'assurer que les indexes appropriés sont en place pour obtenir de bonne performances.
Lorsque vous construisez une requête il est important de se souvenir que seuls les opérateurs du cadre-limite comme &&
tirent avantage des indexes spatiaux GIST. Les fonctions comme distance()
ne peuvent pas utiliser les indexes pour optimiser leur opérations. Par exemple, la requête suivante pourrait être légèrement lente sur une grosse table :
SELECT the_geom FROM geom_table WHERE distance( the_geom, GeomFromText( 'POINT(100000 200000)', -1 ) ) < 100
Cette requête sélectionne toutes les géométries de
geom_table
qui sont dans un rayon de 100 unités du point (100000, 200000). Elle est lente parce qu'elle calcule la distance entre chaque point de la table et le point que nous avons spécifié, par exemple un calcul de
distance()
pour chaque enregistrement de la table. Nous pouvons éviter cela en utilisant l'opérateur
&&
pour réduire le nombre de distance requit :
SELECT the_geom FROM geom_table
WHERE the_geom && 'BOX3D(90900 190900, 100100 200100)'::box3d
AND distance( the_geom, GeomFromText( 'POINT(100000 200000)', -1 ) ) < 100
Cette requête sélectionne les même géométries, mais il le fait de manière plus efficace. Supposons qu'il existe un indexe GiST sur
the_geom
, le planificateur de requêtes constatera qu'il peut utiliser l'indexe pour réduire le nombre de ligne avant de calculer le résultat de la fonction
distance()
. Notez que la géométrie
BOX3D
qui est utilisé dans l'opération
&&
est un cadre carré de 200 unités centré sur le point original - c'est notre "cadre de requête". L'opérateur
&&
utilise l'indexe pour réduire rapidement l'ensemble résultant aux géométries qui ont un cadre limite qui recouvre le "cadre de requête". En supposant que notre "cadre de requête" est plus petit que l'extension de la totalité de la table, cela réduira considérablement le nombre de calculs de distance qui devra être effectué.
depuis PostGIS 1.3.0, la plupart des fonctions relationnelles, à l'exception de ST_Disjoint
and ST_Relate
, utilisent implicitement l'opérateur de superposition du cadre limite.
haut de la page | table des matières Les exemples de cette section utiliseront deux tables, une table de routes linéaires, et une table de limite polygonales des municipalités. La définition pour la table bc_routes est la suivante :
Column | Type | Description
---------+-------------------+-------------------
gid | integer | Unique ID
name | character varying | Road Name
the_geom | geometry | Location Geometry (Linestring)
La définition pour la table bc_minicipalitees est la suivante :
Column | Type | Description
-----------+-------------------+-------------------
gid | integer | Unique ID
code | integer | Unique ID
name | character varying | City / Town Name
the_geom | geometry | Location Geometry (Polygon)
haut de la page | table des matières
Vous pouvez répondre à cette question avec une requête SQL simple :
postgis=# SELECT sum(length(the_geom))/1000 AS longueur_routes_en_km FROM bc_routes :
longueur_routes_en_km
------------------
70842.1243039643
(1 row)
haut de la page | table des matières Cette requête combine une condition attributaire (sur le nom de la municipalité) avec un calcul spatial (de l'aire) :
postgis=# SELECT area(the_geom)/10000 AS hectares FROM bc_municipalite WHERE name = 'PRINCE GEORGE';
hectares
------------------
32657.9103824927
(1 row)
haut de la page | table des matières Cette requête utilise une mesure spatiale comme condition. Il y a plusieurs manières d'approcher ce problème, mais la plus efficace est la suivante :
postgis=# SELECT name, area(the_geom)/10000 AS hectares
FROM bc_municipalite
ORDER BY hectares DESC LIMIT 1;
name | hectares
---------------+-----------------
TUMBLER RIDGE | 155020.02556131
(1 row)
Vous remarquerez que pour répondre à cette question nous devons calculer l'aire de tous les polygones. Si nous faisons souvent cela, il serait judicieux d'ajouter une colonne "aire" à la table que nous pourrions alors indexer pour plus de performance. En ordonnant le résultat par ordre décroissant, et en utilisant la commende LIMIT
de PostgreSQL nous pouvons aisément récupérer la plus grande valeur sans avoir à utiliser de fonction d'agrégation comme max()
.
haut de la page | table des matières Ceci est un exemple de "jointure spatiale', parce que nous utilisons les données de deux tables (en faisant une jointure) mais en utilisant une condition d'interaction spatiale ("contenue") comme condition de jointure au lieu d'utiliser l'approche relationnelle habituelle de jointure sur une clef commune :
postgis=# SELECT m.name, sum(length(r.the_geom))/1000 as roads_km
FROM bc_roads AS r,bc_municipality AS m
WHERE r.the_geom && m.the_geom
AND contains(m.the_geom,r.the_geom)
GROUP BY m.name
ORDER BY roads_km;
name | roads_km
----------------------------+------------------
SURREY | 1539.47553551242
VANCOUVER | 1450.33093486576
LANGLEY DISTRICT | 833.793392535662
BURNABY | 773.769091404338
PRINCE GEORGE | 694.37554369147
...
Cette requête dure un moment, parce que chaque route de la table est disponible dans le résultat final (environ 250K routes pour notre table d'exemple). Pour des couvertures plus petites (quelques milliers ou centaines d'enregistrements) la réponse peut être très rapide.
haut de la page | table des matières Ceci est un exemple de "couverture", qui extrait les données de deux tables et retourne une nouvelle table qui contient l'ensemble des résultantes incluses ou partiellement coupées. Contrairement à la "jointure spatiale" présentée précédemment, cette requête crée de nouvelles géométries. Une couverture est comme une jointure spatiale turbo-cached, et est utile pour un travail d'analyse plus précis :
postgis=# CREATE TABLE pg_roads as
SELECT intersection(r.the_geom, m.the_geom) AS intersection_geom,
length(r.the_geom) AS rd_orig_length,
r.*
FROM bc_roads AS r, bc_municipality AS m
WHERE r.the_geom && m.the_geom
AND intersects(r.the_geom, m.the_geom)
AND m.name = 'PRINCE GEORGE';
haut de la page | table des matières
postgis=# SELECT gid, name, area(the_geom) AS area
FROM bc_municipality
WHERE nrings(the_geom) > 1
ORDER BY area DESC LIMIT 1;
gid | name | area
-----+--------------+------------------
12 | SPALLUMCHEEN | 257374619.430216
(1 row)
haut de la page | table des matières