【備忘録】Yahoo!ローカルサーチAPIで牛丼チェーン店の位置情報を取得してdeck.glで表示してみました

# はじめに
- [Yahoo!ローカルサーチAPI](https://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/localsearch.html)で牛丼チェーン店の位置情報を取得して[deck.gl](https://deck.gl/)で表示してみました。
- Yahoo!ローカルサーチAPIでは「丼もの、牛丼、親子丼（業種コード：0101031）」で絞り込み、位置情報を取得しています。
- 取得した牛丼チェーン店の位置情報はエクセル等で「すき家」「吉野家」「松屋」で抽出してdeck.glで表示しています。

# アウトプットイメージ
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">YahooローカルサーチAPIで牛丼チェーン店の位置情報を取得して <a href="https://t.co/j5x1odcLvT">https://t.co/j5x1odcLvT</a>で表示してみた。すき家：赤色、松屋：橙色、吉野家：緑色。吉野家はデータに抜けがあるため参考。すき家は郊外型、松屋は鉄道沿線型という棲み分けが見えたかな。三大都市圏は同様の傾向。牛丼食べたい。<a href="https://twitter.com/hashtag/Mapbox?src=hash&amp;ref_src=twsrc%5Etfw">#Mapbox</a> <a href="https://t.co/plH5GpH7mf">https://t.co/plH5GpH7mf</a> <a href="https://t.co/T221y8tzvr">pic.twitter.com/T221y8tzvr</a></p>&mdash; shi_works☀ (@syanseto) <a href="https://twitter.com/syanseto/status/1434810135344410625?ref_src=twsrc%5Etfw">September 6, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 前提条件
- Yahoo!ローカルサーチAPIのアプリケーションIDが必要です。
- 都道府県ごとに位置情報を取得して出力しているため、都道府県コードのリストが必要です。
- 都道府県コードのリスト（PrefCode.csv）は以下のとおりとします（01:北海道～47:沖縄県）。
- 今回は、業種コードが「0101031（丼もの、牛丼、親子丼）」を用いていますが、業種コードは変更可能です。
- 業種コードは[こちら](https://developer.yahoo.co.jp/webapi/map/openlocalplatform/genre.html)で確認することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205045/26095ace-2b12-788f-da5f-91706e7d43ea.png)

# python
```python:localSearchAPI.py
import json
import socket
import urllib.error
import urllib.request
import csv

# Yahoo!ローカルサーチAPI

baseurl = 'https://map.yahooapis.jp/search/local/V1/localSearch'

# アプリケーションIDを指定
appid = 'Yahoo!ローカルサーチAPIのアプリケーションIDを入力してください'

# 都道府県コードのリストを作成する
PrefCodelist = []

# csvファイル（都道府県コードのリスト）の読み込み
with open('都道府県コードのリスト（PrefCode.csv）のパスを入力してください') as f:
    header = next(csv.reader(f))
    for row in csv.reader(f):
        s = row[0]
        PrefCodelist.append(s)

print(PrefCodelist)

# 都道府県コードのリストの数でループ
for PrefCode in PrefCodelist:
    # 出力csvファイルの指定
    output_csvfile = "出力フォルダのパスを入力してください" + "/" + PrefCode + ".csv"

    result = []
    i = 1
    total = 3102
    while i < total:
        params = {
            'appid': appid,
            'ac': str(PrefCode),    # 住所コード（都道府県コード）
            'gc': '0101031',        # 業種コード（丼もの、牛丼、親子丼）
            'output': 'json',
            'sort': 'score',
            'start': i,
            'results': 100
        }

        url = '{}?{}'.format(baseurl, urllib.parse.urlencode(params))
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req, timeout=3) as res:
            data = res.read()
        # print(data)
        ydf = json.loads(data)
        # print(ydf)
        # Count（データ件数）取得
        Count = ydf['ResultInfo']['Count']
        # Countがゼロの場合、ループを抜ける
        if Count == 0:
            break
        features = ydf['Feature']
        for f in features:
            if f['Geometry']['Type'] == 'point':
                latlng = f['Geometry']['Coordinates'].split(',')
                poi = {
                    'uid': f['Property']['Uid'],                        # ユニークID
                    'name': f['Name'],                                  # 地域・拠点情報名
                    'address': f['Property']['Address'],                # 住所
                    'lat': latlng[1],                                   # 緯度
                    'lng': latlng[0]                                    # 経度
                }
            result.append(poi)
        i += 100

    # 出力ファイルオープン
    with open(output_csvfile, 'a', encoding='cp932') as f:
        # ヘッダー行出力
        fieldnames = ['i', 'uid', 'name', 'address', 'lat', 'lng']
        # writeオブジェクト作成
        csvfile_writer = csv.DictWriter(
            f, fieldnames=fieldnames, lineterminator='\n')
        csvfile_writer.writeheader()

        for j, poi in enumerate(result, 1):
            csvfile_writer.writerow({
                'i': j,
                'uid': poi['uid'],
                'name': poi['name'],
                'address': poi['address'],
                'lat': poi['lat'],
                'lng': poi['lng']
            })

print(u'処理終了')
```

# deck.gl
- 上記で取得した牛丼チェーン店の位置情報（csvファイル）を[deck.gl](https://deck.gl/)で表示します。
- Mapboxのアクセストークンが必要になります。
- 牛丼チェーン店の位置情報（csvファイル）をdeck.glで表示するために[QGIS](https://qgis.org/ja/site/)等でgeojson形式（yahoo_gc0101031_MergeFile.geojson）に変換しています。
- また、[国土数値情報の鉄道データ（令和元年）](https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N02-v2_3.html)から鉄道路線のラインデータ（N02-19_RailroadSection.geojson）を読み込んでいます。
 
## html
```html:Yahoo_Gyudon_chain_store.html
<html>

<head>
  <title>牛丼チェーン店の立地状況（Yahoo!ローカルサーチAPI）</title>
  <meta charset="UTF-8">
  <script src="https://unpkg.com/deck.gl@^7.0.0/dist.min.js"></script>
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.50.0/mapbox-gl.js"></script>
  <link rel="stylesheet" href="style.css" />
</head>

<body>
  <div id="tooltip"></div>
  <script src="script.js"></script>
</body>

</html>
```
## CSS
```css:style.css
html,body {
  font-family: Helvetica, Arial, sans-serif;
  width: 100vw;
  height: 100vh;
  margin: 0;
}
.deck-tooltip {
  font-family: Helvetica, Arial, sans-serif;
  padding: 6px !important;
  margin: 8px;
  max-width: 300px;
  font-size: 20px;
}

#control-panel {
  position: absolute;
  top: 0;
  left: 0;
  margin: 12px;
  padding: 20px;
  font-size: 12px;
  line-height: 1.5;
  z-index: 1;
  background: #fff;
  font-family: Helvetica, Arial, sans-serif;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);
}

label {
  display: inline-block;
  width: 140px;
}
```
## javascript
```javascript:script.js
const deckgl = new deck.DeckGL({
  mapboxApiAccessToken: 'Mapboxのアクセストークンを入力してください',
  mapStyle: 'mapbox://styles/mapbox/dark-v9',
  // mapStyle: 'mapbox://styles/mapbox/streets-v9',
  initialViewState: {
    latitude: 35.1801494,
    longitude: 136.9034925,
    zoom: 5,
    maxZoom: 16
  },
  controller: true,
});

const railway = d3.json('data/N02-19_RailroadSection.geojson');
const yahoo = d3.json('data/yahoo_gc0101031_MergeFile.geojson');
const renderLayer = () => {

  const LINE_COLOR_SCALE = d3.scaleLinear()
    .domain([1, 2, 3])
    .range([
      [216, 25, 0],
      [0, 65, 33],
      [255, 217, 58]
    ]);

  const FILL_COLOR_SCALE = d3.scaleLinear()
    .domain([1, 2, 3])
    .range([
      [255, 204, 216],
      [216, 255, 204],
      [255, 255, 204]
    ]);

  const geoJsonLayer_1 = new deck.GeoJsonLayer({
    id: 'railway-geojson',
    data: railway,
    stroked: false,
    filled: false,
    lineWidthMinPixels: 1,
    parameters: {
      depthTest: false
    },
    opacity: 0.5,
    getLineColor: [240, 240, 240],
    getLineWidth: 1,
    pickable: false,
  });

  const geoJsonLayer_2 = new deck.GeoJsonLayer({
    id: 'yahoo-geojson',
    data: yahoo,
    lineWidthMinPixels: 2,
    pointRadiusUnits: 'meters',
    pointRadiusScale: 20,
    getRadius: 50,
    getLineColor: d => LINE_COLOR_SCALE(d.properties.flg),
    getFillColor: d => FILL_COLOR_SCALE(d.properties.flg),
    opacity: 0.5,
    pickable: true
  });

  deckgl.setProps({
    layers: [geoJsonLayer_2, geoJsonLayer_1],
    getTooltip: ({ object }) => object && `${object.properties.name}`
  });

}

renderLayer();
```
# 参考文献
https://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/localsearch.html#start

https://deck.gl/docs/api-reference/layers/scatterplot-layer