Cómo replicar ArcGIS Intersect en PostGIS

8

Estoy tratando de replicar este proceso de ArcGIS en PostGIS: http://blogs.esri.com/esri/arcgis/2012/11/13/spaghetti_and_meatballs/ . Esto describe cómo dividir los puntos almacenados en polígonos según sus intersecciones, contando el número de capas y atribuyéndolo a los polígonos para clasificarlos. Lo estoy usando para crear un mapa de densidad de punto aproximado con vectores, y los resultados fueron sorprendentemente buenos para mi conjunto de datos en ArcGIS. Sin embargo, estoy luchando por encontrar algo viable en PostGIS donde lo necesito para producir capas de densidad de puntos dinámicos para un mapa web.

En ArcGIS, simplemente ejecuté la herramienta Intersecar en mi capa de puntos almacenados para crear las formas que necesitaba.

En PostGIS, ejecuté esta consulta:

CREATE TABLE buffer_table AS SELECT a.gid AS gid, ST_Buffer(a.geo,.003) AS geo FROM public.pointTable a;

CREATE TABLE intersections AS SELECT a.gid AS gid_a, b.gid AS gid_b, ST_Intersection(a.geo,b.geo) AS geo FROM public.pointTable a, public.pointTable b WHERE ST_Intersects(a.geo, b.geo) AND a.gid < b.gid;

DELETE FROM intersections WHERE id_a = id_b;

La salida se ve bastante idéntica a la salida de ArcGIS, excepto que no está desglosando los polígonos en la misma medida que se requiere para un mapa de densidad significativo. Aquí hay capturas de pantalla de lo que quiero decir:

ArcGIS PostGIS

ArcGIS está a la izquierda y PostGIS está a la derecha. Es un poco difícil de distinguir, pero la imagen de ArcGIS muestra el polígono 'interior' creado donde se cruzan los 3 búferes. La salida de PostGIS, por otro lado, no crea ese polígono interior y, en cambio, mantiene sus componentes intactos. Esto hace que sea imposible proporcionar una clasificación solo para esa área interior con 3 capas una encima de la otra en comparación con solo 1 para las partes externas.

¿Alguien sabe de alguna función PostGIS para descomponer el polígono en la medida que necesito? Alternativamente, ¿alguien sabe de una mejor manera de producir un mapa de densidad de puntos con vectores en PostGIS?

Scavok
fuente

Respuestas:

9

Puede hacer todo esto en un paso encadenando los CTE juntos, pero lo hice en varios para poder ver los resultados en QGIS a medida que avanzaba.

Primero, genere un montón de puntos aleatorios para trabajar, utilizando una distribución gaussiana para que tengamos más superposición en el medio.

create table pts as with 
    rands as (
        select generate_series as id, random() as u1, random() as u2 
        from generate_series(1,100))
select
      id,
      st_setsrid(st_makepoint(
            50 * sqrt(-2 * ln(u1)) * cos(2*pi()*u2), 
            50 * sqrt(-2 * ln(u1)) * sin(2*pi()*u2)),4326) as geom
from rands;

Ahora amortigua los puntos en círculos para que tengamos cierta superposición.

create table circles as
    select id, st_buffer(geom, 10) as geom from pts;

Ahora, extraiga solo los límites de los círculos. Si tiene polígonos con agujeros, deberá usar ST_DumpRings () y obtener más fantasía aquí. Tengo polígonos simples, así que hago trampa. Una vez que tenga los límites, únalos contra sí mismos (en realidad, cualquier pequeña pieza de línea coincidente servirá) para forzarlos a ser anudados y deduplicados. (Esto es magia.)

create table boundaries as
    select st_union(st_exteriorring(geom)) as geom from circles;

Ahora reconstruya las áreas utilizando la línea de nudos. Estas son las áreas desglosadas, con solo un polígono por área. Después de poligonalizar, volcar los polígonos individuales de la salida multipolígono.

create sequence polyseq;

create table polys as
    select 
        nextval('polyseq') as id, 
        (st_dump(st_polygonize(geom))).geom as geom 
    from boundaries;

Ahora, agregue un lugar para el recuento de polígonos y llénelo uniendo los centroides de los polígonos pequeños cortados a los círculos originales y resumiendo para cada pieza pequeña. Para conjuntos de datos más grandes, al menos se requerirá un índice en la tabla de círculos para que las cosas no sean increíblemente lentas.

create index circles_gix on circles using gist (geom);

alter table polys add column count integer default 0;

update polys set count = p.count 
from (
    select count(*) as count, 
           p.id as id 
    from polys p 
    join circles c 
    on st_contains(c.geom, st_pointonsurface(p.geom)) 
    group by p.id
) as p
where p.id = polys.id;

Eso es todo, ahora no tiene polígonos superpuestos, pero cada polígono resultante tiene un recuento que indica cuántas superposiciones está representando.

Paul Ramsey
fuente
Eso es muy impresionante. Terminé usando un poco de un método de trampa que funcionó y con mi conjunto de datos particular (también podría ser menos intensivo en recursos, lo cual es importante para mi proyecto que implica mapeo web). Publicaré mi solución como un método alternativo para producir un mapa de calor, pero esta es la respuesta correcta a la pregunta que hice.
scavok
2

El método que terminé usando fue crear una cuadrícula de rejilla en mi área de interés con una "resolución" lo suficientemente alta como para diseñar y reflejar los datos en un grado razonable. Puede leer sobre la función de red aquí: ¿Cómo crear una cuadrícula poligonal regular en PostGIS?

CREATE TABLE fishnet AS
SELECT * FROM ST_CreateFishnet(800,850,.0005,.0005,-104.9190,38.7588);

Esto crea la red con 800 filas, 850 columnas, que son 0.0005 radianes de altura y longitud (usando la proyección WGS84 en lat / long y es una extensión geográfica lo suficientemente pequeña como para que la distorsión sea insignificante, es decir, todas están distorsionadas más o menos por igual) ), y luego las coordenadas de la esquina inferior izquierda de la cuadrícula.

UPDATE fishnet SET geom = ST_SetSRID(geom,4326);
CREATE INDEX fishnet_geom ON fishnet USING gist (geom);
ANALYZE fishnet;

Debido a que esto creó una gran cantidad de polígonos que tendrán consultas ejecutadas en ellos, creé un índice y actualicé las estadísticas. Esto redujo mis consultas típicas de más de 50 segundos a 4-5 segundos.

SELECT ST_Union(a.geom), a.count
FROM (SELECT count(*) as count, fishnet.geom as geom
    FROM fishnet, incidents
    WHERE ST_DWithin(incidents.geo,fishnet.geom,.002) AND (incidents.incidenttype = 'Burglary')
    GROUP BY fishnet.geom) a
WHERE a.count >= 3
GROUP BY a.count;

La subconsulta aquí cuenta el número de incidentes dentro de .002 radianes (aproximadamente 220 metros) de cada polígono de rejilla de rejilla, y los agrupa por la rejilla de rejilla. Esto cuenta efectivamente el número de círculos superpuestos a la resolución de la cuadrícula.

La consulta externa que utilicé para unir el valor de recuento de cada polígono y restringir el recuento a 3 o más. Si bien la unión no es estrictamente necesaria y es la parte más intensiva de recursos de la consulta, es fundamental para el mapeo web, ya que efectivamente convierte decenas de miles de polígonos de cuadrícula, lo que no funciona demasiado bien cuando se sirve directamente a capas abiertas, en multipolígonos de los diferentes valores de conteo que existen (generalmente unas pocas docenas para mis datos).

Restringir el valor de conteo es una capacidad importante para los mapas de calor para que no muestren demasiados datos hasta el punto de no poder interpretarlos; también tiene la utilidad adicional de acelerar significativamente la consulta.

Resultado final: mapa

Scavok
fuente