Рассчитайте расстояние между Zip-кодами... и пользователями.

это более сложный вопрос, чем то, что мне срочно нужно, поэтому не тратьте на него весь день, ребята.

Я построил сайт знакомств (давно ушел) в 2000 году или около того, и одной из проблем было вычисление расстояния между пользователями, чтобы мы могли представить ваши "матчи" в радиусе X миль. Чтобы просто указать проблему, учитывая следующую схему базы данных (примерно):

ТАБЛИЦА ПОЛЬЗОВАТЕЛЯ идентификатор пользователя имя пользователя: ZipCode

ZIPCODE ТАБЛИЦА индекс Широта Долгота

С пользователем и ZIPCODE присоединяется к пользователю.ZipCode = ZIPCODE.индекс.

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

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

на Haversine Формула для расчета расстояния между любыми двумя точками на сфере... довольно простая математика.

вопрос, по крайней мере для нас, будучи 19-летними студентами колледжа, которым мы были, действительно стал тем, как эффективно вычислять и/хранить расстояния от всех членов до всех других членов. Один из подходов (тот, который мы использовали) - импортировать все данные и рассчитать расстояние от каждого почтового индекса до каждого другого почтового индекса. Затем вы сохраняете и индексируете результаты. Что-то например:

SELECT  User.UserId
FROM    ZipCode AS MyZipCode
        INNER JOIN ZipDistance ON MyZipCode.ZipCode = ZipDistance.MyZipCode
        INNER JOIN ZipCode AS TheirZipCode ON ZipDistance.OtherZipCode = TheirZipCode.ZipCode
        INNER JOIN User AS User ON TheirZipCode.ZipCode = User.ZipCode
WHERE   ( MyZipCode.ZipCode = 75044 )
        AND ( ZipDistance.Distance < 50 )

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

8 ответов


хорошо, для начала, вам действительно не нужно использовать формулу Haversine здесь. Для больших расстояний, где менее точная формула дает большую ошибку, пользователям все равно, плюс или минус несколько миль, а для более близких расстояний ошибка очень мала. Есть более простые (для расчета) формулы, перечисленные в Географическое Расстояние статья в Википедии.

поскольку почтовые индексы не похожи на равномерно расположенные, любой процесс, который разделяет их равномерно будет сильно страдать в районах, где они плотно сгруппированы (восточный берег вблизи DC является хорошим примером). Если вы хотите визуальное сравнение, проверьтеhttp://benfry.com/zipdecode и сравните префикс zipcode 89 с 07.

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

вот как выглядит квадратное дерево:

Quadtree

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

конечно, так как это довольно распространенная вещь, чтобы сделать, кто-то уже сделал самое трудное для вас. Поскольку вы не указали, какую базу данных вы используете, расширение PostgreSQL PostGIS будет служить в качестве примера. PostGIS включает в себя возможность делать пространственные индексы R-дерева, которые позволяют выполнять эффективные пространственные запросы.

после того как вы импортировали данные, и строится пространственный индекс, запрос на дистанции запрос:

SELECT zip
FROM zipcode
WHERE
geom && expand(transform(PointFromText('POINT(-116.768347 33.911404)', 4269),32661), 16093)
AND
distance(
   transform(PointFromText('POINT(-116.768347 33.911404)', 4269),32661),
   geom) < 16093

Я позволю вам работать через остальную часть урока самостоятельно.

вот некоторые другие ссылки, чтобы вы начали.


Я просто создам таблицу zip_code_distances и предварительно вычислю расстояния между всеми 42k zipcodes в США, которые находятся в радиусе 20-25 миль друг от друга.

create table zip_code_distances
(
from_zip_code mediumint not null,
to_zip_code mediumint not null,
distance decimal(6,2) default 0.0,
primary key (from_zip_code, to_zip_code),
key (to_zip_code)
)
engine=innodb;

только включение zipcodes в радиусе 20-25 миль друг от друга уменьшает количество строк, которые вам нужно хранить в таблице расстояний от максимума 1,7 миллиарда (42K ^ 2) - 42K до гораздо более управляемых 4 миллионов или около того.

Я загрузил файл данных zipcode из интернета, который содержал долготы и широты всех официальных американских zipcodes в формате csv:

"00601","Adjuntas","Adjuntas","Puerto Rico","PR","787","Atlantic", 18.166, -66.7236
"00602","Aguada","Aguada","Puerto Rico","PR","787","Atlantic", 18.383, -67.1866
...
"91210","Glendale","Los Angeles","California","CA","818","Pacific", 34.1419, -118.261
"91214","La Crescenta","Los Angeles","California","CA","818","Pacific", 34.2325, -118.246
"91221","Glendale","Los Angeles","California","CA","818","Pacific", 34.1653, -118.289
...

Я написал быструю и грязную программу на C#, чтобы прочитать файл и вычислить расстояния между каждым zipcode, но только выходные zipcodes, которые попадают в радиусе 25 миль:

sw = new StreamWriter(path);

foreach (ZipCode fromZip in zips){

    foreach (ZipCode toZip in zips)
    {
        if (toZip.ZipArea == fromZip.ZipArea) continue;

        double dist = ZipCode.GetDistance(fromZip, toZip);

        if (dist > 25) continue;

        string s = string.Format("{0}|{1}|{2}", fromZip.ZipArea, toZip.ZipArea, dist);
        sw.WriteLine(s);
    }
}

результирующий выходной файл выглядит следующим образом:

from_zip_code|to_zip_code|distance
...
00601|00606|16.7042215574185
00601|00611|9.70353520976393
00601|00612|21.0815707704904
00601|00613|21.1780461311929
00601|00614|20.101431539283
...
91210|90001|11.6815708119899
91210|90002|13.3915723402714
91210|90003|12.371251171873
91210|90004|5.26634939906721
91210|90005|6.56649623829871
...

Я бы просто загрузил эти данные расстояния в мою таблицу zip_code_distances, используя load data infile, а затем использовал ее для ограничения пространство поиска моего приложения.

например, если у вас есть пользователь, чей zipcode 91210, и они хотят найти людей, которые находятся в радиусе 10 миль от них, то теперь вы можете просто сделать следующее:

select 
 p.*
from
 people p
inner join
(
 select 
  to_zip_code 
 from 
  zip_code_distances 
 where 
  from_zip_code = 91210 and distance <= 10
) search
on p.zip_code = search.to_zip_code
where
 p.gender = 'F'....

надеюсь, что это помогает

EDIT: расширенный радиус до 100 миль, который увеличил количество расстояний zipcode до 32,5 миллионов строк.

быстрая проверка производительности для zipcode 91210 runtime 0.009 секунд.

select count(*) from zip_code_distances
count(*)
========
32589820

select 
 to_zip_code 
from 
 zip_code_distances 
where 
 from_zip_code = 91210 and distance <= 10;

0:00:00.009: Query OK

вы можете сократить расчет, просто предположив поле вместо кругового радиуса. Затем при поиске вы просто вычисляете нижнюю / верхнюю границу lat/lon для данной точки+"радиус", и пока у вас есть индекс на столбцах lat / lon, вы можете довольно легко вернуть все записи, которые попадают в поле.


вы можете разделить свое пространство на области примерно одинакового размера - например, приблизиться к земле как бакибол или икосаэдр. Области могут даже немного перекрываться, если это проще (например, сделать их круговыми). Запишите, в каком регионе находится каждый почтовый индекс. Затем вы можете предварительно рассчитать максимальное расстояние между каждой парой регионов, которое имеет одинаковое O (n^2) проблема в вычислении всех пар почтовых индексов, но для меньших n.

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

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


Я бы использовал широту и долготу. Например, если у вас есть широта 45 и долгота 45, и вас попросили найти совпадения в пределах 50 миль, то вы можете сделать это, переместив 50/69 тыс. вверх по широте и 50/69 тыс. вниз по широте (1 град широты ~ 69 миль). Выберите почтовые индексы с широтами в этом диапазоне. Долготы немного отличаются, потому что они становятся меньше, когда вы приближаетесь к полюсам.

но на 45 град, 1 долгота ~ 49 миль, так что вы можете двигаться 50 / 49ths слева на широте и 50 / 49ths справа на широте и выберите все почтовые индексы из набора широты с этой долготой. Это дает вам все почтовые индексы в пределах квадрата с длиной в сто миль. Если вы хотели быть очень точным, то можно использовать гаверсинус формула ведьма, Ты упомянул, чтобы отсеять молнии в углах коробки, чтобы дать вам сфере.


не все возможные пары почтовых индексов будут использоваться. Я бы построил zipdistance как таблицу "кэш". Для каждого запроса вычислите расстояние для этой пары и сохраните его в кэше. Когда приходит запрос на пару расстояний, сначала посмотрите в кэше, а затем вычислите, если он недоступен.

Я не знаю тонкостей вычислений расстояния, поэтому я бы также проверил, дешевле ли вычислять на лету, чем смотреть вверх (также принимая во внимание, как часто вы надо вычислить).


Я знаю, что этот пост слишком стар, но делая некоторые исследования для клиента, я нашел некоторые полезные функции Google Maps API и настолько прост в реализации, вам просто нужно передать url-адрес происхождения и назначения почтовых индексов, и он вычисляет расстояние даже с трафиком, вы можете использовать его с любым язык:

origins = 90210
destinations = 93030
mode = driving

http://maps.googleapis.com/maps/api/distancematrix/json?origins=90210&destinations=93030&mode=driving&language=en-EN&sensor=false%22

следуя ссылке, вы можете увидеть, что она возвращает json. Помните, что вам нужен ключ API, чтобы использовать его на своем собственном хостинге.

источник: http://stanhub.com/find-distance-between-two-postcodes-zipcodes-driving-time-in-current-traffic-using-google-maps-api/


У меня проблема работает отлично, и в значительной степени каждый ответ привык. Я думал об этом с точки зрения старого решения, а не просто "начать сначала.- Бабтек получает кивок за то, что говорит простейшими словами.

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

1) рассмотрим точку A на сфере, представленную широтой и долготой. выяснить, Север, Юг, Восточные и западные края коробки 2x миль в поперечнике с точкой A в центре.

2) Выберите все точки в поле из таблицы ZipCode. Это включает простое предложение WHERE с двумя операторами Between, ограничивающими lat и Long.

3) используйте формулу хаверсина для определения сферического расстояния между точкой A и каждой точкой B, возвращаемой на Шаге 2.

4) отбросить все точки B, где расстояние A - > B > X.

5) Выберите пользователей, где ZipCode находится в оставшемся наборе точек B.

Это довольно быстро для > 100 миль. Самый длинный результат составил ~ 0.014 секунды для вычисления матча и тривиальный для запуска оператора select.

кроме того, в качестве примечания необходимо было реализовать математику в нескольких функциях и вызвать их в SQL. Как только я прошел определенное расстояние, совпадающее количество ZipCodes было слишком большим, чтобы вернуться к SQL и использовать в качестве оператора IN, поэтому мне пришлось использовать временную таблицу и присоединиться к результирующие ZipCodes для пользователя в столбце ZipCode.

Я подозреваю, что использование таблицы ZipDistance не обеспечит долгосрочный прирост производительности. Количество строк становится очень большим. Если вы вычислите расстояние от каждого почтового индекса до каждого другого почтового индекса (в конечном итоге), то результирующее количество строк из 40 000 почтовых индексов будет ~ 1.6 B. Whoah!

альтернативно, я заинтересован в использовании встроенного типа географии SQL, чтобы увидеть, сделает ли это проще, но старый добрый типы int/float отлично подходят для этого образца.

Так... окончательный список интернет-ресурсов, которые я использовал, для вашего удобства:

1) максимальная разница, широта и долгота.

2) Формула Гаверсинуса.

3) длительное, но полное обсуждение всего процесса, который я нашел в Гугле в ваших ответах.