PostGIS จุดที่ใกล้ที่สุดด้วย ST_Distance, kNN


23

ฉันต้องการได้รับในแต่ละองค์ประกอบในหนึ่งตารางจุดที่ใกล้เคียงที่สุดของตารางอื่น ตารางแรกมีสัญญาณไฟจราจรและทางเข้าที่สองเป็นโถงทางเข้าของเมือง สิ่งหนึ่งคือฉันไม่สามารถใช้ฟังก์ชัน ST_ClosestPoint ได้และฉันต้องใช้ฟังก์ชัน ST_Distance และรับระเบียน min (ST_distance) แต่ฉันติดคำถามในการสร้าง

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
);

ฉันต้องได้รับ id ของ entrnce_hall ที่ใกล้เคียงที่สุดของทุก traffic_sign

คำถามของฉันจนถึงขณะนี้:

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")

ด้วยวิธีนี้ฉันได้รับระยะทางจากทุกการจราจรลงชื่อเข้าทุกทางเข้าห้องโถง แต่ฉันจะหาระยะห่างขั้นต่ำได้อย่างไร

ความนับถือ,


PostgreSQL รุ่นใด
Jakub Kania

คำตอบ:


41

คุณใกล้จะถึงแล้ว มีเคล็ดลับเล็กน้อยที่จะใช้โอเปอเรเตอร์ที่ชัดเจนของ Postgres ซึ่งจะคืนค่าการจับคู่ครั้งแรกของชุดค่าผสมแต่ละชุด - เมื่อคุณสั่งซื้อโดย ST_ ระยะทางได้อย่างมีประสิทธิภาพจะคืนค่าจุดที่ใกล้ที่สุดจากแต่ละ Senal ไปยังแต่ละพอร์ต

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");

หากคุณรู้ว่าระยะห่างต่ำสุดในแต่ละกรณีนั้นไม่เกินจำนวน x, (และคุณมีดัชนีเชิงพื้นที่บนโต๊ะของคุณ) คุณสามารถเร่งความเร็วได้โดยการใส่ a WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", distance)เช่นถ้าระยะทางขั้นต่ำทั้งหมดรู้ว่าเป็น ไม่เกิน 10 กม. จากนั้น:

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");

เห็นได้ชัดว่าสิ่งนี้จำเป็นต้องใช้ด้วยความระมัดระวังราวกับว่าระยะทางขั้นต่ำนั้นมากกว่าคุณจะไม่ได้รับแถวสำหรับการรวมกันของ senal และพอร์ต

หมายเหตุ:การสั่งซื้อตามคำสั่งซื้อจะต้องตรงกับความแตกต่างในการสั่งซื้อซึ่งทำให้เข้าใจได้เนื่องจากความแตกต่างคือการใช้กลุ่มที่แตกต่างกันครั้งแรกตามการสั่งซื้อบางอย่าง

มันจะสันนิษฐานว่าคุณมีดัชนีเชิงพื้นที่บนทั้งสองตาราง

แก้ไข 1 มีตัวเลือกอื่นซึ่งใช้ตัวดำเนินการ <-> และ <#> ของ Postgres (จุดกึ่งกลางและการคำนวณระยะห่างของกล่องตามลำดับ) ซึ่งใช้ดัชนีดัชนีเชิงพื้นที่อย่างมีประสิทธิภาพมากขึ้นและไม่ต้องการแฮ็ค ST_D ภายในแฮ็คเพื่อหลีกเลี่ยง n เปรียบเทียบ ^ 2 มีบทความบล็อกที่ดีอธิบายวิธีการทำงาน สิ่งทั่วไปที่ควรทราบก็คือตัวดำเนินการทั้งสองนี้ทำงานในข้อ ORDER BY

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

แก้ไข 2 เนื่องจากคำถามนี้ได้รับความสนใจเป็นอย่างมากและเพื่อนบ้านที่ใกล้ที่สุด (kNN) เป็นปัญหาที่ยาก (ในแง่ของอัลกอริทึมรันไทม์) ใน GIS ดูเหมือนว่าคุ้มค่าที่จะขยายขอบเขตเดิมของคำถามนี้

วิธีมาตรฐานในการค้นหา x เพื่อนบ้านที่ใกล้ที่สุดของวัตถุหนึ่งคือการใช้ LATERAL JOIN (แนวคิดคล้ายกับ a สำหรับแต่ละลูป) การยืมคำตอบจากdbaston อย่างไร้ยางอายคุณจะทำสิ่งที่ชอบ:

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 พอร์ตที่ใกล้ที่สุดซึ่งเรียงลำดับตามระยะทางคุณเพียงแค่เปลี่ยนคำสั่ง LIMIT ในแบบสอบถามย่อยด้านข้าง สิ่งนี้ทำได้ยากกว่าหากไม่ได้เข้าร่วมในภายหลังและเกี่ยวข้องกับการใช้ตรรกะประเภท ARRAY ในขณะที่วิธีการนี้ใช้งานได้ดีคุณสามารถเร่งความเร็วได้อย่างมหาศาลหากคุณรู้ว่าคุณต้องค้นหาระยะทางที่กำหนด ในตัวอย่างนี้คุณสามารถใช้ST_DWithin (signs.geom, ports.geom, 1000) ในแบบสอบถามย่อยซึ่งเนื่องจากวิธีการทำดัชนีทำงานกับตัวดำเนินการ <-> หนึ่งในรูปทรงเรขาคณิตควรเป็นค่าคงที่แทนที่จะเป็น การอ้างอิงคอลัมน์ - อาจเร็วกว่ามาก ตัวอย่างเช่นในการรับพอร์ตที่ใกล้ที่สุด 3 แห่งภายในระยะทาง 10 กม. คุณสามารถเขียนสิ่งต่อไปนี้

 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;

เช่นเคยการใช้งานจะแตกต่างกันไปขึ้นอยู่กับการกระจายข้อมูลและการสืบค้นของคุณดังนั้นจึงควรอธิบายถึงเพื่อนที่ดีที่สุดของคุณ

ในที่สุดก็มี gotcha เล็กน้อยหากใช้LEFTแทนที่จะเป็นCROSS JOIN LATERALคุณต้องเพิ่มค่าTRUEหลังจากการสืบค้นด้วยชื่อแทนด้านข้างเช่น

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;

ควรสังเกตว่าสิ่งนี้จะทำงานได้ไม่ดีกับข้อมูลจำนวนมหาศาล
Jakub Kania

@JakubKania ขึ้นอยู่กับว่าคุณสามารถใช้ ST_D ภายในหรือไม่ แต่ใช่แล้ว น่าเสียดายที่ผู้ดำเนินการสั่งซื้อโดย <-> / <#> ต้องการหนึ่งในรูปทรงเรขาคณิตที่จะคงที่ใช่หรือไม่?
John Powell

@ JohnPowellakaBarçaโอกาสใด ๆ ที่คุณรู้ว่าบล็อกโพสต์ที่อาศัยอยู่ในปัจจุบัน? - หรือคำอธิบายที่คล้ายคลึงกันของตัวดำเนินการ <-> และ <#> ขอบคุณ !!
DPSSpatial

@ DPSSpatial มันน่ารำคาญ ฉันไม่ได้ แต่มีสิ่งนี้และสิ่งนี้ที่พูดคุยเล็กน้อยเกี่ยวกับวิธีการนี้ ส่วนที่สองใช้การเชื่อมต่อด้านข้างด้วยซึ่งเป็นการปรับปรุงที่น่าสนใจอีกอย่างหนึ่ง
John Powell

@DPSSpatial มันลื่นนิดหน่อย <->, <#> และสิ่งด้านข้างนี้เข้าร่วม ฉันได้ทำสิ่งนี้ด้วยชุดข้อมูลที่มีขนาดใหญ่มากและมีประสิทธิภาพที่น่ากลัวโดยไม่ต้องใช้ ST_D ภายในซึ่งทั้งหมดนี้ควรหลีกเลี่ยง ในที่สุด knn เป็นปัญหาที่ซับซ้อนดังนั้นการใช้งานอาจแตกต่างกันไป ขอให้โชคดี :-)
John Powell

13

สามารถทำได้ด้วยLATERAL JOINPostgreSQL 9.3+:

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

aproach ที่มี cross-join ไม่ได้ใช้ดัชนีและต้องการหน่วยความจำมาก โดยพื้นฐานแล้วคุณมีสองทางเลือก ก่อน 9.3 คุณจะต้องใช้แบบสอบถามย่อยที่มีความสัมพันธ์ 9.3+ LATERAL JOINคุณสามารถใช้

KNN GIST พร้อมเกลียวด้านข้างเร็ว ๆ นี้ไปยังฐานข้อมูลใกล้บ้านคุณ

(คำค้นหาที่แน่นอนที่จะติดตามในไม่ช้า)


1
ใช้เย็นของการเข้าร่วมด้านข้าง ไม่เคยเห็นมาก่อนในบริบทนี้
John Powell

1
@ JohnBarçaเป็นหนึ่งในบริบทที่ดีที่สุดที่ฉันเคยเห็น ฉันยังสงสัยว่าจะเป็นประโยชน์เมื่อคุณต้องการใช้ST_DISTANCE()เพื่อค้นหารูปหลายเหลี่ยมที่ใกล้ที่สุดและการเข้าร่วมแบบข้ามทำให้เซิร์ฟเวอร์มีหน่วยความจำไม่เพียงพอ แบบสอบถามรูปหลายเหลี่ยมที่ใกล้ที่สุดยังคงเป็น AFAIK ที่ยังไม่ได้แก้ไข
Jakub Kania

2

@John Barça

สั่งซื้อโดยผิด!

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

ขวา

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

มิฉะนั้นจะส่งคืนไม่ได้ใกล้ที่สุดเท่านั้นซึ่งมีพอร์ต id น้อย


1
หนึ่งที่ถูกต้องมีลักษณะเช่นนี้ (ฉันใช้คะแนนและเส้น):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
ตกลงฉันไปรับคุณตอนนี้ จริง ๆ แล้วมันอาจจะดีกว่าถ้าใช้ LATERAL JOIN approach เช่นเดียวกับในคำตอบของ @ dbaston ซึ่งทำให้ชัดเจนว่าสิ่งใดที่ถูกเปรียบเทียบกับสิ่งอื่นในแง่ของความใกล้ชิด ฉันไม่ได้ใช้วิธีการข้างต้นอีกต่อไป
John Powell
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.