Files
eacp_webapp/src/Map/Leaflet_Map.tsx
T
2026-08-04 17:21:15 +08:00

460 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import "leaflet/dist/leaflet.css"
import L from 'leaflet';
import gcoord from "gcoord"
import React, {useEffect, useState} from 'react';
import enhance from "../core/enhance.tsx";
import {baseURL, G, host, lightenColor} from "../Global.js";
import {Aircraft, Aircraft_Track_Point} from "./Aircraft.tsx"
import {Button, Space, message} from "antd";
// https://ant.design/components/tree-cn
import {Menu, type MenuProps, Tree} from 'antd';
import type {TreeDataNode, TreeProps} from 'antd';
// @ts-ignore
import {ReloadOutlined, SearchOutlined, SettingOutlined} from "@ant-design/icons";
import axios from "axios";
import {Aircraft_Info_Show} from "./Aircraft_Info_Show.tsx";
import {Data_Source_Config} from "../Data_Source/Data_Source_Config.tsx";
import {Data_Source_Show} from "../Data_Source/Data_Source_Show.tsx";
import {app} from "../App.tsx";
//import {Base_Station} from "./Base_Station.tsx";
import {Data_Source} from "../Data_Source/Data_Source.tsx";
import {
imagery_source_for_key,
load_map_resources,
type Map_Resources_Metadata,
type Map_Tile_Metadata
} from "./Map_Resources.tsx";
import {default_map_view_config, load_map_view_config, save_map_view_config, type Map_Tile_View_Config, type Map_View_Config} from "./Map_View.tsx";
export class POS {
lat: number;
lon: number;
}
export class Leaflet_Map extends enhance.Base {
// @ts-ignore
map: L.Map = null;
data_source_show: Data_Source_Show = new Data_Source_Show();
// @ts-ignore
aircraft_info_show: Aircraft_Info_Show = new Aircraft_Info_Show(this);
data_source_config: Data_Source_Config = null
tile_layer: L.TileLayer | null = null
map_resources: Map_Resources_Metadata | null = null
map_view_config: Map_View_Config | null = null
current_imagery_key: string = ""
context_menu: {x: number, y: number, aircraft?: Aircraft, base_station?: Data_Source} | null = null
tile_display_level_draft: number | null | undefined = undefined
private savedCenter: L.LatLng | null = null;
private savedZoom: number | null = null;
intervalId: number | null = null;
autoRefreshTimer = null; // 用于存储定时器ID
refreshInterval : number = 1; // 用于存储定时器ID
private is_click_on_map_entity(e: L.LeafletMouseEvent): boolean {
const target = e.originalEvent.target;
if (!(target instanceof Element)) {
return false;
}
return Boolean(target.closest([
".leaflet-marker-icon",
".leaflet-interactive",
".leaflet-popup",
".leaflet-tooltip",
".leaflet-control",
].join(",")));
}
private clear_active_aircraft_by_map_click = (e: L.LeafletMouseEvent) => {
console.log(e);
const had_context_menu = this.context_menu !== null;
this.context_menu = null;
// if (this.is_click_on_map_entity(e)) {
// return;
// }
let changed = false;
this.list().forEach((ds: Data_Source) => {
if (!ds.active_aircraft) return;
ds.active_aircraft.hide_path();
ds.active_aircraft = null;
ds.refresh_aircraft_list();
changed = true;
});
if (changed || had_context_menu) {
this.aircraft_info_show.active_aircraft = null;
this.aircraft_info_show.flush();
this.data_source_show.flush();
this.flush();
}
}
on_mount: () => void = () => {
console.log("地图加载");
this.map = this.load_map();
this.load_imagery_layer();
this.map.on("click", this.clear_active_aircraft_by_map_click);
this.map.whenReady(() => {
});
if (this.savedCenter && this.savedZoom) {
this.map.setView(this.savedCenter, this.savedZoom);
}
if (this.data_source_config) {
let ds_list = this.data_source_config.list.list;
ds_list.forEach((ds: Data_Source) => {
if (!ds.enable) return; // ✅ 跳过当前项
for (const [key, aircraft] of ds.aircraftMap) {
aircraft.on_mount();
}
ds.base_station.on_mount();
ds.refresh()
});
}
this.autoRefreshTimer = setInterval(() => {
if (this.data_source_config){
let ds_list = this.data_source_config.list.list;
ds_list.forEach((ds: Data_Source) => {
if (!ds.enable) return; // ✅ 跳过当前项
ds.refresh()
});
}
}, this.refreshInterval * 1000); // 以秒为单位
this.flush();
}
on_un_mount: () => void = () => {
console.log("地图卸载");
if (this.map) {
this.savedCenter = this.map.getCenter();
this.savedZoom = this.map.getZoom();
this.map.remove()
}
this.tile_layer = null
let map = this.map;
map.eachLayer(function(layer) {
map.removeLayer(layer);
});
if (this.autoRefreshTimer) {
clearInterval(this.autoRefreshTimer); // 清除定时器
this.autoRefreshTimer = null;
}
this.map = null
}
list(): Data_Source[] {
if (!this.data_source_config) return []
return this.data_source_config.list.list
}
get_aircraft(data_source_key: string, icao: string): Aircraft {
let d = app.get_Data_Source(data_source_key)
return d.get_aircraft(icao)
}
style = {}
render(props: any) {
return (
<div style={{
width: '100%',
height: '100%',
}}>
<this.aircraft_info_show.x></this.aircraft_info_show.x>
<this.data_source_show.x mapDisplayMode="map2d"></this.data_source_show.x>
<div id="map" style={{
width: '100%',
height: '100%',
zIndex: 1,
}}/>
{this.render_context_menu()}
</div>
);
}
aircraft_click(that: Aircraft) {
this.aircraft_info_show.active_aircraft = that;
let data_source = this.data_source_config.map(that.data_source.key)
if (data_source.active_aircraft != that) {
if (data_source.active_aircraft) {
data_source.active_aircraft.hide_path();
}
data_source.active_aircraft = that;
data_source.active_aircraft.show_path();
}
let a = this.aircraft_info_show;
if(a.drawer.have_open === false){
a.drawer.toggleOpen()
}
if (a.selected_key === a.DETAILED_INFORMATION) {
axios.post(`${baseURL}/get_aircraft_detail_info`, {
data_source_key: that.data_source.key,
icao: that.icao,
}).then(res => {
a.detailed_information = G.transformToTreeData(res.data);
a.flush();
})
}
if (a.selected_key === a.BASE_INFORMATION) {
axios.post(`${baseURL}/get_aircraft_base_info`, {
data_source_key: that.data_source.key,
icao: that.icao,
}).then(res => {
a.base_information = G.transformToTreeData(res.data);
a.flush();
})
}
}
timer
mlat_source: Data_Source = new Data_Source();
mlat: Map<string, Aircraft> = new Map();
constructor() {
super();
this.mlat_source.key = "mlat_source";
// this.timer = setInterval(() => {
// this.mlat_source.aircraft_show = true;
// axios.post(`${baseURL}/get_mlat_list`, {})
// .then(res => {
//
// //console.log(res.data);
// res.data.forEach(item => {
// //console.log(item);
// let mlat_air = this.mlat.get(item.icao);
// if (!mlat_air) {
// mlat_air = new Aircraft(item.icao, this.mlat_source)
// mlat_air.mlat_show = true;
// this.mlat.set(item.icao, mlat_air);
// //mlat_air.track_path.addTo(this.map);
// }
//
// mlat_air.add_malt_data(item);
//
// });
// })
// .catch(err => {
// console.error("请求出错:", err);
// });
// }, 1000);
}
INIT_EXTENT = L.latLngBounds(
[36.42, 119.98], // 西南角:
[38.40, 122.56] // 东北角:
);
load_map = () => {
const MAP_CENTER: [number, number] = [34.3227, 118.5525];
// Leaflet 默认图标路径(你原来需要的话保留)
L.Icon.Default.imagePath = `http://${host}/images/`;
// 1) 创建地图(等价 MapContainer
const map = L.map("map", {
center: MAP_CENTER,
zoom: 3,
// minZoom: 3,
// maxZoom: 9,
zoomControl: false,
attributionControl: false,
doubleClickZoom: false,
// preferCanvas: true, // 可选
});
// 3) 等价 MapContent: fitBounds + on_mount
// INIT_EXTENT 必须是 Leaflet 接受的 bounds 格式:
// [[southLat, westLng], [northLat, eastLng]]
map.fitBounds(this.INIT_EXTENT, {
padding: [50, 50],
maxZoom: 10,
});
return map;
};
current_imagery_resource(): Map_Tile_Metadata {
const source = imagery_source_for_key(this.map_resources!, this.current_imagery_key);
return source.resource;
}
map2d_tile_view(): Map_Tile_View_Config {
return (this.map_view_config || default_map_view_config).map2d;
}
set_imagery_layer() {
if (!this.map || !this.map_resources) return;
if (this.tile_layer) {
this.map.removeLayer(this.tile_layer);
}
const imagery = this.current_imagery_resource();
const view_config = this.map2d_tile_view();
const display_maximum_level = Math.max(imagery.minimum_level, Math.min(view_config.tile_display_maximum_level, 24));
const max_native_zoom = Math.min(imagery.maximum_level, display_maximum_level);
const max_zoom = Math.max(imagery.maximum_level, display_maximum_level, 24);
this.map.setMinZoom(0);
this.map.setMaxZoom(max_zoom);
if (this.map.getZoom() > max_zoom) {
this.map.setZoom(max_zoom);
}
this.tile_layer = L.tileLayer(imagery.url, {
minZoom: 0,
maxZoom: max_zoom,
minNativeZoom: imagery.minimum_level,
maxNativeZoom: max_native_zoom,
tileSize: imagery.tile_size,
tms: imagery.y_axis === "tms",
});
this.tile_layer.addTo(this.map);
}
aircraft_icon_size(aircraft: Aircraft): number {
return Math.max(1, aircraft.data_source.map2d_style().aircraft_pixel_size || 50);
}
base_station_icon_size(ds: Data_Source): number {
return Math.max(1, ds.map2d_style().base_station_pixel_size || 100);
}
load_imagery_layer = async () => {
const [resources, view_config] = await Promise.all([load_map_resources(), load_map_view_config()]);
if (!this.map) return;
this.map_resources = resources;
this.map_view_config = view_config;
this.current_imagery_key = view_config.map2d.current_imagery_key || resources.current_imagery_key;
this.set_imagery_layer();
this.flush();
}
change_imagery_source(key: string) {
if (!this.map_resources) return;
const config = this.map_view_config || default_map_view_config;
this.current_imagery_key = key;
this.map_view_config = {...config, current_imagery_key: key, map2d: {...config.map2d, current_imagery_key: key}};
this.set_imagery_layer();
this.flush();
}
change_tile_zoom_mode(mode: "native" | "upscale" | "both") {
const config = this.map_view_config || default_map_view_config;
this.map_view_config = {...config, map2d: {...config.map2d, tile_zoom_mode: mode}};
this.set_imagery_layer();
this.flush();
}
change_tile_display_maximum_level(value: number | null) {
this.tile_display_level_draft = value;
if (value === null) {
this.flush();
return;
}
const config = this.map_view_config || default_map_view_config;
this.map_view_config = {...config, map2d: {...config.map2d, tile_display_maximum_level: value}};
this.set_imagery_layer();
this.flush();
}
blur_tile_display_maximum_level() {
if (this.tile_display_level_draft !== undefined) {
this.tile_display_level_draft = undefined;
this.flush();
}
}
async save_current_map_view() {
const config = this.map_view_config || default_map_view_config;
this.map_view_config = await save_map_view_config({
...config,
current_imagery_key: this.current_imagery_key,
map2d: {...config.map2d, current_imagery_key: this.current_imagery_key}
});
this.current_imagery_key = this.map_view_config.map2d.current_imagery_key;
this.set_imagery_layer();
message.success("2D地图瓦片源已保存");
this.flush();
}
open_aircraft_context_menu(aircraft: Aircraft, point: L.Point) {
this.context_menu = {x: point.x, y: point.y, aircraft};
this.flush();
}
open_base_station_context_menu(base_station: Data_Source, point: L.Point) {
this.context_menu = {x: point.x, y: point.y, base_station};
this.flush();
}
toggle_manual_tracking_aircraft(aircraft: Aircraft) {
const enabled = !aircraft.data_source.is_manual_tracking_aircraft(aircraft.icao);
this.context_menu = null;
aircraft.data_source.set_manual_tracking_aircraft(aircraft.icao, enabled).then(() => this.flush());
this.flush();
}
monitor_all_aircraft(ds: Data_Source) {
this.context_menu = null;
ds.set_monitor_all_aircraft_mode(true).then(() => this.flush());
this.flush();
}
clear_manual_tracking(ds: Data_Source) {
this.context_menu = null;
ds.clear_all_aircraft_tracking();
this.flush();
}
render_context_menu() {
const menu = this.context_menu;
if (!menu) return null;
const stop = (event: React.MouseEvent) => {
event.preventDefault();
event.stopPropagation();
};
if (menu.aircraft) {
const aircraft = menu.aircraft;
const tracking = aircraft.data_source.is_manual_tracking_aircraft(aircraft.icao);
return (
<div onMouseDown={stop} onClick={stop} style={{position: "absolute", left: menu.x, top: menu.y, zIndex: 4, minWidth: 150, padding: 6, borderRadius: 4, background: "rgba(28, 28, 28, 0.92)", boxShadow: "0 4px 14px rgba(0, 0, 0, 0.24)", color: "#fff"}}>
<div style={{padding: "4px 8px", fontWeight: 700}}>{aircraft.icao}</div>
<Button block size="small" onClick={() => this.toggle_manual_tracking_aircraft(aircraft)}>{tracking ? "取消持久跟踪" : "持久跟踪飞机"}</Button>
</div>
);
}
if (menu.base_station) {
const ds = menu.base_station;
return (
<div onMouseDown={stop} onClick={stop} style={{position: "absolute", left: menu.x, top: menu.y, zIndex: 4, minWidth: 170, padding: 6, borderRadius: 4, background: "rgba(28, 28, 28, 0.92)", boxShadow: "0 4px 14px rgba(0, 0, 0, 0.24)", color: "#fff"}}>
<div style={{padding: "4px 8px", fontWeight: 700}}>{ds.key}</div>
<Space direction="vertical" size={4} style={{width: "100%"}}>
<Button block size="small" onClick={() => this.monitor_all_aircraft(ds)}>{ds.monitor_all_aircraft_mode ? "已监控所有飞机" : "监控所有飞机"}</Button>
<Button block size="small" onClick={() => this.clear_manual_tracking(ds)}></Button>
</Space>
</div>
);
}
return null;
}
}