Skip to content

Commit cbad17f

Browse files
feat: weather route visualisation
1 parent eb43e6a commit cbad17f

File tree

9 files changed

+188
-14
lines changed

9 files changed

+188
-14
lines changed

examples/playground/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
"@navigraph/packages": "*",
1919
"@navigraph/weather": "*",
2020
"@tanstack/react-query": "^5.52.2",
21+
"@turf/buffer": "^7.1.0",
22+
"@turf/helpers": "^7.1.0",
2123
"clsx": "^2.1.1",
2224
"leaflet": "^1.9.4",
2325
"react": "^18.3.1",

examples/playground/src/components/JsonView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ interface Props {
1010
*/
1111
export default function JsonView({ content, onClick }: Props) {
1212
return (
13-
<div className={clsx("pane overflow-auto w-full no-scrollbar", onClick && "cursor-pointer")}>
13+
<div className={clsx("pane overflow-auto w-full max-h-96", onClick && "cursor-pointer")}>
1414
<pre className={clsx("text-white text-xs", onClick && "hover:text-gray-50")}>
1515
{JSON.stringify(content, null, 2)}
1616
</pre>

examples/playground/src/components/map/avwx.tsx renamed to examples/playground/src/components/map/AviationWeather.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const weatherColors: Record<AVWXSource, string> = {
2121
/**
2222
* Handles the rendering of Aviation Weather reports to the map
2323
*/
24-
const Avwx = memo(() => {
24+
const AviationWeather = memo(() => {
2525
const weatherApi = getWeatherApi()
2626

2727
const sources = useRecoilValue(avwxState)
@@ -88,4 +88,4 @@ const Avwx = memo(() => {
8888
})
8989
})
9090

91-
export default Avwx
91+
export default AviationWeather
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { memo } from "react"
2+
import { Marker, Popup } from "react-leaflet"
3+
import { useRecoilValue } from "recoil"
4+
import { metarTafMarkersState } from "../../state/weather"
5+
import JsonView from "../JsonView"
6+
7+
const MetarTaf = memo(() => {
8+
const markers = useRecoilValue(metarTafMarkersState)
9+
10+
return [
11+
...markers.flatMap(({ latitude, longitude, ...data }) => {
12+
if (!latitude || !longitude) return
13+
14+
return (
15+
<Marker position={{ lat: latitude, lng: longitude }}>
16+
<Popup>
17+
<JsonView content={data} />
18+
</Popup>
19+
</Marker>
20+
)
21+
}),
22+
]
23+
})
24+
25+
export default MetarTaf

examples/playground/src/components/map/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import {
2121
} from "../../state/map"
2222
import Button from "../Button"
2323
import AmdbManager from "./AmdbManager"
24-
import Avwx from "./avwx"
24+
import AviationWeather from "./AviationWeather"
25+
import MetarTaf from "./MetarTaf"
2526
import WeatherRouteManager from "./weatherRoute"
2627

2728
/**
@@ -173,7 +174,8 @@ export default function MapPane() {
173174
))}
174175
{user?.scope.includes(Scope.CHARTS) && <ChartOverlay />}
175176
<WeatherRouteManager />
176-
<Avwx />
177+
<AviationWeather />
178+
<MetarTaf />
177179
{user?.scope.includes(Scope.AMDB) && <AmdbManager />}
178180
</MapContainer>
179181
<OverlayControls />

examples/playground/src/components/map/weatherRoute.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { buffer } from "@turf/buffer"
2+
import * as helpers from "@turf/helpers"
13
import { LatLng, LeafletMouseEvent } from "leaflet"
2-
import { useEffect, useState } from "react"
3-
import { Marker, Polyline, useMap } from "react-leaflet"
4-
import { useRecoilState } from "recoil"
5-
import { weatherRouteEditState, weatherRouteState } from "../../state/weather"
4+
import { useEffect, useMemo, useState } from "react"
5+
import { GeoJSON, Marker, Polyline, useMap } from "react-leaflet"
6+
import { useRecoilState, useRecoilValue } from "recoil"
7+
import { weatherRouteEditState, weatherRouteRangeState, weatherRouteState } from "../../state/weather"
68

79
/**
810
* Handles the creation and rendering of routes for weather queries along routes
@@ -60,13 +62,27 @@ export default function WeatherRouteManager() {
6062
}
6163
}, [editActive, setEditActive])
6264

65+
const positions = useRecoilValue(weatherRouteState)
66+
const range = useRecoilValue(weatherRouteRangeState)
67+
68+
const area = useMemo(() => {
69+
if (positions.length < 2) {
70+
return null
71+
}
72+
73+
const linestring = helpers.lineString(positions.map(({ lng, lat }) => [lng, lat]))
74+
75+
return buffer(linestring, range * 1852, { units: "meters" })
76+
}, [range, positions])
77+
6378
return (
6479
<>
6580
<Polyline positions={route} />
6681
{nextPosition && route.length >= 1 && (
6782
<Polyline color="red" positions={[route[route.length - 1], nextPosition]} />
6883
)}
6984
{nextPosition && <Marker position={nextPosition} />}
85+
{area && <GeoJSON key={Math.random()} data={area} />}
7086
</>
7187
)
7288
}

examples/playground/src/pages/Weather.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { AVWX_SOURCES, getWeatherApi } from "@navigraph/weather"
22
import { useQuery } from "@tanstack/react-query"
3-
import { useState } from "react"
3+
import { useEffect, useState } from "react"
44
import { BiTrash } from "react-icons/bi"
55
import { FaMagnifyingGlass } from "react-icons/fa6"
66
import SpinningCircles from "react-loading-icons/dist/esm/components/spinning-circles"
7-
import { useRecoilState } from "recoil"
7+
import { useRecoilState, useSetRecoilState } from "recoil"
88
import Button from "../components/Button"
99
import JsonView from "../components/JsonView"
10-
import { weatherColors } from "../components/map/avwx"
10+
import { weatherColors } from "../components/map/AviationWeather"
1111
import { protectedPage } from "../components/protectedPage"
1212
import SegmentControl from "../components/SegmentControl"
1313
import { TextField } from "../components/TextField"
1414
import {
1515
avwxState,
16+
metarTafMarkersState,
1617
weatherRouteEditState,
1718
weatherRouteRangeState,
1819
weatherRouteState,
@@ -26,6 +27,8 @@ function AirportPage() {
2627

2728
const weatherApi = getWeatherApi()
2829

30+
const setMetarTafMarkers = useSetRecoilState(metarTafMarkersState)
31+
2932
const { data: metar, isLoading: metarLoading } = useQuery({
3033
queryKey: ["metar", icao],
3134
queryFn: () => {
@@ -42,6 +45,18 @@ function AirportPage() {
4245
enabled: icao.length === 4,
4346
})
4447

48+
useEffect(() => {
49+
if (page === 0) {
50+
setMetarTafMarkers(metar ?? [])
51+
} else {
52+
setMetarTafMarkers(taf ?? [])
53+
}
54+
55+
return () => {
56+
setMetarTafMarkers([])
57+
}
58+
}, [setMetarTafMarkers, page, metar, taf])
59+
4560
return (
4661
<>
4762
<TextField label="Airport ICAO" icon={FaMagnifyingGlass} value={icao} onChange={setIcao} />
@@ -82,6 +97,8 @@ function RoutePage() {
8297

8398
const weatherApi = getWeatherApi()
8499

100+
const setMetarTafMarkers = useSetRecoilState(metarTafMarkersState)
101+
85102
const { data, isLoading } = useQuery({
86103
queryKey: ["route-reports", route, reportType, reportRange],
87104
queryFn: () => {
@@ -94,6 +111,14 @@ function RoutePage() {
94111
enabled: route.length >= 2,
95112
})
96113

114+
useEffect(() => {
115+
setMetarTafMarkers(data ?? [])
116+
117+
return () => {
118+
setMetarTafMarkers([])
119+
}
120+
}, [setMetarTafMarkers, data])
121+
97122
return (
98123
<>
99124
<SegmentControl segments={["Positions", "Reports"]} index={page} onChange={setPage} />

examples/playground/src/state/weather.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AVWXSource } from "@navigraph/weather"
1+
import { AVWXSource, Metar, Taf } from "@navigraph/weather"
22
import { LatLng } from "leaflet"
33
import { atom } from "recoil"
44

@@ -26,3 +26,8 @@ export const weatherRouteTypeState = atom<"metar" | "taf">({
2626
key: "weather-route-type",
2727
default: "metar",
2828
})
29+
30+
export const metarTafMarkersState = atom<(Metar | Taf)[]>({
31+
key: "metar-taf-markers",
32+
default: [],
33+
})

yarn.lock

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1668,6 +1668,83 @@
16681668
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
16691669
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
16701670

1671+
"@turf/bbox@^7.1.0":
1672+
version "7.1.0"
1673+
resolved "https://registry.yarnpkg.com/@turf/bbox/-/bbox-7.1.0.tgz#45a9287c084f7b79577ee88b7b539d83562b923b"
1674+
integrity sha512-PdWPz9tW86PD78vSZj2fiRaB8JhUHy6piSa/QXb83lucxPK+HTAdzlDQMTKj5okRCU8Ox/25IR2ep9T8NdopRA==
1675+
dependencies:
1676+
"@turf/helpers" "^7.1.0"
1677+
"@turf/meta" "^7.1.0"
1678+
"@types/geojson" "^7946.0.10"
1679+
tslib "^2.6.2"
1680+
1681+
"@turf/buffer@^7.1.0":
1682+
version "7.1.0"
1683+
resolved "https://registry.yarnpkg.com/@turf/buffer/-/buffer-7.1.0.tgz#87afad8ff306eda6199769d040d7723a52553be1"
1684+
integrity sha512-QM3JiCMYA19k5ouO8wJtvICX3Y8XntxVpDfHSKhFFidZcCkMTR2PWWOpwS6EoL3t75rSKw/FOLIPLZGtIu963w==
1685+
dependencies:
1686+
"@turf/bbox" "^7.1.0"
1687+
"@turf/center" "^7.1.0"
1688+
"@turf/helpers" "^7.1.0"
1689+
"@turf/jsts" "^2.7.1"
1690+
"@turf/meta" "^7.1.0"
1691+
"@turf/projection" "^7.1.0"
1692+
"@types/geojson" "^7946.0.10"
1693+
d3-geo "1.7.1"
1694+
1695+
"@turf/center@^7.1.0":
1696+
version "7.1.0"
1697+
resolved "https://registry.yarnpkg.com/@turf/center/-/center-7.1.0.tgz#de0338ebabe2c1895d07a5585bcda2ebc51c48bc"
1698+
integrity sha512-p9AvBMwNZmRg65kU27cGKHAUQnEcdz8Y7f/i5DvaMfm4e8zmawr+hzPKXaUpUfiTyLs8Xt2W9vlOmNGyH+6X3w==
1699+
dependencies:
1700+
"@turf/bbox" "^7.1.0"
1701+
"@turf/helpers" "^7.1.0"
1702+
"@types/geojson" "^7946.0.10"
1703+
tslib "^2.6.2"
1704+
1705+
"@turf/clone@^7.1.0":
1706+
version "7.1.0"
1707+
resolved "https://registry.yarnpkg.com/@turf/clone/-/clone-7.1.0.tgz#b0cbf60b84fadd30ae8411f12d3bdcd3e773577f"
1708+
integrity sha512-5R9qeWvL7FDdBIbEemd0eCzOStr09oburDvJ1hRiPCFX6rPgzcZBQ0gDmZzoF4AFcNLb5IwknbLZjVLaUGWtFA==
1709+
dependencies:
1710+
"@turf/helpers" "^7.1.0"
1711+
"@types/geojson" "^7946.0.10"
1712+
tslib "^2.6.2"
1713+
1714+
"@turf/helpers@^7.1.0":
1715+
version "7.1.0"
1716+
resolved "https://registry.yarnpkg.com/@turf/helpers/-/helpers-7.1.0.tgz#eb734e291c9c205822acdd289fe20e91c3cb1641"
1717+
integrity sha512-dTeILEUVeNbaEeoZUOhxH5auv7WWlOShbx7QSd4s0T4Z0/iz90z9yaVCtZOLbU89umKotwKaJQltBNO9CzVgaQ==
1718+
dependencies:
1719+
"@types/geojson" "^7946.0.10"
1720+
tslib "^2.6.2"
1721+
1722+
"@turf/jsts@^2.7.1":
1723+
version "2.7.1"
1724+
resolved "https://registry.yarnpkg.com/@turf/jsts/-/jsts-2.7.1.tgz#c039569fcef704bef2bb7367c7ddada5008d9628"
1725+
integrity sha512-+nwOKme/aUprsxnLSfr2LylV6eL6T1Tuln+4Hl92uwZ8FrmjDRCH5Bi1LJNVfWCiYgk8+5K+t2zDphWNTsIFDA==
1726+
dependencies:
1727+
jsts "2.7.1"
1728+
1729+
"@turf/meta@^7.1.0":
1730+
version "7.1.0"
1731+
resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-7.1.0.tgz#b2af85afddd0ef08aeae8694a12370a4f06b6d13"
1732+
integrity sha512-ZgGpWWiKz797Fe8lfRj7HKCkGR+nSJ/5aKXMyofCvLSc2PuYJs/qyyifDPWjASQQCzseJ7AlF2Pc/XQ/3XkkuA==
1733+
dependencies:
1734+
"@turf/helpers" "^7.1.0"
1735+
"@types/geojson" "^7946.0.10"
1736+
1737+
"@turf/projection@^7.1.0":
1738+
version "7.1.0"
1739+
resolved "https://registry.yarnpkg.com/@turf/projection/-/projection-7.1.0.tgz#cfefa6cf2570ca15b2753b423c6e994604c34467"
1740+
integrity sha512-3wHluMoOvXnTe7dfi0kcluTyLNG5MwGsSsK5OA98vkkLH6a1xvItn8e9GcesuT07oB2km/bgefxYEIvjQG5JCA==
1741+
dependencies:
1742+
"@turf/clone" "^7.1.0"
1743+
"@turf/helpers" "^7.1.0"
1744+
"@turf/meta" "^7.1.0"
1745+
"@types/geojson" "^7946.0.10"
1746+
tslib "^2.6.2"
1747+
16711748
"@types/babel__core@^7.1.14", "@types/babel__core@^7.20.2":
16721749
version "7.20.3"
16731750
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.3.tgz#d5625a50b6f18244425a1359a858c73d70340778"
@@ -1722,7 +1799,7 @@
17221799
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.13.tgz#e6e77ea9ecf36564980a861e24e62a095988775e"
17231800
integrity sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==
17241801

1725-
"@types/geojson@^7946.0.14":
1802+
"@types/geojson@^7946.0.10", "@types/geojson@^7946.0.14":
17261803
version "7946.0.14"
17271804
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.14.tgz#319b63ad6df705ee2a65a73ef042c8271e696613"
17281805
integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==
@@ -2752,6 +2829,18 @@ csv@^5.5.3:
27522829
csv-stringify "^5.6.5"
27532830
stream-transform "^2.1.3"
27542831

2832+
d3-array@1:
2833+
version "1.2.4"
2834+
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
2835+
integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
2836+
2837+
2838+
version "1.7.1"
2839+
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.7.1.tgz#44bbc7a218b1fd859f3d8fd7c443ca836569ce99"
2840+
integrity sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==
2841+
dependencies:
2842+
d3-array "1"
2843+
27552844
data-urls@^3.0.2:
27562845
version "3.0.2"
27572846
resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143"
@@ -4651,6 +4740,11 @@ jsonify@^0.0.1:
46514740
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978"
46524741
integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
46534742

4743+
4744+
version "2.7.1"
4745+
resolved "https://registry.yarnpkg.com/jsts/-/jsts-2.7.1.tgz#a921c0cc9eefeef588bd53e952e0a7782d812d52"
4746+
integrity sha512-x2wSZHEBK20CY+Wy+BPE7MrFQHW6sIsdaGUMEqmGAio+3gFzQaBYPwLRonUfQf9Ak8pBieqj9tUofX1+WtAEIg==
4747+
46544748
keyv@^4.5.3:
46554749
version "4.5.4"
46564750
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -6274,6 +6368,11 @@ tslib@^2.5.0, tslib@^2.6.0:
62746368
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
62756369
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
62766370

6371+
tslib@^2.6.2:
6372+
version "2.7.0"
6373+
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
6374+
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
6375+
62776376
tsup@^6.3.0:
62786377
version "6.7.0"
62796378
resolved "https://registry.yarnpkg.com/tsup/-/tsup-6.7.0.tgz#416f350f32a07b6ae86792ad7e52b0cafc566d64"

0 commit comments

Comments
 (0)