Поиск городов в пределах "X" километров (или миль)
Это может быть или не быть ясно, оставьте мне комментарий, если я не в базе, или вам нужна дополнительная информация. Возможно, уже есть решение для того, что я хочу в PHP.
Я ищу функцию, которая будет добавлять или вычитать расстояние от значения долготы ИЛИ широты.
Причина: у меня есть база данных со всеми широтами и долготами, и я хочу сформировать запрос для извлечения всех городов в пределах X километров (или миль). Мой запрос будет выглядеть примерно так...
Select * From Cities Where (Longitude > X1 and Longitude < X2) And (Latitude > Y1 and Latitude < Y2)
Where X1 = Longitude - (distance)
Where X2 = Longitude + (distance)
Where Y1 = Latitude - (distance)
Where Y2 = Latitude + (distance)
Я работаю в PHP, с базой данных MySql.
Также открыт для любых предложений!:)
10 ответов
Это запрос MySQL, который будет делать именно то, что вы хотите. Имейте в виду, что такие вещи, как правило, являются приблизительными, поскольку Земля не является совершенно сферической, и при этом это не учитывает горы, холмы, долины и т. Д. Мы используем этот код на http://www.academichomes.com/ с PHP и MySQL, он возвращает записи в пределах $ радиус миль $ широты, $ долготы.
$res = mysql_query("SELECT
*
FROM
your_table
WHERE
(
(69.1 * (latitude - " . $latitude . ")) *
(69.1 * (latitude - " . $latitude . "))
) + (
(69.1 * (longitude - " . $longitude . ") * COS(" . $latitude . " / 57.3)) *
(69.1 * (longitude - " . $longitude . ") * COS(" . $latitude . " / 57.3))
) < " . pow($radius, 2) . "
ORDER BY
(
(69.1 * (latitude - " . $latitude . ")) *
(69.1 * (latitude - " . $latitude . "))
) + (
(69.1 * (longitude - " . $longitude . ") * COS(" . $latitude . " / 57.3)) *
(69.1 * (longitude - " . $longitude . ") * COS(" . $latitude . " / 57.3))
) ASC");
РЕДАКТИРОВАТЬ: Если у вас есть где-нибудь, список всех городов в мире вместе с их лат. и долго. значения, вы можете сделать поиск. В этом случае, см. Мою первую ссылку ниже для формулы для расчета ширины одного продольного градуса на широте :
Честно говоря, осложнения, связанные с этой проблемой, таковы, что вам будет гораздо лучше использовать такой сервис, как Google Maps, для получения ваших данных. В частности, Земля не является идеальной сферой, и расстояние между двумя градусами меняется по мере того, как вы находитесь ближе / дальше от экватора.
См. http://en.wikipedia.org/wiki/Geographic_coordinate_system для примеров того, что я имею в виду, и ознакомьтесь с API Карт Google.
Я пытался использовать приведенный выше код, и ответы были слишком много, когда расстояние между точками было в диапазоне 20-30 миль, и я в порядке с ошибкой в несколько миль. Поговорили с моим приятелем по картированию, и мы вместо этого придумали этот. Код Python, но вы можете перевести его довольно легко. Чтобы избежать постоянного преобразования в радианы, я переделал свою базу данных, преобразовав точки широты / долготы из градусов в радианы. Самое приятное в этом то, что большая часть математики выполняется в основном один раз.
ra = 3963.1906 # radius @ equator in miles, change to km if you want distance in km
rb = 3949.90275 # radius @ poles in miles, change to km if you want distance in km
ra2 = ra * ra
rb2 = rb * rb
phi = self.lat
big_ol_constant = (math.pow(ra2*math.cos(phi), 2) + pow(rb2*math.sin(phi), 2))/ (pow(ra*math.cos(phi), 2) + pow(rb*math.sin(phi), 2))
sqlWhere = "%(distance)g > sqrt((power(lat - %(lat)g,2) + power(lng-%(lng)g,2)) * %(big_ol_constant)g)" % {
'big_ol_constant': big_ol_constant, 'lat': self.lat, 'lng': self.lng, 'distance': distance}
# This is the Django portion of it, where the ORM kicks in. sqlWhere is what you would put after the WHERE part of your SQL Query.
qs = ZipData.objects.extra(where=[sqlWhere]);
Кажется очень точным, когда расстояние между ними небольшое, и в пределах 10 миль или около того, когда расстояние увеличивается до 200 миль (конечно, к тому времени у вас возникнут проблемы с "по прямой линии" против "дорог с твердым покрытием").
Вот модель ZipData, о которой я упоминал выше.
class ZipData(models.Model):
zipcode = ZipCodeField(null=False, blank=False, verbose_name="ZipCode", primary_key=True)
city = models.CharField(max_length=32, null=False, blank=False)
state = models.CharField(max_length=2)
lat = models.FloatField(null=False, blank=False)
lng = models.FloatField(null=False, blank=False)
Еще одно замечание: вы можете получить МНОГО геоданных, связанных с почтовыми кодами, на GeoNames.org, и у них даже есть некоторые API веб-сервисов, которые вы также можете использовать.
В зависимости от того, сколько городов вы включаете, вы можете предварительно вычислить список. Мы делаем это здесь для внутреннего приложения, где неточность +100 м слишком велика для нашей установки. Он работает, имея две ключевые таблицы location1, location2, distance. Затем мы можем очень быстро отодвинуть местоположения на расстояние от местоположения1.
Кроме того, поскольку вычисления могут выполняться в автономном режиме, это не влияет на работу системы. Пользователи также получают более быстрые результаты.
Вы можете использовать теорему Пифагора для вычисления близости двух пар точек широты и долготы.
Если у вас есть два местоположения (Альфа и Бета), вы можете рассчитать их расстояние с помощью:
SQRT( POW(Alpha_lat - Beta_lat,2) + POW(Alpha_lon - Beta_lon,2) )
Не изобретай велосипед. Это пространственный запрос. Используйте встроенные пространственные расширения MySQL для хранения данных координат широты и долготы в собственном типе столбца геометрии MySQL. Затем используйте функцию Расстояние для запроса точек, которые находятся на указанном расстоянии друг от друга.
Отказ от ответственности: это основано на чтении документации, я сам не пробовал.
Используя настройки из следующего URL, я создал запрос ниже. (Обратите внимание, что я использую codeIgnitor для запроса базы данных)
http://howto-use-mysql-spatial-ext.blogspot.com/2007/11/using-circular-area-selection.html
function getRadius($point="POINT(-29.8368 30.9096)", $radius=2)
{
$km = 0.009;
$center = "GeomFromText('$point')";
$radius = $radius*$km;
$bbox = "CONCAT('POLYGON((',
X($center) - $radius, ' ', Y($center) - $radius, ',',
X($center) + $radius, ' ', Y($center) - $radius, ',',
X($center) + $radius, ' ', Y($center) + $radius, ',',
X($center) - $radius, ' ', Y($center) + $radius, ',',
X($center) - $radius, ' ', Y($center) - $radius, '
))')";
$query = $this->db->query("
SELECT id, AsText(latLng) AS latLng, (SQRT(POW( ABS( X(latLng) - X({$center})), 2) + POW( ABS(Y(latLng) - Y({$center})), 2 )))/0.009 AS distance
FROM crime_listing
WHERE Intersects( latLng, GeomFromText($bbox) )
AND SQRT(POW( ABS( X(latLng) - X({$center})), 2) + POW( ABS(Y(latLng) - Y({$center})), 2 )) < $radius
ORDER BY distance
");
if($query->num_rows()>0){
return($query->result());
}else{
return false;
}
}
Приведенная ниже функция взята из базы данных http://nerddinner.com/ (пример приложения ASP.NET MVC, доступный в codeplex) (MSSQL).
ALTER FUNCTION [dbo].[DistanceBetween] (@Lat1 as real,
@Long1 as real, @Lat2 as real, @Long2 as real)
RETURNS real
AS
BEGIN
DECLARE @dLat1InRad as float(53);
SET @dLat1InRad = @Lat1 * (PI()/180.0);
DECLARE @dLong1InRad as float(53);
SET @dLong1InRad = @Long1 * (PI()/180.0);
DECLARE @dLat2InRad as float(53);
SET @dLat2InRad = @Lat2 * (PI()/180.0);
DECLARE @dLong2InRad as float(53);
SET @dLong2InRad = @Long2 * (PI()/180.0);
DECLARE @dLongitude as float(53);
SET @dLongitude = @dLong2InRad - @dLong1InRad;
DECLARE @dLatitude as float(53);
SET @dLatitude = @dLat2InRad - @dLat1InRad;
/* Intermediate result a. */
DECLARE @a as float(53);
SET @a = SQUARE (SIN (@dLatitude / 2.0)) + COS (@dLat1InRad)
* COS (@dLat2InRad)
* SQUARE(SIN (@dLongitude / 2.0));
/* Intermediate result c (great circle distance in Radians). */
DECLARE @c as real;
SET @c = 2.0 * ATN2 (SQRT (@a), SQRT (1.0 - @a));
DECLARE @kEarthRadius as real;
/* SET kEarthRadius = 3956.0 miles */
SET @kEarthRadius = 6376.5; /* kms */
DECLARE @dDistance as real;
SET @dDistance = @kEarthRadius * @c;
return (@dDistance);
END
Я предполагаю, что это может быть полезно.
У lessthandot.com на самом деле есть 3 различных способа сделать это. вам придется немного пролистать блоги, но они есть. http://blogs.lessthandot.com/
Есть много (плохие варианты)
Рассчитайте расстояние, используя математическую формулу (трактуйте X1-X2 и Y1-Y2) как векторы.
Создайте таблицу соответствия заранее со всеми комбинациями и соблюдайте расстояния.
Подумайте об использовании специфического для ГИС расширения MySQL. Вот одна статья, которую я нашел об этом.