はじめに
この記事は以下の記事の続編です:
前回の記事では、デイリーノート作成時に天気情報を自動取得する仕組みを構築しましたが、場所を選択できなかったという課題がありました。これでは異なる場所で使用している複数端末連携時や、ノートPCを外部に持ち出した際に、実行時の場所の天気を選べずに不便でした。
今回の記事では、以下の画像のようにあらかじめコードに埋め込んだ住所を実行時に提案・選択することで、複数の住所での天気情報を取得できるデイリーノートを提案します。
▼
必要な環境
- Obsidian
- Templater プラグイン
- OpenWeatherMap API キー(取得方法は前回記事を参照)
実装方法
1. デイリーノートテンプレートの作成
すでに作ってあるdailynote_temp.md
の内容を以下のコードに置き換えてください。
(またdailynoteを作っていないなら、頑張らない最低限のObsidian環境構築 - Qiitaなどの内容を参考にして作ってください)
---
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 = ``; // 画像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ファイル(ほぼ同じ内容なので格納)
<%*
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 = ``; // 画像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);
-%>
使用方法
デイリーノートでの使用
- 新しいデイリーノートを作成
- テンプレート実行時に住所選択ダイアログが表示される
- 希望の住所を選択
- 天気情報が自動挿入されたデイリーノートが開く
任意タイミングでの使用
- コマンドパレット(Windowsなら
Ctrl + P
)を開く - 「
Templater: Insert template/templater/weather_insert.md
」を選択 - 住所選択ダイアログが表示される
- 希望の住所を選択
- 天気情報が挿入される
注意事項
- OpenWeatherMap APIキーは実際のものに置き換えてください
- 郵便番号は日本の形式(7桁)で入力してください
- インターネット接続が必要です
- オフラインでもデイリーノートを作成したいなら、デイリーノートから天気情報取得コード部分は消して、ネット接続があるときに
weather_insert.md
でインサートするような運用にすると良いです
- オフラインでもデイリーノートを作成したいなら、デイリーノートから天気情報取得コード部分は消して、ネット接続があるときに
まとめ
今回の改善により、以下の機能が実現されました:
- 場所選択機能: 実行時に複数の住所から選択可能
- 柔軟な使用: デイリーノート作成時以外でも天気取得可能
素敵なObsidianライフを🖖。