Skip to content

Commit

Permalink
Merge branch 'feature/metar-provider-selection' into merge-branch
Browse files Browse the repository at this point in the history
  • Loading branch information
epranka committed Nov 8, 2024
2 parents 6e8c851 + ba006ef commit 52c3168
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 48 deletions.
19 changes: 14 additions & 5 deletions server/api/runway.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
const downloadFile = require("../helpers/downloadData.js");
const metarParser = require('aewx-metar-parser');

const createMetarUrl = (icao) => `https://aviationweather.gov/cgi-bin/data/metar.php?ids=${icao}`;
const createMetarUrl = (provider, icao) => {
switch (provider.toLowerCase()) {
case 'vatsim':
return `https://metar.vatsim.net/${icao}`;
case 'aviationweather':
default:
return `https://aviationweather.gov/cgi-bin/data/metar.php?ids=${icao}`;
}
};

const createAirportUrl = (icao) =>
`https://airportdb.io/api/v1/airport/${icao}?apiToken=${process.env.AIRPORTDB_API_TOKEN}`;
Expand All @@ -17,6 +25,7 @@ const isString = (value) => {
const runwayAPI = async (req, res) => {
try {
const { icao } = req.params;
const metarProvider = req.query.metarProvider || 'aviationweather';

const airportUrl = createAirportUrl(icao);
const airportDataRaw = await downloadFile(airportUrl);
Expand All @@ -31,7 +40,7 @@ const runwayAPI = async (req, res) => {
if (!airportData.runways || !airportData.runways.length) {
return res.json({
code: 3,
error: `We have an invalid airport runways data, so can't display it. Sorry. Try other nearest airport`,
error: `Sorry. The requested airport has invalid runway data, so it can't be displayed. Try other nearest airport`,
});
}

Expand Down Expand Up @@ -77,18 +86,18 @@ const runwayAPI = async (req, res) => {
if (!validRunways.length) {
return res.json({
code: 4,
error: `We have an invalid airport runways data, so can't display it. Sorry. Try other nearest airport`,
error: `Sorry. The requested airport has invalid runway data, so it can't be displayed. Try other nearest airport`,
});
}

const metarUrl = createMetarUrl(station.icao_code);
const metarUrl = createMetarUrl(metarProvider, station.icao_code);

const metar = await downloadFile(metarUrl);

if (!metar.trim()) {
return res.json({
code: 1,
error: `Can't find airport ${icao.toUpperCase()} metar data. Try to search nearest a bigger airport`,
error: `Can't find airport ${icao.toUpperCase()} metar data. Try to search a nearby international airport`,
});
}

Expand Down
83 changes: 50 additions & 33 deletions src/components/AirportSelectInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import clsx from "clsx";
import React, { useCallback, useEffect, useState } from "react";
import useAirportFetch from "../hooks/useAirportFetch";
import useStickyState from "../hooks/useStickyState";
import {DEFAULT_METAR_PROVIDER, METAR_PROVIDER_STORAGE_KEY} from "../consts";

export default function AirportSelectInput(props) {
const [metarProviderValue, setMetarProviderValue] = useStickyState(DEFAULT_METAR_PROVIDER, METAR_PROVIDER_STORAGE_KEY);
const [icaoValue, setIcaoValue] = useState(props.initialValue ?? "");
const [error, setError] = useState(props.initialError ?? null);
const [edited, setEdited] = useState(false);
Expand All @@ -16,6 +19,10 @@ export default function AirportSelectInput(props) {
setEdited(true);
}, []);

const handleMetarProviderValueChange = useCallback((e) => {
setMetarProviderValue(e.target.value);
}, []);

const fetchData = useAirportFetch({
onLoading: setLoading,
onError: setError,
Expand All @@ -24,49 +31,59 @@ export default function AirportSelectInput(props) {

useEffect(() => {
if (debouncedIcaoValue) {
fetchData(debouncedIcaoValue);
fetchData(metarProviderValue, debouncedIcaoValue);
} else if (edited) {
setLoading(false);
setError(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedIcaoValue]);
}, [metarProviderValue, debouncedIcaoValue]);

return (
<div className="text-center relative">
{error ? (
<div className="mb-3 font-semibold text-sm text-red-600 max-w-xs mx-auto">
{error}
</div>
) : null}
<div className="max-w-xs mx-auto relative">
<input
value={icaoValue}
onChange={handleIcaoValueChange}
type="text"
className={`block bg-white w-full mx-auto uppercase border-2 border-black rounded-md h-14 text-2xl font-semibold text-center
<label for="metar-provider" className="block text-sm mb-2">METAR provider</label>
<select id="metar-provider" value={metarProviderValue} onChange={handleMetarProviderValueChange} className={`block mb-5 bg-white w-full mx-auto uppercase border-2 border-black rounded-md h-14 text-2xl font-semibold text-center
outline-none placeholder-opacity-0`}>
<option value="aviationweather">Aviation Weather</option>
<option value="vatsim">VATSIM</option>
</select>
<label for="icao" className="block text-sm mb-2">Enter ICAO</label>
<div className="relative">
<input
id="icao"
value={icaoValue}
onChange={handleIcaoValueChange}
type="text"
className={`block bg-white w-full mx-auto uppercase border-2 border-black rounded-md h-14 text-2xl font-semibold text-center
outline-none placeholder-opacity-0`}
placeholder="Enter ICAO"
/>
{isLoading || error ? (
<div
className={clsx("absolute right-4 top-4 text-gray-300", {
"text-red-500": error,
})}
>
<FontAwesomeIcon
icon={[
"fas",
isLoading
? "circle-notch"
: error
? "exclamation-triangle"
: null,
]}
spin={isLoading}
/>
</div>
) : null}
placeholder="EGLL"
/>
{isLoading || error ? (
<div
className={clsx("absolute right-4 top-4 text-gray-300", {
"text-red-500": error,
})}
>
<FontAwesomeIcon
icon={[
"fas",
isLoading
? "circle-notch"
: error
? "exclamation-triangle"
: null,
]}
spin={isLoading}
/>
</div>
) : null}
{error ? (
<div className="mt-3 font-semibold text-sm text-red-600 max-w-xs mx-auto">
{error}
</div>
) : null}
</div>
</div>
</div>
);
Expand Down
2 changes: 2 additions & 0 deletions src/consts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const DEFAULT_METAR_PROVIDER = "aviationweather";
export const METAR_PROVIDER_STORAGE_KEY = "metarProvider";
11 changes: 4 additions & 7 deletions src/hooks/useAirportFetch.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import axios from "axios";
import { useCallback } from "react";

const MAIN_HUMAN_ERROR_MESSAGE =
"Sorry, something went wrong. Try again later or contact administrators";
const MAIN_HUMAN_ERROR_MESSAGE = "Sorry, something went wrong. Try again later or contact administrators";

const useAirportFetch = ({ onLoading, onError, onLoaded }) => {
const fetch = useCallback(
async (icaoValue) => {
return useCallback(
async (metarProviderValue, icaoValue) => {
onLoading && onLoading(true);
onError(null);
try {
const response = await axios.get(
import.meta.env.VITE_APP_API_HOST + "/api/v1/runway/" + icaoValue
process.env.VITE_APP_API_HOST + "/api/v1/runway/" + icaoValue + "?metarProvider=" + metarProviderValue,
);
const data = response.data;
if (!data) {
Expand All @@ -32,8 +31,6 @@ const useAirportFetch = ({ onLoading, onError, onLoaded }) => {
},
[onLoading, onError, onLoaded]
);

return fetch;
};

export default useAirportFetch;
18 changes: 18 additions & 0 deletions src/hooks/useStickyState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {useEffect, useState} from "react";

const useStickyState = (defaultValue, key) => {
const [value, setValue] = useState(() => {
const stickyValue = window.localStorage.getItem(key);
return stickyValue !== null
? JSON.parse(stickyValue)
: defaultValue;
});

useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);

return [value, setValue];
}

export default useStickyState;
9 changes: 6 additions & 3 deletions src/pages/AirportPage/AirportPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ import AirportRunways from "./AirportRunways";
import AirportRunwaysMap from "./AirportRunwaysMap";
import Compass from "./Compass";
import WindDirectionBackground from "./WindDirectionBackground";
import useStickyState from "../../hooks/useStickyState";
import {DEFAULT_METAR_PROVIDER, METAR_PROVIDER_STORAGE_KEY} from "../../consts";

export default function AirportPage(props) {
export default function AirportPage() {
const [metarProvider] = useStickyState(DEFAULT_METAR_PROVIDER, METAR_PROVIDER_STORAGE_KEY);
const [airport, setAirport] = useAirport();
const { params } = useRouteMatch();
const history = useHistory();
Expand All @@ -36,10 +39,10 @@ export default function AirportPage(props) {

useEffect(() => {
if (!airportIsValid) {
fetchAirport(params?.icao);
fetchAirport(metarProvider, params?.icao);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [metarProvider]);

const activeRunwaysData = useCalculateActiveRunways(airport, airportIsValid);

Expand Down

0 comments on commit 52c3168

Please sign in to comment.