FEAT: Added ability to query weatherservice API
This commit is contained in:
42
src/lib/api/types/metar.ts
Normal file
42
src/lib/api/types/metar.ts
Normal file
@ -0,0 +1,42 @@
|
||||
export type MetarCloud = {
|
||||
cover: string;
|
||||
base: number | null;
|
||||
};
|
||||
|
||||
export type Metar = {
|
||||
metar_id: number;
|
||||
icaoId: string;
|
||||
receiptTime: string;
|
||||
obsTime: number;
|
||||
reportTime: string;
|
||||
temp: number;
|
||||
dewp: number;
|
||||
wdir: number;
|
||||
wspd: number;
|
||||
wgst: number | null;
|
||||
visib: string;
|
||||
altim: number;
|
||||
slp: number | null;
|
||||
qcField: number;
|
||||
wxString: string | null;
|
||||
presTend: string | null;
|
||||
maxT: number | null;
|
||||
minT: number | null;
|
||||
maxT24: number | null;
|
||||
minT24: number | null;
|
||||
precip: number | null;
|
||||
pcp3hr: number | null;
|
||||
pcp6hr: number | null;
|
||||
pcp24hr: number | null;
|
||||
snow: number | null;
|
||||
vertVis: number | null;
|
||||
metarType: string;
|
||||
rawOb: string;
|
||||
mostRecent: number;
|
||||
lat: number;
|
||||
lon: number;
|
||||
elev: number;
|
||||
prior: number;
|
||||
name: string;
|
||||
clouds: MetarCloud[];
|
||||
};
|
16
src/lib/api/weatherservice.ts
Normal file
16
src/lib/api/weatherservice.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { Metar } from '$lib/api/types/metar';
|
||||
|
||||
export const aviationWeatherApi = {
|
||||
/**
|
||||
* Fetches METAR data for a given ICAO code.
|
||||
* @param icao - The ICAO code (e.g., "EGKK")
|
||||
* @returns A Promise resolving to a Metar object or null if not found.
|
||||
*/
|
||||
async fetchMetar(icao: string): Promise<Metar | null> {
|
||||
const url = `/api/metar/${encodeURIComponent(icao)}`;
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`Failed to fetch METAR: ${response.statusText}`);
|
||||
const data = await response.json();
|
||||
return data as Metar | null;
|
||||
}
|
||||
};
|
@ -10,7 +10,7 @@
|
||||
let icaoInput = $state<string>('');
|
||||
|
||||
$effect(() => {
|
||||
// Watch for changes to icaoInput and update the store if exactly 4 chars
|
||||
// Watch for changes to icaoInput and update the store if exactly 4 chars
|
||||
if (icaoInput.length === 4) {
|
||||
icao.set(icaoInput);
|
||||
} else {
|
||||
@ -18,8 +18,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
@ -30,7 +28,7 @@
|
||||
class="text-xxl flex w-full items-center justify-center gap-10 px-8 py-4 font-semibold text-green-600"
|
||||
>
|
||||
<a href="/" class="hover:underline">METAR ⛅</a>
|
||||
<a href="/taf" class="hover:underline">TAF ☔</a>
|
||||
<a href="/taf" class="hover:underline">TAF ☔</a>
|
||||
</nav>
|
||||
|
||||
<div class="container mx-auto flex flex-col items-center justify-center">
|
||||
@ -39,10 +37,7 @@
|
||||
<Card>
|
||||
<h2 slot="headerLeft">METARIUS</h2>
|
||||
<h2 slot="headerRight">Start Here</h2>
|
||||
<form
|
||||
slot="content"
|
||||
class="mx-auto flex w-full max-w-3xl flex-col gap-4 text-lg"
|
||||
>
|
||||
<form slot="content" class="mx-auto flex w-full max-w-3xl flex-col gap-4 text-lg">
|
||||
<div class="flex-space-between flex flex-col gap-10 md:flex-row">
|
||||
<div class="flex-1">
|
||||
<p>
|
||||
|
@ -5,6 +5,32 @@
|
||||
import Loading from '$lib/components/Loading.svelte';
|
||||
|
||||
import { icao } from '$lib/stores/icao';
|
||||
import { aviationWeatherApi } from '$lib/api/weatherservice';
|
||||
import type { Metar } from '$lib/api/types/metar'
|
||||
import { onDestroy } from 'svelte';
|
||||
|
||||
let metar: Metar | null = null;
|
||||
let loading = false;
|
||||
let error: string | null = null;
|
||||
|
||||
let unsubscribe = icao.subscribe(async (val) => {
|
||||
metar = null;
|
||||
error = null;
|
||||
if (val && val.length === 4) {
|
||||
loading = true;
|
||||
try {
|
||||
const result = await aviationWeatherApi.fetchMetar(val);
|
||||
metar = result;
|
||||
if (!metar) error = 'No METAR found for this ICAO code.';
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Unknown error';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => unsubscribe());
|
||||
</script>
|
||||
|
||||
<div style="display: none;">
|
||||
@ -22,8 +48,22 @@
|
||||
<h2 slot="headerLeft">METAR</h2>
|
||||
<h2 slot="headerRight">Results</h2>
|
||||
<div slot="content" class="flex h-96 w-full items-center justify-center">
|
||||
{#if $icao}
|
||||
<span class="text-3xl font-bold">ICAO: {$icao}</span>
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else if error}
|
||||
<span class="text-red-600 text-xl">{error}</span>
|
||||
{:else if metar}
|
||||
<div class="text-left text-lg space-y-2">
|
||||
<div><strong>ICAO:</strong> {metar.icaoId}</div>
|
||||
<div><strong>Airport:</strong> {metar.name}</div>
|
||||
<div><strong>Observed:</strong> {metar.reportTime}</div>
|
||||
<div><strong>Temperature:</strong> {metar.temp}°C</div>
|
||||
<div><strong>Dew Point:</strong> {metar.dewp}°C</div>
|
||||
<div><strong>Wind:</strong> {metar.wdir}° at {metar.wspd}kt</div>
|
||||
<div><strong>Visibility:</strong> {metar.visib}</div>
|
||||
<div><strong>Pressure:</strong> {metar.altim} hPa</div>
|
||||
<div><strong>Raw METAR:</strong> <code>{metar.rawOb}</code></div>
|
||||
</div>
|
||||
{:else}
|
||||
<Loading />
|
||||
{/if}
|
||||
|
19
src/routes/api/metar/[icao]/+server.ts
Normal file
19
src/routes/api/metar/[icao]/+server.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import type { Metar } from '$lib/api/types/metar';
|
||||
|
||||
export const GET: RequestHandler = async ({ params }) => {
|
||||
const icao = params.icao?.toUpperCase();
|
||||
if (!icao || icao.length !== 4) {
|
||||
return new Response(JSON.stringify({ error: 'Invalid ICAO' }), { status: 400 });
|
||||
}
|
||||
|
||||
const url = `https://aviationweather.gov/api/data/metar?ids=${icao}&format=json`;
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
return new Response(JSON.stringify({ error: 'Failed to fetch METAR' }), { status: 502 });
|
||||
}
|
||||
const data: Metar[] = await res.json();
|
||||
return new Response(JSON.stringify(data[0] ?? null), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
};
|
Reference in New Issue
Block a user