PostGIS ST_Distance, kNN ile en yakın puan


23

Bir tablodaki her elementten başka bir tablonun en yakın noktasını elde etmem gerekiyor. İlk tablo trafik işaretleri ve ikincisi şehir giriş salonlarını içermektedir. Mesele şu ki, ST_ClosestPoint işlevini kullanamıyorum ve ST_Distance işlevini kullanmalı ve min (ST_distance) kaydını almalıyım, ancak sorguyu oluştururken oldukça sıkıştım.

CREATE TABLE traffic_signs
(
  id numeric(8,0) ),
  "GEOMETRY" geometry,
  CONSTRAINT traffic_signs_pkey PRIMARY KEY (id),
  CONSTRAINT traffic_signs_id_key UNIQUE (id)
)
WITH (
  OIDS=TRUE
);

CREATE TABLE entrance_halls
(
  id numeric(8,0) ),
  "GEOMETRY" geometry,
  CONSTRAINT entrance_halls_pkey PRIMARY KEY (id),
  CONSTRAINT entrance_halls_id_key UNIQUE (id)
)
WITH (
  OIDS=TRUE
);

Her traffic_sign'ın en yakın entrnce_hall'unun kimliğini bulmam gerekiyor.

Sorgu şu ana kadar:

SELECT senal.id,port.id,ST_Distance(port."GEOMETRY",senal."GEOMETRY")  as dist
    FROM traffic_signs As senal, entrance_halls As port   
    ORDER BY senal.id,port.id,ST_Distance(port."GEOMETRY",senal."GEOMETRY")

Bununla her trafik işaretinden her girişe uzaklığa gidiyorum. Fakat sadece minimum mesafeyi nasıl alabilirim?

Saygılarımızla,


PostgreSQL'in hangi sürümü?
Jakub Kania

Yanıtlar:


41

Neredeyse oradasın. Postgres'in farklı operatörünü kullanacak ve her kombinasyonun ilk eşleşmesini geri döndürecek küçük bir hile var - ST_Distance tarafından sipariş verirken, her bir sinyalden her bir limana en yakın noktayı etkili bir şekilde döndürür.

SELECT 
   DISTINCT ON (senal.id) senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY")  as dist
FROM traffic_signs As senal, entrance_halls As port   
ORDER BY senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY");

Her bir durumda minimum mesafenin x miktarından fazla olmadığını biliyorsanız (ve masanızda uzamsal bir dizine sahipseniz) WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", distance), örneğin, tüm minimum mesafelerin olduğu biliniyorsa , bunu hızlandırabilir o zaman en fazla 10km

SELECT 
   DISTINCT ON (senal.id) senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY")  as dist
FROM traffic_signs As senal, entrance_halls As port  
WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", 10000) 
ORDER BY senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY");

Açıkçası, bu asgari mesafe daha büyükse sanki senal ve limanın birleşimi için herhangi bir satır elde edemeyeceğiniz gibi dikkatli kullanılması gerekir.

Not: Siparişteki siparişte, siparişte belirtilen ilke göre farklı olan, siparişte belirgin olan ilk sıradaki gruba uyması gerekir.

Her iki tabloda da bir uzaysal indeksinizin olduğu varsayılmaktadır.

EDIT 1 . Mekansal indeksi daha verimli kullanan ve ST_DWithin kesmesini gerektirmeyen, Postgres'in <-> ve <#> işleçlerini (sırasıyla merkez nokta ve sınırlama kutusu mesafe hesaplamaları) kullanmak olan ve başka bir seçenek yoktur. 2 karşılaştırması. Nasıl çalıştıklarını açıklayan iyi bir blog yazısı var . Unutulmaması gereken genel şey, bu iki operatörün ORDER BY deyiminde çalıştığıdır.

SELECT senal.id, 
  (SELECT port.id 
   FROM entrance_halls as port 
   ORDER BY senal.geom <#> port.geom LIMIT 1)
FROM  traffic_signs as senal;

EDIT 2 . Bu soru çok fazla dikkat çekti ve K-en yakın komşular (kNN) GIS'de genellikle zor bir sorun (algoritmik çalışma zamanı) olarak olduğundan, bu sorunun orijinal kapsamı üzerinde biraz genişlemeye değecek gibi görünüyor.

Bir nesnenin en yakın komşusunu bulmak için standart yol, bir LATERAL JOIN kullanmaktır (kavramsal olarak her bir döngüye benzer). Dbaston'ın cevabından utanmadan ödünç almak , şöyle bir şey yaparsın:

SELECT
  signs.id,
  closest_port.id,
  closest_port.dist
 FROM traffic_signs
CROSS JOIN LATERAL 
  (SELECT
      id, 
      ST_Distance(ports.geom, signs.geom) as dist
      FROM ports
      ORDER BY signs.geom <-> ports.geom
     LIMIT 1
   ) AS closest_port

Bu nedenle, mesafeye göre sıralanan en yakın 10 portu bulmak istiyorsanız, yanal sorguda LIMIT yan tümcesini değiştirmeniz yeterlidir. LATERAL JOINS olmadan yapmak çok zordur ve ARRAY tipi mantığı kullanmayı gerektirir. Bu yaklaşım iyi çalışsa da, yalnızca belirli bir mesafeyi araştırmanız gerektiğini biliyorsanız, çok büyük hız kazanabilir. Bu örnekte, alt sorguda ST_DWithin (signs.geom, ports.geom, 1000) kullanabilirsiniz. Dizinlemenin <-> operatörü ile çalışması nedeniyle - geometrilerden birinin sabit olması gerekir. sütun başvurusu - çok daha hızlı olabilir. Örneğin, en yakın 3 bağlantı noktasını 10 km içinde almak için aşağıdaki gibi bir şeyler yazabilirsiniz.

 SELECT
  signs.id,
  closest_port.id,
  closest_port.dist
 FROM traffic_signs
CROSS JOIN LATERAL 
  (SELECT
      id, 
      ST_Distance(ports.geom, signs.geom) as dist
      FROM ports
      WHERE ST_DWithin(ports.geom, signs.geom, 10000)
      ORDER BY ST_Distance(ports.geom, signs.geom)
     LIMIT 3
   ) AS closest_port;

Her zaman olduğu gibi, kullanım veri dağıtımınıza ve sorgularınıza bağlı olarak değişecektir, bu nedenle EXPLAIN en iyi arkadaşınızdır.

Son olarak, CROSS JOIN LATERAL yerine LEFT kullanıyorsanız , lateral sorgu takma adlarından sonra ON TRUE eklemeniz gerektiğinde , örneğin

SELECT
  signs.id,
  closest_port.id,
  closest_port.dist
 FROM traffic_signs
LEFT JOIN LATERAL 
  (SELECT
      id, 
      ST_Distance(ports.geom, signs.geom) as dist
      FROM ports          
      ORDER BY signs.geom <-> ports.geom
      LIMIT 1
   ) AS closest_port
   ON TRUE;

Bunun, büyük miktarda veri ile iyi bir performans göstermeyeceği belirtilmelidir.
Jakub Kania

@JakubKania. ST_DWithin kullanıp kullanmamaya bağlıdır. Ama evet, alınan nokta. Ne yazık ki, <-> / <#> operatörüne göre sırala, geometrilerden birinin sabit olmasını gerektirir, değil mi?
John Powell

@ JohnPowellakaBarça, blog postasının bugün nerede yaşadığını bilme ihtimalin var mı? - veya <-> ve <#> operatörlerinin benzer bir açıklaması? Teşekkürler!!
DPSSpatial

@DPSSpatial, bu can sıkıcı bir durum. Bilmiyorum, ama orada bu ve bu bu yaklaşımın hakkında biraz konuşmak hangi. İkincisi, yanal bağlantılar kullanarak da bir başka ilginç gelişmedir.
John Powell,

@DPSSpatial. Her şey biraz kaygan, bu <->, <#> ve lateral birleştirme işleri. Bunu çok büyük veri setleri ile yaptım ve performans, tüm bunların kaçınması gereken ST_DWithin kullanılmadan korkunçtu. Nihayetinde knn karmaşık bir problemdir, bu nedenle kullanım değişebilir. İyi şanslar :-)
John Powell,

13

Bu bir LATERAL JOINPostgreSQL 9.3+ ile yapılabilir :

SELECT
  signs.id,
  closest_port.id,
  closest_port.dist
FROM traffic_signs
CROSS JOIN LATERAL 
  (SELECT
     id, 
     ST_Distance(ports.geom, signs.geom) as dist
     FROM ports
     ORDER BY signs.geom <-> ports.geom
   LIMIT 1) AS closest_port

10

Çapraz birleştirmeli yaklaşım, dizin kullanmaz ve çok fazla bellek gerektirir. Yani temelde iki seçeneğiniz var. 9.3 öncesi, ilişkili bir alt sorgu kullanırsınız. 9.3+ kullanabilirsiniz LATERAL JOIN.

Yanal bir bükülme ile KNN GIST Size yakın bir veritabanına yakında

(yakında sorulacak soruları kesin)


1
Yanal bir birleşimin soğuk kullanımı. Bu bağlamda daha önce görmemiştim.
John Powell,

1
@ JohnBarça Gördüğüm en iyi içeriklerden biri. Ayrıca, ST_DISTANCE()en yakın poligonu bulmak için gerçekten kullanmanız gerektiğinde ve çapraz birleştirme işleminde sunucunun belleği tükenmesine neden olacağından şüpheleniyorum . En yakın çokgen sorgusu hala çözülmemiş AFAIK.
Jakub Kania

2

@John Barça

SİPARİŞ TARAFINDAN yanlış!

ORDER BY senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY");

Sağ

senal.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY"),port.id;

Aksi takdirde, en küçük değere geri dönecektir, yalnızca küçük port kimliğine sahip olan


1
Doğru olanı şuna benziyor (noktaları ve çizgileri kullandım):SELECT DISTINCT ON (points.id) points.id, lines.id, ST_Distance(lines.geom, points.geom) as dist FROM development.passed_entries As points, development."de_muc_rawSections_cleaned" As lines ORDER BY points.id, ST_Distance(lines.geom, points.geom),lines.id;
blackgis

1
Tamam, şimdi anladım. @ Dbaston'ın cevabında olduğu gibi, LATERAL JOIN yaklaşımını kullanmak muhtemelen daha iyidir, bu da hangi şeyin yakınlık açısından başka bir şeyle karşılaştırıldığını açıkça göstermektedir. Artık yukarıdaki yaklaşımı kullanmıyorum.
John Powell,
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.