Come creare una griglia di punti regolare all'interno di un poligono in Postgis?


31

Come creare, all'interno di un poligono, una griglia regolare di punti distanziati x, y in postgis? Come nell'esempio:

testo alternativo


Ho provato a fare dei poligoni di ritaglio unendo questo codice con il codice di taglio "postGIS in azione" ma è stato creato un solo poligono ... Ho dimenticato qualcosa? FUNZIONE CREA O SOSTITUISCI makegrid (geometria, intero, intero) INVIA la geometria COME SELEZIONA st_intersection (g1.geom1, g2.geom2) COME geom DA (SELEZIONA $ 1 AS geom1) COME g1 INNER JOIN (Seleziona st_setsrid (CAST (ST_MakeBox2d (st_setsrid) ST_Point (x, y), $ 3), st_setsrid (ST_Point (x + $ 2, y + $ 2), $ 3)) come geometria), $ 3) come geom2 FROM generate_series (floor (st_xmin ($ 1)) :: int, ceiling ( st_xmax ($ 1)) :: int, $ 2) as x, generate_series (floor (st_ymin ($ 1)) :: int, ceiling (st_ymax (
aurel_nc

guarda la mia risposta dettagliata.
Muhammad Imran Siddique,

Risposte:


30

Lo fai con generate_series.

Se non si desidera scrivere manualmente dove la griglia deve iniziare e arrestarsi, il modo più semplice è creare una funzione.

Non ho testato correttamente il seguito, ma penso che dovrebbe funzionare:

CREATE OR REPLACE FUNCTION makegrid(geometry, integer)
RETURNS geometry AS
'SELECT ST_Collect(ST_POINT(x,y)) FROM 
generate_series(floor(st_xmin($1))::int, ceiling(st_xmax($1)-st_xmin($1))::int, $2) as x
,generate_series(floor(st_ymin($1))::int, ceiling(st_ymax($1)-st_ymin($1))::int,$2) as y 
where st_intersects($1,ST_POINT(x,y))'
LANGUAGE sql

Per usarlo puoi fare:

SELECT makegrid(the_geom, 1000) from mytable;

dove il primo argomento è il poligono in cui si desidera la griglia e il secondo argomento è la distanza tra i punti nella griglia.

Se vuoi un punto per riga, usi semplicemente ST_Dump come:

SELECT (ST_Dump(makegrid(the_geom, 1000))).geom as the_geom from mytable;

HTH

Nicklas


2
Potrebbe essere necessario aggiungere st_setSRID () alle funzioni st_point, altrimenti st_intersects non funziona.
JaakL

Aggiunta la mia versione testata come risposta separata.
JaakL

12

Ho acquisito il codice della funzione makegrid di Nicklas Avén e l'ho reso un po 'più generico leggendo e usando lo srid dalla geometria del poligono. Altrimenti l'uso di un poligono con uno srid definito darebbe un errore.

La funzione:

CREATE OR REPLACE FUNCTION makegrid(geometry, integer)
RETURNS geometry AS
'SELECT ST_Collect(ST_SetSRID(ST_POINT(x,y),ST_SRID($1))) FROM 
generate_series(floor(st_xmin($1))::int, ceiling(st_xmax($1)-st_xmin($1))::int, $2) as x
,generate_series(floor(st_ymin($1))::int, ceiling(st_ymax($1)-st_ymin($1))::int,$2) as y 
where st_intersects($1,ST_SetSRID(ST_POINT(x,y),ST_SRID($1)))'
LANGUAGE sql

Per usare la funzione è fatto esattamente come ha scritto Nicklas Avén :

SELECT makegrid(the_geom, 1000) from mytable;

o se vuoi un punto per riga:

SELECT (ST_Dump(makegrid(the_geom, 1000))).geom as the_geom from mytable;

Spero che questo sia utile per qualcuno.

alex


La risposta accettata non funziona con i miei dati a causa di errori SRID. Questa modifica funziona meglio.
Vitaly Isaev,

Potresti voler aggiungere qualcosa quando il poligono attraversa l'antimeridiano? Posso immaginare che causerebbe un problema con xmin / xmax.
Thomas,

2
Non ha funzionato per me. Utilizzo di Postgres 9.6 e PostGIS 2.3.3. All'interno della chiamata generate_series, ho dovuto inserire questo come secondo parametro "soffitto (st_xmax ($ 1)) :: int" anziché "soffitto (st_xmax ($ 1) -st_xmin ($ 1)) :: int" e "soffitto ( st_ymax ($ 1)) :: int "anziché" soffitto (st_ymax ($ 1) -st_ymin ($ 1)) :: int "
Vitor Sapucaia

Approvo il commento precedente; il limite superiore di generate_series dovrebbe essere il limite massimo e non massimo della differenza (massimo - minimo).
R. Bourgeon,

10

Le persone che usano una geometria wgs84 avranno probabilmente problemi con questa funzione da allora

generate_series(floor(st_xmin($1))::int, ceiling(st_xmax($1))::int,$2) as x
,generate_series(floor(st_ymin($1))::int, ceiling(st_ymax($1))::int,$2) as y 

restituisce solo numeri interi. Fatta eccezione per geometrie molto grandi come i paesi (che si trovano su più lat, gradi di lng), questo causerà la raccolta di solo 1 punto, il più delle volte nemmeno intersecando la geometria stessa ... => risultato vuoto!

Il mio problema era che non riesco a usare generate_series () con una distanza decimale su numeri mobili come quelli WSG84 ... Questo è il motivo per cui ho modificato la funzione per farlo funzionare comunque:

SELECT ST_Collect(st_setsrid(ST_POINT(x/1000000::float,y/1000000::float),st_srid($1))) FROM 
  generate_series(floor(st_xmin($1)*1000000)::int, ceiling(st_xmax($1)*1000000)::int,$2) as x ,
  generate_series(floor(st_ymin($1)*1000000)::int, ceiling(st_ymax($1)*1000000)::int,$2) as y 
WHERE st_intersects($1,ST_SetSRID(ST_POINT(x/1000000::float,y/1000000::float),ST_SRID($1)))

Fondamentalmente esattamente lo stesso. Basta moltiplicare e dividere per 1000000 per ottenere i decimali nel gioco quando ne ho bisogno.

Esiste sicuramente una soluzione migliore per raggiungere questo obiettivo. ++


È una soluzione intelligente. Hai controllato i risultati? Sono coerenti?
Pablo,

Ciao. Sì pablo. Sono contento dei risultati finora. Ne avevo bisogno per costruire un poligono con altitudine relativa dal suolo. (Uso SRTM per calcolare l'altitudine che desidero per ogni punto della griglia). Mi manca solo un modo per includere anche i punti che si trovano sul perimetro del poligono. Attualmente la forma renderizzata è in qualche modo troncata sul bordo.
Julien Garcia,

ha funzionato, quando tutte le soluzioni degli altri hanno fallito, grazie!
Jordan Arseno,

7

Questo algoritmo dovrebbe andare bene:

createGridInPolygon(polygon, resolution) {
    for(x=polygon.xmin; x<polygon.xmax; x+=resolution) {
       for(y=polygon.ymin; y<polygon.ymax; y+=resolution) {
          if(polygon.contains(x,y)) createPoint(x,y);
       }
    }
}

dove "poligono" è il poligono e "risoluzione" è la risoluzione della griglia richiesta.

Per implementarlo in PostGIS, possono essere necessarie le seguenti funzioni:

In bocca al lupo!


1
Nota che se hai aree poligonali complesse di grandi dimensioni (ad esempio ho un buffer di costa), questo approccio non è del tutto ottimale.
JaakL

Allora, cosa proponi invece?
luglio

4

Tre algoritmi che utilizzano metodi diversi.

Github Repo Link

  1. L'approccio semplice e migliore, utilizzando l'effettiva distanza terrestre delle coordinate dalla direzione xey. L'algoritmo funziona con qualsiasi SRID, internamente funziona con WGS 1984 (EPSG: 4326) e il risultato si trasforma in input SRID.

Funzione ================================================= ==================

CREATE OR REPLACE FUNCTION public.I_Grid_Point_Distance(geom public.geometry, x_side decimal, y_side decimal)
RETURNS public.geometry AS $BODY$
DECLARE
x_min decimal;
x_max decimal;
y_max decimal;
x decimal;
y decimal;
returnGeom public.geometry[];
i integer := -1;
srid integer := 4326;
input_srid integer;
BEGIN
CASE st_srid(geom) WHEN 0 THEN
    geom := ST_SetSRID(geom, srid);
        ----RAISE NOTICE 'No SRID Found.';
    ELSE
        ----RAISE NOTICE 'SRID Found.';
END CASE;
    input_srid:=st_srid(geom);
    geom := st_transform(geom, srid);
    x_min := ST_XMin(geom);
    x_max := ST_XMax(geom);
    y_max := ST_YMax(geom);
    y := ST_YMin(geom);
    x := x_min;
    i := i + 1;
    returnGeom[i] := st_setsrid(ST_MakePoint(x, y), srid);
<<yloop>>
LOOP
IF (y > y_max) THEN
    EXIT;
END IF;

CASE i WHEN 0 THEN 
    y := ST_Y(returnGeom[0]);
ELSE 
    y := ST_Y(ST_Project(st_setsrid(ST_MakePoint(x, y), srid), y_side, radians(0))::geometry);
END CASE;

x := x_min;
<<xloop>>
LOOP
  IF (x > x_max) THEN
      EXIT;
  END IF;
    i := i + 1;
    returnGeom[i] := st_setsrid(ST_MakePoint(x, y), srid);
    x := ST_X(ST_Project(st_setsrid(ST_MakePoint(x, y), srid), x_side, radians(90))::geometry);
END LOOP xloop;
END LOOP yloop;
RETURN
ST_CollectionExtract(st_transform(ST_Intersection(st_collect(returnGeom), geom), input_srid), 1);
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE;

Utilizzare la funzione con una query semplice, la geometria deve essere valida e il poligono, i poligoni multipli o il tipo di busta

SELECT I_Grid_Point_Distance(geom, 50, 61) from polygons limit 1;

Risultato ================================================= =====================

inserisci qui la descrizione dell'immagine

  1. Seconda funzione basata sull'algoritmo Nicklas Avén . L'ho migliorato per gestire qualsiasi SRID.

    aggiornare le seguenti modifiche all'algoritmo.

    1. Variabile separata per la direzione xey per la dimensione dei pixel,
    2. Nuova variabile per calcolare la distanza in sferoide o ellissoide.
    3. Immettere qualsiasi SRID, la funzione trasforma Geom nell'ambiente di lavoro di Spheroid o Ellipsoid Datum, quindi applica la distanza su ciascun lato, ottiene il risultato e trasforma in input SRID.

Funzione ================================================= ==================

CREATE OR REPLACE FUNCTION I_Grid_Point(geom geometry, x_side decimal, y_side decimal, spheroid boolean default false)
RETURNS SETOF geometry AS $BODY$ 
DECLARE
x_max decimal; 
y_max decimal;
x_min decimal;
y_min decimal;
srid integer := 4326;
input_srid integer; 
BEGIN
CASE st_srid(geom) WHEN 0 THEN
  geom := ST_SetSRID(geom, srid);
  RAISE NOTICE 'SRID Not Found.';
    ELSE
        RAISE NOTICE 'SRID Found.';
    END CASE;

    CASE spheroid WHEN false THEN
        RAISE NOTICE 'Spheroid False';
        srid := 4326;
        x_side := x_side / 100000;
        y_side := y_side / 100000;
    else
        srid := 900913;
        RAISE NOTICE 'Spheroid True';
    END CASE;
    input_srid:=st_srid(geom);
    geom := st_transform(geom, srid);
    x_max := ST_XMax(geom);
    y_max := ST_YMax(geom);
    x_min := ST_XMin(geom);
    y_min := ST_YMin(geom);
RETURN QUERY
WITH res as (SELECT ST_SetSRID(ST_MakePoint(x, y), srid) point FROM
generate_series(x_min, x_max, x_side) as x,
generate_series(y_min, y_max, y_side) as y
WHERE st_intersects(geom, ST_SetSRID(ST_MakePoint(x, y), srid))
) select ST_TRANSFORM(ST_COLLECT(point), input_srid) from res;
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE STRICT;

Usalo con una semplice query.

SELECT I_Grid_Point(geom, 22, 15, false) from polygons;

Risultato ================================================= ==================inserisci qui la descrizione dell'immagine

  1. Funzione basata sul generatore di serie.

funzione ================================================= =================

CREATE OR REPLACE FUNCTION I_Grid_Point_Series(geom geometry, x_side decimal, y_side decimal, spheroid boolean default false)
RETURNS SETOF geometry AS $BODY$
DECLARE
x_max decimal;
y_max decimal;
x_min decimal;
y_min decimal;
srid integer := 4326;
input_srid integer;
x_series DECIMAL;
y_series DECIMAL;
BEGIN
CASE st_srid(geom) WHEN 0 THEN
  geom := ST_SetSRID(geom, srid);
  RAISE NOTICE 'SRID Not Found.';
    ELSE
        RAISE NOTICE 'SRID Found.';
    END CASE;

    CASE spheroid WHEN false THEN
        RAISE NOTICE 'Spheroid False';
    else
        srid := 900913;
        RAISE NOTICE 'Spheroid True';
    END CASE;
    input_srid:=st_srid(geom);
    geom := st_transform(geom, srid);
    x_max := ST_XMax(geom);
    y_max := ST_YMax(geom);
    x_min := ST_XMin(geom);
    y_min := ST_YMin(geom);

    x_series := CEIL ( @( x_max - x_min ) / x_side);
    y_series := CEIL ( @( y_max - y_min ) / y_side );
RETURN QUERY
SELECT st_collect(st_setsrid(ST_MakePoint(x * x_side + x_min, y*y_side + y_min), srid)) FROM
generate_series(0, x_series) as x,
generate_series(0, y_series) as y
WHERE st_intersects(st_setsrid(ST_MakePoint(x*x_side + x_min, y*y_side + y_min), srid), geom);
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE STRICT;

Usalo con una semplice query.

SELECT I_Grid_Point_Series(geom, 22, 15, false) from polygons; Risultato ================================================= =========================

inserisci qui la descrizione dell'immagine


3

Quindi la mia versione fissa:

CREATE OR REPLACE FUNCTION makegrid(geometry, integer, integer)
RETURNS geometry AS
'SELECT ST_Collect(st_setsrid(ST_POINT(x,y),$3)) FROM 
  generate_series(floor(st_xmin($1))::int, ceiling(st_xmax($1))::int,$2) as x
  ,generate_series(floor(st_ymin($1))::int, ceiling(st_ymax($1))::int,$2) as y 
where st_intersects($1,st_setsrid(ST_POINT(x,y),$3))'
LANGUAGE sql

Uso:

SELECT (ST_Dump(makegrid(the_geom, 1000, 3857))).geom as the_geom from my_polygon_table

1
Ciao, ottengo risultati vuoti con la funzione makegrid. Lo shapefile è stato importato in PostGIS usando shp2pgsql. Non ho idea di cosa potrebbe causare problemi, l'srs è impostato su wgs84.
Michal Zimmermann,

3

Ecco un altro approccio che è sicuramente più veloce e più facile da capire.

Ad esempio per una griglia di 1000m per 1000m:

SELECT (ST_PixelAsCentroids(ST_AsRaster(the_geom,1000.0,1000.0))).geom 
FROM the_polygon

Anche l'SRID originale viene conservato.

Questo frammento converte la geometria del poligono in un raster vuoto, quindi converte ciascun pixel in un punto. Vantaggio: non è necessario verificare nuovamente se il poligono originale interseca i punti.

Opzionale:

È inoltre possibile aggiungere l'allineamento della griglia con il parametro gridx e gridy. Ma poiché usiamo il centroide di ogni pixel (e non un angolo), dobbiamo usare un modulo per calcolare il giusto valore:

SELECT (ST_PixelAsCentroids(ST_AsRaster(the_geom,1000.0,1000.0,mod(1000/2,100),mod(1000/2,100)))).geom 
FROM the_polygon

Con mod(grid_size::integer/2,grid_precision)

Ecco la funzione postgres:

CREATE OR REPLACE FUNCTION st_makegrid(geometry, float, integer)
RETURNS SETOF geometry AS
'SELECT (ST_PixelAsCentroids(ST_AsRaster($1,$2::float,$2::float,mod($2::int/2,$3),mod($2::int/2,$3)))).geom'
LANGUAGE sql;

Può essere usato con:

SELECT makegrid(the_geom,1000.0,100) as geom from the_polygon  
-- makegrid(the_geom,grid_size,alignement)

1

Un piccolo potenziale aggiornamento alle risposte precedenti - terzo argomento come scala per wgs84 (o usare 1 per quelli normali) e anche arrotondamento all'interno del codice in modo che i punti in scala su più forme siano allineati.

Spero che questo aiuti, Martin

CREATE OR REPLACE FUNCTION makegrid(geometry, integer, integer)
RETURNS geometry AS



/*geometry column , integer: distance between points, integer: scale factor for distance (useful for wgs84, e.g. use there 50000 as distance and 1000000 as scale factor*/

'
SELECT ST_Collect(st_setsrid(ST_POINT(x/$3::float,y/$3::float),st_srid($1))) FROM 
  generate_series(
                (round(floor(st_xmin($1)*$3)::int/$2)*$2)::int, 
                (round(ceiling(st_xmax($1)*$3)::int/$2)*$2)::int,
                $2) as x ,
  generate_series(
                (round(floor(st_ymin($1)*$3)::int/$2)*$2)::int, 
                (round(ceiling(st_ymax($1)*$3)::int/$2)*$2)::int,
                $2) as y 
WHERE st_intersects($1,ST_SetSRID(ST_POINT(x/$3::float,y/$3::float),ST_SRID($1)))
'

LANGUAGE sql

Trasformare la geometria in un SRID specifico (come EPSG: 3857) non sarebbe meglio che semplicemente moltiplicare per un fattore di scala?
Nikolaus Krismer,
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.