Dateline en yakın noktayı verimli bir şekilde nasıl bulabilirim?


10

Yüz binlerce PostGIS NOKTASI içeren bir PostgreSQL 9.1 tablom var. Bunların her biri için başka bir POINT tablosundaki en yakın noktayı bulmak istiyorum. İkinci tablodaki noktalar tüm dünyadaki bir tabloyu temsil ediyor, bu yüzden her zaman 1 derece içinde bir eşleşme olacağını biliyorum. Bu şimdiye kadar kullandığım sorgu GIST dizinleri kullanır, bu yüzden oldukça hızlı (toplam yaklaşık 30 saniye).

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)

Tek sorun datelindir. Izgara noktaları -180 değil, yalnızca enlem 180'e sahiptir. ST_Distance'ın geometri sürümünü kullanırken, bu datelinin diğer tarafındaki noktaları döndürmez. Örneğin. p.pos POINT(-179.88056 -16.68833)en yakın ızgara noktasıysa POINT(180 -16.25), ancak yukarıdaki sorgu döndürmez. Bunu düzeltmenin en iyi yolu nedir?

Tek bir ızgara noktası (-180 ve +180) için iki koordinat istemiyorum. Bu özel durum için kontrol kendi işlevi ekleyerek denedim, ancak daha sonra dizin artık kullanamazsınız çünkü sorgu 5 dakika içinde dönmez. Ayrıca ST_DWithin coğrafya sürümünü kullanarak denedim ve bu sorgu da 5 dakika sonra dönmedi.


İyi soru (ve cevabınızı akıllıca kesmek!). Biri merak etmeli: eğer yazılım boylam için -180 = 180'i tanıyamazsa, muhtemelen bunlar öngörülen koordinatlar gibi davranıyor ve en yakın noktaları bulmak için Öklid algoritmaları kullanıyor, bu da hata üretecek (ince yakın ekvator, kutupların yakınında büyük ve + -180 meridyen). Bunun uygulamanızda önemli sorunlara yol açıp açmadığını bilmiyorum, ancak diğerlerinde de işe yarayacak ve bu geçici çözüm hataları düzeltmeyecek.
whuber

İyi bir nokta, ancak bu durumda istemci uygulaması diğer "en yakın" hesaplamaları yapmaz - sadece benim sorgudan döndürülen ızgara noktası ile ilişkili bazı veriler alır.
EM0

Yanıtlar:


6

Tamam, nihayet kesmek için bir yol buldum, sadece dateline sorunu etrafında değil, aynı zamanda daha hızlı.

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

Her satır için çağrılan bu işlevin orijinal pencere işlevinden daha hızlı olduğunu görünce çok şaşırdım, ancak - 10 kat daha hızlı. PostgreSQL performansı gerçekten siyah bir sanattır!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.