Adquisición de velocidad similar a ArcGIS en Postgis

62

He estado usando Postgis 2.0 durante 3/4 de año y aunque realmente disfruto usarlo, el tiempo excesivo de procesamiento de consultas lo ha dejado básicamente inutilizable para mi caso de uso.

Tiendo a hacer geoprocesamiento pesado en conjuntos de datos municipales que a menudo tienen cientos de miles de multipolígonos. Estos multipolígonos a veces tienen una forma muy irregular y pueden variar de 4 puntos a 78,000 puntos por multipolígono.

Por ejemplo, cuando interseco un conjunto de datos de parcela con 329,152 multipolígonos con un conjunto de datos de jurisdicción que contiene 525 multipolígonos, obtengo las siguientes estadísticas para el tiempo total consumido:

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

En otras palabras, se requiere un 1500% más de tiempo para hacer esta intersección en Postgis que en ArcGIS, ¡ y esta es una de mis consultas más simples!

Una de las razones por las que ArcGIS supuestamente se ejecuta más rápido se debe a mejores índices. Algunos programadores descubrieron recientemente cómo funcionan estos índices y me pregunto si alguien sabe cómo construir estos índices en Postgis (o crear tablas que imiten los índices). Quizás esto resolvería la mayoría de los problemas de velocidad en Postgis. Solo puedo esperar que haya alguna forma, especialmente porque ArcGIS solo puede usar 4 GB de RAM, ¡mientras que podría usar hasta 4 veces más para mi servidor postgis!

Por supuesto, hay muchas razones por las que postgis puede ejecutarse lentamente, por lo que proporcionaré una versión detallada de las especificaciones de mi 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

También detallo todo el proceso de instalación que utilicé para configurar postgis, incluida la creación de la propia máquina virtual .

También aumenté la memoria compartida de los 24 MB predeterminados a 6 GB en el archivo conf y ejecuté los siguientes comandos para permitir la ejecución de 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

Por lo que puedo decir, esto no hace absolutamente nada notable en términos de rendimiento.

Aquí hay enlaces a los datos que utilicé para esta prueba:

  1. Paquetes: tcad_parcels_06142012.shp.zip de la ciudad de Austin, TX
  2. Jurisdicciones: límites jurisdiccionales de la ciudad de Austin, TX

Estos son los pasos que tomé para procesar los datos:

ArcGIS

  1. Agregar conjuntos de datos a ArcMap
  2. Establecer el sistema de coordenadas en pies centrales de texas (srid 2277)
  3. Usar la herramienta de intersección del menú desplegable

Postgis

Importar paquetes usando:

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

Jurisdicciones de importación utilizando:

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

Limpiar geometría inválida en parcelas:

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;

Limpiar geometría inválida en jurisdicciones:

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;

Ejecutar clúster:

cluster;

Ejecute el análisis de vacío:

vacuum analyze;

Realizar intersección en mesas limpias:

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

Explicar Analizar consulta de intersección:

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)

De todo lo que he leído, mi consulta de intersección es eficiente y no tengo ni idea de lo que estoy haciendo mal para que la consulta tarde 56 minutos en geometría limpia.

THX1138
fuente
2
Es un idioma común en PostGIS agregar una verificación de intersección de cuadro delimitador para acelerar las cosas. Intente agregar 'AND a.geom && b.geom' a su cláusula WHERE y vea qué diferencia hace.
Sean
2
st_intersects () incluye una consulta de cuadro delimitador antes de realizar cualquier prueba de intersección en postgis 2.x, así que desafortunadamente eso no ahorrará tiempo.
THX1138
1
¿Puede ejecutar su consulta utilizando EXPLAIN ANALYZE y publicar los resultados
Nathan W
1
También debe tener en cuenta que está ejecutando diferentes conjuntos de datos en postgis vs arcgis ya que dice que debe hacerlos válidos para ser aceptados vy postgis.
Nicklas Avén
2
¿Es posible obtener los conjuntos de datos para echar un vistazo?
Nicklas Avén

Respuestas:

87

Enfoque diferente. Sabiendo que el dolor está en ST_Intersection, y que las pruebas de verdadero / falso son rápidas, tratar de minimizar la cantidad de geometría que pasa por la intersección podría acelerar las cosas. Por ejemplo, las parcelas que están totalmente contenidas en una jurisdicción no necesitan ser recortadas, pero ST_Intersection probablemente todavía se tomará la molestia de construir parte de la superposición de intersección antes de darse cuenta de que no tiene que generar ninguna geometría nueva. Así que esto

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

Incluso podría ser más rápido sin la UNIÓN.

Paul Ramsey
fuente
13
Gracias, eso me lleva a 3.63 minutos. Nunca hubiera pensado que una unión sería más rápida. Esta respuesta realmente me hará repensar la forma en que hago consultas a partir de ahora.
THX1138
2
Esto es muy genial. Tuve un caso en el trabajo donde mi consulta de st_intersection fue de 30 minutos y ahora sé cómo puedo evitar eso :)
Nathan W
1
¡Esta pregunta me hizo aprender Postgis! Hoy dormiré bien viendo a Postgis correr hombro con hombro con Arcgis :-)
vinayan
2
Una mejora más de Martin Davis, podría incluir "¿dentro o fuera?" pregunta en SELECT utilizando una declaración CASE y evite UNION de esa manera.
Paul Ramsey
2
UNIONelimina filas duplicadas de las dos consultas, pero estas dos consultas no pueden tener la misma fila en su conjunto de resultados. Entonces UNION ALL, lo que omite la verificación duplicada, sería apropiado aquí. (No uso UNIONmucho, pero generalmente encuentro que fuera de las veces que lo hago, uso con mucha más frecuencia UNION ALL.)
jpmc26
4

¿Qué pasaría si omites la "st_multi(st_intersection(a.geom,b.geom))"parte?

¿La consulta a continuación no significa lo mismo sin ella? Lo ejecuté con los datos que proporcionó.

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

Configuración

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"

Analizar resultados

"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
fuente
No, él quiere los polígonos de intersección resultantes, pero su consulta demuestra muy bien que todo el dolor está en la generación de intersección, no en la parte de prueba binaria verdadero / falso de la consulta. Y eso es bastante esperado, ya que el código verdadero / falso está altamente optimizado mientras que la generación de intersección no lo está.
Paul Ramsey