##概要
複数の場所で、半径2.5km以内にある町名を洗い出したい。と相談されたので挑戦。
そう言えば2点間の距離を調べるのはGoogleMapなんかでも簡単にできるけど、範囲内にある町名を調べる方法が(あるんだろうけど)無いなぁと、中心点を決めたら範囲内にある町名一覧を出力するツールを作ることに。
スプレッドシートで作成。使いやすいようにGASでオリジナル関数にしてみた。
##没ネタ
任意の中心点から、緯度経度を少しずつズラしながらgeocodeで町名を取得し、重複を削除すればいいのかなと仮組み。
この方法だと、目が粗ければ取りこぼしがあるし、細かければgeocodeの呼び出し回数が爆発的に増えてしまう。
どのくらいずつズラせば良いか分からないので、この方法は没に^^;
##便利なデータベース
そうこうしていたら、使えそうな良いのを見つけた。
全国の町丁目(190,165件)の住所データがCSVで公開されている。
→ Geolonia 住所データ
市区町村名や大字町丁目名の他に、代表点の緯度経度などが一覧になっているので、これを使うことに。
##考え方
中心点と、町丁目を拾いたい範囲の半径を設定。中心点の緯度経度と全国町丁目の代表緯度経度の距離を計算。範囲の半径以下の町丁目を一覧として出力する。
##下準備
上記サイトでダウンロードした町丁目データをスプレッドシートにインポートする。
並び順がバラバラなので「大字町丁目コード」をAZでソート。都道府県名、市区町村名、大字町丁目名、緯度、経度を残し他の列を削除。
こんな感じで190016行目の与那国まで並ぶ。
もし特定の地域でしか使わないのであれば、不要な行を削除しておくと動作が軽くなる。
##オリジナル関数
オリジナル関数「tellmettm」を作成する。
ccmでなくttmなのは、個人的にタイプしやすいからw
コードがこちら
function tellmettm(area, lat, lng) {
//インポートした住所情報シート「latest」を丸っと取得
const data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('latest').getDataRange().getValues();
//出力用の配列宣言
var address = [];
//全町名ループ
for(var i of data){
//中心点~町丁目代表点の距離計算
with(Math) var span = acos(sin(lat*(R=PI/180))*sin(i[3]*R)+cos(lat*R)*cos(i[3]*R)*cos(lng*R-i[4]*R))*6371.008;
//もし指定の範囲内なら 配列に[都道府県,市区町村,大字町目]をプッシュ
if(span <= area){address.push([i[0], i[1], i[2]]);}
}
return address;
}
範囲、中心点の緯度、中心点の経度を引数に、範囲内の町名を出力する。
距離計算部分は別記事「【ワンライナー】緯度経度から1行で距離計算」で解説。
##使ってみた
例として、品川駅(35.62888932, 139.7388039)から1.5km圏内を調べてみた。
A1セルに=tellmettm(35.62888932,139.7388039,1.5)
と入力。
素晴らしい!
##工夫
漢数字を半角数字にしたい。
漢数字をreplaceすれば良いかと安直に追記してみたら「東五反田五丁目」が「東5反田5丁目」に(笑)
後ろ3文字条件にしても〇丁目が無い町丁目もある。「丁目」の前の文字を条件にしても前1文字だと二桁丁目に対応できないし、前2文字にして町丁目名最後の1文字が漢数字の町名(あるか知らないけど)には対応できない。
難しく考えるのはやめ。日本の最大番地は四十二丁目らしいので、チカラ技で全てreplaceした。
半角数字バージョンのコードがこちら
function tellmettm(lat, lng, area) {
const data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('latest').getDataRange().getValues();
var address = [];
for(var i of data){
with(Math) var span = acos(sin(lat*(R=PI/180))*sin(i[3]*R)+cos(lat*R)*cos(i[3]*R)*cos(lng*R-i[4]*R))*6371.008;
i[2] = i[2].replace('一丁目', '1丁目')
.replace('二丁目', '2丁目')
.replace('三丁目', '3丁目')
.replace('四丁目', '4丁目')
.replace('五丁目', '5丁目')
.replace('六丁目', '6丁目')
.replace('七丁目', '7丁目')
.replace('八丁目', '8丁目')
.replace('九丁目', '9丁目')
.replace('十丁目', '10丁目')
.replace('十一丁目', '11丁目')
.replace('十二丁目', '12丁目')
.replace('十三丁目', '13丁目')
.replace('十四丁目', '14丁目')
.replace('十五丁目', '15丁目')
.replace('十六丁目', '16丁目')
.replace('十七丁目', '18丁目')
.replace('十八丁目', '17丁目')
.replace('十九丁目', '19丁目')
.replace('二十丁目', '20丁目')
.replace('二十一丁目', '21丁目')
.replace('二十二丁目', '22丁目')
.replace('二十三丁目', '23丁目')
.replace('二十四丁目', '24丁目')
.replace('二十五丁目', '25丁目')
.replace('二十六丁目', '26丁目')
.replace('二十七丁目', '28丁目')
.replace('二十八丁目', '27丁目')
.replace('二十九丁目', '29丁目')
.replace('三十丁目', '30丁目')
.replace('三十一丁目', '31丁目')
.replace('三十二丁目', '32丁目')
.replace('三十三丁目', '33丁目')
.replace('三十四丁目', '34丁目')
.replace('三十五丁目', '35丁目')
.replace('三十六丁目', '36丁目')
.replace('三十七丁目', '38丁目')
.replace('三十八丁目', '37丁目')
.replace('三十九丁目', '39丁目')
.replace('四十丁目', '40丁目')
.replace('四十一丁目', '41丁目')
.replace('四十二丁目', '42丁目');
if(span <= area) address.push([i[0], i[1], i[2]]);
}
return address;
}
##問題点
地図上の見た目で中心点からの範囲内にかかっていても、代表緯度経度が外側にある場合は一覧に含まれない。
##追記
コメントで
・20万回近いループで、都度R
の計算と代入が行われている。
・打ち捨てられる町名にもreplace
が行われている。
と、無駄の指摘をいただきました。
その通りなので修正!
function tellmettm(lat, lng, area) {
const data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('latest').getDataRange().getValues();
var address = [];
var R = Math.PI / 180;
for(var i of data){
with(Math) var span = acos(sin(lat*R)*sin(i[3]*R)+cos(lat*R)*cos(i[3]*R)*cos(lng*R-i[4]*R))*6371.008;
if(span <= area) address.push([i[0], i[1], i[2]]);
}
return address;
}
function tellmettm(lat, lng, area) {
const data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('latest').getDataRange().getValues();
var address = [];
var R = Math.PI / 180;
for(var i of data){
with(Math) var span = acos(sin(lat*R)*sin(i[3]*R)+cos(lat*R)*cos(i[3]*R)*cos(lng*R-i[4]*R))*6371.008;
if(span <= area){
i[2] = i[2].replace('一丁目', '1丁目')
.replace('二丁目', '2丁目')
.replace('三丁目', '3丁目')
.replace('四丁目', '4丁目')
.replace('五丁目', '5丁目')
.replace('六丁目', '6丁目')
.replace('七丁目', '7丁目')
.replace('八丁目', '8丁目')
.replace('九丁目', '9丁目')
.replace('十丁目', '10丁目')
.replace('十一丁目', '11丁目')
.replace('十二丁目', '12丁目')
.replace('十三丁目', '13丁目')
.replace('十四丁目', '14丁目')
.replace('十五丁目', '15丁目')
.replace('十六丁目', '16丁目')
.replace('十七丁目', '18丁目')
.replace('十八丁目', '17丁目')
.replace('十九丁目', '19丁目')
.replace('二十丁目', '20丁目')
.replace('二十一丁目', '21丁目')
.replace('二十二丁目', '22丁目')
.replace('二十三丁目', '23丁目')
.replace('二十四丁目', '24丁目')
.replace('二十五丁目', '25丁目')
.replace('二十六丁目', '26丁目')
.replace('二十七丁目', '28丁目')
.replace('二十八丁目', '27丁目')
.replace('二十九丁目', '29丁目')
.replace('三十丁目', '30丁目')
.replace('三十一丁目', '31丁目')
.replace('三十二丁目', '32丁目')
.replace('三十三丁目', '33丁目')
.replace('三十四丁目', '34丁目')
.replace('三十五丁目', '35丁目')
.replace('三十六丁目', '36丁目')
.replace('三十七丁目', '38丁目')
.replace('三十八丁目', '37丁目')
.replace('三十九丁目', '39丁目')
.replace('四十丁目', '40丁目')
.replace('四十一丁目', '41丁目')
.replace('四十二丁目', '42丁目');
address.push([i[0], i[1], i[2]]);
}
}
return address;
}
投稿とコメントの整合性が無くなってしまうので、本文中のコードは修正前のまま。修正後のコードは上に折りたたんでます。
###おしまい