Express経由でLivedoor Weather Web Service の RestAPI を呼んでみる

# ※livedoor 天気サービスは2020/7/31で終了
代替サービス探します

# はじめに
お天気情報とってくるAPIがいくつかあると知って、試した

# [Livedoor Weather Web Service](http://weather.livedoor.com/weather_hacks/webservice)

## 概要
- livedoorが[WeatherHack](http://weather.livedoor.com/weather_hacks/)というお天気情報出してる
- お天気情報取得RestAPIも出してる

## 準備

- [Node.js](https://nodejs.org/ja/download/)
サーバー立てるのに使用

- [dataTables.js](https://datatables.net/)
データ表示に使用(今回ソースはダウンロードせずimportして使う)

## 使用方法

[公式ドキュメント](http://weather.livedoor.com/weather_hacks/webservice)

- GET:http://weather.livedoor.com/forecast/webservice/json/v1 + cityID
- [City ID](http://weather.livedoor.com/forecast/rss/primary_area.xml)は公式のXMLから探してる使う模様
- CORS許可がないので、HTMLから直Requestできない→サーバー立てる必要がある
- JSONでかえってくる（Valueがすべてendcodeされているので、parseする）
かえってきた中身は↓のような感じ

```sampledata.json
{
    "pinpointLocations": [
        {
            "link": "http://weather.livedoor.com/area/forecast/0121400",
            "name": "稚内市"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/0151100",
            "name": "猿払村"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/0151200",
            "name": "浜頓別町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/0151300",
            "name": "中頓別町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/0151400",
            "name": "枝幸町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/0151600",
            "name": "豊富町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/0151700",
            "name": "礼文町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/0151800",
            "name": "利尻町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/0151900",
            "name": "利尻富士町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/0152000",
            "name": "幌延町"
        }
    ],
    "link": "http://weather.livedoor.com/area/forecast/011000",
    "forecasts": [
        {
            "dateLabel": "今日",
            "telop": "曇り",
            "date": "2020-04-23",
            "temperature": {
                "min": null,
                "max": null
            },
            "image": {
                "width": 50,
                "url": "http://weather.livedoor.com/img/icon/8.gif",
                "title": "曇り",
                "height": 31
            }
        },
        {
            "dateLabel": "明日",
            "telop": "曇のち晴",
            "date": "2020-04-24",
            "temperature": {
                "min": {
                    "celsius": "2",
                    "fahrenheit": "35.6"
                },
                "max": {
                    "celsius": "7",
                    "fahrenheit": "44.6"
                }
            },
            "image": {
                "width": 50,
                "url": "http://weather.livedoor.com/img/icon/12.gif",
                "title": "曇のち晴",
                "height": 31
            }
        },
        {
            "dateLabel": "明後日",
            "telop": "曇時々晴",
            "date": "2020-04-25",
            "temperature": {
                "min": null,
                "max": null
            },
            "image": {
                "width": 50,
                "url": "http://weather.livedoor.com/img/icon/9.gif",
                "title": "曇時々晴",
                "height": 31
            }
        }
    ],
    "location": {
        "city": "稚内",
        "area": "北海道",
        "prefecture": "道北"
    },
    "publicTime": "2020-04-23T17:00:00+0900",
    "copyright": {
        "provider": [
            {
                "link": "http://tenki.jp/",
                "name": "日本気象協会"
            }
        ],
        "link": "http://weather.livedoor.com/",
        "title": "(C) LINE Corporation",
        "image": {
            "width": 118,
            "link": "http://weather.livedoor.com/",
            "url": "http://weather.livedoor.com/img/cmn/livedoor.gif",
            "title": "livedoor 天気情報",
            "height": 26
        }
    },
    "title": "道北 稚内 の天気",
    "description": {
        "text": " 北海道付近は、24日にかけて低気圧を含む気圧の谷の中で、はじめ気圧の傾きが大きいでしょう。\n\n 宗谷地方の23日15時の天気は、おおむね雪となっています。\n\n 23日夜は、曇りで、雪の降る所があるでしょう。\n\n 24日は、曇りで、昼前から晴れの見込みです。\n\n 海の波の高さは、23日夜は、2.5メートルのち2メートルとやや高いでしょう。24日は2メートルとやや高い見込みです。",
        "publicTime": "2020-04-23T16:39:00+0900"
    }
}
```

# サーバーを立てる

## 仕様
- `/index`にアクセスしたらお天気検索ページへ飛ぶ
- `/forcast`にアクセスしたらお天気情報のJSONを返す

## ソースコード

- expressでロールサーバー起動
- requestでWeatherWebServiceAPIを叩く

```package.json
{
  "name": "forcast_api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "request": "^2.88.2"
  }
}

```

```index.js
const express = require("express")
const server = express();
const port = 3000
const request = require('request');

server.get("/", function (req, res) {
    res.send("server is up")
})

server.get("/index", function (req, res) {
    res.sendFile("./index.html")
})

server.get("/forcast", function (req, res) {
    const base_url = "http://weather.livedoor.com/forecast/webservice/json/v1"
    const url = base_url + "?city=" + req.query.city

    request({ url: url, method: "GET" }, function (error, responce, data) {
        if (error) {
            res.status(400).send(error)
        } else {
            res.send(JSON.parse(data))
        }
    })
})

server.listen(port, function () {
    console.log("sever: " + port)
})
```

# Webページを作る

## 仕様

1. 公式に乗ってる全地域から1つ選択
2. `天気情報を取得`ボタン押下→天気情報の(全部表示するのは大変そうなので)一部が表示される

## ソースコード

- dataTable.jsの説明は今回省略
- optionベタ書きのため長くなった

```index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="urf-8">
    <title>forcast api test</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
        integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdn.datatables.net/t/bs-3.3.6/jqc-1.12.0,dt-1.10.11/datatables.min.css" />
    <style>
        textarea {
            width: 600px;
            height: 8em;
            line-height: 1.5em;
        }
    </style>

</head>

<body>
    <select id="city_select">
        <option value=011000> 道北-稚内 </option>
        <option value=012010> 道北-旭川 </option>
        <option value=012020> 道北-留萌 </option>
        <option value=013010> 道東-網走 </option>
        <option value=013020> 道東-北見 </option>
        <option value=013030> 道東-紋別 </option>
        <option value=014010> 道東-根室 </option>
        <option value=014020> 道東-釧路 </option>
        <option value=014030> 道東-帯広 </option>
        <option value=015010> 道南-室蘭 </option>
        <option value=015020> 道南-浦河 </option>
        <option value=016010> 道央-札幌 </option>
        <option value=016020> 道央-岩見沢 </option>
        <option value=016030> 道央-倶知安 </option>
        <option value=017010> 道南-函館 </option>
        <option value=017020> 道南-江差 </option>
        <option value=020010> 青森県-青森 </option>
        <option value=020020> 青森県-むつ </option>
        <option value=020030> 青森県-八戸 </option>
        <option value=030010> 岩手県-盛岡 </option>
        <option value=030020> 岩手県-宮古 </option>
        <option value=030030> 岩手県-大船渡 </option>
        <option value=040010> 宮城県-仙台 </option>
        <option value=040020> 宮城県-白石 </option>
        <option value=050010> 秋田県-秋田 </option>
        <option value=050020> 秋田県-横手 </option>
        <option value=060010> 山形県-山形 </option>
        <option value=060020> 山形県-米沢 </option>
        <option value=060030> 山形県-酒田 </option>
        <option value=060040> 山形県-新庄 </option>
        <option value=070010> 福島県-福島 </option>
        <option value=070020> 福島県-小名浜 </option>
        <option value=070030> 福島県-若松 </option>
        <option value=080010> 茨城県-水戸 </option>
        <option value=080020> 茨城県-土浦 </option>
        <option value=090010> 栃木県-宇都宮 </option>
        <option value=090020> 栃木県-大田原 </option>
        <option value=100010> 群馬県-前橋 </option>
        <option value=100020> 群馬県-みなかみ </option>
        <option value=110010> 埼玉県-さいたま </option>
        <option value=110020> 埼玉県-熊谷 </option>
        <option value=110030> 埼玉県-秩父 </option>
        <option value=120010> 千葉県-千葉 </option>
        <option value=120020> 千葉県-銚子 </option>
        <option value=120030> 千葉県-館山 </option>
        <option value=130010> 東京都-東京 </option>
        <option value=130020> 東京都-大島 </option>
        <option value=130030> 東京都-八丈島 </option>
        <option value=130040> 東京都-父島 </option>
        <option value=140010> 神奈川県-横浜 </option>
        <option value=140020> 神奈川県-小田原 </option>
        <option value=150010> 新潟県-新潟 </option>
        <option value=150020> 新潟県-長岡 </option>
        <option value=150030> 新潟県-高田 </option>
        <option value=150040> 新潟県-相川 </option>
        <option value=160010> 富山県-富山 </option>
        <option value=160020> 富山県-伏木 </option>
        <option value=170010> 石川県-金沢 </option>
        <option value=170020> 石川県-輪島 </option>
        <option value=180010> 福井県-福井 </option>
        <option value=180020> 福井県-敦賀 </option>
        <option value=190010> 山梨県-甲府 </option>
        <option value=190020> 山梨県-河口湖 </option>
        <option value=200010> 長野県-長野 </option>
        <option value=200020> 長野県-松本 </option>
        <option value=200030> 長野県-飯田 </option>
        <option value=210010> 岐阜県-岐阜 </option>
        <option value=210020> 岐阜県-高山 </option>
        <option value=220010> 静岡県-静岡 </option>
        <option value=220020> 静岡県-網代 </option>
        <option value=220030> 静岡県-三島 </option>
        <option value=220040> 静岡県-浜松 </option>
        <option value=230010> 愛知県-名古屋 </option>
        <option value=230020> 愛知県-豊橋 </option>
        <option value=240010> 三重県-津 </option>
        <option value=240020> 三重県-尾鷲 </option>
        <option value=250010> 滋賀県-大津 </option>
        <option value=250020> 滋賀県-彦根 </option>
        <option value=260010> 京都府-京都 </option>
        <option value=260020> 京都府-舞鶴 </option>
        <option value=270000> 大阪府-大阪 </option>
        <option value=280010> 兵庫県-神戸 </option>
        <option value=280020> 兵庫県-豊岡 </option>
        <option value=290010> 奈良県-奈良 </option>
        <option value=290020> 奈良県-風屋 </option>
        <option value=300010> 和歌山県-和歌山 </option>
        <option value=300020> 和歌山県-潮岬 </option>
        <option value=310010> 鳥取県-鳥取 </option>
        <option value=310020> 鳥取県-米子 </option>
        <option value=320010> 島根県-松江 </option>
        <option value=320020> 島根県-浜田 </option>
        <option value=320030> 島根県-西郷 </option>
        <option value=330010> 岡山県-岡山 </option>
        <option value=330020> 岡山県-津山 </option>
        <option value=340010> 広島県-広島 </option>
        <option value=340020> 広島県-庄原 </option>
        <option value=350010> 山口県-下関 </option>
        <option value=350020> 山口県-山口 </option>
        <option value=350030> 山口県-柳井 </option>
        <option value=350040> 山口県-萩 </option>
        <option value=360010> 徳島県-徳島 </option>
        <option value=360020> 徳島県-日和佐 </option>
        <option value=370000> 香川県-高松 </option>
        <option value=380010> 愛媛県-松山 </option>
        <option value=380020> 愛媛県-新居浜 </option>
        <option value=380030> 愛媛県-宇和島 </option>
        <option value=390010> 高知県-高知 </option>
        <option value=390020> 高知県-室戸岬 </option>
        <option value=390030> 高知県-清水 </option>
        <option value=400010> 福岡県-福岡 </option>
        <option value=400020> 福岡県-八幡 </option>
        <option value=400030> 福岡県-飯塚 </option>
        <option value=400040> 福岡県-久留米 </option>
        <option value=410010> 佐賀県-佐賀 </option>
        <option value=410020> 佐賀県-伊万里 </option>
        <option value=420010> 長崎県-長崎 </option>
        <option value=420020> 長崎県-佐世保 </option>
        <option value=420030> 長崎県-厳原 </option>
        <option value=420040> 長崎県-福江 </option>
        <option value=430010> 熊本県-熊本 </option>
        <option value=430020> 熊本県-阿蘇乙姫 </option>
        <option value=430030> 熊本県-牛深 </option>
        <option value=430040> 熊本県-人吉 </option>
        <option value=440010> 大分県-大分 </option>
        <option value=440020> 大分県-中津 </option>
        <option value=440030> 大分県-日田 </option>
        <option value=440040> 大分県-佐伯 </option>
        <option value=450010> 宮崎県-宮崎 </option>
        <option value=450020> 宮崎県-延岡 </option>
        <option value=450030> 宮崎県-都城 </option>
        <option value=450040> 宮崎県-高千穂 </option>
        <option value=460010> 鹿児島県-鹿児島 </option>
        <option value=460020> 鹿児島県-鹿屋 </option>
        <option value=460030> 鹿児島県-種子島 </option>
        <option value=460040> 鹿児島県-名瀬 </option>
        <option value=471010> 沖縄県-那覇 </option>
        <option value=471020> 沖縄県-名護 </option>
        <option value=471030> 沖縄県-久米島 </option>
        <option value=472000> 沖縄県-南大東 </option>
        <option value=473000> 沖縄県-宮古島 </option>
        <option value=474010> 沖縄県-石垣島 </option>
        <option value=474020> 沖縄県-与那国島 </option>

    </select>

    <button onclick="getForcast()">天気情報を取得</button>

    <div>
        <h1><span id="title"></span></h1>
        <span id="publicTime"></span>
    </div>

    <div>
        <textarea id="description"></textarea>
    </div>

    <h3>天気情報</h3>
    <table id="forcast_table" class="table table-bordered">
        <thead>
            <tr>
                <th class="dateLabel"></th>
                <th class="date">日付</th>
                <th class="telop">天気</th>
                <th class="temperature.min.celsius">最低気温</th>
                <th class="temperature.max.celsius">最高気温</th>
            </tr>
        </thead>

    </table>

    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
    <script src="https://cdn.datatables.net/t/bs-3.3.6/jqc-1.12.0,dt-1.10.11/datatables.min.js"></script>


    <script>
        // datatableのinitialize
        let dt = $('#forcast_table').DataTable(
            {
                dom: "t",
                paging: false,
                autoWidth: true,
                scrollY: "500px",
                scrollX: true,
                order: [],
                orderClasses: false,
                columnDefs: [{
                    className: "dt-right",
                }],
                columns: [
                    { data: "dateLabel", width: "50px" },
                    { data: "date", width: "50px" },
                    { data: "telop", width: "50px" },
                    { data: "temperature.min.celsius", width: "60px", "defaultContent": "-" },
                    { data: "temperature.max.celsius", width: "60px", "defaultContent": "-" },
                ]
            }
        );

        function getForcast() {
            console.log("clicled")
            const base_url = "http://localhost:3000/forcast"
            const city = document.getElementById("city_select").value
            const url = base_url + "?city=" + city

            const request = new XMLHttpRequest();
            request.open('GET', url, true);
            request.onreadystatechange = function () {
                if (request.readyState != 4) {
                } else if (request.status != 200) {
                    console.log("http request failed.")
                } else {
                    console.log("success", request.responseText);
                    const data = JSON.parse(request.responseText)
                    const newdate = new Date(data.publicTime)
                    document.getElementById("title").innerText = data.title
                    document.getElementById("publicTime").innerText = newdate.toLocaleDateString() + " " + newdate.toLocaleTimeString()
                    document.getElementById("description").value = data.description.text
                    // datatableを更新
                    dt.clear().draw();
                    dt.rows.add(data.forecasts);
                    dt.columns.adjust().draw();
                }
            };
            request.send();

        }
    </script>
</body>

</html>
```

## 結果

1. `npm install`した後に
2. `npm start`して
3. http://localhost:3000/index にアクセス

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431932/758b3e00-3b8f-a1c2-5d15-10f2aebd0cbe.png)

4.`天気情報を取得`ボタン押下

うまくいきました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431932/7e8e15fb-52fa-5382-0e34-6374cf4033fe.png)