JAK PORADZIĆ SOBIE Z TOPOLOGY EXCEPTION W POSTGIS

Czasem zdarza się tak, że warstwy na jakich pracujemy mają nie do końca poprawną geometrię. Pół biedy jeśli nasza praca sprowadza się do dodawania obiektów. Jeśli jednak chcemy wykonać jakieś operacje przestrzenne, możemy spotkać się z błędem topology exception np. takim:

 ERROR: GEOSIntersects: TopologyException: side location conflict at 510951.19710927358 534259.35837036208

Warstwy oczywiście można sprawdzić poleceniami ST_IsValid, ST_IsValidReason, ST_IsValidDetail ale nie zawsze to działa. Może zdarzyć się tak, że warstwy zwalidują się poprawnie, a mimo to nie da się wykonać ich przecięcia. Prawdopodobnie dzieje się tak dlatego, że wasze dane zapisywane są przez bazę w zbyt dużej dokładności (np. do setnych części milimetra). Wtedy podczas cięcia i wyliczania współrzędnych nowych punktów jesteśmy częstowani przez PostGIS opisywanym błędem.

Poza próbami naprawienia danych pozostaje nam jeszcze jedno rozwiązanie – napisać własną funkcję do przecinania warstw. No to zaczynamy!

Funkcja przecinające dwie warstwy – ST_Intersects zwraca wartość boolean, zatem nasza funkcja bez bebechów w środku będzie wyglądała tak:

CREATE OR REPLACE FUNCTION public.intersect2(geom_a geometry, geom_b geometry)
  RETURNS boolean AS
$BODY$
BEGIN

END
$BODY$
  LANGUAGE plpgsql VOLATILE

Nasza nowa funkcja musi w ogólności robić to samo co poprzednia. Umieszczamy więc w ciele funkcji domyślną funkcję ST_Intersects

CREATE OR REPLACE FUNCTION public.intersect2(geom_a geometry, geom_b geometry)
  RETURNS boolean AS
$BODY$
BEGIN
RETURN ST_Intersects(geom_a, geom_b);

END
$BODY$
  LANGUAGE plpgsql VOLATILE

Jeśli zostawimy funkcję w tej postaci będzie ona robić dokładnie to samo co funkcja domyślna. Kolejnym krokiem jest więc dopisanie obsługi błędów. Nasza funkcja musi omijać napotkane błędy topology exception i nie wykładać się na nich. Aby to osiągnąć dopisujemy:

CREATE OR REPLACE FUNCTION public.intersect2(geom_a geometry, geom_b geometry)
  RETURNS boolean AS
$BODY$
BEGIN
RETURN ST_Intersects(geom_a, geom_b);
EXCEPTION
        WHEN OTHERS THEN
            return false;
END
$BODY$
  LANGUAGE plpgsql VOLATILE

Teraz jeśli nasza funkcja napotka na inny niż standardowy błąd zwróci false (czyli nie wykona przecięcia) i przejdzie do kolejnego obiektu. W zasadzie moglibyśmy na tym poprzestać ale dobrze byłoby dowiedzieć się, które obiekty nie zostały przecięte. Możemy dopisać zatem komendę raise notice aby postgresql wypisał co potrzeba. Nasza funkcja przyjmie ostatecznie taką postać:

CREATE OR REPLACE FUNCTION public.intersect2(geom_a geometry, geom_b geometry)
  RETURNS boolean AS
$BODY$
BEGIN
RETURN ST_Intersects(geom_a, geom_b);
EXCEPTION
        WHEN OTHERS THEN
            raise notice 'O, tu się wywaliło: %', st_astext(geom_a) || '#####' || st_astext(geom_b);
            return false;
END
$BODY$
  LANGUAGE plpgsql VOLATILE

Funkcja wykona przecięcie, a dla obiektów powodujących błędy wypisze ich geometrię. Można taką funkcję rozwinąć i np. błędne geometrie wrzucać do osobnej tabeli.

Z takim rozwiązaniem problemów z topology exception wiąże się jeden problem. Nie da się używać takiej funkcji na dużych zbiorach, więc jeśli macie setki tysięcy czy miliony obiektów do przecięcia musicie wymyślić inne rozwiązanie. Ale to już temat na inny wpis.

Related Posts