Acquisition d'une vitesse similaire à ArcGIS dans Postgis

62

J'utilise Postgis 2.0 depuis 3/4 d'année et, bien que j'apprécie vraiment de l'utiliser, le temps de traitement excessif des requêtes l'a rendu pratiquement inutilisable pour mon cas d'utilisation.

J'ai tendance à faire du géotraitement lourd sur des jeux de données municipaux qui contiennent souvent des centaines de milliers de multipolygones. Ces multipolygones ont parfois une forme très irrégulière et peuvent varier de 4 à 78 000 points par multipolygone.

Par exemple, lorsque je croise un jeu de données de parcelle avec 329 152 multipolygones avec un jeu de données de juridiction contenant 525 multipolygones, les statistiques suivantes s'affichent pour le temps total consommé:

ArcGIS 10.0 (on same host with windows 7 OS): 3 minutes
Postgis:56 minutes (not including geometry pre-processing queries)

En d'autres termes, cette intersection nécessite 1500% plus de temps que dans ArcGIS - et ceci est l'une de mes requêtes les plus simples!

Une des raisons pour lesquelles ArcGIS est censé fonctionner plus rapidement est due à de meilleurs index. Certains programmeurs ont récemment découvert comment ces index fonctionnent et je me demande si quelqu'un sait comment construire ces index dans Postgis (ou construire des tables qui imiteraient les index). Cela résoudrait peut-être la plupart des problèmes de vitesse dans Postgis. J'espère seulement qu'il y aura un moyen, d'autant plus qu'ArcGIS ne peut utiliser que 4 Go de RAM alors que je pourrais utiliser jusqu'à 4 fois plus pour mon serveur postgis!

Bien sûr, les raisons pour lesquelles postgis peut fonctionner lentement sont nombreuses, je vais donc vous fournir une version détaillée de mes spécifications système:

Machine: Dell XPS 8300 
Processor: i7-2600 CPU @ 3.40 GHz 3.40 GHz 
Memory: Total Memory 16.0 GB (10.0 GB on virtual machine)

Platform: Ubuntu Server 12.04 Virtual Box VM

Potgres Version: 9.1.4
Postgis Version: POSTGIS="2.0.1 r9979" GEOS="3.3.5-CAPI-1.7.5" PROJ="Rel. 4.8.0, 6 March 2012" GDAL="GDAL 1.9.1, released 2012/05/15" LIBXML="2.7.8" LIBJSON="UNKNOWN" TOPOLOGY RASTER

Je détaille également l' ensemble du processus d'installation que j'ai utilisé pour configurer postgis, y compris la création de la machine virtuelle elle-même .

J'ai également augmenté la mémoire partagée de 24 Mo par défaut à 6 Go dans le fichier de configuration et exécuté les commandes suivantes pour permettre à postgres de s'exécuter:

sudo sysctl -w kernel.shmmax=7516192768 (I know this setting is deleted every time you restart the OS)
sudo /etc/init.d/postgresql restart

Autant que je sache, cela ne fait absolument rien de remarquable en termes de performances.

Voici des liens vers les données que j'ai utilisées pour ce test:

  1. Colis: tcad_parcels_06142012.shp.zip à partir de City of Austin, TX
  2. Juridictions: limites juridictionnelles de la ville d'Austin, TX

Voici les étapes que j'ai suivies pour traiter les données:

ArcGIS

  1. Ajouter des jeux de données à ArcMap
  2. Réglez le système de coordonnées sur les pieds centraux du texas (compteur 2277)
  3. Utiliser l'outil d'intersection du menu déroulant

Postgis

Importer des colis en utilisant:

shp2pgsql -c -s 2277 -D -i -I -W UTF-8 "tcad_parcels_06142012.shp" "public"."tcad_parcels_06142012" |psql -d postgis_testing -U postgres -h local_ip -p 5432

Juridictions d'importation utilisant:

shp2pgsql -c -s 2277 -D -i -I -W UTF-8 "jurisdictions.shp" "public"."jurisdictions" |psql -d postgis_testing -U postgres -h local_ip -p 5432

Nettoyer la géométrie non valide dans les colis:

DROP TABLE IF EXISTS valid_parcels;
CREATE TABLE valid_parcels(
  gid serial PRIMARY KEY,
  orig_gid integer,
  geom geometry(multipolygon,2277)
);
CREATE INDEX ON valid_parcels USING gist (geom);
INSERT INTO valid_parcels(orig_gid,geom)
  SELECT 
    gid 
    orig_gid,
    st_multi(st_makevalid(geom)) 
  FROM 
    tcad_parcels_06142012;
CLUSTER valid_parcels USING valid_parcels_geom_idx;

Nettoyer la géométrie non valide dans les juridictions:

DROP TABLE IF EXISTS valid_jurisdictions;
CREATE TABLE valid_jurisdictions(
  gid serial PRIMARY KEY,
  orig_gid integer,
  geom geometry(multipolygon,2277)
);
CREATE INDEX ON valid_jurisdictions USING gist (geom);
INSERT INTO valid_jurisdictions(orig_gid,geom)
  SELECT 
    gid 
    orig_gid,
    st_multi(st_makevalid(geom)) 
  FROM 
    jurisdictions;
CLUSTER valid_jurisdictions USING valid_jurisdictions_geom_idx;

Exécuter le cluster:

cluster;

Exécuter une analyse sous vide:

vacuum analyze;

Effectuer l'intersection sur les tables nettoyées:

CREATE TABLE parcel_jurisdictions(
  gid serial primary key,
  parcel_gid integer,
  jurisdiction_gid integer,
  isect_geom geometry(multipolygon,2277)
);
CREATE INDEX ON parcel_jurisdictions using gist (isect_geom);

INSERT INTO parcel_jurisdictions(parcel_gid,jurisdiction_gid,isect_geom)
  SELECT
    a.orig_gid parcel_gid,
    b.orig_gid jurisdiction_gid,
    st_multi(st_intersection(a.geom,b.geom))
  FROM
    valid_parcels a, valid_jurisdictions b
  WHERE
    st_intersects(a.geom,b.geom);

Expliquez la requête d'analyse d'intersection:

Total runtime: 3446860.731 ms
        Index Cond: (geom && b.geom)
  ->  Index Scan using valid_parcels_geom_idx on valid_parcels a  (cost=0.00..11.66 rows=2 width=1592) (actual time=0.030..4.596 rows=1366 loops=525)
  ->  Seq Scan on valid_jurisdictions b  (cost=0.00..113.25 rows=525 width=22621) (actual time=0.009..0.755 rows=525 loops=1)
Nested Loop  (cost=0.00..61428.74 rows=217501 width=24213) (actual time=2.625..3445946.889 rows=329152 loops=1)
  Join Filter: _st_intersects(a.geom, b.geom)

D'après tout ce que j'ai lu, ma requête d'intersection est efficace et je n'ai absolument aucune idée de ce que je fais mal. La requête prend 56 minutes en géométrie pure!

THX1138
la source
2
Dans PostGIS, il est courant d’ajouter une vérification de l’intersection du cadre de sélection pour accélérer les choses. Essayez d'ajouter 'AND a.geom && b.geom' à votre clause WHERE et voyez à quel point cela fait une différence.
Sean
2
st_intersects () inclut une requête de boîte englobante avant d'effectuer tout test d'intersection dans postgis 2.x, ce qui ne vous fera malheureusement pas gagner du temps.
THX1138
1
Pouvez-vous exécuter votre requête avec EXPLAIN ANALYSE et publier les résultats
Nathan W
1
vous devez également savoir que vous utilisez différents ensembles de données sur postgis vs arcgis puisque vous dites que vous ne voulez pas les rendre valides pour être acceptés par postgis.
Nicklas Avén
2
Est-il possible de faire examiner les ensembles de données?
Nicklas Avén

Réponses:

87

Une approche différente. Sachant que la douleur est dans ST_Intersection et que les tests vrai / faux sont rapides, essayer de minimiser la quantité de géométrie traversant l'intersection pourrait accélérer les choses. Par exemple, les parcelles qui sont totalement contenues dans une juridiction n'ont pas besoin d'être écrêtées, mais ST_Intersection continuera probablement à créer une partie de la superposition d'intersection avant de réaliser qu'elle ne génère pas de nouvelle géométrie. Donc ça

INSERT INTO parcel_jurisdictions(parcel_gid,jurisdiction_gid,isect_geom)
SELECT
  a.orig_gid AS parcel_gid,
  b.orig_gid AS jurisdiction_gid,

  st_multi(st_intersection(a.geom,b.geom)) AS geom
FROM
  valid_parcels a, valid_jurisdictions b
WHERE
  st_intersects(a.geom, b.geom) and not st_within(a.geom, b.geom)
UNION ALL
SELECT
  a.orig_gid AS parcel_gid,
  b.orig_gid AS jurisdiction_gid,
  a.geom AS geom
FROM
  valid_parcels a, valid_jurisdictions b
WHERE
  st_within(a.geom, b.geom);

Ou même terser

INSERT INTO parcel_jurisdictions(parcel_gid,jurisdiction_gid,isect_geom)
SELECT
  a.orig_gid AS parcel_gid,
  b.orig_gid AS jurisdiction_gid,
  CASE 
     WHEN ST_Within(a.geom,b.geom) 
     THEN a.geom
     ELSE ST_Multi(ST_Intersection(a.geom,b.geom)) 
  END AS geom
FROM valid_parcels a
JOIN valid_jurisdictions b
ON ST_Intersects(a.geom, b.geom)

Peut-être même être plus rapide sans l’UNION.

Paul Ramsey
la source
13
Merci cela me fait, 3,63 minutes! Je n'aurais jamais pensé qu'un syndicat serait plus rapide. Cette réponse va vraiment me faire repenser ma façon de faire des requêtes à partir de maintenant.
THX1138
2
C'est très cool. J'ai eu un cas au travail où ma requête st_intersection a été prendre 30mins + et maintenant je sais comment je peux éviter ça :)
Nathan W
1
cette question m'a fait apprendre Postgis! Je vais bien dormir aujourd'hui en voyant Postgis courir côte à côte avec Arcgis :-)
vinayan
2
Une autre amélioration de Martin Davis, vous pouvez insérer le "in ou out?" question dans SELECT en utilisant une instruction CASE et éviter ainsi l’UNION.
Paul Ramsey
2
UNIONélimine les lignes en double des deux requêtes, mais ces deux requêtes ne peuvent pas avoir la même ligne dans leur jeu de résultats. Donc UNION ALL, ce qui évite le double contrôle, serait approprié ici. (Je ne l'utilise pas UNIONbeaucoup, mais en général, je trouve que je l'utilise beaucoup plus souvent UNION ALL.)
jpmc26
4

Que se passerait-il si vous omettiez la "st_multi(st_intersection(a.geom,b.geom))"pièce?

La requête ci-dessous ne signifie-t-elle pas la même chose sans elle? Je l'ai couru sur les données que vous avez fournies.

INSERT INTO parcel_jurisdictions(parcel_gid,jurisdiction_gid,isect_geom)
  SELECT
    a.orig_gid parcel_gid,
    b.orig_gid jurisdiction_gid,
    a.geom
  FROM
    valid_parcels a, valid_jurisdictions b
  WHERE
    st_intersects(a.geom,b.geom);

Configuration

Processor: AMD Athlon II X4 635 2.9 GHz 
Memory: 4 GB
Platform: Windows 7 Professional
Potgres Version: 8.4
Postgis Version: "POSTGIS="2.0.1 r9979" GEOS="3.3.5-CAPI-1.7.5" PROJ="Rel. 4.8.0, 6 March 2012" GDAL="GDAL 1.9.1, released 2012/05/15" LIBXML="2.7.8" LIBJSON="UNKNOWN" TOPOLOGY RASTER"

Analyser les résultats

"Nested Loop  (cost=0.00..7505.18 rows=217489 width=1580) (actual time=1.994..248405.616 rows=329150 loops=1)"
"  Join Filter: _st_intersects(a.geom, b.geom)"
"  ->  Seq Scan on valid_jurisdictions b  (cost=0.00..37.25 rows=525 width=22621) (actual time=0.054..1.732 rows=525 loops=1)"
"  ->  Index Scan using valid_parcels_index on valid_parcels a  (cost=0.00..11.63 rows=2 width=1576) (actual time=0.068..6.423 rows=1366 loops=525)"
"        Index Cond: (a.geom && b.geom)"
"Total runtime: 280087.497 ms"
vinayan
la source
Non, il veut les polygones d'intersection résultants, mais votre requête démontre très bien que toute la douleur réside dans la génération d'intersection, pas dans la partie test binaire vrai / faux de la requête. Et c'est tout à fait attendu, car le code vrai / faux est hautement optimisé alors que la génération d'intersection ne l'est pas.
Paul Ramsey