683 lines
33 KiB
C++
683 lines
33 KiB
C++
#include "Config.h"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <exception>
|
|
#include <filesystem>
|
|
#include <mutex>
|
|
#include <stdexcept>
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include "Config_Default/Config_Default.h"
|
|
#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<std::mutex, static_cast<std::size_t>(Config_Section::count)> config_section_mutexes;
|
|
void write_config_file_unlocked(const std::filesystem::path& path, const Psc::JSON& value) {
|
|
std::filesystem::create_directories(path.parent_path());
|
|
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<std::streamsize>(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_unlocked(Config_Section section, const Psc::JSON& value) {
|
|
write_config_file_unlocked(config_section_path(section), value);
|
|
}
|
|
void write_config_section(Config_Section section, const Psc::JSON& value) {
|
|
std::lock_guard lock(config_section_mutexes[static_cast<std::size_t>(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;
|
|
}
|
|
}
|
|
[[noreturn]] void config_load_failed(Config_Section section, std::string_view reason) {
|
|
std::cerr << "配置文件加载失败: 【" << config_section_path(section).string() << "】 配置: 【"
|
|
<< config_section_name(section) << "】 原因: " << reason << std::endl;
|
|
std::terminate();
|
|
}
|
|
Psc::JSON read_config_section(Config_Section section) {
|
|
try {
|
|
return read_existing_config_section_unlocked(section);
|
|
}
|
|
catch (const std::exception& error) {
|
|
config_load_failed(section, error.what());
|
|
}
|
|
}
|
|
template <typename Function>
|
|
void assign_config_section(Config_Section section, Function&& function) {
|
|
try {
|
|
function();
|
|
}
|
|
catch (const std::exception& error) {
|
|
config_load_failed(section, error.what());
|
|
}
|
|
}
|
|
Psc::JSON config_section_value(Global& global, Config_Section section) {
|
|
switch (section) {
|
|
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<std::string, std::string> 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;
|
|
}
|
|
}
|
|
} // namespace
|
|
void Data_Topology_View_Config::from_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), "data_topology_view");
|
|
Get_J(initialized) Get_J(layout_revision) Get_J(zoom) Get_J(pan_x) Get_J(
|
|
pan_y) if (!std::isfinite(zoom) || zoom <= 0.0 || !std::isfinite(pan_x) ||
|
|
!std::isfinite(pan_y)) throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument),
|
|
"data_topology_view");
|
|
const auto* positions = that_json->get("node_positions");
|
|
if (positions == nullptr || positions->valueType != Psc::Object)
|
|
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "node_positions");
|
|
node_positions.clear();
|
|
for (const auto& item : positions->children) {
|
|
if (item.valueType != Psc::Object)
|
|
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), item.key);
|
|
const auto x = item.try_get_number<double>("x");
|
|
const auto y = item.try_get_number<double>("y");
|
|
if (!x.has_value() || !y.has_value() || !std::isfinite(x.value()) || !std::isfinite(y.value()))
|
|
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), item.key);
|
|
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) {
|
|
settings.write([&](auto& value) {
|
|
value.from_base_json(that_json);
|
|
SSR::Monitor_Mode_ACS_Message_Num = value.monitor_msg_live;
|
|
});
|
|
data_source_config.init(that_json->get("data_source"));
|
|
data_feed_config.init(that_json->get("data_feed"));
|
|
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")); });
|
|
}
|
|
Config::Config() : version(get_ecap_build_version()) {
|
|
}
|
|
void Config::create_missing_config_files() {
|
|
std::filesystem::create_directories(config_directory());
|
|
std::error_code ignored_error;
|
|
std::filesystem::remove(config_directory() / "device.json", ignored_error);
|
|
std::filesystem::remove(config_directory() / "version.json", ignored_error);
|
|
for (std::size_t index = 0; index < static_cast<std::size_t>(Config_Section::count); ++index) {
|
|
const auto section = static_cast<Config_Section>(index);
|
|
if (std::filesystem::exists(config_section_path(section)))
|
|
continue;
|
|
const auto file = make_default_config_file(section, config_directory());
|
|
try {
|
|
std::lock_guard lock(config_section_mutexes[static_cast<std::size_t>(file.section)]);
|
|
write_config_file_unlocked(file.path, file.value);
|
|
}
|
|
catch (const std::exception& error) {
|
|
config_load_failed(section, error.what());
|
|
}
|
|
std::cout << "生成默认配置文件: 【" << file.path.string() << "】" << std::endl;
|
|
}
|
|
}
|
|
Psc::JSON Config::load() {
|
|
create_missing_config_files();
|
|
auto result = Psc::JSON::object();
|
|
for (std::size_t index = 0; index < static_cast<std::size_t>(Config_Section::count); ++index) {
|
|
const auto section = static_cast<Config_Section>(index);
|
|
result.append({std::string(config_section_name(section)), read_config_section(section)});
|
|
}
|
|
std::cout << get_current_date_string() << " 启动加载配置目录: 【" << config_directory().string() << "】"
|
|
<< std::endl;
|
|
return result;
|
|
}
|
|
void Config::from_log_json(const Psc::JSON* that_json) {
|
|
assign_config_section(Config_Section::log, [&] { log_config.from_base_json(that_json); });
|
|
}
|
|
void Config::from_base_json(Psc::JSON* that_json) {
|
|
assign_config_section(Config_Section::mlat, [&] { mlat.from_json(that_json->get("mlat")); });
|
|
assign_config_section(Config_Section::web_server,
|
|
[&] { web_server_config.from_base_json(that_json->get("web_server")); });
|
|
assign_config_section(Config_Section::map_resources, [&] {
|
|
map_resources_config.write([&](auto& value) { value.from_base_json(that_json->get("map_resources")); });
|
|
});
|
|
assign_config_section(Config_Section::map_view, [&] {
|
|
map_view_config.write([&](auto& value) { value.from_base_json(that_json->get("map_view")); });
|
|
});
|
|
assign_config_section(Config_Section::cesium_graphics,
|
|
[&] { cesium_graphics_config.from_base_json(that_json->get("cesium_graphics")); });
|
|
assign_config_section(Config_Section::map_models, [&] {
|
|
map_model_config.write([&](auto& value) { value.from_base_json(that_json->get("map_models")); });
|
|
});
|
|
assign_config_section(Config_Section::mode_acs, [&] { mode_acs.from_json(that_json->get("mode_acs")); });
|
|
assign_config_section(Config_Section::console, [&] { console_config.from_base_json(that_json->get("console")); });
|
|
assign_config_section(Config_Section::dsp, [&] { dsp_config.from_json(that_json->get("dsp")); });
|
|
assign_config_section(Config_Section::drogon, [&] { drogon_config = *that_json->get("drogon"); });
|
|
assign_config_section(Config_Section::http_monitor,
|
|
[&] { http_monitor_config.from_base_json(that_json->get("http_monitor")); });
|
|
assign_config_section(Config_Section::external_database,
|
|
[&] { external_resources_manager.from_json(that_json->get("external_database")); });
|
|
}
|
|
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) {
|
|
std::error_code error;
|
|
auto result = Psc::assign_field_ec(webapp, that_json, "webapp", error);
|
|
Psc::check_json_assign_field(result, error, that_json, "webapp");
|
|
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<std::string> read_string_array(const Psc::JSON* json, std::string_view key) {
|
|
auto field = json->get(key);
|
|
if (field == nullptr || 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) {
|
|
auto array = Psc::JSON::array();
|
|
for (const auto& path : list) {
|
|
array.append(Psc::JSON(path));
|
|
}
|
|
return Psc::JSON(key, array);
|
|
}
|
|
static const std::pair<std::string_view, std::string_view> 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<std::string, Map_Model_Item_Config> make_default_aircraft_models() {
|
|
std::map<std::string, Map_Model_Item_Config> 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<std::string, Map_Model_Item_Config> read_model_object_field(const Psc::JSON* json,
|
|
std::string_view key) {
|
|
auto field = json->get(key);
|
|
if (field == nullptr || field->valueType != Psc::Object) {
|
|
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key));
|
|
}
|
|
std::map<std::string, Map_Model_Item_Config> result;
|
|
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));
|
|
}
|
|
auto& item = result[child.key];
|
|
item.from_base_json(&child);
|
|
}
|
|
return result;
|
|
}
|
|
static Psc::JSON write_model_object(std::string_view key, const std::map<std::string, Map_Model_Item_Config>& 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_string_field(const Psc::JSON* json, std::string_view key) {
|
|
auto field = json->get(key);
|
|
if (field == nullptr || 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_uint32_field(const Psc::JSON* json, std::string_view key) {
|
|
auto field = json->get(key);
|
|
if (field == nullptr || field->valueType != Psc::Number) {
|
|
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key));
|
|
}
|
|
return field->number_val<std::uint32_t>();
|
|
}
|
|
static double read_double_field(const Psc::JSON* json, std::string_view key) {
|
|
auto field = json->get(key);
|
|
if (field == nullptr || field->valueType != Psc::Number) {
|
|
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key));
|
|
}
|
|
return field->number_val<double>();
|
|
}
|
|
void Map_Tile_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 config");
|
|
}
|
|
Map_Tile_Config_Data::from_base_json(that_json);
|
|
directories = read_string_array(that_json, "directories");
|
|
files = read_string_array(that_json, "files");
|
|
encoding.reset();
|
|
if (that_json->get("encoding") != nullptr) {
|
|
std::error_code error;
|
|
const auto result = Psc::assign_field_ec(encoding, that_json, "encoding", error);
|
|
Psc::check_json_assign_field(result, error, 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.has_value())
|
|
ret.append({"encoding", encoding.value()});
|
|
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.has_value())
|
|
ret.append({"encoding", encoding.value()});
|
|
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");
|
|
}
|
|
Map_Tile_Source_Config_Data::from_base_json(that_json);
|
|
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 = 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<Map_Tile_Source_Config> read_tile_source_array(const Psc::JSON* json, std::string_view key) {
|
|
auto field = json->get(key);
|
|
if (field == nullptr || 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) {
|
|
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_string_field(that_json, "current_imagery_key");
|
|
tile_sources = read_tile_source_array(that_json, "tile_sources");
|
|
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;
|
|
});
|
|
}
|
|
void Map_Tile_View_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_view");
|
|
}
|
|
current_imagery_key = read_string_field(that_json, "current_imagery_key");
|
|
current_terrain_key = read_string_field(that_json, "current_terrain_key");
|
|
tile_zoom_mode = Psc::to_enum<Map_Tile_Zoom_Mode>(read_string_field(that_json, "tile_zoom_mode"));
|
|
tile_display_maximum_level = std::clamp(read_uint32_field(that_json, "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) {
|
|
if (that_json == nullptr || that_json->valueType != Psc::Object) {
|
|
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map_view");
|
|
}
|
|
std::error_code error;
|
|
const auto result = Psc::assign_field_ec(scene_mode, that_json, "scene_mode", error);
|
|
Psc::check_json_assign_field(result, error, that_json, "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"));
|
|
base_station_fly_to_height_offset_meters =
|
|
std::max(100.0, read_double_field(that_json, "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) {
|
|
if (that_json == nullptr || 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);
|
|
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) {
|
|
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_string_field(that_json, "url");
|
|
built_in_size = read_double_field(that_json, "built_in_size");
|
|
heading_offset_degrees = read_double_field(that_json, "heading_offset_degrees");
|
|
pitch_offset_degrees = read_double_field(that_json, "pitch_offset_degrees");
|
|
roll_offset_degrees = read_double_field(that_json, "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 || 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_model_object_field(that_json, "aircraft_models");
|
|
base_station_model.from_base_json(that_json->get("base_station_model"));
|
|
device_model.from_base_json(that_json->get("device_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()});
|
|
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<std::size_t>(Config_Section::count); ++index) {
|
|
const auto section = static_cast<Config_Section>(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<std::size_t>(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);
|
|
}
|