Điểm gần nhất của PostGIS với ST_Distance, kNN


23

Tôi cần phải đạt được trên mỗi phần tử trên một bảng điểm gần nhất của một bảng khác. Bảng đầu tiên chứa các biển báo giao thông và bảng thứ hai là Lối vào của thị trấn. Có một điều là tôi không thể sử dụng hàm ST_ClosestPoint và tôi phải sử dụng hàm ST_Distance và nhận bản ghi tối thiểu (ST_distance) nhưng tôi khá bế tắc khi xây dựng truy vấn.

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

Tôi cần lấy id của entrnce_hall gần nhất của mỗi Traffic_sign.

Truy vấn của tôi cho đến nay:

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

Với điều này, tôi nhận được khoảng cách từ mỗi Traffic_sign đến mỗi entry_hall. Nhưng làm thế nào tôi chỉ có thể nhận được khoảng cách minimun?

Trân trọng,


Phiên bản nào của PostgreSQL?
Jakub Kania

Câu trả lời:


41

Bạn đang ở gần đó. Có một mẹo nhỏ đó là sử dụng toán tử riêng biệt của Postgres , nó sẽ trả về trận đấu đầu tiên của mỗi kết hợp - khi bạn đặt hàng theo ST_Distance, nó sẽ trả lại điểm gần nhất từ ​​mỗi senal cho mỗi cổng.

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

Nếu bạn biết rằng khoảng cách tối thiểu trong mỗi trường hợp không nhiều hơn một lượng x, (và bạn có chỉ số không gian trên các bảng của mình), bạn có thể tăng tốc độ này bằng cách đặt một WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", distance), ví dụ: nếu tất cả các khoảng cách tối thiểu được biết là không quá 10km, sau đó:

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

Rõ ràng, điều này cần phải được sử dụng một cách thận trọng, vì nếu khoảng cách tối thiểu lớn hơn, bạn sẽ không nhận được hàng nào cho sự kết hợp của senal và cổng.

Lưu ý: Thứ tự theo thứ tự phải khớp với phân biệt theo thứ tự, điều này có ý nghĩa, vì khác biệt là lấy nhóm riêng biệt đầu tiên dựa trên một số thứ tự.

Giả định rằng bạn có một chỉ mục không gian trên cả hai bảng.

CHỈNH SỬA 1 . Có một tùy chọn khác, đó là sử dụng các toán tử <-> và <#> của Postgres, (tính toán khoảng cách giữa điểm trung tâm và giới hạn hộp), giúp sử dụng hiệu quả hơn chỉ số không gian và không yêu cầu hack ST_DWithin để tránh n ^ 2 so sánh. Có một bài viết blog tốt giải thích cách họ làm việc. Điều chung cần lưu ý là hai toán tử này hoạt động trong mệnh đề 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;

CHỈNH SỬA 2 . Vì câu hỏi này đã nhận được rất nhiều sự chú ý và hàng xóm gần nhất (kNN) nói chung là một vấn đề khó khăn (về thời gian chạy thuật toán) trong GIS, có vẻ đáng để mở rộng phần nào phạm vi ban đầu của câu hỏi này.

Cách tiêu chuẩn để tìm x hàng xóm gần nhất của một đối tượng là sử dụng LATITH THAM GIA (về mặt khái niệm tương tự như đối với mỗi vòng lặp). Mượn không biết xấu hổ từ câu trả lời của dbaston , bạn sẽ làm một cái gì đó như:

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

Vì vậy, nếu bạn muốn tìm 10 cổng gần nhất, được sắp xếp theo khoảng cách, bạn chỉ cần thay đổi mệnh đề LIMIT trong truy vấn con bên. Điều này khó thực hiện hơn nhiều nếu không có THAM GIA LATITH và liên quan đến việc sử dụng logic loại ARRAY. Trong khi phương pháp này hoạt động tốt, nó có thể được tăng tốc rất lớn nếu bạn biết bạn chỉ phải tìm kiếm ở một khoảng cách nhất định. Trong trường hợp này, bạn có thể sử dụng ST_DWithin (Sign.geom, port.geom, 1000) trong truy vấn phụ, do cách lập chỉ mục hoạt động với toán tử <-> - một trong những hình học nên là một hằng số, thay vì một tham chiếu cột - có thể nhanh hơn nhiều. Vì vậy, ví dụ, để có được 3 cổng gần nhất, trong vòng 10km, bạn có thể viết một cái gì đó như sau.

 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;

Như mọi khi, việc sử dụng sẽ thay đổi tùy thuộc vào phân phối dữ liệu và truy vấn của bạn, vì vậy GIẢI THÍCH là người bạn tốt nhất của bạn.

Cuối cùng, có một vấn đề nhỏ, nếu sử dụng TRÁI thay vì CROSS THAM GIA LATITH trong đó bạn phải thêm TRUE sau các bí danh truy vấn bên, ví dụ:

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;

Cần lưu ý rằng điều này sẽ không hoạt động tốt với dữ liệu lớn.
Jakub Kania

@JakubKania. Nó phụ thuộc vào việc bạn có thể sử dụng ST_DWithin hay không. Nhưng, vâng, điểm lấy. Thật không may, toán tử Order by <-> / <#> yêu cầu một trong các hình học là một hằng số, phải không?
John Powell

@ JohnPowellakaBarça bất kỳ cơ hội nào bạn biết bài viết blog đó hiện đang sống ở đâu? - hoặc, một lời giải thích tương tự của các toán tử <-> và <#>? Cảm ơn!!
DPSSpatial

@DPSSpatial, thật khó chịu. Tôi không, nhưng có cái nàycái này nói một chút về phương pháp này. Cái thứ hai, sử dụng tham gia bên, đó là một cải tiến thú vị khác.
John Powell

@DPSSpatial. Đó là tất cả một chút trơn trượt này <->, <#> và công cụ tham gia bên. Tôi đã thực hiện điều này với các bộ dữ liệu rất lớn và hiệu suất thật kinh khủng, mà không sử dụng ST_DWithin, điều mà tất cả những điều này được cho là nên tránh. Cuối cùng, knn là một vấn đề phức tạp, vì vậy việc sử dụng có thể khác nhau. Chúc may mắn :-)
John Powell

13

Điều này có thể được thực hiện với một LATERAL JOIN trong PostgreQuery 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 với liên kết chéo không sử dụng các chỉ mục và đòi hỏi nhiều bộ nhớ. Vì vậy, về cơ bản bạn có hai sự lựa chọn. Trước 9.3 bạn sẽ sử dụng truy vấn con tương quan. 9.3+ bạn có thể sử dụng a LATERAL JOIN.

KNN GIST với một vòng xoắn bên Sắp có cơ sở dữ liệu gần bạn

(truy vấn chính xác để theo dõi sớm)


1
Sử dụng mát mẻ của một tham gia bên. Chưa từng thấy điều đó trước đây trong bối cảnh này.
John Powell

1
@ JohnBarça Đó là một trong những bối cảnh tốt nhất tôi từng thấy. Tôi cũng nghi ngờ nó sẽ hữu ích khi bạn thực sự cần sử dụng ST_DISTANCE()để tìm đa giác gần nhất và tham gia chéo đang khiến máy chủ hết bộ nhớ. Truy vấn đa giác gần nhất vẫn chưa được giải quyết AFAIK.
Jakub Kania

2

@ John Barça

ĐẶT HÀNG B isNG là sai!

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

Đúng

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

nếu không, nó sẽ trả về không phải là gần nhất, chỉ có id cổng nhỏ


1
Một cái đúng trông như thế này (tôi đã sử dụng các điểm và đường thẳng):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
OK, tôi hiểu bạn ngay bây giờ. Có lẽ thực sự tốt hơn khi sử dụng phương pháp LATITH THAM GIA, như trong câu trả lời của @ dbaston, điều này cho thấy rõ điều gì đang được so sánh với những điều khác về sự gần gũi. Tôi không sử dụng cách tiếp cận trên nữa.
John Powell
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.