วิธีที่ดีที่สุดในการแก้ไขปัญหาการตัดกันแบบไม่ซ้อนใน PostGIS


38

ฉันใช้PL/Rฟังก์ชั่นและPostGISสร้างรูปหลายเหลี่ยม voronoi รอบจุดต่างๆ ฟังก์ชั่นที่ผมใช้ถูกกำหนดไว้ที่นี่ เมื่อฉันใช้ฟังก์ชั่นนี้บนชุดข้อมูลเฉพาะฉันได้รับข้อความแสดงข้อผิดพลาดต่อไปนี้:

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

จากการตรวจสอบข้อผิดพลาดส่วนนี้:

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

นี่คือปัญหาที่ปรากฏในรายการด้านบน:

ป้อนคำอธิบายรูปภาพที่นี่

ตอนแรกฉันคิดว่าข้อความนี้อาจเกิดจากการมีจุดที่เหมือนกันและพยายามแก้ไขโดยใช้st_translate()ฟังก์ชั่นที่ใช้ในวิธีต่อไปนี้:

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

สิ่งนี้จะแก้ไขปัญหาได้ แต่ข้อกังวลของฉันคือตอนนี้ฉันแปลคะแนนทั้งหมดได้สูงถึง ~ 20m ในทิศทาง x / y ฉันไม่สามารถบอกได้ว่าต้องการจำนวนการแปลที่เหมาะสมเท่าไร ตัวอย่างเช่นในชุดข้อมูลนี้ผ่านการทดลองและข้อผิดพลาด a 20m * random numberก็โอเค แต่ฉันจะบอกได้อย่างไรว่าสิ่งนี้จะต้องใหญ่กว่านี้

จากภาพด้านบนฉันคิดว่าปัญหาคือจุดตัดกับเส้นตรงในขณะที่อัลกอริทึมพยายามตัดจุดด้วยรูปหลายเหลี่ยม ฉันไม่แน่ใจว่าฉันควรทำอะไรเพื่อให้แน่ใจว่าจุดนั้นอยู่ในรูปหลายเหลี่ยมแทนที่จะตัดกันด้วยเส้น ข้อผิดพลาดเกิดขึ้นในบรรทัดนี้:

"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'');"

ฉันได้อ่านคำถามก่อนหน้านี้แล้วอะไรคือ "จุดตัดที่ไม่ได้ซ้อนกัน" เพื่อพยายามทำความเข้าใจปัญหานี้ให้ดีขึ้นและขอขอบคุณคำแนะนำในการแก้ไขปัญหาให้ดีที่สุด


หากอินพุตของคุณไม่ถูกต้องให้เริ่มต้นด้วยให้เรียกใช้ ST_MakeValid () ที่อินพุต หากพวกเขาถูกต้องเพิ่มเอนโทรปีในขณะที่คุณกำลังทำอยู่เป็นเคล็ดลับถัดไปที่มีอยู่และอาจเป็นเคล็ดลับสุดท้ายสำหรับตอนนี้
Paul Ramsey

ใช่ฉันใช้WHERE ST_IsValid(p.geom)เพื่อกรองคะแนนในตอนแรก
djq

คำตอบ:


30

จากประสบการณ์ของฉันปัญหานี้มักเกิดจาก:

  1. ความแม่นยำสูงในพิกัดของคุณ (43.231499999999996) รวมกับ
  2. เส้นที่เกือบจะเหมือนกัน แต่ไม่เหมือนกัน

วิธีการ "เขยิบ" ของST_Bufferวิธีแก้ปัญหาช่วยให้คุณสามารถหลบหนีด้วย # 2 แต่สิ่งที่คุณสามารถทำได้เพื่อแก้ไขสาเหตุพื้นฐานเหล่านี้เช่นการตัดรูปเรขาคณิตของคุณลงในตาราง 1e-6 จะทำให้ชีวิตของคุณง่ายขึ้น รูปทรงเรขาคณิตที่บัฟเฟอร์มักจะใช้งานได้ดีสำหรับการคำนวณระดับกลางเช่นพื้นที่ทับซ้อน แต่คุณจะต้องระมัดระวังเกี่ยวกับการเก็บรักษาไว้เพราะมันสามารถทำให้ปัญหาระยะใกล้ของคุณ แต่ไม่มากนักในระยะยาว

ความสามารถในการจัดการข้อยกเว้นของ PostgreSQL ช่วยให้คุณสามารถเขียนฟังก์ชั่น wrapper เพื่อจัดการกับกรณีพิเศษเหล่านี้บัฟเฟอร์เฉพาะเมื่อจำเป็น นี่คือตัวอย่างสำหรับST_Intersection; ST_Differenceผมใช้ฟังก์ชั่นที่คล้ายกันสำหรับ คุณจะต้องตัดสินใจว่าการบัฟเฟอร์และการส่งคืนรูปหลายเหลี่ยมที่ว่างเปล่านั้นเป็นที่ยอมรับในสถานการณ์ของคุณหรือไม่

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;

ข้อดีอีกอย่างของวิธีนี้คือคุณสามารถระบุรูปทรงเรขาคณิตที่ทำให้เกิดปัญหาของคุณได้ เพียงเพิ่มRAISE NOTICEคำสั่งบางอย่างในEXCEPTIONบล็อกเพื่อส่งออก WKT หรืออย่างอื่นที่จะช่วยคุณติดตามปัญหา


นั่นเป็นความคิดที่ฉลาด ฉันมักจะพบว่าปัญหาการตัดกันมาจาก linestrings ปรากฏขึ้นในระหว่างการรวมกันของสหภาพความแตกต่างบัฟเฟอร์ ฯลฯ ซึ่งสามารถแก้ไขได้โดยการบัฟเฟอร์ทุกอย่างหรือทิ้งทุกอย่างและเลือก Polygons / Mutlipolygons เท่านั้น นี่เป็นวิธีการที่น่าสนใจ
John Powell

คุณพูดถึงการจัดรูปทรงเรขาคณิตให้กับตาราง 1e-6 แต่ฉันนั่งอยู่ที่นี่โดยสงสัยว่าการหักค่ากำลังสองจะดีกว่าหรือไม่ PostGIS (และ GEOS) โดยใช้ตัวเลขจำนวนจุดลอยตัวดังนั้นการถ่ายภาพที่กำลัง 10 อาจไม่ตัดทอนพิกัดมากนักเนื่องจากตัวเลขอาจไม่มีการแสดงเลขฐานสองที่มีความยาว จำกัด แต่ถ้าคุณบอกว่า 2 ^ -16 ฉันเชื่อว่ามันจะรับรองว่าจะตัดส่วนเศษส่วนใด ๆ ให้เหลือเพียง 2 ไบต์ หรือฉันคิดผิด
jpmc26

12

จากการทดลองและข้อผิดพลาดมากมายในที่สุดฉันก็รู้ว่าnon-noded intersectionผลลัพธ์มาจากปัญหาการแยกตนเอง ฉันพบเธรดที่แนะนำให้ใช้ST_buffer(geom, 0)สามารถใช้เพื่อแก้ไขปัญหา (แม้ว่าจะทำให้โดยรวมช้าลงมาก) ฉันลองใช้ST_MakeValid()และเมื่อใช้กับเรขาคณิตโดยตรงก่อนฟังก์ชั่นอื่น ๆ ดูเหมือนว่าจะแก้ไขปัญหาได้ดี

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

ฉันทำเครื่องหมายคำตอบนี้เป็นคำตอบเนื่องจากเป็นแนวทางเดียวที่แก้ไขปัญหาของฉันได้


11

ฉันพบปัญหาเดียวกันนี้ (Postgres 9.1.4, PostGIS 2.1.1) และสิ่งเดียวที่ทำงานให้ฉันก็คือการหุ้มเรขาคณิตด้วยบัฟเฟอร์เล็ก ๆ

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

ST_MakeValidไม่ได้ทำงานสำหรับฉันหรือไม่การรวมกันของและST_Node ST_Dumpบัฟเฟอร์ดูเหมือนจะไม่ส่งผลให้ประสิทธิภาพลดลง แต่ถ้าฉันทำให้มีขนาดเล็กลงฉันก็ยังได้รับข้อผิดพลาดที่ไม่ได้อยู่ในตำแหน่งจุดตัด

น่าเกลียด แต่ก็ใช้งานได้

ปรับปรุง:

กลยุทธ์ ST_Buffer ดูเหมือนว่าจะทำงานได้ดี แต่ฉันพบปัญหาที่ทำให้เกิดข้อผิดพลาดเมื่อชี้เรขาคณิตเป็นภูมิศาสตร์ ตัวอย่างเช่นถ้าจุดแรกเริ่มที่ -90.0 และถูกบัฟเฟอร์ด้วย 0.0000001 ตอนนี้จะอยู่ที่ -90.0000001 ซึ่งเป็นภูมิศาสตร์ที่ไม่ถูกต้อง

นั่นหมายความว่าถึงแม้ST_IsValid(geom)เป็นt, ST_Area(geom::geography)กลับNaNสำหรับคุณสมบัติมากมาย

เพื่อหลีกเลี่ยงปัญหาการตัดกันแบบไม่มีการซ้อนทับในขณะที่ยังคงสภาพทางภูมิศาสตร์ที่ถูกต้องอยู่ฉันจึงใช้มันST_SnapToGridอย่างนั้น

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

6

ใน postgis ST_Nodeควรแบ่งชุดของเส้นตรงที่ทางแยกซึ่งควรแก้ปัญหาการตัดกันแบบไม่มีการโหน การตัดสิ่งนี้ในST_Dumpจะสร้างอาร์เรย์แบบผสมของเส้นที่ขาด

มีความเกี่ยวข้องกันเล็กน้อยมีงานนำเสนอที่ยอดเยี่ยมPostGIS: เคล็ดลับสำหรับผู้ใช้ระดับสูงซึ่งสรุปปัญหาและแนวทางแก้ไขปัญหาเหล่านี้อย่างชัดเจน


นั่นเป็นการนำเสนอที่ยอดเยี่ยม (ขอบคุณ @ PaulRamsey) ฉันควรใช้ST_NodeและST_Dumpอย่างไร? ฉันคิดว่าฉันจะต้องใช้พวกเขาใกล้ส่วนนี้ของฟังก์ชั่น แต่ไม่แน่ใจ: st_intersection(''SRID=''||st_srid(%2$s)||'';%4$s''::text,''%5$s'')ใน
djq

อืมฉันไม่ได้สังเกตว่าทั้งสองเส้นมีพิกัดเหมือนกันซึ่งน่าจะใช้ได้ หากคุณพล็อตพิกัดเหล่านั้นจุดตัดจะอยู่ห่างจากจุดตัดประมาณ 18 ซม. ไม่ใช่วิธีแก้ปัญหาจริงๆเพียงสังเกต
WolfOdrade

ไม่ชัดเจนทั้งหมดเกี่ยวกับวิธีการใช้st_nodeที่นี่ - ฉันสามารถใช้งานได้ก่อนst_intersectionหรือไม่
djq

1
ไม่มีการนำเสนออีกต่อไป ฉันติดอยู่กับปัญหาเดียวกันเมื่อพยายาม ST_Clip (rast, polygon)
Jackie

1
@ แจ็กกี้: ฉันแก้ไขลิงก์ไปยังงานนำเสนอในคำตอบ: PostGIS: เคล็ดลับสำหรับผู้ใช้ระดับสูง
Pete

1

จากประสบการณ์ของฉันฉันแก้ไขnon-noded intersectionข้อผิดพลาดโดยใช้ฟังก์ชันSt_SnapToGridซึ่งแก้ไขปัญหาการมีความแม่นยำสูงในพิกัดของจุดยอดของรูปหลายเหลี่ยม

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';
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.