development #1
							
								
								
									
										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