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

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

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

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

Таблица ZIPCODE Почтовый Индекс широта Долгота

С USER и ZIPCODE, соединенными с USER.ZipCode = ZIPCODE.ZipCode.

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

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

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

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

Во всяком случае, мне было интересно, какой подход некоторые из вас, гуру, могут принять что-то вроде этого. Кроме того, я думаю, что это общая проблема, с которой программисты вынуждены время от времени заниматься, особенно если вы рассматриваете проблемы, которые просто алгоритмически схожи. Я заинтересован в тщательном решении, которое включает по крайней мере HINTS на всех частях, чтобы это действительно быстро закончилось эффективно. Спасибо!

Ответ 1

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

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

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

Вот что выглядит Quadtree:

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

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

Вот несколько других ссылок, которые помогут вам начать работу.

Ответ 2

Я бы просто создал таблицу 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;

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

Я загрузил файл данных zipcode из Интернета, в котором были указаны долготы и широты всех официальных почтовых индексов США в формате 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
...

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

Например, если у вас есть пользователь, чей 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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

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

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

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

3) Используйте формулу гаверсина для определения сферического расстояния между точкой А и каждой точкой В, возвращаемой на шаге 2.

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

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

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

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

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

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

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

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

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

3) Длительное, но полное обсуждение всего процесса, которое я нашел из материалов Googling в ваших ответах.

Ответ 8

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

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/