Skip to content

Commit

Permalink
天気予報の地域設定を実装
Browse files Browse the repository at this point in the history
  • Loading branch information
CoreNion committed Dec 15, 2023
1 parent 5bbbfc1 commit db47087
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 22 deletions.
14 changes: 14 additions & 0 deletions app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ onMounted(async () => {
console.warn(e);
}
// 天気の各設定コードを取得
const wideRegion = localStorage.getItem('weatherWideRegion');
if (wideRegion != null) {
weatherWideRegionNumber().value = wideRegion;
}
const officeNumber = localStorage.getItem('weatherOfficeNumber');
if (officeNumber != null) {
weatherOfficeNumber().value = officeNumber;
}
const areaNumber = localStorage.getItem('weatherAreaNumber');
if (areaNumber != null) {
weatherAreaNumber().value = areaNumber;
}
// OPFSからチャイム音源とアラート音源を読み込む
const opfsRoot = await navigator.storage.getDirectory();
Expand Down
2 changes: 2 additions & 0 deletions components/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ onMounted(() => {
<option value="rpi">Raspberry Pi</option>
</select>
</label>

<WeatherSetting></WeatherSetting>
</p>

<div class="divider"></div>
Expand Down
57 changes: 35 additions & 22 deletions components/status.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,37 @@ async function refleshStatus() {
sensorInterval?.resume();
}
// 10分ごとに天気を更新
async function refleshWeather() {
const djs = dayjs();
// クライアントサイドのみで実行
onMounted(async () => {
// 2秒ごとに温度を更新 (1秒だとシリアル通信が追いつかない?
await refleshStatus();
sensorInterval = useIntervalFn(refleshStatus, 2000);
// 10分ごとに天気を更新
await refleshWeather();
useIntervalFn(refleshWeather, 10000);
});
</script>

<script lang="ts">
/**
* 表示されている天気を更新する
*/
export async function refleshWeather() {
// 現在の気圧
const pressureState = pressure();
// 現在の外気温
const outTmpState = outTmp();
// 天気
const weatherState = weather();
// アメダスの番号 (44132: 東京)
const amedasNumber = 44132;
// office番号 (東京地方)
const officeNumber = 130000;
const areaNumber = 130010;
const weatherOfficeNumberState = weatherOfficeNumber();
// 最新の気象データの時刻を取得
const latestTime = dayjs(await $fetch<string>('https://www.jma.go.jp/bosai/amedas/data/latest_time.txt'));
const latestTime = dayjs(await $fetch < string > ('https://www.jma.go.jp/bosai/amedas/data/latest_time.txt'));
// 最新のJSONファイル名 (3時間ごとに別ファイル)
const latestJsonName = `${latestTime.format("YYYYMMDD")}_${(Math.floor(latestTime.hour() / 3) * 3).toString().padStart(2, "0")}.json`;
Expand All @@ -140,7 +159,7 @@ async function refleshWeather() {
// アメダスのデータ
$fetch(`https://www.jma.go.jp/bosai/amedas/data/point/${amedasNumber}/${latestJsonName}`),
// 天気予報のデータ
$fetch(`https://www.jma.go.jp/bosai/forecast/data/forecast/${officeNumber}.json`)
$fetch(`https://www.jma.go.jp/bosai/forecast/data/forecast/${weatherOfficeNumberState.value}.json`)
]).catch((e) => {
console.error(e);
return;
Expand All @@ -163,20 +182,13 @@ async function refleshWeather() {
const forecastData = Array(data[1]);
// 今日の天気を取得
const todayWeatherCode = Object(forecastData[0])[0].timeSeries[0].areas[0].weatherCodes[0];
const todayForecast = Object(forecastData[0])[0].timeSeries[0].areas as Array<any>;
// 設定されている地域のインデックスを取得
const areaIndex = todayForecast.findIndex((value) => value.area.code == weatherAreaNumber().value);
const todayWeatherCode = todayForecast[areaIndex].weatherCodes[0];
weatherState.value = weather2str(todayWeatherCode);
}
// クライアントサイドのみで実行
onMounted(async () => {
// 2秒ごとに温度を更新 (1秒だとシリアル通信が追いつかない?
await refleshStatus();
sensorInterval = useIntervalFn(refleshStatus, 2000);
// 10分ごとに天気を更新
await refleshWeather();
useIntervalFn(refleshWeather, 600000);
});
</script>

<template>
Expand All @@ -190,13 +202,13 @@ onMounted(async () => {
</div>

<div v-else class="stat-value text-[4.6vw]">{{ roomTmpState != null ? roomTmpState.toFixed(1) : "-" }}
<Icon name="uil:celsius" size="4vw" />
<Icon name="uil:celsius" size="4vw" />
</div>
</div>
<div class="stat">
<div class="stat-title text-[3vw]">気圧*</div>
<div class="stat-value leading-none flex flex-col">
<span class="text-[4.6vw]">{{ pressureState != null ? pressureState.toFixed(1): "-" }}</span>
<span class="text-[4.6vw]">{{ pressureState != null ? pressureState.toFixed(1) : "-" }}</span>
<span class="text-[3vw]">hPa</span>
</div>
</div>
Expand All @@ -208,7 +220,8 @@ onMounted(async () => {
</div>
<div class="stat">
<div class="stat-title text-[3vw]">天気*</div>
<Icon :name="weatherState != null ? weatherState : 'system-uicons:cloud-disconnect'" class="stat-value m-auto leading-none" size="5vw" />
<Icon :name="weatherState != null ? weatherState : 'system-uicons:cloud-disconnect'"
class="stat-value m-auto leading-none" size="5vw" />
</div>

<div class="stat m-auto gap-2">
Expand Down
123 changes: 123 additions & 0 deletions components/weather-setting.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<script setup lang="ts">
import { refleshWeather } from './status.vue';
import { regionCodeName } from '#imports';
// 広域地方 (コード)
const selectedWideRegion = weatherWideRegionNumber();
// 地方 (コード)
const selectedLocalRegion = weatherOfficeNumber();
// 地域 (コード)
const selectedRegion = weatherAreaNumber();
// 広域地方の選択肢
const wideRegionOptions = useState<Array<regionCodeName>>(() => []);
// 地方の選択肢
const localRegionOptions = useState<Array<regionCodeName>>(() => []);
// 地域の選択肢
const regionOptions = useState<Array<regionCodeName>>(() => []);
let refleshLocalRegion: (e: Event) => void;
let refleshRegion: (e: Event) => void;
let setAreaNumber: (e: Event) => void;
onMounted(async () => {
// エリア情報を取得
const areas = (await $fetch<string>('https://www.jma.go.jp/bosai/common/const/area.json'));
// 広域地方, 地方, 地域のコードを取得
const regions = Object(areas)["centers"];
const offices = Object(areas)["offices"];
const class10s = Object(areas)["class10s"];
// 広域地方の選択肢を作成 (コード, 地方名)
wideRegionOptions.value = Object.entries(regions).map(([key, value]) => {
return new regionCodeName(key, (value as any)["name"])
});
// 地方/地域の選択肢を作成
localRegionOptions.value = getWeatherAreaCodes(regions, selectedWideRegion.value, offices);
regionOptions.value = getWeatherAreaCodes(offices, selectedLocalRegion.value, class10s);
// 地方の選択肢を更新する関数
refleshLocalRegion = (e: Event) => {
if (!(e.target instanceof HTMLSelectElement)) return;
selectedWideRegion.value = e.target.value;
regionOptions.value = [];
// 地方内の地域のコードを取得
localRegionOptions.value = getWeatherAreaCodes(regions, selectedWideRegion.value, offices);
}
// 地域の選択肢を更新する関数
refleshRegion = (e: Event) => {
if (!(e.target instanceof HTMLSelectElement)) return;
selectedLocalRegion.value = e.target.value;
// 地域内の地域のコードを取得
regionOptions.value = getWeatherAreaCodes(offices, selectedLocalRegion.value, class10s);
}
// 地域番号などを更新する関数
setAreaNumber = async (e: Event) => {
if (!(e.target instanceof HTMLSelectElement)) return;
// メモリ上
weatherWideRegionNumber().value = selectedWideRegion.value!;
weatherOfficeNumber().value = selectedLocalRegion.value!;
weatherAreaNumber().value = e.target.value;
// ストレージ
localStorage.setItem('weatherWideRegion', weatherWideRegionNumber().value);
localStorage.setItem('weatherOfficeNumber', weatherOfficeNumber().value);
localStorage.setItem('weatherAreaNumber', weatherAreaNumber().value);
// 天気を更新
await refleshWeather();
}
});
</script>

<template>
<button class="btn btn-neutral min-w-full mt-3" onclick="weatherSettingsModal.showModal()">天気設定</button>
<dialog id="weatherSettingsModal" class="modal">
<div class="modal-box">
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
</form>

<h3 class="font-bold text-lg">天気設定</h3>

<div class="divider"></div>

<h3 class="font-bold text-lg">予報の地域設定</h3>
<p class="my-3">
<label class="label">
<span class="label-text">広域地方</span>
<select class="select select-bordered w-full max-w-xs" v-model="selectedWideRegion"
@change="refleshLocalRegion">
<option disabled selected>地方... (表示されない場合は読み込み中かオフラインです)</option>
<option v-for="region of wideRegionOptions" :value="region.code">{{ region.name }}</option>
</select>
</label>

<label class="label">
<span class="label-text">都道府県/地方</span>
<select class="select select-bordered w-full max-w-xs" v-model="selectedLocalRegion" @change="refleshRegion">
<option disabled selected>都道府県/地方を選択... (表示されない場合は読み込み中かオフラインです)</option>
<option v-for="region of localRegionOptions" :value="region.code">{{ region.name }}
</option>
</select>
</label>

<label class="label">
<span class="label-text">地域</span>
<select class="select select-bordered w-full max-w-xs" v-model="selectedRegion" @change="setAreaNumber">
<option disabled selected>地域を選択... (表示されない場合は読み込み中かオフラインです)</option>
<option v-for="region of regionOptions" :value="region.code">{{ region.name }}
</option>
</select>
</label>
</p>
</div>
</dialog>
</template>
7 changes: 7 additions & 0 deletions composables/states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ export const isChimeEnabled = () => useState<boolean>('isChimeEnabled', () => fa
// 予鈴の有効/無効
export const isPreChimeEnabled = () => useState<boolean>('isPreChimeEnabled', () => false);

// 天気の広域地方番号
export const weatherWideRegionNumber = () => useState<string>('weatherWideRegionNumber', () => "010300");
// 天気のoffice番号
export const weatherOfficeNumber = () => useState<string>('weatherOfficeNumber', () => "130000");
// 天気の地域番号
export const weatherAreaNumber = () => useState<string>('weatherAreaNumber', () => "130010");

// 現在の室温
export const roomTmp = () => useState<Number | null>('roomTemp', () => null);
// 現在の気圧
Expand Down
27 changes: 27 additions & 0 deletions utils/weather-code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* 気象庁の地域番号と地域名のクラス
*/
export class regionCodeName {
code: string;
name: string;

constructor(code: string, name: string) {
this.code = code;
this.name = name;
}
}

/**
* 気象庁の地域番号で親コードから子コードの番号と地域名のリストを取得する
* @param parentsObj 親要素のオブジェクト (例:centers)
* @param parentCode 親要素のコード (例:010300[関東地方])
* @param childrenObj 子要素のオブジェクト (例:offices)
* @returns 子要素のコードと地域名のリスト (例:{130000: "東京都"}, 140000: "神奈川県", ...])
*/
export function getWeatherAreaCodes(parentsObj:any, parentCode:string, childrenObj:any) {
// 親要素内の地域のコードを取得する
const childrenCodes = Object(parentsObj)[parentCode]["children"] as Array<string>;
return childrenCodes.map((code) => {
return new regionCodeName(code, Object(childrenObj)[code]["name"]);
});
}

0 comments on commit db47087

Please sign in to comment.