Географический поиск без расстояния
Это мой первый взгляд на географическое местоположение. Пожалуйста, прости мой длинный вопрос. Надеюсь понятно.
У меня есть база данных, содержащая записи с лат. Я пытаюсь написать класс PHP, который определяет четыре десятичных координаты, которые образуют граничный блок (чтобы затем получить все записи в этом блоке).
Мне нужно только знать, какие местоположения находятся в пределах n км от местоположения пользователя. Мне не нужны расстояния от пользователя до локации.
Я строю на первой части этого ответа.
В основе вышеприведенного ответа лежит:
10 км по прямой это:
on the latitude is equal to ~1'(minute) on the longitude is equal to ~6'(minutes)
Используя это в качестве основы, сделайте небольшую математическую операцию и в своем запросе добавьте к предложению WHERE, удалив все местоположения, которые находятся за пределами "коробки", которая создается путем добавления буферной зоны с допущением 1' lat & 6' long.
Мммм, "сделай немного математики", - говорит Патрик. Я не смог сделать математику правильно через несколько дней.
Мой класс держится там и, кажется, работает на 10 км. Однако я не могу заставить его работать на любом другом расстоянии.
Сначала я перешел на секунды, поэтому 10км это:
on the latitude is equal to 60 seconds
on the longitude is equal to 360 seconds
Это работало нормально.
Однако я тогда пробовал 5км это:
on the latitude is equal to 30 seconds
on the longitude is equal to 180 seconds
Это не сработало.
Можете ли вы объяснить, почему этот подход не работает на 5 км (и на других расстояниях, которые я пробовал)?
Ниже мой код класса. Ясно, что переключатель это мусор. Любая помощь приветствуется.
class coords
{
/*
Calculate a bounding box from a decimal coordinate and distance.
The public vars are populated with minimum and maximum longitudes and latitudes which define the boundary.
*/
public $dec_lat_min;
public $dec_lat_max;
public $dec_lng_min;
public $dec_lng_max;
function init($dec_lat, $dec_lng, $dist)
{
// This switch is a terrible way to allow multiple distances.
// 10km = 1 min lat = 60 sec lat
// 10km = 6 min lng = 360 sec lng
// 5km = 30 sec lat
// 5km = 180 sec lng
// 1km = 6 sec lat
// 1km = 36 sec lat
// 500m = 3 sec lat
// 500m = 18 sec lat
switch($dist)
{
case 10: // 10km
$sec_diff_lat = 60;
$sec_diff_lng = 360;
break;
case 5: // 5km
$sec_diff_lat = 30;
$sec_diff_lng = 180;
break;
case 1: // 1km
$sec_diff_lat = 6;
$sec_diff_lng = 36;
break;
default: // 500m
$sec_diff_lat = 3;
$sec_diff_lng = 18;
break;
}
// Convert lat to DMS
$dms_lat = $this->dec2dms($dec_lat);
// Allow for western hemisphere (ie negative)
$dms_lat['hem'] == '-' ? $h = -1 : $h = 1;
// Populate min and max latitudes
$this->dec_lat_min = $this->dms2dec($dms_lat['deg'],$dms_lat['min'],$dms_lat['sec']+(-1 * $sec_diff_lat * $h),$dms_lat['hem']);
$this->dec_lat_max = $this->dms2dec($dms_lat['deg'],$dms_lat['min'],$dms_lat['sec']+($sec_diff_lat * $h),$dms_lat['hem']);
$dms_lng = $this->dec2dms($dec_lng);
$dms_lng['hem'] == '-' ? $h = -1 : $h = 1;
$this->dec_lng_min = $this->dms2dec($dms_lng['deg'],$dms_lng['min'],$dms_lng['sec']+(-1 * $sec_diff_lng * $h),$dms_lng['hem']);
$this->dec_lng_max = $this->dms2dec($dms_lng['deg'],$dms_lng['min'],$dms_lng['sec']+($sec_diff_lng * $h),$dms_lng['hem']);
}
function dec2dms($d)
{
$d = (string)$d;
// got dashes?
if ($d[0] == "-") {
$hem = '-';
$dVal = substr($d,1);
} else {
$hem = '';
$dVal = $d;
}
// degrees = degrees
$dVals = explode('.', $dVal);
$dmsDeg = $dVals[0];
// * 60 = mins
$dRemainder = ('0.'.$dVals[1]) * 60;
$dmsMinVals = explode('.', $dRemainder);
$dmsMin = $dmsMinVals[0];
// * 60 again = secs
$dMinRemainder = ('0.'.$dmsMinVals[1]) * 60;
$dmsSec = round($dMinRemainder);
return array("deg"=>$dmsDeg,"min"=>$dmsMin,"sec"=>$dmsSec, "hem"=>$hem);
}
function dms2dec($deg,$min,$sec,$hem)
{
// find decimal latitude
$d = $deg+((($min*60)+($sec))/3600);
$d = $hem.$d;
return $this->round100000($d);
}
function round100000($v)
{
return round($v * 100000) / 100000;
}
}
2 ответа
Как отмечалось в другом ответе, вы действительно хотите использовать формулу Haversine, которая учитывает широту пользователей.
Формула Haversine: http://en.wikipedia.org/wiki/Haversine_formula
Этот другой ответ имеет массу полезных ресурсов для достижения этой цели в PHP или SQL:
10 км по прямой это: по долготе равен ~6 минутам
Отношение расстояния к долготе не является постоянным - оно зависит от широты пользователя. Вы можете использовать эту функцию для разности долготы:
function distanceToLng($distance,$latDeg) {
$latRad = $latDeg / 180 * M_PI;
$R = 6371;
return atan2(1*sin($distance/$R)*cos($latRad), cos($distance/$R) - sin($latRad)*sin($latRad)) * (180 / M_PI);
}
Формула взята из этого вопроса.
Вы можете рассчитать разницу широт от заданного расстояния, используя что-то вроде этого:
$lngDeg = $distanceInKm/6371 * 180 / M_PI