Отсутствующие результаты из-за формулы геометрической близости (локатор хранилища)

Хорошо. Я боролся с этим примерно 3 месяца и с тех пор, как я исчерпал всю формулу геометрии близости, которую я встретил, и я не ближе к тому, чтобы получить правильные результаты. Я решил, что это время попросите о помощи.

ЦЕЛЬ

Я создаю довольно базовую реализацию локатора хранилища. Пользователь вводит свой почтовый индекс и выбирает из предопределенного списка радиусов поиска. API-интерфейс gmaps генерирует координаты lat / long для этого адреса и передает их в php-скрипт. В этом скрипте пользовательские координаты запрашиваются в таблице базы данных mysql (структура ниже)

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

Результаты этого запроса (post ids) вводятся в запрос WordPress, который генерирует XML, который содержит данные маркера карты. (запрос wordpress использует post__in и posts_per_page -1 для отображения информации для всего ID, сгенерированного запросом

ПРОБЛЕМА

В двух словах, каждая реализация формулы Хаверсина, с которой я столкнулась, по-видимому, приводит к отсутствию маркеров – в частности, любые маркеры, которые очень близки к введенным координатам пользователей (точно не знаю, но я думаю, что это около 500 м). Это большая проблема, как если бы пользователь вводил свой почтовый индекс, и есть магазин, очень близкий к его местоположению, он не появится.

Я пробовал около 8 различных перестановок forumla, которые я выкопал из различных учебников с теми же результатами. Ниже приведена формула, которую я сейчас использую на сайте, который предоставляет все маркеры, за исключением тех, которые находятся в непосредственной близости от введенной пользователем позиции:

 $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']; } 

Было 1 формула, которую я пробовал, что было предложено Майком Пелли здесь: Geolocation SQL-запрос не находит точное местоположение

Эта формула, казалось, показывала маркеры, которые были очень близки к введенному пользователю месту, но пропустили другие, которые должны были отображаться в пределах заданного радиуса. Чтобы устранить любую путаницу, это код, который я использовал:

 $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']; } 

ЗАПРОС

В принципе, я хотел бы знать, почему ни один из этих блоков кода не отображает правильные маркеры. Если кто-то может предложить какие-либо улучшения в коде или может указать мне на какой-то ресурс, который я, возможно, пропустил, это было бы здорово

РЕДАКТИРОВАТЬ

Думал, что мой ответ psudeo работает, но, как выясняется, все еще возникают проблемы. Теперь я перешел на совершенно другую тему, и я использую очень хороший локатор хранилища jquery, который можно найти здесь: http://www.bjornblog.com/web/jquery-store-locator-plugin

Не будет работать для каждого проекта, но для моих нужд он идеально подходит (и работает!)

РЕДАКТИРОВАТЬ Этот поисковик достаточно часто появляется, и я написал статью об этом.

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

Оригинальное сообщение

Начнем с рассмотрения формулы Хаверсина один раз для всех, поставив ее в хранимую функцию, чтобы мы могли забыть о ее гнурных деталях. ПРИМЕЧАНИЕ. Все это решение находится в законах мили.

 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 ; 

Теперь давайте составим запрос, который ищет в ограничивающей рамке, а затем уточняет поиск с помощью нашей функции расстояния и заказов по расстоянию

На основе кода PHP в вашем вопросе:

Предположим, что $radius – ваш радиус, $center_lat , $center_lng – ваша контрольная точка.

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

Обратите внимание на некоторые вещи.

Во-первых, это вычисление ограничивающей коробки в SQL, а не в PHP. Для этого нет веской причины, кроме сохранения всех вычислений в одной среде. (radius / 69) – это количество градусов в radius устава.

Во-вторых, он не возится с размером продольной ограничивающей рамки на основе широты. Вместо этого он использует более простой, но слишком большой ограничивающий прямоугольник. Этот ограничивающий блок ловит несколько лишних записей, но дистанционное измерение избавляется от них. Для вашего типичного приложения для поиска почтового индекса / магазина разница в производительности незначительна. Если вы искали еще много записей (например, базу данных всех полюсов полезности), это может быть не так тривиально.

В-третьих, он использует вложенный запрос для устранения дистанции, чтобы избежать необходимости запуска функции расстояния более одного раза для каждого элемента.

В-четвертых, он заказывает расстояние ASCENDING. Это означает, что результаты нулевой дистанции должны отображаться сначала в наборе результатов. Обычно имеет смысл сначала перечислить наиболее близкие вещи.

В-пятых, он использует FLOAT а не DOUBLE . Для этого есть веская причина. Формула расстояния Хаверсина не идеальна, потому что она делает приближение землей идеальной сферой. Это приближение разрушается примерно на том же уровне точности, что и эпсилон для чисел FLOAT . Таким образом, DOUBLE является обманчивым численным избытком для этой проблемы. (Не используйте эту формулу гаверсина для выполнения строительных работ, таких как дренаж парковки, или вы получите большие лужи на пару эпсилон, на несколько дюймов, я обещаю.) Это нормально для приложений магазина.

В-шестых, вам определенно захочется создать индекс для вашего столбца lat . Если ваша таблица местоположений не меняется очень часто, она также поможет создать индекс для столбца lng . Но ваш индекс lat даст вам большую часть вашего увеличения производительности запросов.

Наконец, я проверил хранимую процедуру и SQL, но не PHP.

Ссылка: http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL Также мой опыт работы с множеством датчиков близости для медицинских учреждений.

————— РЕДАКТИРОВАТЬ ——————–

Если у вас нет пользовательского интерфейса, который позволяет вам определять хранимую процедуру, это неприятно. Во всяком случае, PHP позволяет использовать нумерованные параметры в вызове sprintf, поэтому вы можете сгенерировать весь вложенный оператор, как это. ПРИМЕЧАНИЕ. Возможно, вам понадобится% $ 1f и т. Д. Вам нужно будет поэкспериментировать с этим.

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

Вот решение, которое я успешно использовал в своих геометрических расчетах:

 /** * 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 */ 

Это код из рабочей системы производства,

 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 

Используется другое расстояние, но для локатора магазина разница минимальна.

Немного подумав, я придумал «своеобразное» решение проблемы недостающих маркеров. Два первоначально опубликованных уравнения дали правильные результаты, но каждый пропустил либо маркеры, близкие к цели, либо по краям радиуса поиска

Это не очень элегантно, но я решил, что запуск обоих уравнений и создание 2 массивов, которые я тогда объединил (удалив любые дубликаты), дал бы мне все маркеры, которые я ищу. Это действительно работает (очевидно, поражает производительность, но это не приложение с высоким трафиком), поэтому я буду работать с этим пока, но я все еще получаю более практичное решение, если у кого-то есть один!

Вы можете попробовать мой класс по адресу http://www.phpclasses.org/package/6202-PHP-Generate-points-of-an-Hilbert-curve.html . Он использует формулу харвезина и кривую гильберта для вычисления квад-ключа. Затем вы можете искать четырехъядерную клавиатуру слева направо. Каждая позиция ключа – это точка на кривой монстра. Лучшее объяснение кривой можно найти в блоге кривой квадроциклов индекса Quad's. Это похоже на расширение пространственного индекса из mysql, но у вас больше контроля. Вы можете использовать кривую az или кривую moore, или вы можете изменить внешний вид.