Refactor of map & other components
This commit is contained in:
parent
527d4417ff
commit
c7df38e6ce
10 changed files with 532 additions and 466 deletions
|
|
@ -1,239 +1,173 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import * as L from 'leaflet';
|
||||
import type { Map as LeafletMap } from 'leaflet';
|
||||
import type { Marker } from 'leaflet';
|
||||
import type { LatLng } from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import VelocityLayer from './velocity.svelte';
|
||||
import { distHaversine } from '../lib/mathutil.ts';
|
||||
import { onMount, createEventDispatcher } from "svelte";
|
||||
import * as L from "leaflet";
|
||||
import type { Map as LeafletMap, LayerGroup } from "leaflet";
|
||||
type LatLngExpression = [number, number] | { lat: number; lng: number };
|
||||
type LatLngLiteral = { lat: number; lng: number };
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import VelocityLayer from "./velocity.svelte";
|
||||
import { distHaversine } from "../lib/mathutil.ts";
|
||||
|
||||
import { latestPredictionParsed } from '../lib/prediction.ts';
|
||||
import { latestTelemetryParsed } from '../lib/telemetry.ts';
|
||||
interface Point {
|
||||
latlng: LatLngLiteral & { alt: number };
|
||||
datetime: Date;
|
||||
}
|
||||
|
||||
let map: typeof LeafletMap;
|
||||
interface PredictionData {
|
||||
launch: Point;
|
||||
landing: Point;
|
||||
burst: Point;
|
||||
flight_path: LatLngExpression[];
|
||||
flight_time: number;
|
||||
}
|
||||
|
||||
interface TelemetryPoint {
|
||||
altitude: number;
|
||||
datetime: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
|
||||
interface TelemetryData {
|
||||
launch: Point;
|
||||
datapoints: TelemetryPoint[];
|
||||
flight_path: LatLngExpression[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {'prediction' | 'telemetry'}
|
||||
*/
|
||||
export let mode: "prediction" | "telemetry" = "prediction";
|
||||
export let data: PredictionData | TelemetryData | null = null;
|
||||
|
||||
let map: typeof LeafletMap | undefined;
|
||||
let mapContainer: HTMLDivElement;
|
||||
let plotLayerGroup: typeof LayerGroup;
|
||||
let mouseLat = 0;
|
||||
let mouseLng = 0;
|
||||
let inputLat = '56.3576';
|
||||
let inputLng = '39.8666';
|
||||
let mapContainer;
|
||||
let isSelecting = false;
|
||||
|
||||
let velocityOptions = {
|
||||
displayValues: true,
|
||||
displayOptions: {
|
||||
velocityType: 'Global Wind',
|
||||
position: 'bottomleft',
|
||||
emptyString: 'No velocity data',
|
||||
velocityType: "Global Wind",
|
||||
position: "bottomleft",
|
||||
emptyString: "No velocity data",
|
||||
},
|
||||
data: null, // здесь будут ваши данные
|
||||
data: null,
|
||||
};
|
||||
let marker: typeof Marker | null = null;
|
||||
|
||||
export { mouseLat, mouseLng, inputLat, inputLng, updateMapPosition };
|
||||
const dispatch = createEventDispatcher<{ coordinatesSelected: { lat: number; lng: number } }>();
|
||||
|
||||
const updateMapPosition = () => {
|
||||
const lat = parseFloat(inputLat);
|
||||
const lng = parseFloat(inputLng);
|
||||
|
||||
if (isNaN(lat)) {
|
||||
alert("Please enter a valid latitude");
|
||||
return;
|
||||
}
|
||||
if (isNaN(lng)) {
|
||||
alert("Please enter a valid longitude");
|
||||
return;
|
||||
}
|
||||
if (lat < -90 || lat > 90) {
|
||||
alert("Latitude must be between -90 and 90");
|
||||
return;
|
||||
}
|
||||
if (lng < -180 || lng > 180) {
|
||||
alert("Longitude must be between -180 and 180");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove existing marker if it exists
|
||||
if (marker) {
|
||||
map.removeLayer(marker);
|
||||
}
|
||||
|
||||
// Create new marker
|
||||
marker = L.marker([lat, lng]).addTo(map)
|
||||
.bindPopup("Launch Point");
|
||||
|
||||
// Center map on new coordinates
|
||||
map.setView([lat, lng], map.getZoom());
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
map = L.map('map').setView([51.505, -0.09], 13);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
if (!mapContainer) return;
|
||||
|
||||
map = L.map(mapContainer).setView([51.505, -0.09], 13);
|
||||
plotLayerGroup = L.layerGroup().addTo(map);
|
||||
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
}).addTo(map);
|
||||
|
||||
// Загрузка данных для velocity (пример)
|
||||
const response = await fetch('src/routes/testVelo.json');
|
||||
const response = await fetch("src/routes/testVelo.json");
|
||||
velocityOptions.data = await response.json();
|
||||
|
||||
map.on('mousemove', (e: any) => {
|
||||
mouseLat = e.latlng.lat.toFixed(6);
|
||||
mouseLng = e.latlng.lng.toFixed(6);
|
||||
map.on("mousemove", (e: any) => {
|
||||
mouseLat = e.latlng.lat;
|
||||
mouseLng = e.latlng.lng;
|
||||
});
|
||||
|
||||
marker = L.marker([56.3576, 39.8666]).addTo(map)
|
||||
.bindPopup(() => {
|
||||
return `
|
||||
<b>Launch Point</b><br>, Lat: ${marker.getLatLng().lat.toFixed(6)}<br>, Long: ${marker.getLatLng().lng.toFixed(6)}<br>
|
||||
`;
|
||||
});
|
||||
|
||||
latestPredictionParsed.subscribe((prediction) => {
|
||||
if (prediction) {
|
||||
plotPrediction(prediction);
|
||||
map.on("click", (e: any) => {
|
||||
if (isSelecting) {
|
||||
dispatch("coordinatesSelected", { lat: e.latlng.lat, lng: e.latlng.lng });
|
||||
stopSelection();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const plotPrediction = (prediction: any) => {
|
||||
console.log("Flight data parsed, creating map plot...");
|
||||
$: if (map && data) {
|
||||
plotData(data);
|
||||
} else if (map) {
|
||||
clearMapLayers();
|
||||
}
|
||||
|
||||
// Clear existing map items
|
||||
if (marker) {
|
||||
map.eachLayer((layer: any) => {
|
||||
if (layer instanceof L.Marker || layer instanceof L.Polyline) {
|
||||
map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
export const startSelection = () => {
|
||||
isSelecting = true;
|
||||
if (mapContainer) mapContainer.style.cursor = "crosshair";
|
||||
};
|
||||
|
||||
export const stopSelection = () => {
|
||||
isSelecting = false;
|
||||
if (mapContainer) mapContainer.style.cursor = "";
|
||||
};
|
||||
|
||||
export const plotData = (plotData: PredictionData | TelemetryData) => {
|
||||
if (mode === "prediction") {
|
||||
plotPrediction(plotData as PredictionData);
|
||||
} else if (mode === "telemetry") {
|
||||
plotTelemetry(plotData as TelemetryData);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearMapLayers = () => {
|
||||
plotLayerGroup?.clearLayers();
|
||||
};
|
||||
|
||||
const launchIcon = L.icon({ iconUrl: "target-blue.png", iconSize: [10, 10], iconAnchor: [5, 5] });
|
||||
const landIcon = L.icon({ iconUrl: "target-red.png", iconSize: [10, 10], iconAnchor: [5, 5] });
|
||||
const burstIcon = L.icon({ iconUrl: "pop-marker.png", iconSize: [16, 16], iconAnchor: [8, 8] });
|
||||
const telemetryIcon = L.icon({ iconUrl: "marker-sm-red.png", iconSize: [10, 10], iconAnchor: [5, 5] });
|
||||
|
||||
const plotPrediction = (prediction: PredictionData) => {
|
||||
const { launch, landing, burst, flight_path, flight_time } = prediction;
|
||||
|
||||
// Calculate range and time of flight
|
||||
const range = distHaversine(launch.latlng, landing.latlng, 1);
|
||||
const f_hours = Math.floor(flight_time / 3600);
|
||||
const f_minutes = Math.floor(((flight_time % 86400) % 3600) / 60).toString().padStart(2, '0');
|
||||
const f_minutes = Math.floor((flight_time % 3600) / 60).toString().padStart(2, "0");
|
||||
const flighttime = `${f_hours}hr${f_minutes}`;
|
||||
console.log(`Range: ${range}, Flight Time: ${flighttime}`);
|
||||
|
||||
// Create custom icons
|
||||
const launchIcon = L.icon({
|
||||
iconUrl: 'target-blue.png',
|
||||
iconSize: [10, 10],
|
||||
iconAnchor: [5, 5],
|
||||
});
|
||||
L.marker(launch.latlng, { title: `Launch`, icon: launchIcon }).addTo(plotLayerGroup);
|
||||
L.marker(landing.latlng, { title: `Landing`, icon: landIcon }).addTo(plotLayerGroup);
|
||||
L.marker(burst.latlng, { title: `Burst`, icon: burstIcon }).addTo(plotLayerGroup);
|
||||
|
||||
const landIcon = L.icon({
|
||||
iconUrl: 'target-red.png',
|
||||
iconSize: [10, 10],
|
||||
iconAnchor: [5, 5],
|
||||
});
|
||||
L.polyline(flight_path, { weight: 3, color: "#000000" }).addTo(plotLayerGroup);
|
||||
|
||||
const burstIcon = L.icon({
|
||||
iconUrl: 'pop-marker.png',
|
||||
iconSize: [16, 16],
|
||||
iconAnchor: [8, 8],
|
||||
});
|
||||
|
||||
// Add markers to the map
|
||||
const launchMarker = L.marker(launch.latlng, {
|
||||
title: `Launch (${launch.latlng.lat.toFixed(4)}, ${launch.latlng.lng.toFixed(4)}) at ${launch.datetime.toUTCString()}`,
|
||||
icon: launchIcon,
|
||||
}).addTo(map);
|
||||
|
||||
const landMarker = L.marker(landing.latlng, {
|
||||
title: `Landing (${landing.latlng.lat.toFixed(4)}, ${landing.latlng.lng.toFixed(4)}) at ${landing.datetime.toUTCString()}`,
|
||||
icon: landIcon,
|
||||
}).addTo(map);
|
||||
|
||||
const burstMarker = L.marker(burst.latlng, {
|
||||
title: `Burst (${burst.latlng.lat.toFixed(4)}, ${burst.latlng.lng.toFixed(4)} at altitude ${burst.latlng.alt.toFixed(0)}) at ${burst.datetime.toUTCString()}`,
|
||||
icon: burstIcon,
|
||||
}).addTo(map);
|
||||
|
||||
// Add flight path polyline
|
||||
const pathPolyline = L.polyline(flight_path, {
|
||||
weight: 3,
|
||||
color: "#000000",
|
||||
}).addTo(map);
|
||||
|
||||
// Center the map on the launch point
|
||||
map.setView(launch.latlng, 8);
|
||||
map?.fitBounds(L.latLngBounds(flight_path));
|
||||
};
|
||||
|
||||
const plotTelemetryTrack = (telemetry: any) => {
|
||||
console.log("Telemetry data parsed, creating map plot...");
|
||||
const plotTelemetry = (telemetry: TelemetryData) => {
|
||||
L.marker(telemetry.launch.latlng, { title: `Launch`, icon: launchIcon }).addTo(plotLayerGroup);
|
||||
|
||||
// Clear existing map items
|
||||
if (marker) {
|
||||
map.eachLayer((layer: any) => {
|
||||
if (layer instanceof L.Marker || layer instanceof L.Polyline) {
|
||||
map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Create custom icons for telemetry markers
|
||||
const telemetryIcon = L.icon({
|
||||
iconUrl: 'marker-sm-red.png',
|
||||
iconSize: [10, 10],
|
||||
iconAnchor: [5, 5],
|
||||
});
|
||||
|
||||
const launchIcon = L.icon({
|
||||
iconUrl: 'target-blue.png',
|
||||
iconSize: [10, 10],
|
||||
iconAnchor: [5, 5],
|
||||
});
|
||||
|
||||
// Add markers to the map
|
||||
const launchMarker = L.marker(telemetry.launch.latlng, {
|
||||
title: `Launch (${telemetry.launch.latlng.lat.toFixed(4)}, ${telemetry.launch.latlng.lng.toFixed(4)}) at ${telemetry.launch.datetime.toUTCString()}`,
|
||||
icon: launchIcon,
|
||||
}).addTo(map);
|
||||
|
||||
// interface TelemetryPoint {
|
||||
// altitude: number;
|
||||
// datetime: string;
|
||||
// latitude: number;
|
||||
// longitude: number;
|
||||
// payload: string;
|
||||
// }
|
||||
|
||||
// Add telemetry markers to the map
|
||||
telemetry.datapoints.forEach((point: any) => {
|
||||
const telemetryMarker = L.marker([point.latitude, point.longitude], {
|
||||
title: `Telemetry (${point.latitude.toFixed(4)}, ${point.longitude.toFixed(4)}) at ${point.datetime}`,
|
||||
telemetry.datapoints.forEach((point) => {
|
||||
L.marker([point.latitude, point.longitude], {
|
||||
title: `Telemetry at ${point.datetime}`,
|
||||
icon: telemetryIcon,
|
||||
}).addTo(map)
|
||||
.bindPopup(() => {
|
||||
return `
|
||||
<b>Telemetry Point</b><br>, Lat: ${point.latitude.toFixed(6)}<br>, Lon: ${point.longitude.toFixed(6)}<br>
|
||||
`;
|
||||
});
|
||||
})
|
||||
.bindPopup(`<b>Telemetry Point</b><br>Lat: ${point.latitude.toFixed(6)}<br>Lon: ${point.longitude.toFixed(6)}`)
|
||||
.addTo(plotLayerGroup);
|
||||
});
|
||||
|
||||
// Add flight path polyline
|
||||
const pathPolyline = L.polyline(telemetry.flight_path, {
|
||||
weight: 3,
|
||||
color: "#000000",
|
||||
}).addTo(map);
|
||||
};
|
||||
L.polyline(telemetry.flight_path, { weight: 3, color: "#000000" }).addTo(plotLayerGroup);
|
||||
|
||||
map?.fitBounds(L.latLngBounds(telemetry.flight_path));
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="map-container">
|
||||
<div id="map"></div>
|
||||
<div class="map-container" bind:this={mapContainer}>
|
||||
<div class="card coordinates-display">
|
||||
<p class="card-text"><b>Lat:</b> {mouseLat}, <b>Lon:</b> {mouseLng}</p>
|
||||
<p class="card-text">
|
||||
<b>Lat:</b>
|
||||
{mouseLat.toFixed(6)},
|
||||
<b>Lon:</b>
|
||||
{mouseLng.toFixed(6)}
|
||||
</p>
|
||||
</div>
|
||||
<div class="panel-container">
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<div bind:this={mapContainer} style="width: 100%; height: 100vh;">
|
||||
{#if map}
|
||||
<VelocityLayer {map} {velocityOptions} />
|
||||
{/if}
|
||||
{#if map}
|
||||
<VelocityLayer {map} {velocityOptions} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
@ -243,26 +177,19 @@
|
|||
height: 100vh;
|
||||
}
|
||||
|
||||
#map {
|
||||
width: 100%;
|
||||
height: calc(100% - 44px); /* Adjust height to account for navbar */
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.coordinates-display {
|
||||
position: absolute;
|
||||
top: 54px;
|
||||
right: 10px;
|
||||
background: #fff; /* Remove transparency */
|
||||
padding: 3px 8px; /* Reduce padding */
|
||||
background: #fff;
|
||||
padding: 3px 8px;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
z-index: 1000; /* Ensure it's above the map */
|
||||
z-index: 1000;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid #ccc; /* Add card border */
|
||||
width: 150px; /* Fixed width */
|
||||
border: 1px solid #ccc;
|
||||
width: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.panel-container {
|
||||
|
|
@ -271,4 +198,4 @@
|
|||
right: 20px;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue