@@ -4,15 +4,9 @@ import 'ol/ol.css';
44// openlayers
55import Map from 'ol/Map' ;
66import View from 'ol/View' ;
7- import TileLayer from 'ol/layer/Tile' ;
87import VectorLayer from 'ol/layer/Vector' ;
98import VectorSource from 'ol/source/Vector' ;
10- import OSM from 'ol/source/OSM' ;
119import FullScreen from 'ol/control/FullScreen' ;
12- import Feature from 'ol/Feature' ;
13- import Point from 'ol/geom/Point' ;
14- import LineString from 'ol/geom/LineString' ;
15- import { Style , Icon , Fill , Text } from 'ol/style' ;
1610import Overlay from 'ol/Overlay' ;
1711import { transform } from 'ol/proj' ;
1812// MUI UI elements
@@ -25,14 +19,9 @@ import { queryClient } from '../../util/http/http';
2519import { fetchAirport } from '../../util/http/airport' ;
2620import { DownloadMapButton } from './DownloadMapButton' ;
2721import useCustomFields from '../../hooks/useCustomFields' ;
28-
29- import icon1 from "../../assets/favicon.ico" ;
30- import icon2 from "../../assets/map-pin.png" ;
31-
32- const icons = {
33- ico : { src : icon1 , displacement : [ 0 , 0 ] } ,
34- pin : { src : icon2 , displacement : [ 0 , 14 ] }
35- } ;
22+ import { DEFAULT_MAP_OPTIONS , drawGreatCircleLine , drawTrackLog , addMarker , MAP_OPTIONS_NAME , getMapBase } from './helpers' ;
23+ import MapOptionsButton from './MapOptionsButton' ;
24+ import { CODEC_JSON , useLocalStorageState } from '../../hooks/useLocalStorageState' ;
3625
3726const getAirportData = async ( id , airportsMap ) => {
3827 if ( airportsMap ) {
@@ -62,95 +51,9 @@ const getAirportData = async (id, airportsMap) => {
6251 }
6352}
6453
65- const addMarker = ( features , airport , options ) => {
66- /**
67- * Code string for an airport based on its IATA and ICAO codes.
68- * If the airport has both IATA and ICAO codes and they are different,
69- * the code will be in the format "ICAO/IATA". Otherwise, it will just be the ICAO code.
70- */
71- const code = airport . iata && airport . iata !== airport . icao ? `${ airport . icao } /${ airport . iata } ` : airport . icao ;
72-
73- // Check if marker already exists
74- const exists = features . find ( f => f . get ( 'code' ) === code ) ;
75- if ( exists ) return ;
76-
77- const feature = new Feature ( {
78- geometry : new Point ( [ airport . lon , airport . lat ] ) . transform ( 'EPSG:4326' , 'EPSG:3857' ) ,
79- code : code ,
80- name : airport . name ,
81- country : airport . country ,
82- city : airport . city ,
83- elevation : airport . elevation ,
84- coordinates : `${ airport . lat } , ${ airport . lon } ` ,
85- } ) ;
86-
87- feature . setStyle (
88- new Style ( {
89- image : new Icon ( { ...icons [ options . icon ] } ) ,
90- text : options . airport_ids ? new Text ( {
91- text : code ,
92- offsetY : - 12 ,
93- scale : 1.3 ,
94- fill : new Fill ( {
95- color : '#333' ,
96- } ) ,
97- } ) : null ,
98- } ) ,
99- ) ;
100-
101- features . push ( feature ) ;
102- }
103-
104- const createGreatCircleLine = ( start , end , segments = 64 ) => {
105- const lon1 = start . lon * Math . PI / 180
106- const lat1 = start . lat * Math . PI / 180
107- const lon2 = end . lon * Math . PI / 180
108- const lat2 = end . lat * Math . PI / 180
109-
110- const coords = [ ]
111-
112- const d = 2 * Math . asin ( Math . sqrt ( Math . sin ( ( lat1 - lat2 ) / 2 ) ** 2 + Math . cos ( lat1 ) * Math . cos ( lat2 ) * Math . sin ( ( lon1 - lon2 ) / 2 ) ** 2 ) )
113-
114- for ( let i = 0 ; i <= segments ; i ++ ) {
115- const f = i / segments
116- const A = Math . sin ( ( 1 - f ) * d ) / Math . sin ( d )
117- const B = Math . sin ( f * d ) / Math . sin ( d )
118- const x = A * Math . cos ( lat1 ) * Math . cos ( lon1 ) + B * Math . cos ( lat2 ) * Math . cos ( lon2 )
119- const y = A * Math . cos ( lat1 ) * Math . sin ( lon1 ) + B * Math . cos ( lat2 ) * Math . sin ( lon2 )
120- const z = A * Math . sin ( lat1 ) + B * Math . sin ( lat2 )
121- const lat = Math . atan2 ( z , Math . sqrt ( x * x + y * y ) )
122- const lon = Math . atan2 ( y , x )
123-
124- coords . push ( transform ( [ lon * 180 / Math . PI , lat * 180 / Math . PI ] , 'EPSG:4326' , 'EPSG:3857' ) )
125- }
54+ export const FlightMap = ( { data, title = "Flight Map" , sx, airportsMap } ) => {
55+ const [ options ] = useLocalStorageState ( MAP_OPTIONS_NAME , DEFAULT_MAP_OPTIONS , { codec : CODEC_JSON } ) ;
12656
127- return new LineString ( coords )
128- }
129-
130- const drawGreatCircleLine = ( departure , arrival , vectorSource ) => {
131- const geometry = createGreatCircleLine ( departure , arrival )
132- const routeFeature = new Feature ( { geometry, type : 'route' } )
133- vectorSource . addFeature ( routeFeature )
134- }
135-
136- const drawTrackLog = ( flightTrack , vectorSource , flightId ) => {
137- const track = JSON . parse ( atob ( flightTrack ) ) ;
138-
139- const coordinates = track . map ( ( geometry ) =>
140- transform ( [ geometry [ 1 ] , geometry [ 0 ] ] , 'EPSG:4326' , 'EPSG:3857' )
141- ) ;
142-
143- const lineFeature = new LineString ( coordinates ) ;
144- const feature = new Feature ( {
145- geometry : lineFeature ,
146- lineKey : `track-${ flightId } ` ,
147- } ) ;
148- vectorSource . addFeature ( feature ) ;
149- }
150-
151- const DEFAULT_OPTIONS = { routes : true , tracks : false , airport_ids : true , icon : 'ico' } ;
152-
153- export const FlightMap = ( { data, options = DEFAULT_OPTIONS , title = "Flight Map" , sx, airportsMap } ) => {
15457 const mapRef = useRef ( null ) ;
15558 const containerRef = useRef ( null ) ;
15659
@@ -171,9 +74,11 @@ export const FlightMap = ({ data, options = DEFAULT_OPTIONS, title = "Flight Map
17174 overlayRef . current = new Overlay ( { element : containerRef . current } ) ;
17275 const vectorLayer = new VectorLayer ( { source : vectorSourceRef . current } ) ;
17376
77+ const mapLayer = getMapBase ( options . map_base ) ;
78+
17479 const mapInstance = new Map ( {
17580 target : mapRef . current ,
176- layers : [ new TileLayer ( { source : new OSM ( ) } ) , vectorLayer ] ,
81+ layers : [ mapLayer , vectorLayer ] ,
17782 view : new View ( {
17883 center : transform ( [ 10 , 45 ] , "EPSG:4326" , "EPSG:3857" ) ,
17984 zoom : 4 ,
@@ -211,7 +116,7 @@ export const FlightMap = ({ data, options = DEFAULT_OPTIONS, title = "Flight Map
211116 mapRefInstance . current = null ;
212117 setMap ( null ) ;
213118 } ;
214- } , [ ] ) ;
119+ } , [ options . map_base ] ) ;
215120
216121 useEffect ( ( ) => {
217122 if ( ! map || ! data ) return ;
@@ -257,14 +162,14 @@ export const FlightMap = ({ data, options = DEFAULT_OPTIONS, title = "Flight Map
257162
258163 fullRoute . push ( arrival ) ;
259164
260- if ( options . routes ) {
165+ if ( options . routes . enabled ) {
261166 for ( let i = 0 ; i < fullRoute . length - 1 ; i ++ ) {
262- drawGreatCircleLine ( fullRoute [ i ] , fullRoute [ i + 1 ] , vectorSourceRef . current ) ;
167+ drawGreatCircleLine ( fullRoute [ i ] , fullRoute [ i + 1 ] , vectorSourceRef . current , options . routes . color , options . routes . thickness ) ;
263168 }
264169 }
265170
266- if ( options . tracks && flight . track ) {
267- drawTrackLog ( flight . track , vectorSourceRef . current , flight . uuid || flight . id ) ;
171+ if ( options . tracks . enabled && flight . track ) {
172+ drawTrackLog ( flight . track , vectorSourceRef . current , flight . uuid || flight . id , options . tracks . color , options . tracks . thickness ) ;
268173 }
269174
270175 totalDistance += flight . distance ;
@@ -307,7 +212,13 @@ export const FlightMap = ({ data, options = DEFAULT_OPTIONS, title = "Flight Map
307212 < >
308213 < Card variant = "outlined" sx = { { ...sx , height : "85vh" , position : "relative" } } >
309214 < CardContent sx = { { height : "100%" } } >
310- < CardHeader title = { title } action = { map ? < DownloadMapButton map = { map } /> : null } />
215+ < CardHeader title = { title }
216+ action = {
217+ < >
218+ < DownloadMapButton map = { map } />
219+ < MapOptionsButton />
220+ </ >
221+ } />
311222 < div ref = { mapRef } style = { { width : "100%" , height : "calc(100% - 50px)" , borderRadius : "4px" , overflow : "hidden" } } />
312223 { distance > 0 && (
313224 < Typography sx = { { mt : 1 } } >
0 commit comments