Bagaimana cara membuat titik biasa di dalam poligon di Postgis?

31

Bagaimana cara membuat, di dalam poligon, kisi-kisi titik reguler berjarak x, y di postgis? Seperti contohnya:

teks alternatif

Pablo
sumber
Saya mencoba membuat kliping poligon yang menggabungkan kode ini dengan kode pos "postGIS in action" tetapi hanya satu poligon yang dibuat ... Apakah saya lupa sesuatu? BUAT ATAU GANTI FUNGSI makegrid (geometri, bilangan bulat, bilangan bulat) RETURNS geometri SEBAGAI SELECT st_intersection (g1.geom1, g2.geom2) SEBAGAI geom FROM (SELECT $ 1 AS geom1) AS g1 INNER JOIN (Memilih st_setsrid (Kotak_Kotak) ST_Point (x, y), $ 3), st_setsrid (ST_Point (x + $ 2, y + $ 2), $ 3)) sebagai geometri), $ 3) sebagai geom2 FROM generate_series (floor (st_xmin ($ 1)) :: int, ceiling ( st_xmax ($ 1)) :: int, $ 2) sebagai x, generate_series (floor (st_ymin ($ 1)) :: int, plafon (st_ymax (
aurel_nc
lihat jawaban terperinci saya.
Muhammad Imran Siddique

Jawaban:

30

Anda melakukannya dengan generate_series.

Jika Anda tidak ingin menulis secara manual di mana kisi dimulai dan berhenti, cara termudah adalah membuat fungsi.

Saya belum menguji di bawah ini dengan benar, tetapi saya pikir itu harus bekerja:

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

Untuk menggunakannya Anda bisa melakukan:

SELECT makegrid(the_geom, 1000) from mytable;

di mana argumen pertama adalah poligon yang Anda inginkan grid, dan argumen kedua adalah jarak antara titik-titik di grid.

Jika Anda ingin satu titik per baris, Anda hanya menggunakan ST_Dump seperti:

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

HTH

Nicklas

Nicklas Avén
sumber
2
Anda mungkin perlu menambahkan st_setSRID () ke fungsi st_point, jika tidak st_intersects tidak berfungsi.
JaakL
Menambahkan versi teruji saya sebagai jawaban terpisah.
JaakL
12

Saya telah mengambil kode fungsi Nicklas Avén makegrid dan membuatnya sedikit lebih umum dengan membaca dan menggunakan srid dari geometri poligon. Kalau tidak menggunakan poligon dengan srid yang ditentukan, akan memberikan kesalahan.

Fungsi:

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

Untuk menggunakan fungsi ini dilakukan persis seperti yang ditulis Nicklas Avén :

SELECT makegrid(the_geom, 1000) from mytable;

atau jika Anda ingin satu titik per baris:

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

Semoga ini bermanfaat bagi seseorang.

Alex

Alexandre Neto
sumber
Jawaban yang diterima tidak berfungsi dengan data saya karena Kesalahan SRID. Modifikasi ini berfungsi lebih baik.
Vitaly Isaev
Anda mungkin ingin menambahkan sesuatu ketika poligon melintasi antimeridian? Saya bisa membayangkan itu akan menyebabkan masalah dengan xmin / xmax.
Thomas
2
Itu tidak berhasil untuk saya. Menggunakan Postgres 9.6 dan PostGIS 2.3.3. Di dalam panggilan generate_series, saya harus meletakkan ini sebagai parameter kedua "ceiling (st_xmax ($ 1)) :: int" bukannya "ceiling (st_xmax ($ 1) -st_xmin ($ 1)) :: int", dan "ceiling ( st_ymax ($ 1)) :: int "bukannya" ceiling (st_ymax ($ 1) -st_ymin ($ 1)) :: int "
Vitor Sapucaia
Saya menyetujui komentar sebelumnya; batas atas dari generate_series harus merupakan plafon dari max, dan bukan plafon dari selisih (maks - min).
R. Bourgeon
10

Orang yang menggunakan geometri wgs84 mungkin akan mengalami masalah dengan fungsi ini sejak saat itu

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 

hanya mengembalikan bilangan bulat. Kecuali untuk geometri yang sangat besar seperti negara (yang bertumpu pada beberapa lat, derajat lng), ini akan menyebabkan hanya mengumpulkan 1 titik yang sebagian besar waktu bahkan tidak memotong geometri itu sendiri ... => hasil kosong!

Masalah saya adalah saya sepertinya tidak bisa menggunakan generate_series () dengan jarak desimal pada angka mengambang seperti WSG84 itu ... Ini sebabnya saya mengubah fungsi agar tetap berfungsi:

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

Pada dasarnya persis sama. Hanya mengalikan dan membaginya dengan 1000000 untuk mendapatkan desimal ke dalam permainan saat saya membutuhkannya.

Pasti ada solusi yang lebih baik untuk mencapainya. ++

Julien Garcia
sumber
Itu solusi cerdas. Sudahkah Anda memeriksa hasilnya? Apakah mereka konsisten?
Pablo
Hai. Ya pablo. Saya senang dengan hasilnya sejauh ini. Saya membutuhkannya untuk membangun beberapa poligon dengan ketinggian relatif di atas tanah. (Saya menggunakan SRTM untuk menghitung ketinggian yang saya inginkan untuk setiap titik kisi). Saya sekarang hanya kehilangan cara untuk memasukkan titik-titik yang terletak pada batas poligon juga. Saat ini bentuk yang diberikan agak terpotong di tepi.
Julien Garcia
berhasil, ketika semua solusi orang lain gagal, terima kasih!
Jordan Arseno
7

Algoritma ini harus baik-baik saja:

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

di mana 'poligon' adalah poligon dan 'resolusi' adalah resolusi grid yang diperlukan.

Untuk mengimplementasikannya di PostGIS, fungsi-fungsi berikut mungkin diperlukan:

Semoga berhasil!

Julien
sumber
1
Perhatikan bahwa jika Anda memiliki area poligon kompleks yang besar (misalnya saya memiliki buffer garis pantai), maka pendekatan ini tidak cukup optimal.
JaakL
Lalu, apa yang Anda usulkan sebagai gantinya?
julien
4

Tiga algoritma menggunakan metode yang berbeda.

Tautan Repo Github

  1. Pendekatan sederhana dan terbaik, menggunakan jarak bumi aktual koordinat dari arah x dan y. Algoritma bekerja dengan SRID apa saja, secara internal bekerja dengan WGS 1984 (EPSG: 4326) dan hasilnya mentransformasikan kembali ke input SRID.

Fungsi ================================================= ==================

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;

Gunakan fungsi dengan kueri sederhana, geometri harus valid dan jenis poligon, multi-poligon atau amplop

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

Hasil ================================================= =====================

masukkan deskripsi gambar di sini

  1. Fungsi kedua berdasarkan algoritma Nicklas Avén . Saya meningkatkannya untuk menangani SRID apa pun.

    telah memutakhirkan perubahan berikut dalam algoritme.

    1. Variabel terpisah untuk arah x dan y untuk ukuran piksel,
    2. Variabel baru untuk menghitung jarak dalam spheroid atau ellipsoid.
    3. Masukkan SRID, fungsi ubah Geom ke lingkungan kerja Spheroid atau Ellipsoid Datum, lalu terapkan jarak ke setiap sisi, dapatkan hasilnya dan ubah ke input SRID.

Fungsi ================================================= ==================

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;

Gunakan dengan kueri sederhana.

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

Hasil ================================================= ==================masukkan deskripsi gambar di sini

  1. Fungsi berdasarkan generator seri.

Fungsi ================================================= =================

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;

Gunakan dengan kueri sederhana.

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

masukkan deskripsi gambar di sini

Muhammad Imran Siddique
sumber
3

Jadi versi saya yang tetap:

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

Pemakaian:

SELECT (ST_Dump(makegrid(the_geom, 1000, 3857))).geom as the_geom from my_polygon_table
JaakL
sumber
1
Hai, saya mendapatkan hasil kosong dengan fungsi makegrid. Shapefile diimpor ke PostGIS menggunakan shp2pgsql. Tidak tahu apa yang bisa menyebabkan masalah, srs diatur ke wgs84.
Michal Zimmermann
3

Berikut adalah pendekatan lain yang tentunya lebih cepat dan lebih mudah dipahami.

Misalnya untuk kisi 1000m x 1000m:

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

SRID asli juga dipertahankan.

Cuplikan ini mengonversi geometri poligon menjadi raster kosong, lalu mengonversi setiap piksel menjadi sebuah titik. Keuntungan: kita tidak perlu memeriksa lagi apakah poligon asli memotong titik.

Pilihan:

Anda juga dapat menambahkan perataan grid dengan parameter gridx dan gridy. Tetapi karena kita menggunakan centroid setiap piksel (dan bukan sudut) kita perlu menggunakan modulo untuk menghitung nilai yang tepat:

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

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

Berikut adalah fungsi 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;

Dapat digunakan dengan:

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

Sebuah pembaruan kecil yang potensial untuk jawaban sebelumnya - argumen ketiga sebagai skala untuk wgs84 (atau gunakan 1 untuk yang normal), dan juga pembulatan di dalam kode sehingga titik-titik skala pada berbagai bentuk diselaraskan.

Semoga ini bisa membantu, 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
sumber
Bukankah mengubah geometri ke SRID tertentu (seperti EPSG: 3857) lebih baik daripada hanya mengalikan dengan faktor skala?
Nikolaus Krismer