Czasem funkcje wbudowane (st_sharedpatsh, st_intersect) są niewystarczające. Część danych geograficznych zapisanych w systemach GIS zawiera informacje o przecięciach tych obiektów w formie zdefiniowania punktu wspólnego dla obu obiektów w miejscu przecięcia (tak np. działa format OSM). PostGIS nie potrafi prosto rozróżnić np. linii które się przecinają i nie są zdefiniowane w oparciu o wspólny węzeł oraz tych które wspólny węzeł posiadają. Funkcje st_shared_paths, st_intersect nie są w tym przypadku wystarczające, a funkcji st_sharedpoints nie ma (za to napiszemy swoją!). Przykładowo:
SELECT st_asText('LINESTRING(-10 0, 10 0)'::geometry);
SELECT st_asText('LINESTRING(0 -10, 0 10)'::geometry);
Te dwie linie przecinają się, ale nie posiadają wspólnego węzła. Z punktu widzenia PostGIS’a wynik sprawdzenia ich przecięcia zwróci wartość TRUE:
SELECT st_Intersects('LINESTRING(-10 0, 10 0)'::geometry, 'LINESTRING(0 -10, 0 10)'::geometry);
Czy da się te sytuacje w PostGIS rozróżnić? Oczywiście!
W niektórych systemach GIS interpretacja takich danych (linii bez wspólnych węzłów) oznacza jednak, że linie te się nie przecinają– np. są to rury które się mijają. Oczywiście aby poprawnie zapisać takie dane w zgodzie ze standardem OpenGIS należałoby użyć trzeciego wymiaru który wyraźnie by podkreślił brak przecięcia. Dla prostych przypadków (mało przecięć) jednak nie ma sensu zamieszczać trzeciego wymiaru w danych. Czy PostGIS jest w stanie wykryć występowanie punktów wspólnych obiektów? Jest funkcja st_sharedpath która zwraca ścieżkę zwróconą ze wspólnych punktów dwóch obiektów:
SELECT st_astext(st_sharedpaths('LINESTRING(-10 0, 0 0, 1 1, 10 0)'::geometry, 'LINESTRING(0 -10, 0 0 , 1 1, 0 10)'::geometry));
Wynik:
GEOMETRYCOLLECTION(MULTILINESTRING((0 0,1 1)),MULTILINESTRING EMPTY)
Wydaje się więc, że to funkcja o którą nam chodzi. Sprawdźmy zatem taki przypadek:
SELECT st_astext(st_sharedpaths('LINESTRING(-10 0, 0 0, 10 0)'::geometry, 'LINESTRING(0 -10, 0 0, 0 10)'::geometry));
Wynik:
GEOMETRYCOLLECTION(MULTILINESTRING EMPTY,MULTILINESTRING EMPTY)
Zatem funkcja nie radzi sobie z wykryciem zetknięcia linii. Można to oczywiście testować warunkiem st_touches. Dla ułatwienia zapisu można jednak zdefiniować swoje własne funkcje do przetwarzania danych GIS:
create or replace function ST_SharedPoints(a geometry, b geometry) returns setof geometry_dump as
$$
begin
if not a && b then
return;
end if;
return query select * from st_dumppoints(a) a where exists (select 1 from st_dumppoints(b) b where a.geom = b.geom);
end;
$$ language plpgsql;
create or replace function ST_HasSharedPoint(a geometry, b geometry) returns boolean as
$$
declare
v_dummy int;
begin
if not a && b then
return false;
end if;
select 1 into v_dummy from st_dumppoints(a) a where exists (select 1 from st_dumppoints(b) b where a.geom = b.geom) limit 1;
return found;
end;
$$ language plpgsql;
Funkcja ST_SharedPoints zwraca listę punktów węzłowych wchodzących w skład obu linii.
Przykład:
SELECT * FROM st_sharedpoints('LINESTRING(-10 0, 0 0, 10 0)'::geometry, 'LINESTRING(0 -10, 0 0, 0 10)'::geometry);
Wynik:
Dwie kolumny: path to numer węzła w pierwszym przekazanym obiekcie a geom to definicja punktu węzłowego.
Jak sprawdzimy to linie ‘LINESTRING(-10 0, 0 0, 10 0)’ oraz ‘LINESTRING(-10 0, 0 0, 10 0)’ mają jeden punkt wspólny, nie mają ścieżek wspólnych i się przecinają, a np. ‘LINESTRING(-10 0, 10 0)’ oraz ‘LINESTRING(-10 0, 10 0)’ nie mają punktów wspólnych, nie mają wspólnych ścieżek ale się przecinają.
Do zwykłego testowania czy dwa obiekty posiadają węzły wspólne można zastosować ST_HasSharedPoint:
select st_hassharedpoint('LINESTRING(-10 1, 0 -10, 0 0, 1 1, 0 10)', 'LINESTRING(10 0, 0 0, 1 2, -10 0)');
Powyższe oczywiście mają a poniższe już nie (chociaż zarówno powyższe jak i poniższe linie się przecinają):
select st_hassharedpoint('LINESTRING(0 -10, 0 10)', 'LINESTRING(10 0, -10 0)');
Funkcje do wykrycia wspólnych węzłów korzystają z st_dumppoints, ale zanim to spróbują zrobić upewniają się, że obiekty przekazane jako parametry mogą się stykać (przez sprawdzenie ich prostokątów ograniczających).
Reasumując – wskazane rozwiązanie świetnie sprawdza się w sytuacji kiedy przechowujemy w naszych danych przestrzennych informacje o przecięciach linii w postaci wspólnych punktów węzłów i nie chcemy dodawać trzeciego wymiaru do danych. Tak działa na przykład format Open Street Maps więc jest na czym eksperymentować.