web-dev-qa-db-ja.com

郵便番号とユーザーの間の距離を計算します。

これは私が緊急に必要とするものよりも挑戦的な質問なので、みんなに一日を費やさないでください。

私は2000年頃に出会い系サイト(昔)を構築しましたが、課題の1つは、ユーザー間の距離を計算して半径Xマイル以内の「対戦」を提示できるようにすることでした。問題を述べるだけで、次のデータベーススキーマが(大まかに)与えられます。

ユーザーテーブルUserId UserName ZipCode

郵便番号表郵便番号緯度経度

USERとZIPCODEがUSER.ZipCode = ZIPCODE.ZipCodeで結合されています。

次の質問に答えるためにどのようなアプローチをとりますか?特定のユーザーの郵便番号からXマイル以内にある郵便番号に他のユーザーが住んでいるもの。

2000年の国勢調査データ を使用しました。郵便番号とそれらのおおよその緯度と経度のテーブルが含まれています。

Haversine Formula を使用して、球上の任意の2点間の距離を計算しました...非常に単純な数学です。

少なくとも私たちにとっては、私たちが19歳の大学生だったため、すべてのメンバーから他のすべてのメンバーまでの距離を効率的に計算および保存する方法が本当に問題になりました。 1つの方法(使用した方法)では、すべてのデータをインポートし、すべての郵便番号から他のすべての郵便番号までの距離を計算します。次に、結果を保存してインデックスを作成します。何かのようなもの:

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テーブルに行が多数含まれることです。完全に機能しないわけではありませんが、本当に大きいです。また、データセット全体の完全な事前作業が必要です。これも管理不可能ではありませんが、必ずしも望ましいわけではありません。

とにかく、私はあなたのグルの一部がこのようなことをするのにどんなアプローチをとるかもしれないと思っていました。また、これはプログラマーが時々取り組む必要のある一般的な問題だと思います。特に、アルゴリズム的に類似している問題を考える場合はそうです。私はこれを実際にすばやく効率的に終了するために、すべての部分に少なくともヒントを含む完全なソリューションに興味があります。ありがとう!

31
bopapa_1979

まず、ここでは、Haversineの式を実際に使用する必要はありません。距離が遠く、式の精度が低くなるとエラーが大きくなりますが、ユーザーは一致が数マイルのプラスかマイナスかを気にしません。距離が近い場合、エラーは非常に小さくなります。 地理的距離 ウィキペディアの記事に記載されているより簡単な(計算する)式があります。

郵便番号は等間隔に配置されているようなものではないため、それらを均等に分割するプロセスは、それらが密に密集している領域で大きな影響を受けます(DCの近くの東海岸が良い例です)。視覚的に比較するには、 http://benfry.com/zipdecode を確認し、郵便番号のプレフィックス89と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

チュートリアルの残りの部分は自分で進めます。

ここに、あなたが始めるためのいくつかの他の参照があります。

33
Paul McMillan

Zip_code_distancesテーブルを作成して、半径20〜25マイル以内にある米国内のすべての42K郵便番号間の距離を事前計算するだけです。

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;

半径20〜25マイル以内の郵便番号のみを含めると、距離テーブルに格納する必要のある行数が最大の17億(42K ^ 2)〜42Kから、はるかに管理しやすい400万程度に削減されます。

私は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#プログラムを作成しましたが、半径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
...

次に、load data infileを使用してこの距離データをZip_code_distancesテーブルにロードし、それを使用してアプリケーションの検索スペースを制限します。

たとえば、郵便番号が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'....

お役に立てれば

編集:半径を100マイルに拡張し、郵便番号の距離の数を3250万行に増やしました。

郵便番号91210ランタイムのクイックパフォーマンスチェック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
14
Jon Black

円形半径の代わりにボックスを仮定するだけで計算を簡略化できます。次に、検索時に、特定のポイント+ "半径"の緯度/経度の下限/上限を計算し、緯度/経度の列にインデックスがある限り、ボックス内にあるすべてのレコードを簡単に引き戻すことができます。 。

5
babtek

緯度と経度を使用します。たとえば、緯度が45、経度が45で、50マイル以内の一致を検索するように求められた場合、緯度を50/69秒上に移動し、緯度を50/69秒下に移動します(1度緯度〜69マイル)。この範囲の緯度の郵便番号を選択します。経度は、極に近づくにつれて小さくなるため、少し異なります。

ただし、45度、経度1〜49マイルなので、緯度の50/49を左に、緯度の50/49を右に移動し、この経度で設定された緯度からすべての郵便番号を選択できます。これにより、100マイルの長さの正方形内のすべての郵便番号が得られます。本当に正確にしたい場合は、先ほど触れたHaversine式の魔女を使用して、ボックスの隅にあるジッパーを取り除き、球体を作成できます。

1
David Watson

スペースをほぼ同じサイズの領域に分割できます。たとえば、地球をバッキーボールまたは正二十面体として近似します。リージョンは、それが簡単な場合(たとえば、それらを円形にする)、少しオーバーラップすることさえできます。各郵便番号がどの地域にあるかを記録します。次に、すべての郵便番号ペアを計算するのと同じO(n ^ 2)問題があるすべての地域ペア間の可能な最大距離を事前計算できます。 、しかしより小さなnの場合。

これで、特定の郵便番号について、確実に指定された範囲内にある地域のリストと、国境を越える地域のリストを取得できます。前者の場合は、すべての郵便番号を取得します。後者の場合、各境界領域にドリルダウンし、個々の郵便番号に対して計算します。

これは確かに数学的には複雑です。特に、領域の数は、テーブルのサイズとその場での計算に費やされた時間のバランスをとるために選択する必要がありますが、事前計算されたテーブルのサイズを適切に削減しますマージン。

1
Jander

すべての可能な郵便番号のペアが使用されるわけではありません。 zipdistanceを「キャッシュ」テーブルとして作成します。リクエストごとに、そのペアの距離を計算し、それをキャッシュに保存します。距離ペアのリクエストが来たら、まずキャッシュを調べ、それが利用可能かどうかを計算します。

距離計算の複雑さがわからないので、その場での計算の方がルックアップよりも安くなるかどうかも確認します(計算の頻度も考慮に入れます)。

0
John Smith

私はうまく実行できていない問題があり、ほとんど全員の答えが使われました。私はこれを単に「最初からやり直す」のではなく、古いソリューションの観点から考えていました。 Babtekは、最も簡単な言葉で述べたことを認めます。

必要な数式を導出するための参照を提供するため、コードをスキップします。ここには、明確に投稿するには多すぎます。

1)緯度と経度で表される球の点Aを考えます。 ポイントAを中心として2Xマイルのボックスの北、南、東、西の端を計算します

2)ZipCodeテーブルからボックス内のすべてのポイントを選択します。これには、LatとLongで制限する2つのBetweenステートメントを含む単純なWHERE句が含まれます。

3)ハバーシンの公式を使用して、ステップ2で返されたポイントAとすべてのポイントBの間の球面距離を決定します。

4)距離A-> B> XであるすべてのポイントBを破棄します。

5)ZipCodeが残りのポイントセットBにあるユーザーを選択します。

これは100マイルを超えるとかなり高速です。最長の結果は、一致を計算するために約0.014秒で、selectステートメントを実行するのは簡単でした。

また、補足として、数学をいくつかの関数に実装し、SQLで呼び出す必要がありました。特定の距離を過ぎると、一致するZipCodeの数が多すぎてSQLに戻してINステートメントとして使用できないため、一時テーブルを使用して、結果のZipCodeをZipCode列のユーザーに結合する必要がありました。

ZipDistanceテーブルを使用しても、長期的にはパフォーマンスが向上しないと思います。行数が非常に大きくなるだけです。すべての郵便番号から他のすべての郵便番号までの距離を(最終的には)計算すると、40,000の郵便番号からの結果の行数は約1.6Bになります。うわぁ!

あるいは、SQLの組み込み地理タイプを使用してこれが容易になるかどうかを確認することに興味がありますが、このサンプルでは古き良きint/floatタイプがうまく機能しました。

だから...あなたの簡単な参照のために、私が使用したオンラインリソースの最後のリスト:

1) 最大差、緯度と経度

2) Haversine Formula

3) プロセス全体の長いが完全な議論 、これはあなたの答えのグーグルのものから見つけました。

0
Eric Burcham

この投稿は古すぎることを知っていますが、クライアント向けに調査を行ったところ、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/

0