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


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


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


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. ++


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!


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


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

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)

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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.