2012-01-04 32 views
7

OK - Tôi đã vật lộn với điều này trong khoảng 3 tháng và tắt và kể từ khi tôi đã cạn kiệt mọi công thức địa lý gần đó mà tôi đã gặp và tôi không phải là gần gũi hơn để có được kết quả đúng, tôi đã tìm ra thời gian để yêu cầu sự giúp đỡ.Thiếu kết quả do công thức lân cận địa lý (định vị cửa hàng)

THE AIM

Tôi đang thiết lập một thực hiện khá cơ bản của một cửa hàng định vị. Người dùng nhập mã bưu điện của họ và chọn từ danh sách tìm kiếm được xác định trước. API gmaps tạo ra các tọa độ vĩ độ/dài cho địa chỉ này và chuyển chúng đến một tập lệnh php. Trong kịch bản này, coords người dùng được truy vấn đối với một bảng cơ sở dữ liệu mysql (cấu trúc bên dưới)

post_id int(11)        
post_type varchar(20)         
lat float(10,6)        
lng float(10,6) 

Kết quả của truy vấn này (bài id) được nhập vào một truy vấn wordpress mà tạo ra XML chứa dữ liệu bản đồ đánh dấu. (Truy vấn wordpress sử dụng post__in và posts_per_page -1 để hiển thị thông tin cho tất cả các ID được tạo ra bởi các truy vấn

CÁC VẤN ĐỀ

Tóm lại, tất cả các thực hiện của công thức Haversine tôi đã đi qua dường như là kết quả trong các điểm đánh dấu bị thiếu - đặc biệt là bất kỳ điểm đánh dấu nào rất gần với tọa độ đã nhập của người dùng (không biết chính xác nhưng tôi nghĩ nó nằm trong khoảng 500m) .Đây là một vấn đề lớn nếu người dùng nhập mã bưu điện của họ và có một cửa hàng rất gần vị trí của họ sẽ không hiển thị.

Tôi đã thử khoảng 8 hoán vị khác nhau của forumla mà tôi đã đào lên fr om hướng dẫn khác nhau với cùng một kết quả. Dưới đây là công thức mà tôi hiện đang sử dụng trên các trang web mà cung cấp tất cả các dấu hiệu ngoại trừ những người rất gần gũi với người sử dụng vào các mục:

$center_lat = $_GET["lat"]; 
$center_lng = $_GET["lng"]; 
$radius = $_GET["radius"]; 

// Calculate square radius search 

$lat1 = (float) $center_lat - ((int) $radius/69); 
$lat2 = (float) $center_lat + ((int) $radius/69); 
$lng1 = (float) $center_lng - (int) $radius/abs(cos(deg2rad((float) $center_lat)) * 69); 
$lng2 = (float) $center_lng + (int) $radius/abs(cos(deg2rad((float) $center_lat)) * 69); 

$sqlsquareradius = " 
SELECT 
post_id, lat, lng 
FROM 
wp_geodatastore 
WHERE 
lat BETWEEN ".$lat1." AND ".$lat2." 
AND 
lng BETWEEN ".$lng1." AND ".$lng2." 
"; // End $sqlsquareradius 

// Create sql for circle radius check 
$sqlcircleradius = " 
SELECT 
t.post_id, 
3956 * 2 * ASIN(
    SQRT(
     POWER(
      SIN(
       (".(float) $center_lat." - abs(t.lat)) * pi()/180/2 
      ), 2 
     ) + COS(
      ".(float) $center_lat." * pi()/180 
     ) * COS(
      abs(t.lat) * pi()/180 
     ) * POWER(
      SIN(
       (".(float) $center_lng." - t.lng) * pi()/180/2 
      ), 2 
     ) 
    ) 
) AS distance 
FROM 
(".$sqlsquareradius.") AS t 
HAVING 
distance <= ".(int) $radius." 
ORDER BY distance 
"; // End $sqlcircleradius 


$result = mysql_query($sqlcircleradius); 

$row = mysql_fetch_array($result); 

while($row = mysql_fetch_array($result)) { 
// the contents of each row 
$post_ids[] = $row['post_id']; 
} 

Có 1 công thức mà tôi đã cố gắng đã được đề xuất bởi Mike Pelley đây : Geolocation SQL query not finding exact location

Công thức này dường như hiển thị các điểm đánh dấu rất gần với vị trí đã nhập của người dùng nhưng bỏ qua các điểm đánh dấu khác đã được hiển thị trong bán kính nhất định. Để làm sáng tỏ bất kỳ sự nhầm lẫn này là mã tôi đã sử dụng:

$center_lat = $_GET["lat"]; 
$center_lng = $_GET["lng"]; 
$radius = $_GET["radius"]; 

$sql = " 
SELECT post_id, lat, lng, 
truncate((degrees(acos(sin(radians(lat)) 
* sin(radians(".$center_lat.")) 
+ cos(radians(lat)) 
* cos(radians(".$center_lat.")) 
* cos(radians(".$center_lng." - lng)))) 
* 69.09*1.6),1) as distance 
FROM wp_geodatastore HAVING distance <= ".$radius." ORDER BY distance desc 
"; // End $sqlcircleradius 


$result = mysql_query($sql); 

$row = mysql_fetch_array($result); 

while($row = mysql_fetch_array($result)) { 
// Print out the contents of each row 
$post_ids[] = $row['post_id']; 
} 

CÁC YÊU CẦU

Về cơ bản tôi muốn biết tại sao không phải của các khối mã được hiển thị đánh dấu chính xác. Nếu bất cứ ai có thể đề nghị bất kỳ cải tiến mã hoặc có thể chỉ cho tôi hướng tới một số tài nguyên mà tôi có thể đã bỏ lỡ điều đó sẽ là tuyệt vời

EDIT

tưởng câu trả lời psudeo tôi đang làm việc nhưng khi nó quay ra rằng vẫn gặp sự cố. Tôi đã kết thúc lên cho một tack rất khác nhau bây giờ và tôi đang sử dụng một định vị cửa hàng jquery rất tốt mà có thể được tìm thấy ở đây: http://www.bjornblog.com/web/jquery-store-locator-plugin

Sẽ không làm việc cho mọi dự án ra có nhưng cho nhu cầu của tôi nó là hoàn hảo (và hoạt động!)

+0

Có lý do nào bạn không sử dụng khả năng không gian địa lý được xây dựng của MySQL không? http://dev.mysql.com/doc/refman/5.0/en/creating-a-spatially-enabled-mysql-database.html – Kenneth

+0

Tôi đang đấu tranh để hiểu một số nội dung trong mã của bạn. Tại sao 'HAVING' thay vì' WHERE'? Điều gì đang xảy ra với công cụ 'int' trong' (float) $ center_lat - ((int) $ radius/69); 'và' truncate' trong truy vấn khác của bạn? Hãy ghi nhớ điều này khi bạn áp dụng các công thức: một phút kinh độ tại đường xích đạo được định nghĩa là một hải lý. Một mức độ nguyên là 60 hải lý. Cuối cùng, hãy thử loại bỏ 'BETWEEN' và sử dụng 'WHERE a> = lat1 AND thay vì <= lat2'. Nó sẽ cung cấp cho cùng một phức tạp của truy vấn và nó giải thích sự bao gồm/độc quyền của phạm vi bạn đang tìm kiếm. –

+0

@Kenneth - Tôi không sử dụng các truy vấn địa không gian vì một vài lý do. Đầu tiên, tôi đang sử dụng [plugin lưu trữ dữ liệu địa lý] (http://wordpress.org/extend/plugins/geo-data-store) để tạo và duy trì bảng dữ liệu điểm đánh dấu của mình. Plugin này tạo ra cấu trúc bảng mà tôi đã trình bày ở trên. Thứ hai, phần lớn các hướng dẫn tôi đã đi qua để tạo bản đồ kiểu định vị cửa hàng dường như đề xuất cấu trúc bảng tương tự như cấu trúc được liệt kê. Có lẽ đây không phải là lý do tốt nhất nhưng tôi đã đi xa với thiết lập hiện tại và tôi khá chắc chắn rằng những gì tôi đang cố gắng làm nên có thể. – FourStacks

Trả lời

0

Suy nghĩ một chút về sau tôi đã đưa ra giải pháp 'loại' cho vấn đề của các điểm đánh dấu bị thiếu. Hai phương trình mà tôi đã đăng ban đầu đã đưa ra kết quả chính xác nhưng mỗi lần bỏ qua một trong hai điểm đánh dấu gần mục tiêu hoặc trên các cạnh của bán kính tìm kiếm

Nó không phải là rất thanh lịch nhưng tôi thấy rằng chạy cả hai phương trình và tạo 2 mảng kết hợp (loại bỏ bất kỳ bản sao nào) sẽ cho tôi tất cả các điểm đánh dấu mà tôi đang tìm kiếm. Điều này làm việc (rõ ràng là một hit hiệu suất nhưng nó không phải là một ứng dụng giao thông cao) vì vậy tôi sẽ làm việc với điều này trong thời gian nhưng tôi vẫn còn sau một giải pháp thực tế hơn nếu có ai có!

0

Dưới đây là một giải pháp tôi sử dụng thành công trong một thời gian trong tính toán địa lý gần gũi của riêng tôi:

/** 
* This portion of the routine calculates the minimum and maximum lat and 
* long within a given range. This portion of the code was written 
* by Jeff Bearer (http:return true;//www.jeffbearer.com). 
*/ 

$lat = somevalue;  // The latitude of our search origin 
$lon = someothervalue; // The longitude of our search origin 
$range = 50; // The range of our search, in miles, of your zip 

// Find Max - Min Lat/Long for Radius and zero point and query only zips in that range. 
$lat_range = $range/69.172; 
$lon_range = abs($range/(cos($lon) * 69.172)); 
$min_lat = number_format($lat - $lat_range, '4', '.', ''); 
$max_lat = number_format($lat + $lat_range, '4', '.', ''); 
$min_lon = number_format($lon - $lon_range, '4', '.', ''); 
$max_lon = number_format($lon + $lon_range, '4', '.', ''); 

/* Query for matching zips: 

    SELECT post_id, lat, lng 
    FROM wp_geodatastore 
    WHERE 
    lat BETWEEN $min_lat AND $max_lat 
    AND lng BETWEEN $min_lon AND $max_lon 
*/ 
+0

Cảm ơn bạn đã gửi mã @Fleep. Thật không may trong khi nó làm việc nói chung tôi vẫn kết thúc với cùng một vấn đề đánh dấu thiếu. Đánh giá cao nỗ lực mặc dù! – FourStacks

2

EDIT vị trí công cụ tìm này đi lên thường xuyên đủ mà tôi đã viết một bài viết về nó.

http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

Original Post

Hãy bắt đầu bằng cách giao dịch với công thức haversine một lần cho tất cả, bằng cách đặt nó vào một chức năng lưu trữ vì vậy chúng tôi có thể quên đi chi tiết gnarly của nó. LƯU Ý: toàn bộ giải pháp này là trong dặm luật.

DELIMITER $$ 

CREATE 
    FUNCTION distance(lat1 FLOAT, long1 FLOAT, lat2 FLOAT, long2 FLOAT) 
    RETURNS FLOAT 
    DETERMINISTIC NO SQL 
    BEGIN 
    RETURN (3959 * ACOS(COS(RADIANS(lat1)) 
       * COS(RADIANS(lat2)) 
       * COS(RADIANS(long1) - RADIANS(long2)) 
       + SIN(RADIANS(lat1)) 
       * SIN(RADIANS(lat2)) 
       )); 
    END$$ 

DELIMITER ; 

Bây giờ chúng ta hãy cùng nhau đưa một truy vấn để tìm kiếm trên hộp bounding, và sau đó tinh chỉnh tìm kiếm với chức năng khoảng cách và đơn đặt hàng của chúng tôi theo khoảng cách

Dựa trên mã PHP trong câu hỏi của bạn:

Giả sử $radius là bán kính của bạn, $center_lat, $center_lng là điểm tham chiếu của bạn.

$sqlsquareradius = " 
SELECT post_id, lat, lng 
    FROM 
(
    SELECT post_id, lat, lng, 
      distance(lat, lng, " . $center_lat . "," . $center_lng . ") AS distance 
     FROM wp_geodatastore 
    WHERE lat >= " . $center_lat . " -(" . $radius . "/69) 
     AND lat <= " . $center_lat . " +(" . $radius . "/69) 
     AND lng >= " . $center_lng . " -(" . $radius . "/69) 
     AND lng <= " . $center_lng . " +(" . $radius . "/69) 
)a 
WHERE distance <= " . $radius . " 
ORDER BY distance 
"; 

Lưu ý một vài điều về điều này.

Đầu tiên, tính toán hộp giới hạn trong SQL thay vì trong PHP. Không có lý do chính đáng cho điều đó, ngoại trừ việc giữ tất cả tính toán trong một môi trường. (radius/69) là số độ trong radius dặm luật.

Thứ hai, nó không phù hợp với kích thước của hộp giới hạn theo chiều dọc dựa trên vĩ độ. Thay vào đó, nó sử dụng hộp giới hạn đơn giản hơn nhưng hơi quá lớn. Hộp giới hạn này bắt thêm một vài bản ghi, nhưng phép đo khoảng cách sẽ loại bỏ chúng. Đối với ứng dụng công cụ tìm mã bưu điện/cửa hàng điển hình của bạn, chênh lệch hiệu suất là không đáng kể. Nếu bạn đang tìm kiếm nhiều bản ghi hơn (ví dụ: một cơ sở dữ liệu của tất cả các cực tiện ích) nó có thể không quá tầm thường.

Thứ ba, nó sử dụng truy vấn lồng nhau để thực hiện loại bỏ khoảng cách, để tránh phải chạy hàm khoảng cách nhiều lần cho mỗi mục.

Thứ tư, nó đặt hàng theo khoảng cách ASCENDING. Điều này có nghĩa là kết quả khoảng cách bằng 0 sẽ hiển thị đầu tiên trong tập kết quả. Nó thường có ý nghĩa để liệt kê những thứ gần nhất trước tiên.

Thứ năm, nó sử dụng FLOAT thay vì DOUBLE trong toàn bộ. Có một lý do chính đáng cho điều đó. Công thức khoảng cách haversine không hoàn hảo, bởi vì nó làm cho xấp xỉ rằng trái đất là một quả cầu hoàn hảo. Sự xấp xỉ đó xảy ra để phá vỡ ở mức độ chính xác gần giống như epsilon cho số FLOAT. Vì vậy, DOUBLE là quá mức cần thiết cho vấn đề này. (Không sử dụng công thức haversine này để làm công việc kỹ thuật dân dụng như thoát nước bãi đậu xe, hoặc bạn sẽ nhận được những vũng nước lớn một vài epsilon, một vài inch, sâu, tôi hứa.) Nó tốt cho các ứng dụng tìm cửa hàng.

Thứ sáu, bạn chắc chắn sẽ muốn tạo chỉ mục cho cột lat của mình. Nếu bảng vị trí của bạn không thay đổi thường xuyên, nó cũng sẽ giúp tạo chỉ mục cho cột lng của bạn.Tuy nhiên, chỉ số lat sẽ cung cấp cho bạn hầu hết hiệu suất truy vấn của bạn.

Cuối cùng, tôi đã thử nghiệm thủ tục được lưu trữ và SQL, nhưng không phải là PHP.

Tham khảo: http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL Ngoài ra kinh nghiệm của tôi với một loạt các công cụ tìm lân cận cho các cơ sở chăm sóc sức khỏe.

--------------- EDIT --------------------

Nếu bạn không có một giao diện người dùng cho phép bạn xác định một thủ tục được lưu trữ, đó là một mối phiền toái. Ở bất kỳ mức nào, PHP cho phép bạn sử dụng các tham số được đánh số trong cuộc gọi chạy nước rút, vì vậy bạn có thể tạo ra toàn bộ câu lệnh lồng nhau như thế này. LƯU Ý: Bạn có thể cần% $ 1f v.v. Bạn sẽ cần phải thử nghiệm với điều này.

$sql_stmt = sprintf (" 
    SELECT post_id, lat, lng 
    FROM 
    (
    SELECT post_id, lat, lng, 
      (3959 * ACOS(COS(RADIANS(lat)) 
       * COS(RADIANS(%$1s)) 
       * COS(RADIANS(lng) - RADIANS(%$2s)) 
       + SIN(RADIANS(lat)) 
       * SIN(RADIANS(%$1s)) 
      )) 
      AS distance 
     FROM wp_geodatastore 
    WHERE lat >= %$1s -(%$3s/69) 
     AND lat <= %$1s +(%$3s/69) 
     AND lng >= %$2s -(%$3s/69) 
     AND lng <= %$2s +(%$3s/69) 
)a 
    WHERE distance <= %$3s 
    ORDER BY distance 
",$center_lat,$center_lng, $radius); 
+0

Cảm ơn câu trả lời rất chi tiết Ollie. Có một số vấn đề thực hiện nó để kiểm tra nó mặc dù. Xin lỗi là một nỗi đau nhưng tôi thực sự không quen thuộc với thủ tục lưu trữ MySQL (tôi có thể làm việc theo cách của tôi xung quanh một cơ sở dữ liệu MySQL với PhpMyAdmin nhưng đó là nơi mà kiến ​​thức MySQL của tôi khô). Đã có một cái nhìn xung quanh cho một số hướng dẫn về việc tạo ra các chức năng được lưu trữ bằng cách sử dụng GUI này nhưng không tìm thấy bất cứ điều gì để hướng dẫn tôi qua. Có quan trọng là công thức haversine có định dạng này hay nó có thể được đưa vào phần còn lại của truy vấn php? – FourStacks

+0

Cảm ơn bạn đã chỉnh sửa Ollie - đánh giá cao việc bạn chuyển đổi nó thành php và tất cả trợ giúp cho đến giờ. Thật không may nó dường như không tạo ra bất kỳ giá trị ID nào cả. Tăng gấp đôi kiểm tra mã để chắc chắn rằng tôi đã không làm bất cứ điều gì ngu ngốc - không thể nhìn thấy bất cứ điều gì. Cũng đã thử đề xuất của bạn thay đổi loại specifier trong sprintf nhưng không có thay đổi. Không chắc chắn nếu trường hợp thấp hơn 'a' trong mã của bạn là cố ý hoặc một lỗi đánh máy, nhưng một trong hai cách nó sản xuất không có kết quả. – FourStacks

+0

Tôi đã trao phần thưởng cho câu trả lời này bởi vì đó là câu trả lời đầy đủ nhất và được giải thích đầy đủ (mặc dù không thực sự sử dụng câu trả lời cuối cùng - hãy xem câu trả lời của riêng tôi bên dưới mà tôi đang giới thiệu). Điều đó nói rằng câu hỏi này tạo ra một số câu trả lời rất tốt và tôi chắc chắn rằng các phương trình được liệt kê dưới đây có thể sẽ làm việc cho các dự án của người khác với một thiết lập khác để tôi xem tất cả đều đáng xem. – FourStacks

0

Bạn có thể thử lớp học của mình tại http://www.phpclasses.org/package/6202-PHP-Generate-points-of-an-Hilbert-curve.html. Nó sử dụng công thức harvesine và một đường cong hilbert để tính toán một quadkey. Sau đó, bạn có thể tìm kiếm tứ giác từ trái sang phải. Mỗi vị trí của khóa là một điểm trên đường cong quái vật. Một lời giải thích tốt hơn về đường cong có thể được tìm thấy tại blog chỉ số không gian của quadtree hilbert curve của Nick. Nó giống như sử dụng phần mở rộng chỉ mục không gian từ mysql nhưng bạn có nhiều quyền kiểm soát hơn. Bạn có thể sử dụng đường cong z hoặc đường cong moore hoặc bạn có thể thay đổi giao diện.

0

Đây là mã từ một hệ thống sản xuất làm việc,

6371.04 * acos(cos(pi()/2-radians(90-wgs84_lat)) * cos(pi()/2-radians(90-$lat)) * cos(radians(wgs84_long)-radians($lon)) + sin(pi()/2-radians(90-wgs84_lat)) * sin(pi()/2-radians(90-$lat))) as distance 

Sử dụng một công thức khoảng cách khác nhau, nhưng đối với một cửa hàng định vị sự khác biệt là rất nhỏ.

Các vấn đề liên quan