1
- import { MapContainer , useMap , TileLayer } from "react-leaflet" ;
1
+ import { MapContainer , useMap , TileLayer , ImageOverlay } from "react-leaflet" ;
2
2
import { useEffect , useMemo , useRef } from "react" ;
3
- import { Map } from "leaflet" ;
4
- import { useRecoilValue } from "recoil" ;
3
+ import { LatLngBounds , Map } from "leaflet" ;
4
+ import { useRecoilState , useRecoilValue } from "recoil" ;
5
5
import { appState } from "../state/app" ;
6
6
import { NavigraphRasterSource , NavigraphTheme , NavigraphTileLayer , PresetConfig } from "@navigraph/leaflet" ;
7
7
import { userState } from "../state/user" ;
@@ -10,6 +10,12 @@ import { Scope } from "@navigraph/app";
10
10
11
11
import "leaflet/dist/leaflet.css"
12
12
import { mapFaaState , mapSourceState , mapTacState , mapThemeState } from "../state/mapStyle" ;
13
+ import { chartOverlayOpacityState , chartOverlayState } from "../state/chartOverlay" ;
14
+ import { calculateChartBounds } from "@navigraph/charts" ;
15
+ import { useQuery } from "@tanstack/react-query" ;
16
+ import { protectedPage } from "./protectedPage" ;
17
+ import { TbCircleX } from "react-icons/tb" ;
18
+ import Button from "./Button" ;
13
19
14
20
export function createPreset ( source : NavigraphRasterSource , theme : NavigraphTheme , faa : boolean , tac : boolean ) : PresetConfig {
15
21
if ( source === 'WORLD' ) {
@@ -22,6 +28,51 @@ export function createPreset(source: NavigraphRasterSource, theme: NavigraphThem
22
28
return { source, theme, type : faa ? 'FAA' : 'Navigraph' }
23
29
}
24
30
31
+ const ChartOverlay = protectedPage ( ( { charts } ) => {
32
+ const theme = useRecoilValue ( mapThemeState ) ;
33
+
34
+ const opacity = useRecoilValue ( chartOverlayOpacityState ) ;
35
+
36
+ const chart = useRecoilValue ( chartOverlayState ) ;
37
+
38
+ const map = useMap ( ) ;
39
+
40
+ const bounds = useMemo ( ( ) => {
41
+ if ( ! chart || ! chart . is_georeferenced ) return ;
42
+
43
+ const { sw, ne } = calculateChartBounds ( chart ) ;
44
+
45
+ return new LatLngBounds ( sw , ne ) ;
46
+ } , [ chart ] ) ;
47
+
48
+ useEffect ( ( ) => {
49
+ if ( bounds ) {
50
+ map . flyToBounds ( bounds )
51
+ }
52
+ } , [ bounds ] ) ;
53
+
54
+ const { data : urls } = useQuery ( {
55
+ queryKey : [ 'chart-overlay-urls' , chart ] ,
56
+ queryFn : async ( ) => {
57
+ if ( ! chart ) return null ;
58
+
59
+ const blobs = await Promise . all ( [ charts . getChartImage ( { chart, theme : 'light' } ) , charts . getChartImage ( { chart, theme : 'dark' } ) ] ) ;
60
+
61
+ return blobs . map ( ( blob ) => blob ? URL . createObjectURL ( blob ) : null ) ;
62
+ }
63
+ } )
64
+
65
+ if ( ! bounds || ! urls ?. [ 0 ] || ! urls ?. [ 1 ] ) return null ;
66
+
67
+ return (
68
+ < ImageOverlay
69
+ url = { theme === 'DAY' ? urls [ 0 ] : urls [ 1 ] }
70
+ bounds = { bounds }
71
+ opacity = { opacity }
72
+ />
73
+ )
74
+ } , [ Scope . CHARTS ] ) ;
75
+
25
76
function NavigraphTiles ( { auth } : { auth : NavigraphAuth } ) {
26
77
const map = useMap ( ) ;
27
78
@@ -51,6 +102,28 @@ function NavigraphTiles({ auth }: { auth: NavigraphAuth }) {
51
102
return null ;
52
103
}
53
104
105
+ function OverlayControls ( ) {
106
+ const [ opacity , setOpacity ] = useRecoilState ( chartOverlayOpacityState ) ;
107
+
108
+ const [ chart , setChart ] = useRecoilState ( chartOverlayState ) ;
109
+
110
+ if ( ! chart ) return null ;
111
+
112
+ return (
113
+ < div className = 'absolute right-5 top-5 bg-blue-gray-500 z-[999] p-2 rounded-md flex flex-col gap-2' >
114
+ < div className = "flex justify-between items-center gap-3" >
115
+ < span className = "text-xs" > { chart . index_number } : { chart . name } </ span >
116
+ < TbCircleX className = "text-white hover:text-blue-25 cursor-pointer" size = { 25 } onClick = { ( ) => setChart ( null ) } />
117
+ </ div >
118
+ < div className = "flex gap-2" >
119
+ < Button selected = { opacity === 1 } onClick = { ( ) => setOpacity ( 1 ) } > < span className = "text-white text-xs" > 100%</ span > </ Button >
120
+ < Button selected = { opacity === 0.9 } onClick = { ( ) => setOpacity ( 0.9 ) } > < span className = "text-white text-xs" > 90%</ span > </ Button >
121
+ < Button selected = { opacity === 0.7 } onClick = { ( ) => setOpacity ( 0.7 ) } > < span className = "text-white text-xs" > 70%</ span > </ Button >
122
+ </ div >
123
+ </ div >
124
+ )
125
+ }
126
+
54
127
export default function MapPane ( ) {
55
128
const mapRef = useRef < Map > ( null ) ;
56
129
@@ -62,6 +135,7 @@ export default function MapPane() {
62
135
< MapContainer center = { [ 51.505 , - 0.09 ] } zoom = { 13 } className = 'h-screen' zoomControl = { false } ref = { mapRef } whenReady = { ( ) => {
63
136
setInterval ( ( ) => mapRef . current ?. invalidateSize ( ) , 1000 )
64
137
} } >
138
+ < OverlayControls />
65
139
{ app && user ?. scope . includes ( Scope . TILES ) ? (
66
140
< NavigraphTiles auth = { app . auth } />
67
141
) : (
@@ -70,6 +144,7 @@ export default function MapPane() {
70
144
url = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
71
145
/>
72
146
) }
147
+ < ChartOverlay />
73
148
</ MapContainer >
74
149
</ div >
75
150
)
0 commit comments