주어진 Lat Lng 위치에서 특정 거리 내에있는 모든 Latitude Longitude 위치를 찾는 알고리즘


86

위도 + 경도 위치가있는 장소 데이터베이스 (예 : 40.8120390, -73.4889650)가 주어지면 특정 위치에서 주어진 거리 내에있는 모든 위치를 어떻게 찾을 수 있습니까?

DB에서 모든 위치를 선택한 다음 하나씩 살펴보고 시작 위치에서 거리를 가져와 지정된 거리 내에 있는지 확인하는 것은 그리 효율적이지 않은 것 같습니다. DB에서 처음에 선택한 위치를 좁히는 좋은 방법이 있습니까? 좁혀진 위치 세트가있는 경우 (또는없는 경우) 여전히 거리를 확인하기 위해 하나씩 이동합니까, 아니면 더 좋은 방법이 있습니까?

내가 이것을하는 언어는별로 중요하지 않습니다. 감사!


4
이것이 필요할 수 있습니다 : en.wikipedia.org/wiki/K-d_tree
biziclop

1
하나의 SQL 쿼리로 해결할 수 없습니까? SELECT * FROM Places WHERE (Lat-: Lat) ^ 2 + (Long-: Long) ^ 2 <= : Distance ^ 2 (ofc, 지구가 구형이되는 것과 관련된 다른 수학, 이것은 단지 예입니다)
Dialecticus

1
@Ashu, nOiAd, 불행히도 그 프로젝트를 포기해야 했으므로 해결책을 선택하지 않았습니다. 프로젝트에서 솔루션 중 하나를 사용한다면 저와 다른 사람들이 여기에 대한 귀하의 의견을 정말 감사하게 생각합니다.
Valera

답변:


41

위도 사이의 거리를 비교하여 시작하십시오. 각 위도는 약 111km (69 마일) 떨어져 있습니다. 범위는 (지구의 약간 타원체 모양으로 인해) 적도에서 110.567km (68.703 마일)에서 극에서 69.407 (111.699km)까지 다양합니다. 두 위치 사이의 거리는 위도 사이의 거리와 같거나 더 큽니다.

경도의 경우에는 해당되지 않습니다. 각 경도의 길이는 위도에 따라 다릅니다. 그러나 데이터가 특정 지역 (예 : 단일 국가)에 한정된 경우 경도에 대한 최소 및 최대 경계도 계산할 수 있습니다.


계속하면 구형 지구를 가정하는 낮은 정확도의 빠른 거리 계산이 수행됩니다.

좌표가 {lat1, lon1} 및 {lat2, lon2} 인 두 점 사이의 대원 거리 d는 다음과 같이 지정됩니다.

d = acos(sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos(lon1-lon2))

단거리에서 반올림 오차가 덜 발생하는 수학적으로 동등한 공식은 다음과 같습니다.

d = 2*asin(sqrt((sin((lat1-lat2)/2))^2 +
    cos(lat1)*cos(lat2)*(sin((lon1-lon2)/2))^2))

d는 라디안 단위의 거리입니다.

distance_km ≈ radius_km * distance_radians ≈ 6371 * d

(6371km 는 지구의 평균 반경입니다. )

이 방법 계산 요구 사항은 미미합니다. 그러나 결과는 작은 거리에서 매우 정확합니다.


그런 다음 주어진 거리에 있으면 더 정확한 방법을 사용하십시오.

Vincenty 역 공식 도 사용할 수 있지만 GeographicLib 는 내가 아는 가장 정확한 구현 입니다.


RDBMS를 사용하는 경우 위도를 기본 키로 설정하고 경도를 보조 키로 설정합니다. 위에서 설명한대로 위도 범위 또는 위도 / 경도 범위를 쿼리 한 다음 결과 집합에 대한 정확한 거리를 계산합니다.

모든 주요 RDBMS의 최신 버전은 기본적으로 지리적 데이터 유형 및 쿼리를 지원합니다.


참고로 첫 번째 링크가 끊어졌습니다.
kunruh

@kunruh : 감사합니다. 링크는 현재 오프라인 인 것처럼 보이는 Ed Williams의 Aviation Formulary를 가리키고있었습니다. 링크를 수식으로 대체했습니다.
리 오르 년 Kogan

이 링크는 거의 모든 주제와 관련 설명한 movable-type.co.uk/scripts/...
madeinQuant

14

현재 사용자의 위도, 경도 및 찾고자하는 거리를 기반으로 SQL 쿼리는 아래와 같습니다.

SELECT * FROM(
    SELECT *,(((acos(sin((@latitude*pi()/180)) * sin((Latitude*pi()/180))+cos((@latitude*pi()/180)) * cos((Latitude*pi()/180)) * cos(((@longitude - Longitude)*pi()/180))))*180/pi())*60*1.1515*1.609344) as distance FROM Distances) t
WHERE distance <= @distance

@latitude 및 @longitude는 지점의 위도와 경도입니다. 위도와 경도는 거리 테이블의 열입니다. pi 값은 22/7입니다.


2
@distance 매개 변수가 KM 또는 마일로되어 있습니까?
garfbradaz

거리가 KM에 있거나 내 스크립트가 잘못되었다고 가정합니다. 누군가 위의 질문에 대답하십시오.
Omar Abbas


5

Tank´s Yogihosting

내 데이터베이스에 Open Streep Maps의 테이블 하나가 있고 성공적으로 테스트했습니다.

거리는 미터 단위로 잘 작동합니다.

SET @orig_lat=-8.116137;
SET @orig_lon=-34.897488;
SET @dist=1000;

SELECT *,(((acos(sin((@orig_lat*pi()/180)) * sin((dest.latitude*pi()/180))+cos((@orig_lat*pi()/180))*cos((dest.latitude*pi()/180))*cos(((@orig_lon-dest.longitude)*pi()/180))))*180/pi())*60*1.1515*1609.344) as distance FROM nodes AS dest HAVING distance < @dist ORDER BY distance ASC LIMIT 100;

세상은 구체가 아닙니다!
Toby Speight

당신의 제안은 무엇입니까?
Helmut Kemper


2

biziclop이 언급했듯이 일종의 메트릭 공간 트리가 아마도 최선의 선택이 될 것입니다. 저는 kd- 트리와 쿼드 트리를 사용하여 이러한 종류의 범위 쿼리를 수행 한 경험이 있으며 놀랍도록 빠릅니다. 그들은 또한 쓰기가 그렇게 어렵지 않습니다. "내 데이터 세트에서이 다른 지점에 가장 가까운 지점은 무엇입니까?"와 같은 다른 흥미로운 질문에 답할 수 있으므로 이러한 구조 중 하나를 살펴 보는 것이 좋습니다.


이것이 문제를 해결하는 데 귀중한 힌트가 될 수 있지만 답은 실제로 솔루션을 입증하는 데 필요합니다. 의미를 보여주기 위해 예제 코드를 제공하도록 편집 하십시오 . 또는 대신 주석으로 작성하는 것이 좋습니다.
Toby Speight

1
나는 실제로 여기에 코드가 산만 할 것이라고 생각합니다. 트리 구조와 선택한 특정 언어를 포함하는 라이브러리에 너무 구체적 일 것입니다 (이 질문에는 언어 태그가 없습니다.)
templatetypedef


0

위도-경도를 거리 계산에 도움이 될 수있는 미터법 형식 인 UTM 형식으로 변환 할 수 있습니다. 그러면 포인트가 특정 위치에 속하는지 쉽게 결정할 수 있습니다.


1
이것이 문제를 해결하는 데 귀중한 힌트가 될 수 있지만 답은 실제로 솔루션을 입증하는 데 필요합니다. 의미를 보여주기 위해 예제 코드를 제공하도록 편집 하십시오 . 또는 대신 주석으로 작성하는 것이 좋습니다.
Toby Speight

0

모든 언어가 허용된다고 말 했으므로 자연스러운 선택은 PostGIS입니다.

SELECT * FROM places
WHERE ST_DistanceSpheroid(geom, $location, $spheroid) < $max_metres;

WGS 데이텀을 사용하려면 다음과 같이 설정해야 $spheroid합니다.'SPHEROID["WGS 84",6378137,298.257223563]'

당신이 색인했다고 가정 places에 의해 geom열이 합리적으로 효율적이어야한다.


0

@yogihosting에서 제공 한 솔루션 덕분에 아래 표시된 코드를 사용하여 mysql의 스키마없는 열에서 유사한 결과를 얻을 수있었습니다.

// @params - will be bound to named query parameters
$criteria = [];
$criteria['latitude'] = '9.0285183';
$criteria['longitude'] = '7.4869546';
$criteria['distance'] = 500;
$criteria['skill'] = 'software developer';

// Get doctrine connection 
$conn = $this->getEntityManager()->getConnection();

        $sql = '
               SELECT DISTINCT m.uuid AS phone, (((acos(sin((:latitude*pi()/180)) * sin((JSON_EXTRACT(m.location, "$.latitude")*pi()/180))+cos((:latitude*pi()/180)) * 
              cos((JSON_EXTRACT(m.location, "$.latitude")*pi()/180)) * 
              cos(((:longitude - JSON_EXTRACT(m.location, "$.longitude"))*pi()/180))))*180/pi())*60*1.1515*1.609344) AS distance FROM member_profile AS m 
               INNER JOIN member_card_subscription mcs ON mcs.primary_identity = m.uuid
               WHERE mcs.end > now() AND JSON_SEARCH(m.skill_logic, "one", :skill) IS NOT NULL  AND (((acos(sin((:latitude*pi()/180)) * sin((JSON_EXTRACT(m.location, "$.latitude")*pi()/180))+cos((:latitude*pi()/180)) * 
              cos((JSON_EXTRACT(m.location, "$.latitude")*pi()/180)) * 
              cos(((:longitude - JSON_EXTRACT(m.location, "$.longitude"))*pi()/180))))*180/pi())*60*1.1515*1.609344) <= :distance ORDER BY distance
               ';
        $stmt = $conn->prepare($sql);
        $stmt->execute(['latitude'=>$criteria['latitude'], 'longitude'=>$criteria['longitude'], 'skill'=>$criteria['skill'], 'distance'=>$criteria['distance']]);
        var_dump($stmt->fetchAll());

위의 코드는 교리 DB 연결 및 PHP를 사용하고 있습니다.


-2

이 방정식을 확인하면 도움이 될 것 같습니다.

SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distance FROM markers HAVING distance < 25 ORDER BY distance LIMIT 0 , 20;

이 코드는 문제를 해결하는 데 도움이 될 수 있지만 질문에 대한 이유 및 / 또는 답변 방법을 설명하지 않습니다 . 이 추가 컨텍스트를 제공하면 장기적인 교육 가치가 크게 향상됩니다. 제발 편집 제한 및 가정이 적용되는 것을 포함하여, 설명을 추가 답변을. 특히 마법 값 3959와 37은 어디에서 왔습니까?
Toby Speight
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.