diff --git a/src/Data_Source/Data_Source_Aircraft_Stream.ts b/src/Data_Source/Data_Source_Aircraft_Stream.ts index 1e9e3bc..5bf9cbc 100644 --- a/src/Data_Source/Data_Source_Aircraft_Stream.ts +++ b/src/Data_Source/Data_Source_Aircraft_Stream.ts @@ -78,7 +78,6 @@ class Data_Source_Aircraft_Stream { source.apply_aircraft_delta(Array.isArray(source_update.change_list) ? source_update.change_list : [], Array.isArray(source_update.removed_icaos) ? source_update.removed_icaos : []); source.apply_track_stream_list(Array.isArray(source_update.tracks) ? source_update.tracks : []); } - app.cesium_map?.request_sync_data_sources(); } } diff --git a/src/Data_Source/Data_Source_Map_Runtime.ts b/src/Data_Source/Data_Source_Map_Runtime.ts index 132f745..26df0c6 100644 --- a/src/Data_Source/Data_Source_Map_Runtime.ts +++ b/src/Data_Source/Data_Source_Map_Runtime.ts @@ -164,6 +164,7 @@ export class Data_Source_Map_Runtime { aircraft.refresh_from_change_list(item); this.aircraft_map.set(item.icao, aircraft); this.aircraft_change_versions.set(item.icao, String(item.change_version || "")); + app.cesium_map?.update_aircraft(aircraft); } this.refresh_aircraft_range(); } @@ -173,6 +174,7 @@ export class Data_Source_Map_Runtime { if (!aircraft) return; aircraft.hide_track(); aircraft.pos_marker.remove(); + app.cesium_map?.remove_aircraft(app.cesium_map.entity_key(this.source.key, icao)); this.aircraft_map.delete(icao); this.aircraft_change_versions.delete(icao); if (this.active_aircraft === aircraft) this.active_aircraft = null; @@ -216,6 +218,7 @@ export class Data_Source_Map_Runtime { if (!aircraft) continue; aircraft.is_show_path = true; aircraft.append_path_response(item); + app.cesium_map?.update_aircraft(aircraft); } } } diff --git a/src/Map/Aircraft.tsx b/src/Map/Aircraft.tsx index 27c0498..4731162 100644 --- a/src/Map/Aircraft.tsx +++ b/src/Map/Aircraft.tsx @@ -4,7 +4,7 @@ import {app} from "../App.tsx" import {baseURL, lightenColor, StateHolder} from "../Global.ts"; import axios from "axios"; import {Data_Source} from "../Data_Source/Data_Source.tsx"; -import {Aircraft_Model, Aircraft_Status, get_aircraft_angle, orientation_from_item} from "./Aircraft_Model.tsx"; +import {Aircraft_Model, Aircraft_Status} from "./Aircraft_Model.tsx"; class Aircraft_Marker extends Marker { constructor(latlng: LatLngExpression, options?: MarkerOptions) { @@ -74,7 +74,6 @@ export class Aircraft { mlat: boolean track_point_list: Aircraft_Track_Point[] = [] - data_list: any[] = []; data_model: Aircraft_Model; status = new StateHolder(Aircraft_Status.Need_Init); angle = new StateHolder(0); @@ -113,17 +112,16 @@ export class Aircraft { const svg = this.generateSVG(color, size, rotationDegree); const selected = this.data_source.active_aircraft === this; const outline = selected ? "filter: drop-shadow(1px 0 0 #111) drop-shadow(-1px 0 0 #111) drop-shadow(0 1px 0 #111) drop-shadow(0 -1px 0 #111) drop-shadow(0 0 4px #fff);" : ""; - - const positionStyle = this.get_label_position(size); - - const lines: string[] = []; if (style.show_icao) lines.push(this.icao); if (style.show_call_sign) lines.push(this.call_sign); if (style.show_fly_status) lines.push(this.fly_status); + if (style.show_heading) { + lines.push(`航向 H ${this.data_model.heading.toFixed(1)}°`); + lines.push(`vortex_type ${this.data_model.vortexType ?? "未知"}`); + } let label=`` - if (lines.length != 0) { label = `
@@ -131,8 +129,6 @@ export class Aircraft {
`; } - - return L.divIcon({ html: `
@@ -193,27 +189,13 @@ export class Aircraft { }); this.refresh(); } - - - refresh_pos_by_track() { - let list = this.data_list; - if (list.length >= 1) { - let last_data = list[list.length - 1]; - let last_pos = L.latLng(last_data.lat, last_data.lon); - if (list.length >= 2) { - let second_last_data = list[list.length - 2]; - let second_last_pos = L.latLng(second_last_data.lat, second_last_data.lon, second_last_data.alt); - let angle = get_aircraft_angle(second_last_pos, last_pos); - this.angle.set(angle); - // @ts-ignore - this.pos_marker.setRotationAngle(this.angle); - } - this.pos_marker.setLatLng(last_pos); - } + const points = this.data_model.track_points; + if (points.length === 0) return; + const last = points[points.length - 1]; + this.set_angle(this.data_model.heading) + this.pos_marker.setLatLng(L.latLng(last.lat, last.lon, last.alt)) } - - refresh_from_change_list(item) { const previous_alt = this.data_model.status === Aircraft_Status.Need_Init ? undefined : this.data_model.alt this.data_model.update_from_change(item, previous_alt) @@ -228,13 +210,8 @@ export class Aircraft { set_pos(lat: number, lon: number, alt : number) { let pos = L.latLng(lat, lon, alt); this.pos = pos; - let old_pos = this.pos_marker.getLatLng(); - if (pos.lat != old_pos.lat || pos.lng != old_pos.lng) { - let angle = get_aircraft_angle(old_pos, pos); - this.set_angle(angle) - this.pos_marker.setLatLng(pos); - } - } + this.pos_marker.setLatLng(pos); + } apply_track_data(data: any) { if (!data) { @@ -249,13 +226,9 @@ export class Aircraft { } this.last_size = end_seq; if (data.track_orientation) { - this.data_model.trackOrientation = orientation_from_item(data, this.data_model.trackOrientation); - this.data_model.heading = this.data_model.trackOrientation.heading; - this.data_model.pitch = this.data_model.trackOrientation.pitch; - this.data_model.roll = this.data_model.trackOrientation.roll; + this.data_model.update_orientation(data) } this.data_model.append_track_points(list) - this.data_list.push(...list); return list } diff --git a/src/Map/Aircraft_Model.tsx b/src/Map/Aircraft_Model.tsx index cc38114..86b76d3 100644 --- a/src/Map/Aircraft_Model.tsx +++ b/src/Map/Aircraft_Model.tsx @@ -46,10 +46,7 @@ export class Aircraft_Model { this.lat = Number(item.latitude) this.lon = Number(item.longitude) this.alt = Number(item.altitude || 0) - this.trackOrientation = orientation_from_item(item, this.trackOrientation) - this.heading = this.trackOrientation.heading - this.pitch = this.trackOrientation.pitch - this.roll = this.trackOrientation.roll + this.update_orientation(item) this.timestamp = item.utc ?? item.timestamp ?? item.last_seen ?? this.timestamp this.call_sign = item.call_sign ?? this.call_sign this.vortexType = typeof item.vortex_type === "number" ? item.vortex_type : this.vortexType @@ -75,6 +72,24 @@ export class Aircraft_Model { properties: rest }) } + this.resolve_orientation() + } + update_orientation(item: any) { + this.trackOrientation = orientation_from_item(item, this.trackOrientation) + this.resolve_orientation() + } + private resolve_orientation() { + if (this.track_points.length < 2) { + this.heading = this.trackOrientation.heading + this.pitch = this.trackOrientation.pitch + this.roll = this.trackOrientation.roll + return + } + const last = this.track_points.length - 1 + const orientation = orientation_from_points(this.track_points[last - 1], this.track_points[last]) + this.heading = orientation.heading + this.pitch = orientation.pitch + this.roll = orientation.roll } } export function orientation_from_item(item: any, fallback: Aircraft_Orientation_Model): Aircraft_Orientation_Model { @@ -110,6 +125,17 @@ function aircraft_status_text(status: Aircraft_Status): string { [Aircraft_Status.Need_Init]: "未初始化", }[status] } -export function get_aircraft_angle(p1: { lat: number, lng: number }, p2: { lat: number, lng: number }) { - return Math.atan2(p2.lng - p1.lng, p2.lat - p1.lat) * 180 / Math.PI +function orientation_from_points(first: Aircraft_Track_Point_Model, second: Aircraft_Track_Point_Model): Aircraft_Orientation_Model { + const radians = Math.PI / 180 + const lat1 = first.lat * radians + const lat2 = second.lat * radians + const delta_lat = (second.lat - first.lat) * radians + const delta_lon = (second.lon - first.lon) * radians + const y = Math.sin(delta_lon) * Math.cos(lat2) + const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(delta_lon) + const heading = (Math.atan2(y, x) / radians + 360) % 360 + const haversine = Math.sin(delta_lat / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(delta_lon / 2) ** 2 + const distance = 6371000 * 2 * Math.atan2(Math.sqrt(haversine), Math.sqrt(1 - haversine)) + const pitch = Math.atan2(second.alt - first.alt, distance) / radians + return {heading, pitch, roll: 0} } diff --git a/src/Map/Cesium_Data_Source_Sync.ts b/src/Map/Cesium_Data_Source_Sync.ts index e42284b..897e458 100644 --- a/src/Map/Cesium_Data_Source_Sync.ts +++ b/src/Map/Cesium_Data_Source_Sync.ts @@ -1,27 +1,14 @@ -import type {Aircraft} from "./Aircraft.tsx"; import type {Cesium_Map} from "./Cesium_Map.tsx"; -type Aircraft_Sync_Task = { - key: string - aircraft: Aircraft -} - export class Cesium_Data_Source_Sync { private request_timer: number | null = null; - private queue_timer: number | null = null; - private generation = 0; - private aircraft_tasks: Aircraft_Sync_Task[] = []; private refreshing = false; constructor(private readonly map: Cesium_Map) {} destroy() { if (this.request_timer) window.clearTimeout(this.request_timer); - if (this.queue_timer) window.clearTimeout(this.queue_timer); this.request_timer = null; - this.queue_timer = null; - this.aircraft_tasks = []; - this.generation++; } async refresh() { @@ -47,14 +34,8 @@ export class Cesium_Data_Source_Sync { sync() { if (!this.map.viewer) return; - if (this.queue_timer) { - window.clearTimeout(this.queue_timer); - this.queue_timer = null; - } - const generation = ++this.generation; const alive = new Set(); const alive_base_stations = new Set(); - const tasks: Aircraft_Sync_Task[] = []; for (const source of this.map.list()) { if (!source.enable) continue; this.map.sync_base_station(source, alive_base_stations); @@ -62,10 +43,9 @@ export class Cesium_Data_Source_Sync { for (const [icao, aircraft] of source.aircraftMap) { const key = this.map.entity_key(source.key, icao); alive.add(key); - tasks.push({key, aircraft}); + this.map.sync_aircraft(key, aircraft); } } - this.aircraft_tasks = tasks; for (const [key, record] of this.map.entity_map) { if (alive.has(key)) continue; this.map.remove_aircraft_record(record); @@ -79,25 +59,6 @@ export class Cesium_Data_Source_Sync { this.map.remove_base_station_record(record); this.map.base_station_entity_map.delete(key); } - this.process_queue(generation); - } - - private process_queue(generation: number) { - if (!this.map.viewer || generation !== this.generation) return; - const start = performance.now(); - let count = 0; - while (this.aircraft_tasks.length > 0 && count < 10 && (count === 0 || performance.now() - start < 8)) { - const task = this.aircraft_tasks.shift()!; - this.map.sync_aircraft(task.key, task.aircraft); - count++; - } - if (this.aircraft_tasks.length > 0) { - this.queue_timer = window.setTimeout(() => { - this.queue_timer = null; - this.process_queue(generation); - }, 180); - return; - } this.map.viewer.scene.requestRender(); } } diff --git a/src/Map/Cesium_Map.tsx b/src/Map/Cesium_Map.tsx index 10eda00..32c417c 100644 --- a/src/Map/Cesium_Map.tsx +++ b/src/Map/Cesium_Map.tsx @@ -1148,6 +1148,22 @@ export class Cesium_Map extends enhance.Base { sync_aircraft(key: string, aircraft: Aircraft) { this.aircraft_layer.sync(key, aircraft); } + update_aircraft(aircraft: Aircraft) { + const key = this.entity_key(aircraft.data_source.key, aircraft.icao); + if (aircraft.data_source.enable && aircraft.data_source.map3d_style().aircraft_show) { + this.sync_aircraft(key, aircraft); + } else { + this.remove_aircraft(key); + } + this.viewer?.scene.requestRender(); + } + remove_aircraft(key: string) { + const record = this.entity_map.get(key); + if (!record) return; + this.remove_aircraft_record(record); + this.entity_map.delete(key); + if (this.selected_aircraft && key === this.entity_key(this.selected_aircraft.data_source.key, this.selected_aircraft.icao)) this.clear_selected_aircraft(); + } ensure_aircraft_track_entities(key: string, aircraft: Aircraft, record: Aircraft_Entity_Record) { this.aircraft_layer.ensure_track_entities(key, aircraft, record); } @@ -1238,7 +1254,6 @@ export class Cesium_Map extends enhance.Base { record.occlusion_marker.description = new Cesium.ConstantProperty(this.aircraft_occlusion_description(aircraft, result)); } if (record.aircraft.model) { - const selected = this.is_selected_aircraft(this.entity_key(aircraft.data_source.key, aircraft.icao)); record.aircraft.model.silhouetteColor = new Cesium.ConstantProperty(color); record.aircraft.model.silhouetteSize = new Cesium.ConstantProperty(result ? result.obstructed ? 2 : 0 : 1); } @@ -1309,13 +1324,13 @@ export class Cesium_Map extends enhance.Base { return {lon: model.lon, lat: model.lat, alt: model.alt}; } aircraft_heading(aircraft: Aircraft): number { - return aircraft.data_model.trackOrientation.heading; + return aircraft.data_model.heading; } aircraft_pitch(aircraft: Aircraft): number { - return aircraft.data_model.trackOrientation.pitch || 0; + return aircraft.data_model.pitch; } aircraft_roll(aircraft: Aircraft): number { - return aircraft.data_model.trackOrientation.roll || 0; + return aircraft.data_model.roll; } aircraft_orientation(aircraft: Aircraft, position: Cesium.Cartesian3): Cesium.Quaternion { const model = this.aircraft_model_item(aircraft); @@ -1392,7 +1407,7 @@ export class Cesium_Map extends enhance.Base { } aircraft_label_enabled(aircraft: Aircraft): boolean { const source = aircraft.data_source.map3d_style(); - return source.show_icao || source.show_call_sign || source.show_fly_status; + return source.show_icao || source.show_call_sign || source.show_fly_status || source.show_heading; } should_show_aircraft_track(key: string, aircraft: Aircraft): boolean { return this.is_selected_aircraft(key) || aircraft.data_source.is_aircraft_track_monitored(aircraft.icao); @@ -1403,9 +1418,19 @@ export class Cesium_Map extends enhance.Base { const model = aircraft.data_model; if (source.show_icao) lines.push(model.icao); if (source.show_call_sign && model.call_sign) lines.push(model.call_sign); - if (source.show_fly_status && model.fly_status) lines.push(`${model.fly_status} ${model.heading.toFixed(0)}°`); + if (source.show_fly_status && model.fly_status) lines.push(model.fly_status); + if (source.show_heading) { + const config = this.aircraft_model_item(aircraft); + lines.push(`vortex_type ${model.vortexType ?? "未知"}`); + lines.push(`当前 H ${model.heading.toFixed(1)}° P ${model.pitch.toFixed(1)}° R ${model.roll.toFixed(1)}°`); + lines.push(`偏移 H ${config.heading_offset_degrees.toFixed(1)}° P ${config.pitch_offset_degrees.toFixed(1)}° R ${config.roll_offset_degrees.toFixed(1)}°`); + lines.push(`模型 H ${this.normalize_degrees(model.heading + config.heading_offset_degrees).toFixed(1)}° P ${(model.pitch + config.pitch_offset_degrees).toFixed(1)}° R ${(model.roll + config.roll_offset_degrees).toFixed(1)}°`); + } return lines.join("\n"); } + normalize_degrees(value: number): number { + return (value % 360 + 360) % 360; + } sync_track_points(key: string, aircraft: Aircraft, record: Aircraft_Entity_Record) { this.ensure_aircraft_track_entities(key, aircraft, record); const points = aircraft.data_model.track_points; @@ -1596,8 +1621,8 @@ export class Cesium_Map extends enhance.Base { `高度: ${model.alt}`, `航向: ${model.heading.toFixed(1)}°`, `涡流/目标类型: ${model.vortexTypeLabel || model.vortexTypeKey || ""}`, - `航迹俯仰: ${model.trackOrientation.pitch.toFixed(1)}°`, - `航迹横滚: ${model.trackOrientation.roll.toFixed(1)}°` + `航迹俯仰: ${model.pitch.toFixed(1)}°`, + `航迹横滚: ${model.roll.toFixed(1)}°` ].join("
"); } track_point_description(point: Aircraft_Track_Point_Model) {