Comment trouver efficacement le point le plus proche sur la ligne de données?

10

J'ai une table PostgreSQL 9.1 avec des centaines de milliers de POINTS PostGIS. Pour chacun d'eux, j'aimerais trouver le point le plus proche dans un autre tableau de POINTS. Les points du deuxième tableau représentent une grille sur le monde entier, donc je sais qu'il y aura toujours une correspondance à 1 degré près. C'est la requête que j'utilise jusqu'à présent, qui utilise des index GIST, donc c'est assez rapide (environ 30 secondes au total).

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)

Le seul problème est la ligne de données. Les points de la grille n'ont qu'une latitude 180, pas -180. Lorsque vous utilisez la version géométrique de ST_Distance, cela ne renvoie pas de points de l'autre côté de la ligne de données. Par exemple. si p.pos est POINT(-179.88056 -16.68833)le point de grille le plus proche POINT(180 -16.25), mais la requête ci-dessus ne le renvoie pas. Quelle est la meilleure façon de résoudre ce problème?

Je ne veux pas vraiment avoir deux coordonnées pour un seul point de grille (-180 et +180). J'ai essayé d'ajouter ma propre fonction qui vérifie ce cas spécifique, mais la requête ne revient pas dans 5 minutes, probablement parce qu'elle ne peut plus utiliser l'index. J'ai également essayé d'utiliser la version géographique de ST_DWithin et cette requête n'est pas revenue non plus après 5 minutes.

EM0
la source
Bonne question (et astuce intelligente dans votre réponse!). Il faut cependant se demander: si le logiciel est incapable de reconnaître que -180 = 180 pour la longitude, alors il fait probablement semblant qu'il s'agit de coordonnées projetées et utilise des algorithmes euclidiens pour trouver les points les plus proches, ce qui va produire des erreurs (subtile près l'équateur, immense près des pôles et des + -180 méridiens). Je ne sais pas si cela entraîne des problèmes importants dans votre application, mais dans beaucoup d'autres, cela le fera, et cette solution de contournement ne résoudra pas les erreurs.
whuber
Bon point, mais dans ce cas, l'application cliente ne fera pas d'autres calculs "les plus proches" - elle obtiendra simplement des données associées au point de grille renvoyées par ma requête.
EM0

Réponses:

6

OK, j'ai enfin trouvé un moyen de le pirater qui fonctionne non seulement autour du problème de la ligne de données, mais est également plus rapide.

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

J'ai été très surpris de voir que cette fonction, qui est appelée pour chaque ligne, est plus rapide que la fonction de fenêtre d'origine, mais elle est - plus de 10 fois plus rapide. Les performances de PostgreSQL sont vraiment un art noir!

EM0
la source