1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Obsidianのデイリーノートに今日の天気を埋め込みたい(その3)〜場所選択機能付き〜

Posted at

はじめに

この記事は以下の記事の続編です:

前回の記事では、デイリーノート作成時に天気情報を自動取得する仕組みを構築しましたが、場所を選択できなかったという課題がありました。これでは異なる場所で使用している複数端末連携時や、ノートPCを外部に持ち出した際に、実行時の場所の天気を選べずに不便でした。

今回の記事では、以下の画像のようにあらかじめコードに埋め込んだ住所を実行時に提案・選択することで、複数の住所での天気情報を取得できるデイリーノートを提案します。

image.png

image.png

必要な環境

  • Obsidian
  • Templater プラグイン
  • OpenWeatherMap API キー(取得方法は前回記事を参照)

実装方法

1. デイリーノートテンプレートの作成

すでに作ってあるdailynote_temp.mdの内容を以下のコードに置き換えてください。
(またdailynoteを作っていないなら、頑張らない最低限のObsidian環境構築 - Qiitaなどの内容を参考にして作ってください)

dailynote_temp.md
---
aliases:
  - "<% moment(tp.file.title).format('MMDD') %>"
  - "<% moment(tp.file.title).format('YYYYMMDD') %>"
tags:
  - "daily"
  - "<% moment(tp.file.title).format('YYYY') %>"
  - "<% moment(tp.file.title).format('YYYY-MM') %>"
---
#### << [[<% moment(tp.file.title).add(-1, 'd').format('YYYY-MM-DD') %>|Yesterday]] | [[<% moment(tp.file.title).add(-1, 'y').format('YYYY-MM-DD') %>|Last year]] | [[<% moment(tp.file.title).add(1, 'd').format('YYYY-MM-DD') %>|Tomorrow]] >>

今年の残り日数:<% moment(`${moment(tp.file.title).endOf('year')}`).diff(tp.file.title, "days") %>日

<%*
const formatTime = (dateTimeStr) => {
    const date = new Date(dateTimeStr);
    return date.toLocaleTimeString("ja-JP", {
        hour: "2-digit",
        minute: "2-digit",
        hour12: false,
    });
};

const padJapanese = (str, length) => {
    const padding = " ".repeat(Math.max(0, length - str.length));
    return str + padding;
};

const getJapaneseDescription = (description) => {
    const descMap = {
        "clear sky": "快晴",
        "few clouds": "晴れ",
        "scattered clouds": "晴れ時々曇り",
        "broken clouds": "所により曇り",
        "overcast clouds": "曇り",
        "light rain": "小雨",
        "shower rain": "霧雨",
        "moderate rain": "雨",
        thunderstorm: "雷雨",
        "heavy rain": "大雨",
        snow: "雪",
        "light snow": "小雪",
        mist: "霧",
        "thunderstorm with light rain": "弱い雨を伴う雷雨",
        "thunderstorm with rain": "雨を伴う雷雨",
        "thunderstorm with heavy rain": "強い雨を伴う雷雨",
        "light thunderstorm": "弱い雷雨",
        "heavy thunderstorm": "強い雷雨",
        "ragged thunderstorm": "断続的な雷雨",
        "thunderstorm with light drizzle": "弱い霧雨を伴う雷雨",
        "thunderstorm with drizzle": "霧雨を伴う雷雨",
        "thunderstorm with heavy drizzle": "強い霧雨を伴う雷雨",
        "light intensity drizzle": "弱い霧雨",
        drizzle: "霧雨",
        "heavy intensity drizzle": "強い霧雨",
        "light intensity drizzle rain": "弱い霧雨混じりの雨",
        "drizzle rain": "霧雨混じりの雨",
        "heavy intensity drizzle rain": "強い霧雨混じりの雨",
        "shower rain and drizzle": "霧雨を伴うにわか雨",
        "heavy shower rain and drizzle": "強い霧雨を伴うにわか雨",
        "shower drizzle": "にわか霧雨",
        rain: "雨",
        "heavy intensity rain": "激しい雨",
        "very heavy rain": "非常に強い雨",
        "extreme rain": "猛烈な雨",
        "freezing rain": "凍雨",
        "light intensity shower rain": "弱いにわか雨",
        "heavy intensity shower rain": "強いにわか雨",
        "ragged shower rain": "断続的なにわか雨",
        "heavy snow": "大雪",
        sleet: "みぞれ",
        "light shower sleet": "弱いにわかみぞれ",
        "shower sleet": "にわかみぞれ",
        "light rain and snow": "弱い雨雪",
        "rain and snow": "雨雪",
        "light shower snow": "弱いにわか雪",
        "shower snow": "にわか雪",
        "heavy shower snow": "強いにわか雪",
        smoke: "煙",
        haze: "もや",
        "sand/dust whirls": "砂塵旋風",
        fog: "濃霧",
        sand: "砂",
        dust: "粉塵",
        "volcanic ash": "火山灰",
        squalls: "スコール",
        tornado: "竜巻",
    };
    return descMap[description] || description;
};

const convertTimestamp = (timestamp) => {
    const date = new Date(timestamp * 1000);
    const hours = date.getHours().toString().padStart(2, "0");
    const minutes = date.getMinutes().toString().padStart(2, "0");
    return `${hours}:${minutes}`;
};

// formatWeatherData 関数に selectedCityName 引数を追加
const formatWeatherData = (weatherData, selectedCityName) => {
    // weatherData.city.name ではなく selectedCityName を使用
    const city = selectedCityName;
    const { list } = weatherData;

    const sunrise = convertTimestamp(weatherData.city.sunrise); // 日の出時刻
    const sunset = convertTimestamp(weatherData.city.sunset); // 日の入時刻

    const formattedData = list.slice(0, 8).map((period) => {
        const {
            dt_txt,
            weather,
            main: { temp, pressure, humidity },
            pop,
            rain,
            wind: { speed, deg },
        } = period;

        const time = formatTime(dt_txt);
        const iconCode = weather[0].icon;
        const iconImageUrl = `![icon|16](https://openweathermap.org/img/wn/${iconCode}@2x.png)`; // 画像URLを生成
        const description = getJapaneseDescription(weather[0].description);
        const paddedDesc = padJapanese(description, 6);
        const temperature = `${Math.round(temp - 273.15)}°C`;
        const rainChance = `${Math.round(pop * 100)}`;
        const precipitation = rain ? `${rain["3h"].toFixed(1)}` : "0.0";
        const windSpeed = `${Math.round(speed * 3.6)}`;
        //let compassSector = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N"];
        let compassSector = [
            "⬆️",
            "↗️",
            "↗️",
            "↗️",
            "➡️",
            "↘️",
            "↘️",
            "↘️",
            "⬇️",
            "↙️",
            "↙️",
            "↙️",
            "⬅️",
            "↖️",
            "↖️",
            "↖️",
            "⬆️",
        ];
        const windDirection = compassSector[(deg / 22.5).toFixed(0)];

        return `${time} | ${iconImageUrl} ${paddedDesc} | ${temperature.padStart(
            4
        )} | ${rainChance.padStart(3)}% | ${precipitation.padStart(
            4
        )}mm | ${pressure}hPa | ${windDirection} ${windSpeed.padStart(
            2
        )}km/h | ${humidity.toString().padStart(3)}%`;
    });

    return `${city}の天気 | 🌅${sunrise} / 🌇${sunset}\n${formattedData.join(
        "\n"
    )}`;
};

list = {
    "東京 新宿": "160-0022",
    "大阪 梅田": "530-0001",
    "京都 京都駅": "600-8216",
    "仙台 青葉区": "980-0803",
    "福岡 博多": "812-0012",
    "名古屋 栄": "460-0008",
    "横浜 みなとみらい": "220-0012",
};
k = Object.keys(list);
x = await tp.system.suggester(k, k);
zipCode = list[x]; // x は選択されたキー(例: "東京 新宿")

const appid = // 取得した自分のapiを入力
const response = await fetch(
    `https://api.openweathermap.org/data/2.5/forecast?zip=${zipCode},JP&appid=${appid}`
);

const weatherData = await response.json();
// formatWeatherData を呼び出す際に、選択されたキー x を渡す
const result = formatWeatherData(weatherData, x);
-%>
<% result %>

-----------------------------------

## Diary:<% moment(tp.file.title).format("YYYY-MM-DD (ddd)") %>

-

## Memo

-

以下の部分を編集して、お好みの住所を追加・変更できます:

list = {
    "東京 新宿": "160-0022",
    "大阪 梅田": "530-0001",
    "京都 京都駅": "600-8216",
    "仙台 青葉区": "980-0803",
    "福岡 博多": "812-0012",
    "名古屋 栄": "460-0008",
    "横浜 みなとみらい": "220-0012",
    // ここに新しい住所を追加
    "広島 中区": "730-0000",
    "沖縄 那覇": "900-0000",
};

住所によっては実行時エラーが出る場合があります。その場合は実行できる近い住所を指定してください。

また、任意のタイミング・箇所で天気情報を取得・インサートできるように、天気情報機能だけを抽出したTemplaterファイルも作ります。以下の内容をtemplate/templater内にmd形式で保存してください。

天気情報機能抽出Templaterファイル(ほぼ同じ内容なので格納)
weather_insert.md.md
<%*
const formatTime = (dateTimeStr) => {
    const date = new Date(dateTimeStr);
    return date.toLocaleTimeString("ja-JP", {
        hour: "2-digit",
        minute: "2-digit",
        hour12: false,
    });
};

const padJapanese = (str, length) => {
    const padding = " ".repeat(Math.max(0, length - str.length));
    return str + padding;
};

const getJapaneseDescription = (description) => {
    const descMap = {
        "clear sky": "快晴",
        "few clouds": "晴れ",
        "scattered clouds": "晴れ時々曇り",
        "broken clouds": "所により曇り",
        "overcast clouds": "曇り",
        "light rain": "小雨",
        "shower rain": "霧雨",
        "moderate rain": "雨",
        thunderstorm: "雷雨",
        "heavy rain": "大雨",
        snow: "雪",
        "light snow": "小雪",
        mist: "霧",
        "thunderstorm with light rain": "弱い雨を伴う雷雨",
        "thunderstorm with rain": "雨を伴う雷雨",
        "thunderstorm with heavy rain": "強い雨を伴う雷雨",
        "light thunderstorm": "弱い雷雨",
        "heavy thunderstorm": "強い雷雨",
        "ragged thunderstorm": "断続的な雷雨",
        "thunderstorm with light drizzle": "弱い霧雨を伴う雷雨",
        "thunderstorm with drizzle": "霧雨を伴う雷雨",
        "thunderstorm with heavy drizzle": "強い霧雨を伴う雷雨",
        "light intensity drizzle": "弱い霧雨",
        drizzle: "霧雨",
        "heavy intensity drizzle": "強い霧雨",
        "light intensity drizzle rain": "弱い霧雨混じりの雨",
        "drizzle rain": "霧雨混じりの雨",
        "heavy intensity drizzle rain": "強い霧雨混じりの雨",
        "shower rain and drizzle": "霧雨を伴うにわか雨",
        "heavy shower rain and drizzle": "強い霧雨を伴うにわか雨",
        "shower drizzle": "にわか霧雨",
        rain: "雨",
        "heavy intensity rain": "激しい雨",
        "very heavy rain": "非常に強い雨",
        "extreme rain": "猛烈な雨",
        "freezing rain": "凍雨",
        "light intensity shower rain": "弱いにわか雨",
        "heavy intensity shower rain": "強いにわか雨",
        "ragged shower rain": "断続的なにわか雨",
        "heavy snow": "大雪",
        sleet: "みぞれ",
        "light shower sleet": "弱いにわかみぞれ",
        "shower sleet": "にわかみぞれ",
        "light rain and snow": "弱い雨雪",
        "rain and snow": "雨雪",
        "light shower snow": "弱いにわか雪",
        "shower snow": "にわか雪",
        "heavy shower snow": "強いにわか雪",
        smoke: "煙",
        haze: "もや",
        "sand/dust whirls": "砂塵旋風",
        fog: "濃霧",
        sand: "砂",
        dust: "粉塵",
        "volcanic ash": "火山灰",
        squalls: "スコール",
        tornado: "竜巻",
    };
    return descMap[description] || description;
};

const convertTimestamp = (timestamp) => {
    const date = new Date(timestamp * 1000);
    const hours = date.getHours().toString().padStart(2, "0");
    const minutes = date.getMinutes().toString().padStart(2, "0");
    return `${hours}:${minutes}`;
};

// formatWeatherData 関数に selectedCityName 引数を追加
const formatWeatherData = (weatherData, selectedCityName) => {
    // weatherData.city.name ではなく selectedCityName を使用
    const city = selectedCityName;
    const { list } = weatherData;

    const sunrise = convertTimestamp(weatherData.city.sunrise); // 日の出時刻
    const sunset = convertTimestamp(weatherData.city.sunset); // 日の入時刻

    const formattedData = list.slice(0, 8).map((period) => {
        const {
            dt_txt,
            weather,
            main: { temp, pressure, humidity },
            pop,
            rain,
            wind: { speed, deg },
        } = period;

        const time = formatTime(dt_txt);
        const iconCode = weather[0].icon;
        const iconImageUrl = `![icon|16](https://openweathermap.org/img/wn/${iconCode}@2x.png)`; // 画像URLを生成
        const description = getJapaneseDescription(weather[0].description);
        const paddedDesc = padJapanese(description, 6);
        const temperature = `${Math.round(temp - 273.15)}°C`;
        const rainChance = `${Math.round(pop * 100)}`;
        const precipitation = rain ? `${rain["3h"].toFixed(1)}` : "0.0";
        const windSpeed = `${Math.round(speed * 3.6)}`;
        //let compassSector = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N"];
        let compassSector = [
            "⬆️",
            "↗️",
            "↗️",
            "↗️",
            "➡️",
            "↘️",
            "↘️",
            "↘️",
            "⬇️",
            "↙️",
            "↙️",
            "↙️",
            "⬅️",
            "↖️",
            "↖️",
            "↖️",
            "⬆️",
        ];
        const windDirection = compassSector[(deg / 22.5).toFixed(0)];

        return `${time} | ${iconImageUrl} ${paddedDesc} | ${temperature.padStart(
            4
        )} | ${rainChance.padStart(3)}% | ${precipitation.padStart(
            4
        )}mm | ${pressure}hPa | ${windDirection} ${windSpeed.padStart(
            2
        )}km/h | ${humidity.toString().padStart(3)}%`;
    });

    return `${city}の天気 | 🌅${sunrise} / 🌇${sunset}\n${formattedData.join(
        "\n"
    )}`;
};

list = {
    "東京 新宿": "160-0022",
    "大阪 梅田": "530-0001",
    "京都 京都駅": "600-8216",
    "仙台 青葉区": "980-0803",
    "福岡 博多": "812-0012",
    "名古屋 栄": "460-0008",
    "横浜 みなとみらい": "220-0012",
};
k = Object.keys(list);
// tp.system.suggester の結果を x に格納
x = await tp.system.suggester(k, k);
zipCode = list[x]; // x は選択されたキー(例: "津田沼")

console.log(zipCode); // 郵便番号を確認

const appid = // 取得した自分のapiを入力
const response = await fetch(
    `https://api.openweathermap.org/data/2.5/forecast?zip=${zipCode},JP&appid=${appid}`
);

const weatherData = await response.json();
// formatWeatherData を呼び出す際に、選択されたキー x を渡す
return formatWeatherData(weatherData, x);
-%>


使用方法

デイリーノートでの使用

  1. 新しいデイリーノートを作成
  2. テンプレート実行時に住所選択ダイアログが表示される
  3. 希望の住所を選択
  4. 天気情報が自動挿入されたデイリーノートが開く

image.png

任意タイミングでの使用

  1. コマンドパレット(WindowsならCtrl + P)を開く
  2. Templater: Insert template/templater/weather_insert.md」を選択
  3. 住所選択ダイアログが表示される
  4. 希望の住所を選択
  5. 天気情報が挿入される

注意事項

  • OpenWeatherMap APIキーは実際のものに置き換えてください
  • 郵便番号は日本の形式(7桁)で入力してください
  • インターネット接続が必要です
    • オフラインでもデイリーノートを作成したいなら、デイリーノートから天気情報取得コード部分は消して、ネット接続があるときにweather_insert.mdでインサートするような運用にすると良いです

まとめ

今回の改善により、以下の機能が実現されました:

  1. 場所選択機能: 実行時に複数の住所から選択可能
  2. 柔軟な使用: デイリーノート作成時以外でも天気取得可能

素敵なObsidianライフを🖖。

1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?