Acquisizione della velocità simile ad ArcGIS in Postgis


62

Ormai uso Postgis 2.0 da 3/4 di anno e, sebbene mi diverta molto, l'eccessivo tempo di elaborazione delle query lo ha reso sostanzialmente inutilizzabile per il mio caso d'uso.

Tendo a fare un forte geoprocessing su set di dati comunali che spesso hanno centinaia di migliaia di multipoligoni. Questi multipoligoni sono talvolta modellati in modo molto irregolare e possono variare da 4 punti a 78.000 punti per multipoligono.

Ad esempio, quando interseco un set di dati del pacco con 329.152 multipoligoni con un set di dati della giurisdizione contenente 525 multipoligoni, ottengo le seguenti statistiche per il tempo totale impiegato:

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

In altre parole, ci vuole il 1500% di tempo in più per fare questo incrocio in Postgis che in ArcGIS - e questa è una delle mie domande più semplici!

Uno dei motivi per cui ArcGIS è presumibilmente più veloce è dovuto a indici migliori. Alcuni programmatori hanno recentemente scoperto come funzionano questi indici e mi chiedo se qualcuno sappia come costruire questi indici in Postgis (o costruire tabelle che imitino gli indici). Forse questo risolverebbe la maggior parte dei problemi di velocità in Postgis. Posso solo sperare che ci debba essere un modo, soprattutto perché ArcGIS può usare solo 4 GB di RAM mentre potrei usare fino a 4 volte quello per il mio server Postgis!

Naturalmente ci sono molte ragioni per cui i postgis possono funzionare lentamente, quindi fornirò una versione dettagliata delle mie specifiche di sistema:

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

Descrivo anche in dettaglio l' intero processo di installazione che ho usato per impostare Postgis, compresa la creazione della VM stessa .

Ho anche aumentato la memoria condivisa dai 24 MB predefiniti a 6 GB nel file conf ed eseguito i seguenti comandi per consentire l'esecuzione di postgres:

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

Per quanto posso dire, questo non fa assolutamente nulla di evidente in termini di prestazioni.

Ecco i collegamenti ai dati che ho usato per questo test:

  1. Pacchi: tcad_parcels_06142012.shp.zip da City of Austin, TX
  2. Giurisdizioni: Confini giurisdizionali da City of Austin, TX

Ecco i passaggi che ho preso per elaborare i dati:

ArcGIS

  1. Aggiungi set di dati ad ArcMap
  2. Imposta il sistema di coordinate sui piedi centrali del Texas (srid 2277)
  3. Utilizza lo strumento Intersezione dal menu a discesa

PostGIS

Importa i pacchi utilizzando:

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

Giurisdizione di importazione utilizzando:

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

Pulisci geometria non valida nei pacchi:

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;

Pulisci geometria non valida nelle giurisdizioni:

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;

Esegui cluster:

cluster;

Esegui analisi vuoto:

vacuum analyze;

Esegui intersezione su tabelle pulite:

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);

Spiegare Analizza query intersezione:

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)

Da tutto ciò che ho letto, la mia query di intersezione è efficiente e non ho assolutamente idea di cosa sto facendo di sbagliato perché la query impieghi 56 minuti su una geometria pulita!


2
In PostGIS è un linguaggio comune aggiungere un controllo di intersezione del riquadro di selezione per velocizzare le cose. Prova ad aggiungere "AND a.geom && b.geom" alla tua clausola WHERE e vedi quanta differenza fa.
Sean,

2
st_intersects () include una query sul riquadro di delimitazione prima di eseguire qualsiasi test di intersezione in postgis 2.x, quindi sfortunatamente non si risparmia tempo.
THX1138,

1
Puoi eseguire la tua query utilizzando EXPLAIN ANALYZE e pubblicare i risultati
Nathan W

1
dovresti anche essere consapevole del fatto che stai eseguendo diversi set di dati su Postgis vs Arcgis poiché dici che neex per renderli validi per essere accettati con Postgis.
Nicklas Avén,

2
È possibile ottenere i set di dati per dare un'occhiata?
Nicklas Avén,

Risposte:


87

Approccio diverso. Sapendo che il dolore è in ST_Intersection e che i test vero / falso sono veloci, provare a minimizzare la quantità di geometria che passa attraverso l'intersezione potrebbe accelerare le cose. Ad esempio, i pacchi totalmente contenuti in una giurisdizione non devono essere ritagliati, ma ST_Intersection probabilmente continuerà a preoccuparsi di costruire parte della sovrapposizione di intersezione prima di rendersi conto che non deve generare alcuna nuova geometria. Così questo

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);

O addirittura 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)

Potrebbe anche essere più veloce senza l'UNIONE.


13
Grazie che mi porta a 3,63 minuti! Non avrei mai pensato che un'unione sarebbe stata più veloce. Questa risposta mi farà davvero ripensare il modo in cui faccio le query da ora in poi.
THX1138,

2
Questo è molto bello. Ho avuto un caso al lavoro in cui la mia query st_intersection era di 30 minuti + e ora so come posso evitarlo :)
Nathan W

1
questa domanda mi ha fatto imparare Postgis! Dormirò bene oggi vedendo Postgis correre spalla a spalla con Arcgis :-)
vinayan

2
Un altro miglioramento di Martin Davis, potresti inserire "dentro o fuori?" interrogare in SELECT usando un'istruzione CASE ed evitare l'unione in quel modo.
Paul Ramsey,

2
UNIONelimina le righe duplicate dalle due query, ma queste due query non possono avere la stessa riga nel loro set di risultati. Quindi UNION ALL, che salta il controllo duplicato, sarebbe appropriato qui. (Non uso UNIONmolto, ma in genere lo trovo fuori dai tempi che uso, lo uso molto più frequentemente UNION ALL.)
jpmc26

4

Cosa succederebbe se si omettesse la "st_multi(st_intersection(a.geom,b.geom))"parte?

La query seguente non significa la stessa cosa senza di essa? L'ho eseguito sui dati forniti.

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);

Configurazione

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"

Analizza i risultati

"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"

No, vuole i poligoni di intersezione risultanti, ma la tua query dimostra molto bene che tutto il dolore è nella generazione dell'intersezione, non nella parte binaria di test vero / falso della query. E questo è abbastanza atteso, poiché il codice vero / falso è altamente ottimizzato mentre la generazione dell'intersezione non lo è.
Paul Ramsey,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.