Поиск городов в пределах «Х» километров (или миль) - PullRequest
12 голосов
/ 01 мая 2009

Это может быть или не быть ясным, оставьте мне комментарий, если я не в базе, или вам нужна дополнительная информация. Возможно, уже есть решение для того, что я хочу в 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 ]

17 голосов
/ 01 мая 2009

Это запрос MySQL, который будет делать именно то, что вы хотите. Имейте в виду, что такие вещи, как правило, являются приблизительными, поскольку Земля не является идеально сферической, и это не учитывает горы, холмы, долины и т. Д. Мы используем этот код на 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");
3 голосов
/ 01 мая 2009

РЕДАКТИРОВАТЬ: Если у вас есть где-нибудь, список всех городов в мире вместе с их лат. и долго. значения, вы можете сделать поиск. В этом случае см. Мою первую ссылку ниже для формулы для вычисления ширины одного продольного градуса на широте alt text:

alt text

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

См. http://en.wikipedia.org/wiki/Geographic_coordinate_system для примеров того, что я имею в виду, и посмотрите API Карт Google .

1 голос
/ 07 июня 2010

Я пытался использовать приведенный выше код, и ответы были слишком много, когда расстояние между точками было в диапазоне 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 веб-сервисов, которые вы также можете использовать.

1 голос
/ 01 мая 2009

В зависимости от того, сколько городов вы включаете, вы можете предварительно вычислить список. Мы делаем это здесь для внутреннего приложения, где погрешность + 100 м слишком велика для нашей установки. Он работает, имея две ключевые таблицы location1, location2, distance. Затем мы можем очень быстро отодвинуть локации на расстояние x от локации1.

Кроме того, поскольку вычисления могут выполняться в автономном режиме, это не влияет на работу системы. Пользователи также получают более быстрые результаты.

0 голосов
/ 02 декабря 2009

Не изобретай велосипед. Это пространственный запрос. Используйте встроенные пространственные расширения MySQL для хранения данных координат широты и долготы в типе столбца геометрии MySQL . Затем используйте функцию Distance для запроса точек, которые находятся на определенном расстоянии друг от друга.

Отказ от ответственности: это основано на чтении документации, я сам не пробовал.

0 голосов
/ 01 декабря 2009

Используя настройки из следующего URL, я построил запрос ниже. (Обратите внимание, что я использую codeIgnitor для запросов к базе данных)

http://howto -use-mysql-spatial-ext.blogspot.com / 2007/11 / с использованием круговой-зонная 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;
    }
}
0 голосов
/ 01 мая 2009

Вы можете использовать теорему Пифагора для вычисления близости двух пар точек широты и долготы.

Если у вас есть два местоположения (альфа и бета), вы можете рассчитать их расстояние с помощью:

SQRT( POW(Alpha_lat - Beta_lat,2) + POW(Alpha_lon - Beta_lon,2) )
0 голосов
/ 01 мая 2009

Следующая функция взята из базы данных nerddinner (пример приложения 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

Полагаю, это может быть полезно.

0 голосов
/ 01 мая 2009

lessthandot.com на самом деле имеет 3 различных способа сделать это. вам придется немного пролистать блоги, но они есть. http://blogs.lessthandot.com/

0 голосов
/ 01 мая 2009

Есть много (плохих вариантов)

  • Рассчитайте расстояние, используя математическую формулу (трактуйте X1-X2 и Y1-Y2) как векторы.

  • Создайте справочную таблицу заранее со всеми комбинациями и сохраните расстояния.

  • Рассмотрите возможность использования специфического для ГИС расширения MySQL. Вот одна статья Я нашел об этом.

...