Thuật toán để tìm tất cả các vị trí Kinh độ trong một khoảng cách nhất định từ một vị trí Vĩ độ nhất định


81

Với cơ sở dữ liệu về các địa điểm có vị trí Vĩ độ + Kinh độ, chẳng hạn như 40.8120390, -73.4889650, làm cách nào để tìm tất cả các địa điểm trong một khoảng cách nhất định của một vị trí cụ thể?

Có vẻ không hiệu quả lắm khi chọn tất cả các vị trí từ DB và sau đó đi qua từng vị trí một, lấy khoảng cách từ vị trí xuất phát để xem chúng có nằm trong khoảng cách được chỉ định hay không. Có cách nào tốt để thu hẹp các vị trí được chọn ban đầu từ DB không? Sau khi tôi có (hay không?) Một nhóm các vị trí bị thu hẹp, tôi vẫn đi qua từng vị trí một để kiểm tra khoảng cách, hay có cách nào tốt hơn?

Ngôn ngữ tôi làm việc này không thực sự quan trọng. Cảm ơn!


4
Đây có thể là những gì bạn cần: en.wikipedia.org/wiki/K-d_tree
biziclop

1
Một truy vấn SQL không thể giải quyết nó? CHỌN * TỪ Địa điểm WHERE (Vĩ độ -: Vĩ độ) ^ 2 + (Dài -: Dài) ^ 2 <=: Khoảng cách ^ 2 (ofc, một số phép toán khác liên quan đến Trái đất là hình cầu và tất cả, đây chỉ là một ví dụ)
Dialecticus

1
@Ashu, nOiAd, Thật không may, tôi đã phải từ bỏ dự án đó, vì vậy cuối cùng tôi đã không chọn được giải pháp. Nếu các bạn sử dụng một trong các giải pháp trong các dự án của mình, tôi và những người khác sẽ thực sự đánh giá cao nhận xét của các bạn về nó ở đây.
Valera

Câu trả lời:


41

Bắt đầu bằng cách So sánh khoảng cách giữa các vĩ độ. Mỗi mức độ vĩ độ khoảng 69 dặm (111 km) ngoài. Phạm vi dao động (do hình dạng của trái đất nhẹ ellipsoid) từ 68,703 dặm (110,567 km) tại đường xích đạo đến 69,407 (111,699 km) ở hai cực. Khoảng cách giữa hai địa điểm sẽ bằng hoặc lớn hơn khoảng cách giữa các vĩ độ của chúng.

Lưu ý rằng điều này không đúng đối với các kinh độ - độ dài của mỗi độ kinh độ phụ thuộc vào vĩ độ. Tuy nhiên, nếu dữ liệu của bạn bị giới hạn ở một số khu vực (ví dụ: một quốc gia) - bạn cũng có thể tính toán giới hạn tối thiểu và tối đa cho các kinh độ.


Tiếp tục sẽ là một phép tính khoảng cách nhanh, có độ chính xác thấp, giả sử trái đất hình cầu:

Khoảng cách đường tròn d giữa hai điểm có tọa độ {lat1, lon1} và {lat2, lon2} được cho bởi:

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

Một công thức tương đương về mặt toán học, ít bị lỗi làm tròn cho khoảng cách ngắn là:

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

d là khoảng cách tính bằng radian

distance_km ≈ radius_km * distance_radians ≈ 6371 * d

(6371 km là bán kính trung bình của trái đất )

Yêu cầu tính toán của phương pháp này là số thập phân. Tuy nhiên, kết quả là rất chính xác đối với khoảng cách nhỏ.


Sau đó, nếu nó nằm trong một khoảng cách nhất định, nhiều hơn hoặc ít hơn, hãy sử dụng một phương pháp chính xác hơn.

GeographicLib là cách triển khai chính xác nhất mà tôi biết, mặc dù công thức nghịch đảo Vincenty cũng có thể được sử dụng.


Nếu bạn đang sử dụng RDBMS, hãy đặt vĩ độ làm khóa chính và kinh độ làm khóa phụ. Truy vấn phạm vi vĩ độ hoặc phạm vi vĩ độ / kinh độ, như được mô tả ở trên, sau đó tính toán khoảng cách chính xác cho tập kết quả.

Lưu ý rằng các phiên bản hiện đại của tất cả các RDBMS chính đều hỗ trợ kiểu dữ liệu địa lý và truy vấn nguyên bản.


Chỉ cần sơ suất, liên kết đầu tiên đã bị hỏng.
kunruh

@kunruh: Cảm ơn bạn. Liên kết đã trỏ đến Danh mục hàng không của Ed Williams, hiện có vẻ như đang ngoại tuyến. Tôi đã thay thế liên kết bằng một công thức.
Lior Kogan

Liên kết này giải thích hầu hết tất cả những gì liên quan đến chủ đề này movable-type.co.uk/scripts/…
madeinQuant

13

Dựa trên vĩ độ, kinh độ của người dùng hiện tại và khoảng cách bạn muốn tìm, truy vấn sql được đưa ra bên dưới.

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 và @longitude là vĩ độ và kinh độ của điểm. Vĩ độ và kinh độ là các cột của bảng khoảng cách. Giá trị của pi là 22/7


1
Là tham số @distance ở KM hay Miles?
garfbradaz


5

Yogihosting của xe tăng

Tôi có trong cơ sở dữ liệu của mình một nhóm bảng từ Open Streep Maps và tôi đã kiểm tra thành công.

Khoảng cách làm việc tốt tính bằng mét.

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;

Thế giới không phải là một hình cầu!
Toby Speight

Gợi ý của bạn là gì?
Helmut Kemper


2

Như biziclop đã đề cập, một số loại cây không gian số liệu có thể sẽ là lựa chọn tốt nhất của bạn. Tôi có kinh nghiệm sử dụng kd-tree và quad tree để thực hiện các loại truy vấn phạm vi này và chúng nhanh đáng kinh ngạc; chúng cũng không khó viết. Tôi khuyên bạn nên xem xét một trong những cấu trúc này, vì chúng cũng cho phép bạn trả lời các câu hỏi thú vị khác như "điểm gần nhất trong tập dữ liệu của tôi với điểm khác này là gì?"


Mặc dù đây có thể là một gợi ý có giá trị để giải quyết vấn đề, nhưng một câu trả lời thực sự cần phải chứng minh giải pháp. Vui lòng chỉnh sửa để cung cấp mã ví dụ để hiển thị ý bạn. Ngoài ra, thay vào đó, hãy cân nhắc viết bài này dưới dạng nhận xét.
Toby Speight

1
Tôi thực sự nghĩ rằng mã ở đây sẽ được mất tập trung - nó sẽ là quá cụ thể cho các thư viện chứa các cấu trúc cây và ngôn ngữ cụ thể được chọn (thông báo rằng câu hỏi này không được gắn thẻ với một ngôn ngữ.)
templatetypedef


0

Bạn có thể chuyển đổi kinh độ-vĩ độ sang định dạng UTM, đây là định dạng số liệu có thể giúp bạn tính toán khoảng cách. Sau đó, bạn có thể dễ dàng quyết định nếu điểm rơi vào vị trí cụ thể.


1
Mặc dù đây có thể là một gợi ý có giá trị để giải quyết vấn đề, nhưng một câu trả lời thực sự cần phải chứng minh giải pháp. Vui lòng chỉnh sửa để cung cấp mã ví dụ để hiển thị ý bạn. Ngoài ra, thay vào đó, hãy cân nhắc viết bài này dưới dạng nhận xét.
Toby Speight

0

Vì bạn nói rằng bất kỳ ngôn ngữ nào cũng được chấp nhận, lựa chọn tự nhiên là PostGIS:

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

Nếu bạn muốn sử dụng dữ liệu WGS, bạn nên đặt $spheroidthành'SPHEROID["WGS 84",6378137,298.257223563]'

Giả sử rằng bạn đã lập chỉ mục placestheo geomcột, điều này sẽ hiệu quả một cách hợp lý.


0

Nhờ giải pháp được cung cấp bởi @yogihosting, tôi đã có thể đạt được kết quả tương tự từ các cột không toán học của mysql với các mã được hiển thị bên dưới:

// @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());

Xin lưu ý rằng đoạn mã trên đang sử dụng kết nối DB học thuyết và PHP


-2

bạn có thể kiểm tra phương trình này, tôi nghĩ nó sẽ hữu ích

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;

Mặc dù mã này có thể giúp giải quyết vấn đề, nhưng nó không giải thích tại sao và / hoặc cách nó trả lời câu hỏi. Cung cấp bối cảnh bổ sung này sẽ cải thiện đáng kể giá trị giáo dục lâu dài của nó. Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích, bao gồm những giới hạn và giả định nào được áp dụng. Đặc biệt, giá trị ma thuật 3959 và 37 đến từ đâu?
Toby Speight
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.