MarkdownAIで、昨日と今日の天気を比較できるアプリを作った

# はじめに
ようやく長い猛暑が終わったと思っていたら、2月に入ってからとても寒い日が続いて大変ですね。
温度や天気に対する感覚があまりないので、「今日の最高気温は11度です」と言われても、結局寒いのか暑いのか分からず、外に出ると「寒っ！」となって困っています。

そこで、今日の天気予報だけでなく、昨日の天気と比較して、「昨日より厚着をしたら良いんだな」「薄着のほうが良いんだな」と判断できるような、簡単なアプリを作ってみました。


![スクリーンショット 2025-02-08 15.34.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3954993/8f698e21-88f6-0699-05ba-1ae323d56f84.png)

[今日は昨日より暑い？寒い？（東京の天気）](https://mdown.ai/content/b54c89ff-b180-4555-aafb-1e18c0c5d6f4)



## MarkdownAI
今回は[MarkdownAI](https://mdown.ai/)というサービスを使ってみることにしました。
MarkdownAIは、Markdown記法を使って簡単にWebサイトを作成・公開できるサービスです。
Notionのようにサクッとコンテンツを公開することができますが、HTML/CSSやJavaScriptにも対応しているので見た目にも機能にもこだわったアプリを作ることが可能。
さらに、簡単にチャットボットを作ることができるロボットAI機能や、画像生成AIによって生成した画像をサクッと挿入できる機能もあって、全部**無料**。

Streamlitなども良いですが、MarkdownAIはもっと手軽で良いですね。


# スクリプト
![スクリーンショット 2025-02-08 15.43.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3954993/19b78300-39c0-593f-f6fa-fd22c2d13e72.png)

## Markdown部分
書き始めはMarkdownから。シンプルに、次のように記述するだけで済んでいます。

```
# 今日は昨日より暑い？寒い？
## （東京の天気）
```

## 画像生成AI部分
`Insert`から「AI Image」を選択することで、簡単に画像生成と画像の挿入を行うことができました。
今回は、アプリのバナー的な、アイキャッチ的な画像を作って挿し込むことにしました。

![スクリーンショット 2025-02-08 15.43.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3954993/5306b166-96dc-99b5-e017-116ee2417a44.png)
![スクリーンショット 2025-02-08 15.46.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3954993/e65f75f7-6cf2-44fa-a8d0-9640d397aadd.png)

プロンプトはシンプルに、`様々な天気のロゴ`としました。
生成されたのは次の画像。シンプルで良い感じです。
![](https://storage.googleapis.com/topdowncom/content/3WQlILNNcNToPc2ivDCA1VeUXBm1/b54c89ff-b180-4555-aafb-1e18c0c5d6f4/2d007f4a-394b-4945-a377-e97f5d3a58b0)


## Javascript部分
昨日の天気のデータは[こちら](https://www.cultivationdata.net/weather-web-api.html)から、今日・明日の天気のデータは[こちら](https://weather.tsukumijima.net/)から、ありがたく使わせていただきました。
私が住んでいる東京の天気を知りたく、実際に取得するデータのURLはそれぞれ次のものです。
* https://api.cultivationdata.net/past?prec=44&no=47662
* https://weather.tsukumijima.net/api/forecast/city/130010

```javascript
<script>
    async function fetchWeatherData() {
        const pastData = await fetch("https://api.cultivationdata.net/past?prec=44&no=47662")
            .then(res => res.json())
            .catch(() => null);

        const forecastData = await fetch("https://weather.tsukumijima.net/api/forecast/city/130010")
            .then(res => res.json())
            .catch(() => null);

        if (pastData) {
            displayWeather('yesterday', formatPastData(pastData));
        }

        if (forecastData) {
            displayWeather('today', formatForecastData(forecastData, 0));
            displayWeather('tomorrow', formatForecastData(forecastData, 1));
        }
    }

    function formatPastData(data) {
        const cityData = Object.values(data)[0];
        const rawDate = Object.keys(cityData)[0];
        const weatherData = Object.values(cityData)[0];
        const date = new Date(rawDate);
        const formattedDate = date.toISOString().split('T')[0];

        return {
            date: formattedDate,
            dayOfWeek: ['日', '月', '火', '水', '木', '金', '土'][date.getDay()],
            maxTemp: parseFloat(weatherData["最高気温"]).toFixed(1),
            minTemp: parseFloat(weatherData["最低気温"]).toFixed(1),
            precipitation: weatherData["合計降水量"] === "--" ? "0.0mm" : `${weatherData["合計降水量"]}mm`
        };
    }

    function formatForecastData(data, index) {
        const forecast = data.forecasts[index];
        const date = new Date(forecast.date);
        const temp = forecast.temperature;

        const formatTemp = (tempValue) => {
            if (!tempValue || isNaN(parseFloat(tempValue.celsius))) {
                return "--";
            }
            return parseFloat(tempValue.celsius).toFixed(1);
        };

        return {
            date: forecast.date,
            dayOfWeek: ['日', '月', '火', '水', '木', '金', '土'][date.getDay()],
            maxTemp: formatTemp(temp.max),
            minTemp: formatTemp(temp.min),
            precipitation: forecast.chanceOfRain ? 
                Math.max(
                    parseInt(forecast.chanceOfRain["06-12"] || 0),
                    parseInt(forecast.chanceOfRain["12-18"] || 0)
                ) + "%" : "N/A",
            image: forecast.image
        };
    }

    function displayWeather(elementId, data) {
        const titles = {
            yesterday: '昨日',
            today: '今日',
            tomorrow: '明日'
        };

        const imageHtml = data.image ? 
            `<img src="${data.image.url}" alt="${data.image.title}">` :
            '<div class="placeholder-image"></div>';

        document.getElementById(elementId).innerHTML = `
            <h2>${titles[elementId]}
                <span class="date-label">${data.date} ${data.dayOfWeek}</span>
            </h2>
            ${imageHtml}
            <div class="weather-info">
                <p>最高気温: ${data.maxTemp}℃</p>
                <p>最低気温: ${data.minTemp}℃</p>
                <p>${elementId === 'yesterday' ? '降水量' : '降水確率'}: ${data.precipitation}</p>
            </div>
        `;
    }

    fetchWeatherData();
</script>
```

## CSS部分
少しだけアプリっぽくするために、見た目を整形しています。

```css
<style>
    body {
        text-align: center;
    }
    #yesterday, #today, #tomorrow {
        margin: 10px;
        padding: 10px;
        border: 1px solid #ccc;
        display: inline-block;
        width: 250px;
        height: 300px;
        vertical-align: top;
    }
    #today {
        background-color: #e6f3ff;
    }
    h1 {
        font-size: 1.4em;
    }
    h2 {
        font-size: 1.2em;
        margin: 10px 0;
    }
    .date-label {
        font-size: 0.9em;
        display: block;
    }
    .weather-info p {
        margin: 5px 0;
    }
    .placeholder-image {
        width: 100px;
        height: 100px;
        margin: 10px auto;
        background-color: #f8f8f8;
    }
    img {
        margin: 10px auto;
        width: 100px;
        height: 100px;
        display: block;
    }
</style>
```


# おわりに
これで簡単に、昨日と今日（と明日）の天気を比較できるアプリを作って公開することができました。
これで簡単に外出時の服装を決めることができそうです〜。

また、チャットボット機能を使って、今日のおすすめの服装を提案してくれるアプリにしても良かったなあと思っています。

（とはいえしばらくはずっと寒いでしょうから、ひたすら厚着でしょうね）