diff --git a/src/lib/components/Map.svelte b/src/lib/components/Map.svelte index 64216e5..6b84be9 100644 --- a/src/lib/components/Map.svelte +++ b/src/lib/components/Map.svelte @@ -13,6 +13,7 @@ let map: MapLibreMap; let mapContainer: HTMLDivElement; let markers: Marker[] = []; + let animatedMarker: Marker | null = null; let mouseLat = 0; let mouseLng = 0; let isSelecting = false; @@ -102,6 +103,9 @@ markers.forEach((marker) => marker.remove()); markers = []; + // Remove animated marker + removeAnimatedMarker(); + // Remove all layers and sources related to flight paths if (map && map.getLayer("flight-path")) { map.removeLayer("flight-path"); @@ -132,7 +136,23 @@ el.style.backgroundSize = "100%"; el.title = title; - const marker = new maplibregl.Marker({ element: el }).setLngLat([lng, lat]).addTo(map); + // Create popup with coordinates + const popup = new maplibregl.Popup({ offset: 25, closeButton: false }).setHTML( + `${title}
Lat: ${lat.toFixed(6)}
Lng: ${lng.toFixed(6)}`, + ); + + const marker = new maplibregl.Marker({ element: el }) + .setLngLat([lng, lat]) + .setPopup(popup) + .addTo(map); + + // Show popup on hover + el.addEventListener("mouseenter", () => { + marker.togglePopup(); + }); + el.addEventListener("mouseleave", () => { + marker.togglePopup(); + }); markers.push(marker); return marker; @@ -147,7 +167,23 @@ el.style.backgroundSize = "100%"; el.title = title; - const marker = new maplibregl.Marker({ element: el }).setLngLat([lng, lat]).addTo(map); + // Create popup with coordinates + const popup = new maplibregl.Popup({ offset: 25, closeButton: false }).setHTML( + `${title}
Lat: ${lat.toFixed(6)}
Lng: ${lng.toFixed(6)}`, + ); + + const marker = new maplibregl.Marker({ element: el }) + .setLngLat([lng, lat]) + .setPopup(popup) + .addTo(map); + + // Show popup on hover + el.addEventListener("mouseenter", () => { + marker.togglePopup(); + }); + el.addEventListener("mouseleave", () => { + marker.togglePopup(); + }); markers.push(marker); return marker; @@ -319,6 +355,39 @@ export const getMap = () => { return map; }; + + export const updateAnimatedMarker = (lat: number, lng: number) => { + if (!map) return; + + if (!animatedMarker) { + // Create animated marker + const el = document.createElement("div"); + el.className = "animated-marker"; + el.innerHTML = ` + + + + + `; + + animatedMarker = new maplibregl.Marker({ element: el, anchor: "center" }) + .setLngLat([lng, lat]) + .addTo(map); + } else { + // Update position + animatedMarker.setLngLat([lng, lat]); + } + + // Pan to marker + map.panTo([lng, lat], { duration: 100 }); + }; + + export const removeAnimatedMarker = () => { + if (animatedMarker) { + animatedMarker.remove(); + animatedMarker = null; + } + };
@@ -336,30 +405,24 @@ {/if}
- + + :global(.animated-marker .pulse-ring) { + animation: pulse 2s ease-out infinite; + transform-origin: center; + } + + @keyframes :global(pulse) { + 0% { + r: 8; + opacity: 0.8; + } + 100% { + r: 14; + opacity: 0; + } + } + diff --git a/src/lib/components/TimeLine.svelte b/src/lib/components/TimeLine.svelte new file mode 100644 index 0000000..7526a4a --- /dev/null +++ b/src/lib/components/TimeLine.svelte @@ -0,0 +1,393 @@ + + +
+
+
+ Time: + {timeElapsed} +
+ {#if currentPosition} +
+ Altitude: + {Math.round(currentPosition.alt)} m +
+
+ Position: + {currentPosition.lat.toFixed(4)}, {currentPosition.lng.toFixed(4)} +
+ {/if} +
+ +
+
+ + + {#if !isPlaying} + + {:else} + + {/if} + + +
+ +
+ +
+
+
+
+ + diff --git a/src/routes/predict/+page.svelte b/src/routes/predict/+page.svelte index d27d365..bed94a6 100644 --- a/src/routes/predict/+page.svelte +++ b/src/routes/predict/+page.svelte @@ -6,6 +6,7 @@ import ScenarioPanel from "$lib/components/ScenarioPanel.svelte"; import TabComponent from "$lib/components/TabComponent.svelte"; import PointEditor from "$lib/components/PointEditor.svelte"; + import TimeLine from "$lib/components/TimeLine.svelte"; import { onMount } from "svelte"; import { PredictionStore } from "$lib/stores"; import { addToast, removeToast } from "$lib/components/Toast.svelte"; @@ -69,7 +70,10 @@ } } - + function handleTimeUpdate(event: CustomEvent<{ index: number; lat: number; lng: number; alt: number; datetime: Date }>) { + const { lat, lng } = event.detail; + map?.updateAnimatedMarker(lat, lng); + } @@ -101,5 +105,8 @@ + {#if $PredictionStore} + + {/if}