#include "Config.h" #include "Config_Default.h" #include #include #include #include #include #include #include #include #include "Global.h" #include "Psc_Cpp_Core/Base/Check.h" std::string default_config_directory; namespace { std::filesystem::path config_directory() { if (!default_config_directory.empty()) { return default_config_directory; } return std::filesystem::path(get_exe_dir()) / "config"; } std::filesystem::path config_section_path(Config_Section section) { return config_directory() / (std::string(config_section_name(section)) + ".json"); } std::array(Config_Section::count)> config_section_mutexes; void write_config_section_unlocked(Config_Section section, const Psc::JSON& value) { const auto directory = config_directory(); std::filesystem::create_directories(directory); const auto path = config_section_path(section); auto temporary = path; temporary += ".tmp"; std::ofstream output(temporary, std::ios::binary | std::ios::trunc); if (!output) { throw std::runtime_error("cannot open config section: " + temporary.string()); } const auto json = value.to_json_string(); output.write(json.data(), static_cast(json.size())); output.close(); if (output.fail()) { throw std::runtime_error("cannot write config section: " + temporary.string()); } std::error_code error; std::filesystem::remove(path, error); std::filesystem::rename(temporary, path); } void write_config_section(Config_Section section, const Psc::JSON& value) { std::lock_guard lock(config_section_mutexes[static_cast(section)]); write_config_section_unlocked(section, value); } Psc::JSON read_existing_config_section_unlocked(Config_Section section) { const auto path = config_section_path(section); std::ifstream input(path, std::ios::binary); if (!input) { throw std::runtime_error("cannot open config section: " + path.string()); } std::ostringstream buffer; buffer << input.rdbuf(); return Psc::parse_json(buffer.str()); } void merge_json_object(Psc::JSON& target, const Psc::JSON& value) { for (const auto& child : value.children) { auto iterator = std::find_if(target.children.begin(), target.children.end(), [&](const Psc::JSON& current) { return current.key == child.key; }); if (iterator == target.children.end()) { target.append(child); continue; } if (iterator->valueType == Psc::Object && child.valueType == Psc::Object) merge_json_object(*iterator, child); else *iterator = child; } } Psc::JSON read_config_section(Config_Section section, const Psc::JSON& defaults) { const auto path = config_section_path(section); if (!std::filesystem::exists(path)) { const auto name = config_section_name(section); const auto value = defaults.get(name); if (value == nullptr) { throw std::runtime_error("default config section does not exist: " + std::string(name)); } write_config_section(section, *value); return *value; } std::ifstream input(path, std::ios::binary); if (!input) { throw std::runtime_error("cannot open config section: " + path.string()); } std::ostringstream buffer; buffer << input.rdbuf(); return Psc::parse_json(buffer.str()); } Psc::JSON config_section_value(Global& global, Config_Section section) { switch (section) { case Config_Section::version: return Psc::JSON(global.version); case Config_Section::dsp: return global.dsp_config.to_base_json(); case Config_Section::drogon: return global.drogon_config; case Config_Section::http_monitor: return global.http_monitor_config.to_base_json(); case Config_Section::mlat: return global.mlat.to_base_json(); case Config_Section::web_server: return global.web_server_config.to_base_json(); case Config_Section::map_resources: return global.map_resources_config.read([](const auto& value) { return value.to_base_json(); }); case Config_Section::map_view: return global.map_view_config.read([](const auto& value) { return value.to_base_json(); }); case Config_Section::map_models: return global.map_model_config.read([](const auto& value) { return value.to_base_json(); }); case Config_Section::mode_acs: return global.mode_acs.to_base_json(); case Config_Section::log: return global.log_config.to_base_json(); case Config_Section::console: return global.console_config.to_base_json(); case Config_Section::external_database: return global.external_resources_manager.to_base_json(); case Config_Section::cesium_graphics: return global.cesium_graphics_config.to_base_json(); case Config_Section::count: break; } std::terminate(); } std::string optional_string(const Psc::JSON& value, std::string_view key) { const auto* field = value.get(key); return field != nullptr && field->valueType == Psc::String ? field->val : std::string{}; } bool optional_enabled(const Psc::JSON& value) { const auto* field = value.get("enable"); return field != nullptr && field->valueType == Psc::Bool && field->bool_val(); } void migrate_legacy_source_feed_relations(const Psc::JSON* mode_acs, Data_Source_Config& sources, Data_feed_Config& feeds) { const auto* legacy = mode_acs == nullptr ? nullptr : mode_acs->get("source_feed_relation"); const auto* list = legacy == nullptr ? nullptr : legacy->get("list"); if (list == nullptr || list->valueType != Psc::Array) return; std::map assignments; const auto feed_list = feeds.map.list(); for (const auto& relation : list->children) { if (!optional_enabled(relation) || optional_string(relation, "type") != "One_to_One_Relation") continue; const auto source_key = optional_string(relation, "source_key"); const auto feed_key = optional_string(relation, "feed_key"); if (sources.map.get(source_key) && feeds.map.get(feed_key) && !assignments.contains(feed_key)) assignments.emplace(feed_key, source_key); } for (const auto& relation : list->children) { if (!optional_enabled(relation) || optional_string(relation, "type") != "Name_One_To_Many_Relation") continue; const auto source_key = optional_string(relation, "source_key"); if (!sources.map.get(source_key)) continue; const auto configured_prefix = optional_string(relation, "feed_name"); const auto& prefix = configured_prefix.empty() ? source_key : configured_prefix; for (const auto& feed : feed_list) { if (!assignments.contains(feed->key) && feed->key.starts_with(prefix)) assignments.emplace(feed->key, source_key); } } const auto default_relation = std::find_if(list->children.begin(), list->children.end(), [](const auto& relation) { return optional_enabled(relation) && optional_string(relation, "type") == "First_Source_To_All_Feed_Relation"; }); if (default_relation != list->children.end()) { const auto source_list = sources.map.list(); const auto first_source = std::find_if(source_list.begin(), source_list.end(), [](const auto& source) { return source->enabled(); }); if (first_source != source_list.end()) { for (const auto& feed : feed_list) { if (!assignments.contains(feed->key)) assignments.emplace(feed->key, (*first_source)->key); } } } for (const auto& feed : feed_list) { const auto assignment = assignments.find(feed->key); if (feed->source_key.empty() && assignment != assignments.end()) feed->source_key = assignment->second; } } } void Data_Topology_View_Config::from_json(const Psc::JSON* that_json) { if (that_json == nullptr || that_json->valueType != Psc::Object) return; if (const auto* value = that_json->get("initialized"); value != nullptr && value->valueType == Psc::Bool) initialized = value->bool_val(); if (const auto value = that_json->try_get_number("layout_revision"); value.has_value()) layout_revision = value.value(); if (const auto value = that_json->try_get_number("zoom"); value.has_value() && std::isfinite(value.value()) && value.value() > 0.0) zoom = value.value(); if (const auto value = that_json->try_get_number("pan_x"); value.has_value() && std::isfinite(value.value())) pan_x = value.value(); if (const auto value = that_json->try_get_number("pan_y"); value.has_value() && std::isfinite(value.value())) pan_y = value.value(); const auto* positions = that_json->get("node_positions"); if (positions == nullptr || positions->valueType != Psc::Object) return; node_positions.clear(); for (const auto& item : positions->children) { if (item.valueType != Psc::Object) continue; const auto x = item.try_get_number("x"); const auto y = item.try_get_number("y"); if (x.has_value() && y.has_value() && std::isfinite(x.value()) && std::isfinite(y.value())) node_positions.emplace(item.key, Data_Topology_Node_Position{x.value(), y.value()}); } } Psc::JSON Data_Topology_View_Config::to_json() const { auto positions = Psc::JSON::object(); for (const auto& [id, position] : node_positions) positions.append({id, Psc::JSON::object({{"x", position.x}, {"y", position.y}})}); return Psc::JSON::object({{"initialized", initialized}, {"layout_revision", layout_revision}, {"zoom", zoom}, {"pan_x", pan_x}, {"pan_y", pan_y}, {"node_positions", positions}}); } Psc::JSON Mode_ACS_Config::to_base_json() const { auto result = settings.read([](const auto& value) { return value.to_base_json(); }); result.append(Psc::JSON("data_feed", data_feed_config.to_json())); result.append(Psc::JSON("data_source", data_source_config.to_json())); result.append(Psc::JSON("data_topology_view", data_topology_view.read([](const auto& value) { return value.to_json(); }))); return result; } void Mode_ACS_Config::from_json(const Psc::JSON* that_json, bool not_exist_use_default_value) { settings.write([&](auto& value) { value.from_base_json(that_json, not_exist_use_default_value); SSR::Monitor_Mode_ACS_Message_Num = value.monitor_msg_live; }); data_source_config.init(that_json->get("data_source"), not_exist_use_default_value); data_feed_config.init(that_json->get("data_feed"), not_exist_use_default_value); migrate_legacy_source_feed_relations(that_json, data_source_config, data_feed_config); data_topology_view.write([&](auto& value) { value.from_json(that_json->get("data_topology_view")); }); } Psc::JSON Config::load() { std::filesystem::create_directories(config_directory()); std::error_code ignored_error; std::filesystem::remove(config_directory() / "device.json", ignored_error); const auto defaults = Psc::parse_json(std::string_view(reinterpret_cast(ecap_default_config_json), sizeof(ecap_default_config_json))); auto result = Psc::JSON::object(); for (std::size_t index = 0; index < static_cast(Config_Section::count); ++index) { const auto section = static_cast(index); result.append({std::string(config_section_name(section)), read_config_section(section, defaults)}); } std::cout << get_current_date_string() << " 启动加载配置目录: 【" << config_directory().string() << "】" << std::endl; return result; } Psc::JSON Web_Server_Config::to_base_json() const { return Psc::JSON::object({{"webapp", webapp}, {"listen_port", listen_port}}); } void Web_Server_Config::from_base_json(const Psc::JSON* that_json, bool not_exist_use_default_value) { std::error_code error; auto result = Psc::assign_field_ec(webapp, that_json, "webapp", error); if(!not_exist_use_default_value) Psc::check_json_assign_field(result, error, that_json, "webapp"); if(that_json != nullptr && that_json->get("listen_port") != nullptr) { error.clear(); result = Psc::assign_field_ec(listen_port, that_json, "listen_port", error); Psc::check_json_assign_field(result, error, that_json, "listen_port"); } } static std::vector 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 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& list) { auto array = Psc::JSON::array(); for (const auto& path : list) { array.append(Psc::JSON(path)); } return Psc::JSON(key, array); } static const std::pair 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, bool not_exist_use_default_value) { 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, not_exist_use_default_value); } 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_optional_string_field(const Psc::JSON* json, std::string_view key) { auto field = json->get(key); if (field == nullptr) { return {}; } if (field->valueType != Psc::String) { throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key)); } return field->val; } static std::string read_optional_string_field(const Psc::JSON* json, std::string_view key, std::string_view fallback) { auto field = json->get(key); if (field == nullptr) { return std::string(fallback); } if (field->valueType != Psc::String) { throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key)); } return field->val; } static std::uint32_t read_optional_uint32_field(const Psc::JSON* json, std::string_view key, std::uint32_t fallback) { auto field = json->get(key); if (field == nullptr) { return fallback; } if (field->valueType != Psc::Number) { throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key)); } return field->number_val(); } static double read_optional_double_field(const Psc::JSON* json, std::string_view key, double fallback) { auto field = json->get(key); if (field == nullptr) { return fallback; } if (field->valueType != Psc::Number) { throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key)); } return field->number_val(); } void Map_Tile_Config::from_base_json(const Psc::JSON* that_json, bool not_exist_use_default_value) { 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"); } Map_Tile_Config_Data::from_base_json(that_json, not_exist_use_default_value); directories = read_optional_string_array(that_json, "directories"); files = read_optional_string_array(that_json, "files"); encoding = read_optional_string_field(that_json, "encoding"); } Psc::JSON Map_Tile_Config::to_base_json() const { auto ret = Map_Tile_Config_Data::to_base_json(); ret.append(write_string_array("directories", directories)); ret.append(write_string_array("files", files)); if (!encoding.empty()) { ret.append({"encoding", encoding}); } return ret; } Psc::JSON Map_Tile_Config::to_public_json(std::string_view url) const { auto ret = Psc::JSON::object(); ret.append({"url", std::string(url)}); ret.append({"minimum_level", minimum_level}); ret.append({"maximum_level", maximum_level}); ret.append({"tile_size", tile_size}); ret.append({"projection", Psc::to_string(projection)}); ret.append({"y_axis", Psc::to_string(y_axis)}); if (!encoding.empty()) { ret.append({"encoding", encoding}); } return ret; } void Map_Tile_Source_Config::from_base_json(const Psc::JSON* that_json, bool not_exist_use_default_value) { 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"); } Map_Tile_Source_Config_Data::from_base_json(that_json, not_exist_use_default_value); url = read_optional_string_field(that_json, "url"); resource.from_base_json(that_json->get("resource"), not_exist_use_default_value); } Psc::JSON Map_Tile_Source_Config::to_base_json() const { auto ret = Map_Tile_Source_Config_Data::to_base_json(); 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", Psc::to_string(tile_type)}); ret.append({"request_method", Psc::to_string(request_method)}); ret.append({"resource", resource.to_public_json(local() ? local_url : url)}); return ret; } static std::vector read_optional_tile_source_array(const Psc::JSON* json, std::string_view key, bool not_exist_use_default_value) { 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 ret; ret.reserve(field->children.size()); for (const auto& child : field->children) { Map_Tile_Source_Config item; item.from_base_json(&child, not_exist_use_default_value); ret.push_back(std::move(item)); } return ret; } static Psc::JSON write_tile_source_array(std::string_view key, const std::vector& 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, bool not_exist_use_default_value) { if (that_json == nullptr || that_json->valueType != Psc::Object) { throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map_resources"); } current_imagery_key = read_optional_string_field(that_json, "current_imagery_key"); if (current_imagery_key.empty()) { current_imagery_key = "imagery"; } tile_sources = read_optional_tile_source_array(that_json, "tile_sources", not_exist_use_default_value); if (!is_imagery_key(current_imagery_key)) { auto source = std::ranges::find_if( tile_sources, [](const Map_Tile_Source_Config& item) { return item.tile_type == Map_Tile_Type::imagery; }); current_imagery_key = source == tile_sources.end() ? "" : source->key; } } Psc::JSON Map_Resources_Config::to_base_json() const { auto ret = Psc::JSON::object(); ret.append({"current_imagery_key", current_imagery_key}); ret.append(write_tile_source_array("tile_sources", tile_sources)); return ret; } Psc::JSON Map_Resources_Config::to_public_json() const { auto ret = Psc::JSON::object(); auto sources = Psc::JSON::array(); auto imagery_sources = Psc::JSON::array(); auto terrain_sources = Psc::JSON::array(); auto terrain = Psc::JSON::object(); bool has_terrain = false; 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 == Map_Tile_Type::imagery) { imagery_sources.append(public_source); } else if (source.tile_type == Map_Tile_Type::terrain) { terrain_sources.append(public_source); if (!has_terrain) { terrain = *public_source.get("resource"); has_terrain = true; } } } ret.append({"current_imagery_key", current_imagery_key}); ret.append({"tile_sources", sources}); ret.append({"imagery_sources", imagery_sources}); ret.append({"terrain_sources", terrain_sources}); if (has_terrain) { ret.append({"terrain", std::move(terrain)}); } return ret; } bool Map_Resources_Config::is_imagery_key(std::string_view key) const { return std::ranges::any_of(tile_sources, [key](const Map_Tile_Source_Config& source) { return source.tile_type == Map_Tile_Type::imagery && source.key == key; }); } bool Map_Resources_Config::is_terrain_key(std::string_view key) const { return std::ranges::any_of(tile_sources, [key](const Map_Tile_Source_Config& source) { return source.tile_type == Map_Tile_Type::terrain && source.key == key; }); } bool Map_View_Config::is_scene_mode(std::string_view mode) { return mode == "2d" || mode == "2.5d" || mode == "3d"; } void Map_Tile_View_Config::from_base_json(const Psc::JSON* that_json, bool not_exist_use_default_value) { 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); current_terrain_key = read_optional_string_field(that_json, "current_terrain_key", current_terrain_key); tile_zoom_mode = Psc::to_enum( read_optional_string_field(that_json, "tile_zoom_mode", Psc::to_string(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({"current_terrain_key", current_terrain_key}); ret.append({"tile_zoom_mode", Psc::to_string(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, bool not_exist_use_default_value) { 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_view"); } auto next_scene_mode = read_optional_string_field(that_json, "scene_mode", scene_mode); if (!is_scene_mode(next_scene_mode)) { 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"), not_exist_use_default_value); map3d.from_base_json(that_json->get("map3d"), not_exist_use_default_value); camera.from_base_json(that_json->get("camera"), not_exist_use_default_value); base_station_fly_to_height_offset_meters = std::max(100.0, read_optional_double_field(that_json, "base_station_fly_to_height_offset_meters", base_station_fly_to_height_offset_meters)); } 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()}); ret.append({"base_station_fly_to_height_offset_meters", base_station_fly_to_height_offset_meters}); return ret; } void Cesium_Graphics_Config::from_base_json(const Psc::JSON* that_json, bool not_exist_use_default_value) { if (that_json == nullptr) return; if (that_json->valueType != Psc::Object) throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "cesium_graphics"); settings.write([&](auto& value) { value.from_base_json(that_json, not_exist_use_default_value); value.target_frame_rate = std::clamp(value.target_frame_rate, 1u, 120u); value.resolution_scale = std::clamp(value.resolution_scale, 0.2, 1.5); value.msaa_samples = std::clamp(value.msaa_samples, 1u, 8u); value.maximum_screen_space_error = std::clamp(value.maximum_screen_space_error, 1u, 16u); value.terrain_maximum_level = std::clamp(value.terrain_maximum_level, 0u, 24u); value.terrain_cache_tiles = std::clamp(value.terrain_cache_tiles, 16u, 2048u); value.performance_panel_x = std::max(0.0, value.performance_panel_x); value.performance_panel_y = std::max(0.0, value.performance_panel_y); value.view_axes_panel_y = std::max(0.0, value.view_axes_panel_y); value.view_axes_panel_size = std::clamp(value.view_axes_panel_size, 48.0, 240.0); value.occlusion_sample_spacing_meters = std::clamp(value.occlusion_sample_spacing_meters, 10.0, 5000.0); }); } Psc::JSON Cesium_Graphics_Config::to_base_json() const { return settings.read([](const auto& value) { return value.to_base_json(); }); } 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, bool not_exist_use_default_value) { 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, bool not_exist_use_default_value) { 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"), not_exist_use_default_value); aircraft_models = read_optional_model_object_field(that_json, "aircraft_models", make_default_aircraft_models(), not_exist_use_default_value); base_station_model.from_base_json(that_json->get("base_station_model"), not_exist_use_default_value); device_model.from_base_json(that_json->get("device_model"), not_exist_use_default_value); } 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()}); ret.append({"device_model", device_model.to_base_json()}); return ret; } void Mode_ACS_Config::server(Global* g) { data_source_config.server(g); data_feed_config.server(g); } void Config::save() { auto& global = *Global::instance(); for (std::size_t index = 0; index < static_cast(Config_Section::count); ++index) { const auto section = static_cast(index); write_config_section(section, config_section_value(global, section)); } } void Config::save(Config_Section section) { auto& global = *Global::instance(); write_config_section(section, config_section_value(global, section)); } void Config::save(Config_Section section, const Psc::JSON& value) { write_config_section(section, value); } void Config::merge(Config_Section section, const Psc::JSON& value) { std::lock_guard lock(config_section_mutexes[static_cast(section)]); auto current = read_existing_config_section_unlocked(section); if (current.valueType != Psc::Object || value.valueType != Psc::Object) throw std::invalid_argument("config merge requires object"); merge_json_object(current, value); write_config_section_unlocked(section, current); }