Como criar uma grade de pontos regular dentro de um polígono no Postgis?

31

Como criar, dentro de um polígono, uma grade regular de pontos espaçados x, y no postgis? Como no exemplo:

texto alternativo

Pablo
fonte
Tentei fazer polígonos de recorte mesclando esse código com o código de dados "postGIS in action", mas apenas um polígono é criado ... Esqueci alguma coisa? CRIAR OU SUBSTITUIR FUNÇÃO makegrid (geometria, número inteiro, número inteiro) RETORNA a geometria AS 'SELECT st_intersection (g1.geom1, g2.geom2) AS geom FROM (SELECIONE $ 1 AS geom1) AS g1 INNER JOIN (Selecione st_setsrid (CAST (ST_Makeid2) (st_setsrid (CAST (ST_MakeBox2d (st_setsrid) ST_Point (x, y), $ 3), st_setsrid (ST_Point (x + $ 2, y + $ 2), $ 3)) como geometria), $ 3) como geom2 FROM generate_series (floor (st_xmin ($ 1)) :: int, ceiling ( st_xmax ($ 1)) :: int, $ 2) como x, generate_series (floor (st_ymin ($ 1)) :: int, teto (st_ymax (
aurel_nc
veja minha resposta detalhada.
Muhammad Imran Siddique 27/03

Respostas:

30

Você faz isso com generate_series.

Se você não deseja escrever manualmente onde a grade deve iniciar e parar, o mais fácil é criar uma função.

Não testei o abaixo corretamente, mas acho que deve funcionar:

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

Para usá-lo, você pode fazer:

SELECT makegrid(the_geom, 1000) from mytable;

onde o primeiro argumento é o polígono em que você deseja inserir a grade e o segundo argumento é a distância entre os pontos na grade.

Se você quer um ponto por linha, basta usar ST_Dump como:

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

HTH

Nicklas

Nicklas Avén
fonte
2
Pode ser necessário adicionar st_setSRID () às funções st_point, caso contrário, st_intersects não funcionará.
JaakL
Adicionado minha versão testada como resposta separada.
JaakL
12

Eu peguei Nicklas Aven código de função makegrid e fez dele um pouco mais genérico, lendo e usando a srid da geometria de polígono. Caso contrário, usar um polígono com um srid definido causaria um erro.

A função:

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

Para usar a função é feito exatamente como Nicklas Avén escreveu:

SELECT makegrid(the_geom, 1000) from mytable;

ou se você quiser um ponto por linha:

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

Espero que isso seja útil para alguém.

Alex

Alexandre Neto
fonte
A resposta aceita não funciona com meus dados devido a erros SRID. Essa modificação funciona melhor.
precisa
Você pode adicionar algo quando o polígono estiver cruzando o antimeridiano? Eu posso imaginar que causaria um problema com xmin / xmax.
Thomas
2
Não funcionou para mim. Usando o Postgres 9.6 e o ​​PostGIS 2.3.3. Dentro da chamada generate_series, tive que colocar isso como o segundo parâmetro "ceiling (st_xmax ($ 1)) :: int" em vez de "ceiling (st_xmax ($ 1) -st_xmin ($ 1)) :: int" e "ceiling ( st_ymax ($ 1)) :: int "em vez de" teto (st_ymax ($ 1) -st_ymin ($ 1)) :: int "
Vitor Sapucaia
Eu aprovo o comentário anterior; o limite superior de generate_series deve ser o teto do máximo, e não o teto da diferença (max - min).
R. Bourgeon
10

Pessoas que usam uma geometria wgs84 provavelmente terão problemas com essa função, pois

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 

retornam apenas números inteiros. Exceto por geometrias muito grandes, como países (que estão em vários graus de lat, lng), isso fará com que você colete apenas 1 ponto, que na maioria das vezes nem sequer cruza a própria geometria ... => resultado vazio!

Meu problema foi que parece que não consigo usar generate_series () com uma distância decimal em números flutuantes como os WSG84 ... É por isso que aprimorei a função para fazê-la funcionar de qualquer maneira:

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

Basicamente exatamente o mesmo. Apenas multiplicando e dividindo por 1000000 para obter as casas decimais no jogo quando eu precisar.

Certamente há uma solução melhor para conseguir isso. ++

Julien Garcia
fonte
Essa é uma solução inteligente. Você verificou os resultados? Eles são consistentes?
21413 Pablo
Oi. Sim Pablo. Estou feliz com os resultados até agora. Eu precisava construir um polígono com altitude relativa acima do solo. (Eu uso o SRTM para calcular a altitude desejada para cada ponto da grade). Agora estou perdendo apenas uma maneira de incluir os pontos que estão no perímetro do polígono também. Atualmente, a forma renderizada é um pouco truncada na borda.
Julien Garcia
funcionou, quando todas as soluções dos outros falharam, obrigado!
Jordan Arseno
7

Este algoritmo deve estar bem:

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

onde 'polígono' é o polígono e 'resolução' é a resolução de grade necessária.

Para implementá-lo no PostGIS, as seguintes funções podem ser necessárias:

Boa sorte!

julien
fonte
11
Observe que, se você possui grandes áreas de polígonos complexos (por exemplo, eu tenho um buffer de linha de costa), essa abordagem não é ótima.
JaakL
Então, o que você propõe?
julien
4

Três algoritmos usando métodos diferentes.

Link para repositório do Github

  1. A abordagem simples e melhor, usando a distância real da terra das coordenadas da direção xey. O algoritmo funciona com qualquer SRID, internamente, funciona com o WGS 1984 (EPSG: 4326) e a transformação do resultado volta para a entrada do SRID.

Função =================================================== ==================

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;

Use a função com uma consulta simples, a geometria deve ser válida e o tipo de polígono, multi-polígonos ou envelope

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

Resultado =================================================== =====================

insira a descrição da imagem aqui

  1. Segunda função baseada no algoritmo Nicklas Avén . Eu o aprimorei para lidar com qualquer SRID.

    atualize as seguintes alterações no algoritmo.

    1. Variável separada para direção x e y para tamanho de pixel,
    2. Nova variável para calcular a distância em esferóide ou elipsóide.
    3. Insira qualquer SRID, função transforme Geom no ambiente de trabalho do Spheroid ou Ellipsoid Datum, aplique a distância em cada lado, obtenha o resultado e transforme na entrada SRID.

Função =================================================== ==================

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;

Use-o com uma consulta simples.

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

Resultado =================================================== ==================insira a descrição da imagem aqui

  1. Função baseada em gerador de séries.

Função =================================================== =================

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;

Use-o com uma consulta simples.

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

insira a descrição da imagem aqui

Muhammad Imran Siddique
fonte
3

Então, minha versão fixa:

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
JaakL
fonte
11
Oi, eu recebo resultado vazio com a função Makegrid. O shapefile foi importado para o PostGIS usando shp2pgsql. Não tem idéia do que pode estar causando problemas, o srs está definido como wgs84.
precisa
3

Aqui está outra abordagem que é certamente mais rápida e fácil de entender.

Por exemplo, para uma grade de 1000m por 1000m:

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

Além disso, o SRID original é preservado.

Esse trecho converte a geometria do polígono em uma varredura vazia e, em seguida, converte cada pixel em um ponto. Vantagem: não precisamos verificar novamente se o polígono original cruza os pontos.

Opcional:

Você também pode adicionar o alinhamento da grade com o parâmetro gridx e gridy. Mas como usamos o centróide de cada pixel (e não um canto), precisamos usar um módulo para calcular o valor certo:

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

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

Aqui está a função 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;

Canbe usado com:

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

Uma pequena atualização em potencial para as respostas anteriores - terceiro argumento como escala para wgs84 (ou use 1 para as normais) e também arredondando dentro do código para que os pontos dimensionados em várias formas estejam alinhados.

Espero que isso ajude, 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
Martin
fonte
Transformar a geometria em um SRID específico (como EPSG: 3857) não seria melhor do que apenas multiplicar por um fator de escala?
Nikolaus Krismer