diff --git a/README.md b/README.md index f92c808..afffde4 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@ # 构建步骤 -进入third_party执行init.bash 或者 init.ps1 -然后运行cmake目标 ecap_server_env 安装第三方依赖 +进入third_party执行init.bash 或者 init.ps1 然后运行cmake目标 ecap_server_env 安装第三方依赖 然后使用CMakeLists.txt执行构建即可 +模型下地址 +https://poly.pizza diff --git a/config/config.json b/config/config.json index a872a3c..5f1d888 100644 --- a/config/config.json +++ b/config/config.json @@ -41,11 +41,11 @@ "webapp": "@/../third_party/eacp_webapp/wwwroot" }, "map_resources": { - "current_imagery_key": "google_imagery", + "current_imagery_key": "imagery", "imagery": { "directories": [ - "D:/ae/tiles/Esri_World_Imagery_0-9", - "D:/ae/tiles/Esri_World_Imagery_10-12" + "D:/ae/tiles/Esri_World_Imagery_0-9", + "D:/ae/tiles/Esri_World_Imagery_10-12" ], "path_layout": "z/x/y", "y_axis": "xyz", @@ -57,7 +57,7 @@ }, "google_imagery": { "directories": [ - "D:/tiles" + "D:/tiles" ], "path_layout": "z/x/y", "y_axis": "xyz", @@ -69,7 +69,7 @@ }, "terrain": { "directories": [ - "D:/ae/tiles/AWS_Terrarium_0-15" + "D:/ae/tiles/AWS_Terrarium_0-15" ], "path_layout": "z/x/y", "y_axis": "xyz", @@ -83,47 +83,226 @@ }, "map_view": { "scene_mode": "3d", + "map2d": { + "current_imagery_key": "imagery", + "tile_zoom_mode": "native", + "tile_display_maximum_level": 8 + }, + "map3d": { + "current_imagery_key": "imagery", + "tile_zoom_mode": "native", + "tile_display_maximum_level": 19 + }, "camera": { - "longitude": 121.27, - "latitude": 37.41, - "height": 300000, - "heading": 0, - "pitch": -90, - "roll": 0 + "longitude": 121.599586, + "latitude": 37.221651, + "height": 298108.121570, + "heading": 360.000000, + "pitch": -90.000000, + "roll": 0.000000 + } + }, + "map_models": { + "aircraft_model": { + "url": "/ui/model/aircraft.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "aircraft_models": { + "glider": { + "url": "/ui/model/aircraft-glider.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "ground_obstruction_4": { + "url": "/ui/model/aircraft-ground-obstruction-4.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "ground_obstruction_5": { + "url": "/ui/model/aircraft-ground-obstruction-5.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "ground_obstruction_6": { + "url": "/ui/model/aircraft-ground-obstruction-6.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "ground_obstruction_7": { + "url": "/ui/model/aircraft-ground-obstruction-7.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "heavy_aircraft": { + "url": "/ui/model/aircraft-heavy-aircraft.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "high_performance_aircraft": { + "url": "/ui/model/aircraft-high-performance-aircraft.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "high_vortex_aircraft": { + "url": "/ui/model/aircraft-high-vortex-aircraft.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "light_aircraft": { + "url": "/ui/model/aircraft-light-aircraft.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "lighter_than_air": { + "url": "/ui/model/aircraft-lighter-than-air.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "medium_1_aircraft": { + "url": "/ui/model/aircraft-medium-1-aircraft.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "medium_2_aircraft": { + "url": "/ui/model/aircraft-medium-2-aircraft.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "no_category_information": { + "url": "/ui/model/aircraft-no-category-information.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "parachutist": { + "url": "/ui/model/aircraft-parachutist.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "reserved_3_5": { + "url": "/ui/model/aircraft-reserved-3-5.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "rotorcraft": { + "url": "/ui/model/aircraft-rotorcraft.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "space_transatmospheric_vehicle": { + "url": "/ui/model/aircraft-space-transatmospheric-vehicle.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "surface_emergency_vehicle": { + "url": "/ui/model/aircraft-surface-emergency-vehicle.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "surface_service_vehicle": { + "url": "/ui/model/aircraft-surface-service-vehicle.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "ultralight_hangglider_paraglider": { + "url": "/ui/model/aircraft-ultralight-hangglider-paraglider.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + }, + "unmanned_aerial_vehicle": { + "url": "/ui/model/aircraft-unmanned-aerial-vehicle.glb", + "built_in_size": 50.000000, + "heading_offset_degrees": -90.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 + } + }, + "base_station_model": { + "url": "/ui/model/base-station.glb", + "built_in_size": 100.000000, + "heading_offset_degrees": 0.000000, + "pitch_offset_degrees": 0.000000, + "roll_offset_degrees": 0.000000 } }, "device": { "net": [ - { - "key": "device", - "ip": "192.168.1.75", - "port": 80, - "netmask": "255.255.255.0", - "gateway": "192.168.1.1" - }, - { - "key": "wifi", - "ip": "192.168.10.193", - "port": 80, - "netmask": "255.255.255.0", - "gateway": "192.168.10.1" - } + { + "key": "device", + "ip": "192.168.1.75", + "port": 80, + "netmask": "255.255.255.0", + "gateway": "192.168.1.1" + }, + { + "key": "wifi", + "ip": "192.168.10.193", + "port": 80, + "netmask": "255.255.255.0", + "gateway": "192.168.10.1" + } ] }, "mode_acs": { + "timeout_seconds": 160, + "max_speed_m_s": 1000.000000, + "air_pos_timeout": 8.000000, + "surface_pos_timeout": 32.000000, + "read_milliseconds": 100, "wait_process_msg": true, "time_space_filter": true, "speed_filter": true, - "timeout_seconds": 160, - "max_speed_m_s": 1000, - "air_pos_timeout": 8, - "surface_pos_timeout": 32, - "read_milliseconds": 100, "max_track_point_size": 20000, "mode_s_max_num": 700000, "mode_other_max_num": 10000, "report_data_feed_msg_mum": 10000, "default_min_aircraft_list_num": 10, + "aircraft_change_list_min_position_points": 2, + "aircraft_change_list_adsb_range_filter": true, + "aircraft_change_list_adsb_range_factor": 1.200000, + "adsb_theoretical_target_altitude_meters": 10000.000000, "ignore_df_11": true, "monitor_msg_live": false, "data_feed": { @@ -132,135 +311,185 @@ "tcp_server_default_connect_user_buffer_size": 409600, "tcp_server_default_connect_system_buffer_size": 409600, "list": [ - { - "type": "Data_Feed_TCP_Server", - "key": "Port_10003", - "enable": true, - "output_format": { - "type": "BIN", - "use_status": true, - "mode_s_output_type": "ALL_Mode_S", - "use_mode_ac": true, - "sbs_only_pos": true - }, - "port": 10003, - "connect_user_buffer_size": 409600, - "connect_system_buffer_size": 409600 - }, - { - "type": "Data_Feed_UDP_Server", - "key": "22333", - "enable": false, - "output_format": { - "type": "BIN", - "use_status": false, - "mode_s_output_type": "ALL_Mode_S", - "use_mode_ac": false, - "sbs_only_pos": false - }, - "port": 50001 - }, - { - "type": "Data_Feed_UDP_Client", - "key": "Port_10004", - "enable": false, - "output_format": { - "type": "AVR", - "use_status": false, - "mode_s_output_type": "DF_11_17_18", - "use_mode_ac": false, - "sbs_only_pos": true - }, - "port": 30004, - "url": "192.168.1.75" - }, - { - "type": "Data_Feed_TCP_Client", - "key": "Port_10005", - "enable": false, - "output_format": { - "type": "BIN", - "use_status": true, - "mode_s_output_type": "NO_POS_Mode_S", - "use_mode_ac": true, - "sbs_only_pos": true - }, - "url": "192.168.1.167", - "port": 10005 - }, - { - "type": "Data_Feed_TCP_Server", - "key": "Port_30003", - "enable": false, - "output_format": { - "type": "SBS", - "use_status": false, - "mode_s_output_type": "ALL_Mode_S", - "use_mode_ac": true, - "sbs_only_pos": true - }, - "port": 30003, - "connect_user_buffer_size": 0, - "connect_system_buffer_size": 0 - } + { + "type": "Data_Feed_TCP_Server", + "key": "Port_10003", + "enable": true, + "output_format": { + "type": "BIN", + "use_status": true, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": true, + "sbs_only_pos": true + }, + "port": 10003, + "connect_user_buffer_size": 409600, + "connect_system_buffer_size": 409600 + }, + { + "type": "Data_Feed_UDP_Server", + "key": "22333", + "enable": false, + "output_format": { + "type": "BIN", + "use_status": false, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": false, + "sbs_only_pos": false + }, + "port": 50001 + }, + { + "type": "Data_Feed_UDP_Client", + "key": "Port_10004", + "enable": false, + "output_format": { + "type": "AVR", + "use_status": false, + "mode_s_output_type": "DF_11_17_18", + "use_mode_ac": false, + "sbs_only_pos": true + }, + "port": 30004, + "url": "192.168.1.75" + }, + { + "type": "Data_Feed_TCP_Client", + "key": "Port_10005", + "enable": false, + "output_format": { + "type": "BIN", + "use_status": true, + "mode_s_output_type": "NO_POS_Mode_S", + "use_mode_ac": true, + "sbs_only_pos": true + }, + "url": "192.168.1.167", + "port": 10005 + }, + { + "type": "Data_Feed_TCP_Server", + "key": "Port_30003", + "enable": false, + "output_format": { + "type": "SBS", + "use_status": false, + "mode_s_output_type": "ALL_Mode_S", + "use_mode_ac": true, + "sbs_only_pos": true + }, + "port": 30003, + "connect_user_buffer_size": 0, + "connect_system_buffer_size": 0 + } ] }, "data_source": { "list": [ - { - "type": "Dll_Data_Source", - "key": "lzy_dll", - "enable": true, - "base_station_show": true, - "base_station_has_valid_position": false, - "aircraft_show": true, - "color": "#454641", - "aircraft_pixel_size": 100, - "lat": 37.433547, - "lon": 121.408730, - "alt": 2300.000000, - "update_form_gps": false, - "show_icao": true, - "show_call_sign": false, - "show_fly_status": false, - "keep_mode": true, - "library_path": "@/dll_source.dll", - "function_name": "adsb_read", - "data_type": "BIN_Blank_Text", - "buffer_size": 200000, - "ignore_msg_time": true - }, - { - "type": "File_Data_Source", - "key": "aaaa", - "enable": false, - "base_station_show": true, - "base_station_has_valid_position": false, - "aircraft_show": true, - "color": "#d1a54c", - "aircraft_pixel_size": 100, - "lat": 37.000000, - "lon": 121.000000, - "alt": 1000.000000, - "update_form_gps": false, - "show_icao": true, - "show_call_sign": true, - "show_fly_status": true, - "keep_mode": true, - "file_path": "D:\\ae\\proj\\projects\\ECAP_Server\\data\\BIN_Blank_Text\\ADS-B_195_0205.txt", - "data_type": "BIN_Blank_Text", - "play_mode": "loop", - "ignore_msg_time": true - } + { + "type": "Dll_Data_Source", + "key": "lzy_dll", + "enable": true, + "base_station_has_valid_position": true, + "map_display": { + "map2d": { + "base_station_show": true, + "aircraft_show": true, + "constant_screen_size": true, + "color": "#454641", + "track_point_color": "#ffff00", + "base_station_color": "#1677ff", + "aircraft_pixel_size": 34, + "base_station_pixel_size": 30, + "aircraft_scale": 2.000000, + "base_station_scale": 0.440000, + "show_icao": true, + "show_call_sign": false, + "show_fly_status": false + }, + "map3d": { + "base_station_show": true, + "aircraft_show": true, + "constant_screen_size": true, + "color": "#454641", + "track_point_color": "#ffff00", + "base_station_color": "#1677ff", + "aircraft_pixel_size": 50, + "base_station_pixel_size": 100, + "aircraft_scale": 2.000000, + "base_station_scale": 0.440000, + "show_icao": true, + "show_call_sign": false, + "show_fly_status": false + } + }, + "lat": 37.433547, + "lon": 121.408730, + "alt": 2300.000000, + "ignore_msg_time": true, + "update_form_gps": false, + "keep_mode": true, + "library_path": "@/dll_source.dll", + "function_name": "adsb_read", + "data_type": "BIN_Blank_Text", + "buffer_size": 200000 + }, + { + "type": "File_Data_Source", + "key": "aaaa", + "enable": false, + "base_station_has_valid_position": false, + "map_display": { + "map2d": { + "base_station_show": true, + "aircraft_show": true, + "constant_screen_size": true, + "color": "#d1a54c", + "track_point_color": "#ffff00", + "base_station_color": "#1677ff", + "aircraft_pixel_size": 50, + "base_station_pixel_size": 100, + "aircraft_scale": 2.000000, + "base_station_scale": 0.440000, + "show_icao": true, + "show_call_sign": true, + "show_fly_status": true + }, + "map3d": { + "base_station_show": true, + "aircraft_show": true, + "constant_screen_size": true, + "color": "#d1a54c", + "track_point_color": "#ffff00", + "base_station_color": "#1677ff", + "aircraft_pixel_size": 50, + "base_station_pixel_size": 100, + "aircraft_scale": 2.000000, + "base_station_scale": 0.440000, + "show_icao": true, + "show_call_sign": true, + "show_fly_status": true + } + }, + "lat": 37.000000, + "lon": 121.000000, + "alt": 1000.000000, + "ignore_msg_time": true, + "update_form_gps": false, + "keep_mode": true, + "file_path": "D:\\ae\\proj\\projects\\ECAP_Server\\data\\BIN_Blank_Text\\ADS-B_195_0205.txt", + "data_type": "BIN_Blank_Text", + "play_mode": "loop" + } ] }, "source_feed_relation": { "list": [ - { - "key": "key_10003", - "enable": true, - "type": "First_Source_To_All_Feed_Relation" - } + { + "key": "key_10003", + "enable": true, + "type": "First_Source_To_All_Feed_Relation" + } ] } }, @@ -278,97 +507,97 @@ "external_database": { "ecap_sqlite_path": "@/../data/sqlite/ecap.sqlite", "list": [ - { - "name": "tar1090_db_aircraft", - "download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz", - "description": "wiedehopf/tar1090-db 发布的轻量 ICAO24 飞机静态库,提供注册号、机型、年份和运营方备注。", - "update_interval_seconds": 604800, - "last_updated_at": 1780388281 - }, - { - "name": "wiedehopf_tar1090_db_aircraft", - "download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz", - "description": "wiedehopf/tar1090-db 项目发布的飞机静态数据库镜像,供 tar1090 本地识别飞机。", - "update_interval_seconds": 604800, - "last_updated_at": 1780381804 - }, - { - "name": "mictronics_aircraft_database", - "download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz", - "description": "Mictronics 社区飞机数据库的 tar1090 汇总镜像,可按 ICAO24 查询注册号、机型和运营方。", - "update_interval_seconds": 604800, - "last_updated_at": 1780381804 - }, - { - "name": "opensky_aircraft_database", - "download_url": "https://opensky-network.org/datasets/metadata/aircraftDatabase.csv", - "description": "OpenSky Network 飞机元数据 CSV,包含制造商、型号、注册号和运营方等字段。", - "update_interval_seconds": 604800, - "last_updated_at": 1780381804 - }, - { - "name": "faa_aircraft_registry", - "download_url": "https://registry.faa.gov/database/ReleasableAircraft.zip", - "description": "FAA 官方可公开下载的美国飞机注册库,MASTER.txt 包含 Mode-S Hex 注册信息。", - "update_interval_seconds": 86400, - "last_updated_at": 1780381804 - }, - { - "name": "icao_doc_8643_aircraft_type_designators", - "download_url": "", - "description": "ICAO Doc 8643 机型代码表,用于将 A320、B738 等 designator 映射为制造商、型号和类别。", - "update_interval_seconds": 2419200, - "last_updated_at": 1780381804 - }, - { - "name": "vrs_routes", - "download_url": "https://vrs-standing-data.adsb.lol/routes.csv", - "description": "VRS 航线表,以 callsign 查询机场代码序列,用于推断起飞机场、经停机场和目的机场。", - "update_interval_seconds": 3600, - "last_updated_at": 1780381804 - }, - { - "name": "vrs_airports", - "download_url": "https://vrs-standing-data.adsb.lol/airports.csv", - "description": "VRS 机场字典,提供机场代码、名称、国家、经纬度和高度。", - "update_interval_seconds": 3600, - "last_updated_at": 1780381804 - }, - { - "name": "adsblol_vrs_standing_data_routes", - "download_url": "https://vrs-standing-data.adsb.lol/routes.csv", - "description": "ADSB.lol 发布的 VRS standing-data 航线镜像,以 callsign 查询机场代码序列。", - "update_interval_seconds": 3600, - "last_updated_at": 1780381804 - }, - { - "name": "vradarserver_standing_data_routes", - "download_url": "https://vrs-standing-data.adsb.lol/routes.csv", - "description": "Virtual Radar Server standing-data 的航线适配表;当前在线更新使用 ADSB.lol 公共镜像。", - "update_interval_seconds": 3600, - "last_updated_at": 1780381804 - }, - { - "name": "opensky_flightdata_api_cache", - "download_url": "", - "description": "OpenSky FlightData API 查询缓存,按 ICAO24 和时间范围保存历史航班起降机场结果。", - "update_interval_seconds": 0, - "last_updated_at": 1780381804 - } + { + "name": "tar1090_db_aircraft", + "download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz", + "description": "wiedehopf/tar1090-db 发布的轻量 ICAO24 飞机静态库,提供注册号、机型、年份和运营方备注。", + "update_interval_seconds": 604800, + "last_updated_at": 1780388281 + }, + { + "name": "wiedehopf_tar1090_db_aircraft", + "download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz", + "description": "wiedehopf/tar1090-db 项目发布的飞机静态数据库镜像,供 tar1090 本地识别飞机。", + "update_interval_seconds": 604800, + "last_updated_at": 1780381804 + }, + { + "name": "mictronics_aircraft_database", + "download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz", + "description": "Mictronics 社区飞机数据库的 tar1090 汇总镜像,可按 ICAO24 查询注册号、机型和运营方。", + "update_interval_seconds": 604800, + "last_updated_at": 1780381804 + }, + { + "name": "opensky_aircraft_database", + "download_url": "https://opensky-network.org/datasets/metadata/aircraftDatabase.csv", + "description": "OpenSky Network 飞机元数据 CSV,包含制造商、型号、注册号和运营方等字段。", + "update_interval_seconds": 604800, + "last_updated_at": 1780381804 + }, + { + "name": "faa_aircraft_registry", + "download_url": "https://registry.faa.gov/database/ReleasableAircraft.zip", + "description": "FAA 官方可公开下载的美国飞机注册库,MASTER.txt 包含 Mode-S Hex 注册信息。", + "update_interval_seconds": 86400, + "last_updated_at": 1780381804 + }, + { + "name": "icao_doc_8643_aircraft_type_designators", + "download_url": "", + "description": "ICAO Doc 8643 机型代码表,用于将 A320、B738 等 designator 映射为制造商、型号和类别。", + "update_interval_seconds": 2419200, + "last_updated_at": 1780381804 + }, + { + "name": "vrs_routes", + "download_url": "https://vrs-standing-data.adsb.lol/routes.csv", + "description": "VRS 航线表,以 callsign 查询机场代码序列,用于推断起飞机场、经停机场和目的机场。", + "update_interval_seconds": 3600, + "last_updated_at": 1780381804 + }, + { + "name": "vrs_airports", + "download_url": "https://vrs-standing-data.adsb.lol/airports.csv", + "description": "VRS 机场字典,提供机场代码、名称、国家、经纬度和高度。", + "update_interval_seconds": 3600, + "last_updated_at": 1780381804 + }, + { + "name": "adsblol_vrs_standing_data_routes", + "download_url": "https://vrs-standing-data.adsb.lol/routes.csv", + "description": "ADSB.lol 发布的 VRS standing-data 航线镜像,以 callsign 查询机场代码序列。", + "update_interval_seconds": 3600, + "last_updated_at": 1780381804 + }, + { + "name": "vradarserver_standing_data_routes", + "download_url": "https://vrs-standing-data.adsb.lol/routes.csv", + "description": "Virtual Radar Server standing-data 的航线适配表;当前在线更新使用 ADSB.lol 公共镜像。", + "update_interval_seconds": 3600, + "last_updated_at": 1780381804 + }, + { + "name": "opensky_flightdata_api_cache", + "download_url": "", + "description": "OpenSky FlightData API 查询缓存,按 ICAO24 和时间范围保存历史航班起降机场结果。", + "update_interval_seconds": 0, + "last_updated_at": 1780381804 + } ] }, "cesium_graphics": { "debug_show_frames_per_second": true, "target_frame_rate": 30, "use_browser_recommended_resolution": true, - "resolution_scale": 0.7, + "resolution_scale": 1.000000, "msaa_samples": 1, "fxaa": false, "shadows": false, "enable_lighting": false, "maximum_screen_space_error": 4, - "terrain_enabled_in_3d": false, - "terrain_exaggeration": 1.0, + "terrain_enabled_in_3d": true, + "terrain_exaggeration": 5.000000, "terrain_limit_maximum_level": true, "terrain_maximum_level": 15, "terrain_cache_tiles": 128 diff --git a/module/Local_Server/Aircraft/Flight_VTO.cpp b/module/Local_Server/Aircraft/Flight_VTO.cpp index 2a581b8..e47273c 100644 --- a/module/Local_Server/Aircraft/Flight_VTO.cpp +++ b/module/Local_Server/Aircraft/Flight_VTO.cpp @@ -1,5 +1,60 @@ #include "../Aircraft/Flight_VTO.h" +#include +#include +namespace { +double to_radians(double degrees) { + return degrees * 3.14159265358979323846 / 180.0; +} +double track_heading_from_points(const SSR::Position_Info& first, + const SSR::Position_Info& second) { + auto lat1 = to_radians(first.lat); + auto lat2 = to_radians(second.lat); + auto dlon = to_radians(second.lon - first.lon); + auto y = std::sin(dlon) * std::cos(lat2); + auto x = std::cos(lat1) * std::sin(lat2) - + std::sin(lat1) * std::cos(lat2) * std::cos(dlon); + auto angle = std::atan2(y, x) * 180.0 / 3.14159265358979323846; + return angle < 0 ? angle + 360.0 : angle; +} +double ground_distance_meters(const SSR::Position_Info& first, + const SSR::Position_Info& second) { + auto dlat = to_radians(second.lat - first.lat); + auto dlon = to_radians(second.lon - first.lon); + auto lat1 = to_radians(first.lat); + auto lat2 = to_radians(second.lat); + auto a = std::sin(dlat / 2) * std::sin(dlat / 2) + + std::cos(lat1) * std::cos(lat2) * std::sin(dlon / 2) * + std::sin(dlon / 2); + auto safe_a = std::min(1.0, std::max(0.0, a)); + return 6371000.0 * 2 * + std::atan2(std::sqrt(safe_a), std::sqrt(1 - safe_a)); +} +double track_pitch_from_points(const SSR::Position_Info& first, + const SSR::Position_Info& second) { + auto distance = ground_distance_meters(first, second); + return std::atan2(second.alt - first.alt, distance) * 180.0 / + 3.14159265358979323846; +} +Flight_Track_Orientation track_orientation_from_points(const SSR::Position_Info& first, + const SSR::Position_Info& second) { + return {track_heading_from_points(first, second), + track_pitch_from_points(first, second), 0.0}; +} +} +Psc::JSON Flight_Track_Orientation::to_json() const { + auto ret = Psc::JSON::object(); + ret.append({"heading", heading}); + ret.append({"pitch", pitch}); + ret.append({"roll", roll}); + return ret; +} Psc::JSON Flight_VTO::to_json() { + return to_json(true); +} +Psc::JSON Flight_VTO::to_base_info_json() { + return to_json(false); +} +Psc::JSON Flight_VTO::to_json(bool include_runtime_fields) { auto ret = Psc::JSON::object(); ADD_Json(icao); ADD_Json(mlat); @@ -13,11 +68,32 @@ Psc::JSON Flight_VTO::to_json() { ret.children.emplace_back("longitude", nullptr); } ADD_Json_RET(track); + if (include_runtime_fields) { + if (track_orientation.has_value()) { + ret.append({"track_orientation", track_orientation.value().to_json()}); + } + else { + ret.append({"track_orientation", nullptr}); + } + } ADD_Json_RET(speed); ADD_Json_RET(altitude); ADD_Json_RET(vert_speed); ADD_Json_RET(call_sign); ADD_Json_RET(squawk); + if (include_runtime_fields) { + if (vortex_type.has_value()) { + auto value = vortex_type.value(); + ret.append({"vortex_type", static_cast(value)}); + ret.append({"vortex_type_key", std::string(SSR::Vortex_Type_to_key(value))}); + ret.append({"vortex_type_label", std::string(SSR::Vortex_Type_to_label(value))}); + } + else { + ret.append({"vortex_type", nullptr}); + ret.append({"vortex_type_key", nullptr}); + ret.append({"vortex_type_label", nullptr}); + } + } return ret; } std::string Flight_VTO::time_to_string(time_t time_stamp) { @@ -34,6 +110,12 @@ Flight_VTO Flight_VTO::to_VTO(SSR::Aircraft_Info* info) { vto.speed = info->speed(); // 这个地 是相对于地的意思 vto.track = info->surface_magnetic_heading(); + auto last_two = info->air_pos_track_list.last_two(); + if (last_two.has_value()) { + auto orientation = track_orientation_from_points(last_two->first, last_two->second); + vto.track = orientation.heading; + vto.track_orientation = orientation; + } // vto.pos = info->pos(); auto opt = info->air_pos_track_list.last(); vto.pos = opt; @@ -50,5 +132,6 @@ Flight_VTO Flight_VTO::to_VTO(SSR::Aircraft_Info* info) { vto.vert_speed = info->vertical_rate(); vto.call_sign = info->bds20_call_sign(); vto.squawk = info->squawk(); + vto.vortex_type = info->vortex_type(); return vto; } diff --git a/module/Local_Server/Aircraft/Flight_VTO.h b/module/Local_Server/Aircraft/Flight_VTO.h index a69a655..4bda727 100644 --- a/module/Local_Server/Aircraft/Flight_VTO.h +++ b/module/Local_Server/Aircraft/Flight_VTO.h @@ -1,17 +1,27 @@ #pragma once #include "global.h" +struct Flight_Track_Orientation { + double heading = 0.0; // 由最后两个空中位置点计算出的水平航迹角,单位:度 + double pitch = 0.0; // 由最后两个空中位置点计算出的三维俯仰角,单位:度 + double roll = 0.0; // 当前 ADS-B 位置轨迹没有横滚数据,保留给 Cesium 姿态结构 + Psc::JSON to_json() const; +}; struct Flight_VTO { std::string icao; // 飞机身份标识(四字节十六进制代码) time_t time_stamp{}; // 时间(原始时间数据,可能需要翻译显示) bool mlat; RET pos; // 纬度(飞机的当前位置纬度) RET track; // 航迹(航迹方向,单位:度) + RET track_orientation; // Cesium 使用的航迹姿态,单位:度 RET speed; // 航速(单位:米/秒) RET vert_speed; // 航速(单位:米/秒) RET altitude; // 米 RET call_sign; RET squawk; + RET vortex_type; Psc::JSON to_json(); + Psc::JSON to_base_info_json(); + Psc::JSON to_json(bool include_runtime_fields); static std::string time_to_string(time_t time_stamp); static Flight_VTO to_VTO(SSR::Aircraft_Info* info); }; diff --git a/module/Local_Server/Data_Source/BaseStation.cpp b/module/Local_Server/Data_Source/BaseStation.cpp index 0406bf0..38ffed3 100644 --- a/module/Local_Server/Data_Source/BaseStation.cpp +++ b/module/Local_Server/Data_Source/BaseStation.cpp @@ -1,15 +1,21 @@ #include "BaseStation.h" - namespace SSR { struct HULC_Status_Message; } - -std::optional BaseStation::get_pos() { +double BaseStation::theoretical_detection_range_meters(double base_height_meters, + double target_height_meters) { + return static_cast(SSR::CPR::radio_line_of_sight_range_meters(base_height_meters, target_height_meters)); +} +std::optional BaseStation::get_pos() { std::lock_guard g(mtx); if (!hulc.GPS_has_valid_fix()) { return std::nullopt; } - return SSR::CPR::Position{hulc.get_latitude(), hulc.get_longitude()}; + return SSR::Position_3D{hulc.get_latitude(), hulc.get_longitude(), static_cast(hulc.Alt)}; +} +double BaseStation::get_height() { + std::lock_guard g(mtx); + return hulc.Alt; } bool BaseStation::has_valid_position() { std::lock_guard g(mtx); @@ -33,9 +39,6 @@ Psc::JSON BaseStation::to_Json() { ret.append({"状态标志", hulc.Flags}); ret.append({"内部使用", hulc.I_U_}); ret.append({"时间", Psc::utc_2_local_time(hulc.xTime)}); - ret.append({"latitude", hulc.get_latitude()}); - ret.append({"longitude", hulc.get_longitude()}); - ret.append({"height", hulc.Alt}); ret.append({"卫星数量", hulc.Sat}); ret.append({"HDOP", hulc.get_HDOP()}); ret.append({"GPS设备检测到", hulc.GPS_device_detected()}); diff --git a/module/Local_Server/Data_Source/BaseStation.h b/module/Local_Server/Data_Source/BaseStation.h index d60c599..4e3900d 100644 --- a/module/Local_Server/Data_Source/BaseStation.h +++ b/module/Local_Server/Data_Source/BaseStation.h @@ -1,21 +1,17 @@ #pragma once #include "global.h" - - - class BaseStation { public: - std::string to_string(); - Psc::JSON to_Json(); - std::optional get_pos(); - bool has_valid_position(); - void set_msg(const SSR::HULC_Status_Message& msg); - std::function handle_when_updated = nullptr; + std::string to_string(); + Psc::JSON to_Json(); + std::optional get_pos(); + double get_height(); + bool has_valid_position(); + void set_msg(const SSR::HULC_Status_Message& msg); + static double theoretical_detection_range_meters(double base_height_meters, double target_height_meters); + std::function handle_when_updated = nullptr; protected: - std::mutex mtx; - SSR::HULC_Status_Message hulc{}; + std::mutex mtx; + SSR::HULC_Status_Message hulc{}; }; class Global; - - - diff --git a/module/Local_Server/Data_Source/Data_Source.cpp b/module/Local_Server/Data_Source/Data_Source.cpp index a3bda50..7b59889 100644 --- a/module/Local_Server/Data_Source/Data_Source.cpp +++ b/module/Local_Server/Data_Source/Data_Source.cpp @@ -513,6 +513,7 @@ void Data_Source_Config::server(Global* g) { ds->from_json(¶ms); g->save(); g->mode_acs.source_feed_relation_config.set_need_refresh(); + res->setBody(warp(ds->to_json()).to_json_string()); }); svr.Post(api + svr.insert + name, [g, this](HTTP_Param) { CHECK_JSON_PARAM diff --git a/module/Local_Server/Data_Source/Data_Source.h b/module/Local_Server/Data_Source/Data_Source.h index cb7db6b..d955c73 100644 --- a/module/Local_Server/Data_Source/Data_Source.h +++ b/module/Local_Server/Data_Source/Data_Source.h @@ -50,23 +50,73 @@ protected: std::mutex mtx; std::vector test_v; }; +class Data_Source_Map_Display_Data { +public: + Psc::Copyable_Atomic base_station_show = true; + Psc::Copyable_Atomic aircraft_show = true; + Psc::Copyable_Atomic constant_screen_size = true; + std::string color = "#1677ff"; + std::string track_point_color = "#ffff00"; + std::string base_station_color = "#1677ff"; + std::uint32_t aircraft_pixel_size = 50; + std::uint32_t base_station_pixel_size = 100; + double aircraft_scale = 1.0; + double base_station_scale = 1.0; + Psc::Copyable_Atomic show_icao = true; + Psc::Copyable_Atomic show_call_sign = false; + Psc::Copyable_Atomic show_fly_status = false; + PSC_USE_JSON +}; +class Data_Source_Map_Display_Config { +public: + Data_Source_Map_Display_Data map2d; + Data_Source_Map_Display_Data map3d; + [[nodiscard]] Psc::JSON to_base_json() const { + auto ret = Psc::JSON::object(); + ret.append({"map2d", map2d.to_base_json()}); + ret.append({"map3d", map3d.to_base_json()}); + return ret; + } + void from_base_json(const Psc::JSON* that_json) { + if (that_json == nullptr || that_json->valueType != Psc::Object) { + throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map_display"); + } + map2d.from_base_json(that_json->get("map2d")); + map3d.from_base_json(that_json->get("map3d")); + } +}; class Data_Source_Data { public: - Psc::Copyable_Atomic base_station_show{}; Psc::Copyable_Atomic base_station_has_valid_position{}; - Psc::Copyable_Atomic aircraft_show{}; - std::string color = "#1677ff"; - int aircraft_pixel_size{}; + Data_Source_Map_Display_Config map_display; double lat{}; double lon{}; double alt{}; Psc::Copyable_Atomic ignore_msg_time = false; // 忽略消息时间戳 如果启用 解码位置将不在判断消息自带时间戳 Psc::Copyable_Atomic update_form_gps{}; - Psc::Copyable_Atomic show_icao = true; - Psc::Copyable_Atomic show_call_sign = false; - Psc::Copyable_Atomic show_fly_status = false; Psc::Copyable_Atomic keep_mode = true; - PSC_USE_JSON + [[nodiscard]] Psc::JSON to_base_json() const { + auto ret = Psc::JSON::object(); + Ret_J(base_station_has_valid_position) + ret.append({"map_display", map_display.to_base_json()}); + Ret_J(lat) + Ret_J(lon) + Ret_J(alt) + Ret_J(ignore_msg_time) + Ret_J(update_form_gps) + Ret_J(keep_mode) + return ret; + } + void from_base_json(const Psc::JSON* that_json) { + Get_J(base_station_has_valid_position) + map_display.from_base_json(that_json->get("map_display")); + Get_J(lat) + Get_J(lon) + Get_J(alt) + Get_J(ignore_msg_time) + Get_J(update_form_gps) + Get_J(keep_mode) + } }; class Data_Source : public std::enable_shared_from_this, public Data_Source_Handler, public Data_Source_Data { diff --git a/module/Local_Server/Data_Source/Data_Source_Handler.cpp b/module/Local_Server/Data_Source/Data_Source_Handler.cpp index a38c552..18f2be5 100644 --- a/module/Local_Server/Data_Source/Data_Source_Handler.cpp +++ b/module/Local_Server/Data_Source/Data_Source_Handler.cpp @@ -1,7 +1,7 @@ #include "Data_Source_Handler.h" +#include #include "Data_Source.h" #include "Local_Server/server/Global.h" -#include #include "Local_Server/server/io_coro.h" using namespace Psc; std::shared_ptr ds(Data_Source_Handler* dsh) { @@ -13,18 +13,16 @@ std::shared_ptr Data_Source_Handler::create_msg(std::string_view packe std::cout << "first:" << mem2hex(std::string(packet)) << std::endl; return nullptr; } - std::string error_len = source->key + " " + LOG_POS_SIMPLE + - std::string(" ") + "mode_s_error_length"; - if (packet.size() < 2) return nullptr; + std::string error_len = source->key + " " + LOG_POS_SIMPLE + std::string(" ") + "mode_s_error_length"; + if (packet.size() < 2) + return nullptr; auto mt = packet[1]; if (mt == SSR::Msg::AC) { if (packet.size() != SSR::Msg::AC_len) { auto packet_hex = mem2hex(std::string(packet)); - SSR::mode_s_logger->c_debug( - error_len, {}, - std::to_string(mt) + " size:" + std::to_string(packet.size()) + - " hex:" + packet_hex + - " should:" + std::to_string(SSR::Msg::AC_len)); + SSR::mode_s_logger->c_debug(error_len, {}, + std::to_string(mt) + " size:" + std::to_string(packet.size()) + + " hex:" + packet_hex + " should:" + std::to_string(SSR::Msg::AC_len)); mode_ac_statistic.add_length_error(); return nullptr; } @@ -34,13 +32,10 @@ std::shared_ptr Data_Source_Handler::create_msg(std::string_view packe if (mt == SSR::Msg::S7) { if (packet.size() != SSR::Mode_S_Msg::S7_len) { auto packet_hex = mem2hex(std::string(packet)); - SSR::mode_s_logger->c_debug( - error_len, {}, - std::to_string(mt) + " size:" + std::to_string(packet.size()) + - " hex:" + packet_hex + - " should:" + std::to_string(SSR::Msg::S7_len)); - mode_s_statistic.add_length_error( - "[length error] Msg::S7 handle_mode_s_source"); + SSR::mode_s_logger->c_debug(error_len, {}, + std::to_string(mt) + " size:" + std::to_string(packet.size()) + + " hex:" + packet_hex + " should:" + std::to_string(SSR::Msg::S7_len)); + mode_s_statistic.add_length_error("[length error] Msg::S7 handle_mode_s_source"); return nullptr; } return std::make_shared(source, packet); @@ -48,13 +43,10 @@ std::shared_ptr Data_Source_Handler::create_msg(std::string_view packe if (mt == SSR::Msg::S14) { if (packet.size() != SSR::Msg::S14_len) { auto packet_hex = mem2hex(std::string(packet)); - SSR::mode_s_logger->c_debug( - error_len, {}, - std::to_string(mt) + " size:" + std::to_string(packet.size()) + - " hex:" + packet_hex + - " should:" + std::to_string(SSR::Msg::S14_len)); - mode_s_statistic.add_length_error( - "[length error] Msg::S14 handle_mode_s_source"); + SSR::mode_s_logger->c_debug(error_len, {}, + std::to_string(mt) + " size:" + std::to_string(packet.size()) + + " hex:" + packet_hex + " should:" + std::to_string(SSR::Msg::S14_len)); + mode_s_statistic.add_length_error("[length error] Msg::S14 handle_mode_s_source"); return nullptr; } return std::make_shared(source, packet); @@ -63,13 +55,10 @@ std::shared_ptr Data_Source_Handler::create_msg(std::string_view packe return nullptr; // 不知道如何解析跳过 if (packet.size() != SSR::Msg::Radarcape_status_len) { auto packet_hex = mem2hex(std::string(packet)); - SSR::mode_s_logger->c_debug( - error_len, {}, - std::to_string(mt) + " size:" + std::to_string(packet.size()) + - " hex:" + packet_hex + - " should:" + std::to_string(SSR::Msg::Radarcape_status_len)); - mode_s_statistic.add_length_error( - "[length error] Msg::Radarcape_status_len handle_mode_s_source"); + SSR::mode_s_logger->c_debug(error_len, {}, + std::to_string(mt) + " size:" + std::to_string(packet.size()) + " hex:" + + packet_hex + " should:" + std::to_string(SSR::Msg::Radarcape_status_len)); + mode_s_statistic.add_length_error("[length error] Msg::Radarcape_status_len handle_mode_s_source"); return nullptr; } return std::make_shared(source, packet); @@ -79,10 +68,9 @@ std::shared_ptr Data_Source_Handler::create_msg(std::string_view packe auto packet_hex = mem2hex(std::string(packet)); // 找不到协议 size:23 hex:1A34195F0000001500B502FF06F423CE50000090000000 // should:5 - SSR::mode_s_logger->c_debug( - error_len, {}, - Psc::to_string(source->type) + " HULC size:" + - std::to_string(packet.size()) + " hex:" + packet_hex); + SSR::mode_s_logger->c_debug(error_len, {}, + Psc::to_string(source->type) + " HULC size:" + std::to_string(packet.size()) + + " hex:" + packet_hex); return nullptr; } return std::make_shared(source, packet); @@ -101,8 +89,8 @@ size_t Data_Source_Handler::process_mode_acs_data(std::string_view origin_data) } origin_data_transform_mode_data(mode_data); if (data_source_debug) { - std::cout << " after origin_data_transform_mode_data " << VAR_STR_2(name, mode_data.size()) << - key << std::endl; + std::cout << " after origin_data_transform_mode_data " << VAR_STR_2(name, mode_data.size()) << key + << std::endl; } static Value_Growth_Multi_T mt; auto t = key; @@ -124,17 +112,16 @@ size_t Data_Source_Handler::process_mode_acs_data(std::string_view origin_data) static bool mode_s_console = Global::instance()->console_config.mode_s_console; static bool record_playback = Global::instance()->console_config.record_playback; if (record_playback) { - pure_log(get_exe_dir() + "/playback/" + source->key + "_playback.dat", - mem2hex(mode_data, true, " ") + "\n"); + pure_log(get_exe_dir() + "/playback/" + source->key + "_playback.dat", mem2hex(mode_data, true, " ") + "\n"); } if (mode_s_console) { - std::cout << source->key + " read:[mode_s_serial]:" << mem2hex(mode_data) - << std::endl; + std::cout << source->key + " read:[mode_s_serial]:" << mem2hex(mode_data) << std::endl; } auto handle_packet = [this, source, &ret](std::string& packet) { ret++; auto msg = create_msg(packet); - if (!msg) return; + if (!msg) + return; source->push_to_feed(msg); auto mt = msg->type; bool mode_s = mt == SSR::Msg::S7 || mt == SSR::Msg::S14; @@ -144,8 +131,7 @@ size_t Data_Source_Handler::process_mode_acs_data(std::string_view origin_data) else if (mt == SSR::Msg::Radarcape_status) { auto radarcape_msg = SSR::create_Radarcape_STATUS_Message(packet); std::cout << radarcape_msg.toJson().to_json_string() << std::endl; - SSR::mode_s_logger->debug("Radarcape_status/radarcape", {}, - radarcape_msg.toJson().to_json_string()); + SSR::mode_s_logger->debug("Radarcape_status/radarcape", {}, radarcape_msg.toJson().to_json_string()); } else if (mode_s) { // 拓展点 @@ -160,9 +146,7 @@ size_t Data_Source_Handler::process_mode_acs_data(std::string_view origin_data) else { auto co = Coro::instance(); auto executor = co->process_data->get_executor(); - asio::post(executor, [handle_packet, packet = std::move(packet)]() mutable { - handle_packet(packet); - }); + asio::post(executor, [handle_packet, packet = std::move(packet)]() mutable { handle_packet(packet); }); } }; SSR::Binary_Format_handle_buffer(source->buffer, mode_data, f); @@ -170,8 +154,8 @@ size_t Data_Source_Handler::process_mode_acs_data(std::string_view origin_data) } void Data_Source_Handler::handle_mode_s(std::shared_ptr mode_s_msg) { auto source = ds(this); - std::string t = mode_s_msg->mlat_timestamp.to_memory() + - mode_s_msg->signal_level + Psc::hex2mem(mode_s_msg->msg_hex); + std::string t = + mode_s_msg->mlat_timestamp.to_memory() + mode_s_msg->signal_level + Psc::hex2mem(mode_s_msg->msg_hex); auto p = mode_s_msg->packet.substr(2); if (t != p) { std::cout << mem2hex(t, true, " ") << std::endl; @@ -183,12 +167,26 @@ void Data_Source_Handler::handle_mode_s(std::shared_ptr mode_s_ bool time_space_filter = cfg.time_space_filter.load(); bool speed_filter = cfg.speed_filter.load(); bool use_system_time = source->ignore_msg_time.load(); - - SSR::ADS_B_T::Constraint air_constraint{cfg.max_speed_m_s, cfg.air_pos_timeout, SSR::cpr_cb, time_space_filter, speed_filter, use_system_time}; - SSR::ADS_B_T::Constraint surface_constraint{cfg.max_speed_m_s, cfg.surface_pos_timeout, SSR::cpr_cb, time_space_filter, speed_filter, use_system_time}; - SSR::parse_mode_s_bin(source.get(), mode_s_msg, - source->base_station.get_pos(), air_constraint, - surface_constraint); + auto base_station_pos = source->base_station.get_pos(); + if (!base_station_pos && source->base_station_has_valid_position.load()) { + base_station_pos = SSR::Position_3D{source->lat, source->lon, source->alt}; + } + auto range_filter = cfg.aircraft_change_list_adsb_range_filter.load(); + auto range_factor = cfg.aircraft_change_list_adsb_range_factor.load(); + SSR::ADS_B_T::Constraint air_constraint{cfg.max_speed_m_s, cfg.air_pos_timeout, SSR::cpr_cb, time_space_filter, + speed_filter, use_system_time, base_station_pos, source->alt, + range_filter, range_factor}; + SSR::ADS_B_T::Constraint surface_constraint{cfg.max_speed_m_s, + cfg.surface_pos_timeout, + SSR::cpr_cb, + time_space_filter, + speed_filter, + use_system_time, + base_station_pos, + source->alt, + range_filter, + range_factor}; + SSR::parse_mode_s_bin(source.get(), mode_s_msg, base_station_pos, air_constraint, surface_constraint); auto base = source->get_aircraft(mode_s_msg->icao); if (base) { auto derived = std::dynamic_pointer_cast(base); @@ -199,8 +197,7 @@ void Data_Source_Handler::handle_mode_s(std::shared_ptr mode_s_ mh.push(mode_s_msg); auto tt = mh.get_all(); if (!tt.empty()) { - pure_log("@/logs/mlat.log", - "receive[" + std::to_string(tt.size()) + "]:\n"); + pure_log("@/logs/mlat.log", "receive[" + std::to_string(tt.size()) + "]:\n"); for (Mlat_MSG& t : tt) { std::ostringstream oss; oss << std::fixed << std::setprecision(10); @@ -223,12 +220,9 @@ void Data_Source_Handler::handle_mode_s(std::shared_ptr mode_s_ auto pos = o_pos.value(); source->alt = o_alt.value(); double x, y, z; - SSR::CPR::WGS84_LBH_to_XYZ(pos.lon, pos.lat, source->alt, x, y, - z); - oss << "\tpos:[" << pos.lon << "," << pos.lat << "," - << source->alt << "]" << std::endl; - oss << "\tXYZ:[" << x << "," << y << "," << z << "]" - << std::endl; + SSR::CPR::WGS84_LBH_to_XYZ(pos.lon, pos.lat, source->alt, x, y, z); + oss << "\tpos:[" << pos.lon << "," << pos.lat << "," << source->alt << "]" << std::endl; + oss << "\tXYZ:[" << x << "," << y << "," << z << "]" << std::endl; } } } @@ -262,23 +256,22 @@ void Data_Source_Handler::handle_HULC(std::string_view packet) { auto msg = mem2hex(std::string(packet)); if (len != packet.size() - 4) { SSR::mode_s_logger->debug("HULC/error_length", {}, - "需要: " + std::to_string(len) + - " 当前: " + std::to_string(packet.size()) + + "需要: " + std::to_string(len) + " 当前: " + std::to_string(packet.size()) + " hex: " + mem2hex(std::string(packet))); return; } if (id == 1) { // 状态消息 auto status_msg = SSR::create_HULC_Status_Message(packet); - bool gps_ok = status_msg.GPS_device_detected() && status_msg.GPS_valid() && - status_msg.GPS_has_valid_fix(); - if (gps_ok) {} + bool gps_ok = status_msg.GPS_device_detected() && status_msg.GPS_valid() && status_msg.GPS_has_valid_fix(); + if (gps_ok) { + } auto g = Global::instance(); base_station.set_msg(status_msg); Log_Type type({}, {{"msg", std::string(msg)}}); - SSR::mode_s_logger->debug("HULC/status", type, - status_msg.toJson().to_json_string()); - if (gps_ok) {} + SSR::mode_s_logger->debug("HULC/status", type, status_msg.toJson().to_json_string()); + if (gps_ok) { + } } else if (id == 24) { SSR::mode_s_logger->debug("HULC/reply", {}, msg); @@ -290,9 +283,9 @@ void Data_Source_Handler::handle_HULC(std::string_view packet) { void Data_Source_Handler::refresh_data_feed_key_list() { // std::cout << key << " refresh_data_feed_key_list" << std::endl; std::vector tmp; - for (auto& relation : - Global::instance()->mode_acs.source_feed_relation_config.map.list()) { - if (!relation->enable) continue; + for (auto& relation : Global::instance()->mode_acs.source_feed_relation_config.map.list()) { + if (!relation->enable) + continue; if (relation->type == "One_to_One_Relation") { auto t = dynamic_cast(relation.get()); // std::cout << VAR_STR_2(t->source_key, this->key) << " @@ -302,19 +295,16 @@ void Data_Source_Handler::refresh_data_feed_key_list() { } } else if (relation->type == "First_Source_To_All_Feed_Relation") { - auto t = - dynamic_cast(relation.get()); + auto t = dynamic_cast(relation.get()); std::shared_ptr first = nullptr; - for (const auto& ds : - Global::instance()->mode_acs.data_source_config.map.list()) { + for (const auto& ds : Global::instance()->mode_acs.data_source_config.map.list()) { if (ds->enable) { first = ds; break; } } if (first->key == key) { - for (const auto& df : - Global::instance()->mode_acs.data_feed_config.map.list()) { + for (const auto& df : Global::instance()->mode_acs.data_feed_config.map.list()) { if (df->enable) { tmp.push_back(df->key); } @@ -337,10 +327,9 @@ void Data_Source_Handler::refresh_data_feed_key_list() { // 1. 删除 tmp 中没有的 cached_data_feed_key_list 元素 auto it = cached_data_feed_key_list.begin(); while (it != cached_data_feed_key_list.end()) { - if (std::find_if(tmp_info.begin(), tmp_info.end(), - [&](const Cached_Source_Info& info) { - return info.key == it->key; - }) == tmp_info.end()) { + if (std::find_if(tmp_info.begin(), tmp_info.end(), [&](const Cached_Source_Info& info) { + return info.key == it->key; + }) == tmp_info.end()) { // 如果当前元素在 tmp 中找不到,删除它 it = cached_data_feed_key_list.erase(it); } @@ -350,11 +339,9 @@ void Data_Source_Handler::refresh_data_feed_key_list() { } // 2. 创建 tmp 中有但 cached_data_feed_key_list 没有的元素 for (const auto& tmp_item : tmp_info) { - auto found = std::find_if(cached_data_feed_key_list.begin(), - cached_data_feed_key_list.end(), - [&](const Cached_Source_Info& cached_item) { - return cached_item.key == tmp_item.key; - }); + auto found = std::find_if( + cached_data_feed_key_list.begin(), cached_data_feed_key_list.end(), + [&](const Cached_Source_Info& cached_item) { return cached_item.key == tmp_item.key; }); if (found == cached_data_feed_key_list.end()) { // 如果 tmp_item 不在 cached_data_feed_key_list 中,添加它 cached_data_feed_key_list.push_back(tmp_item); @@ -364,8 +351,7 @@ void Data_Source_Handler::refresh_data_feed_key_list() { } } } -std::optional convert_to_send_format(Data_Source_Handler* ds, - const std::shared_ptr& feed, +std::optional convert_to_send_format(Data_Source_Handler* ds, const std::shared_ptr& feed, const std::shared_ptr& msg) { auto& type = msg->type; auto fs = dynamic_cast(msg->source.get()); @@ -382,22 +368,22 @@ std::optional convert_to_send_format(Data_Source_Handler* ds, // 计算出是否输出消息 std::optional send_msg = std::nullopt; if (type == SSR::Msg::HULC_Status) { - if (!use_status) return std::nullopt; + if (!use_status) + return std::nullopt; auto data = static_cast(msg.get()); - if (output_format == Output_Data_Format::BIN || - output_format == Output_Data_Format::BIN_ID) { + if (output_format == Output_Data_Format::BIN || output_format == Output_Data_Format::BIN_ID) { send_msg = SSR::packet_to_escape_format(data->packet); } } else if (type == SSR::Msg::Radarcape_status) { auto data = static_cast(msg.get()); - if (output_format == Output_Data_Format::BIN || - output_format == Output_Data_Format::BIN_ID) { + if (output_format == Output_Data_Format::BIN || output_format == Output_Data_Format::BIN_ID) { send_msg = SSR::packet_to_escape_format(data->packet); } } else if (type == SSR::Msg::AC) { - if (!use_mode_ac) return std::nullopt; + if (!use_mode_ac) + return std::nullopt; auto data = static_cast(msg.get()); std::string& msg_hex = data->msg_hex; char signal_level = data->signal_level; @@ -448,10 +434,12 @@ std::optional convert_to_send_format(Data_Source_Handler* ds, sbs_out_put = true; } } - if (!sbs_out_put) return std::nullopt; + if (!sbs_out_put) + return std::nullopt; send_msg = ""; SSR::SBS_MSG msg_base; - if (!aircraft) return std::nullopt; + if (!aircraft) + return std::nullopt; msg_base.set(aircraft.get()); for (int msg_t = 1; msg_t <= 8; ++msg_t) { send_msg->append(msg_base.to_msg(msg_t) + "\n"); @@ -463,11 +451,11 @@ std::optional convert_to_send_format(Data_Source_Handler* ds, send_msg->append(msg_base.to_STA() + "\n"); return send_msg; } - bool DF_11_17_18 = - df == SSR::Downlink_Format::All_Call_Reply_11 || + bool DF_11_17_18 = df == SSR::Downlink_Format::All_Call_Reply_11 || df == SSR::Downlink_Format::Extended_Squitter_17 || df == SSR::Downlink_Format::Extended_Squitter_Non_Transponder_18; - if (mode_s_output_type == Mode_S_Output_Type::DF_11_17_18 && !DF_11_17_18) return std::nullopt; + if (mode_s_output_type == Mode_S_Output_Type::DF_11_17_18 && !DF_11_17_18) + return std::nullopt; if (mode_s_output_type == Mode_S_Output_Type::NO_POS_Mode_S) { if (aircraft && aircraft->pos().has_value()) { return std::nullopt; @@ -493,8 +481,7 @@ std::optional convert_to_send_format(Data_Source_Handler* ds, send_msg = create_MLAT_AVR_format(msg_hex, &mlat_timestamp); } else { - std::cerr << "Unknown output format " << VAR_STR_1(output_format) - << std::endl; + std::cerr << "Unknown output format " << VAR_STR_1(output_format) << std::endl; return std::nullopt; } } @@ -516,9 +503,9 @@ void Data_Source_Handler::push_to_feed(const std::shared_ptr& msg) { break; } std::shared_ptr& feed = opt_feed.value(); - if (!feed->enable) continue; - if (msg->type == SSR::Mode_Msg::T::S7 || - msg->type == SSR::Mode_Msg::T::S14) { + if (!feed->enable) + continue; + if (msg->type == SSR::Mode_Msg::T::S7 || msg->type == SSR::Mode_Msg::T::S14) { // static Frequency_Limit fl; // if (!fl.test()) { // std::ostringstream oss; diff --git a/module/Local_Server/Data_Source/Database.cpp b/module/Local_Server/Data_Source/Database.cpp index 1e077fd..d135361 100644 --- a/module/Local_Server/Data_Source/Database.cpp +++ b/module/Local_Server/Data_Source/Database.cpp @@ -1,12 +1,56 @@ #include "Database.h" +#include "Data_Source.h" #include "../server/Global.h" #include "../server/Performance_Monitor.h" +#include #include +#include #include #include #include +namespace { +double to_radians(double degrees) { + return degrees * 3.14159265358979323846 / 180.0; +} +double to_degrees(double radians) { + return radians * 180.0 / 3.14159265358979323846; +} +bool aircraft_in_base_station_radio_range(const std::optional &base_position, + const SSR::Position_Info &position, + double factor) { + if (!base_position) { + return true; + } + return SSR::CPR::in_radio_line_of_sight_range(*base_position, base_position->alt, position, position.alt, factor); +} +double track_heading_from_points(const SSR::Position_Info &first, + const SSR::Position_Info &second) { + auto lat1 = to_radians(first.lat); + auto lat2 = to_radians(second.lat); + auto dlon = to_radians(second.lon - first.lon); + auto y = std::sin(dlon) * std::cos(lat2); + auto x = std::cos(lat1) * std::sin(lat2) - + std::sin(lat1) * std::cos(lat2) * std::cos(dlon); + auto angle = to_degrees(std::atan2(y, x)); + return angle < 0 ? angle + 360.0 : angle; +} +double track_pitch_from_points(const SSR::Position_Info &first, + const SSR::Position_Info &second) { + auto distance = SSR::CPR::haversine(first, second); + return std::atan2(second.alt - first.alt, distance) * 180.0 / + 3.14159265358979323846; +} +Psc::JSON track_orientation_json_from_points(const SSR::Position_Info &first, + const SSR::Position_Info &second) { + auto ret = Psc::JSON::object(); + ret.append({"heading", track_heading_from_points(first, second)}); + ret.append({"pitch", track_pitch_from_points(first, second)}); + ret.append({"roll", 0.0}); + return ret; +} +} Aircraft::Aircraft(std::string_view icao) : Aircraft_Info(icao) { auto size = Global::instance()->mode_acs.max_track_point_size.load(); air_pos_track_list.init(size); @@ -77,14 +121,34 @@ Psc::JSON DataBase::get_aircraftlist(int limit_msg_num) { JSON DataBase::get_all_aircraft_json() { JSON json = JSON::array(); auto g = Global::instance(); + auto min_position_points = + g->mode_acs.aircraft_change_list_min_position_points.load(); + auto range_filter = g->mode_acs.aircraft_change_list_adsb_range_filter.load(); + auto range_factor = g->mode_acs.aircraft_change_list_adsb_range_factor.load(); + auto source = g->source(get_key()); + auto base_position = base_station.get_pos(); + if (source) { + if (!base_position && source->base_station_has_valid_position.load()) { + base_position = SSR::Position_3D{source->lat, source->lon, source->alt}; + } + } size_t num = 0; for (auto &it : aircraft_map.values()) { - auto vto = Flight_VTO::to_VTO(it.get()); - bool have_pos = !it.get()->air_pos_track_list.empty(); - if (have_pos) { - json.children.push_back(vto.to_json()); - num++; + if (it->air_pos_track_list.size() < min_position_points) { + continue; } + auto last_position = it->air_pos_track_list.last(); + if (!last_position) { + continue; + } + if (range_filter && + !aircraft_in_base_station_radio_range(base_position, *last_position, + range_factor)) { + continue; + } + auto vto = Flight_VTO::to_VTO(it.get()); + json.children.push_back(vto.to_json()); + num++; } have_pos_aircraft_num = num; return json; @@ -187,6 +251,16 @@ void database_server(Global *g) { auto ret = JSON::object(); if (db) { ret = db->base_station.to_Json(); + auto target_height = + g->mode_acs.adsb_theoretical_target_altitude_meters.load(); + auto range = BaseStation::theoretical_detection_range_meters(db->alt, + target_height); + ret.append({"latitude", db->lat}); + ret.append({"longitude", db->lon}); + ret.append({"height", db->alt}); + ret.append({"adsb_theoretical_target_altitude_meters", target_height}); + ret.append({"adsb_theoretical_detection_range_meters", range}); + ret.append({"理论探测范围", range}); } res->setBody(warp(ret).to_json_string()); }); @@ -198,7 +272,7 @@ void database_server(Global *g) { JSON ret; if (aircraft != nullptr) { auto vto = Flight_VTO::to_VTO(aircraft.get()); - ret = vto.to_json(); + ret = vto.to_base_info_json(); } res->setBody(ret.to_json_string()); }); @@ -222,6 +296,14 @@ void database_server(Global *g) { JSON ret; if (aircraft != nullptr) { ret = aircraft->air_pos_track_list.get_last_array_json(last_size); + auto last_two = aircraft->air_pos_track_list.last_two(); + if (last_two.has_value()) { + ret.children.insert( + ret.children.begin(), + JSON("track_orientation", + track_orientation_json_from_points(last_two->first, + last_two->second))); + } ret.children.insert(ret.children.begin(), JSON("ds", data_source_key)); ret.children.insert(ret.children.begin(), JSON("icao", icao)); } diff --git a/module/Local_Server/global_include.h b/module/Local_Server/global_include.h index 21693bb..eb4841d 100644 --- a/module/Local_Server/global_include.h +++ b/module/Local_Server/global_include.h @@ -1,8 +1,5 @@ #ifndef PSC_GLOBAL_INCLUDE_H #define PSC_GLOBAL_INCLUDE_H -#include "Core/Statistics/Statistics.h" -#include "Core/socket/ByteOrder.h" -#include "SSR/export.h" #include #include #include @@ -11,10 +8,13 @@ #include #include #include +#include #include #include #include -#include +#include "Core/Statistics/Statistics.h" +#include "Core/socket/ByteOrder.h" +#include "SSR/export.h" class Global; class Invalid_Http_Param final : public std::runtime_error { public: @@ -23,28 +23,28 @@ public: [[noreturn]] inline void throw_invalid_http_param(const char* detail) { throw Invalid_Http_Param(detail); } -#define CHECK_JSON_PARAM \ - auto o_params = Psc::try_parse_json(req->body().data()); \ - if (!o_params.has_value()) { \ - throw_invalid_http_param("request body is not valid JSON"); \ - } \ - auto ¶ms = o_params.value(); \ - auto that_json = ¶ms; -#define HTTP_REQUIRE_VALUE(NAME, EXPRESSION) \ - auto o_http_##NAME = (EXPRESSION); \ - if (!o_http_##NAME.has_value()) { \ - throw_invalid_http_param(#NAME); \ - } \ - auto NAME = std::move(o_http_##NAME).value(); -#define HTTP_REQUIRE_PTR(NAME, EXPRESSION) \ - auto NAME = (EXPRESSION); \ - if ((NAME) == nullptr) { \ - throw_invalid_http_param(#NAME); \ - } -#define HTTP_REQUIRE_TRUE(EXPRESSION, DETAIL) \ - if (!(EXPRESSION)) { \ - throw_invalid_http_param(DETAIL); \ - } +#define CHECK_JSON_PARAM \ + auto o_params = Psc::try_parse_json(req->body().data()); \ + if (!o_params.has_value()) { \ + throw_invalid_http_param("request body is not valid JSON"); \ + } \ + auto& params = o_params.value(); \ + auto that_json = ¶ms; +#define HTTP_REQUIRE_VALUE(NAME, EXPRESSION) \ + auto o_http_##NAME = (EXPRESSION); \ + if (!o_http_##NAME.has_value()) { \ + throw_invalid_http_param(#NAME); \ + } \ + auto NAME = std::move(o_http_##NAME).value(); +#define HTTP_REQUIRE_PTR(NAME, EXPRESSION) \ + auto NAME = (EXPRESSION); \ + if ((NAME) == nullptr) { \ + throw_invalid_http_param(#NAME); \ + } +#define HTTP_REQUIRE_TRUE(EXPRESSION, DETAIL) \ + if (!(EXPRESSION)) { \ + throw_invalid_http_param(DETAIL); \ + } template class Ordered_List { public: @@ -218,9 +218,7 @@ public: // Ret_J(speed); std::string t = "null"; if (total_num != 0) { - t = std::to_string(static_cast(crc_error_num) / - static_cast(total_num)) + - "%"; + t = std::to_string(static_cast(crc_error_num) / static_cast(total_num)) + "%"; } ret.append({"crc误码率", t}); for (auto& cur : statistic_map) { @@ -301,12 +299,8 @@ public: if (iter != statistic.result_num.end()) { num = iter->second; } - double rate = static_cast(num) / - static_cast(statistic.total_num) * 100.0; - type_result.append({ - Psc::to_string(result_type), - std::format("{} {:.2f}%", num, rate) - }); + double rate = static_cast(num) / static_cast(statistic.total_num) * 100.0; + type_result.append({Psc::to_string(result_type), std::format("{} {:.2f}%", num, rate)}); } result.append({Psc::to_string(cpr_type), type_result}); } @@ -318,11 +312,8 @@ protected: std::map result_num; }; inline static constexpr std::array statistic_types{ - SSR::CPR_Ret_Type::Speed_Error, - SSR::CPR_Ret_Type::Parse_OK, - SSR::CPR_Ret_Type::Inter_Error, - SSR::CPR_Ret_Type::Time_Space_Too_Long - }; + SSR::CPR_Ret_Type::Speed_Error, SSR::CPR_Ret_Type::Parse_OK, SSR::CPR_Ret_Type::Inter_Error, + SSR::CPR_Ret_Type::Time_Space_Too_Long, SSR::CPR_Ret_Type::Out_of_Maximum_Detection_Range}; std::map statistic_map; }; class Mode_S_Statistic_Data { @@ -336,21 +327,14 @@ public: Ret_J(total_num); std::string t = "null"; if (total_num != 0) { - t = std::format( - "{}%", - static_cast(crc_error_num) / - static_cast(total_num) - ); + t = std::format("{}%", static_cast(crc_error_num) / static_cast(total_num)); } Ret_J(crc_error_num); ret.append({"crc_error_rate", t}); Psc::JSON df_sub_type = Psc::JSON::object(); for (auto& cur : DF_Statistic_Data_map) { - df_sub_type.append({ - Psc::to_string(cur.first) + " [" + - std::to_string(static_cast(cur.first)) + "]", - cur.second.to_json() - }); + df_sub_type.append({Psc::to_string(cur.first) + " [" + std::to_string(static_cast(cur.first)) + "]", + cur.second.to_json()}); } ret.append({"DF子类型", df_sub_type}); ret.append({"CPR统计信息", cpr_statistic_data.to_json()}); @@ -362,9 +346,7 @@ public: void add_cpr_error(SSR::CPR_Type cpr_type, SSR::CPR_Ret_Type result_type) { cpr_statistic_data.add(cpr_type, result_type); } - DF_Statistic_Data* get_create_df_statistic_data( - const SSR::Downlink_Format& key - ) { + DF_Statistic_Data* get_create_df_statistic_data(const SSR::Downlink_Format& key) { DF_Statistic_Data* t; auto iter = DF_Statistic_Data_map.find(key); if (iter == DF_Statistic_Data_map.end()) { diff --git a/module/Local_Server/server/Config.cpp b/module/Local_Server/server/Config.cpp index e92dc52..8b0f5d9 100644 --- a/module/Local_Server/server/Config.cpp +++ b/module/Local_Server/server/Config.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #undef interface std::string default_config_path; @@ -49,6 +50,59 @@ static Psc::JSON write_string_array(std::string_view key, const std::vector kDefaultAircraftModelUrls[] = { + {"no_category_information", "/ui/model/aircraft-no-category-information.glb"}, + {"surface_emergency_vehicle", "/ui/model/aircraft-surface-emergency-vehicle.glb"}, + {"surface_service_vehicle", "/ui/model/aircraft-surface-service-vehicle.glb"}, + {"ground_obstruction_4", "/ui/model/aircraft-ground-obstruction-4.glb"}, + {"ground_obstruction_5", "/ui/model/aircraft-ground-obstruction-5.glb"}, + {"ground_obstruction_6", "/ui/model/aircraft-ground-obstruction-6.glb"}, + {"ground_obstruction_7", "/ui/model/aircraft-ground-obstruction-7.glb"}, + {"glider", "/ui/model/aircraft-glider.glb"}, + {"lighter_than_air", "/ui/model/aircraft-lighter-than-air.glb"}, + {"parachutist", "/ui/model/aircraft-parachutist.glb"}, + {"ultralight_hangglider_paraglider", "/ui/model/aircraft-ultralight-hangglider-paraglider.glb"}, + {"reserved_3_5", "/ui/model/aircraft-reserved-3-5.glb"}, + {"unmanned_aerial_vehicle", "/ui/model/aircraft-unmanned-aerial-vehicle.glb"}, + {"space_transatmospheric_vehicle", "/ui/model/aircraft-space-transatmospheric-vehicle.glb"}, + {"light_aircraft", "/ui/model/aircraft-light-aircraft.glb"}, + {"medium_1_aircraft", "/ui/model/aircraft-medium-1-aircraft.glb"}, + {"medium_2_aircraft", "/ui/model/aircraft-medium-2-aircraft.glb"}, + {"high_vortex_aircraft", "/ui/model/aircraft-high-vortex-aircraft.glb"}, + {"heavy_aircraft", "/ui/model/aircraft-heavy-aircraft.glb"}, + {"high_performance_aircraft", "/ui/model/aircraft-high-performance-aircraft.glb"}, + {"rotorcraft", "/ui/model/aircraft-rotorcraft.glb"} +}; +static std::map make_default_aircraft_models() { + std::map ret; + for (const auto& item : kDefaultAircraftModelUrls) { + ret.emplace(std::string(item.first), Map_Model_Item_Config{std::string(item.second), 50.0, -90.0, 0.0, 0.0}); + } + return ret; +} +static std::map read_optional_model_object_field(const Psc::JSON* json, std::string_view key, std::map fallback) { + auto field = json->get(key); + if (field == nullptr) { + return fallback; + } + if (field->valueType != Psc::Object) { + throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key)); + } + for (const auto& child : field->children) { + if (child.valueType != Psc::Object) { + throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key)); + } + fallback[child.key].from_base_json(&child); + } + return fallback; +} +static Psc::JSON write_model_object(std::string_view key, const std::map& values) { + auto object = Psc::JSON::object(); + for (const auto& item : values) { + object.append({item.first, item.second.to_base_json()}); + } + return Psc::JSON(key, object); +} static std::string read_string_field(const Psc::JSON* json, std::string_view key) { auto field = json->get(key); if (field == nullptr || field->valueType != Psc::String) { @@ -242,6 +296,31 @@ Psc::JSON Map_Camera_Config::to_base_json() const { bool Map_View_Config::is_scene_mode(std::string_view mode) { return mode == "2d" || mode == "2.5d" || mode == "3d"; } +bool Map_Tile_View_Config::is_tile_zoom_mode(std::string_view mode) { + return mode == "native" || mode == "upscale" || mode == "both"; +} +void Map_Tile_View_Config::from_base_json(const Psc::JSON* that_json) { + if (that_json == nullptr) { + return; + } + if (that_json->valueType != Psc::Object) { + throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map_tile_view"); + } + current_imagery_key = read_optional_string_field(that_json, "current_imagery_key", current_imagery_key); + auto next_tile_zoom_mode = read_optional_string_field(that_json, "tile_zoom_mode", tile_zoom_mode); + if (!is_tile_zoom_mode(next_tile_zoom_mode)) { + throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "tile_zoom_mode"); + } + tile_zoom_mode = next_tile_zoom_mode; + tile_display_maximum_level = std::clamp(read_optional_uint32_field(that_json, "tile_display_maximum_level", tile_display_maximum_level), 0u, 24u); +} +Psc::JSON Map_Tile_View_Config::to_base_json() const { + auto ret = Psc::JSON::object(); + ret.append({"current_imagery_key", current_imagery_key}); + ret.append({"tile_zoom_mode", tile_zoom_mode}); + ret.append({"tile_display_maximum_level", tile_display_maximum_level}); + return ret; +} void Map_View_Config::from_base_json(const Psc::JSON* that_json) { if (that_json == nullptr) { return; @@ -254,11 +333,15 @@ void Map_View_Config::from_base_json(const Psc::JSON* that_json) { throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "scene_mode"); } scene_mode = next_scene_mode; + map2d.from_base_json(that_json->get("map2d")); + map3d.from_base_json(that_json->get("map3d")); camera.from_base_json(that_json->get("camera")); } Psc::JSON Map_View_Config::to_base_json() const { auto ret = Psc::JSON::object(); ret.append({"scene_mode", scene_mode}); + ret.append({"map2d", map2d.to_base_json()}); + ret.append({"map3d", map3d.to_base_json()}); ret.append({"camera", camera.to_base_json()}); return ret; } @@ -279,7 +362,7 @@ void Cesium_Graphics_Config::from_base_json(const Psc::JSON* that_json) { enable_lighting = read_optional_bool_field(that_json, "enable_lighting", enable_lighting); maximum_screen_space_error = std::clamp(read_optional_uint32_field(that_json, "maximum_screen_space_error", maximum_screen_space_error), 1u, 16u); terrain_enabled_in_3d = read_optional_bool_field(that_json, "terrain_enabled_in_3d", terrain_enabled_in_3d); - terrain_exaggeration = std::clamp(read_optional_double_field(that_json, "terrain_exaggeration", terrain_exaggeration), 0.0, 5.0); + terrain_exaggeration = read_optional_double_field(that_json, "terrain_exaggeration", terrain_exaggeration); terrain_limit_maximum_level = read_optional_bool_field(that_json, "terrain_limit_maximum_level", terrain_limit_maximum_level); terrain_maximum_level = std::clamp(read_optional_uint32_field(that_json, "terrain_maximum_level", terrain_maximum_level), 0u, 24u); terrain_cache_tiles = std::clamp(read_optional_uint32_field(that_json, "terrain_cache_tiles", terrain_cache_tiles), 16u, 2048u); @@ -302,6 +385,49 @@ Psc::JSON Cesium_Graphics_Config::to_base_json() const { ret.append({"terrain_cache_tiles", terrain_cache_tiles}); return ret; } +Map_Model_Item_Config::Map_Model_Item_Config(std::string url) + : url(std::move(url)) {} +Map_Model_Item_Config::Map_Model_Item_Config(std::string url, double built_in_size, double heading, double pitch, double roll) + : url(std::move(url)), built_in_size(built_in_size), heading_offset_degrees(heading), pitch_offset_degrees(pitch), roll_offset_degrees(roll) {} +void Map_Model_Item_Config::from_base_json(const Psc::JSON* that_json) { + if (that_json == nullptr || that_json->valueType != Psc::Object) { + throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map_model_item"); + } + url = read_optional_string_field(that_json, "url", url); + built_in_size = read_optional_double_field(that_json, "built_in_size", built_in_size); + heading_offset_degrees = read_optional_double_field(that_json, "heading_offset_degrees", heading_offset_degrees); + pitch_offset_degrees = read_optional_double_field(that_json, "pitch_offset_degrees", pitch_offset_degrees); + roll_offset_degrees = read_optional_double_field(that_json, "roll_offset_degrees", roll_offset_degrees); +} +Psc::JSON Map_Model_Item_Config::to_base_json() const { + auto ret = Psc::JSON::object(); + ret.append({"url", url}); + ret.append({"built_in_size", built_in_size}); + ret.append({"heading_offset_degrees", heading_offset_degrees}); + ret.append({"pitch_offset_degrees", pitch_offset_degrees}); + ret.append({"roll_offset_degrees", roll_offset_degrees}); + return ret; +} +Map_Model_Config::Map_Model_Config() + : aircraft_models(make_default_aircraft_models()) {} +void Map_Model_Config::from_base_json(const Psc::JSON* that_json) { + if (that_json == nullptr) { + return; + } + if (that_json->valueType != Psc::Object) { + throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map_models"); + } + aircraft_model.from_base_json(that_json->get("aircraft_model")); + aircraft_models = read_optional_model_object_field(that_json, "aircraft_models", make_default_aircraft_models()); + base_station_model.from_base_json(that_json->get("base_station_model")); +} +Psc::JSON Map_Model_Config::to_base_json() const { + auto ret = Psc::JSON::object(); + ret.append({"aircraft_model", aircraft_model.to_base_json()}); + ret.append(write_model_object("aircraft_models", aircraft_models)); + ret.append({"base_station_model", base_station_model.to_base_json()}); + return ret; +} void Mode_ACS_Config::server(Global *g) { auto &svr = g->svr; @@ -318,6 +444,19 @@ void Mode_ACS_Config::server(Global *g) { CHECK_JSON_PARAM auto &j = o_params.value(); init_base_json(&j); + if (auto min_points = j.try_get_number("aircraft_change_list_min_position_points")) { + aircraft_change_list_min_position_points = min_points.value(); + } + if (auto range_filter = j.try_get_bool("aircraft_change_list_adsb_range_filter")) { + aircraft_change_list_adsb_range_filter = range_filter.value(); + } + if (auto range_factor = j.try_get_number("aircraft_change_list_adsb_range_factor")) { + aircraft_change_list_adsb_range_factor = range_factor.value(); + } + if (auto target_altitude = j.try_get_number("adsb_theoretical_target_altitude_meters")) { + adsb_theoretical_target_altitude_meters = target_altitude.value(); + } + Global::save(); res->setBody(warp(to_base_json()).to_json_string()); }); } diff --git a/module/Local_Server/server/Config.h b/module/Local_Server/server/Config.h index c64a742..476f542 100644 --- a/module/Local_Server/server/Config.h +++ b/module/Local_Server/server/Config.h @@ -7,6 +7,7 @@ #include "MLAT.h" #include "Source_Feed_Relation.h" #include +#include #include inline bool delete_log = false; extern std::string default_config_path; @@ -56,6 +57,10 @@ struct Mode_ACS_Config_Data { Psc::Copyable_Atomic mode_other_max_num{}; Psc::Copyable_Atomic report_data_feed_msg_mum{}; Psc::Copyable_Atomic default_min_aircraft_list_num{}; + Psc::Copyable_Atomic aircraft_change_list_min_position_points = 2; + Psc::Copyable_Atomic aircraft_change_list_adsb_range_filter = true; + Psc::Copyable_Atomic aircraft_change_list_adsb_range_factor = 1.2; + Psc::Copyable_Atomic adsb_theoretical_target_altitude_meters = 10000.0; Psc::Copyable_Atomic ignore_df_11{}; Psc::Copyable_Atomic monitor_msg_live{}; PSC_USE_JSON @@ -148,8 +153,18 @@ struct Map_Camera_Config { void from_base_json(const Psc::JSON* that_json); [[nodiscard]] Psc::JSON to_base_json() const; }; +struct Map_Tile_View_Config { + std::string current_imagery_key = "imagery"; + std::string tile_zoom_mode = "native"; + std::uint32_t tile_display_maximum_level = 19; + void from_base_json(const Psc::JSON* that_json); + [[nodiscard]] Psc::JSON to_base_json() const; + [[nodiscard]] static bool is_tile_zoom_mode(std::string_view mode); +}; struct Map_View_Config { std::string scene_mode = "3d"; + Map_Tile_View_Config map2d; + Map_Tile_View_Config map3d; Map_Camera_Config camera; void from_base_json(const Psc::JSON* that_json); [[nodiscard]] Psc::JSON to_base_json() const; @@ -173,6 +188,26 @@ struct Cesium_Graphics_Config { void from_base_json(const Psc::JSON* that_json); [[nodiscard]] Psc::JSON to_base_json() const; }; +struct Map_Model_Item_Config { + std::string url; + double built_in_size = 50.0; + double heading_offset_degrees = 0.0; + double pitch_offset_degrees = 0.0; + double roll_offset_degrees = 0.0; + Map_Model_Item_Config() = default; + explicit Map_Model_Item_Config(std::string url); + Map_Model_Item_Config(std::string url, double built_in_size, double heading, double pitch, double roll); + void from_base_json(const Psc::JSON* that_json); + [[nodiscard]] Psc::JSON to_base_json() const; +}; +struct Map_Model_Config { + Map_Model_Item_Config aircraft_model{"/ui/model/aircraft.glb", 50.0, -90.0, 0.0, 0.0}; + std::map aircraft_models; + Map_Model_Item_Config base_station_model{"/ui/model/base-station.glb", 100.0, 0.0, 0.0, 0.0}; + Map_Model_Config(); + void from_base_json(const Psc::JSON* that_json); + [[nodiscard]] Psc::JSON to_base_json() const; +}; struct Net_Config { std::string key{}; std::string ip{}; @@ -229,6 +264,7 @@ public: Map_Resources_Config map_resources_config; Map_View_Config map_view_config; Cesium_Graphics_Config cesium_graphics_config; + Map_Model_Config map_model_config; Mode_ACS_Config mode_acs; Log_Config log_config; Console_Config console_config{}; @@ -261,6 +297,7 @@ public: map_resources_config.from_base_json(that_json->get("map_resources")); map_view_config.from_base_json(that_json->get("map_view")); cesium_graphics_config.from_base_json(that_json->get("cesium_graphics")); + map_model_config.from_base_json(that_json->get("map_models")); device_config.init(that_json->get("device")); mode_acs.init(that_json->get("mode_acs")); console_config.from_base_json(that_json->get("console")); @@ -279,6 +316,7 @@ public: {"web_server", web_server_config.to_base_json()}, {"map_resources", map_resources_config.to_base_json()}, {"map_view", map_view_config.to_base_json()}, + {"map_models", map_model_config.to_base_json()}, {"device", device_config.to_base_json()}, {"mode_acs", mode_acs.to_base_json()}, //{"ais", ais.to_json()}, diff --git a/module/Local_Server/server/server.cpp b/module/Local_Server/server/server.cpp index 66a945a..ecb78e7 100644 --- a/module/Local_Server/server/server.cpp +++ b/module/Local_Server/server/server.cpp @@ -148,6 +148,12 @@ void Global::init_web_server() { httplib::detail::read_file(path, content); auto content_type = httplib::detail::find_content_type( path, {}, "application/octet-stream"); + if (path.ends_with(".glb")) { + content_type = "model/gltf-binary"; + } + else if (path.ends_with(".gltf")) { + content_type = "model/gltf+json"; + } res->setContentTypeString(content_type); res->setBody(content); // res.set_file_content(path); diff --git a/module/Local_Server/server/tiles.cpp b/module/Local_Server/server/tiles.cpp index ff40368..0382b36 100644 --- a/module/Local_Server/server/tiles.cpp +++ b/module/Local_Server/server/tiles.cpp @@ -399,6 +399,135 @@ Psc::JSON make_map_view_response(Global* global) { ret.append({"current_imagery_key", global->map_resources_config.current_imagery_key}); return ret; } +bool is_multipart_request(const drogon::HttpRequestPtr& req) { + auto content_type = req->getHeader("content-type"); + return content_type.find("multipart/form-data") != std::string::npos; +} +std::string normalize_upload_extension(std::string extension) { + std::ranges::transform(extension, extension.begin(), [](unsigned char ch) { + return static_cast(std::tolower(ch)); + }); + if (!extension.empty() && extension.front() != '.') { + extension.insert(extension.begin(), '.'); + } + return extension; +} +std::filesystem::path web_model_directory(Global* global) { + return std::filesystem::path(global->web_server_config.get_true_webapp()) / "model"; +} +std::filesystem::path source_model_directory(Global* global) { + return std::filesystem::path(global->web_server_config.get_true_webapp()).parent_path() / "model"; +} +bool is_safe_aircraft_model_key(std::string_view key) { + return !key.empty() && std::ranges::all_of(key, [](unsigned char ch) { + return std::isalnum(ch) != 0 || ch == '_'; + }); +} +std::string model_file_name(std::string_view model_type, std::string_view aircraft_model_key) { + if (model_type == "aircraft") { + return "aircraft-custom.glb"; + } + if (model_type == "aircraft_type") { + if (!is_safe_aircraft_model_key(aircraft_model_key)) { + throw std::invalid_argument("invalid aircraft_model_key"); + } + auto normalized_key = std::string(aircraft_model_key); + std::ranges::replace(normalized_key, '_', '-'); + return "aircraft-" + normalized_key + ".glb"; + } + if (model_type == "base_station") { + return "base-station-custom.glb"; + } + throw std::invalid_argument("invalid model_type"); +} +void save_uploaded_model_file(const drogon::HttpFile& upload, + std::string_view model_type, + std::string_view aircraft_model_key, + Global* global) { + auto extension = normalize_upload_extension(std::string(upload.getFileExtension())); + if (extension != ".glb") { + throw std::invalid_argument("model file must be .glb"); + } + if (model_type == "aircraft_type" && !global->map_model_config.aircraft_models.contains(std::string(aircraft_model_key))) { + throw std::invalid_argument("unknown aircraft_model_key"); + } + auto upload_dir = std::filesystem::path(drogon::app().getUploadPath()); + std::filesystem::create_directories(upload_dir); + auto temp_name = std::string(model_type) + "_" + std::string(aircraft_model_key) + "_model_upload.tmp"; + if (upload.saveAs(temp_name) != 0) { + throw std::runtime_error("cannot save uploaded model"); + } + auto temp_path = upload_dir / temp_name; + auto file_name = model_file_name(model_type, aircraft_model_key); + auto web_dir = web_model_directory(global); + auto source_dir = source_model_directory(global); + std::filesystem::create_directories(web_dir); + std::filesystem::create_directories(source_dir); + std::filesystem::copy_file(temp_path, web_dir / file_name, std::filesystem::copy_options::overwrite_existing); + std::filesystem::copy_file(temp_path, source_dir / file_name, std::filesystem::copy_options::overwrite_existing); + std::filesystem::remove(temp_path); + auto url = "/ui/model/" + file_name; + if (model_type == "aircraft") { + global->map_model_config.aircraft_model.url = url; + } + else if (model_type == "aircraft_type") { + global->map_model_config.aircraft_models[std::string(aircraft_model_key)].url = url; + } + else { + global->map_model_config.base_station_model.url = url; + } +} +void handle_map_model_upload(const drogon::HttpRequestPtr& req, Global* global) { + drogon::MultiPartParser parser; + if (parser.parse(req) != 0) { + throw std::invalid_argument("invalid multipart model upload"); + } + auto model_type = parser.getOptionalParameter("model_type"); + if (!model_type || parser.getFiles().size() != 1) { + throw std::invalid_argument("model_type or file"); + } + auto aircraft_model_key = parser.getOptionalParameter("aircraft_model_key").value_or(""); + save_uploaded_model_file(parser.getFiles().front(), *model_type, aircraft_model_key, global); + Global::save(); +} +void handle_map_model_config_update(const drogon::HttpRequestPtr& req, Global* global) { + auto parsed = Psc::try_parse_json(req->body().data()); + if (!parsed.has_value()) { + throw std::invalid_argument("invalid map_models"); + } + global->map_model_config.from_base_json(&parsed.value()); + Global::save(); +} +void register_map_models_handler() { + drogon::app().registerHandlerViaRegex( + R"(^/map/models$)", + [](const drogon::HttpRequestPtr& req, + std::function&& callback) { + auto global = Global::instance(); + auto response = drogon::HttpResponse::newHttpResponse(); + try { + if (req->method() == drogon::Post) { + if (is_multipart_request(req)) { + handle_map_model_upload(req, global); + } + else { + handle_map_model_config_update(req, global); + } + } + response->setStatusCode(drogon::k200OK); + response->setContentTypeCode(drogon::CT_APPLICATION_JSON); + response->setBody(global->map_model_config.to_base_json().to_json_string()); + } + catch (const std::exception& error) { + response->setStatusCode(drogon::k400BadRequest); + response->setContentTypeCode(drogon::CT_TEXT_PLAIN); + response->setBody(Psc::platform_2_utf8(error.what())); + } + response->addHeader("Cache-Control", "no-store"); + callback(response); + }, + {drogon::Get, drogon::Post}); +} void register_map_view_handler() { drogon::app().registerHandlerViaRegex( R"(^/map/view$)", @@ -426,6 +555,10 @@ void register_map_view_handler() { try { auto next_view = global->map_view_config; next_view.from_base_json(&parsed.value()); + if (!global->map_resources_config.is_imagery_key(next_view.map2d.current_imagery_key) || + !global->map_resources_config.is_imagery_key(next_view.map3d.current_imagery_key)) { + throw std::invalid_argument("invalid current_imagery_key"); + } global->map_resources_config.current_imagery_key = key.value(); global->map_view_config = next_view; Global::save(); @@ -453,6 +586,7 @@ void Global::init_tiles() { auto terrain = std::make_shared(map_resources_config.terrain); register_map_resources_handler(); register_map_graphics_handler(); + register_map_models_handler(); register_map_view_handler(); register_tile_handler(R"(^/map/imagery/.*$)", "/map/imagery/", imagery); register_tile_handler(R"(^/tiles/.*$)", "/tiles/", google_imagery);