Skip to content

Commit

Permalink
Merge pull request #5 from benjasper/feature/weather-update-notification
Browse files Browse the repository at this point in the history
feat: added weather update notification
  • Loading branch information
benjasper authored Oct 27, 2023
2 parents d71e115 + 4fee99b commit 1e29865
Show file tree
Hide file tree
Showing 16 changed files with 573 additions and 252 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
"linebreak-style": ["error", "unix"],
"quotes": ["error", "single"],
"semi": ["error", "never"],
"prettier/prettier": "error"
"prettier/prettier": "warn"
}
}
9 changes: 6 additions & 3 deletions src/components/ForecastElements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,12 @@ const ForecastElements: Component<ForecastElementsProps> = props => {
<Show when={forecast.windSpeed && forecast.windDirection}>
<WindElement
airport={props.airport}
windDirection={forecast.windDirection!}
windSpeed={forecast.windSpeed!}
windGust={forecast.windGust ?? 0}
windData={{
windDirection: forecast.windDirection!,
windSpeed: forecast.windSpeed!,
windGust: forecast.windGust ?? 0,
isVariable: forecast.windDirectionVariable,
}}
size="small"
/>
</Show>
Expand Down
46 changes: 37 additions & 9 deletions src/components/WeatherElements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface ParsedWeatherElementsProps {

const WeatherElements: Component<ParsedWeatherElementsProps> = props => {
const latestMetar = createMemo(() => props.airport?.station?.metars?.edges[0]?.node)
const previousMetar = createMemo(() => props.airport?.station?.metars?.edges[1]?.node)

const now = useTimeStore()

Expand Down Expand Up @@ -107,44 +108,71 @@ const WeatherElements: Component<ParsedWeatherElementsProps> = props => {
<div class="flex flex-shrink-0 flex-col">
<WindElement
airport={props.airport}
windDirection={latestMetar()!.windDirection}
windSpeed={latestMetar()!.windSpeed}
windGust={latestMetar()!.windGust}
variableWindDirection={latestMetar()?.rawText ?? ''}
windData={{
windDirection: latestMetar()!.windDirection,
windSpeed: latestMetar()!.windSpeed,
windGust: latestMetar()!.windGust,
variableWindDirection: latestMetar()?.rawText ?? '',
isVariable: latestMetar()!.windDirectionVariable,
}}
previousWindDate={{
windDirection: previousMetar()?.windDirection,
windSpeed: previousMetar()?.windSpeed,
windGust: previousMetar()?.windGust,
variableWindDirection: previousMetar()?.rawText ?? '',
isVariable: previousMetar()?.windDirectionVariable ?? false,
}}
size="large"
/>
</div>
<div class="flex flex-row flex-wrap justify-center gap-8 md:justify-start">
<VisibilityElement
visibility={latestMetar()!.visibility}
visibilityMoreThan={latestMetar()!.visibilityIsMoreThan}
previousVisibility={previousMetar()!.visibility}
previousVisibilityMoreThan={previousMetar()!.visibilityIsMoreThan}
/>

<Show when={latestMetar()!.skyConditions!.length > 0}>
<SkyConditionsElement
skyConditions={latestMetar()!.skyConditions!}
previousSkyConditions={previousMetar()?.skyConditions ?? undefined}
airport={props.airport}
/>
</Show>

<Show when={latestMetar()!.temperature !== undefined}>
<TemperatureElement temperature={latestMetar()!.temperature!} name="Temperature" />
<TemperatureElement
temperature={latestMetar()!.temperature!}
previousTemperature={previousMetar()?.temperature ?? undefined}
name="Temperature"
/>
</Show>

<Show when={latestMetar()!.dewpoint !== undefined}>
<TemperatureElement temperature={latestMetar()!.dewpoint!} name="Dewpoint" />
<TemperatureElement
temperature={latestMetar()!.dewpoint!}
previousTemperature={previousMetar()?.dewpoint ?? undefined}
name="Dewpoint"
/>
</Show>

<Show when={latestMetar()!.altimeter !== undefined}>
<AltimeterElement altimeter={latestMetar()!.altimeter!} />
<AltimeterElement
altimeter={latestMetar()!.altimeter!}
previousAltimeter={previousMetar()?.altimeter ?? undefined}
/>
</Show>

<Show when={latestMetar()!.presentWeather && (latestMetar()!.presentWeather ?? '').length > 0}>
<PrecipitationElement weather={latestMetar()!.presentWeather ?? ''} />
<PrecipitationElement
weather={latestMetar()!.presentWeather ?? ''}
previousWeather={previousMetar()!.presentWeather ?? undefined}
/>
</Show>

<Show when={latestMetar()!.flightCategory}>
<FlightCategoryElement latestMetar={latestMetar()!} />
<FlightCategoryElement latestMetar={latestMetar()!} previousMetar={previousMetar()} />
</Show>
</div>
</Show>
Expand Down
82 changes: 53 additions & 29 deletions src/components/special/RunwayAndWindRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ interface RunwayDirection {
heading: number
x: number
y: number
windAngle: number
windAngle?: number
favourableLevel: number
}

Expand All @@ -58,25 +58,35 @@ const RunwayPopup = (props: {
runway: Runway
runwayDirection: RunwayDirection
windSpeed: number
windDirection: number
windDirection?: number
}) => {
const [unitStore] = useUnitStore()
const selectedLengthUnit = () => unitStore.smallLength.units[unitStore.smallLength.selected]
const selectedSpeedUnit = () => unitStore.speed.units[unitStore.speed.selected]

// Calculate crosswind components
const crosswindComponent = () => Math.sin((props.runwayDirection.windAngle * Math.PI) / 180) * props.windSpeed
const crosswindComponent = () =>
props.runwayDirection.windAngle !== undefined
? Math.sin((props.runwayDirection.windAngle * Math.PI) / 180) * props.windSpeed
: undefined

// Left or right crosswind by comparing wind direction and runway heading
const crosswindDirection = () => {
if (props.windDirection === undefined) {
return undefined
}

let result = props.windDirection - props.runwayDirection.heading
if (result < 0) result += 360
if (result > 180) return 'left'
else return 'right'
}

const headwindComponent = () => Math.cos((props.runwayDirection.windAngle * Math.PI) / 180) * props.windSpeed
const tailwindComponent = () => -headwindComponent()
const headwindComponent = () =>
props.runwayDirection.windAngle !== undefined
? Math.cos((props.runwayDirection.windAngle * Math.PI) / 180) * props.windSpeed
: undefined
const tailwindComponent = () => (headwindComponent() ? -headwindComponent()! : undefined)

return (
<div class="flex flex-col gap-1">
Expand Down Expand Up @@ -105,7 +115,7 @@ const RunwayPopup = (props: {
</span>
</div>

<Show when={props.windSpeed > 0 && props.windDirection > 0}>
<Show when={props.windSpeed > 0 && props.windDirection != undefined}>
<div class="flex gap-1">
<div
class="my-auto h-3 w-3 flex-shrink-0 rounded-full border-[3px]"
Expand All @@ -119,17 +129,20 @@ const RunwayPopup = (props: {
}}
aria-label={`Wind direction is ${favourableToText(props.runwayDirection.favourableLevel)}`}
/>
<span class="text-sm">
Wind angle: {favourableToText(props.runwayDirection.favourableLevel)} (
{Math.round(props.runwayDirection.windAngle)}°)
</span>
<Show when={props.runwayDirection.windAngle !== undefined}>
<span class="text-sm">
Wind angle: {favourableToText(props.runwayDirection.favourableLevel)} (
{Math.round(props.runwayDirection.windAngle!)}°)
</span>
</Show>
</div>
</Show>

<Show
when={
Math.round(selectedSpeedUnit().conversionFunction(headwindComponent())) > 0 &&
props.windDirection > 0
props.windDirection !== undefined &&
headwindComponent() != undefined &&
Math.round(selectedSpeedUnit().conversionFunction(headwindComponent()!)) > 0
}>
<div class="flex gap-1">
<BsArrowUp
Expand All @@ -139,15 +152,16 @@ const RunwayPopup = (props: {
}}
/>
<span class="text-sm">
Headwind: {Math.round(selectedSpeedUnit().conversionFunction(headwindComponent()))}{' '}
Headwind: {Math.round(selectedSpeedUnit().conversionFunction(headwindComponent()!))}{' '}
{selectedSpeedUnit().symbol}
</span>
</div>
</Show>
<Show
when={
Math.round(selectedSpeedUnit().conversionFunction(crosswindComponent())) > 0 &&
props.windDirection > 0
props.windDirection != undefined &&
crosswindComponent() != undefined &&
Math.round(selectedSpeedUnit().conversionFunction(crosswindComponent()!)) > 0
}>
<div class="flex gap-1">
<BsArrowUp
Expand All @@ -158,15 +172,16 @@ const RunwayPopup = (props: {
}}
/>
<span class="text-sm">
Crosswind: {Math.round(selectedSpeedUnit().conversionFunction(crosswindComponent()))}{' '}
Crosswind: {Math.round(selectedSpeedUnit().conversionFunction(crosswindComponent()!))}{' '}
{selectedSpeedUnit().symbol} from the {crosswindDirection()}{' '}
</span>
</div>
</Show>
<Show
when={
Math.round(selectedSpeedUnit().conversionFunction(tailwindComponent())) > 0 &&
props.windDirection > 0
tailwindComponent() != undefined &&
props.windDirection != undefined &&
Math.round(selectedSpeedUnit().conversionFunction(tailwindComponent()!)) > 0
}>
<div class="flex gap-1">
<BsArrowUp
Expand All @@ -176,7 +191,7 @@ const RunwayPopup = (props: {
}}
/>
<span class="text-sm">
Tailwind: {Math.round(selectedSpeedUnit().conversionFunction(tailwindComponent()))}{' '}
Tailwind: {Math.round(selectedSpeedUnit().conversionFunction(tailwindComponent()!))}{' '}
{selectedSpeedUnit().symbol}
</span>
</div>
Expand All @@ -188,8 +203,9 @@ const RunwayPopup = (props: {
const RunwayAndWindRenderer = (props: {
airport: AirportSearchFragment
windSpeed: number
windDirection: number
windDirection?: number
variableWind: VariableWind | undefined
isVariable: boolean
}) => {
const [runways, setRunways] = createSignal<Runway[]>([])

Expand Down Expand Up @@ -236,34 +252,42 @@ const RunwayAndWindRenderer = (props: {
x: direction1.x,
y: direction1.y,
favourableLevel: 0,
windAngle: 180 - Math.abs(Math.abs((runway.lowRunwayHeading ?? 0) - props.windDirection) - 180),
windAngle: props.windDirection
? 180 - Math.abs(Math.abs((runway.lowRunwayHeading ?? 0) - props.windDirection) - 180)
: undefined,
},
direction2: {
runway: runway.highRunwayIdentifier,
heading: runway.highRunwayHeading ?? 0,
x: direction2.x,
y: direction2.y,
favourableLevel: 0,
windAngle: 180 - Math.abs(Math.abs((runway.highRunwayHeading ?? 0) - props.windDirection) - 180),
windAngle: props.windDirection
? 180 - Math.abs(Math.abs((runway.highRunwayHeading ?? 0) - props.windDirection) - 180)
: undefined,
},
length: runway.length ?? 0,
width: runway.width ?? 0,
})
})

// Calculate the best runway heading
if (props.windSpeed > 1 && props.windDirection != 0) {
if (props.windSpeed > 0 && props.windDirection && props.windDirection != 0) {
const bestRunways = preparingRunways.filter(runway => {
if (runway.direction1.windAngle === undefined || runway.direction2.windAngle === undefined) {
return
}

return runway.direction1.windAngle < 90 || runway.direction2.windAngle < 90
})

bestRunways.forEach(runway => {
// Set the favourable level to 1 if the wind angle is less than 90 and 2 if the wind angle is less than 45 degrees
if (runway.direction1.windAngle < 90)
runway.direction1.favourableLevel = runway.direction1.windAngle < 45 ? 2 : 1
if (runway.direction1.windAngle! < 90)
runway.direction1.favourableLevel = runway.direction1.windAngle! < 45 ? 2 : 1

if (runway.direction2.windAngle < 90)
runway.direction2.favourableLevel = runway.direction2.windAngle < 45 ? 2 : 1
if (runway.direction2.windAngle! < 90)
runway.direction2.favourableLevel = runway.direction2.windAngle! < 45 ? 2 : 1
})
}

Expand Down Expand Up @@ -299,7 +323,7 @@ const RunwayAndWindRenderer = (props: {
const windArrows = (): { angle: number; x: number; y: number; isVariable: boolean }[] => {
const arrows: { angle: number; x: number; y: number; isVariable: boolean }[] = []

if (props.windSpeed > 0 && props.windDirection != 0) {
if (props.windSpeed > 0 && props.windDirection && props.windDirection != 0) {
arrows.push({
angle: props.windDirection,
x: realCenterX() + radius() * Math.cos(((props.windDirection - 90) * Math.PI) / 180),
Expand Down Expand Up @@ -345,7 +369,7 @@ const RunwayAndWindRenderer = (props: {
/>

{/* Wind arrow */}
<Show when={props.windSpeed > 0 && props.windDirection != 0}>
<Show when={props.windDirection && props.windDirection != 0}>
<For each={windArrows()}>
{arrow => (
<svg
Expand Down
28 changes: 19 additions & 9 deletions src/components/weather-elements/AltimeterElement.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
import { FaSolidGauge } from 'solid-icons/fa'
import { Component } from 'solid-js'
import { useUnitStore } from '../../context/UnitStore'
import WeatherElementLayout from '../../layouts/WeatherElementLayout'
import WeatherElementLayout, { UpdatePing } from '../../layouts/WeatherElementLayout'
import { PressureUnit } from '../../models/units'

interface AltimeterElementProps {
altimeter: number
previousAltimeter?: number
}

const AltimeterElement: Component<AltimeterElementProps> = props => {
const [unitStore] = useUnitStore()

const selected = () => unitStore.pressure.units[unitStore.pressure.selected]
const altimeter = () =>
selected().symbol === PressureUnit.InchesOfMercury
? (Math.round((selected().conversionFunction(props.altimeter) + Number.EPSILON) * 100) / 100).toFixed(2)
: Math.round(selected().conversionFunction(props.altimeter))

const value = (altimeter: number) => {
const result =
selected().symbol === PressureUnit.InchesOfMercury
? (Math.round((selected().conversionFunction(altimeter) + Number.EPSILON) * 100) / 100).toFixed(2)
: Math.round(selected().conversionFunction(altimeter))

return `${result} ${selected().symbol}`
}

return (
<WeatherElementLayout name="Altimeter" icon={<FaSolidGauge />} unitType={[{ unitType: 'pressure' }]}>
<p class="text-center text-xl dark:text-white-dark">
{altimeter()} {selected().symbol}
</p>
<WeatherElementLayout
name="Altimeter"
icon={<FaSolidGauge />}
unitType={[{ unitType: 'pressure' }]}
updatePing={UpdatePing.Neutral}
updatePingOldValue={props.previousAltimeter ? value(props.previousAltimeter) : undefined}
updatePingNewValue={value(props.altimeter)}>
<p class="text-center text-xl dark:text-white-dark">{value(props.altimeter)}</p>
</WeatherElementLayout>
)
}
Expand Down
Loading

1 comment on commit 1e29865

@vercel
Copy link

@vercel vercel bot commented on 1e29865 Oct 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.