アプリケーション開発ポータルサイト
ServerNote.NET
Amazon.co.jpでPC関連商品タイムセール開催中!
カテゴリー【RedisPHPDebian
【Redisメモ・5】位置情報関数GEOADD・GEORADUSを使う
POSTED BY
2023-10-12

Redisには緯度経度を扱うGEOから始まる関数が用意されています。
今回、任意の座標(緯度・経度)を入力したら、近い駅を返すという機能の実装を目指します。

駅・緯度・経度の一覧データは、こちらのサイト

http://linkdata.org/work/rdf1s4125i

の、station20151215free.txt を使わせていただきました。タブ区切りのCSVファイルです。

データ登録

緯度経度つきデータを登録するコマンドは以下の通りです。

GEOADD key longitude latitude member [longitude latitude member ...]

例えば新宿、新大久保を登録するには

GEOADD ekipos 139.700464 35.689729 "新宿"
GEOADD ekipos 139.700261 35.700875 "新大久保"

などとします。では上記CSVを読んでGEOADDに変換する作業ですが、同一の駅名が複数出てきます。異なる都道府県で同じ駅がある場合、または新宿のように複数の路線で同じ駅がある場合。今回、特定地点から近い駅を出す、が目的なので、後者は場所的に同じ駅なので無視してOK。しかし前者はれっきとした別の駅なので、駅名を加工して登録が必要です(member登録が重複したらredisは無視する)。今回の抽出PHPスクリプトでは「駅名-都道府県コード」というふうにしています。

PHPstation_tsv_to_redis.phpGitHub Source
<?php date_default_timezone_set('Asia/Tokyo');

$file = new SplFileObject('station20151215free.txt', 'r');
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY | SplFileObject::READ_AHEAD);
$file->setCsvControl("\t");

foreach ($file as $line)
{
    $fields[] = $line;
}

$n = count($fields);
$i = 9; // fields[9]からが実データ

for( ; $i < $n; $i++ ){
    // fields[i][10]=経度,[11]=緯度,[3]=駅名,[7]=都道府県番号
    // 駅名は同一駅名があるため、駅名-都道府県番号 という感じで登録する。
    $pref_no = $fields[$i][7];
    if(strlen($pref_no) <= 1){
      $pref_no = "0" . $pref_no;
    }
    echo "GEOADD ekipos " .
    $fields[$i][10] . " " .
    $fields[$i][11] . " " .
    "\"" . $fields[$i][3] . "-" . $pref_no . "\"\n";

    //駅名-都道府県番号でなく行番号-駅名で登録する場合
    //"\"" . ($i - 9 + 1) . "-" . $fields[$i][3] . "\"\n";
}

station20151215free.txtを設置したディレクトリで実行、出力をファイルに落とします。

php station_tsv_to_redis.php > station_tsv_to_redis.out.txt

head -n 3 station_tsv_to_redis.out.txt
GEOADD ekipos 140.726413 41.773709 "函館-01"
GEOADD ekipos 140.733539 41.803557 "五稜郭-01"
GEOADD ekipos 140.722952 41.846457 "桔梗-01"
tail -n 3 station_tsv_to_redis.out.txt
GEOADD ekipos 130.965292 33.947792 "出光美術館-40"
GEOADD ekipos 130.964254 33.955973 "ノーフォーク広場-40"
GEOADD ekipos 130.967347 33.960627 "関門海峡めかり-40"

これで全国分の駅名経度緯度GEOADD命令がstation_tsv_to_redis.out.txtに入りました。

https://redis-documentasion-japanese.readthedocs.io/ja/latest/topics/mass-insert.html

公式に書いてある通り、大きなデータは--pipeをつけるのが習わしとのこと。では、インサートします。

cat station_tsv_to_redis.out.txt | redis-cli --pipe

All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 10827

GEOADDは内部ではSORTED SET型として扱われているそうなので、要素数(member数)の確認は以下で取得。

127.0.0.1:6379> ZCARD ekipos
(integer) 9210

扱った行数が10827なのに実際の登録が9210個なのは、丸の内線の新宿や都営線の新宿などを無視した結果です。(どちらも東京の駅=新宿-13)。この場合最初の新宿さえ入れば良い訳なので問題ありません。

では任意の経度緯度から、近い駅を検索してみます。

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

Google Mapsで好きな場所を右クリック→この場所について で、経度緯度が出るので取得。

今回はイオンスタイル入間を選択し緯度経度139.394872 35.822019を取得、半径5キロ以内の駅を近い順に検索してみる。

折角なので、前回設定したレプリケーションスレーブRedisのほうから読み取ってみる。

redis-cli -p 6380
127.0.0.1:6380> GEORADIUS ekipos 139.394872 35.822019 5000 m WITHDIST ASC
(error) READONLY You can't write against a read only replica.

このRedisはスレーブだから書き込みできないなどというエラーが出た。GEORADIUSはデータ取得するだけなのになぜ???と悩んだが、

http://mogile.web.fc2.com/redis/commands/georadius.html

ここの最後のほうに記載がある通り、GEORADIUSは内部的に書き込みフラグが立つ仕様だそうで、読み込み目的ならGEORADIUS_ROコマンドを使え、とあった。ので、それに従う。

127.0.0.1:6380> GEORADIUS_RO ekipos 139.394872 35.822019 5000 m WITHDIST ASC
1) 1) "\xe6\xad\xa6\xe8\x94\xb5\xe8\x97\xa4\xe6\xb2\xa2-11"
   2) "1615.3342"

今度はうまくいったようだが、文字が16進で表示されているので読みづらい。これはredis-cliに--rawをつければ解決するようだ。

redis-cli -p 6380 --raw
127.0.0.1:6380> GEORADIUS_RO ekipos 139.394872 35.822019 5000 m WITHDIST ASC
武蔵藤沢-11
1615.3342
入間市-11
2359.4490
狭山ヶ丘-11
2372.9044
稲荷山公園-11
2588.4687
入曽-11
3147.7753
仏子-11
3586.3663
狭山市-11
4214.0209
小手指-11
4566.8008
元加治-11
4922.6774

ようやく正常結果が返り、WITHDISTオプションにより、あくまで入力したCSVデータによるものだが、イオンスタイル入間から直線距離5KM以内で、ちゃんと近い順に返してくれている。

次に、イオンスタイル入間から日本で最も遠い駅を遠い順に5つ出すには以下。

redis-cli --raw
127.0.0.1:6379> GEORADIUS_RO ekipos 139.394872 35.822019 3000 km WITHDIST COUNT 5 DESC
赤嶺-47
1546.3152
那覇空港-47
1545.8033
小禄-47
1545.6134
奥武山公園-47
1544.6936
壺川-47
1544.0601

おお、やっぱり沖縄なんですね。今度は単位をkmで検索しているので、結果の距離もkm単位で返っています。

※本記事は当サイト管理人の個人的な備忘録です。本記事の参照又は付随ソースコード利用後にいかなる損害が発生しても当サイト及び管理人は一切責任を負いません。
※本記事内容の無断転載を禁じます。
【WEBMASTER/管理人】
自営業プログラマーです。お仕事ください!
ご連絡は以下アドレスまでお願いします★

☆ServerNote.NETショッピング↓
ShoppingNote / Amazon.co.jp
☆お仲間ブログ↓
一人社長の不動産業務日誌
【キーワード検索】