Bagaimana cara terbaik untuk memperbaiki masalah persimpangan non-mengangguk di PostGIS?

38

Saya menggunakan PL/Rfungsi dan PostGISuntuk menghasilkan poligon voronoi di sekitar serangkaian poin. Fungsi yang saya gunakan didefinisikan di sini . Ketika saya menggunakan fungsi ini pada dataset tertentu saya mendapatkan pesan kesalahan berikut:

Error : ERROR:  R interpreter expression evaluation error
DETAIL:  Error in pg.spi.exec(sprintf("SELECT %3$s AS id,   
st_intersection('SRID='||st_srid(%2$s)||';%4$s'::text,'%5$s') 
AS polygon FROM %1$s WHERE st_intersects(%2$s::text,'SRID='||st_srid(%2$s)||';%4$s');",  
:error in SQL statement : Error performing intersection: TopologyException: found non-noded 
intersection between LINESTRING (571304 310990, 568465 264611) and LINESTRING (568465 
264611, 594406 286813) at 568465.05533706467 264610.82749605528
CONTEXT:  In R support function pg.spi.exec In PL/R function r_voronoi

Dari memeriksa bagian ini dari pesan kesalahan:

Error performing intersection: TopologyException: found non-noded intersection between
LINESTRING (571304 310990, 568465 264611) and LINESTRING (568465 264611, 594406 286813) 
at 568465.05533706467 264610.82749605528

Seperti inilah masalah yang tercantum di atas:

masukkan deskripsi gambar di sini

Saya awalnya berpikir bahwa pesan ini mungkin disebabkan oleh adanya titik-titik yang identik, dan mencoba menyelesaikannya dengan menggunakan st_translate()fungsi, yang digunakan dengan cara berikut:

ST_Translate(geom, random()*20, random()*20) as geom 

Ini memang memperbaiki masalah, tetapi kekhawatiran saya adalah bahwa saya sekarang menerjemahkan semua poin hingga ~ 20m ke arah x / y. Saya juga tidak tahu berapa jumlah terjemahan yang sesuai. Misalnya, dalam dataset ini melalui coba-coba a 20m * random numberadalah ok, tapi bagaimana saya bisa tahu apakah ini perlu lebih besar?

Berdasarkan gambar di atas saya pikir masalahnya adalah bahwa titik tersebut bersinggungan dengan garis sementara algoritma mencoba untuk memotong titik dengan poligon. Saya tidak yakin apa yang harus saya lakukan untuk memastikan bahwa intinya ada dalam poligon, daripada berpotongan dengan garis. Kesalahan terjadi pada baris ini:

"SELECT 
  %3$s AS id, 
  st_intersection(''SRID=''||st_srid(%2$s)||'';%4$s''::text,''%5$s'') AS polygon 
FROM 
  %1$s 
WHERE 
  st_intersects(%2$s::text,''SRID=''||st_srid(%2$s)||'';%4$s'');"

Saya telah membaca pertanyaan sebelumnya, Apa itu "persimpangan non-mengangguk"? untuk mencoba lebih memahami masalah ini, dan akan sangat menghargai saran tentang cara terbaik untuk menyelesaikannya.

djq
sumber
Jika input Anda tidak valid untuk memulai, jalankan ST_MakeValid () pada input itu. Jika mereka valid, menambahkan entropi, seperti yang Anda lakukan, adalah trik berikutnya yang tersedia, dan mungkin trik terakhir untuk saat ini.
Paul Ramsey
Ya, saya menggunakan WHERE ST_IsValid(p.geom)untuk memfilter poin pada awalnya.
djq

Jawaban:

30

Dalam pengalaman saya, masalah ini hampir selalu disebabkan oleh:

  1. Presisi tinggi dalam koordinat Anda (43.231499999999996), dikombinasikan dengan
  2. Garis yang hampir bersamaan tetapi tidak identik

Pendekatan "nudge" dari ST_Buffersolusi memungkinkan Anda lolos dengan # 2, tetapi apa pun yang dapat Anda lakukan untuk menyelesaikan penyebab mendasar ini, seperti menjepitkan geometri Anda ke kisi 1e-6, akan membuat hidup Anda lebih mudah. Geometri buffer biasanya baik untuk perhitungan menengah seperti area tumpang tindih, tetapi Anda harus berhati-hati mempertahankannya karena mereka dapat membuat masalah Anda yang dekat tapi tidak terlalu buruk dalam jangka panjang.

Kemampuan penanganan pengecualian PostgreSQL memungkinkan Anda menulis fungsi wrapper untuk menangani kasus-kasus khusus ini, buffering hanya bila diperlukan. Ini contoh untuk ST_Intersection; Saya menggunakan fungsi serupa untuk ST_Difference. Anda harus memutuskan apakah buffering dan potensi pengembalian poligon kosong dapat diterima dalam situasi Anda.

CREATE OR REPLACE FUNCTION safe_isect(geom_a geometry, geom_b geometry)
RETURNS geometry AS
$$
BEGIN
    RETURN ST_Intersection(geom_a, geom_b);
    EXCEPTION
        WHEN OTHERS THEN
            BEGIN
                RETURN ST_Intersection(ST_Buffer(geom_a, 0.0000001), ST_Buffer(geom_b, 0.0000001));
                EXCEPTION
                    WHEN OTHERS THEN
                        RETURN ST_GeomFromText('POLYGON EMPTY');
    END;
END
$$
LANGUAGE 'plpgsql' STABLE STRICT;

Manfaat lain dengan pendekatan ini adalah Anda dapat menentukan geometri yang sebenarnya menyebabkan masalah Anda; cukup tambahkan beberapa RAISE NOTICEpernyataan di EXCEPTIONblok untuk menghasilkan WKT atau sesuatu yang lain yang akan membantu Anda melacak masalahnya.

dbaston
sumber
Itu ide yang cerdas. Saya sering menemukan bahwa masalah persimpangan berasal dari linestrings muncul selama kombinasi serikat, perbedaan, buffer, dll, yang dapat diperbaiki dengan buffering semuanya, atau membuang semuanya dan hanya memilih Polygons / Mutlipolygons. Ini merupakan pendekatan yang menarik.
John Powell
Anda menyebutkan menjentikkan geometri ke grid 1e-6, tapi saya duduk di sini bertanya-tanya apakah memotret dengan kekuatan 2 akan lebih baik. PostGIS (dan GEOS) menggunakan angka floating point, jadi gertakan dengan kekuatan 10 mungkin tidak benar-benar memotong koordinat sangat banyak karena angka tersebut mungkin tidak memiliki representasi biner panjang yang terbatas. Tetapi jika Anda snap untuk mengatakan 2 ^ -16, saya percaya itu akan dijamin untuk memotong bagian fraksional menjadi hanya 2 byte. Atau saya salah berpikir?
jpmc26
12

Melalui banyak trial and error akhirnya saya menyadari bahwa hasil non-noded intersectiondari masalah persimpangan diri. Saya menemukan utas yang disarankan menggunakan ST_buffer(geom, 0)dapat digunakan untuk memperbaiki masalah (meskipun itu membuatnya jauh lebih lambat secara keseluruhan). Saya kemudian mencoba menggunakan ST_MakeValid()dan ketika diterapkan langsung ke geometri sebelum fungsi lainnya. Ini sepertinya memperbaiki masalah dengan kuat.

ipoint <- pg.spi.exec(
        sprintf(
            "SELECT 
                    %3$s AS id, 
                    st_intersection(ST_MakeValid(''SRID=''||st_srid(%2$s)||'';%4$s''::text), ST_MakeValid(''%5$s'', 0)) AS polygon 
            FROM %1$s 
            WHERE 
                ST_Intersects(ST_MakeValid(%2$s::text),ST_MakeValid(''SRID=''||st_srid(%2$s)||'';%4$s''));",
            arg1,
            arg2,
            arg3,
            curpoly,
            buffer_set$ewkb[1]
        )
    )

Saya telah menandai ini sebagai jawaban karena tampaknya merupakan satu-satunya pendekatan yang memperbaiki masalah saya.

djq
sumber
11

Saya mengalami masalah yang sama (Postgres 9.1.4, PostGIS 2.1.1), dan satu-satunya hal yang berhasil bagi saya adalah membungkus geometri dengan buffer yang sangat kecil.

SELECT ST_Intersection(
    (SELECT geom FROM table1), ST_Union(ST_Buffer(geom, 0.0000001))
) FROM table2

ST_MakeValidtidak bekerja untuk saya, juga tidak kombinasi ST_Nodedan ST_Dump. Buffer tampaknya tidak mengakibatkan penurunan kinerja, tetapi jika saya membuatnya lebih kecil saya masih menerima kesalahan persimpangan non-mengangguk.

Jelek, tapi berhasil.

Memperbarui:

Strategi ST_Buffer tampaknya bekerja dengan baik, tapi saya mengalami masalah di mana ia menghasilkan kesalahan saat casting geometri ke geografi. Sebagai contoh, jika suatu titik awalnya di -90.0, dan buffered oleh 0,0000001, sekarang di -90,0000001, yang merupakan geografi yang tidak valid.

Ini berarti bahwa meskipun ST_IsValid(geom)sudah t, ST_Area(geom::geography)dikembalikan NaNuntuk banyak fitur.

Untuk menghindari masalah persimpangan non-mengangguk, sambil mempertahankan geografi yang valid, saya akhirnya menggunakan ST_SnapToGridseperti itu

SELECT ST_Union(ST_MakeValid(ST_SnapToGrid(geom, 0.0001))) AS geom, common_id
    FROM table
    GROUP BY common_id;
jczaplew
sumber
6

Dalam postgis, ST_Node harus memutus serangkaian garis di persimpangan, yang seharusnya menyelesaikan masalah persimpangan tanpa mengangguk. Membungkus ini dalam ST_Dump menghasilkan array komposit dari garis yang terputus.

Sedikit terkait, ada presentasi PostGIS yang luar biasa: Kiat untuk Pengguna Kuat yang dengan jelas menguraikan masalah dan solusi semacam ini.

WolfOdrade
sumber
Itu adalah presentasi yang luar biasa (terima kasih @PaulRamsey). Bagaimana saya harus menggunakan ST_Nodedan ST_Dump? Saya membayangkan saya perlu menggunakannya di dekat bagian fungsi ini, tetapi saya tidak yakin: st_intersection(''SRID=''||st_srid(%2$s)||'';%4$s''::text,''%5$s'')in
djq
Hmmm saya tidak memperhatikan bahwa kedua garis memiliki koordinat yang identik, yang seharusnya baik-baik saja. Jika Anda merencanakan koordinat tersebut, titik persimpangan berjarak sekitar 18 cm dari persimpangan. Bukan benar-benar solusi, hanya pengamatan.
WolfOdrade
Tidak sepenuhnya jelas tentang bagaimana saya menggunakan di st_nodesini - dapatkah saya menggunakannya sebelumnya st_intersection?
djq
1
Presentasi tidak lagi tersedia. Saya terjebak dengan masalah yang sama, ketika mencoba ST_Clip (rast, poligon)
Jackie
1
@ Jackie: Saya memperbaiki tautan ke presentasi dalam jawaban: PostGIS: Kiat untuk Pengguna yang Kuat .
Pete
1

Dalam pengalaman saya, saya memecahkan non-noded intersectionkesalahan saya dengan menggunakan fungsi St_SnapToGrid yang memecahkan masalah memiliki presisi tinggi dalam koordinat titik puncak poligon.

SELECT dissolve.machine, dissolve.geom FROM (
        SELECT machine, (ST_Dump(ST_Union(ST_MakeValid(ST_SnapToGrid(geom,0.000001))))).geom 
        FROM cutover_automatique
        GROUP BY machine) as dissolve
WHERE ST_isvalid(dissolve.geom)='t' AND ST_GeometryType(dissolve.geom) = 'ST_Polygon';
CptGasse
sumber