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スクリプトでは「駅名-都道府県コード」というふうにしています。
PHP | station_tsv_to_redis.php | GitHub 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単位で返っています。
※本記事内容の無断転載を禁じます。
ご連絡は以下アドレスまでお願いします★
さくらインターネットでPython MecabをCGIから使う
さくらインターネットのPHPでAnalytics-G4 APIを使う
インクルードパスの調べ方
【Git】特定ファイルを除外する.gitignore
【Ubuntu/Debian】NVIDIA関係のドライバを自動アップデートさせない
【Python】Spacyを使用して文章から出発地と目的地を抜き出す
HomeBrewでApache2を入れて自動起動つきで動かしPHPモジュールと連携する
macOSに標準付属のApacheを自動起動つきで動かす
HomeBrewでPostgreSQLを入れて自動起動つきで動かす
Windows版Google Driveが使用中と言われアンインストールできない場合
【C/C++】小数点以下の切り捨て・切り上げ・四捨五入
進研ゼミチャレンジタッチをAndroid端末化する
Windows11+WSL2でUbuntuを使う【2】ブリッジ接続+固定IPの設定
Googleスプレッドシートで図形をコピーして使いまわすには
【Linux】iconv/libiconvをソースコードからインストール
【Apache】サーバーに同時接続可能なクライアント数を調整する
Pythonで処理にかかった時間を計測するには
Windows11のコマンドプロンプトでテキストをコピーする