mirror of
https://github.com/azaion/ui.git
synced 2026-04-23 04:56:34 +00:00
274800e508
- Port mission-planner flight planning to main app (Tailwind, react-leaflet v5, react-i18next) - Add FlightMap with click-to-add waypoints, draggable markers, polyline with arrows - Add FlightParamsPanel with action modes, waypoint list (drag-reorder), altitude chart, wind, JSON import/export - Add FlightListSidebar with create/delete and telemetry date - Add collapsible left panel with quick action mode shortcuts - Add work area / no-go zone drawing via manual mouse events (L.rectangle) - Add AltitudeDialog and JsonEditorDialog (Tailwind modals) - Add battery/time/distance calculations per waypoint segment - Add satellite/classic map toggle and mini-map on point drag - Add Camera FOV and Communication Addr fields - Add current position display under location search - Merge mission-planner translations under flights.planner.* - Gitignore .superpowers session data
147 lines
5.1 KiB
TypeScript
147 lines
5.1 KiB
TypeScript
import type { FlightPoint, CalculatedPointInfo, AircraftParams } from './types'
|
|
|
|
export function newGuid(): string {
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
const r = (Math.random() * 16) | 0
|
|
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
|
return v.toString(16)
|
|
})
|
|
}
|
|
|
|
export function calculateDistance(
|
|
point1: FlightPoint,
|
|
point2: FlightPoint,
|
|
aircraftType: string,
|
|
initialAltitude: number,
|
|
downang: number,
|
|
upang: number,
|
|
): number {
|
|
if (!point1?.position || !point2?.position) return 0
|
|
|
|
const R = 6371
|
|
const { lat: lat1, lng: lon1 } = point1.position
|
|
const { lat: lat2, lng: lon2 } = point2.position
|
|
const alt1 = point1.altitude || 0
|
|
const alt2 = point2.altitude || 0
|
|
|
|
const toRad = (value: number) => (value * Math.PI) / 180
|
|
const dLat = toRad(lat2 - lat1)
|
|
const dLon = toRad(lon2 - lon1)
|
|
|
|
const a =
|
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
|
|
Math.sin(dLon / 2) * Math.sin(dLon / 2)
|
|
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
|
const horizontalDistance = R * c
|
|
|
|
const initialAltitudeKm = initialAltitude / 1000
|
|
const altitude1Km = alt1 / 1000
|
|
const altitude2Km = alt2 / 1000
|
|
|
|
const descentAngleRad = toRad(downang || 0.01)
|
|
const ascentAngleRad = toRad(upang || 0.01)
|
|
|
|
if (aircraftType === 'Plane') {
|
|
const ascentDist = Math.max(0, (initialAltitudeKm - altitude1Km) / Math.sin(ascentAngleRad))
|
|
const descentDist = Math.max(0, (initialAltitudeKm - altitude2Km) / Math.sin(descentAngleRad))
|
|
const hAscent = Math.max(0, ascentDist * Math.cos(ascentAngleRad))
|
|
const hDescent = Math.max(0, descentDist * Math.cos(descentAngleRad))
|
|
return horizontalDistance - (hDescent + hAscent) + Math.max(0, descentDist) + Math.max(0, ascentDist)
|
|
}
|
|
|
|
const ascentVertical = Math.abs(initialAltitudeKm - altitude1Km)
|
|
const descentVertical = Math.abs(initialAltitudeKm - altitude2Km)
|
|
return ascentVertical + horizontalDistance + descentVertical
|
|
}
|
|
|
|
export async function getWeatherData(lat: number, lon: number) {
|
|
const apiKey = '335799082893fad97fa36118b131f919'
|
|
const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric`
|
|
try {
|
|
const res = await fetch(url)
|
|
const data = await res.json()
|
|
return { windSpeed: data.wind.speed as number, windAngle: data.wind.deg as number }
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export async function calculateBatteryPercentUsed(
|
|
groundSpeed: number,
|
|
time: number,
|
|
position: { lat: number; lon: number },
|
|
aircraft: AircraftParams,
|
|
): Promise<number> {
|
|
const weatherData = await getWeatherData(position.lat, position.lon)
|
|
const airDensity = 1.05
|
|
const groundSpeedMs = groundSpeed / 3.6
|
|
const headwind = (weatherData?.windSpeed ?? 0) * Math.cos((Math.PI / 180) * (weatherData?.windAngle ?? 0))
|
|
const effectiveAirspeed = groundSpeedMs + headwind
|
|
const drag = 0.5 * airDensity * (effectiveAirspeed ** 2) * aircraft.dragCoefficient * aircraft.frontalArea
|
|
const adjustedDrag = drag + aircraft.weight * 9.8 * 0.05
|
|
|
|
let watts = aircraft.thrustWatts[aircraft.thrustWatts.length - 1].watts
|
|
for (const item of aircraft.thrustWatts) {
|
|
const thrustN = (item.thrust / 1000) * 9.8
|
|
if (thrustN > adjustedDrag) { watts = item.watts; break }
|
|
}
|
|
const power = watts / aircraft.propellerEfficiency
|
|
const energyUsed = power * time
|
|
return Math.min((energyUsed / aircraft.batteryCapacity) * 100, 100)
|
|
}
|
|
|
|
export async function calculateAllPoints(
|
|
points: FlightPoint[],
|
|
aircraft: AircraftParams,
|
|
initialAltitude: number,
|
|
): Promise<CalculatedPointInfo[]> {
|
|
const infos: CalculatedPointInfo[] = [{ bat: 100, time: 0 }]
|
|
for (let i = 1; i < points.length; i++) {
|
|
const p1 = points[i - 1], p2 = points[i]
|
|
const dist = calculateDistance(p1, p2, aircraft.type, initialAltitude, aircraft.downang, aircraft.upang)
|
|
const time = dist / aircraft.speed
|
|
const midPos = { lat: (p1.position.lat + p2.position.lat) / 2, lon: (p1.position.lng + p2.position.lng) / 2 }
|
|
const pct = await calculateBatteryPercentUsed(aircraft.speed, time, midPos, aircraft)
|
|
infos.push({ bat: infos[i - 1].bat - pct, time: infos[i - 1].time + time })
|
|
}
|
|
return infos
|
|
}
|
|
|
|
export function parseCoordinates(input: string): { lat: number; lng: number } | null {
|
|
const cleaned = input.trim().replace(/[°NSEW]/gi, '')
|
|
const parts = cleaned.split(/[,\s]+/).filter(Boolean)
|
|
if (parts.length >= 2) {
|
|
const lat = parseFloat(parts[0])
|
|
const lng = parseFloat(parts[1])
|
|
if (!isNaN(lat) && !isNaN(lng) && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
|
|
return { lat, lng }
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
export function getMockAircraftParams(): AircraftParams {
|
|
return {
|
|
type: 'Plane',
|
|
downang: 40,
|
|
upang: 45,
|
|
weight: 3.4,
|
|
speed: 80,
|
|
frontalArea: 0.12,
|
|
dragCoefficient: 0.45,
|
|
batteryCapacity: 315,
|
|
thrustWatts: [
|
|
{ thrust: 500, watts: 55.5 },
|
|
{ thrust: 750, watts: 91.02 },
|
|
{ thrust: 1000, watts: 137.64 },
|
|
{ thrust: 1250, watts: 191 },
|
|
{ thrust: 1500, watts: 246 },
|
|
{ thrust: 1750, watts: 308 },
|
|
{ thrust: 2000, watts: 381 },
|
|
],
|
|
propellerEfficiency: 0.95,
|
|
}
|
|
}
|