支持mbtiles
This commit is contained in:
+381
-273
@@ -41,11 +41,11 @@
|
|||||||
"webapp": "@/../third_party/eacp_webapp/wwwroot"
|
"webapp": "@/../third_party/eacp_webapp/wwwroot"
|
||||||
},
|
},
|
||||||
"map_resources": {
|
"map_resources": {
|
||||||
"current_imagery_key": "imagery",
|
"current_imagery_key": "mixed_imagery",
|
||||||
"imagery": {
|
"imagery": {
|
||||||
"directories": [
|
"directories": [
|
||||||
"D:/ae/tiles/Esri_World_Imagery_0-9",
|
"D:/ae/tiles/Esri_World_Imagery_0-9",
|
||||||
"D:/ae/tiles/Esri_World_Imagery_10-12"
|
"D:/ae/tiles/Esri_World_Imagery_10-12"
|
||||||
],
|
],
|
||||||
"path_layout": "z/x/y",
|
"path_layout": "z/x/y",
|
||||||
"y_axis": "xyz",
|
"y_axis": "xyz",
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
},
|
},
|
||||||
"google_imagery": {
|
"google_imagery": {
|
||||||
"directories": [
|
"directories": [
|
||||||
"D:/tiles"
|
"D:/tiles"
|
||||||
],
|
],
|
||||||
"path_layout": "z/x/y",
|
"path_layout": "z/x/y",
|
||||||
"y_axis": "xyz",
|
"y_axis": "xyz",
|
||||||
@@ -68,28 +68,136 @@
|
|||||||
"maximum_level": 10
|
"maximum_level": 10
|
||||||
},
|
},
|
||||||
"terrain": {
|
"terrain": {
|
||||||
|
"file_format": "mbtiles",
|
||||||
"directories": [
|
"directories": [
|
||||||
"D:/ae/tiles/AWS_Terrarium_0-15"
|
],
|
||||||
|
"files": [
|
||||||
|
"C:/tiles/高程 server.arcgisonline.com.mbtiles"
|
||||||
],
|
],
|
||||||
"path_layout": "z/x/y",
|
"path_layout": "z/x/y",
|
||||||
"y_axis": "xyz",
|
"y_axis": "tms",
|
||||||
"projection": "web_mercator",
|
"projection": "web_mercator",
|
||||||
"extension": ".png",
|
"extension": ".png",
|
||||||
"tile_size": 256,
|
"tile_size": 256,
|
||||||
"minimum_level": 0,
|
"minimum_level": 11,
|
||||||
"maximum_level": 10,
|
"maximum_level": 11,
|
||||||
"encoding": "terrarium"
|
"encoding": "terrarium"
|
||||||
}
|
},
|
||||||
|
"tile_sources": [
|
||||||
|
{
|
||||||
|
"key": "mixed_imagery",
|
||||||
|
"name": "本地混合图",
|
||||||
|
"tile_type": "imagery",
|
||||||
|
"resource": {
|
||||||
|
"file_format": "mbtiles",
|
||||||
|
"directories": [
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"C:/tiles/混合图.mbtiles"
|
||||||
|
],
|
||||||
|
"path_layout": "z/x/y",
|
||||||
|
"y_axis": "tms",
|
||||||
|
"projection": "web_mercator",
|
||||||
|
"extension": ".png",
|
||||||
|
"tile_size": 256,
|
||||||
|
"minimum_level": 11,
|
||||||
|
"maximum_level": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "height_terrain",
|
||||||
|
"name": "本地高程",
|
||||||
|
"tile_type": "terrain",
|
||||||
|
"resource": {
|
||||||
|
"file_format": "mbtiles",
|
||||||
|
"directories": [
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"C:/tiles/高程 server.arcgisonline.com.mbtiles",
|
||||||
|
"C:/tiles/中国高程9-10.mbtiles"
|
||||||
|
],
|
||||||
|
"path_layout": "z/x/y",
|
||||||
|
"y_axis": "tms",
|
||||||
|
"projection": "web_mercator",
|
||||||
|
"extension": ".png",
|
||||||
|
"tile_size": 256,
|
||||||
|
"minimum_level": 11,
|
||||||
|
"maximum_level": 11,
|
||||||
|
"encoding": "terrarium"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "imagery",
|
||||||
|
"name": "本地卫星影像",
|
||||||
|
"tile_type": "imagery",
|
||||||
|
"resource": {
|
||||||
|
"file_format": "directory",
|
||||||
|
"directories": [
|
||||||
|
"D:/ae/tiles/Esri_World_Imagery_0-9",
|
||||||
|
"D:/ae/tiles/Esri_World_Imagery_10-12"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
],
|
||||||
|
"path_layout": "z/x/y",
|
||||||
|
"y_axis": "xyz",
|
||||||
|
"projection": "web_mercator",
|
||||||
|
"extension": ".png",
|
||||||
|
"tile_size": 256,
|
||||||
|
"minimum_level": 0,
|
||||||
|
"maximum_level": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "google_imagery",
|
||||||
|
"name": "本地Google瓦片",
|
||||||
|
"tile_type": "imagery",
|
||||||
|
"resource": {
|
||||||
|
"file_format": "directory",
|
||||||
|
"directories": [
|
||||||
|
"D:/tiles"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
],
|
||||||
|
"path_layout": "z/x/y",
|
||||||
|
"y_axis": "xyz",
|
||||||
|
"projection": "web_mercator",
|
||||||
|
"extension": ".png",
|
||||||
|
"tile_size": 256,
|
||||||
|
"minimum_level": 1,
|
||||||
|
"maximum_level": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "osm_online",
|
||||||
|
"name": "OpenStreetMap在线",
|
||||||
|
"tile_type": "imagery",
|
||||||
|
"url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||||
|
"resource": {
|
||||||
|
"file_format": "online",
|
||||||
|
"directories": [
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
],
|
||||||
|
"path_layout": "z/x/y",
|
||||||
|
"y_axis": "xyz",
|
||||||
|
"projection": "web_mercator",
|
||||||
|
"extension": ".png",
|
||||||
|
"tile_size": 256,
|
||||||
|
"minimum_level": 0,
|
||||||
|
"maximum_level": 19
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"map_view": {
|
"map_view": {
|
||||||
"scene_mode": "3d",
|
"scene_mode": "3d",
|
||||||
"map2d": {
|
"map2d": {
|
||||||
"current_imagery_key": "imagery",
|
"current_imagery_key": "mixed_imagery",
|
||||||
"tile_zoom_mode": "native",
|
"tile_zoom_mode": "native",
|
||||||
"tile_display_maximum_level": 8
|
"tile_display_maximum_level": 8
|
||||||
},
|
},
|
||||||
"map3d": {
|
"map3d": {
|
||||||
"current_imagery_key": "imagery",
|
"current_imagery_key": "mixed_imagery",
|
||||||
"tile_zoom_mode": "native",
|
"tile_zoom_mode": "native",
|
||||||
"tile_display_maximum_level": 19
|
"tile_display_maximum_level": 19
|
||||||
},
|
},
|
||||||
@@ -277,20 +385,20 @@
|
|||||||
},
|
},
|
||||||
"device": {
|
"device": {
|
||||||
"net": [
|
"net": [
|
||||||
{
|
{
|
||||||
"key": "device",
|
"key": "device",
|
||||||
"ip": "192.168.1.75",
|
"ip": "192.168.1.75",
|
||||||
"port": 80,
|
"port": 80,
|
||||||
"netmask": "255.255.255.0",
|
"netmask": "255.255.255.0",
|
||||||
"gateway": "192.168.1.1"
|
"gateway": "192.168.1.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "wifi",
|
"key": "wifi",
|
||||||
"ip": "192.168.10.193",
|
"ip": "192.168.10.193",
|
||||||
"port": 80,
|
"port": 80,
|
||||||
"netmask": "255.255.255.0",
|
"netmask": "255.255.255.0",
|
||||||
"gateway": "192.168.10.1"
|
"gateway": "192.168.10.1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mode_acs": {
|
"mode_acs": {
|
||||||
@@ -319,185 +427,185 @@
|
|||||||
"tcp_server_default_connect_user_buffer_size": 409600,
|
"tcp_server_default_connect_user_buffer_size": 409600,
|
||||||
"tcp_server_default_connect_system_buffer_size": 409600,
|
"tcp_server_default_connect_system_buffer_size": 409600,
|
||||||
"list": [
|
"list": [
|
||||||
{
|
{
|
||||||
"type": "Data_Feed_TCP_Server",
|
"type": "Data_Feed_TCP_Server",
|
||||||
"key": "Port_10003",
|
"key": "Port_10003",
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"output_format": {
|
"output_format": {
|
||||||
"type": "BIN",
|
"type": "BIN",
|
||||||
"use_status": true,
|
"use_status": true,
|
||||||
"mode_s_output_type": "ALL_Mode_S",
|
"mode_s_output_type": "ALL_Mode_S",
|
||||||
"use_mode_ac": true,
|
"use_mode_ac": true,
|
||||||
"sbs_only_pos": true
|
"sbs_only_pos": true
|
||||||
},
|
},
|
||||||
"port": 10003,
|
"port": 10003,
|
||||||
"connect_user_buffer_size": 409600,
|
"connect_user_buffer_size": 409600,
|
||||||
"connect_system_buffer_size": 409600
|
"connect_system_buffer_size": 409600
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "Data_Feed_UDP_Server",
|
"type": "Data_Feed_UDP_Server",
|
||||||
"key": "22333",
|
"key": "22333",
|
||||||
"enable": false,
|
"enable": false,
|
||||||
"output_format": {
|
"output_format": {
|
||||||
"type": "BIN",
|
"type": "BIN",
|
||||||
"use_status": false,
|
"use_status": false,
|
||||||
"mode_s_output_type": "ALL_Mode_S",
|
"mode_s_output_type": "ALL_Mode_S",
|
||||||
"use_mode_ac": false,
|
"use_mode_ac": false,
|
||||||
"sbs_only_pos": false
|
"sbs_only_pos": false
|
||||||
},
|
},
|
||||||
"port": 50001
|
"port": 50001
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "Data_Feed_UDP_Client",
|
"type": "Data_Feed_UDP_Client",
|
||||||
"key": "Port_10004",
|
"key": "Port_10004",
|
||||||
"enable": false,
|
"enable": false,
|
||||||
"output_format": {
|
"output_format": {
|
||||||
"type": "AVR",
|
"type": "AVR",
|
||||||
"use_status": false,
|
"use_status": false,
|
||||||
"mode_s_output_type": "DF_11_17_18",
|
"mode_s_output_type": "DF_11_17_18",
|
||||||
"use_mode_ac": false,
|
"use_mode_ac": false,
|
||||||
"sbs_only_pos": true
|
"sbs_only_pos": true
|
||||||
},
|
},
|
||||||
"port": 30004,
|
"port": 30004,
|
||||||
"url": "192.168.1.75"
|
"url": "192.168.1.75"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "Data_Feed_TCP_Client",
|
"type": "Data_Feed_TCP_Client",
|
||||||
"key": "Port_10005",
|
"key": "Port_10005",
|
||||||
"enable": false,
|
"enable": false,
|
||||||
"output_format": {
|
"output_format": {
|
||||||
"type": "BIN",
|
"type": "BIN",
|
||||||
"use_status": true,
|
"use_status": true,
|
||||||
"mode_s_output_type": "NO_POS_Mode_S",
|
"mode_s_output_type": "NO_POS_Mode_S",
|
||||||
"use_mode_ac": true,
|
"use_mode_ac": true,
|
||||||
"sbs_only_pos": true
|
"sbs_only_pos": true
|
||||||
},
|
},
|
||||||
"url": "192.168.1.167",
|
"url": "192.168.1.167",
|
||||||
"port": 10005
|
"port": 10005
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "Data_Feed_TCP_Server",
|
"type": "Data_Feed_TCP_Server",
|
||||||
"key": "Port_30003",
|
"key": "Port_30003",
|
||||||
"enable": false,
|
"enable": false,
|
||||||
"output_format": {
|
"output_format": {
|
||||||
"type": "SBS",
|
"type": "SBS",
|
||||||
"use_status": false,
|
"use_status": false,
|
||||||
"mode_s_output_type": "ALL_Mode_S",
|
"mode_s_output_type": "ALL_Mode_S",
|
||||||
"use_mode_ac": true,
|
"use_mode_ac": true,
|
||||||
"sbs_only_pos": true
|
"sbs_only_pos": true
|
||||||
},
|
},
|
||||||
"port": 30003,
|
"port": 30003,
|
||||||
"connect_user_buffer_size": 0,
|
"connect_user_buffer_size": 0,
|
||||||
"connect_system_buffer_size": 0
|
"connect_system_buffer_size": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"data_source": {
|
"data_source": {
|
||||||
"list": [
|
"list": [
|
||||||
{
|
{
|
||||||
"type": "Dll_Data_Source",
|
"type": "Dll_Data_Source",
|
||||||
"key": "lzy_dll",
|
"key": "lzy_dll",
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"base_station_has_valid_position": true,
|
"base_station_has_valid_position": true,
|
||||||
"map_display": {
|
"map_display": {
|
||||||
"map2d": {
|
"map2d": {
|
||||||
"base_station_show": true,
|
"base_station_show": true,
|
||||||
"aircraft_show": true,
|
"aircraft_show": true,
|
||||||
"constant_screen_size": true,
|
"constant_screen_size": true,
|
||||||
"color": "#454641",
|
"color": "#454641",
|
||||||
"track_point_color": "#ffff00",
|
"track_point_color": "#ffff00",
|
||||||
"base_station_color": "#1677ff",
|
"base_station_color": "#1677ff",
|
||||||
"aircraft_pixel_size": 34,
|
"aircraft_pixel_size": 34,
|
||||||
"base_station_pixel_size": 30,
|
"base_station_pixel_size": 30,
|
||||||
"aircraft_scale": 2.000000,
|
"aircraft_scale": 2.000000,
|
||||||
"base_station_scale": 0.440000,
|
"base_station_scale": 0.440000,
|
||||||
"show_icao": true,
|
"show_icao": true,
|
||||||
"show_call_sign": false,
|
"show_call_sign": false,
|
||||||
"show_fly_status": false
|
"show_fly_status": false
|
||||||
},
|
},
|
||||||
"map3d": {
|
"map3d": {
|
||||||
"base_station_show": true,
|
"base_station_show": true,
|
||||||
"aircraft_show": true,
|
"aircraft_show": true,
|
||||||
"constant_screen_size": true,
|
"constant_screen_size": true,
|
||||||
"color": "#454641",
|
"color": "#454641",
|
||||||
"track_point_color": "#ffff00",
|
"track_point_color": "#ffff00",
|
||||||
"base_station_color": "#1677ff",
|
"base_station_color": "#1677ff",
|
||||||
"aircraft_pixel_size": 50,
|
"aircraft_pixel_size": 50,
|
||||||
"base_station_pixel_size": 100,
|
"base_station_pixel_size": 100,
|
||||||
"aircraft_scale": 1.000000,
|
"aircraft_scale": 1.000000,
|
||||||
"base_station_scale": 0.440000,
|
"base_station_scale": 0.440000,
|
||||||
"show_icao": true,
|
"show_icao": true,
|
||||||
"show_call_sign": false,
|
"show_call_sign": false,
|
||||||
"show_fly_status": false
|
"show_fly_status": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lat": 37.433547,
|
"lat": 37.433547,
|
||||||
"lon": 121.408730,
|
"lon": 121.408730,
|
||||||
"alt": 2300.000000,
|
"alt": 2300.000000,
|
||||||
"ignore_msg_time": true,
|
"ignore_msg_time": true,
|
||||||
"update_form_gps": false,
|
"update_form_gps": false,
|
||||||
"keep_mode": true,
|
"keep_mode": true,
|
||||||
"library_path": "@/dll_source.dll",
|
"library_path": "@/dll_source.dll",
|
||||||
"function_name": "adsb_read",
|
"function_name": "adsb_read",
|
||||||
"data_type": "BIN_Blank_Text",
|
"data_type": "BIN_Blank_Text",
|
||||||
"buffer_size": 200000
|
"buffer_size": 200000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "File_Data_Source",
|
"type": "File_Data_Source",
|
||||||
"key": "aaaa",
|
"key": "aaaa",
|
||||||
"enable": false,
|
"enable": false,
|
||||||
"base_station_has_valid_position": false,
|
"base_station_has_valid_position": false,
|
||||||
"map_display": {
|
"map_display": {
|
||||||
"map2d": {
|
"map2d": {
|
||||||
"base_station_show": true,
|
"base_station_show": true,
|
||||||
"aircraft_show": true,
|
"aircraft_show": true,
|
||||||
"constant_screen_size": true,
|
"constant_screen_size": true,
|
||||||
"color": "#d1a54c",
|
"color": "#d1a54c",
|
||||||
"track_point_color": "#ffff00",
|
"track_point_color": "#ffff00",
|
||||||
"base_station_color": "#1677ff",
|
"base_station_color": "#1677ff",
|
||||||
"aircraft_pixel_size": 50,
|
"aircraft_pixel_size": 50,
|
||||||
"base_station_pixel_size": 100,
|
"base_station_pixel_size": 100,
|
||||||
"aircraft_scale": 2.000000,
|
"aircraft_scale": 2.000000,
|
||||||
"base_station_scale": 0.440000,
|
"base_station_scale": 0.440000,
|
||||||
"show_icao": true,
|
"show_icao": true,
|
||||||
"show_call_sign": true,
|
"show_call_sign": true,
|
||||||
"show_fly_status": true
|
"show_fly_status": true
|
||||||
},
|
},
|
||||||
"map3d": {
|
"map3d": {
|
||||||
"base_station_show": true,
|
"base_station_show": true,
|
||||||
"aircraft_show": true,
|
"aircraft_show": true,
|
||||||
"constant_screen_size": true,
|
"constant_screen_size": true,
|
||||||
"color": "#d1a54c",
|
"color": "#d1a54c",
|
||||||
"track_point_color": "#ffff00",
|
"track_point_color": "#ffff00",
|
||||||
"base_station_color": "#1677ff",
|
"base_station_color": "#1677ff",
|
||||||
"aircraft_pixel_size": 50,
|
"aircraft_pixel_size": 50,
|
||||||
"base_station_pixel_size": 100,
|
"base_station_pixel_size": 100,
|
||||||
"aircraft_scale": 2.000000,
|
"aircraft_scale": 2.000000,
|
||||||
"base_station_scale": 0.440000,
|
"base_station_scale": 0.440000,
|
||||||
"show_icao": true,
|
"show_icao": true,
|
||||||
"show_call_sign": true,
|
"show_call_sign": true,
|
||||||
"show_fly_status": true
|
"show_fly_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lat": 37.000000,
|
"lat": 37.000000,
|
||||||
"lon": 121.000000,
|
"lon": 121.000000,
|
||||||
"alt": 1000.000000,
|
"alt": 1000.000000,
|
||||||
"ignore_msg_time": true,
|
"ignore_msg_time": true,
|
||||||
"update_form_gps": false,
|
"update_form_gps": false,
|
||||||
"keep_mode": true,
|
"keep_mode": true,
|
||||||
"file_path": "D:\\ae\\proj\\projects\\ECAP_Server\\data\\BIN_Blank_Text\\ADS-B_195_0205.txt",
|
"file_path": "D:\\ae\\proj\\projects\\ECAP_Server\\data\\BIN_Blank_Text\\ADS-B_195_0205.txt",
|
||||||
"data_type": "BIN_Blank_Text",
|
"data_type": "BIN_Blank_Text",
|
||||||
"play_mode": "loop"
|
"play_mode": "loop"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"source_feed_relation": {
|
"source_feed_relation": {
|
||||||
"list": [
|
"list": [
|
||||||
{
|
{
|
||||||
"key": "key_10003",
|
"key": "key_10003",
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"type": "First_Source_To_All_Feed_Relation"
|
"type": "First_Source_To_All_Feed_Relation"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -515,83 +623,83 @@
|
|||||||
"external_database": {
|
"external_database": {
|
||||||
"ecap_sqlite_path": "@/../data/sqlite/ecap.sqlite",
|
"ecap_sqlite_path": "@/../data/sqlite/ecap.sqlite",
|
||||||
"list": [
|
"list": [
|
||||||
{
|
{
|
||||||
"name": "tar1090_db_aircraft",
|
"name": "tar1090_db_aircraft",
|
||||||
"download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz",
|
"download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz",
|
||||||
"description": "wiedehopf/tar1090-db 发布的轻量 ICAO24 飞机静态库,提供注册号、机型、年份和运营方备注。",
|
"description": "wiedehopf/tar1090-db 发布的轻量 ICAO24 飞机静态库,提供注册号、机型、年份和运营方备注。",
|
||||||
"update_interval_seconds": 604800,
|
"update_interval_seconds": 604800,
|
||||||
"last_updated_at": 1780388281
|
"last_updated_at": 1780388281
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "wiedehopf_tar1090_db_aircraft",
|
"name": "wiedehopf_tar1090_db_aircraft",
|
||||||
"download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz",
|
"download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz",
|
||||||
"description": "wiedehopf/tar1090-db 项目发布的飞机静态数据库镜像,供 tar1090 本地识别飞机。",
|
"description": "wiedehopf/tar1090-db 项目发布的飞机静态数据库镜像,供 tar1090 本地识别飞机。",
|
||||||
"update_interval_seconds": 604800,
|
"update_interval_seconds": 604800,
|
||||||
"last_updated_at": 1780381804
|
"last_updated_at": 1780381804
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mictronics_aircraft_database",
|
"name": "mictronics_aircraft_database",
|
||||||
"download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz",
|
"download_url": "https://raw.githubusercontent.com/wiedehopf/tar1090-db/refs/heads/csv/aircraft.csv.gz",
|
||||||
"description": "Mictronics 社区飞机数据库的 tar1090 汇总镜像,可按 ICAO24 查询注册号、机型和运营方。",
|
"description": "Mictronics 社区飞机数据库的 tar1090 汇总镜像,可按 ICAO24 查询注册号、机型和运营方。",
|
||||||
"update_interval_seconds": 604800,
|
"update_interval_seconds": 604800,
|
||||||
"last_updated_at": 1780381804
|
"last_updated_at": 1780381804
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "opensky_aircraft_database",
|
"name": "opensky_aircraft_database",
|
||||||
"download_url": "https://opensky-network.org/datasets/metadata/aircraftDatabase.csv",
|
"download_url": "https://opensky-network.org/datasets/metadata/aircraftDatabase.csv",
|
||||||
"description": "OpenSky Network 飞机元数据 CSV,包含制造商、型号、注册号和运营方等字段。",
|
"description": "OpenSky Network 飞机元数据 CSV,包含制造商、型号、注册号和运营方等字段。",
|
||||||
"update_interval_seconds": 604800,
|
"update_interval_seconds": 604800,
|
||||||
"last_updated_at": 1780381804
|
"last_updated_at": 1780381804
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "faa_aircraft_registry",
|
"name": "faa_aircraft_registry",
|
||||||
"download_url": "https://registry.faa.gov/database/ReleasableAircraft.zip",
|
"download_url": "https://registry.faa.gov/database/ReleasableAircraft.zip",
|
||||||
"description": "FAA 官方可公开下载的美国飞机注册库,MASTER.txt 包含 Mode-S Hex 注册信息。",
|
"description": "FAA 官方可公开下载的美国飞机注册库,MASTER.txt 包含 Mode-S Hex 注册信息。",
|
||||||
"update_interval_seconds": 86400,
|
"update_interval_seconds": 86400,
|
||||||
"last_updated_at": 1780381804
|
"last_updated_at": 1780381804
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "icao_doc_8643_aircraft_type_designators",
|
"name": "icao_doc_8643_aircraft_type_designators",
|
||||||
"download_url": "",
|
"download_url": "",
|
||||||
"description": "ICAO Doc 8643 机型代码表,用于将 A320、B738 等 designator 映射为制造商、型号和类别。",
|
"description": "ICAO Doc 8643 机型代码表,用于将 A320、B738 等 designator 映射为制造商、型号和类别。",
|
||||||
"update_interval_seconds": 2419200,
|
"update_interval_seconds": 2419200,
|
||||||
"last_updated_at": 1780381804
|
"last_updated_at": 1780381804
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "vrs_routes",
|
"name": "vrs_routes",
|
||||||
"download_url": "https://vrs-standing-data.adsb.lol/routes.csv",
|
"download_url": "https://vrs-standing-data.adsb.lol/routes.csv",
|
||||||
"description": "VRS 航线表,以 callsign 查询机场代码序列,用于推断起飞机场、经停机场和目的机场。",
|
"description": "VRS 航线表,以 callsign 查询机场代码序列,用于推断起飞机场、经停机场和目的机场。",
|
||||||
"update_interval_seconds": 3600,
|
"update_interval_seconds": 3600,
|
||||||
"last_updated_at": 1780381804
|
"last_updated_at": 1780381804
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "vrs_airports",
|
"name": "vrs_airports",
|
||||||
"download_url": "https://vrs-standing-data.adsb.lol/airports.csv",
|
"download_url": "https://vrs-standing-data.adsb.lol/airports.csv",
|
||||||
"description": "VRS 机场字典,提供机场代码、名称、国家、经纬度和高度。",
|
"description": "VRS 机场字典,提供机场代码、名称、国家、经纬度和高度。",
|
||||||
"update_interval_seconds": 3600,
|
"update_interval_seconds": 3600,
|
||||||
"last_updated_at": 1780381804
|
"last_updated_at": 1780381804
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "adsblol_vrs_standing_data_routes",
|
"name": "adsblol_vrs_standing_data_routes",
|
||||||
"download_url": "https://vrs-standing-data.adsb.lol/routes.csv",
|
"download_url": "https://vrs-standing-data.adsb.lol/routes.csv",
|
||||||
"description": "ADSB.lol 发布的 VRS standing-data 航线镜像,以 callsign 查询机场代码序列。",
|
"description": "ADSB.lol 发布的 VRS standing-data 航线镜像,以 callsign 查询机场代码序列。",
|
||||||
"update_interval_seconds": 3600,
|
"update_interval_seconds": 3600,
|
||||||
"last_updated_at": 1780381804
|
"last_updated_at": 1780381804
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "vradarserver_standing_data_routes",
|
"name": "vradarserver_standing_data_routes",
|
||||||
"download_url": "https://vrs-standing-data.adsb.lol/routes.csv",
|
"download_url": "https://vrs-standing-data.adsb.lol/routes.csv",
|
||||||
"description": "Virtual Radar Server standing-data 的航线适配表;当前在线更新使用 ADSB.lol 公共镜像。",
|
"description": "Virtual Radar Server standing-data 的航线适配表;当前在线更新使用 ADSB.lol 公共镜像。",
|
||||||
"update_interval_seconds": 3600,
|
"update_interval_seconds": 3600,
|
||||||
"last_updated_at": 1780381804
|
"last_updated_at": 1780381804
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "opensky_flightdata_api_cache",
|
"name": "opensky_flightdata_api_cache",
|
||||||
"download_url": "",
|
"download_url": "",
|
||||||
"description": "OpenSky FlightData API 查询缓存,按 ICAO24 和时间范围保存历史航班起降机场结果。",
|
"description": "OpenSky FlightData API 查询缓存,按 ICAO24 和时间范围保存历史航班起降机场结果。",
|
||||||
"update_interval_seconds": 0,
|
"update_interval_seconds": 0,
|
||||||
"last_updated_at": 1780381804
|
"last_updated_at": 1780381804
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"cesium_graphics": {
|
"cesium_graphics": {
|
||||||
|
|||||||
@@ -43,6 +43,24 @@ static std::vector<std::string> read_string_array(const Psc::JSON* json, std::st
|
|||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
static std::vector<std::string> read_optional_string_array(const Psc::JSON* json, std::string_view key) {
|
||||||
|
auto field = json->get(key);
|
||||||
|
if (field == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (field->valueType != Psc::Array) {
|
||||||
|
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key));
|
||||||
|
}
|
||||||
|
std::vector<std::string> ret;
|
||||||
|
ret.reserve(field->children.size());
|
||||||
|
for (const auto& child : field->children) {
|
||||||
|
if (child.valueType != Psc::String) {
|
||||||
|
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key));
|
||||||
|
}
|
||||||
|
ret.emplace_back(child.val);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
static Psc::JSON write_string_array(std::string_view key, const std::vector<std::string>& list) {
|
static Psc::JSON write_string_array(std::string_view key, const std::vector<std::string>& list) {
|
||||||
auto array = Psc::JSON::array();
|
auto array = Psc::JSON::array();
|
||||||
for (const auto& path : list) {
|
for (const auto& path : list) {
|
||||||
@@ -171,7 +189,9 @@ void Map_Tile_Config::from_base_json(const Psc::JSON* that_json) {
|
|||||||
if (that_json == nullptr || that_json->valueType != Psc::Object) {
|
if (that_json == nullptr || that_json->valueType != Psc::Object) {
|
||||||
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map tile config");
|
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map tile config");
|
||||||
}
|
}
|
||||||
directories = read_string_array(that_json, "directories");
|
file_format = read_optional_string_field(that_json, "file_format", "directory");
|
||||||
|
directories = read_optional_string_array(that_json, "directories");
|
||||||
|
files = read_optional_string_array(that_json, "files");
|
||||||
path_layout = read_string_field(that_json, "path_layout");
|
path_layout = read_string_field(that_json, "path_layout");
|
||||||
y_axis = read_string_field(that_json, "y_axis");
|
y_axis = read_string_field(that_json, "y_axis");
|
||||||
projection = read_string_field(that_json, "projection");
|
projection = read_string_field(that_json, "projection");
|
||||||
@@ -183,7 +203,9 @@ void Map_Tile_Config::from_base_json(const Psc::JSON* that_json) {
|
|||||||
}
|
}
|
||||||
Psc::JSON Map_Tile_Config::to_base_json() const {
|
Psc::JSON Map_Tile_Config::to_base_json() const {
|
||||||
auto ret = Psc::JSON::object();
|
auto ret = Psc::JSON::object();
|
||||||
|
ret.append({"file_format", file_format});
|
||||||
ret.append(write_string_array("directories", directories));
|
ret.append(write_string_array("directories", directories));
|
||||||
|
ret.append(write_string_array("files", files));
|
||||||
ret.append({"path_layout", path_layout});
|
ret.append({"path_layout", path_layout});
|
||||||
ret.append({"y_axis", y_axis});
|
ret.append({"y_axis", y_axis});
|
||||||
ret.append({"projection", projection});
|
ret.append({"projection", projection});
|
||||||
@@ -209,6 +231,59 @@ Psc::JSON Map_Tile_Config::to_public_json(std::string_view url) const {
|
|||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
void Map_Tile_Source_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 tile source config");
|
||||||
|
}
|
||||||
|
key = read_string_field(that_json, "key");
|
||||||
|
name = read_string_field(that_json, "name");
|
||||||
|
tile_type = read_optional_string_field(that_json, "tile_type", "imagery");
|
||||||
|
url = read_optional_string_field(that_json, "url");
|
||||||
|
resource.from_base_json(that_json->get("resource"));
|
||||||
|
}
|
||||||
|
Psc::JSON Map_Tile_Source_Config::to_base_json() const {
|
||||||
|
auto ret = Psc::JSON::object();
|
||||||
|
ret.append({"key", key});
|
||||||
|
ret.append({"name", name});
|
||||||
|
ret.append({"tile_type", tile_type});
|
||||||
|
if (!url.empty()) {
|
||||||
|
ret.append({"url", url});
|
||||||
|
}
|
||||||
|
ret.append({"resource", resource.to_base_json()});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
Psc::JSON Map_Tile_Source_Config::to_public_json(std::string_view local_url) const {
|
||||||
|
auto ret = Psc::JSON::object();
|
||||||
|
ret.append({"key", key});
|
||||||
|
ret.append({"name", name});
|
||||||
|
ret.append({"tile_type", tile_type});
|
||||||
|
ret.append({"resource", resource.to_public_json(local() ? local_url : url)});
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
static std::vector<Map_Tile_Source_Config> read_optional_tile_source_array(const Psc::JSON* json, std::string_view key) {
|
||||||
|
auto field = json->get(key);
|
||||||
|
if (field == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (field->valueType != Psc::Array) {
|
||||||
|
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key));
|
||||||
|
}
|
||||||
|
std::vector<Map_Tile_Source_Config> ret;
|
||||||
|
ret.reserve(field->children.size());
|
||||||
|
for (const auto& child : field->children) {
|
||||||
|
Map_Tile_Source_Config item;
|
||||||
|
item.from_base_json(&child);
|
||||||
|
ret.push_back(std::move(item));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
static Psc::JSON write_tile_source_array(std::string_view key, const std::vector<Map_Tile_Source_Config>& list) {
|
||||||
|
auto array = Psc::JSON::array();
|
||||||
|
for (const auto& source : list) {
|
||||||
|
array.append(source.to_base_json());
|
||||||
|
}
|
||||||
|
return Psc::JSON(key, array);
|
||||||
|
}
|
||||||
void Map_Resources_Config::from_base_json(const Psc::JSON* that_json) {
|
void Map_Resources_Config::from_base_json(const Psc::JSON* that_json) {
|
||||||
if (that_json == nullptr || that_json->valueType != Psc::Object) {
|
if (that_json == nullptr || that_json->valueType != Psc::Object) {
|
||||||
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map_resources");
|
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map_resources");
|
||||||
@@ -220,6 +295,7 @@ void Map_Resources_Config::from_base_json(const Psc::JSON* that_json) {
|
|||||||
imagery.from_base_json(that_json->get("imagery"));
|
imagery.from_base_json(that_json->get("imagery"));
|
||||||
google_imagery.from_base_json(that_json->get("google_imagery"));
|
google_imagery.from_base_json(that_json->get("google_imagery"));
|
||||||
terrain.from_base_json(that_json->get("terrain"));
|
terrain.from_base_json(that_json->get("terrain"));
|
||||||
|
tile_sources = read_optional_tile_source_array(that_json, "tile_sources");
|
||||||
if (!is_imagery_key(current_imagery_key)) {
|
if (!is_imagery_key(current_imagery_key)) {
|
||||||
current_imagery_key = "imagery";
|
current_imagery_key = "imagery";
|
||||||
}
|
}
|
||||||
@@ -230,44 +306,40 @@ Psc::JSON Map_Resources_Config::to_base_json() const {
|
|||||||
ret.append({"imagery", imagery.to_base_json()});
|
ret.append({"imagery", imagery.to_base_json()});
|
||||||
ret.append({"google_imagery", google_imagery.to_base_json()});
|
ret.append({"google_imagery", google_imagery.to_base_json()});
|
||||||
ret.append({"terrain", terrain.to_base_json()});
|
ret.append({"terrain", terrain.to_base_json()});
|
||||||
return ret;
|
ret.append(write_tile_source_array("tile_sources", tile_sources));
|
||||||
}
|
|
||||||
static Psc::JSON make_public_imagery_source(std::string_view key, std::string_view name, const Psc::JSON& resource) {
|
|
||||||
auto ret = Psc::JSON::object();
|
|
||||||
ret.append({"key", std::string(key)});
|
|
||||||
ret.append({"name", std::string(name)});
|
|
||||||
ret.append({"resource", resource});
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
static Psc::JSON make_osm_public_resource() {
|
|
||||||
auto ret = Psc::JSON::object();
|
|
||||||
ret.append({"url", "https://tile.openstreetmap.org/{z}/{x}/{y}.png"});
|
|
||||||
ret.append({"minimum_level", 0});
|
|
||||||
ret.append({"maximum_level", 19});
|
|
||||||
ret.append({"tile_size", 256});
|
|
||||||
ret.append({"projection", "web_mercator"});
|
|
||||||
ret.append({"y_axis", "xyz"});
|
|
||||||
ret.append({"credit", "OpenStreetMap contributors"});
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
Psc::JSON Map_Resources_Config::to_public_json() const {
|
Psc::JSON Map_Resources_Config::to_public_json() const {
|
||||||
auto ret = Psc::JSON::object();
|
auto ret = Psc::JSON::object();
|
||||||
auto imagery_json = imagery.to_public_json("/map/imagery/{z}/{x}/{y}");
|
auto imagery_json = imagery.to_public_json("/map/imagery/{z}/{x}/{y}");
|
||||||
auto google_imagery_json = google_imagery.to_public_json("/tiles/{z}/{x}/{y}");
|
auto google_imagery_json = google_imagery.to_public_json("/tiles/{z}/{x}/{y}");
|
||||||
auto osm_json = make_osm_public_resource();
|
|
||||||
auto sources = Psc::JSON::array();
|
auto sources = Psc::JSON::array();
|
||||||
sources.append(make_public_imagery_source("imagery", "本地卫星影像", imagery_json));
|
auto imagery_sources = Psc::JSON::array();
|
||||||
sources.append(make_public_imagery_source("google_imagery", "本地Google瓦片", google_imagery_json));
|
auto terrain_sources = Psc::JSON::array();
|
||||||
sources.append(make_public_imagery_source("osm_online", "OpenStreetMap在线", osm_json));
|
for (const auto& source : tile_sources) {
|
||||||
|
auto url = "/map/tile_sources/" + source.key + "/{z}/{x}/{y}";
|
||||||
|
auto public_source = source.to_public_json(url);
|
||||||
|
sources.append(public_source);
|
||||||
|
if (source.tile_type == "imagery") {
|
||||||
|
imagery_sources.append(public_source);
|
||||||
|
}
|
||||||
|
else if (source.tile_type == "terrain") {
|
||||||
|
terrain_sources.append(public_source);
|
||||||
|
}
|
||||||
|
}
|
||||||
ret.append({"current_imagery_key", current_imagery_key});
|
ret.append({"current_imagery_key", current_imagery_key});
|
||||||
ret.append({"imagery_sources", sources});
|
ret.append({"tile_sources", sources});
|
||||||
|
ret.append({"imagery_sources", imagery_sources});
|
||||||
|
ret.append({"terrain_sources", terrain_sources});
|
||||||
ret.append({"imagery", imagery_json});
|
ret.append({"imagery", imagery_json});
|
||||||
ret.append({"google_imagery", google_imagery_json});
|
ret.append({"google_imagery", google_imagery_json});
|
||||||
ret.append({"terrain", terrain.to_public_json("/map/terrain/{z}/{x}/{y}")});
|
ret.append({"terrain", terrain.to_public_json("/map/terrain/{z}/{x}/{y}")});
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
bool Map_Resources_Config::is_imagery_key(std::string_view key) const {
|
bool Map_Resources_Config::is_imagery_key(std::string_view key) const {
|
||||||
return key == "imagery" || key == "google_imagery" || key == "osm_online";
|
return std::ranges::any_of(tile_sources, [key](const Map_Tile_Source_Config& source) {
|
||||||
|
return source.tile_type == "imagery" && source.key == key;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
void Map_Camera_Config::from_base_json(const Psc::JSON* that_json) {
|
void Map_Camera_Config::from_base_json(const Psc::JSON* that_json) {
|
||||||
if (that_json == nullptr) {
|
if (that_json == nullptr) {
|
||||||
|
|||||||
@@ -112,7 +112,9 @@ struct Web_Server_Config {
|
|||||||
std::string webapp;
|
std::string webapp;
|
||||||
};
|
};
|
||||||
struct Map_Tile_Config {
|
struct Map_Tile_Config {
|
||||||
|
std::string file_format = "directory";
|
||||||
std::vector<std::string> directories;
|
std::vector<std::string> directories;
|
||||||
|
std::vector<std::string> files;
|
||||||
std::string path_layout;
|
std::string path_layout;
|
||||||
std::string y_axis;
|
std::string y_axis;
|
||||||
std::string projection;
|
std::string projection;
|
||||||
@@ -129,15 +131,37 @@ struct Map_Tile_Config {
|
|||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] std::vector<std::string> get_true_files() const {
|
||||||
|
std::vector<std::string> ret;
|
||||||
|
ret.reserve(files.size());
|
||||||
|
for (const auto& path : files) {
|
||||||
|
ret.emplace_back(Psc::get_abs_path(path));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
void from_base_json(const Psc::JSON* that_json);
|
void from_base_json(const Psc::JSON* that_json);
|
||||||
[[nodiscard]] Psc::JSON to_base_json() const;
|
[[nodiscard]] Psc::JSON to_base_json() const;
|
||||||
[[nodiscard]] Psc::JSON to_public_json(std::string_view url) const;
|
[[nodiscard]] Psc::JSON to_public_json(std::string_view url) const;
|
||||||
};
|
};
|
||||||
|
struct Map_Tile_Source_Config {
|
||||||
|
std::string key;
|
||||||
|
std::string name;
|
||||||
|
std::string tile_type = "imagery";
|
||||||
|
std::string url;
|
||||||
|
Map_Tile_Config resource;
|
||||||
|
[[nodiscard]] bool local() const {
|
||||||
|
return resource.file_format != "online";
|
||||||
|
}
|
||||||
|
void from_base_json(const Psc::JSON* that_json);
|
||||||
|
[[nodiscard]] Psc::JSON to_base_json() const;
|
||||||
|
[[nodiscard]] Psc::JSON to_public_json(std::string_view local_url) const;
|
||||||
|
};
|
||||||
struct Map_Resources_Config {
|
struct Map_Resources_Config {
|
||||||
std::string current_imagery_key = "imagery";
|
std::string current_imagery_key = "imagery";
|
||||||
Map_Tile_Config imagery;
|
Map_Tile_Config imagery;
|
||||||
Map_Tile_Config google_imagery;
|
Map_Tile_Config google_imagery;
|
||||||
Map_Tile_Config terrain;
|
Map_Tile_Config terrain;
|
||||||
|
std::vector<Map_Tile_Source_Config> tile_sources;
|
||||||
void from_base_json(const Psc::JSON* that_json);
|
void from_base_json(const Psc::JSON* that_json);
|
||||||
[[nodiscard]] Psc::JSON to_base_json() const;
|
[[nodiscard]] Psc::JSON to_base_json() const;
|
||||||
[[nodiscard]] Psc::JSON to_public_json() const;
|
[[nodiscard]] Psc::JSON to_public_json() const;
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
#include "Global.h"
|
#include "Global.h"
|
||||||
#include "Performance_Monitor.h"
|
#include "Performance_Monitor.h"
|
||||||
|
#include "psc_global_include/codec.h"
|
||||||
|
#include <SQLiteCpp/Database.h>
|
||||||
|
#include <SQLiteCpp/Statement.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <asio/co_spawn.hpp>
|
||||||
|
#include <asio/thread_pool.hpp>
|
||||||
#include <asio/use_awaitable.hpp>
|
#include <asio/use_awaitable.hpp>
|
||||||
#if defined(ASIO_HAS_FILE)
|
#if defined(ASIO_HAS_FILE)
|
||||||
#include <asio/random_access_file.hpp>
|
#include <asio/random_access_file.hpp>
|
||||||
#include <asio/read_at.hpp>
|
#include <asio/read_at.hpp>
|
||||||
#else
|
#else
|
||||||
#include <asio/co_spawn.hpp>
|
|
||||||
#include <asio/thread_pool.hpp>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#endif
|
#endif
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -16,6 +19,8 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -53,6 +58,10 @@ struct Tile_Load_Result {
|
|||||||
std::string content_type;
|
std::string content_type;
|
||||||
File_Cache_Info cache_info;
|
File_Cache_Info cache_info;
|
||||||
};
|
};
|
||||||
|
asio::thread_pool& tile_file_pool() {
|
||||||
|
static asio::thread_pool pool(4);
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
std::string format_http_date(std::time_t time) {
|
std::string format_http_date(std::time_t time) {
|
||||||
std::tm tm{};
|
std::tm tm{};
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -83,6 +92,12 @@ std::optional<File_Cache_Info> get_file_cache_info(const std::filesystem::path&
|
|||||||
result.last_modified = format_http_date(result.modified_time);
|
result.last_modified = format_http_date(result.modified_time);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
File_Cache_Info make_mbtiles_cache_info(const File_Cache_Info& file_info, const Tile_Request& request, std::uint64_t content_size) {
|
||||||
|
auto result = file_info;
|
||||||
|
result.size = content_size;
|
||||||
|
result.etag = std::format("W/\"{:x}-{:x}-{}-{}-{}-{:x}\"", static_cast<std::uint64_t>(result.modified_time), file_info.size, request.z, request.x, request.y, content_size);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
std::string_view trim_http_ows(std::string_view value) {
|
std::string_view trim_http_ows(std::string_view value) {
|
||||||
while (!value.empty() && (value.front() == ' ' || value.front() == '\t')) {
|
while (!value.empty() && (value.front() == ' ' || value.front() == '\t')) {
|
||||||
value.remove_prefix(1);
|
value.remove_prefix(1);
|
||||||
@@ -177,12 +192,9 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::optional<std::filesystem::path> find_file(const Tile_Request& request) const {
|
std::optional<std::filesystem::path> find_file(const Tile_Request& request) const {
|
||||||
if (request.z < config_.minimum_level || request.z > config_.maximum_level) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
auto relative = relative_path(request);
|
auto relative = relative_path(request);
|
||||||
for (const auto& directory : directories_) {
|
for (const auto& directory : directories_) {
|
||||||
auto path = std::filesystem::path(directory) / relative;
|
auto path = Psc::path_from_utf8(directory) / relative;
|
||||||
if (std::filesystem::is_regular_file(path)) {
|
if (std::filesystem::is_regular_file(path)) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
@@ -205,12 +217,78 @@ private:
|
|||||||
Map_Tile_Config config_;
|
Map_Tile_Config config_;
|
||||||
std::vector<std::string> directories_;
|
std::vector<std::string> directories_;
|
||||||
};
|
};
|
||||||
|
class Mbtiles_File {
|
||||||
|
public:
|
||||||
|
explicit Mbtiles_File(std::string path)
|
||||||
|
: path_(std::move(path)), db_(std::make_unique<SQLite::Database>(path_, SQLite::OPEN_READONLY)) {}
|
||||||
|
std::optional<Tile_Load_Result> load(const Tile_Request& request, const Tile_Cache_Request& cache_request, std::string_view content_type) {
|
||||||
|
auto file_info = get_file_cache_info(Psc::path_from_utf8(path_));
|
||||||
|
if (!file_info) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
std::lock_guard lock(mtx_);
|
||||||
|
SQLite::Statement statement(*db_, "SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ? LIMIT 1");
|
||||||
|
statement.bind(1, static_cast<int>(request.z));
|
||||||
|
statement.bind(2, static_cast<int>(request.x));
|
||||||
|
statement.bind(3, static_cast<int>(request.y));
|
||||||
|
if (!statement.executeStep()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
auto column = statement.getColumn(0);
|
||||||
|
auto bytes = column.getBytes();
|
||||||
|
if (bytes < 0 || static_cast<std::uint64_t>(bytes) > kMaxTileFileSize) {
|
||||||
|
throw std::runtime_error("mbtiles tile is too large: " + path_);
|
||||||
|
}
|
||||||
|
auto cache_info = make_mbtiles_cache_info(*file_info, request, static_cast<std::uint64_t>(bytes));
|
||||||
|
if (is_not_modified(cache_request, cache_info)) {
|
||||||
|
return Tile_Load_Result{Tile_Load_Status::Not_Modified, path_, {}, std::string(content_type), std::move(cache_info)};
|
||||||
|
}
|
||||||
|
std::string content;
|
||||||
|
if (cache_request.include_body) {
|
||||||
|
auto data = static_cast<const char*>(column.getBlob());
|
||||||
|
content.assign(data, static_cast<std::size_t>(bytes));
|
||||||
|
}
|
||||||
|
return Tile_Load_Result{Tile_Load_Status::Ready, path_, std::move(content), std::string(content_type), std::move(cache_info)};
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::string path_;
|
||||||
|
std::unique_ptr<SQLite::Database> db_;
|
||||||
|
std::mutex mtx_;
|
||||||
|
};
|
||||||
class Tile_Source {
|
class Tile_Source {
|
||||||
public:
|
public:
|
||||||
explicit Tile_Source(Map_Tile_Config config)
|
explicit Tile_Source(Map_Tile_Config config)
|
||||||
: resolver_(config) {}
|
: config_(std::move(config)) {
|
||||||
|
if (config_.extension.empty() || config_.extension.front() != '.') {
|
||||||
|
throw std::invalid_argument("invalid tile extension: " + config_.extension);
|
||||||
|
}
|
||||||
|
if (config_.file_format == "directory") {
|
||||||
|
resolver_ = std::make_unique<Tile_Path_Resolver>(config_);
|
||||||
|
}
|
||||||
|
else if (config_.file_format == "mbtiles") {
|
||||||
|
for (auto& file : config_.get_true_files()) {
|
||||||
|
mbtiles_files_.push_back(std::make_unique<Mbtiles_File>(std::move(file)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::invalid_argument("invalid tile file_format: " + config_.file_format);
|
||||||
|
}
|
||||||
|
}
|
||||||
asio::awaitable<Tile_Load_Result> load(const Tile_Request& request, const Tile_Cache_Request& cache_request) const {
|
asio::awaitable<Tile_Load_Result> load(const Tile_Request& request, const Tile_Cache_Request& cache_request) const {
|
||||||
auto path = resolver_.find_file(request);
|
if (request.z < config_.minimum_level || request.z > config_.maximum_level) {
|
||||||
|
co_return Tile_Load_Result{Tile_Load_Status::Not_Found};
|
||||||
|
}
|
||||||
|
if (config_.file_format == "mbtiles") {
|
||||||
|
co_return co_await load_mbtiles_tile(request, cache_request);
|
||||||
|
}
|
||||||
|
co_return co_await load_directory_tile(request, cache_request);
|
||||||
|
}
|
||||||
|
const Map_Tile_Config& config() const {
|
||||||
|
return config_;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
asio::awaitable<Tile_Load_Result> load_directory_tile(const Tile_Request& request, const Tile_Cache_Request& cache_request) const {
|
||||||
|
auto path = resolver_->find_file(request);
|
||||||
if (!path) {
|
if (!path) {
|
||||||
co_return Tile_Load_Result{Tile_Load_Status::Not_Found};
|
co_return Tile_Load_Result{Tile_Load_Status::Not_Found};
|
||||||
}
|
}
|
||||||
@@ -241,16 +319,22 @@ public:
|
|||||||
}
|
}
|
||||||
co_return Tile_Load_Result{Tile_Load_Status::Ready, std::move(generic_path), std::move(content), std::move(content_type), *cache_info};
|
co_return Tile_Load_Result{Tile_Load_Status::Ready, std::move(generic_path), std::move(content), std::move(content_type), *cache_info};
|
||||||
}
|
}
|
||||||
const Map_Tile_Config& config() const {
|
asio::awaitable<Tile_Load_Result> load_mbtiles_tile(Tile_Request request, Tile_Cache_Request cache_request) const {
|
||||||
return resolver_.config();
|
auto content_type = httplib::detail::find_content_type("tile" + config_.extension, {}, "application/octet-stream");
|
||||||
|
co_return co_await asio::co_spawn(tile_file_pool(), [this, request, cache_request = std::move(cache_request), content_type = std::move(content_type)]() -> asio::awaitable<Tile_Load_Result> {
|
||||||
|
for (const auto& file : mbtiles_files_) {
|
||||||
|
auto result = file->load(request, cache_request, content_type);
|
||||||
|
if (result) {
|
||||||
|
co_return std::move(*result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
co_return Tile_Load_Result{Tile_Load_Status::Not_Found};
|
||||||
|
}, asio::use_awaitable);
|
||||||
}
|
}
|
||||||
private:
|
Map_Tile_Config config_;
|
||||||
Tile_Path_Resolver resolver_;
|
std::unique_ptr<Tile_Path_Resolver> resolver_;
|
||||||
|
std::vector<std::unique_ptr<Mbtiles_File>> mbtiles_files_;
|
||||||
#if !defined(ASIO_HAS_FILE)
|
#if !defined(ASIO_HAS_FILE)
|
||||||
static asio::thread_pool& tile_file_pool() {
|
|
||||||
static asio::thread_pool pool(4);
|
|
||||||
return pool;
|
|
||||||
}
|
|
||||||
static asio::awaitable<std::string> read_file_fallback(std::filesystem::path path, std::size_t size) {
|
static asio::awaitable<std::string> read_file_fallback(std::filesystem::path path, std::size_t size) {
|
||||||
co_return co_await asio::co_spawn(tile_file_pool(), [path = std::move(path), size]() -> asio::awaitable<std::string> {
|
co_return co_await asio::co_spawn(tile_file_pool(), [path = std::move(path), size]() -> asio::awaitable<std::string> {
|
||||||
std::ifstream file(path, std::ios::binary);
|
std::ifstream file(path, std::ios::binary);
|
||||||
@@ -355,6 +439,15 @@ void register_tile_handler(std::string_view regex, std::string_view prefix, std:
|
|||||||
},
|
},
|
||||||
{drogon::Get, drogon::Head});
|
{drogon::Get, drogon::Head});
|
||||||
}
|
}
|
||||||
|
void register_tile_source_handler(const Map_Tile_Source_Config& config) {
|
||||||
|
if (!config.local()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto source = std::make_shared<Tile_Source>(config.resource);
|
||||||
|
auto prefix = "/map/tile_sources/" + config.key + "/";
|
||||||
|
auto regex = "^" + prefix + ".*$";
|
||||||
|
register_tile_handler(regex, prefix, source);
|
||||||
|
}
|
||||||
void register_map_resources_handler() {
|
void register_map_resources_handler() {
|
||||||
drogon::app().registerHandlerViaRegex(
|
drogon::app().registerHandlerViaRegex(
|
||||||
R"(^/map/resources$)",
|
R"(^/map/resources$)",
|
||||||
@@ -626,6 +719,9 @@ void Global::init_tiles() {
|
|||||||
register_map_graphics_handler();
|
register_map_graphics_handler();
|
||||||
register_map_models_handler();
|
register_map_models_handler();
|
||||||
register_map_view_handler();
|
register_map_view_handler();
|
||||||
|
for (const auto& source : map_resources_config.tile_sources) {
|
||||||
|
register_tile_source_handler(source);
|
||||||
|
}
|
||||||
register_tile_handler(R"(^/map/imagery/.*$)", "/map/imagery/", imagery);
|
register_tile_handler(R"(^/map/imagery/.*$)", "/map/imagery/", imagery);
|
||||||
register_tile_handler(R"(^/tiles/.*$)", "/tiles/", google_imagery);
|
register_tile_handler(R"(^/tiles/.*$)", "/tiles/", google_imagery);
|
||||||
register_tile_handler(R"(^/map/terrain/.*$)", "/map/terrain/", terrain);
|
register_tile_handler(R"(^/map/terrain/.*$)", "/map/terrain/", terrain);
|
||||||
|
|||||||
Reference in New Issue
Block a user