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

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

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

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

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

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

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

全国の町丁目（190,165件）の住所データがCSVで公開されている。
→ [Geolonia 住所データ](https://geolonia.github.io/japanese-addresses/)

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

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

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

並び順がバラバラなので「大字町丁目コード」をAZでソート。都道府県名、市区町村名、大字町丁目名、緯度、経度を残し他の列を削除。
![ttm1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/708687/29122adb-691a-db50-faba-fcd1d956f863.jpeg)
こんな感じで190016行目の与那国まで並ぶ。

もし特定の地域でしか使わないのであれば、不要な行を削除しておくと動作が軽くなる。

##オリジナル関数
オリジナル関数「tellmettm」を作成する。
ccmでなくttmなのは、個人的にタイプしやすいからｗ

コードがこちら

```javascript
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; 
}

```

範囲、中心点の緯度、中心点の経度を引数に、範囲内の町名を出力する。
距離計算部分は別記事「[【ワンライナー】緯度経度から１行で距離計算](https://qiita.com/YasumiYasumi/items/9e8a6f185b00cba8c8bd)」で解説。

##使ってみた
例として、品川駅（35.62888932, 139.7388039）から1.5km圏内を調べてみた。
A1セルに`=tellmettm(35.62888932,139.7388039,1.5)`と入力。
![ttm1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/708687/f722c016-465c-e969-defd-221d2d3f53dd.jpeg)
素晴らしい！

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

漢数字をreplaceすれば良いかと安直に追記してみたら「東五反田五丁目」が「東5反田5丁目」に（笑）

後ろ３文字条件にしても〇丁目が無い町丁目もある。「丁目」の前の文字を条件にしても前１文字だと二桁丁目に対応できないし、前２文字にして町丁目名最後の１文字が漢数字の町名（あるか知らないけど）には対応できない。

難しく考えるのはやめ。日本の最大番地は四十二丁目らしいので、チカラ技で全てreplaceした。

半角数字バージョンのコードがこちら

```javascript
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/708687/f0b5664a-fad6-f298-32fe-c24d75d392db.jpeg)
パーフェクト ^^)v

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

##追記
コメントで
・20万回近いループで、都度`R`の計算と代入が行われている。
・打ち捨てられる町名にも`replace`が行われている。
と、無駄の指摘をいただきました。

<details><summary>その通りなので修正！</summary><div>

```javascript
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; 
}
```

```javascript
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; 
}
```

</div></details>

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

###おしまい