Files

759 lines
18 KiB
TypeScript
Raw Permalink Normal View History

import type { CityDetail } from "@/lib/dashboard-types";
2026-04-22 23:43:27 +08:00
import { normalizeObservationSourceLabel } from "@/lib/source-labels";
export type OfficialSourceLink = {
label: string;
href: string;
kind: "agency" | "airport" | "metar";
};
function buildJmaAmedasTenMinuteUrl(
localDate?: string | null,
options?: {
blockNo?: string;
precNo?: string;
},
) {
const blockNo = String(options?.blockNo || "0371").trim() || "0371";
const precNo = String(options?.precNo || "44").trim() || "44";
const match = String(localDate || "").match(/^(\d{4})-(\d{2})-(\d{2})$/);
const now = new Date();
const year = match?.[1] || String(now.getFullYear());
const month = match?.[2] || String(now.getMonth() + 1).padStart(2, "0");
const day = match?.[3] || String(now.getDate()).padStart(2, "0");
return `https://www.data.jma.go.jp/stats/etrn/view/10min_a1.php?prec_no=${encodeURIComponent(precNo)}&block_no=${encodeURIComponent(blockNo)}&year=${encodeURIComponent(year)}&month=${encodeURIComponent(month)}&day=${encodeURIComponent(day)}&view=`;
}
const CITY_SPECIFIC_SOURCES: Record<string, OfficialSourceLink[]> = {
singapore: [
{
label: "MSS 官方天气",
href: "https://www.weather.gov.sg/",
kind: "agency",
},
{
label: "樟宜机场",
href: "https://www.changiairport.com/",
kind: "airport",
},
{
label: "WSSS METAR",
href: "https://aviationweather.gov/data/metar/?id=WSSS&decoded=1&taf=1",
kind: "metar",
},
],
wellington: [
{
label: "MetService",
href: "https://www.metservice.com/",
kind: "agency",
},
{
label: "Wellington Airport",
href: "https://www.wellingtonairport.co.nz/",
kind: "airport",
},
{
label: "NZWN METAR",
href: "https://aviationweather.gov/data/metar/?id=NZWN&decoded=1&taf=1",
kind: "metar",
},
],
"hong kong": [
{
label: "香港天文台",
href: "https://www.hko.gov.hk/en/index.html",
kind: "agency",
},
{
label: "香港国际机场",
href: "https://www.hongkongairport.com/",
kind: "airport",
},
{
label: "VHHH METAR",
href: "https://aviationweather.gov/data/metar/?id=VHHH&decoded=1&taf=1",
kind: "metar",
},
{
label: "深圳站(HKO",
href: "https://www.hko.gov.hk/sc/wxinfo/ts/index.htm",
kind: "agency",
},
],
"shenzhen": [
{
label: "香港天文台",
href: "https://www.hko.gov.hk/en/index.html",
kind: "agency",
},
{
label: "深圳站(HKO",
href: "https://www.hko.gov.hk/sc/wxinfo/ts/index.htm",
kind: "airport",
},
],
taipei: [
{
label: "Wunderground RCSS",
href: "https://www.wunderground.com/history/daily/tw/taipei/RCSS",
kind: "agency",
},
{
label: "台北松山机场",
href: "https://www.tsa.gov.tw/?lang=en",
kind: "airport",
},
{
label: "RCSS METAR",
href: "https://aviationweather.gov/data/metar/?id=RCSS&decoded=1&taf=1",
kind: "metar",
},
],
busan: [
{
label: "Wunderground RKPK",
href: "https://www.wunderground.com/history/daily/kr/busan/RKPK",
kind: "agency",
},
{
label: "金海国际机场",
href: "https://www.airport.co.kr/gimhaeeng/index.do",
kind: "airport",
},
{
label: "RKPK METAR",
href: "https://aviationweather.gov/data/metar/?id=RKPK&decoded=1&taf=1",
kind: "metar",
},
],
manila: [
{
label: "Wunderground RPLL",
href: "https://www.wunderground.com/history/daily/ph/manila/RPLL",
kind: "agency",
},
{
label: "Ninoy Aquino International Airport",
href: "https://www.newnaia.com.ph/",
kind: "airport",
},
{
label: "RPLL METAR",
href: "https://aviationweather.gov/data/metar/?id=RPLL&decoded=1&taf=1",
kind: "metar",
},
],
karachi: [
{
label: "Wunderground OPKC",
href: "https://www.wunderground.com/history/daily/pk/karachi/OPKC",
kind: "agency",
},
{
label: "Jinnah International Airport",
href: "https://www.caapakistan.com.pk/",
kind: "airport",
},
{
label: "OPKC METAR",
href: "https://aviationweather.gov/data/metar/?id=OPKC&decoded=1&taf=1",
kind: "metar",
},
],
istanbul: [
2026-03-30 19:03:13 +08:00
{
label: "MGM",
href: "https://www.mgm.gov.tr/?il=Istanbul&ilce=Istanbul%20Havalimani",
2026-03-30 19:03:13 +08:00
kind: "agency",
},
{
label: "NOAA LTFM Timeseries",
href: "https://www.weather.gov/wrh/timeseries?site=LTFM",
kind: "agency",
},
{
label: "Istanbul Airport",
href: "https://www.istairport.com/en",
kind: "airport",
},
{
label: "LTFM METAR",
href: "https://aviationweather.gov/data/metar/?id=LTFM&decoded=1&taf=1",
kind: "metar",
},
],
2026-03-30 19:03:13 +08:00
moscow: [
{
label: "NOAA UUWW Timeseries",
href: "https://www.weather.gov/wrh/timeseries?site=UUWW",
kind: "agency",
},
{
label: "Vnukovo International Airport",
href: "https://vnukovo.ru/en/",
kind: "airport",
},
{
label: "UUWW METAR",
href: "https://metar-taf.com/UUWW",
kind: "metar",
},
],
london: [
{
label: "Met Office",
href: "https://www.metoffice.gov.uk/",
kind: "agency",
},
{
label: "EGLC METAR",
href: "https://aviationweather.gov/data/metar/?id=EGLC&decoded=1&taf=1",
kind: "metar",
},
],
"new york": [
{
label: "NWS",
href: "https://www.weather.gov/",
kind: "agency",
},
{
label: "KLGA METAR",
href: "https://aviationweather.gov/data/metar/?id=KLGA&decoded=1&taf=1",
kind: "metar",
},
],
"los angeles": [
{
label: "NWS Los Angeles/Oxnard",
href: "https://www.weather.gov/lox/",
kind: "agency",
},
{
label: "LAX Airport",
href: "https://www.flylax.com/",
kind: "airport",
},
{
label: "KLAX METAR",
href: "https://aviationweather.gov/data/metar/?id=KLAX&decoded=1&taf=1",
kind: "metar",
},
],
"san francisco": [
{
label: "NWS San Francisco Bay Area",
href: "https://www.weather.gov/mtr/",
kind: "agency",
},
{
label: "SFO Airport",
href: "https://www.flysfo.com/",
kind: "airport",
},
{
label: "KSFO METAR",
href: "https://aviationweather.gov/data/metar/?id=KSFO&decoded=1&taf=1",
kind: "metar",
},
],
denver: [
{
label: "NWS Denver/Boulder",
href: "https://www.weather.gov/bou/",
kind: "agency",
},
{
label: "Buckley Space Force Base",
href: "https://www.buckley.spaceforce.mil/",
kind: "airport",
},
{
label: "KBKF METAR",
href: "https://aviationweather.gov/data/metar/?id=KBKF&decoded=1&taf=1",
kind: "metar",
},
],
austin: [
{
label: "NWS Austin/San Antonio",
href: "https://www.weather.gov/ewx/",
kind: "agency",
},
{
label: "Austin-Bergstrom Airport",
href: "https://www.austintexas.gov/airport",
kind: "airport",
},
{
label: "KAUS METAR",
href: "https://aviationweather.gov/data/metar/?id=KAUS&decoded=1&taf=1",
kind: "metar",
},
],
houston: [
{
label: "NWS Houston/Galveston",
href: "https://www.weather.gov/hgx/",
kind: "agency",
},
{
label: "William P. Hobby Airport",
href: "https://www.fly2houston.com/hobby",
kind: "airport",
},
{
label: "KHOU METAR",
href: "https://aviationweather.gov/data/metar/?id=KHOU&decoded=1&taf=1",
kind: "metar",
},
],
"mexico city": [
{
label: "SMN",
href: "https://smn.conagua.gob.mx/",
kind: "agency",
},
{
label: "AICM",
href: "https://www.aicm.com.mx/",
kind: "airport",
},
{
label: "MMMX METAR",
href: "https://aviationweather.gov/data/metar/?id=MMMX&decoded=1&taf=1",
kind: "metar",
},
],
ankara: [
{
label: "MGM",
href: "https://www.mgm.gov.tr/?il=Ankara&ilce=Esenboga",
kind: "agency",
},
{
label: "LTAC METAR",
href: "https://aviationweather.gov/data/metar/?id=LTAC&decoded=1&taf=1",
kind: "metar",
},
],
paris: [
{
label: "Météo-France",
href: "https://meteofrance.com/",
kind: "agency",
},
{
2026-04-19 14:25:38 +08:00
label: "LFPB METAR",
href: "https://aviationweather.gov/data/metar/?id=LFPB&decoded=1&taf=1",
kind: "metar",
},
],
seoul: [
{
label: "KMA",
href: "https://www.weather.go.kr/",
kind: "agency",
},
{
label: "RKSI METAR",
href: "https://aviationweather.gov/data/metar/?id=RKSI&decoded=1&taf=1",
kind: "metar",
},
],
shanghai: [
{
label: "NMC 浦东天气",
href: "https://m.nmc.cn/publish/forecast/ASH/pudong.html",
kind: "agency",
},
{
label: "上海浦东国际机场",
href: "https://www.shanghai-airport.com/",
kind: "airport",
},
{
label: "ZSPD METAR",
href: "https://aviationweather.gov/data/metar/?id=ZSPD&decoded=1&taf=1",
kind: "metar",
},
],
tokyo: [
{
label: "JMA 羽田10分钟实况",
href: "",
kind: "agency",
},
{
label: "RJTT METAR",
href: "https://aviationweather.gov/data/metar/?id=RJTT&decoded=1&taf=1",
kind: "metar",
},
],
"tel aviv": [
{
label: "IMS",
href: "https://ims.gov.il/en",
kind: "agency",
},
{
label: "LLBG METAR",
href: "https://aviationweather.gov/data/metar/?id=LLBG&decoded=1&taf=1",
kind: "metar",
},
],
toronto: [
{
label: "Environment Canada",
href: "https://weather.gc.ca/",
kind: "agency",
},
{
label: "CYYZ METAR",
href: "https://aviationweather.gov/data/metar/?id=CYYZ&decoded=1&taf=1",
kind: "metar",
},
],
"buenos aires": [
{
label: "SMN",
href: "https://www.smn.gob.ar/",
kind: "agency",
},
{
label: "SAEZ METAR",
href: "https://aviationweather.gov/data/metar/?id=SAEZ&decoded=1&taf=1",
kind: "metar",
},
],
chicago: [
{
label: "NWS Chicago",
href: "https://www.weather.gov/lot/",
kind: "agency",
},
{
label: "KORD METAR",
href: "https://aviationweather.gov/data/metar/?id=KORD&decoded=1&taf=1",
kind: "metar",
},
],
dallas: [
{
label: "NWS Fort Worth",
href: "https://www.weather.gov/fwd/",
kind: "agency",
},
{
label: "KDAL METAR",
href: "https://aviationweather.gov/data/metar/?id=KDAL&decoded=1&taf=1",
kind: "metar",
},
],
miami: [
{
label: "NWS Miami",
href: "https://www.weather.gov/mfl/",
kind: "agency",
},
{
label: "KMIA METAR",
href: "https://aviationweather.gov/data/metar/?id=KMIA&decoded=1&taf=1",
kind: "metar",
},
],
atlanta: [
{
label: "NWS Peachtree City",
href: "https://www.weather.gov/ffc/",
kind: "agency",
},
{
label: "KATL METAR",
href: "https://aviationweather.gov/data/metar/?id=KATL&decoded=1&taf=1",
kind: "metar",
},
],
seattle: [
{
label: "NWS Seattle",
href: "https://www.weather.gov/sew/",
kind: "agency",
},
{
label: "KSEA METAR",
href: "https://aviationweather.gov/data/metar/?id=KSEA&decoded=1&taf=1",
kind: "metar",
},
],
lucknow: [
{
label: "IMD",
href: "https://mausam.imd.gov.in/",
kind: "agency",
},
{
label: "VILK METAR",
href: "https://aviationweather.gov/data/metar/?id=VILK&decoded=1&taf=1",
kind: "metar",
},
],
"sao paulo": [
{
label: "INMET",
href: "https://portal.inmet.gov.br/",
kind: "agency",
},
{
label: "SBGR METAR",
href: "https://aviationweather.gov/data/metar/?id=SBGR&decoded=1&taf=1",
kind: "metar",
},
],
munich: [
{
label: "DWD",
href: "https://www.dwd.de/EN/Home/home_node.html",
kind: "agency",
},
{
label: "EDDM METAR",
href: "https://aviationweather.gov/data/metar/?id=EDDM&decoded=1&taf=1",
kind: "metar",
},
],
milan: [
{
label: "MeteoAM",
href: "https://www.meteoam.it/en/home",
kind: "agency",
},
{
label: "LIMC METAR",
href: "https://aviationweather.gov/data/metar/?id=LIMC&decoded=1&taf=1",
kind: "metar",
},
],
warsaw: [
{
label: "IMGW",
href: "https://meteo.imgw.pl/",
kind: "agency",
},
{
label: "EPWA METAR",
href: "https://aviationweather.gov/data/metar/?id=EPWA&decoded=1&taf=1",
kind: "metar",
},
],
madrid: [
{
label: "AEMET",
href: "https://www.aemet.es/en/portada",
kind: "agency",
},
{
label: "LEMD METAR",
href: "https://aviationweather.gov/data/metar/?id=LEMD&decoded=1&taf=1",
kind: "metar",
},
],
chengdu: [
{
label: "NMC 双流天气",
href: "https://m.nmc.cn/publish/forecast/ASC/shuangliu.html",
kind: "agency",
},
{
label: "成都双流国际机场",
href: "https://www.cdairport.com/",
kind: "airport",
},
{
label: "ZUUU METAR",
href: "https://aviationweather.gov/data/metar/?id=ZUUU&decoded=1&taf=1",
kind: "metar",
},
],
chongqing: [
{
label: "NMC 渝北天气",
href: "https://m.nmc.cn/publish/forecast/ACQ/yubei.html",
kind: "agency",
},
{
label: "重庆江北国际机场",
href: "https://www.cqa.cn/",
kind: "airport",
},
{
label: "ZUCK METAR",
href: "https://aviationweather.gov/data/metar/?id=ZUCK&decoded=1&taf=1",
kind: "metar",
},
],
2026-04-28 07:08:33 +08:00
qingdao: [
{
label: "Wunderground ZSQD",
href: "https://www.wunderground.com/history/daily/cn/qingdao/ZSQD",
kind: "agency",
},
{
label: "青岛胶东国际机场",
href: "https://www.qdairport.com/",
kind: "airport",
},
{
label: "ZSQD METAR",
href: "https://aviationweather.gov/data/metar/?id=ZSQD&decoded=1&taf=1",
kind: "metar",
},
],
"kuala lumpur": [
{
label: "Wunderground WMKK",
href: "https://www.wunderground.com/history/daily/my/sepang-district/WMKK",
kind: "agency",
},
{
label: "吉隆坡国际机场",
href: "https://airports.malaysiaairports.com.my/klia",
kind: "airport",
},
{
label: "WMKK METAR",
href: "https://aviationweather.gov/data/metar/?id=WMKK&decoded=1&taf=1",
kind: "metar",
},
],
jakarta: [
{
label: "Wunderground WIHH",
href: "https://www.wunderground.com/history/daily/id/jakarta/WIHH",
kind: "agency",
},
{
label: "Halim Perdanakusuma Airport",
href: "https://www.angkasapura2.co.id/en/airport/read/HLP",
kind: "airport",
},
{
label: "WIHH METAR",
href: "https://aviationweather.gov/data/metar/?id=WIHH&decoded=1&taf=1",
kind: "metar",
},
],
helsinki: [
{
label: "Wunderground EFHK",
href: "https://www.wunderground.com/history/daily/fi/vantaa/EFHK",
kind: "agency",
},
{
label: "Helsinki Airport",
href: "https://www.finavia.fi/en/airports/helsinki-airport",
kind: "airport",
},
{
label: "EFHK METAR",
href: "https://aviationweather.gov/data/metar/?id=EFHK&decoded=1&taf=1",
kind: "metar",
},
],
amsterdam: [
{
label: "Wunderground EHAM",
href: "https://www.wunderground.com/history/daily/nl/schiphol/EHAM",
kind: "agency",
},
{
label: "Amsterdam Airport Schiphol",
href: "https://www.schiphol.nl/en/",
kind: "airport",
},
{
label: "EHAM METAR",
href: "https://aviationweather.gov/data/metar/?id=EHAM&decoded=1&taf=1",
kind: "metar",
},
],
"panama city": [
{
label: "Wunderground MPMG",
href: "https://www.wunderground.com/history/daily/pa/panama-city/MPMG",
kind: "agency",
},
{
label: "Marcos A. Gelabert Airport",
href: "https://www.aeropuertos.net/aeropuerto-internacional-marcos-a-gelabert/",
kind: "airport",
},
{
label: "MPMG METAR",
href: "https://aviationweather.gov/data/metar/?id=MPMG&decoded=1&taf=1",
kind: "metar",
},
],
beijing: [
{
label: "NMC 顺义天气",
href: "https://m.nmc.cn/publish/forecast/ABJ/shunyi.html",
kind: "agency",
},
{
label: "北京首都国际机场",
href: "https://www.bcia.com.cn/",
kind: "airport",
},
{
label: "ZBAA METAR",
href: "https://aviationweather.gov/data/metar/?id=ZBAA&decoded=1&taf=1",
kind: "metar",
},
],
wuhan: [
{
label: "NMC 武汉天气",
href: "https://m.nmc.cn/publish/forecast/AHB/wuhan.html",
kind: "agency",
},
{
label: "武汉天河国际机场",
href: "https://www.whairport.com/",
kind: "airport",
},
{
label: "ZHHH METAR",
href: "https://aviationweather.gov/data/metar/?id=ZHHH&decoded=1&taf=1",
kind: "metar",
},
],
};
export function getOfficialSourceLinks(detail: CityDetail): OfficialSourceLink[] {
const cityKey = String(detail.name || "").trim().toLowerCase();
const links = [...(CITY_SPECIFIC_SOURCES[cityKey] || [])].map((link) => {
if (cityKey === "tokyo" && link.kind === "agency" && link.label === "JMA 羽田10分钟实况") {
return {
...link,
href: buildJmaAmedasTenMinuteUrl(detail.local_date, {
blockNo: "0371",
precNo: "44",
}),
};
}
2026-04-22 23:43:27 +08:00
return {
...link,
label: normalizeObservationSourceLabel(link.label, link.label),
};
});
const seen = new Set<string>();
return links.filter((link) => {
const key = `${link.label}|${link.href}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}