ST_Distance, kNN으로 PostGIS 가장 가까운 포인트


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

모든 traffic_sign에서 가장 가까운 entrnce_hall의 ID를 가져와야합니다.

지금까지 내 쿼리 :

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

이것으로 모든 traffic_sign에서 모든 entrance_hall까지의 거리를 얻습니다. 그러나 어떻게 미니 먼 거리 만 얻을 수 있습니까?

문안 인사,


PostgreSQL의 버전은 무엇입니까?
Jakub Kania

답변:


41

거의 다 왔습니다. ST_Distance에서 주문할 때 각 조합의 첫 번째 일치 항목을 반환하는 Postgres의 고유 연산자 를 사용하는 작은 트릭이 있습니다 .ST_Distance에서 주문할 때 각 센날에서 각 포트에 가장 가까운 지점을 효과적으로 반환합니다.

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의 양보다 크지 않다는 것을 알고 있다면 (그리고 테이블에 공간 인덱스가 있음) WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", distance), 예를 들어 모든 최소 거리가 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");

분명히, 최소 거리가 더 큰 것처럼, 단순히 senal과 port의 조합에 대한 행을 얻지 못하는 것처럼 조심스럽게 사용해야합니다.

참고 : 주문 별 주문은 주문에 따라 다릅니다. 이는 distinct가 일부 주문에 따라 첫 번째 고유 한 그룹을 취하므로 의미가 있습니다.

두 테이블 모두에 공간 인덱스가 있다고 가정합니다.

편집 1 . 공간 인덱스를보다 효율적으로 사용하고 n을 피하기 위해 ST_DWithin 핵을 필요로하지 않는 Postgres의 <-> 및 <#> 연산자 (각각 중심점 및 경계 상자 거리 계산)를 사용하는 또 다른 옵션이 있습니다 ^ 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 (k-nearest neighbors)은 일반적으로 GIS에서 어려운 알고리즘 문제 (알고리즘 런타임으로)이므로이 질문의 원래 범위를 다소 확장하는 것이 좋습니다.

하나의 객체에서 가장 가까운 x 개의 이웃을 찾는 표준 방법은 LATERAL JOIN을 사용하는 것입니다 (개념적으로 각 루프와 유사 함). 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 절을 변경하기 만하면됩니다. LATERAL JOINS 없이는 훨씬 어렵고 ARRAY 유형 논리를 사용합니다. 이 방법은 잘 작동하지만 주어진 거리까지만 검색하면된다는 것을 알고 있다면 엄청나게 빨라질 수 있습니다. 이 경우 하위 쿼리에서 ST_DWithin (signs.geom, ports.geom, 1000)을 사용할 수 있습니다.이 방법은 인덱싱이 <-> 연산자와 작동하는 방식 때문에 지오메트리 중 하나는 상수가 아니라 상수 여야합니다. 열 참조-훨씬 빠를 수 있습니다. 예를 들어, 10km 이내에 가장 가까운 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
      WHERE ST_DWithin(ports.geom, signs.geom, 10000)
      ORDER BY ST_Distance(ports.geom, signs.geom)
     LIMIT 3
   ) AS closest_port;

항상 그렇듯이 사용량은 데이터 배포 및 쿼리에 따라 달라 지므로 EXPLAIN 이 가장 친한 친구입니다.

마지막으로 측면 쿼리 별칭 뒤에 TRUE 를 추가해야한다는 점에서 CROSS JOIN LATERAL 대신 LEFT 를 사용하는 경우 약간의 문제 가 있습니다 .

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_DWithin을 사용할 수 있는지 여부에 따라 다릅니다. 그러나 그렇습니다. 불행히도 Order by <-> / <#> 연산자는 형상 중 하나가 상수 여야합니까?
존 파월

@ JohnPowellakaBarça 블로그 게시물이 현재 어디에 있는지 아십니까? 또는 <-> 및 <#> 연산자에 대한 비슷한 설명입니까? 감사!!
DPSSpatial

@DPSSpatial, 그것은 성가신 일입니다. 나는 그렇지 않지만 이 접근법에 대해 조금 이야기하는 이것이것이 있습니다. 측면 조인을 사용하는 두 번째 방법은 또 다른 흥미로운 개선 사항입니다.
John Powell

@DPSSpatial. 이 <->, <#> 및 측면 조인 항목은 모두 약간 미끄러운 부분입니다. ST_DWithin을 사용하지 않고 매우 큰 데이터 세트 로이 작업을 수행했으며 성능이 끔찍했습니다.이 모든 것을 피해야합니다. 궁극적으로 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

교차 결합을 사용하는 어프로치는 인덱스를 사용하지 않으며 많은 메모리가 필요합니다. 기본적으로 두 가지 선택이 있습니다. 9.3 이전에는 상관 하위 쿼리를 사용했습니다. 9.3 이상을 사용할 수 있습니다 LATERAL JOIN.

측면 비틀림이있는 KNN GIST 가까운 데이터베이스로 곧 제공

(곧 정확한 쿼리가 이어질 것입니다)


1
측면 조인의 멋진 사용. 이 문맥에서 이전에 그것을 보지 못했습니다.
존 파월

1
@ JohnBarça 그것은 내가 본 최고의 컨텍스트 중 하나입니다. 또한 ST_DISTANCE()가장 가까운 다각형을 찾기 위해 실제로 사용해야 하고 교차 조인으로 인해 서버의 메모리가 부족 해질 때 도움이 될 것으로 생각합니다 . 가장 가까운 다각형 쿼리는 여전히 해결되지 않은 AFAIK입니다.
Jakub Kania

2

@ 존 바르카

ORDER BY가 잘못되었습니다!

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;
사용함

1
알았어, 이제 가져와 @dbaston의 답변에서와 같이 실제로 LATERAL JOIN 방식을 사용하는 것이 더 좋을 것입니다. 나는 위의 접근법을 더 이상 사용하지 않습니다.
John Powell
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.