Jak skutecznie znaleźć najbliższy punkt nad linią danych?

10

Mam tabelę PostgreSQL 9.1 z setkami tysięcy PUNKTÓW PostGIS. Dla każdego z nich chciałbym znaleźć najbliższy punkt w innej tabeli PUNKTÓW. Punkty w drugiej tabeli reprezentują siatkę na całym świecie, więc wiem, że zawsze będzie dopasowanie w zakresie 1 stopnia. To pytanie, którego używam do tej pory, które korzysta z indeksów GIST, więc jest dość szybkie (łącznie około 30 sekund).

SELECT DISTINCT ON (p.id)
    p.id, ST_AsText(p.pos)
    , ST_AsText(first_value(g.location) OVER (PARTITION BY p.id ORDER BY ST_Distance(p.pos, g.location::geography)))
FROM point p
JOIN grid g ON ST_DWithin(p.pos::geometry, g.location, 1)

Jedynym problemem jest linia danych. Punkty siatki mają szerokość 180, a nie -180. W przypadku korzystania z wersji geometrii ST_Distance nie zwraca punktów po drugiej stronie linii danych. Na przykład. jeśli p.pos jest POINT(-179.88056 -16.68833)najbliższym punktem siatki POINT(180 -16.25), ale powyższe zapytanie go nie zwraca. Jak najlepiej to naprawić?

Tak naprawdę nie chcę mieć dwóch współrzędnych dla jednego punktu siatki (-180 i +180). Próbowałem dodać własną funkcję, która sprawdza ten konkretny przypadek, ale wtedy zapytanie nie zwraca się w ciągu 5 minut, prawdopodobnie dlatego, że nie może już korzystać z indeksu. Próbowałem także użyć wersji geograficznej ST_DWithin i to zapytanie również nie powróciło po 5 minutach.

EM0
źródło
Dobre pytanie (i sprytny hack w odpowiedzi!). Należy się jednak zastanawiać: jeśli oprogramowanie nie jest w stanie rozpoznać, że -180 = 180 dla długości geograficznej, prawdopodobnie udaje, że są to rzutowane współrzędne i używa algorytmów euklidesowych do znajdowania najbliższych punktów, co spowoduje błędy (subtelne bliskie sąsiedztwo równik, ogromny w pobliżu biegunów i południki + -180). Nie wiem, czy prowadzi to do poważnych problemów w twojej aplikacji, ale w wielu innych tak się stanie, a obejście tego problemu nie naprawi błędów.
whuber
Dobra uwaga, ale w tym przypadku aplikacja kliencka nie wykona innych „najbliższych” obliczeń - po prostu otrzyma pewne dane związane z punktem siatki zwrócone z mojego zapytania.
EM0,

Odpowiedzi:

6

OK, w końcu wymyślę sposób na zhackowanie go, które nie tylko działa wokół problemu linii danych, ale jest również szybsze.

CREATE OR REPLACE FUNCTION nearest_grid_point(point geography(Point))
RETURNS integer
AS $BODY$
    SELECT pointid
    FROM
    (
            -- The normal case
        SELECT pointid, location
        FROM grid
        WHERE ST_DWithin($1::geometry, location, 1)

        UNION ALL

            -- The dateline hack
        SELECT pointid, location
        FROM grid
        WHERE (ST_X($1::geometry) < -178.75 AND longitude = 180)
    ) sub
    ORDER BY ST_Distance($1, location::geography)
    LIMIT 1;
$BODY$ LANGUAGE SQL STABLE;

SELECT p.id, ST_AsText(p.pos), g.pointid, ST_AsText(g.location)
FROM point p
JOIN grid g ON nearest_grid_point(p.pos) = g.pointid

Byłem bardzo zaskoczony, widząc, że ta funkcja, która jest wywoływana dla każdego wiersza, jest szybsza niż oryginalna funkcja okna, ale jest - ponad 10 razy szybsza. Wydajność PostgreSQL to naprawdę czarna sztuka!

EM0
źródło