3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【GAS】任意の位置から指定の範囲内にある町丁目を調べる【スプレッドシート】

Last updated at Posted at 2021-09-25

##概要
複数の場所で、半径2.5km以内にある町名を洗い出したい。と相談されたので挑戦。

そう言えば2点間の距離を調べるのはGoogleMapなんかでも簡単にできるけど、範囲内にある町名を調べる方法が(あるんだろうけど)無いなぁと、中心点を決めたら範囲内にある町名一覧を出力するツールを作ることに。

スプレッドシートで作成。使いやすいようにGASでオリジナル関数にしてみた。

##没ネタ
任意の中心点から、緯度経度を少しずつズラしながらgeocodeで町名を取得し、重複を削除すればいいのかなと仮組み。
この方法だと、目が粗ければ取りこぼしがあるし、細かければgeocodeの呼び出し回数が爆発的に増えてしまう。

どのくらいずつズラせば良いか分からないので、この方法は没に^^;

##便利なデータベース
そうこうしていたら、使えそうな良いのを見つけた。

全国の町丁目(190,165件)の住所データがCSVで公開されている。
Geolonia 住所データ

市区町村名や大字町丁目名の他に、代表点の緯度経度などが一覧になっているので、これを使うことに。

##考え方
中心点と、町丁目を拾いたい範囲の半径を設定。中心点の緯度経度と全国町丁目の代表緯度経度の距離を計算。範囲の半径以下の町丁目を一覧として出力する。

##下準備
上記サイトでダウンロードした町丁目データをスプレッドシートにインポートする。

並び順がバラバラなので「大字町丁目コード」をAZでソート。都道府県名、市区町村名、大字町丁目名、緯度、経度を残し他の列を削除。
ttm1.jpg
こんな感じで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)と入力。
ttm1.jpg
素晴らしい!

##工夫
漢数字を半角数字にしたい。

漢数字を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; 
}

結果がこちら
ttm1.jpg
パーフェクト ^^)v

##問題点
地図上の見た目で中心点からの範囲内にかかっていても、代表緯度経度が外側にある場合は一覧に含まれない。

##追記
コメントで
・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; 
}

投稿とコメントの整合性が無くなってしまうので、本文中のコードは修正前のまま。修正後のコードは上に折りたたんでます。

###おしまい

3
3
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?