Come trovare in modo efficiente il punto più vicino sulla linea dati?


10

Ho una tabella PostgreSQL 9.1 con centinaia di migliaia di PUNTI PostGIS. Per ognuno di questi mi piacerebbe trovare il punto più vicino in un'altra tabella di PUNTI. I punti nella seconda tabella rappresentano una griglia su tutto il mondo, quindi so che ci sarà sempre una corrispondenza entro 1 grado. Questa è la query che sto usando finora, che fa uso degli indici GIST, quindi è ragionevolmente veloce (circa 30 secondi in totale).

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)

L'unico problema è la linea dati. I punti della griglia hanno solo la latitudine 180, non -180. Quando si utilizza la versione geometrica di ST_Distance, questo non restituisce punti sull'altro lato della linea dati. Per esempio. se p.pos è POINT(-179.88056 -16.68833)il punto di griglia più vicino potrebbe essere POINT(180 -16.25), ma la query sopra non lo restituisce. Qual è il modo migliore per risolvere questo problema?

Non voglio davvero avere due coordinate per un singolo punto della griglia (-180 e +180). Ho provato ad aggiungere la mia funzione che controlla questo caso specifico, ma poi la query non ritorna in 5 minuti, probabilmente perché non può più usare l'indice. Ho anche provato a utilizzare la versione geografica di ST_DWithin e anche quella query non è tornata dopo 5 minuti.


Bella domanda (e hack intelligente nella tua risposta!). Tuttavia, ci si deve chiedere: se il software non è in grado di riconoscere che -180 = 180 per longitudine, allora probabilmente sta fingendo che queste siano coordinate proiettate e sta usando algoritmi euclidei per trovare i punti più vicini, che produrrà errori (quasi impercettibile l'equatore, enorme vicino ai poli e + -180 meridiani). Non so se ciò comporti problemi significativi nella tua applicazione, ma in molti altri lo farà e che la soluzione non risolverà gli errori.
whuber

Un buon punto, ma in questo caso l'applicazione client non eseguirà altri calcoli "più vicini" - otterrà solo alcuni dati associati al punto della griglia restituiti dalla mia query.
EM0,

Risposte:


6

OK, ho finalmente trovato un modo per hackerarlo che non solo risolve il problema della linea di dati, ma è anche più veloce.

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

Sono stato molto sorpreso di vedere che questa funzione, chiamata per ogni riga, è più veloce della funzione finestra originale, ma lo è - oltre 10 volte più veloce. Le prestazioni di PostgreSQL sono davvero un'arte nera!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.