修复bug
This commit is contained in:
@@ -7,7 +7,7 @@ import {Base_Station} from "../Map/Base_Station.tsx";
|
||||
import {Aircraft, Aircraft_Track_Point} from "../Map/Aircraft.tsx";
|
||||
import {app} from "../App.tsx";
|
||||
import {Leaflet_Map} from "../Map/Leaflet_Map.tsx";
|
||||
import {Data_Format, Input_Number, Input_Port_Number, Input_String, Play_Mode, Psc, Switch_Bool} from "../A_Global.tsx";
|
||||
import {Data_Format, Input_Number, Input_Port_Number, Input_String, Play_Mode, Switch_Bool} from "../A_Global.tsx";
|
||||
import {Data_Source_Show} from "./Data_Source_Show.tsx";
|
||||
import {Camera_Control_Mode} from "../Map/Camera_Control_Mode.ts";
|
||||
import L from "leaflet";
|
||||
@@ -17,56 +17,8 @@ const {Text} = Typography;
|
||||
const {Option} = Select;
|
||||
|
||||
export type Map_Display_Mode = "map2d" | "map3d"
|
||||
export type Data_Source_Map_Display_Data = {
|
||||
base_station_show: boolean
|
||||
aircraft_show: boolean
|
||||
constant_screen_size: boolean
|
||||
color: string
|
||||
track_point_color: string
|
||||
base_station_color: string
|
||||
aircraft_pixel_size: number
|
||||
base_station_pixel_size: number
|
||||
track_point_pixel_size: number
|
||||
aircraft_scale: number
|
||||
base_station_scale: number
|
||||
show_icao: boolean
|
||||
show_call_sign: boolean
|
||||
show_fly_status: boolean
|
||||
}
|
||||
export type Data_Source_Map_Display_Config = {
|
||||
map2d: Data_Source_Map_Display_Data
|
||||
map3d: Data_Source_Map_Display_Data
|
||||
}
|
||||
export function default_map_display_data(): Data_Source_Map_Display_Data {
|
||||
return {
|
||||
base_station_show: true,
|
||||
aircraft_show: true,
|
||||
constant_screen_size: true,
|
||||
color: "#3388ff",
|
||||
track_point_color: "#ffff00",
|
||||
base_station_color: "#1677ff",
|
||||
aircraft_pixel_size: 50,
|
||||
base_station_pixel_size: 100,
|
||||
track_point_pixel_size: 8,
|
||||
aircraft_scale: 1,
|
||||
base_station_scale: 1,
|
||||
show_icao: true,
|
||||
show_call_sign: false,
|
||||
show_fly_status: false
|
||||
};
|
||||
}
|
||||
function normalize_map_display_data(data: Partial<Data_Source_Map_Display_Data> | undefined): Data_Source_Map_Display_Data {
|
||||
return {...default_map_display_data(), ...(data || {})};
|
||||
}
|
||||
function default_map_display_config(): Data_Source_Map_Display_Config {
|
||||
return {map2d: default_map_display_data(), map3d: default_map_display_data()};
|
||||
}
|
||||
function normalize_map_display_config(data: Partial<Data_Source_Map_Display_Config> | undefined): Data_Source_Map_Display_Config {
|
||||
return {
|
||||
map2d: normalize_map_display_data(data?.map2d),
|
||||
map3d: normalize_map_display_data(data?.map3d)
|
||||
};
|
||||
}
|
||||
export type Data_Source_Map_Display_Data = Record<string, any>
|
||||
export type Data_Source_Map_Display_Config = Record<Map_Display_Mode, Data_Source_Map_Display_Data>
|
||||
export class Data_Source extends enhance.Base {
|
||||
timer
|
||||
|
||||
@@ -77,7 +29,7 @@ export class Data_Source extends enhance.Base {
|
||||
adminive_id: number = 0
|
||||
enable: boolean = false;
|
||||
type: string = ""; // 也就是类型名
|
||||
map_display: Data_Source_Map_Display_Config = default_map_display_config();
|
||||
map_display: Data_Source_Map_Display_Config = {map2d: {}, map3d: {}};
|
||||
lat: number = 0;
|
||||
lon: number = 0;
|
||||
alt: number = 0;
|
||||
@@ -87,13 +39,17 @@ export class Data_Source extends enhance.Base {
|
||||
update_form_gps: boolean = false;
|
||||
ignore_msg_time: boolean = false;
|
||||
|
||||
backend_config_fields: string[] = [];
|
||||
backend_editable_fields: string[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.map_display.map2d.color = Psc.random_color();
|
||||
this.map_display.map3d.color = this.map_display.map2d.color;
|
||||
}
|
||||
normalize_map_display() {
|
||||
this.map_display = normalize_map_display_config(this.map_display);
|
||||
const display = this.map_display && typeof this.map_display === "object" ? this.map_display : {} as Data_Source_Map_Display_Config;
|
||||
display.map2d = display.map2d && typeof display.map2d === "object" ? display.map2d : {};
|
||||
display.map3d = display.map3d && typeof display.map3d === "object" ? display.map3d : {};
|
||||
this.map_display = display;
|
||||
}
|
||||
map_style(mode: Map_Display_Mode): Data_Source_Map_Display_Data {
|
||||
return this.map_display[mode];
|
||||
@@ -129,40 +85,15 @@ export class Data_Source extends enhance.Base {
|
||||
|
||||
confirm() {
|
||||
this.normalize_map_display()
|
||||
const payload: Record<string, unknown> = {
|
||||
enable: this.enable,
|
||||
config: {
|
||||
base_station_has_valid_position: this.base_station_has_valid_position,
|
||||
map_display: this.map_display,
|
||||
lat: this.lat,
|
||||
lon: this.lon,
|
||||
alt: this.alt,
|
||||
ignore_msg_time: this.ignore_msg_time,
|
||||
update_form_gps: this.update_form_gps,
|
||||
keep_mode: this.keep_mode
|
||||
const payload: Record<string, unknown> = {}
|
||||
const value = this as any;
|
||||
for (const field of this.backend_editable_fields) {
|
||||
if (field === "config") {
|
||||
payload.config = Object.fromEntries(this.backend_config_fields.map(name => [name, value[name]]));
|
||||
} else {
|
||||
payload[field] = value[field];
|
||||
}
|
||||
}
|
||||
const value = this as any
|
||||
if (this.type === "TCP_Client_Data_Source") {
|
||||
payload.ip = value.ip
|
||||
payload.port = value.port
|
||||
} else if (this.type === "Serial_Data_Source") {
|
||||
payload.port_name = value.port_name
|
||||
payload.baud_rate = value.baud_rate
|
||||
} else if (this.type === "File_Data_Source") {
|
||||
payload.file_path = value.file_path
|
||||
payload.data_type = value.data_type
|
||||
payload.play_mode = value.play_mode
|
||||
} else if (this.type === "Dll_Data_Source") {
|
||||
payload.library_path = value.library_path
|
||||
payload.function_name = value.function_name
|
||||
payload.data_type = value.data_type
|
||||
payload.buffer_size = value.buffer_size
|
||||
} else if (this.type === "Shared_Memory_Data_Source") {
|
||||
payload.shared_memory_name = value.shared_memory_name
|
||||
payload.shared_memory_size = value.shared_memory_size
|
||||
payload.data_type = value.data_type
|
||||
}
|
||||
axios.patch(`/api/adminive/config/data_sources/${this.adminive_id}`, payload).then(() => {
|
||||
this.refresh_display("map2d")
|
||||
this.refresh_display("map3d")
|
||||
@@ -174,7 +105,9 @@ export class Data_Source extends enhance.Base {
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const {active_aircraft, base_station, aircraftMap, manual_track_icao_set, monitor_all_aircraft_mode, connect_list, first, ...rest} = this;
|
||||
const {active_aircraft, base_station, aircraftMap, manual_track_icao_set, monitor_all_aircraft_mode, connect_list, first, backend_config_fields, backend_editable_fields, ...rest} = this;
|
||||
void backend_config_fields;
|
||||
void backend_editable_fields;
|
||||
return rest; // 返回去除这些属性后的对象
|
||||
}
|
||||
locate_base_station() {
|
||||
|
||||
@@ -7,6 +7,11 @@ import {
|
||||
Shared_Memory_Data_Source,
|
||||
TCP_Client_Data_Source
|
||||
} from "./Data_Source.tsx"
|
||||
import type {
|
||||
Backend_Disabled_Rule,
|
||||
Backend_Field_Descriptor,
|
||||
Backend_Form_Section
|
||||
} from "../Adminive/Backend_Fields.tsx"
|
||||
|
||||
type Adminive_Data_Source_Row = {
|
||||
id: number
|
||||
@@ -17,6 +22,19 @@ type Adminive_Data_Source_Row = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type Data_Source_Sidebar_Schema = {
|
||||
title?: string
|
||||
common: Backend_Form_Section
|
||||
position: Backend_Form_Section
|
||||
display: Record<"map2d" | "map3d", Backend_Form_Section>
|
||||
rules?: Backend_Disabled_Rule[]
|
||||
tiles: Backend_Form_Section & {descriptor_fields: Backend_Field_Descriptor[]}
|
||||
view3d: {
|
||||
graphics: Backend_Form_Section & {descriptor_fields: Backend_Field_Descriptor[]}
|
||||
map_view: Backend_Form_Section & {descriptor_fields: Backend_Field_Descriptor[]}
|
||||
}
|
||||
}
|
||||
|
||||
function create_data_source(type: string): Data_Source {
|
||||
if (type === "Serial_Data_Source")
|
||||
return new Serial_Data_Source()
|
||||
@@ -36,6 +54,9 @@ function create_data_source(type: string): Data_Source {
|
||||
|
||||
export class Data_Source_Config {
|
||||
private sources: Data_Source[] = []
|
||||
descriptor_fields: Backend_Field_Descriptor[] = []
|
||||
config_descriptor_fields: Backend_Field_Descriptor[] = []
|
||||
sidebar_schema: Data_Source_Sidebar_Schema | null = null
|
||||
loading = true
|
||||
error = ""
|
||||
|
||||
@@ -58,13 +79,24 @@ export class Data_Source_Config {
|
||||
|
||||
async refresh() {
|
||||
try {
|
||||
const response = await axios.get("/api/adminive/config/data_sources", {
|
||||
params: {page: 1, perPage: 1000}
|
||||
})
|
||||
const [response, descriptor_response] = await Promise.all([
|
||||
axios.get("/api/adminive/config/data_sources", {params: {page: 1, perPage: 1000}}),
|
||||
axios.get("/api/adminive/config/data_sources/descriptor")
|
||||
])
|
||||
const rows = response.data?.data?.items
|
||||
if (!Array.isArray(rows)) {
|
||||
throw new Error("数据源接口未返回 data.items 数组")
|
||||
}
|
||||
const descriptor = descriptor_response.data?.data
|
||||
if (!descriptor || !Array.isArray(descriptor.fields) || !descriptor.sidebar) {
|
||||
throw new Error("数据源接口未返回后端字段与右侧栏描述")
|
||||
}
|
||||
this.descriptor_fields = descriptor.fields
|
||||
this.config_descriptor_fields = descriptor.fields.find((field: Backend_Field_Descriptor) => field.name === "config")?.children || []
|
||||
this.sidebar_schema = descriptor.sidebar
|
||||
const editable_fields = descriptor.fields
|
||||
.filter((field: Backend_Field_Descriptor) => field.editable)
|
||||
.map((field: Backend_Field_Descriptor) => field.name)
|
||||
|
||||
const previous_sources = new Map(this.sources.map(source => [source.key, source]))
|
||||
this.sources = (rows as Adminive_Data_Source_Row[]).map(row => {
|
||||
@@ -74,6 +106,8 @@ export class Data_Source_Config {
|
||||
void state
|
||||
Object.assign(source, config ?? {}, data)
|
||||
source.adminive_id = id
|
||||
source.backend_config_fields = Object.keys(config ?? {})
|
||||
source.backend_editable_fields = editable_fields
|
||||
source.normalize_map_display()
|
||||
return source
|
||||
})
|
||||
|
||||
@@ -3,11 +3,7 @@ import {Data_Source_Config} from "./Data_Source_Config.tsx";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
CheckboxChangeEvent,
|
||||
ColorPicker,
|
||||
Dropdown,
|
||||
InputNumber,
|
||||
type MenuProps,
|
||||
Select,
|
||||
Space,
|
||||
@@ -17,14 +13,13 @@ import {
|
||||
Typography
|
||||
} from "antd";
|
||||
import React from "react";
|
||||
import {Data_Source, type Data_Source_Map_Display_Data, type Map_Display_Mode} from "./Data_Source.tsx";
|
||||
import {Data_Source, type Map_Display_Mode} from "./Data_Source.tsx";
|
||||
import {Base_Drawer} from "../Base_Drawer.tsx";
|
||||
import {Camera_Control_Mode} from "../Map/Camera_Control_Mode.ts";
|
||||
import {app} from "../App.tsx";
|
||||
import {lightenColor} from "../Global.tsx";
|
||||
import {default_cesium_graphics_config} from "../Map/Cesium_Graphics_Config.ts";
|
||||
import {tile_source_options, type Map_Tile_Type} from "../Map/Map_Resources.tsx";
|
||||
import {default_map_view_config} from "../Map/Map_View.tsx";
|
||||
import {Backend_Fields} from "../Adminive/Backend_Fields.tsx";
|
||||
|
||||
const {Title, Text} = Typography;
|
||||
|
||||
@@ -36,30 +31,6 @@ export class Data_Source_Show extends enhance.Base {
|
||||
active_data_source_key = ""
|
||||
active_tab_key: Settings_Tab = "source"
|
||||
active_tile_type: Map_Tile_Type = "imagery"
|
||||
number_input_drafts: Record<string, number | null> = {}
|
||||
|
||||
number_input_value(key: string, value: number): number | null {
|
||||
return Object.prototype.hasOwnProperty.call(this.number_input_drafts, key) ? this.number_input_drafts[key] : value;
|
||||
}
|
||||
|
||||
change_number_input(key: string, value: number | null, commit: (value: number) => void) {
|
||||
this.number_input_drafts[key] = value;
|
||||
if (value !== null) {
|
||||
commit(value);
|
||||
}
|
||||
this.flush();
|
||||
}
|
||||
|
||||
blur_number_input(key: string) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.number_input_drafts, key)) {
|
||||
delete this.number_input_drafts[key];
|
||||
this.flush();
|
||||
}
|
||||
}
|
||||
|
||||
display_title(mode: Map_Display_Mode) {
|
||||
return mode === "map3d" ? "3D显示配置" : "2D显示配置";
|
||||
}
|
||||
|
||||
tile_level_range_text(minimum: number | null | undefined, maximum: number | null | undefined): string {
|
||||
if (minimum === null || minimum === undefined || maximum === null || maximum === undefined) return "未加载";
|
||||
@@ -114,132 +85,6 @@ export class Data_Source_Show extends enhance.Base {
|
||||
);
|
||||
}
|
||||
|
||||
render_display_config(ds: Data_Source, title: string, mode: Map_Display_Mode) {
|
||||
const style: Data_Source_Map_Display_Data = ds.map_style(mode);
|
||||
const aircraft_size_key = `${ds.key}:${mode}:aircraft_size`;
|
||||
const base_station_size_key = `${ds.key}:${mode}:base_station_size`;
|
||||
const track_point_size_key = `${ds.key}:${mode}:track_point_size`;
|
||||
const refresh = () => {
|
||||
ds.refresh_display(mode);
|
||||
this.flush();
|
||||
};
|
||||
return (
|
||||
<div style={{marginTop: 12, paddingTop: 8, borderTop: "1px solid #eee"}}>
|
||||
<Title level={5}>{title}</Title>
|
||||
<div style={{display: "flex", flexDirection: "column", gap: 4}}>
|
||||
<Space wrap>
|
||||
<Checkbox checked={style.aircraft_show} onChange={(e: CheckboxChangeEvent) => {
|
||||
style.aircraft_show = e.target.checked;
|
||||
refresh();
|
||||
}}>展示飞机</Checkbox>
|
||||
<Checkbox checked={style.base_station_show} onChange={(e: CheckboxChangeEvent) => {
|
||||
style.base_station_show = e.target.checked;
|
||||
refresh();
|
||||
}}>展示基站</Checkbox>
|
||||
{mode === "map3d" &&
|
||||
<Checkbox checked={style.constant_screen_size} onChange={(e: CheckboxChangeEvent) => {
|
||||
style.constant_screen_size = e.target.checked;
|
||||
refresh();
|
||||
}}>固定视觉大小</Checkbox>}
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<Checkbox checked={style.show_icao} onChange={(e: CheckboxChangeEvent) => {
|
||||
style.show_icao = e.target.checked;
|
||||
refresh();
|
||||
}}>显示icao</Checkbox>
|
||||
<Checkbox checked={style.show_call_sign} onChange={(e: CheckboxChangeEvent) => {
|
||||
style.show_call_sign = e.target.checked;
|
||||
refresh();
|
||||
}}>显示call_sign</Checkbox>
|
||||
<Checkbox checked={style.show_fly_status} onChange={(e: CheckboxChangeEvent) => {
|
||||
style.show_fly_status = e.target.checked;
|
||||
refresh();
|
||||
}}>显示飞行状态</Checkbox>
|
||||
</Space>
|
||||
</div>
|
||||
<br/>
|
||||
{mode === "map2d" ? <InputNumber addonBefore="飞机显示大小" min={1} max={65535} precision={0}
|
||||
placeholder="请输入像素大小"
|
||||
value={this.number_input_value(aircraft_size_key, style.aircraft_pixel_size)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input(aircraft_size_key, value, (value) => {
|
||||
style.aircraft_pixel_size = value;
|
||||
refresh();
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input(aircraft_size_key)}/> :
|
||||
<InputNumber addonBefore="飞机缩放比例" min={0} max={65535} step={0.1} precision={3}
|
||||
placeholder="请输入缩放比例"
|
||||
value={this.number_input_value(aircraft_size_key, style.aircraft_scale)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input(aircraft_size_key, value, (value) => {
|
||||
style.aircraft_scale = value;
|
||||
refresh();
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input(aircraft_size_key)}/>}
|
||||
<br/>
|
||||
{mode === "map2d" ? <InputNumber addonBefore="基站显示大小" min={1} max={65535} precision={0}
|
||||
placeholder="请输入像素大小"
|
||||
value={this.number_input_value(base_station_size_key, style.base_station_pixel_size)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input(base_station_size_key, value, (value) => {
|
||||
style.base_station_pixel_size = value;
|
||||
refresh();
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input(base_station_size_key)}/> :
|
||||
<InputNumber addonBefore="基站显示大小" min={0} max={65535} step={0.1} precision={3}
|
||||
placeholder="请输入缩放比例"
|
||||
value={this.number_input_value(base_station_size_key, style.base_station_scale)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input(base_station_size_key, value, (value) => {
|
||||
style.base_station_scale = value;
|
||||
refresh();
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input(base_station_size_key)}/>}
|
||||
<br/>
|
||||
<InputNumber addonBefore="轨迹点大小" min={1} max={1024} precision={0}
|
||||
value={this.number_input_value(track_point_size_key, style.track_point_pixel_size)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input(track_point_size_key, value, (value) => {
|
||||
style.track_point_pixel_size = value;
|
||||
refresh();
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input(track_point_size_key)}/>
|
||||
<br/>
|
||||
<Space>
|
||||
<span>飞机标注颜色</span>
|
||||
<ColorPicker value={style.color}
|
||||
onChange={(value) => {
|
||||
style.color = value.toHexString();
|
||||
refresh();
|
||||
}}/>
|
||||
</Space>
|
||||
<br/>
|
||||
<Space>
|
||||
<span>轨迹点颜色</span>
|
||||
<ColorPicker value={style.track_point_color}
|
||||
onChange={(value) => {
|
||||
style.track_point_color = value.toHexString();
|
||||
refresh();
|
||||
}}/>
|
||||
</Space>
|
||||
<br/>
|
||||
<Space>
|
||||
<span>基站标注颜色</span>
|
||||
<ColorPicker value={style.base_station_color}
|
||||
onChange={(value) => {
|
||||
style.base_station_color = value.toHexString();
|
||||
refresh();
|
||||
}}/>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render_aircraft_monitor_config(ds: Data_Source, mode: Map_Display_Mode) {
|
||||
const manual_list = ds.manual_track_icao_list();
|
||||
return (
|
||||
@@ -395,10 +240,10 @@ export class Data_Source_Show extends enhance.Base {
|
||||
] : [{value: "imagery", label: "影像瓦片"}];
|
||||
const source_options = tile_source_options(map?.map_resources || null, tile_type);
|
||||
const source_key = this.selected_tile_source_key(map, tile_type, source_options);
|
||||
const display_level = map?.tile_display_level_draft !== undefined ? map.tile_display_level_draft : tile_view?.tile_display_maximum_level;
|
||||
const tile_schema = this.data_source_config?.sidebar_schema?.tiles;
|
||||
return (
|
||||
<div style={{marginTop: 12, paddingTop: 8, borderTop: "1px solid #eee"}}>
|
||||
<Title level={5}>瓦片显示</Title>
|
||||
<Title level={5}>{tile_schema?.title ?? "瓦片显示"}</Title>
|
||||
<Space direction="vertical" size={6}>
|
||||
<Select
|
||||
style={{width: 220}}
|
||||
@@ -414,17 +259,13 @@ export class Data_Source_Show extends enhance.Base {
|
||||
options={source_options}
|
||||
disabled={!source_options.length}
|
||||
onChange={(key: string) => this.change_tile_source(map, tile_type, key)}/>
|
||||
{tile_type === "imagery" && <InputNumber addonBefore="显示层级" min={0} max={24}
|
||||
value={display_level}
|
||||
onChange={(value: number | null) => {
|
||||
map?.change_tile_display_maximum_level(value);
|
||||
this.flush();
|
||||
}}
|
||||
onBlur={() => {
|
||||
map?.blur_tile_display_maximum_level();
|
||||
this.flush();
|
||||
}}
|
||||
style={{width: 220}}/>}
|
||||
{tile_type === "imagery" && tile_schema &&
|
||||
<Backend_Fields root={tile_view || {}} descriptor_fields={tile_schema.descriptor_fields}
|
||||
section={{...tile_schema, descriptor_path: ""}}
|
||||
onChange={(_path, value) => {
|
||||
map?.change_tile_display_maximum_level(typeof value === "number" ? value : null);
|
||||
this.flush();
|
||||
}}/>}
|
||||
<Button size="small" type="primary"
|
||||
onClick={() => map?.save_current_map_view()}>保存地图视图</Button>
|
||||
</Space>
|
||||
@@ -482,9 +323,9 @@ export class Data_Source_Show extends enhance.Base {
|
||||
render_3d_view_controls() {
|
||||
const map = app.cesium_map;
|
||||
const mode = map?.camera_control_mode() || Camera_Control_Mode.Surface_Navigation;
|
||||
const graphics_config = map?.graphics_config || default_cesium_graphics_config;
|
||||
const fly_to_height_key = "map3d:base_station_fly_to_height_offset";
|
||||
const fly_to_height = map?.base_station_fly_to_height_offset_meters() || default_map_view_config.baseStationFlyToHeightOffsetMeters;
|
||||
const graphics_config = map?.graphics_config || {};
|
||||
const map_view_config = map?.map_view_config || {};
|
||||
const schema = this.data_source_config?.sidebar_schema?.view3d;
|
||||
const group_style: React.CSSProperties = {
|
||||
marginTop: 8
|
||||
};
|
||||
@@ -530,24 +371,21 @@ export class Data_Source_Show extends enhance.Base {
|
||||
{this.render_3d_control_button("全球视图", ["相机移动到能看到完整地球的位置。", "会改变相机高度和位置。", "用于从局部场景快速回到全球范围。"], () => map?.fly_to_global_view())}
|
||||
</div>
|
||||
</div>
|
||||
<Space direction="vertical" size={4} style={{marginTop: 8}}>
|
||||
<Checkbox checked={graphics_config.surfaceNavigationReferenceVisible}
|
||||
onChange={(e: CheckboxChangeEvent) => {
|
||||
map?.set_surface_navigation_reference_visible(e.target.checked);
|
||||
this.flush();
|
||||
}}>显示地表导航参考</Checkbox>
|
||||
<Checkbox checked={graphics_config.viewAxesVisible}
|
||||
onChange={(e: CheckboxChangeEvent) => {
|
||||
map?.set_view_axes_visible(e.target.checked);
|
||||
this.flush();
|
||||
}}>显示视角坐标轴</Checkbox>
|
||||
<InputNumber addonBefore="回基站高度" min={100} max={10000000} step={100}
|
||||
value={this.number_input_value(fly_to_height_key, fly_to_height)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input(fly_to_height_key, value, (value) => map?.change_base_station_fly_to_height_offset_meters(value));
|
||||
}}
|
||||
onBlur={() => this.blur_number_input(fly_to_height_key)}
|
||||
style={{width: 210}}/>
|
||||
<Space direction="vertical" size={8} style={{marginTop: 8, width: "100%"}}>
|
||||
{schema && <>
|
||||
<Backend_Fields root={graphics_config} descriptor_fields={schema.graphics.descriptor_fields}
|
||||
section={{...schema.graphics, descriptor_path: ""}}
|
||||
onChange={() => {
|
||||
map?.apply_graphics_config();
|
||||
this.flush();
|
||||
}}/>
|
||||
<Backend_Fields root={map_view_config} descriptor_fields={schema.map_view.descriptor_fields}
|
||||
section={{...schema.map_view, descriptor_path: ""}}
|
||||
onChange={() => {
|
||||
if (map) map.map_view_config = map_view_config;
|
||||
this.flush();
|
||||
}}/>
|
||||
</>}
|
||||
<Space wrap>
|
||||
<Button size="small" onClick={() => map?.save_current_map_view()}>保存视图设置</Button>
|
||||
<Button size="small"
|
||||
@@ -559,70 +397,29 @@ export class Data_Source_Show extends enhance.Base {
|
||||
}
|
||||
|
||||
render_data_source(ds: Data_Source, mode: Map_Display_Mode) {
|
||||
const latitude_key = `${ds.key}:latitude`;
|
||||
const longitude_key = `${ds.key}:longitude`;
|
||||
const altitude_key = `${ds.key}:altitude`;
|
||||
const config = this.data_source_config;
|
||||
const schema = config?.sidebar_schema;
|
||||
if (!config || !schema) {
|
||||
return <Text type="secondary">正在加载后端字段描述…</Text>;
|
||||
}
|
||||
const refresh = () => {
|
||||
ds.refresh_display(mode);
|
||||
this.flush();
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Title level={5}>数据源名称:{ds.key}</Title>
|
||||
<Checkbox checked={ds.update_form_gps}
|
||||
onChange={(e: CheckboxChangeEvent) => {
|
||||
ds.update_form_gps = e.target.checked;
|
||||
this.flush();
|
||||
}}>从GPS中更新</Checkbox>
|
||||
<Checkbox checked={ds.base_station_has_valid_position}
|
||||
disabled={ds.update_form_gps}
|
||||
onChange={(e: CheckboxChangeEvent) => {
|
||||
ds.base_station_has_valid_position = e.target.checked;
|
||||
ds.refresh_display("map2d");
|
||||
ds.refresh_display("map3d");
|
||||
this.flush();
|
||||
}}>基站有效定位</Checkbox>
|
||||
<br/>
|
||||
<Checkbox checked={ds.keep_mode} onChange={(e: CheckboxChangeEvent) => {
|
||||
ds.keep_mode = e.target.checked;
|
||||
ds.refresh();
|
||||
this.flush();
|
||||
}}>基站最大范围保持模式</Checkbox>
|
||||
<Checkbox checked={ds.ignore_msg_time} onChange={(e: CheckboxChangeEvent) => {
|
||||
ds.ignore_msg_time = e.target.checked;
|
||||
ds.refresh();
|
||||
this.flush();
|
||||
}}>忽略消息时间戳</Checkbox>
|
||||
<br/>
|
||||
{this.render_display_config(ds, this.display_title(mode), mode)}
|
||||
<br/>
|
||||
<InputNumber addonBefore="纬度" min={-90} max={90} placeholder="请输入维度"
|
||||
value={this.number_input_value(latitude_key, ds.lat)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input(latitude_key, value, (value) => {
|
||||
ds.lat = value;
|
||||
this.flush();
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input(latitude_key)}/>
|
||||
<br/>
|
||||
<InputNumber addonBefore="经度" min={-180} max={180}
|
||||
placeholder="请输入经度"
|
||||
value={this.number_input_value(longitude_key, ds.lon)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input(longitude_key, value, (value) => {
|
||||
ds.lon = value;
|
||||
this.flush();
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input(longitude_key)}/>
|
||||
<br/>
|
||||
<InputNumber addonBefore="高度" min={0} max={65535} placeholder="请输入高度"
|
||||
value={this.number_input_value(altitude_key, ds.alt)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input(altitude_key, value, (value) => {
|
||||
ds.alt = value;
|
||||
this.flush();
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input(altitude_key)}/>
|
||||
<br/>
|
||||
<Title level={5}>{schema.title ?? "数据源配置"}:{ds.key}</Title>
|
||||
<Backend_Fields root={ds as any} descriptor_fields={config.config_descriptor_fields}
|
||||
section={schema.common} rules={schema.rules} onChange={refresh}/>
|
||||
<div style={{marginTop: 12, paddingTop: 8, borderTop: "1px solid #eee"}}>
|
||||
<Title level={5}>{schema.display[mode].title}</Title>
|
||||
<Backend_Fields root={ds as any} descriptor_fields={config.config_descriptor_fields}
|
||||
section={schema.display[mode]} rules={schema.rules} onChange={refresh}/>
|
||||
</div>
|
||||
<div style={{marginTop: 16}}>
|
||||
<Backend_Fields root={ds as any} descriptor_fields={config.config_descriptor_fields}
|
||||
section={schema.position} rules={schema.rules} onChange={refresh}/>
|
||||
</div>
|
||||
{this.render_aircraft_monitor_config(ds, mode)}
|
||||
<br/>
|
||||
<Button type="primary" autoInsertSpace onClick={() => {
|
||||
|
||||
@@ -4,231 +4,26 @@ export type Cesium_Panel_Position = {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
export type Cesium_Graphics_Config = {
|
||||
// 显示 Cesium FPS/帧耗时诊断面板,不改变画质。
|
||||
debugShowFramesPerSecond: boolean
|
||||
// 限制 Cesium 渲染循环目标帧率,降低上限可减少 GPU 占用。
|
||||
targetFrameRate: number
|
||||
// 使用浏览器推荐分辨率,避免高 DPI 屏幕按物理像素渲染。
|
||||
useBrowserRecommendedResolution: boolean
|
||||
// WebGL 内部渲染分辨率缩放,越低越省 GPU 但越模糊。
|
||||
resolutionScale: number
|
||||
// MSAA 多重采样数量,1 表示关闭几何抗锯齿。
|
||||
msaaSamples: number
|
||||
// FXAA 后处理抗锯齿,开销低但可能让文字和细线变软。
|
||||
fxaa: boolean
|
||||
// 场景阴影开关,大量模型或地形开启后 GPU 开销明显。
|
||||
shadows: boolean
|
||||
// 地球光照开关,开启后地表按光源方向产生明暗。
|
||||
enableLighting: boolean
|
||||
// 真实日照模式,使用太阳方向、系统时间和动态大气表达昼夜状态。
|
||||
solarLighting: boolean
|
||||
// 真实日照模式下的太阳光强度,只影响视觉明暗。
|
||||
solarLightIntensity: number
|
||||
// 地球瓦片屏幕空间误差,值越大越快但地面细节越粗。
|
||||
maximumScreenSpaceError: number
|
||||
// 仅 3D 模式加载 Terrarium 真实地形,2D/2.5D 不加载。
|
||||
terrainEnabledIn3D: boolean
|
||||
// 地形垂直起伏倍率,只改变视觉起伏,不增加高程精度。
|
||||
terrainExaggeration: number
|
||||
// 限制 Terrarium 地形最高请求层级,减少无效子瓦片请求。
|
||||
terrainLimitMaximumLevel: boolean
|
||||
// 启用层级限制时允许请求的最高地形层级。
|
||||
terrainMaximumLevel: number
|
||||
// 已解码地形高度瓦片缓存数量,越大越占内存但回看更快。
|
||||
terrainCacheTiles: number
|
||||
// 地形遮挡判断开关,基于 Terrarium 高程做基站到飞机的通视分析。
|
||||
terrainOcclusionEnabled: boolean
|
||||
// 地表导航模式下在用户点击地面处显示当前导航参考点和地表类型。
|
||||
surfaceNavigationReferenceVisible: boolean
|
||||
// 在 3D 视图角落显示类似 Blender 的视角 XYZ 坐标轴。
|
||||
viewAxesVisible: boolean
|
||||
// 性能诊断面板在地图容器中的左上角位置。
|
||||
performancePanelPosition: Cesium_Panel_Position
|
||||
// ViewCube 面板在地图容器中的左上角位置。
|
||||
viewAxesPanelPosition: Cesium_Panel_Position
|
||||
// ViewCube SVG 控件的显示尺寸。
|
||||
viewAxesPanelSize: number
|
||||
// 通视分析沿 ECEF 视线采样的间距,越小越精细但请求和计算越多。
|
||||
occlusionSampleSpacingMeters: number
|
||||
// 视线相对地形至少需要保留的净空,低于该值视为遮挡。
|
||||
occlusionClearanceMarginMeters: number
|
||||
}
|
||||
function default_view_axes_panel_position(): Cesium_Panel_Position {
|
||||
const width = typeof window === "undefined" ? 1280 : window.innerWidth;
|
||||
return {x: Math.max(16, width - 116), y: 76};
|
||||
}
|
||||
export const default_cesium_graphics_config: Cesium_Graphics_Config = {
|
||||
debugShowFramesPerSecond: true,
|
||||
targetFrameRate: 30,
|
||||
useBrowserRecommendedResolution: true,
|
||||
resolutionScale: 0.7,
|
||||
msaaSamples: 1,
|
||||
fxaa: false,
|
||||
shadows: false,
|
||||
enableLighting: false,
|
||||
solarLighting: false,
|
||||
solarLightIntensity: 1.5,
|
||||
maximumScreenSpaceError: 4,
|
||||
terrainEnabledIn3D: false,
|
||||
terrainExaggeration: 1.0,
|
||||
terrainLimitMaximumLevel: true,
|
||||
terrainMaximumLevel: 15,
|
||||
terrainCacheTiles: 128,
|
||||
terrainOcclusionEnabled: false,
|
||||
surfaceNavigationReferenceVisible: false,
|
||||
viewAxesVisible: false,
|
||||
performancePanelPosition: {x: 12, y: 92},
|
||||
viewAxesPanelPosition: default_view_axes_panel_position(),
|
||||
viewAxesPanelSize: 84,
|
||||
occlusionSampleSpacingMeters: 250,
|
||||
occlusionClearanceMarginMeters: 10
|
||||
};
|
||||
export type Cesium_Graphics_Config = Record<string, any>
|
||||
|
||||
export const default_cesium_graphics_config: Cesium_Graphics_Config = {};
|
||||
export const cesium_graphics_config_event = "ecap_cesium_graphics_config_changed";
|
||||
type Cesium_Graphics_Config_Json = {
|
||||
debug_show_frames_per_second: boolean
|
||||
target_frame_rate: number
|
||||
use_browser_recommended_resolution: boolean
|
||||
resolution_scale: number
|
||||
msaa_samples: number
|
||||
fxaa: boolean
|
||||
shadows: boolean
|
||||
enable_lighting: boolean
|
||||
solar_lighting: boolean
|
||||
solar_light_intensity: number
|
||||
maximum_screen_space_error: number
|
||||
terrain_enabled_in_3d: boolean
|
||||
terrain_exaggeration: number
|
||||
terrain_limit_maximum_level: boolean
|
||||
terrain_maximum_level: number
|
||||
terrain_cache_tiles: number
|
||||
terrain_occlusion_enabled: boolean
|
||||
surface_navigation_reference_visible: boolean
|
||||
view_axes_visible: boolean
|
||||
performance_panel_x: number
|
||||
performance_panel_y: number
|
||||
view_axes_panel_x?: number
|
||||
view_axes_panel_y: number
|
||||
view_axes_panel_size: number
|
||||
occlusion_sample_spacing_meters: number
|
||||
occlusion_clearance_margin_meters: number
|
||||
}
|
||||
function number_value(value: unknown, fallback: number, min: number, max: number): number {
|
||||
const next = Number(value);
|
||||
if (!Number.isFinite(next)) return fallback;
|
||||
return Math.min(max, Math.max(min, next));
|
||||
}
|
||||
function unrestricted_number_value(value: unknown, fallback: number): number {
|
||||
const next = Number(value);
|
||||
return Number.isFinite(next) ? next : fallback;
|
||||
}
|
||||
function boolean_value(value: unknown, fallback: boolean): boolean {
|
||||
return typeof value === "boolean" ? value : fallback;
|
||||
}
|
||||
function panel_position_value(value: Partial<Cesium_Panel_Position> | undefined, fallback: Cesium_Panel_Position): Cesium_Panel_Position {
|
||||
return {
|
||||
x: number_value(value?.x, fallback.x, 0, 100000),
|
||||
y: number_value(value?.y, fallback.y, 0, 100000)
|
||||
};
|
||||
}
|
||||
export function normalize_cesium_graphics_config(data: Partial<Cesium_Graphics_Config>): Cesium_Graphics_Config {
|
||||
return {
|
||||
debugShowFramesPerSecond: boolean_value(data.debugShowFramesPerSecond, default_cesium_graphics_config.debugShowFramesPerSecond),
|
||||
targetFrameRate: number_value(data.targetFrameRate, default_cesium_graphics_config.targetFrameRate, 1, 120),
|
||||
useBrowserRecommendedResolution: boolean_value(data.useBrowserRecommendedResolution, default_cesium_graphics_config.useBrowserRecommendedResolution),
|
||||
resolutionScale: number_value(data.resolutionScale, default_cesium_graphics_config.resolutionScale, 0.2, 1.5),
|
||||
msaaSamples: number_value(data.msaaSamples, default_cesium_graphics_config.msaaSamples, 1, 8),
|
||||
fxaa: boolean_value(data.fxaa, default_cesium_graphics_config.fxaa),
|
||||
shadows: boolean_value(data.shadows, default_cesium_graphics_config.shadows),
|
||||
enableLighting: boolean_value(data.enableLighting, default_cesium_graphics_config.enableLighting),
|
||||
solarLighting: boolean_value(data.solarLighting, default_cesium_graphics_config.solarLighting),
|
||||
solarLightIntensity: number_value(data.solarLightIntensity, default_cesium_graphics_config.solarLightIntensity, 0, 10),
|
||||
maximumScreenSpaceError: number_value(data.maximumScreenSpaceError, default_cesium_graphics_config.maximumScreenSpaceError, 1, 16),
|
||||
terrainEnabledIn3D: boolean_value(data.terrainEnabledIn3D, default_cesium_graphics_config.terrainEnabledIn3D),
|
||||
terrainExaggeration: unrestricted_number_value(data.terrainExaggeration, default_cesium_graphics_config.terrainExaggeration),
|
||||
terrainLimitMaximumLevel: boolean_value(data.terrainLimitMaximumLevel, default_cesium_graphics_config.terrainLimitMaximumLevel),
|
||||
terrainMaximumLevel: number_value(data.terrainMaximumLevel, default_cesium_graphics_config.terrainMaximumLevel, 0, 24),
|
||||
terrainCacheTiles: Math.round(number_value(data.terrainCacheTiles, default_cesium_graphics_config.terrainCacheTiles, 16, 2048)),
|
||||
terrainOcclusionEnabled: boolean_value(data.terrainOcclusionEnabled, default_cesium_graphics_config.terrainOcclusionEnabled),
|
||||
surfaceNavigationReferenceVisible: boolean_value(data.surfaceNavigationReferenceVisible, default_cesium_graphics_config.surfaceNavigationReferenceVisible),
|
||||
viewAxesVisible: boolean_value(data.viewAxesVisible, default_cesium_graphics_config.viewAxesVisible),
|
||||
performancePanelPosition: panel_position_value(data.performancePanelPosition, default_cesium_graphics_config.performancePanelPosition),
|
||||
viewAxesPanelPosition: panel_position_value(data.viewAxesPanelPosition, default_cesium_graphics_config.viewAxesPanelPosition),
|
||||
viewAxesPanelSize: number_value(data.viewAxesPanelSize, default_cesium_graphics_config.viewAxesPanelSize, 48, 240),
|
||||
occlusionSampleSpacingMeters: number_value(data.occlusionSampleSpacingMeters, default_cesium_graphics_config.occlusionSampleSpacingMeters, 10, 5000),
|
||||
occlusionClearanceMarginMeters: unrestricted_number_value(data.occlusionClearanceMarginMeters, default_cesium_graphics_config.occlusionClearanceMarginMeters)
|
||||
};
|
||||
}
|
||||
function from_server_config(data: Partial<Cesium_Graphics_Config_Json>): Cesium_Graphics_Config {
|
||||
return normalize_cesium_graphics_config({
|
||||
debugShowFramesPerSecond: data.debug_show_frames_per_second,
|
||||
targetFrameRate: data.target_frame_rate,
|
||||
useBrowserRecommendedResolution: data.use_browser_recommended_resolution,
|
||||
resolutionScale: data.resolution_scale,
|
||||
msaaSamples: data.msaa_samples,
|
||||
fxaa: data.fxaa,
|
||||
shadows: data.shadows,
|
||||
enableLighting: data.enable_lighting,
|
||||
solarLighting: data.solar_lighting,
|
||||
solarLightIntensity: data.solar_light_intensity,
|
||||
maximumScreenSpaceError: data.maximum_screen_space_error,
|
||||
terrainEnabledIn3D: data.terrain_enabled_in_3d,
|
||||
terrainExaggeration: data.terrain_exaggeration,
|
||||
terrainLimitMaximumLevel: data.terrain_limit_maximum_level,
|
||||
terrainMaximumLevel: data.terrain_maximum_level,
|
||||
terrainCacheTiles: data.terrain_cache_tiles,
|
||||
terrainOcclusionEnabled: data.terrain_occlusion_enabled,
|
||||
surfaceNavigationReferenceVisible: data.surface_navigation_reference_visible,
|
||||
viewAxesVisible: data.view_axes_visible,
|
||||
performancePanelPosition: {x: data.performance_panel_x, y: data.performance_panel_y},
|
||||
viewAxesPanelPosition: {x: data.view_axes_panel_x, y: data.view_axes_panel_y},
|
||||
viewAxesPanelSize: data.view_axes_panel_size,
|
||||
occlusionSampleSpacingMeters: data.occlusion_sample_spacing_meters,
|
||||
occlusionClearanceMarginMeters: data.occlusion_clearance_margin_meters
|
||||
});
|
||||
}
|
||||
function to_server_config(config: Cesium_Graphics_Config): Cesium_Graphics_Config_Json {
|
||||
const normalized = normalize_cesium_graphics_config(config);
|
||||
return {
|
||||
debug_show_frames_per_second: normalized.debugShowFramesPerSecond,
|
||||
target_frame_rate: normalized.targetFrameRate,
|
||||
use_browser_recommended_resolution: normalized.useBrowserRecommendedResolution,
|
||||
resolution_scale: normalized.resolutionScale,
|
||||
msaa_samples: normalized.msaaSamples,
|
||||
fxaa: normalized.fxaa,
|
||||
shadows: normalized.shadows,
|
||||
enable_lighting: normalized.enableLighting,
|
||||
solar_lighting: normalized.solarLighting,
|
||||
solar_light_intensity: normalized.solarLightIntensity,
|
||||
maximum_screen_space_error: normalized.maximumScreenSpaceError,
|
||||
terrain_enabled_in_3d: normalized.terrainEnabledIn3D,
|
||||
terrain_exaggeration: normalized.terrainExaggeration,
|
||||
terrain_limit_maximum_level: normalized.terrainLimitMaximumLevel,
|
||||
terrain_maximum_level: normalized.terrainMaximumLevel,
|
||||
terrain_cache_tiles: normalized.terrainCacheTiles,
|
||||
terrain_occlusion_enabled: normalized.terrainOcclusionEnabled,
|
||||
surface_navigation_reference_visible: normalized.surfaceNavigationReferenceVisible,
|
||||
view_axes_visible: normalized.viewAxesVisible,
|
||||
performance_panel_x: normalized.performancePanelPosition.x,
|
||||
performance_panel_y: normalized.performancePanelPosition.y,
|
||||
view_axes_panel_x: normalized.viewAxesPanelPosition.x,
|
||||
view_axes_panel_y: normalized.viewAxesPanelPosition.y,
|
||||
view_axes_panel_size: normalized.viewAxesPanelSize,
|
||||
occlusion_sample_spacing_meters: normalized.occlusionSampleSpacingMeters,
|
||||
occlusion_clearance_margin_meters: normalized.occlusionClearanceMarginMeters
|
||||
};
|
||||
|
||||
export function normalize_cesium_graphics_config(data: Cesium_Graphics_Config): Cesium_Graphics_Config {
|
||||
return {...data};
|
||||
}
|
||||
|
||||
export async function load_cesium_graphics_config(): Promise<Cesium_Graphics_Config> {
|
||||
const response = await axios.get<Cesium_Graphics_Config_Json>("/map/graphics");
|
||||
return from_server_config(response.data);
|
||||
const response = await axios.get<Cesium_Graphics_Config>("/map/graphics");
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function save_cesium_graphics_config(config: Cesium_Graphics_Config): Promise<Cesium_Graphics_Config> {
|
||||
const response = await axios.post<Cesium_Graphics_Config_Json>("/map/graphics", to_server_config(config));
|
||||
const normalized = from_server_config(response.data);
|
||||
emit_cesium_graphics_config(normalized);
|
||||
return normalized;
|
||||
const response = await axios.post<Cesium_Graphics_Config>("/map/graphics", config);
|
||||
emit_cesium_graphics_config(response.data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export function emit_cesium_graphics_config(config: Cesium_Graphics_Config) {
|
||||
const normalized = normalize_cesium_graphics_config(config);
|
||||
window.dispatchEvent(new CustomEvent<Cesium_Graphics_Config>(cesium_graphics_config_event, {detail: normalized}));
|
||||
window.dispatchEvent(new CustomEvent<Cesium_Graphics_Config>(cesium_graphics_config_event, {detail: config}));
|
||||
}
|
||||
|
||||
+69
-63
@@ -430,25 +430,25 @@ export class Cesium_Map extends enhance.Base {
|
||||
}
|
||||
}
|
||||
base_station_fly_to_height_offset_meters(): number {
|
||||
return (this.map_view_config || default_map_view_config).baseStationFlyToHeightOffsetMeters;
|
||||
return (this.map_view_config || default_map_view_config).base_station_fly_to_height_offset_meters;
|
||||
}
|
||||
change_base_station_fly_to_height_offset_meters(value: number) {
|
||||
const config = this.map_view_config || default_map_view_config;
|
||||
this.map_view_config = {...config, baseStationFlyToHeightOffsetMeters: Math.max(100, value)};
|
||||
this.map_view_config = {...config, base_station_fly_to_height_offset_meters: Math.max(100, value)};
|
||||
this.flush();
|
||||
}
|
||||
set_surface_navigation_reference_visible(value: boolean) {
|
||||
this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, surfaceNavigationReferenceVisible: value});
|
||||
this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, surface_navigation_reference_visible: value});
|
||||
this.apply_graphics_config();
|
||||
this.flush();
|
||||
}
|
||||
set_view_axes_visible(value: boolean) {
|
||||
this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, viewAxesVisible: value});
|
||||
this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, view_axes_visible: value});
|
||||
this.apply_graphics_config();
|
||||
this.flush();
|
||||
}
|
||||
change_view_axes_panel_size(value: number) {
|
||||
this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, viewAxesPanelSize: value});
|
||||
this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, view_axes_panel_size: value});
|
||||
this.flush();
|
||||
}
|
||||
async save_current_cesium_graphics_config() {
|
||||
@@ -497,17 +497,17 @@ export class Cesium_Map extends enhance.Base {
|
||||
return Cesium.SceneMode.SCENE3D;
|
||||
}
|
||||
should_use_terrarium_terrain(): boolean {
|
||||
return this.scene_mode === "3d" && this.graphics_config.terrainEnabledIn3D && Boolean(this.map_resources?.terrain_sources.length);
|
||||
return this.scene_mode === "3d" && this.graphics_config.terrain_enabled_in_3d && Boolean(this.map_resources?.terrain_sources.length);
|
||||
}
|
||||
effective_terrain_maximum_level(): number | undefined {
|
||||
if (!this.map_resources || !this.graphics_config.terrainLimitMaximumLevel) return undefined;
|
||||
return Math.min(this.graphics_config.terrainMaximumLevel, this.current_terrain_resource().maximum_level);
|
||||
if (!this.map_resources || !this.graphics_config.terrain_limit_maximum_level) return undefined;
|
||||
return Math.min(this.graphics_config.terrain_maximum_level, this.current_terrain_resource().maximum_level);
|
||||
}
|
||||
current_terrain_signature(): string {
|
||||
if (!this.should_use_terrarium_terrain()) return "ellipsoid";
|
||||
const terrain = this.current_terrain_resource();
|
||||
const maximum_level = this.effective_terrain_maximum_level();
|
||||
return `terrarium:${this.current_terrain_source().key}:${terrain.minimum_level}:${terrain.maximum_level}:${maximum_level === undefined ? "unlimited" : maximum_level}:${this.graphics_config.terrainCacheTiles}`;
|
||||
return `terrarium:${this.current_terrain_source().key}:${terrain.minimum_level}:${terrain.maximum_level}:${maximum_level === undefined ? "unlimited" : maximum_level}:${this.graphics_config.terrain_cache_tiles}`;
|
||||
}
|
||||
create_terrain_provider(): Cesium.TerrainProvider {
|
||||
this.terrain_provider = null;
|
||||
@@ -517,7 +517,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
}
|
||||
this.terrain_provider = new Terrarium_Terrain_Provider(this.current_terrain_resource(), {
|
||||
maximum_level: this.effective_terrain_maximum_level(),
|
||||
max_cached_height_tiles: this.graphics_config.terrainCacheTiles
|
||||
max_cached_height_tiles: this.graphics_config.terrain_cache_tiles
|
||||
});
|
||||
return this.terrain_provider.provider;
|
||||
}
|
||||
@@ -541,7 +541,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
this.occlusion_terrain_signature = signature;
|
||||
this.occlusion_terrain_provider = new Terrarium_Terrain_Provider(this.current_terrain_resource(), {
|
||||
maximum_level: this.effective_terrain_maximum_level(),
|
||||
max_cached_height_tiles: this.graphics_config.terrainCacheTiles
|
||||
max_cached_height_tiles: this.graphics_config.terrain_cache_tiles
|
||||
});
|
||||
return this.occlusion_terrain_provider;
|
||||
}
|
||||
@@ -573,22 +573,22 @@ export class Cesium_Map extends enhance.Base {
|
||||
if (!this.viewer) return;
|
||||
const config = this.graphics_config;
|
||||
this.viewer.scene.debugShowFramesPerSecond = false;
|
||||
this.viewer.targetFrameRate = config.targetFrameRate;
|
||||
this.viewer.useBrowserRecommendedResolution = config.useBrowserRecommendedResolution;
|
||||
this.viewer.resolutionScale = config.resolutionScale;
|
||||
this.viewer.scene.msaaSamples = config.msaaSamples;
|
||||
this.viewer.targetFrameRate = config.target_frame_rate;
|
||||
this.viewer.useBrowserRecommendedResolution = config.use_browser_recommended_resolution;
|
||||
this.viewer.resolutionScale = config.resolution_scale;
|
||||
this.viewer.scene.msaaSamples = config.msaa_samples;
|
||||
this.viewer.scene.postProcessStages.fxaa.enabled = config.fxaa;
|
||||
this.viewer.shadows = config.shadows;
|
||||
this.viewer.scene.globe.enableLighting = config.enableLighting || config.solarLighting;
|
||||
this.viewer.scene.globe.dynamicAtmosphereLighting = config.solarLighting;
|
||||
this.viewer.scene.globe.dynamicAtmosphereLightingFromSun = config.solarLighting;
|
||||
this.viewer.scene.globe.atmosphereLightIntensity = config.solarLightIntensity;
|
||||
this.viewer.scene.light = new Cesium.SunLight({intensity: config.solarLightIntensity});
|
||||
this.viewer.clock.clockStep = config.solarLighting ? Cesium.ClockStep.SYSTEM_CLOCK : Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER;
|
||||
this.viewer.clock.shouldAnimate = config.solarLighting;
|
||||
this.viewer.scene.maximumRenderTimeChange = config.solarLighting ? 60 : Number.POSITIVE_INFINITY;
|
||||
this.viewer.scene.globe.maximumScreenSpaceError = config.maximumScreenSpaceError;
|
||||
this.viewer.scene.verticalExaggeration = config.terrainExaggeration;
|
||||
this.viewer.scene.globe.enableLighting = config.enable_lighting || config.solar_lighting;
|
||||
this.viewer.scene.globe.dynamicAtmosphereLighting = config.solar_lighting;
|
||||
this.viewer.scene.globe.dynamicAtmosphereLightingFromSun = config.solar_lighting;
|
||||
this.viewer.scene.globe.atmosphereLightIntensity = config.solar_light_intensity;
|
||||
this.viewer.scene.light = new Cesium.SunLight({intensity: config.solar_light_intensity});
|
||||
this.viewer.clock.clockStep = config.solar_lighting ? Cesium.ClockStep.SYSTEM_CLOCK : Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER;
|
||||
this.viewer.clock.shouldAnimate = config.solar_lighting;
|
||||
this.viewer.scene.maximumRenderTimeChange = config.solar_lighting ? 60 : Number.POSITIVE_INFINITY;
|
||||
this.viewer.scene.globe.maximumScreenSpaceError = config.maximum_screen_space_error;
|
||||
this.viewer.scene.verticalExaggeration = config.terrain_exaggeration;
|
||||
this.viewer.scene.verticalExaggerationRelativeHeight = 0.0;
|
||||
this.viewer.scene.requestRender();
|
||||
}
|
||||
@@ -596,15 +596,15 @@ export class Cesium_Map extends enhance.Base {
|
||||
for (const record of this.entity_map.values()) {
|
||||
if (record.aircraft.model) {
|
||||
const aircraft = (record.aircraft as any).ecap_aircraft as Aircraft | undefined;
|
||||
record.aircraft.model.uri = new Cesium.ConstantProperty(aircraft ? this.aircraft_model_item(aircraft).url : this.model_config.aircraftModel.url);
|
||||
record.aircraft.model.uri = new Cesium.ConstantProperty(aircraft ? this.aircraft_model_item(aircraft).url : this.model_config.aircraft_model.url);
|
||||
}
|
||||
}
|
||||
for (const record of this.base_station_entity_map.values()) {
|
||||
if (record.model.model) {
|
||||
record.model.model.uri = new Cesium.ConstantProperty(this.model_config.baseStationModel.url);
|
||||
record.model.model.uri = new Cesium.ConstantProperty(this.model_config.base_station_model.url);
|
||||
}
|
||||
if (record.device.model) {
|
||||
record.device.model.uri = new Cesium.ConstantProperty(this.model_config.deviceModel.url);
|
||||
record.device.model.uri = new Cesium.ConstantProperty(this.model_config.device_model.url);
|
||||
}
|
||||
}
|
||||
this.request_sync_data_sources();
|
||||
@@ -617,10 +617,10 @@ export class Cesium_Map extends enhance.Base {
|
||||
}
|
||||
update_debug_visuals(scene: Cesium.Scene) {
|
||||
this.sync_surface_navigation_reference(scene);
|
||||
if (this.graphics_config.viewAxesVisible && this.view_axes_panel_open) this.flush();
|
||||
if (this.graphics_config.view_axes_visible && this.view_axes_panel_open) this.flush();
|
||||
}
|
||||
sync_surface_navigation_reference(scene: Cesium.Scene) {
|
||||
if (!this.viewer || !this.graphics_config.surfaceNavigationReferenceVisible || this.camera_control_mode() !== Camera_Control_Mode.Surface_Navigation || this.scene_mode !== "3d" || scene.mode !== Cesium.SceneMode.SCENE3D) {
|
||||
if (!this.viewer || !this.graphics_config.surface_navigation_reference_visible || this.camera_control_mode() !== Camera_Control_Mode.Surface_Navigation || this.scene_mode !== "3d" || scene.mode !== Cesium.SceneMode.SCENE3D) {
|
||||
this.destroy_surface_navigation_reference();
|
||||
return;
|
||||
}
|
||||
@@ -637,7 +637,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
}
|
||||
}
|
||||
set_surface_navigation_reference_from_screen(screen_position: Cesium.Cartesian2): boolean {
|
||||
if (!this.viewer || !this.graphics_config.surfaceNavigationReferenceVisible || this.camera_control_mode() !== Camera_Control_Mode.Surface_Navigation) return false;
|
||||
if (!this.viewer || !this.graphics_config.surface_navigation_reference_visible || this.camera_control_mode() !== Camera_Control_Mode.Surface_Navigation) return false;
|
||||
const ray = this.viewer.camera.getPickRay(screen_position);
|
||||
const position = ray ? this.viewer.scene.globe.pick(ray, this.viewer.scene) : undefined;
|
||||
if (!position) return false;
|
||||
@@ -994,7 +994,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
const ground_position = Cesium.Cartesian3.fromDegrees(station.pos.lng, station.pos.lat, heights.ground_height);
|
||||
const device_position = Cesium.Cartesian3.fromDegrees(station.pos.lng, station.pos.lat, heights.device_height);
|
||||
const orientation = this.base_station_orientation(ground_position);
|
||||
const device_orientation = this.model_orientation(device_position, this.model_config.deviceModel);
|
||||
const device_orientation = this.model_orientation(device_position, this.model_config.device_model);
|
||||
const color = Cesium.Color.fromCssColorString(style.base_station_color || style.color);
|
||||
let record = this.base_station_entity_map.get(key);
|
||||
if (!record) {
|
||||
@@ -1019,7 +1019,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
position: ground_position,
|
||||
orientation,
|
||||
model: {
|
||||
uri: this.model_config.baseStationModel.url,
|
||||
uri: this.model_config.base_station_model.url,
|
||||
scale: this.base_station_physical_scale(ds),
|
||||
minimumPixelSize: 0,
|
||||
nodeTransformations: this.base_station_node_transformations(ds),
|
||||
@@ -1033,7 +1033,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
position: device_position,
|
||||
orientation: device_orientation,
|
||||
model: {
|
||||
uri: this.model_config.deviceModel.url,
|
||||
uri: this.model_config.device_model.url,
|
||||
minimumPixelSize: this.device_display_size(ds),
|
||||
maximumScale: 500,
|
||||
nodeTransformations: this.device_node_transformations(),
|
||||
@@ -1332,7 +1332,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
current_terrain_sample_signature(): string {
|
||||
if (!this.map_resources) return "none";
|
||||
const terrain = this.current_terrain_resource();
|
||||
return `${this.current_terrain_source().key}:${terrain.minimum_level}:${terrain.maximum_level}:${this.effective_terrain_maximum_level() ?? "unlimited"}:${this.graphics_config.terrainCacheTiles}`;
|
||||
return `${this.current_terrain_source().key}:${terrain.minimum_level}:${terrain.maximum_level}:${this.effective_terrain_maximum_level() ?? "unlimited"}:${this.graphics_config.terrain_cache_tiles}`;
|
||||
}
|
||||
base_station_description(ds: Data_Source) {
|
||||
const station = ds.base_station;
|
||||
@@ -1371,7 +1371,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
].join("<br>");
|
||||
}
|
||||
base_station_physical_scale(ds: Data_Source): number {
|
||||
return Math.max(0.000001, this.base_station_heights(ds).antenna_height / Math.max(0.000001, this.model_config.baseStationModel.builtInSize));
|
||||
return Math.max(0.000001, this.base_station_heights(ds).antenna_height / Math.max(0.000001, this.model_config.base_station_model.built_in_size));
|
||||
}
|
||||
base_station_node_transformations(ds: Data_Source): {[key: string]: Cesium.TranslationRotationScale} {
|
||||
const horizontal_scale = Math.max(0.000001, ds.map3d_style().base_station_scale || 1);
|
||||
@@ -1385,21 +1385,21 @@ export class Cesium_Map extends enhance.Base {
|
||||
};
|
||||
}
|
||||
device_display_size(ds: Data_Source): number {
|
||||
return Math.max(1, this.model_config.deviceModel.builtInSize * (ds.map3d_style().base_station_scale || 1));
|
||||
return Math.max(1, this.model_config.device_model.built_in_size * (ds.map3d_style().base_station_scale || 1));
|
||||
}
|
||||
device_physical_scale(ds: Data_Source): number {
|
||||
return this.model_config.deviceModel.builtInSize * (ds.map3d_style().base_station_scale || 1) * 0.000001;
|
||||
return this.model_config.device_model.built_in_size * (ds.map3d_style().base_station_scale || 1) * 0.000001;
|
||||
}
|
||||
base_station_orientation(position: Cesium.Cartesian3): Cesium.Quaternion {
|
||||
return this.model_orientation(position, this.model_config.baseStationModel);
|
||||
return this.model_orientation(position, this.model_config.base_station_model);
|
||||
}
|
||||
model_orientation(position: Cesium.Cartesian3, model: Map_Model_Item_Config): Cesium.Quaternion {
|
||||
return Cesium.Transforms.headingPitchRollQuaternion(
|
||||
position,
|
||||
new Cesium.HeadingPitchRoll(
|
||||
Cesium.Math.toRadians(model.headingOffsetDegrees),
|
||||
Cesium.Math.toRadians(model.pitchOffsetDegrees),
|
||||
Cesium.Math.toRadians(model.rollOffsetDegrees)
|
||||
Cesium.Math.toRadians(model.heading_offset_degrees),
|
||||
Cesium.Math.toRadians(model.pitch_offset_degrees),
|
||||
Cesium.Math.toRadians(model.roll_offset_degrees)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1567,7 +1567,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
this.apply_aircraft_occlusion_visual(aircraft, record, station_position, aircraft_position);
|
||||
}
|
||||
should_check_aircraft_occlusion(aircraft: Aircraft): boolean {
|
||||
if (!this.graphics_config.terrainOcclusionEnabled) return false;
|
||||
if (!this.graphics_config.terrain_occlusion_enabled) return false;
|
||||
if (this.selected_aircraft === aircraft) return true;
|
||||
return aircraft.data_source.is_manual_tracking_aircraft(aircraft.icao);
|
||||
}
|
||||
@@ -1591,8 +1591,8 @@ export class Cesium_Map extends enhance.Base {
|
||||
target.latitude.toFixed(8),
|
||||
target.height.toFixed(2),
|
||||
this.current_terrain_sample_signature(),
|
||||
this.graphics_config.occlusionSampleSpacingMeters,
|
||||
this.graphics_config.occlusionClearanceMarginMeters
|
||||
this.graphics_config.occlusion_sample_spacing_meters,
|
||||
this.graphics_config.occlusion_clearance_margin_meters
|
||||
].join(":");
|
||||
}
|
||||
apply_aircraft_occlusion_visual(aircraft: Aircraft, record: Aircraft_Entity_Record, station_position: Cesium.Cartesian3, aircraft_position: Cesium.Cartesian3) {
|
||||
@@ -1621,7 +1621,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
this.viewer?.scene.requestRender();
|
||||
}
|
||||
aircraft_occlusion_description(aircraft: Aircraft, result: Terrain_Occlusion_Result | null) {
|
||||
if (!this.graphics_config.terrainOcclusionEnabled) return "地形遮挡判断: 关闭";
|
||||
if (!this.graphics_config.terrain_occlusion_enabled) return "地形遮挡判断: 关闭";
|
||||
if (!result) return "地形遮挡判断: 计算中";
|
||||
return [
|
||||
`地形遮挡判断: ${result.obstructed ? "遮挡" : "无遮挡"}`,
|
||||
@@ -1648,8 +1648,8 @@ export class Cesium_Map extends enhance.Base {
|
||||
y_axis: terrain.y_axis
|
||||
},
|
||||
terrain_level: this.effective_los_terrain_level(),
|
||||
coarse_spacing: this.graphics_config.occlusionSampleSpacingMeters,
|
||||
clearance_margin: this.graphics_config.occlusionClearanceMarginMeters
|
||||
coarse_spacing: this.graphics_config.occlusion_sample_spacing_meters,
|
||||
clearance_margin: this.graphics_config.occlusion_clearance_margin_meters
|
||||
};
|
||||
const result = await this.los_worker_pool_instance().run(request);
|
||||
return {
|
||||
@@ -1662,13 +1662,13 @@ export class Cesium_Map extends enhance.Base {
|
||||
}
|
||||
aircraft_model_item(aircraft: Aircraft): Map_Model_Item_Config {
|
||||
const key = aircraft.data_model.vortexTypeKey;
|
||||
return (key && this.model_config.aircraftModels[key]) || this.model_config.aircraftModel;
|
||||
return (key && this.model_config.aircraft_models[key]) || this.model_config.aircraft_model;
|
||||
}
|
||||
aircraft_display_size(aircraft: Aircraft): number {
|
||||
return Math.max(1, this.aircraft_model_item(aircraft).builtInSize * (aircraft.data_source.map3d_style().aircraft_scale || 1));
|
||||
return Math.max(1, this.aircraft_model_item(aircraft).built_in_size * (aircraft.data_source.map3d_style().aircraft_scale || 1));
|
||||
}
|
||||
aircraft_physical_scale(aircraft: Aircraft): number {
|
||||
return this.aircraft_model_item(aircraft).builtInSize * (aircraft.data_source.map3d_style().aircraft_scale || 1) * 0.000001;
|
||||
return this.aircraft_model_item(aircraft).built_in_size * (aircraft.data_source.map3d_style().aircraft_scale || 1) * 0.000001;
|
||||
}
|
||||
aircraft_color(aircraft: Aircraft): Cesium.Color {
|
||||
return Cesium.Color.fromCssColorString(aircraft.data_source.map3d_style().color);
|
||||
@@ -1696,9 +1696,9 @@ export class Cesium_Map extends enhance.Base {
|
||||
return Cesium.Transforms.headingPitchRollQuaternion(
|
||||
position,
|
||||
new Cesium.HeadingPitchRoll(
|
||||
Cesium.Math.toRadians(this.aircraft_heading(aircraft) + model.headingOffsetDegrees),
|
||||
Cesium.Math.toRadians(this.aircraft_pitch(aircraft) + model.pitchOffsetDegrees),
|
||||
Cesium.Math.toRadians(this.aircraft_roll(aircraft) + model.rollOffsetDegrees)
|
||||
Cesium.Math.toRadians(this.aircraft_heading(aircraft) + model.heading_offset_degrees),
|
||||
Cesium.Math.toRadians(this.aircraft_pitch(aircraft) + model.pitch_offset_degrees),
|
||||
Cesium.Math.toRadians(this.aircraft_roll(aircraft) + model.roll_offset_degrees)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -2176,16 +2176,18 @@ export class Cesium_Map extends enhance.Base {
|
||||
return null;
|
||||
}
|
||||
graphics_panel_position(key: Graphics_Panel_Key): Cesium_Panel_Position {
|
||||
const position = key === "performance" ? this.graphics_config.performancePanelPosition : this.graphics_config.viewAxesPanelPosition;
|
||||
const position = key === "performance"
|
||||
? {x: Number(this.graphics_config.performance_panel_x || 0), y: Number(this.graphics_config.performance_panel_y || 0)}
|
||||
: {x: Number(this.graphics_config.view_axes_panel_x || 0), y: Number(this.graphics_config.view_axes_panel_y || 0)};
|
||||
return this.clamp_graphics_panel_position(position);
|
||||
}
|
||||
change_graphics_panel_position(key: Graphics_Panel_Key, position: Cesium_Panel_Position) {
|
||||
const next_position = this.clamp_graphics_panel_position(position);
|
||||
if (key === "performance") {
|
||||
this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, performancePanelPosition: next_position});
|
||||
this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, performance_panel_x: next_position.x, performance_panel_y: next_position.y});
|
||||
}
|
||||
else {
|
||||
this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, viewAxesPanelPosition: next_position});
|
||||
this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, view_axes_panel_x: next_position.x, view_axes_panel_y: next_position.y});
|
||||
}
|
||||
this.flush();
|
||||
}
|
||||
@@ -2237,10 +2239,14 @@ export class Cesium_Map extends enhance.Base {
|
||||
this.remove_graphics_panel_drag_listeners();
|
||||
}
|
||||
save_graphics_panel_layout() {
|
||||
const performance = this.graphics_panel_position("performance");
|
||||
const view_axes = this.graphics_panel_position("viewAxes");
|
||||
this.graphics_config = normalize_cesium_graphics_config({
|
||||
...this.graphics_config,
|
||||
performancePanelPosition: this.graphics_panel_position("performance"),
|
||||
viewAxesPanelPosition: this.graphics_panel_position("viewAxes")
|
||||
performance_panel_x: performance.x,
|
||||
performance_panel_y: performance.y,
|
||||
view_axes_panel_x: view_axes.x,
|
||||
view_axes_panel_y: view_axes.y
|
||||
});
|
||||
this.save_current_cesium_graphics_config();
|
||||
}
|
||||
@@ -2260,7 +2266,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
}}/>
|
||||
{key === "viewAxes" && <>
|
||||
<span style={{userSelect: "none"}}>大小</span>
|
||||
<InputNumber size="small" min={48} max={240} step={4} value={this.graphics_config.viewAxesPanelSize} onPointerDown={(event) => event.stopPropagation()} onChange={(value: number | null) => {
|
||||
<InputNumber size="small" min={48} max={240} step={4} value={this.graphics_config.view_axes_panel_size} onPointerDown={(event) => event.stopPropagation()} onChange={(value: number | null) => {
|
||||
if (value !== null) this.change_view_axes_panel_size(value);
|
||||
}} style={{width: 76}}/>
|
||||
</>}
|
||||
@@ -2269,7 +2275,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
);
|
||||
}
|
||||
render_performance_panel() {
|
||||
if (!this.graphics_config.debugShowFramesPerSecond) return null;
|
||||
if (!this.graphics_config.debug_show_frames_per_second) return null;
|
||||
const info = this.performance_info;
|
||||
const position = this.graphics_panel_position("performance");
|
||||
return (
|
||||
@@ -2286,7 +2292,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
);
|
||||
}
|
||||
view_axes_visible(): boolean {
|
||||
return Boolean(this.viewer && this.graphics_config.viewAxesVisible && this.scene_mode === "3d" && this.viewer.scene.mode === Cesium.SceneMode.SCENE3D);
|
||||
return Boolean(this.viewer && this.graphics_config.view_axes_visible && this.scene_mode === "3d" && this.viewer.scene.mode === Cesium.SceneMode.SCENE3D);
|
||||
}
|
||||
view_axes_navigation_center(): Cesium.Cartesian3 {
|
||||
if (this.surface_navigation_reference_position_value) return this.surface_navigation_reference_position_value;
|
||||
@@ -2456,7 +2462,7 @@ export class Cesium_Map extends enhance.Base {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const size = this.graphics_config.viewAxesPanelSize;
|
||||
const size = this.graphics_config.view_axes_panel_size;
|
||||
const center = size * 0.5;
|
||||
const radius = size * 0.31;
|
||||
const stroke_width = Math.max(1.2, size * 0.021);
|
||||
|
||||
@@ -1,134 +1,116 @@
|
||||
import React from "react";
|
||||
import {Button, Input, InputNumber, message, Space, Upload} from "antd";
|
||||
import {Button, message, Space, Upload} from "antd";
|
||||
import {UploadOutlined} from "@ant-design/icons";
|
||||
import enhance from "../core/enhance.tsx";
|
||||
import {col_style, row_style, setting_style} from "../Global.tsx";
|
||||
import {
|
||||
aircraft_model_type_options,
|
||||
empty_map_model_config,
|
||||
load_map_model_config,
|
||||
save_map_model_config,
|
||||
upload_map_model,
|
||||
type Map_Model_Item_Config,
|
||||
type Map_Model_Config
|
||||
} from "./Map_Models.tsx";
|
||||
import {
|
||||
Backend_Fields,
|
||||
type Backend_Field_Descriptor,
|
||||
get_path
|
||||
} from "../Adminive/Backend_Fields.tsx";
|
||||
|
||||
type Model_Number_Key = keyof Pick<Map_Model_Item_Config, "builtInSize" | "headingOffsetDegrees" | "pitchOffsetDegrees" | "rollOffsetDegrees">
|
||||
type Model_Upload_Type = "aircraft" | "base_station" | "device" | "aircraft_type"
|
||||
type Model_Upload_Schema = {
|
||||
model_type: string
|
||||
label: string
|
||||
success_message?: string
|
||||
}
|
||||
type Model_Group_Schema = {
|
||||
title: string
|
||||
path: string
|
||||
upload: Model_Upload_Schema
|
||||
}
|
||||
type Model_Collection_Schema = {
|
||||
title: string
|
||||
path: string
|
||||
upload_model_type: string
|
||||
upload_label?: string
|
||||
upload_success_message?: string
|
||||
items: {key: string, label: string}[]
|
||||
}
|
||||
export type Cesium_Model_Settings_Schema = {
|
||||
title?: string
|
||||
data_api: string
|
||||
upload_accept: string
|
||||
aircraft_types: string[]
|
||||
item_descriptor: {fields: Backend_Field_Descriptor[]}
|
||||
groups: Model_Group_Schema[]
|
||||
collections: Model_Collection_Schema[]
|
||||
}
|
||||
|
||||
export class Cesium_Model_Settings extends enhance.Base {
|
||||
config: Map_Model_Config = {...empty_map_model_config};
|
||||
number_input_drafts: Record<string, number | null> = {};
|
||||
config: Map_Model_Config = empty_map_model_config;
|
||||
schema: Cesium_Model_Settings_Schema;
|
||||
|
||||
constructor(schema: Cesium_Model_Settings_Schema) {
|
||||
super();
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
async on_mount() {
|
||||
this.config = await load_map_model_config(this.schema.data_api);
|
||||
this.number_input_drafts = {};
|
||||
this.flush();
|
||||
}
|
||||
async upload(model_type: Model_Upload_Type, file: File, aircraft_model_key?: string) {
|
||||
if (!file.name.toLowerCase().endsWith(".glb")) {
|
||||
message.error("只支持上传 .glb 模型");
|
||||
|
||||
accepted_file(file: File): boolean {
|
||||
const extensions = this.schema.upload_accept.split(",").map(value => value.trim().toLowerCase()).filter(Boolean);
|
||||
return extensions.length === 0 || extensions.some(extension => file.name.toLowerCase().endsWith(extension));
|
||||
}
|
||||
|
||||
async upload(upload_schema: Model_Upload_Schema, file: File, item_key?: string) {
|
||||
if (!this.accepted_file(file)) {
|
||||
message.error(`只支持上传 ${this.schema.upload_accept} 模型`);
|
||||
return false;
|
||||
}
|
||||
this.config = await upload_map_model(model_type, file, aircraft_model_key, this.schema.data_api);
|
||||
message.success(this.model_upload_message(model_type));
|
||||
this.config = await upload_map_model(upload_schema.model_type, file, item_key, this.schema.data_api);
|
||||
message.success(upload_schema.success_message || "模型已更新");
|
||||
this.flush();
|
||||
return false;
|
||||
}
|
||||
model_upload_message(model_type: Model_Upload_Type): string {
|
||||
if (model_type === "aircraft") return "默认飞机模型已更新";
|
||||
if (model_type === "aircraft_type") return "分类飞机模型已更新";
|
||||
if (model_type === "device") return "设备模型已更新";
|
||||
return "基站模型已更新";
|
||||
}
|
||||
number_input_key(model: Map_Model_Item_Config, key: Model_Number_Key): string {
|
||||
return `${model.url}:${key}`;
|
||||
}
|
||||
number_input_value(model: Map_Model_Item_Config, key: Model_Number_Key): number | null {
|
||||
const input_key = this.number_input_key(model, key);
|
||||
return Object.prototype.hasOwnProperty.call(this.number_input_drafts, input_key) ? this.number_input_drafts[input_key] : model[key];
|
||||
}
|
||||
set_model_number(model: Map_Model_Item_Config, key: Model_Number_Key, value: number | null) {
|
||||
const input_key = this.number_input_key(model, key);
|
||||
this.number_input_drafts[input_key] = value;
|
||||
if (value !== null) {
|
||||
model[key] = value;
|
||||
}
|
||||
this.flush();
|
||||
}
|
||||
blur_model_number(model: Map_Model_Item_Config, key: Model_Number_Key) {
|
||||
const input_key = this.number_input_key(model, key);
|
||||
if (Object.prototype.hasOwnProperty.call(this.number_input_drafts, input_key)) {
|
||||
delete this.number_input_drafts[input_key];
|
||||
}
|
||||
this.flush();
|
||||
}
|
||||
|
||||
async save() {
|
||||
this.config = await save_map_model_config(this.config, this.schema.data_api);
|
||||
this.number_input_drafts = {};
|
||||
message.success("模型配置已保存");
|
||||
this.flush();
|
||||
}
|
||||
render_model_detail(model: Map_Model_Item_Config, size_label = "内置大小") {
|
||||
|
||||
render_model(title: string, path: string, upload_schema: Model_Upload_Schema, item_key?: string) {
|
||||
const fields = this.schema.item_descriptor?.fields || [];
|
||||
if (!get_path(this.config, path)) return null;
|
||||
return (
|
||||
<Space wrap>
|
||||
<InputNumber addonBefore={size_label} value={this.number_input_value(model, "builtInSize")} min={0} step={0.1} precision={6} onChange={(value) => this.set_model_number(model, "builtInSize", value)} onBlur={() => this.blur_model_number(model, "builtInSize")} style={{width: 210}} />
|
||||
<InputNumber addonBefore="航向偏移" value={this.number_input_value(model, "headingOffsetDegrees")} step={1} precision={2} onChange={(value) => this.set_model_number(model, "headingOffsetDegrees", value)} onBlur={() => this.blur_model_number(model, "headingOffsetDegrees")} style={{width: 180}} />
|
||||
<InputNumber addonBefore="俯仰偏移" value={this.number_input_value(model, "pitchOffsetDegrees")} step={1} precision={2} onChange={(value) => this.set_model_number(model, "pitchOffsetDegrees", value)} onBlur={() => this.blur_model_number(model, "pitchOffsetDegrees")} style={{width: 180}} />
|
||||
<InputNumber addonBefore="横滚偏移" value={this.number_input_value(model, "rollOffsetDegrees")} step={1} precision={2} onChange={(value) => this.set_model_number(model, "rollOffsetDegrees", value)} onBlur={() => this.blur_model_number(model, "rollOffsetDegrees")} style={{width: 180}} />
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
render_aircraft_type_model(option: {key: string, label: string}) {
|
||||
const model = this.config.aircraftModels[option.key] || this.config.aircraftModel;
|
||||
return (
|
||||
<Space key={option.key} direction="vertical" size={4}>
|
||||
<Space wrap>
|
||||
<span style={{width: 210}}>{option.label}</span>
|
||||
<Input value={model.url} readOnly style={{width: 360}} />
|
||||
<Upload accept={this.schema.upload_accept} showUploadList={false} beforeUpload={(file) => this.upload("aircraft_type", file, option.key)}>
|
||||
<Button icon={<UploadOutlined />}>上传GLB</Button>
|
||||
</Upload>
|
||||
</Space>
|
||||
{this.render_model_detail(model)}
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
render_model_upload(label: string, model: Map_Model_Item_Config, model_type: "aircraft" | "base_station" | "device") {
|
||||
return (
|
||||
<Space direction="vertical" size={4}>
|
||||
<Space wrap>
|
||||
<Input addonBefore={label} value={model.url} readOnly style={{width: 360}} />
|
||||
<Upload accept={this.schema.upload_accept} showUploadList={false} beforeUpload={(file) => this.upload(model_type, file)}>
|
||||
<Button icon={<UploadOutlined />}>上传GLB</Button>
|
||||
</Upload>
|
||||
</Space>
|
||||
{this.render_model_detail(model, model_type === "base_station" ? "模型原始高度" : "内置大小")}
|
||||
<Space key={path} direction="vertical" size={6} style={{width: "100%"}}>
|
||||
<h3 style={row_style}>{title}</h3>
|
||||
<Backend_Fields root={this.config} descriptor_fields={fields}
|
||||
section={{object_path: path, descriptor_path: "", fields: fields.map(field => field.name)}}
|
||||
onChange={() => this.flush()}/>
|
||||
<Upload accept={this.schema.upload_accept} showUploadList={false}
|
||||
beforeUpload={file => this.upload(upload_schema, file, item_key)}>
|
||||
<Button icon={<UploadOutlined/>}>{upload_schema.label}</Button>
|
||||
</Upload>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
render(props: any) {
|
||||
return (
|
||||
<div style={setting_style}>
|
||||
<h2 style={row_style}>{this.schema.title ?? "Cesium模型设置"}</h2>
|
||||
<Space direction="vertical" size={8} style={col_style}>
|
||||
<h3 style={row_style}>默认飞机模型</h3>
|
||||
{this.render_model_upload("飞机模型", this.config.aircraftModel, "aircraft")}
|
||||
<h3 style={row_style}>基站模型</h3>
|
||||
{this.render_model_upload("基站模型", this.config.baseStationModel, "base_station")}
|
||||
<h3 style={row_style}>设备模型</h3>
|
||||
{this.render_model_upload("设备模型", this.config.deviceModel, "device")}
|
||||
<h3 style={row_style}>按涡流/目标类型选择飞机模型</h3>
|
||||
{this.schema.aircraft_types.map(key => this.render_aircraft_type_model(aircraft_model_type_options.find(option => option.key === key) ?? {key, label: key}))}
|
||||
<Space direction="vertical" size={12} style={col_style}>
|
||||
{(this.schema.groups || []).map(group =>
|
||||
this.render_model(group.title, group.path, group.upload))}
|
||||
{(this.schema.collections || []).flatMap(collection => [
|
||||
<h3 key={`${collection.path}:title`} style={row_style}>{collection.title}</h3>,
|
||||
...collection.items.map(item => this.render_model(item.label, `${collection.path}.${item.key}`, {
|
||||
model_type: collection.upload_model_type,
|
||||
label: collection.upload_label || "上传模型",
|
||||
success_message: collection.upload_success_message
|
||||
}, item.key))
|
||||
])}
|
||||
<Button type="primary" onClick={() => this.save()}>保存模型配置</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
+17
-124
@@ -1,139 +1,32 @@
|
||||
import axios from "axios";
|
||||
|
||||
export type Aircraft_Model_Type_Option = {
|
||||
key: string
|
||||
label: string
|
||||
modelFile: string
|
||||
}
|
||||
export const aircraft_model_type_options: Aircraft_Model_Type_Option[] = [
|
||||
{key: "no_category_information", label: "无类别信息", modelFile: "aircraft-no-category-information.glb"},
|
||||
{key: "surface_emergency_vehicle", label: "地面应急车", modelFile: "aircraft-surface-emergency-vehicle.glb"},
|
||||
{key: "surface_service_vehicle", label: "地面服务车", modelFile: "aircraft-surface-service-vehicle.glb"},
|
||||
{key: "ground_obstruction_4", label: "地面障碍物4", modelFile: "aircraft-ground-obstruction-4.glb"},
|
||||
{key: "ground_obstruction_5", label: "地面障碍物5", modelFile: "aircraft-ground-obstruction-5.glb"},
|
||||
{key: "ground_obstruction_6", label: "地面障碍物6", modelFile: "aircraft-ground-obstruction-6.glb"},
|
||||
{key: "ground_obstruction_7", label: "地面障碍物7", modelFile: "aircraft-ground-obstruction-7.glb"},
|
||||
{key: "glider", label: "滑翔机,滑翔飞机", modelFile: "aircraft-glider.glb"},
|
||||
{key: "lighter_than_air", label: "比空气轻", modelFile: "aircraft-lighter-than-air.glb"},
|
||||
{key: "parachutist", label: "跳伞运动员", modelFile: "aircraft-parachutist.glb"},
|
||||
{key: "ultralight_hangglider_paraglider", label: "超轻型,悬挂式滑翔机,滑翔伞", modelFile: "aircraft-ultralight-hangglider-paraglider.glb"},
|
||||
{key: "reserved_3_5", label: "保留", modelFile: "aircraft-reserved-3-5.glb"},
|
||||
{key: "unmanned_aerial_vehicle", label: "无人机", modelFile: "aircraft-unmanned-aerial-vehicle.glb"},
|
||||
{key: "space_transatmospheric_vehicle", label: "太空或跨大气层飞行器", modelFile: "aircraft-space-transatmospheric-vehicle.glb"},
|
||||
{key: "light_aircraft", label: "轻型飞机(小于 7000 公斤)", modelFile: "aircraft-light-aircraft.glb"},
|
||||
{key: "medium_1_aircraft", label: "中型 1(7000 公斤到 34000 公斤之间)", modelFile: "aircraft-medium-1-aircraft.glb"},
|
||||
{key: "medium_2_aircraft", label: "中型 2(34000 公斤到 136000 公斤之间)", modelFile: "aircraft-medium-2-aircraft.glb"},
|
||||
{key: "high_vortex_aircraft", label: "高涡旋飞机", modelFile: "aircraft-high-vortex-aircraft.glb"},
|
||||
{key: "heavy_aircraft", label: "重型飞机(大于 136000 公斤)", modelFile: "aircraft-heavy-aircraft.glb"},
|
||||
{key: "high_performance_aircraft", label: "高性能(>5 g 加速度)和高速(>400 节)", modelFile: "aircraft-high-performance-aircraft.glb"},
|
||||
{key: "rotorcraft", label: "旋翼机", modelFile: "aircraft-rotorcraft.glb"}
|
||||
];
|
||||
export type Map_Model_Item_Config = {
|
||||
url: string
|
||||
builtInSize: number
|
||||
headingOffsetDegrees: number
|
||||
pitchOffsetDegrees: number
|
||||
rollOffsetDegrees: number
|
||||
}
|
||||
export type Map_Model_Config = {
|
||||
aircraftModel: Map_Model_Item_Config
|
||||
aircraftModels: Record<string, Map_Model_Item_Config>
|
||||
baseStationModel: Map_Model_Item_Config
|
||||
deviceModel: Map_Model_Item_Config
|
||||
}
|
||||
type Map_Model_Item_Config_Json = {
|
||||
url: string
|
||||
built_in_size: number
|
||||
heading_offset_degrees: number
|
||||
pitch_offset_degrees: number
|
||||
roll_offset_degrees: number
|
||||
}
|
||||
type Map_Model_Config_Json = {
|
||||
aircraft_model: Map_Model_Item_Config_Json
|
||||
aircraft_models?: Record<string, Map_Model_Item_Config_Json>
|
||||
base_station_model: Map_Model_Item_Config_Json
|
||||
device_model: Map_Model_Item_Config_Json
|
||||
}
|
||||
function default_model_item(url: string, builtInSize: number, heading = 0, pitch = 0, roll = 0): Map_Model_Item_Config {
|
||||
return {url, builtInSize, headingOffsetDegrees: heading, pitchOffsetDegrees: pitch, rollOffsetDegrees: roll};
|
||||
}
|
||||
function default_aircraft_models(): Record<string, Map_Model_Item_Config> {
|
||||
return Object.fromEntries(aircraft_model_type_options.map(item => [item.key, default_model_item(`/ui/model/${item.modelFile}`, 50, -90)]));
|
||||
}
|
||||
export const empty_map_model_config: Map_Model_Config = {
|
||||
aircraftModel: default_model_item("/ui/model/aircraft.glb", 50, -90),
|
||||
aircraftModels: default_aircraft_models(),
|
||||
baseStationModel: default_model_item("/ui/model/base-station.glb", 16.635522),
|
||||
deviceModel: default_model_item("/ui/model/device.glb", 80)
|
||||
};
|
||||
export type Map_Model_Item_Config = Record<string, any>
|
||||
export type Map_Model_Config = Record<string, any>
|
||||
|
||||
export const empty_map_model_config: Map_Model_Config = {};
|
||||
export const map_model_config_event = "ecap_map_model_config_changed";
|
||||
function number_or(value: unknown, fallback: number): number {
|
||||
const next = Number(value);
|
||||
return Number.isFinite(next) ? next : fallback;
|
||||
}
|
||||
function from_server_model_item(data: Partial<Map_Model_Item_Config_Json> | undefined, fallback: Map_Model_Item_Config): Map_Model_Item_Config {
|
||||
return {
|
||||
url: typeof data?.url === "string" ? data.url : fallback.url,
|
||||
builtInSize: number_or(data?.built_in_size, fallback.builtInSize),
|
||||
headingOffsetDegrees: number_or(data?.heading_offset_degrees, fallback.headingOffsetDegrees),
|
||||
pitchOffsetDegrees: number_or(data?.pitch_offset_degrees, fallback.pitchOffsetDegrees),
|
||||
rollOffsetDegrees: number_or(data?.roll_offset_degrees, fallback.rollOffsetDegrees)
|
||||
};
|
||||
}
|
||||
function to_server_model_item(config: Map_Model_Item_Config): Map_Model_Item_Config_Json {
|
||||
return {
|
||||
url: config.url,
|
||||
built_in_size: config.builtInSize,
|
||||
heading_offset_degrees: config.headingOffsetDegrees,
|
||||
pitch_offset_degrees: config.pitchOffsetDegrees,
|
||||
roll_offset_degrees: config.rollOffsetDegrees
|
||||
};
|
||||
}
|
||||
function aircraft_models_from_server(values?: Record<string, Map_Model_Item_Config_Json>): Record<string, Map_Model_Item_Config> {
|
||||
const models = default_aircraft_models();
|
||||
if (values) {
|
||||
for (const [key, value] of Object.entries(values)) {
|
||||
models[key] = from_server_model_item(value, models[key] || empty_map_model_config.aircraftModel);
|
||||
}
|
||||
}
|
||||
return models;
|
||||
}
|
||||
function from_server_config(data: Partial<Map_Model_Config_Json>): Map_Model_Config {
|
||||
return {
|
||||
aircraftModel: from_server_model_item(data.aircraft_model, empty_map_model_config.aircraftModel),
|
||||
aircraftModels: aircraft_models_from_server(data.aircraft_models),
|
||||
baseStationModel: from_server_model_item(data.base_station_model, empty_map_model_config.baseStationModel),
|
||||
deviceModel: from_server_model_item(data.device_model, empty_map_model_config.deviceModel)
|
||||
};
|
||||
}
|
||||
function to_server_config(config: Map_Model_Config): Map_Model_Config_Json {
|
||||
return {
|
||||
aircraft_model: to_server_model_item(config.aircraftModel),
|
||||
aircraft_models: Object.fromEntries(Object.entries(config.aircraftModels).map(([key, value]) => [key, to_server_model_item(value)])),
|
||||
base_station_model: to_server_model_item(config.baseStationModel),
|
||||
device_model: to_server_model_item(config.deviceModel)
|
||||
};
|
||||
}
|
||||
|
||||
export async function load_map_model_config(api = "/map/models"): Promise<Map_Model_Config> {
|
||||
const response = await axios.get<Map_Model_Config_Json>(api);
|
||||
return from_server_config(response.data);
|
||||
const response = await axios.get<Map_Model_Config>(api);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function save_map_model_config(config: Map_Model_Config, api = "/map/models"): Promise<Map_Model_Config> {
|
||||
const response = await axios.post<Map_Model_Config_Json>(api, to_server_config(config));
|
||||
const saved_config = from_server_config(response.data);
|
||||
emit_map_model_config(saved_config);
|
||||
return saved_config;
|
||||
const response = await axios.post<Map_Model_Config>(api, config);
|
||||
emit_map_model_config(response.data);
|
||||
return response.data;
|
||||
}
|
||||
export async function upload_map_model(model_type: "aircraft" | "base_station" | "device" | "aircraft_type", file: File, aircraft_model_key?: string, api = "/map/models"): Promise<Map_Model_Config> {
|
||||
|
||||
export async function upload_map_model(model_type: string, file: File, aircraft_model_key?: string, api = "/map/models"): Promise<Map_Model_Config> {
|
||||
const form = new FormData();
|
||||
form.append("model_type", model_type);
|
||||
if (aircraft_model_key) form.append("aircraft_model_key", aircraft_model_key);
|
||||
form.append("file", file);
|
||||
const response = await axios.post<Map_Model_Config_Json>(api, form);
|
||||
const config = from_server_config(response.data);
|
||||
emit_map_model_config(config);
|
||||
return config;
|
||||
const response = await axios.post<Map_Model_Config>(api, form);
|
||||
emit_map_model_config(response.data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export function emit_map_model_config(config: Map_Model_Config) {
|
||||
window.dispatchEvent(new CustomEvent<Map_Model_Config>(map_model_config_event, {detail: config}));
|
||||
}
|
||||
|
||||
+11
-90
@@ -1,98 +1,19 @@
|
||||
import axios from "axios";
|
||||
|
||||
export type Scene_Mode_Key = "2d" | "2.5d" | "3d"
|
||||
export type Map_Camera_View = {
|
||||
longitude: number
|
||||
latitude: number
|
||||
height: number
|
||||
heading: number
|
||||
pitch: number
|
||||
roll: number
|
||||
}
|
||||
export type Map_Tile_View_Config = {
|
||||
current_imagery_key: string
|
||||
current_terrain_key: string
|
||||
tile_zoom_mode: "native" | "upscale" | "both"
|
||||
tile_display_maximum_level: number
|
||||
}
|
||||
export type Map_View_Config = {
|
||||
current_imagery_key: string
|
||||
scene_mode: Scene_Mode_Key
|
||||
map2d: Map_Tile_View_Config
|
||||
map3d: Map_Tile_View_Config
|
||||
camera: Map_Camera_View
|
||||
baseStationFlyToHeightOffsetMeters: number
|
||||
}
|
||||
export const default_map_camera_view: Map_Camera_View = {
|
||||
longitude: 121.27,
|
||||
latitude: 37.41,
|
||||
height: 300000,
|
||||
heading: 0,
|
||||
pitch: -90,
|
||||
roll: 0
|
||||
};
|
||||
export const default_map_view_config: Map_View_Config = {
|
||||
current_imagery_key: "imagery",
|
||||
scene_mode: "3d",
|
||||
map2d: {
|
||||
current_imagery_key: "imagery",
|
||||
current_terrain_key: "terrain",
|
||||
tile_zoom_mode: "native",
|
||||
tile_display_maximum_level: 19
|
||||
},
|
||||
map3d: {
|
||||
current_imagery_key: "imagery",
|
||||
current_terrain_key: "terrain",
|
||||
tile_zoom_mode: "native",
|
||||
tile_display_maximum_level: 19
|
||||
},
|
||||
camera: {...default_map_camera_view},
|
||||
baseStationFlyToHeightOffsetMeters: 8000
|
||||
};
|
||||
function number_value(value: unknown, fallback: number): number {
|
||||
const next = Number(value);
|
||||
return Number.isFinite(next) ? next : fallback;
|
||||
}
|
||||
function scene_mode_value(value: unknown, fallback: Scene_Mode_Key): Scene_Mode_Key {
|
||||
return value === "2d" || value === "2.5d" || value === "3d" ? value : fallback;
|
||||
}
|
||||
function tile_zoom_mode_value(value: unknown, fallback: Map_Tile_View_Config["tile_zoom_mode"]): Map_Tile_View_Config["tile_zoom_mode"] {
|
||||
return value === "native" || value === "upscale" || value === "both" ? value : fallback;
|
||||
}
|
||||
function normalize_tile_view_config(data: Partial<Map_Tile_View_Config> | undefined, fallback: Map_Tile_View_Config): Map_Tile_View_Config {
|
||||
return {
|
||||
current_imagery_key: typeof data?.current_imagery_key === "string" ? data.current_imagery_key : fallback.current_imagery_key,
|
||||
current_terrain_key: typeof data?.current_terrain_key === "string" ? data.current_terrain_key : fallback.current_terrain_key,
|
||||
tile_zoom_mode: tile_zoom_mode_value(data?.tile_zoom_mode, fallback.tile_zoom_mode),
|
||||
tile_display_maximum_level: Math.max(0, Math.min(24, number_value(data?.tile_display_maximum_level, fallback.tile_display_maximum_level)))
|
||||
};
|
||||
}
|
||||
function normalize_camera(data: Partial<Map_Camera_View> | undefined): Map_Camera_View {
|
||||
return {
|
||||
longitude: number_value(data?.longitude, default_map_camera_view.longitude),
|
||||
latitude: number_value(data?.latitude, default_map_camera_view.latitude),
|
||||
height: number_value(data?.height, default_map_camera_view.height),
|
||||
heading: number_value(data?.heading, default_map_camera_view.heading),
|
||||
pitch: number_value(data?.pitch, default_map_camera_view.pitch),
|
||||
roll: number_value(data?.roll, default_map_camera_view.roll)
|
||||
};
|
||||
}
|
||||
export function normalize_map_view_config(data: Partial<Map_View_Config>): Map_View_Config {
|
||||
return {
|
||||
current_imagery_key: typeof data.current_imagery_key === "string" ? data.current_imagery_key : default_map_view_config.current_imagery_key,
|
||||
scene_mode: scene_mode_value(data.scene_mode, default_map_view_config.scene_mode),
|
||||
map2d: normalize_tile_view_config(data.map2d, default_map_view_config.map2d),
|
||||
map3d: normalize_tile_view_config(data.map3d, default_map_view_config.map3d),
|
||||
camera: normalize_camera(data.camera),
|
||||
baseStationFlyToHeightOffsetMeters: Math.max(100, number_value(data.baseStationFlyToHeightOffsetMeters, default_map_view_config.baseStationFlyToHeightOffsetMeters))
|
||||
};
|
||||
}
|
||||
export type Map_Camera_View = Record<string, number>
|
||||
export type Map_Tile_View_Config = Record<string, any>
|
||||
export type Map_View_Config = Record<string, any>
|
||||
|
||||
export const default_map_camera_view: Map_Camera_View = {};
|
||||
export const default_map_view_config: Map_View_Config = {map2d: {}, map3d: {}, camera: {}};
|
||||
|
||||
export async function load_map_view_config(): Promise<Map_View_Config> {
|
||||
const response = await axios.get<Map_View_Config>("/map/view");
|
||||
return normalize_map_view_config(response.data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function save_map_view_config(config: Map_View_Config): Promise<Map_View_Config> {
|
||||
const normalized = normalize_map_view_config(config);
|
||||
const response = await axios.post<Map_View_Config>("/map/view", normalized);
|
||||
return normalize_map_view_config(response.data);
|
||||
const response = await axios.post<Map_View_Config>("/map/view", config);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user