diff --git a/jsconfig.json b/jsconfig.json index 0b2d886..c4103ce 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": true, "checkJs": true, + "allowImportingTsExtensions": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, diff --git a/src/lib/mathutil.ts b/src/lib/mathutil.ts new file mode 100644 index 0000000..75bcebe --- /dev/null +++ b/src/lib/mathutil.ts @@ -0,0 +1,24 @@ +export function distHaversine( + p1: { lat: number; lng: number }, + p2: { lat: number; lng: number }, + precision?: number +): string { + const R = 6371; // Earth's mean radius in km + + const rad = (x: number): number => (x * Math.PI) / 180; + + const dLat = rad(p2.lat - p1.lat); + const dLong = rad(p2.lng - p1.lng); + + const a = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(rad(p1.lat)) * + Math.cos(rad(p2.lat)) * + Math.sin(dLong / 2) * + Math.sin(dLong / 2); + + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + const d = R * c; + + return d.toFixed(precision ?? 3); +} \ No newline at end of file diff --git a/src/lib/prediction.ts b/src/lib/prediction.ts new file mode 100644 index 0000000..a5e60c6 --- /dev/null +++ b/src/lib/prediction.ts @@ -0,0 +1,198 @@ +import { writable } from "svelte/store" +import type { LatLngExpression } from "leaflet"; +import L from "leaflet"; + +interface TrajectoryPoint { + altitude: number; + datetime: string; + latitude: number; + longitude: number; +} + +interface PredictionStage { + stage: string; + trajectory: TrajectoryPoint[]; +} + +interface ParsedPrediction { + flight_path: [number, number, number][]; + launch: { + latlng: LatLngExpression; + datetime: Date; + }; + burst: { + latlng: LatLngExpression; + datetime: Date; + }; + landing: { + latlng: LatLngExpression; + datetime: Date; + }; + profile: string; + flight_time: number; +} + + +export const latestPrediction = writable({ + metadata: { + complete_datetime: "", + start_datetime: "" + }, + prediction: [ + { + stage: "", + trajectory: [ + { + altitude: 0.0, + datetime: "", + latitude: 0.0, + longitude: 0.0 + } + ] + } + ] +}); + +export const latestPredictionParsed = writable({} as ParsedPrediction); + +function getLatestDataset() { + const now = new Date(); + const hours = now.getUTCHours(); + const minutes = now.getUTCMinutes(); + const seconds = now.getUTCSeconds(); + + // Round down to the nearest 6-hour interval + const roundedHours = Math.floor(hours / 6) * 6; + const roundedDate = new Date(now); + roundedDate.setUTCHours(roundedHours, 0, 0, 0); + + // Subtract 6 hours to account for the lag + roundedDate.setUTCHours(roundedDate.getUTCHours() - 6); + + return roundedDate.toISOString(); +} + +function formatLaunchDateTime(dateObj: string | Date, timeStr: string): string { + // Ensure date is a Date object + const date = new Date(dateObj); + + // Extract date components + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + + // Format time (ensure it has seconds) + let formattedTime = timeStr; + if (timeStr.split(':').length === 2) { + formattedTime += ':00'; // Add seconds if missing + } + + // Combine into ISO string + const isoString = new Date(`${year}-${month}-${day}T${formattedTime}Z`).toISOString(); + + return isoString; +} + +export const getForecast = async (flightParameters: Record, startDate: string, startTime: string): Promise => { + const launch_datetime = formatLaunchDateTime(startDate, startTime); + + // Create request object + flightParameters.dataset = getLatestDataset(); + flightParameters.launch_datetime = launch_datetime; + + console.log("Sending request:", flightParameters); + + try { + // Example POST request - replace with your actual API endpoint + const response = await fetch('http://127.0.0.1:8000/api/predictions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(flightParameters) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + console.log("Forecast response:", data); + + latestPrediction.set(data.result); + latestPredictionParsed.set(parsePrediction(data.result.prediction)); + + alert("Forecast request successful!"); + // Handle the response data as needed + } catch (error) { + console.error("Error sending forecast request:", error); + alert("Error getting forecast: " + error); + } +}; + +export function parsePrediction(prediction: PredictionStage[]): ParsedPrediction { + const flight_path: [number, number, number][] = []; + const launch: { latlng: LatLngExpression; datetime: Date } = {} as any; + const burst: { latlng: LatLngExpression; datetime: Date } = {} as any; + const landing: { latlng: LatLngExpression; datetime: Date } = {} as any; + + const ascent = prediction[0].trajectory; + const descent = prediction[1].trajectory; + + // Add the ascent track to the flight path array. + ascent.forEach((item) => { + let lon = item.longitude; + if (lon > 180.0) { + lon -= 360.0; + } + + flight_path.push([item.latitude, lon, item.altitude]); + }); + + // Add the descent track to the flight path array. + descent.forEach((item) => { + let lon = item.longitude; + if (lon > 180.0) { + lon -= 360.0; + } + + flight_path.push([item.latitude, lon, item.altitude]); + }); + + // Populate the launch, burst, and landing points + const launchObj = ascent[0]; + let lon = launchObj.longitude; + if (lon > 180.0) { + lon -= 360.0; + } + launch.latlng = L.latLng([launchObj.latitude, lon, launchObj.altitude]); + launch.datetime = new Date(launchObj.datetime); + + const burstObj = descent[0]; + lon = burstObj.longitude; + if (lon > 180.0) { + lon -= 360.0; + } + burst.latlng = L.latLng([burstObj.latitude, lon, burstObj.altitude]); + burst.datetime = new Date(burstObj.datetime); + + const landingObj = descent[descent.length - 1]; + lon = landingObj.longitude; + if (lon > 180.0) { + lon -= 360.0; + } + landing.latlng = L.latLng([landingObj.latitude, lon, landingObj.altitude]); + landing.datetime = new Date(landingObj.datetime); + + const profile = prediction[1].stage === "descent" ? "standard_profile" : "float_profile"; + const flight_time = (new Date(landing.datetime).getTime() - new Date(launch.datetime).getTime()) / 1000; + + return { + flight_path, + launch, + burst, + landing, + profile, + flight_time, + }; +} diff --git a/src/routes/BurstCalculator.svelte b/src/routes/BurstCalculator.svelte deleted file mode 100644 index 48a68b8..0000000 --- a/src/routes/BurstCalculator.svelte +++ /dev/null @@ -1,194 +0,0 @@ - \ No newline at end of file diff --git a/src/routes/ControlPanel.svelte b/src/routes/ControlPanel.svelte index 3f9c82d..b4adb66 100644 --- a/src/routes/ControlPanel.svelte +++ b/src/routes/ControlPanel.svelte @@ -1,114 +1,57 @@ -
+
-
Prediction Parameters
+
Параметры прогнозирования
- +
- + / - +
+ get_map_position(); + }}>Указать на карте
- - + +
- +
- +
- - + +
- - + +
- - + +
- +
- flightParameters.profile = profileMap[profile] || 'standard_profile'}> -
- - - + + +
{/if} diff --git a/src/routes/leaflet.svelte b/src/routes/leaflet.svelte deleted file mode 100644 index 014cb60..0000000 --- a/src/routes/leaflet.svelte +++ /dev/null @@ -1,120 +0,0 @@ - - -
-
-
- Lat: {mouseLat}, Long: {mouseLng} -
-
- diff --git a/src/routes/map.svelte b/src/routes/map.svelte index 270704f..840212c 100644 --- a/src/routes/map.svelte +++ b/src/routes/map.svelte @@ -2,9 +2,12 @@ import { onMount } from 'svelte'; import * as L from 'leaflet'; import 'leaflet/dist/leaflet.css'; + import { distHaversine } from '../lib/mathutil.ts'; + + import { latestPredictionParsed } from '../lib/prediction.ts'; /** - * @type {{ removeLayer: (arg0: any) => void; setView: (arg0: number[], arg1: any) => void; getZoom: () => any; on: (arg0: string, arg1: (e: any) => void) => void; }} + * @type {L.Map} */ let map; let mouseLat = 0; @@ -70,7 +73,80 @@ Launch Point
, Lat: ${marker.getLatLng().lat.toFixed(6)}
, Long: ${marker.getLatLng().lng.toFixed(6)}
`; }); + + latestPredictionParsed.subscribe((prediction) => { + if (prediction) { + plotPrediction(prediction); + } + }); }); + + const plotPrediction = (prediction) => { + console.log("Flight data parsed, creating map plot..."); + + // Clear existing map items + if (marker) { + map.eachLayer((layer) => { + if (layer instanceof L.Marker || layer instanceof L.Polyline) { + map.removeLayer(layer); + } + }); + } + + 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 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], + }); + + 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], + }); + + // 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); + }; +
diff --git a/static/pop-marker.png b/static/pop-marker.png new file mode 100644 index 0000000..a111bfe Binary files /dev/null and b/static/pop-marker.png differ diff --git a/static/target-blue.png b/static/target-blue.png new file mode 100644 index 0000000..6ef9e5b Binary files /dev/null and b/static/target-blue.png differ diff --git a/static/target-red.png b/static/target-red.png new file mode 100644 index 0000000..c3222b8 Binary files /dev/null and b/static/target-red.png differ