Initial implementation of custom profile editor + formatting

This commit is contained in:
ThePetrovich 2025-07-09 20:14:47 +08:00
parent 82b36f96d0
commit ffb27c2e0a
21 changed files with 3045 additions and 2034 deletions

View file

@ -1,154 +1,158 @@
<script lang="ts">
import { onMount, createEventDispatcher } from "svelte";
import * as L from "leaflet";
import { ruler, Ruler } from "$lib/ext/leaflet-ruler/leaflet-ruler";
import type { Map as LeafletMap, LayerGroup } from "leaflet";
import "leaflet/dist/leaflet.css";
import WindVisualization from "$lib/components/WindVisualisation.svelte";
import { distHaversine } from "$lib/mathutil";
import type { Prediction, Telemetry } from "$lib/types";
import { onMount, createEventDispatcher } from "svelte";
import * as L from "leaflet";
import { ruler, Ruler } from "$lib/ext/leaflet-ruler/leaflet-ruler";
import type { Map as LeafletMap, LayerGroup } from "leaflet";
import "leaflet/dist/leaflet.css";
import WindVisualization from "$lib/components/WindVisualisation.svelte";
import { distHaversine } from "$lib/mathutil";
import type { Prediction, Telemetry } from "$lib/types";
export let mode: "prediction" | "telemetry" = "prediction";
export let data: Prediction | Telemetry | null = null;
export let mode: "prediction" | "telemetry" = "prediction";
export let data: Prediction | Telemetry | null = null;
let map: LeafletMap;
let mapContainer: HTMLDivElement;
let plotLayerGroup: LayerGroup;
let mouseLat = 0;
let mouseLng = 0;
let isSelecting = false;
let map: LeafletMap;
let mapContainer: HTMLDivElement;
let plotLayerGroup: LayerGroup;
let mouseLat = 0;
let mouseLng = 0;
let isSelecting = false;
let windData: any;
let windData: any;
const dispatch = createEventDispatcher<{ coordinatesSelected: { lat: number; lng: number } }>();
const dispatch = createEventDispatcher<{ coordinatesSelected: { lat: number; lng: number } }>();
onMount(async () => {
if (!mapContainer) return;
onMount(async () => {
if (!mapContainer) return;
map = L.map(mapContainer, { zoomControl: false }).setView([51.505, -0.09], 13);
L.control.zoom({ position: "bottomleft" }).addTo(map);
map = L.map(mapContainer, { zoomControl: false }).setView([51.505, -0.09], 13);
L.control.zoom({ position: "bottomleft" }).addTo(map);
plotLayerGroup = L.layerGroup().addTo(map);
plotLayerGroup = L.layerGroup().addTo(map);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
ruler({
position: "bottomright",
}).addTo(map);
ruler({
position: "bottomright",
}).addTo(map);
const response = await fetch("src/routes/testVelo.json");
windData = await response.json();
const response = await fetch("src/routes/testVelo.json");
windData = await response.json();
map.on("mousemove", (e: any) => {
mouseLat = e.latlng.lat;
mouseLng = e.latlng.lng;
});
map.on("mousemove", (e: any) => {
mouseLat = e.latlng.lat;
mouseLng = e.latlng.lng;
});
map.on("click", (e: any) => {
if (isSelecting) {
dispatch("coordinatesSelected", { lat: e.latlng.lat, lng: e.latlng.lng });
stopSelection();
}
});
});
map.on("click", (e: any) => {
if (isSelecting) {
dispatch("coordinatesSelected", { lat: e.latlng.lat, lng: e.latlng.lng });
stopSelection();
}
});
});
$: if (map && data) {
plotData(data);
} else if (map) {
clearMapLayers();
}
$: if (map && data) {
plotData(data);
} else if (map) {
clearMapLayers();
}
export const startSelection = () => {
isSelecting = true;
if (mapContainer) mapContainer.style.cursor = "crosshair";
};
export const startSelection = () => {
isSelecting = true;
if (mapContainer) mapContainer.style.cursor = "crosshair";
};
export const stopSelection = () => {
isSelecting = false;
if (mapContainer) mapContainer.style.cursor = "";
};
export const stopSelection = () => {
isSelecting = false;
if (mapContainer) mapContainer.style.cursor = "";
};
export const plotData = (plotData: Prediction | Telemetry) => {
if (mode === "prediction") {
plotPrediction(plotData as Prediction);
} else if (mode === "telemetry") {
plotTelemetry(plotData as Telemetry);
}
};
export const plotData = (plotData: Prediction | Telemetry) => {
if (mode === "prediction") {
plotPrediction(plotData as Prediction);
} else if (mode === "telemetry") {
plotTelemetry(plotData as Telemetry);
}
};
export const clearMapLayers = () => {
plotLayerGroup?.clearLayers();
};
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 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: Prediction) => {
const { launch, landing, burst, flight_path, flight_time } = prediction;
const plotPrediction = (prediction: Prediction) => {
const { launch, landing, burst, flight_path, flight_time } = prediction;
const range = distHaversine(launch.latlng, landing.latlng, 1);
const f_hours = Math.floor(flight_time / 3600);
const f_minutes = Math.floor((flight_time % 3600) / 60).toString().padStart(2, "0");
const flighttime = `${f_hours}hr${f_minutes}`;
const range = distHaversine(launch.latlng, landing.latlng, 1);
const f_hours = Math.floor(flight_time / 3600);
const f_minutes = Math.floor((flight_time % 3600) / 60)
.toString()
.padStart(2, "0");
const flighttime = `${f_hours}hr${f_minutes}`;
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);
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);
L.polyline(flight_path, { weight: 3, color: "#000000" }).addTo(plotLayerGroup);
L.polyline(flight_path, { weight: 3, color: "#000000" }).addTo(plotLayerGroup);
map?.fitBounds(L.latLngBounds(flight_path));
};
map?.fitBounds(L.latLngBounds(flight_path));
};
const plotTelemetry = (telemetry: Telemetry) => {
L.marker(telemetry.launch.latlng, { title: `Launch`, icon: launchIcon }).addTo(plotLayerGroup);
const plotTelemetry = (telemetry: Telemetry) => {
L.marker(telemetry.launch.latlng, { title: `Launch`, icon: launchIcon }).addTo(plotLayerGroup);
telemetry.datapoints.forEach((point) => {
L.marker([point.latitude, point.longitude], {
title: `Telemetry at ${point.datetime}`,
icon: telemetryIcon,
})
.bindPopup(`<b>Telemetry Point</b><br>Lat: ${point.latitude.toFixed(6)}<br>Lon: ${point.longitude.toFixed(6)}`)
.addTo(plotLayerGroup);
});
telemetry.datapoints.forEach((point) => {
L.marker([point.latitude, point.longitude], {
title: `Telemetry at ${point.datetime}`,
icon: telemetryIcon,
})
.bindPopup(
`<b>Telemetry Point</b><br>Lat: ${point.latitude.toFixed(6)}<br>Lon: ${point.longitude.toFixed(6)}`,
)
.addTo(plotLayerGroup);
});
L.polyline(telemetry.flight_path, { weight: 3, color: "#000000" }).addTo(plotLayerGroup);
L.polyline(telemetry.flight_path, { weight: 3, color: "#000000" }).addTo(plotLayerGroup);
map?.fitBounds(L.latLngBounds(telemetry.flight_path));
};
map?.fitBounds(L.latLngBounds(telemetry.flight_path));
};
export const panTo = (lat: number, lng: number) => {
if (map) {
map.setView([lat, lng], map.getZoom());
}
};
export const panTo = (lat: number, lng: number) => {
if (map) {
map.setView([lat, lng], map.getZoom());
}
};
export const zoomTo = (lat: number, lng: number, zoomLevel: number) => {
if (map) {
map.setView([lat, lng], zoomLevel);
}
};
export const zoomTo = (lat: number, lng: number, zoomLevel: number) => {
if (map) {
map.setView([lat, lng], zoomLevel);
}
};
export const getMap = () => {
return map;
};
export const getMap = () => {
return map;
};
</script>
<div class="map-container" bind:this={mapContainer}>
<div class="card coordinates-display">
<p class="card-text">
<b>Lat:</b>
{mouseLat.toFixed(6)},
<b>Lon:</b>
{mouseLng.toFixed(6)}
</p>
</div>
<slot />
{#if map && windData}
<WindVisualization {map} windData={windData} />
{/if}
</div>
<div class="card coordinates-display">
<p class="card-text">
<b>Lat:</b>
{mouseLat.toFixed(6)},
<b>Lon:</b>
{mouseLng.toFixed(6)}
</p>
</div>
<slot />
{#if map && windData}
<WindVisualization {map} {windData} />
{/if}
</div>