This commit is contained in:
2026-07-27 15:17:13 +08:00
parent 8370ff9e79
commit c99b16d000
15 changed files with 1169 additions and 428 deletions
@@ -1,5 +1,60 @@
#include "../Aircraft/Flight_VTO.h"
#include <algorithm>
#include <cmath>
namespace {
double to_radians(double degrees) {
return degrees * 3.14159265358979323846 / 180.0;
}
double track_heading_from_points(const SSR::Position_Info& first,
const SSR::Position_Info& second) {
auto lat1 = to_radians(first.lat);
auto lat2 = to_radians(second.lat);
auto dlon = to_radians(second.lon - first.lon);
auto y = std::sin(dlon) * std::cos(lat2);
auto x = std::cos(lat1) * std::sin(lat2) -
std::sin(lat1) * std::cos(lat2) * std::cos(dlon);
auto angle = std::atan2(y, x) * 180.0 / 3.14159265358979323846;
return angle < 0 ? angle + 360.0 : angle;
}
double ground_distance_meters(const SSR::Position_Info& first,
const SSR::Position_Info& second) {
auto dlat = to_radians(second.lat - first.lat);
auto dlon = to_radians(second.lon - first.lon);
auto lat1 = to_radians(first.lat);
auto lat2 = to_radians(second.lat);
auto a = std::sin(dlat / 2) * std::sin(dlat / 2) +
std::cos(lat1) * std::cos(lat2) * std::sin(dlon / 2) *
std::sin(dlon / 2);
auto safe_a = std::min(1.0, std::max(0.0, a));
return 6371000.0 * 2 *
std::atan2(std::sqrt(safe_a), std::sqrt(1 - safe_a));
}
double track_pitch_from_points(const SSR::Position_Info& first,
const SSR::Position_Info& second) {
auto distance = ground_distance_meters(first, second);
return std::atan2(second.alt - first.alt, distance) * 180.0 /
3.14159265358979323846;
}
Flight_Track_Orientation track_orientation_from_points(const SSR::Position_Info& first,
const SSR::Position_Info& second) {
return {track_heading_from_points(first, second),
track_pitch_from_points(first, second), 0.0};
}
}
Psc::JSON Flight_Track_Orientation::to_json() const {
auto ret = Psc::JSON::object();
ret.append({"heading", heading});
ret.append({"pitch", pitch});
ret.append({"roll", roll});
return ret;
}
Psc::JSON Flight_VTO::to_json() {
return to_json(true);
}
Psc::JSON Flight_VTO::to_base_info_json() {
return to_json(false);
}
Psc::JSON Flight_VTO::to_json(bool include_runtime_fields) {
auto ret = Psc::JSON::object();
ADD_Json(icao);
ADD_Json(mlat);
@@ -13,11 +68,32 @@ Psc::JSON Flight_VTO::to_json() {
ret.children.emplace_back("longitude", nullptr);
}
ADD_Json_RET(track);
if (include_runtime_fields) {
if (track_orientation.has_value()) {
ret.append({"track_orientation", track_orientation.value().to_json()});
}
else {
ret.append({"track_orientation", nullptr});
}
}
ADD_Json_RET(speed);
ADD_Json_RET(altitude);
ADD_Json_RET(vert_speed);
ADD_Json_RET(call_sign);
ADD_Json_RET(squawk);
if (include_runtime_fields) {
if (vortex_type.has_value()) {
auto value = vortex_type.value();
ret.append({"vortex_type", static_cast<int>(value)});
ret.append({"vortex_type_key", std::string(SSR::Vortex_Type_to_key(value))});
ret.append({"vortex_type_label", std::string(SSR::Vortex_Type_to_label(value))});
}
else {
ret.append({"vortex_type", nullptr});
ret.append({"vortex_type_key", nullptr});
ret.append({"vortex_type_label", nullptr});
}
}
return ret;
}
std::string Flight_VTO::time_to_string(time_t time_stamp) {
@@ -34,6 +110,12 @@ Flight_VTO Flight_VTO::to_VTO(SSR::Aircraft_Info* info) {
vto.speed = info->speed();
// 这个地 是相对于地的意思
vto.track = info->surface_magnetic_heading();
auto last_two = info->air_pos_track_list.last_two();
if (last_two.has_value()) {
auto orientation = track_orientation_from_points(last_two->first, last_two->second);
vto.track = orientation.heading;
vto.track_orientation = orientation;
}
// vto.pos = info->pos();
auto opt = info->air_pos_track_list.last();
vto.pos = opt;
@@ -50,5 +132,6 @@ Flight_VTO Flight_VTO::to_VTO(SSR::Aircraft_Info* info) {
vto.vert_speed = info->vertical_rate();
vto.call_sign = info->bds20_call_sign();
vto.squawk = info->squawk();
vto.vortex_type = info->vortex_type();
return vto;
}
+10
View File
@@ -1,17 +1,27 @@
#pragma once
#include "global.h"
struct Flight_Track_Orientation {
double heading = 0.0; // 由最后两个空中位置点计算出的水平航迹角,单位:度
double pitch = 0.0; // 由最后两个空中位置点计算出的三维俯仰角,单位:度
double roll = 0.0; // 当前 ADS-B 位置轨迹没有横滚数据,保留给 Cesium 姿态结构
Psc::JSON to_json() const;
};
struct Flight_VTO {
std::string icao; // 飞机身份标识(四字节十六进制代码)
time_t time_stamp{}; // 时间(原始时间数据,可能需要翻译显示)
bool mlat;
RET<SSR::CPR::Position> pos; // 纬度(飞机的当前位置纬度)
RET<double> track; // 航迹(航迹方向,单位:度)
RET<Flight_Track_Orientation> track_orientation; // Cesium 使用的航迹姿态,单位:度
RET<double> speed; // 航速(单位:米/秒)
RET<double> vert_speed; // 航速(单位:米/秒)
RET<double> altitude; // 米
RET<std::string> call_sign;
RET<std::string> squawk;
RET<SSR::Vortex_Type> vortex_type;
Psc::JSON to_json();
Psc::JSON to_base_info_json();
Psc::JSON to_json(bool include_runtime_fields);
static std::string time_to_string(time_t time_stamp);
static Flight_VTO to_VTO(SSR::Aircraft_Info* info);
};
@@ -1,15 +1,21 @@
#include "BaseStation.h"
namespace SSR {
struct HULC_Status_Message;
}
std::optional<SSR::CPR::Position> BaseStation::get_pos() {
double BaseStation::theoretical_detection_range_meters(double base_height_meters,
double target_height_meters) {
return static_cast<double>(SSR::CPR::radio_line_of_sight_range_meters(base_height_meters, target_height_meters));
}
std::optional<SSR::Position_3D> BaseStation::get_pos() {
std::lock_guard g(mtx);
if (!hulc.GPS_has_valid_fix()) {
return std::nullopt;
}
return SSR::CPR::Position{hulc.get_latitude(), hulc.get_longitude()};
return SSR::Position_3D{hulc.get_latitude(), hulc.get_longitude(), static_cast<SSR::CPR::D>(hulc.Alt)};
}
double BaseStation::get_height() {
std::lock_guard g(mtx);
return hulc.Alt;
}
bool BaseStation::has_valid_position() {
std::lock_guard g(mtx);
@@ -33,9 +39,6 @@ Psc::JSON BaseStation::to_Json() {
ret.append({"状态标志", hulc.Flags});
ret.append({"内部使用", hulc.I_U_});
ret.append({"时间", Psc::utc_2_local_time(hulc.xTime)});
ret.append({"latitude", hulc.get_latitude()});
ret.append({"longitude", hulc.get_longitude()});
ret.append({"height", hulc.Alt});
ret.append({"卫星数量", hulc.Sat});
ret.append({"HDOP", hulc.get_HDOP()});
ret.append({"GPS设备检测到", hulc.GPS_device_detected()});
+10 -14
View File
@@ -1,21 +1,17 @@
#pragma once
#include "global.h"
class BaseStation {
public:
std::string to_string();
Psc::JSON to_Json();
std::optional<SSR::CPR::Position> get_pos();
bool has_valid_position();
void set_msg(const SSR::HULC_Status_Message& msg);
std::function<void (const SSR::HULC_Status_Message&)> handle_when_updated = nullptr;
std::string to_string();
Psc::JSON to_Json();
std::optional<SSR::Position_3D> get_pos();
double get_height();
bool has_valid_position();
void set_msg(const SSR::HULC_Status_Message& msg);
static double theoretical_detection_range_meters(double base_height_meters, double target_height_meters);
std::function<void(const SSR::HULC_Status_Message&)> handle_when_updated = nullptr;
protected:
std::mutex mtx;
SSR::HULC_Status_Message hulc{};
std::mutex mtx;
SSR::HULC_Status_Message hulc{};
};
class Global;
@@ -513,6 +513,7 @@ void Data_Source_Config::server(Global* g) {
ds->from_json(&params);
g->save();
g->mode_acs.source_feed_relation_config.set_need_refresh();
res->setBody(warp(ds->to_json()).to_json_string());
});
svr.Post(api + svr.insert + name, [g, this](HTTP_Param) {
CHECK_JSON_PARAM
+58 -8
View File
@@ -50,23 +50,73 @@ protected:
std::mutex mtx;
std::vector<bool> test_v;
};
class Data_Source_Map_Display_Data {
public:
Psc::Copyable_Atomic<bool> base_station_show = true;
Psc::Copyable_Atomic<bool> aircraft_show = true;
Psc::Copyable_Atomic<bool> constant_screen_size = true;
std::string color = "#1677ff";
std::string track_point_color = "#ffff00";
std::string base_station_color = "#1677ff";
std::uint32_t aircraft_pixel_size = 50;
std::uint32_t base_station_pixel_size = 100;
double aircraft_scale = 1.0;
double base_station_scale = 1.0;
Psc::Copyable_Atomic<bool> show_icao = true;
Psc::Copyable_Atomic<bool> show_call_sign = false;
Psc::Copyable_Atomic<bool> show_fly_status = false;
PSC_USE_JSON
};
class Data_Source_Map_Display_Config {
public:
Data_Source_Map_Display_Data map2d;
Data_Source_Map_Display_Data map3d;
[[nodiscard]] Psc::JSON to_base_json() const {
auto ret = Psc::JSON::object();
ret.append({"map2d", map2d.to_base_json()});
ret.append({"map3d", map3d.to_base_json()});
return ret;
}
void from_base_json(const Psc::JSON* that_json) {
if (that_json == nullptr || that_json->valueType != Psc::Object) {
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map_display");
}
map2d.from_base_json(that_json->get("map2d"));
map3d.from_base_json(that_json->get("map3d"));
}
};
class Data_Source_Data {
public:
Psc::Copyable_Atomic<bool> base_station_show{};
Psc::Copyable_Atomic<bool> base_station_has_valid_position{};
Psc::Copyable_Atomic<bool> aircraft_show{};
std::string color = "#1677ff";
int aircraft_pixel_size{};
Data_Source_Map_Display_Config map_display;
double lat{};
double lon{};
double alt{};
Psc::Copyable_Atomic<bool> ignore_msg_time = false; // 忽略消息时间戳 如果启用 解码位置将不在判断消息自带时间戳
Psc::Copyable_Atomic<bool> update_form_gps{};
Psc::Copyable_Atomic<bool> show_icao = true;
Psc::Copyable_Atomic<bool> show_call_sign = false;
Psc::Copyable_Atomic<bool> show_fly_status = false;
Psc::Copyable_Atomic<bool> keep_mode = true;
PSC_USE_JSON
[[nodiscard]] Psc::JSON to_base_json() const {
auto ret = Psc::JSON::object();
Ret_J(base_station_has_valid_position)
ret.append({"map_display", map_display.to_base_json()});
Ret_J(lat)
Ret_J(lon)
Ret_J(alt)
Ret_J(ignore_msg_time)
Ret_J(update_form_gps)
Ret_J(keep_mode)
return ret;
}
void from_base_json(const Psc::JSON* that_json) {
Get_J(base_station_has_valid_position)
map_display.from_base_json(that_json->get("map_display"));
Get_J(lat)
Get_J(lon)
Get_J(alt)
Get_J(ignore_msg_time)
Get_J(update_form_gps)
Get_J(keep_mode)
}
};
class Data_Source : public std::enable_shared_from_this<Data_Source>,
public Data_Source_Handler, public Data_Source_Data {
@@ -1,7 +1,7 @@
#include "Data_Source_Handler.h"
#include <string_view>
#include "Data_Source.h"
#include "Local_Server/server/Global.h"
#include <string_view>
#include "Local_Server/server/io_coro.h"
using namespace Psc;
std::shared_ptr<Data_Source> ds(Data_Source_Handler* dsh) {
@@ -13,18 +13,16 @@ std::shared_ptr<SSR::Msg> Data_Source_Handler::create_msg(std::string_view packe
std::cout << "first:" << mem2hex(std::string(packet)) << std::endl;
return nullptr;
}
std::string error_len = source->key + " " + LOG_POS_SIMPLE +
std::string(" ") + "mode_s_error_length";
if (packet.size() < 2) return nullptr;
std::string error_len = source->key + " " + LOG_POS_SIMPLE + std::string(" ") + "mode_s_error_length";
if (packet.size() < 2)
return nullptr;
auto mt = packet[1];
if (mt == SSR::Msg::AC) {
if (packet.size() != SSR::Msg::AC_len) {
auto packet_hex = mem2hex(std::string(packet));
SSR::mode_s_logger->c_debug(
error_len, {},
std::to_string(mt) + " size:" + std::to_string(packet.size()) +
" hex:" + packet_hex +
" should:" + std::to_string(SSR::Msg::AC_len));
SSR::mode_s_logger->c_debug(error_len, {},
std::to_string(mt) + " size:" + std::to_string(packet.size()) +
" hex:" + packet_hex + " should:" + std::to_string(SSR::Msg::AC_len));
mode_ac_statistic.add_length_error();
return nullptr;
}
@@ -34,13 +32,10 @@ std::shared_ptr<SSR::Msg> Data_Source_Handler::create_msg(std::string_view packe
if (mt == SSR::Msg::S7) {
if (packet.size() != SSR::Mode_S_Msg::S7_len) {
auto packet_hex = mem2hex(std::string(packet));
SSR::mode_s_logger->c_debug(
error_len, {},
std::to_string(mt) + " size:" + std::to_string(packet.size()) +
" hex:" + packet_hex +
" should:" + std::to_string(SSR::Msg::S7_len));
mode_s_statistic.add_length_error(
"[length error] Msg::S7 handle_mode_s_source");
SSR::mode_s_logger->c_debug(error_len, {},
std::to_string(mt) + " size:" + std::to_string(packet.size()) +
" hex:" + packet_hex + " should:" + std::to_string(SSR::Msg::S7_len));
mode_s_statistic.add_length_error("[length error] Msg::S7 handle_mode_s_source");
return nullptr;
}
return std::make_shared<SSR::Mode_S_Msg>(source, packet);
@@ -48,13 +43,10 @@ std::shared_ptr<SSR::Msg> Data_Source_Handler::create_msg(std::string_view packe
if (mt == SSR::Msg::S14) {
if (packet.size() != SSR::Msg::S14_len) {
auto packet_hex = mem2hex(std::string(packet));
SSR::mode_s_logger->c_debug(
error_len, {},
std::to_string(mt) + " size:" + std::to_string(packet.size()) +
" hex:" + packet_hex +
" should:" + std::to_string(SSR::Msg::S14_len));
mode_s_statistic.add_length_error(
"[length error] Msg::S14 handle_mode_s_source");
SSR::mode_s_logger->c_debug(error_len, {},
std::to_string(mt) + " size:" + std::to_string(packet.size()) +
" hex:" + packet_hex + " should:" + std::to_string(SSR::Msg::S14_len));
mode_s_statistic.add_length_error("[length error] Msg::S14 handle_mode_s_source");
return nullptr;
}
return std::make_shared<SSR::Mode_S_Msg>(source, packet);
@@ -63,13 +55,10 @@ std::shared_ptr<SSR::Msg> Data_Source_Handler::create_msg(std::string_view packe
return nullptr; // 不知道如何解析跳过
if (packet.size() != SSR::Msg::Radarcape_status_len) {
auto packet_hex = mem2hex(std::string(packet));
SSR::mode_s_logger->c_debug(
error_len, {},
std::to_string(mt) + " size:" + std::to_string(packet.size()) +
" hex:" + packet_hex +
" should:" + std::to_string(SSR::Msg::Radarcape_status_len));
mode_s_statistic.add_length_error(
"[length error] Msg::Radarcape_status_len handle_mode_s_source");
SSR::mode_s_logger->c_debug(error_len, {},
std::to_string(mt) + " size:" + std::to_string(packet.size()) + " hex:" +
packet_hex + " should:" + std::to_string(SSR::Msg::Radarcape_status_len));
mode_s_statistic.add_length_error("[length error] Msg::Radarcape_status_len handle_mode_s_source");
return nullptr;
}
return std::make_shared<SSR::Msg>(source, packet);
@@ -79,10 +68,9 @@ std::shared_ptr<SSR::Msg> Data_Source_Handler::create_msg(std::string_view packe
auto packet_hex = mem2hex(std::string(packet));
// 找不到协议 size:23 hex:1A34195F0000001500B502FF06F423CE50000090000000
// should:5
SSR::mode_s_logger->c_debug(
error_len, {},
Psc::to_string(source->type) + " HULC size:" +
std::to_string(packet.size()) + " hex:" + packet_hex);
SSR::mode_s_logger->c_debug(error_len, {},
Psc::to_string(source->type) + " HULC size:" + std::to_string(packet.size()) +
" hex:" + packet_hex);
return nullptr;
}
return std::make_shared<SSR::Msg>(source, packet);
@@ -101,8 +89,8 @@ size_t Data_Source_Handler::process_mode_acs_data(std::string_view origin_data)
}
origin_data_transform_mode_data(mode_data);
if (data_source_debug) {
std::cout << " after origin_data_transform_mode_data " << VAR_STR_2(name, mode_data.size()) <<
key << std::endl;
std::cout << " after origin_data_transform_mode_data " << VAR_STR_2(name, mode_data.size()) << key
<< std::endl;
}
static Value_Growth_Multi_T mt;
auto t = key;
@@ -124,17 +112,16 @@ size_t Data_Source_Handler::process_mode_acs_data(std::string_view origin_data)
static bool mode_s_console = Global::instance()->console_config.mode_s_console;
static bool record_playback = Global::instance()->console_config.record_playback;
if (record_playback) {
pure_log(get_exe_dir() + "/playback/" + source->key + "_playback.dat",
mem2hex(mode_data, true, " ") + "\n");
pure_log(get_exe_dir() + "/playback/" + source->key + "_playback.dat", mem2hex(mode_data, true, " ") + "\n");
}
if (mode_s_console) {
std::cout << source->key + " read:[mode_s_serial]:" << mem2hex(mode_data)
<< std::endl;
std::cout << source->key + " read:[mode_s_serial]:" << mem2hex(mode_data) << std::endl;
}
auto handle_packet = [this, source, &ret](std::string& packet) {
ret++;
auto msg = create_msg(packet);
if (!msg) return;
if (!msg)
return;
source->push_to_feed(msg);
auto mt = msg->type;
bool mode_s = mt == SSR::Msg::S7 || mt == SSR::Msg::S14;
@@ -144,8 +131,7 @@ size_t Data_Source_Handler::process_mode_acs_data(std::string_view origin_data)
else if (mt == SSR::Msg::Radarcape_status) {
auto radarcape_msg = SSR::create_Radarcape_STATUS_Message(packet);
std::cout << radarcape_msg.toJson().to_json_string() << std::endl;
SSR::mode_s_logger->debug("Radarcape_status/radarcape", {},
radarcape_msg.toJson().to_json_string());
SSR::mode_s_logger->debug("Radarcape_status/radarcape", {}, radarcape_msg.toJson().to_json_string());
}
else if (mode_s) {
// 拓展点
@@ -160,9 +146,7 @@ size_t Data_Source_Handler::process_mode_acs_data(std::string_view origin_data)
else {
auto co = Coro::instance();
auto executor = co->process_data->get_executor();
asio::post(executor, [handle_packet, packet = std::move(packet)]() mutable {
handle_packet(packet);
});
asio::post(executor, [handle_packet, packet = std::move(packet)]() mutable { handle_packet(packet); });
}
};
SSR::Binary_Format_handle_buffer(source->buffer, mode_data, f);
@@ -170,8 +154,8 @@ size_t Data_Source_Handler::process_mode_acs_data(std::string_view origin_data)
}
void Data_Source_Handler::handle_mode_s(std::shared_ptr<SSR::Mode_S_Msg> mode_s_msg) {
auto source = ds(this);
std::string t = mode_s_msg->mlat_timestamp.to_memory() +
mode_s_msg->signal_level + Psc::hex2mem(mode_s_msg->msg_hex);
std::string t =
mode_s_msg->mlat_timestamp.to_memory() + mode_s_msg->signal_level + Psc::hex2mem(mode_s_msg->msg_hex);
auto p = mode_s_msg->packet.substr(2);
if (t != p) {
std::cout << mem2hex(t, true, " ") << std::endl;
@@ -183,12 +167,26 @@ void Data_Source_Handler::handle_mode_s(std::shared_ptr<SSR::Mode_S_Msg> mode_s_
bool time_space_filter = cfg.time_space_filter.load();
bool speed_filter = cfg.speed_filter.load();
bool use_system_time = source->ignore_msg_time.load();
SSR::ADS_B_T::Constraint air_constraint{cfg.max_speed_m_s, cfg.air_pos_timeout, SSR::cpr_cb, time_space_filter, speed_filter, use_system_time};
SSR::ADS_B_T::Constraint surface_constraint{cfg.max_speed_m_s, cfg.surface_pos_timeout, SSR::cpr_cb, time_space_filter, speed_filter, use_system_time};
SSR::parse_mode_s_bin(source.get(), mode_s_msg,
source->base_station.get_pos(), air_constraint,
surface_constraint);
auto base_station_pos = source->base_station.get_pos();
if (!base_station_pos && source->base_station_has_valid_position.load()) {
base_station_pos = SSR::Position_3D{source->lat, source->lon, source->alt};
}
auto range_filter = cfg.aircraft_change_list_adsb_range_filter.load();
auto range_factor = cfg.aircraft_change_list_adsb_range_factor.load();
SSR::ADS_B_T::Constraint air_constraint{cfg.max_speed_m_s, cfg.air_pos_timeout, SSR::cpr_cb, time_space_filter,
speed_filter, use_system_time, base_station_pos, source->alt,
range_filter, range_factor};
SSR::ADS_B_T::Constraint surface_constraint{cfg.max_speed_m_s,
cfg.surface_pos_timeout,
SSR::cpr_cb,
time_space_filter,
speed_filter,
use_system_time,
base_station_pos,
source->alt,
range_filter,
range_factor};
SSR::parse_mode_s_bin(source.get(), mode_s_msg, base_station_pos, air_constraint, surface_constraint);
auto base = source->get_aircraft(mode_s_msg->icao);
if (base) {
auto derived = std::dynamic_pointer_cast<Aircraft>(base);
@@ -199,8 +197,7 @@ void Data_Source_Handler::handle_mode_s(std::shared_ptr<SSR::Mode_S_Msg> mode_s_
mh.push(mode_s_msg);
auto tt = mh.get_all();
if (!tt.empty()) {
pure_log("@/logs/mlat.log",
"receive[" + std::to_string(tt.size()) + "]:\n");
pure_log("@/logs/mlat.log", "receive[" + std::to_string(tt.size()) + "]:\n");
for (Mlat_MSG& t : tt) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(10);
@@ -223,12 +220,9 @@ void Data_Source_Handler::handle_mode_s(std::shared_ptr<SSR::Mode_S_Msg> mode_s_
auto pos = o_pos.value();
source->alt = o_alt.value();
double x, y, z;
SSR::CPR::WGS84_LBH_to_XYZ(pos.lon, pos.lat, source->alt, x, y,
z);
oss << "\tpos[" << pos.lon << "," << pos.lat << ","
<< source->alt << "]" << std::endl;
oss << "\tXYZ[" << x << "," << y << "," << z << "]"
<< std::endl;
SSR::CPR::WGS84_LBH_to_XYZ(pos.lon, pos.lat, source->alt, x, y, z);
oss << "\tpos[" << pos.lon << "," << pos.lat << "," << source->alt << "]" << std::endl;
oss << "\tXYZ[" << x << "," << y << "," << z << "]" << std::endl;
}
}
}
@@ -262,23 +256,22 @@ void Data_Source_Handler::handle_HULC(std::string_view packet) {
auto msg = mem2hex(std::string(packet));
if (len != packet.size() - 4) {
SSR::mode_s_logger->debug("HULC/error_length", {},
"需要: " + std::to_string(len) +
" 当前: " + std::to_string(packet.size()) +
"需要: " + std::to_string(len) + " 当前: " + std::to_string(packet.size()) +
" hex: " + mem2hex(std::string(packet)));
return;
}
if (id == 1) {
// 状态消息
auto status_msg = SSR::create_HULC_Status_Message(packet);
bool gps_ok = status_msg.GPS_device_detected() && status_msg.GPS_valid() &&
status_msg.GPS_has_valid_fix();
if (gps_ok) {}
bool gps_ok = status_msg.GPS_device_detected() && status_msg.GPS_valid() && status_msg.GPS_has_valid_fix();
if (gps_ok) {
}
auto g = Global::instance();
base_station.set_msg(status_msg);
Log_Type type({}, {{"msg", std::string(msg)}});
SSR::mode_s_logger->debug("HULC/status", type,
status_msg.toJson().to_json_string());
if (gps_ok) {}
SSR::mode_s_logger->debug("HULC/status", type, status_msg.toJson().to_json_string());
if (gps_ok) {
}
}
else if (id == 24) {
SSR::mode_s_logger->debug("HULC/reply", {}, msg);
@@ -290,9 +283,9 @@ void Data_Source_Handler::handle_HULC(std::string_view packet) {
void Data_Source_Handler::refresh_data_feed_key_list() {
// std::cout << key << " refresh_data_feed_key_list" << std::endl;
std::vector<std::string> tmp;
for (auto& relation :
Global::instance()->mode_acs.source_feed_relation_config.map.list()) {
if (!relation->enable) continue;
for (auto& relation : Global::instance()->mode_acs.source_feed_relation_config.map.list()) {
if (!relation->enable)
continue;
if (relation->type == "One_to_One_Relation") {
auto t = dynamic_cast<One_to_One_Relation*>(relation.get());
// std::cout << VAR_STR_2(t->source_key, this->key) << "
@@ -302,19 +295,16 @@ void Data_Source_Handler::refresh_data_feed_key_list() {
}
}
else if (relation->type == "First_Source_To_All_Feed_Relation") {
auto t =
dynamic_cast<First_Source_To_All_Feed_Relation*>(relation.get());
auto t = dynamic_cast<First_Source_To_All_Feed_Relation*>(relation.get());
std::shared_ptr<Data_Source> first = nullptr;
for (const auto& ds :
Global::instance()->mode_acs.data_source_config.map.list()) {
for (const auto& ds : Global::instance()->mode_acs.data_source_config.map.list()) {
if (ds->enable) {
first = ds;
break;
}
}
if (first->key == key) {
for (const auto& df :
Global::instance()->mode_acs.data_feed_config.map.list()) {
for (const auto& df : Global::instance()->mode_acs.data_feed_config.map.list()) {
if (df->enable) {
tmp.push_back(df->key);
}
@@ -337,10 +327,9 @@ void Data_Source_Handler::refresh_data_feed_key_list() {
// 1. 删除 tmp 中没有的 cached_data_feed_key_list 元素
auto it = cached_data_feed_key_list.begin();
while (it != cached_data_feed_key_list.end()) {
if (std::find_if(tmp_info.begin(), tmp_info.end(),
[&](const Cached_Source_Info& info) {
return info.key == it->key;
}) == tmp_info.end()) {
if (std::find_if(tmp_info.begin(), tmp_info.end(), [&](const Cached_Source_Info& info) {
return info.key == it->key;
}) == tmp_info.end()) {
// 如果当前元素在 tmp 中找不到,删除它
it = cached_data_feed_key_list.erase(it);
}
@@ -350,11 +339,9 @@ void Data_Source_Handler::refresh_data_feed_key_list() {
}
// 2. 创建 tmp 中有但 cached_data_feed_key_list 没有的元素
for (const auto& tmp_item : tmp_info) {
auto found = std::find_if(cached_data_feed_key_list.begin(),
cached_data_feed_key_list.end(),
[&](const Cached_Source_Info& cached_item) {
return cached_item.key == tmp_item.key;
});
auto found = std::find_if(
cached_data_feed_key_list.begin(), cached_data_feed_key_list.end(),
[&](const Cached_Source_Info& cached_item) { return cached_item.key == tmp_item.key; });
if (found == cached_data_feed_key_list.end()) {
// 如果 tmp_item 不在 cached_data_feed_key_list 中,添加它
cached_data_feed_key_list.push_back(tmp_item);
@@ -364,8 +351,7 @@ void Data_Source_Handler::refresh_data_feed_key_list() {
}
}
}
std::optional<std::string> convert_to_send_format(Data_Source_Handler* ds,
const std::shared_ptr<Data_Feed>& feed,
std::optional<std::string> convert_to_send_format(Data_Source_Handler* ds, const std::shared_ptr<Data_Feed>& feed,
const std::shared_ptr<SSR::Msg>& msg) {
auto& type = msg->type;
auto fs = dynamic_cast<File_Data_Source*>(msg->source.get());
@@ -382,22 +368,22 @@ std::optional<std::string> convert_to_send_format(Data_Source_Handler* ds,
// 计算出是否输出消息
std::optional<std::string> send_msg = std::nullopt;
if (type == SSR::Msg::HULC_Status) {
if (!use_status) return std::nullopt;
if (!use_status)
return std::nullopt;
auto data = static_cast<SSR::Msg*>(msg.get());
if (output_format == Output_Data_Format::BIN ||
output_format == Output_Data_Format::BIN_ID) {
if (output_format == Output_Data_Format::BIN || output_format == Output_Data_Format::BIN_ID) {
send_msg = SSR::packet_to_escape_format(data->packet);
}
}
else if (type == SSR::Msg::Radarcape_status) {
auto data = static_cast<SSR::Msg*>(msg.get());
if (output_format == Output_Data_Format::BIN ||
output_format == Output_Data_Format::BIN_ID) {
if (output_format == Output_Data_Format::BIN || output_format == Output_Data_Format::BIN_ID) {
send_msg = SSR::packet_to_escape_format(data->packet);
}
}
else if (type == SSR::Msg::AC) {
if (!use_mode_ac) return std::nullopt;
if (!use_mode_ac)
return std::nullopt;
auto data = static_cast<SSR::Mode_AC_Msg*>(msg.get());
std::string& msg_hex = data->msg_hex;
char signal_level = data->signal_level;
@@ -448,10 +434,12 @@ std::optional<std::string> convert_to_send_format(Data_Source_Handler* ds,
sbs_out_put = true;
}
}
if (!sbs_out_put) return std::nullopt;
if (!sbs_out_put)
return std::nullopt;
send_msg = "";
SSR::SBS_MSG msg_base;
if (!aircraft) return std::nullopt;
if (!aircraft)
return std::nullopt;
msg_base.set(aircraft.get());
for (int msg_t = 1; msg_t <= 8; ++msg_t) {
send_msg->append(msg_base.to_msg(msg_t) + "\n");
@@ -463,11 +451,11 @@ std::optional<std::string> convert_to_send_format(Data_Source_Handler* ds,
send_msg->append(msg_base.to_STA() + "\n");
return send_msg;
}
bool DF_11_17_18 =
df == SSR::Downlink_Format::All_Call_Reply_11 ||
bool DF_11_17_18 = df == SSR::Downlink_Format::All_Call_Reply_11 ||
df == SSR::Downlink_Format::Extended_Squitter_17 ||
df == SSR::Downlink_Format::Extended_Squitter_Non_Transponder_18;
if (mode_s_output_type == Mode_S_Output_Type::DF_11_17_18 && !DF_11_17_18) return std::nullopt;
if (mode_s_output_type == Mode_S_Output_Type::DF_11_17_18 && !DF_11_17_18)
return std::nullopt;
if (mode_s_output_type == Mode_S_Output_Type::NO_POS_Mode_S) {
if (aircraft && aircraft->pos().has_value()) {
return std::nullopt;
@@ -493,8 +481,7 @@ std::optional<std::string> convert_to_send_format(Data_Source_Handler* ds,
send_msg = create_MLAT_AVR_format(msg_hex, &mlat_timestamp);
}
else {
std::cerr << "Unknown output format " << VAR_STR_1(output_format)
<< std::endl;
std::cerr << "Unknown output format " << VAR_STR_1(output_format) << std::endl;
return std::nullopt;
}
}
@@ -516,9 +503,9 @@ void Data_Source_Handler::push_to_feed(const std::shared_ptr<SSR::Msg>& msg) {
break;
}
std::shared_ptr<Data_Feed>& feed = opt_feed.value();
if (!feed->enable) continue;
if (msg->type == SSR::Mode_Msg::T::S7 ||
msg->type == SSR::Mode_Msg::T::S14) {
if (!feed->enable)
continue;
if (msg->type == SSR::Mode_Msg::T::S7 || msg->type == SSR::Mode_Msg::T::S14) {
// static Frequency_Limit fl;
// if (!fl.test()) {
// std::ostringstream oss;
+88 -6
View File
@@ -1,12 +1,56 @@
#include "Database.h"
#include "Data_Source.h"
#include "../server/Global.h"
#include "../server/Performance_Monitor.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <functional>
#include <map>
#include <string_view>
namespace {
double to_radians(double degrees) {
return degrees * 3.14159265358979323846 / 180.0;
}
double to_degrees(double radians) {
return radians * 180.0 / 3.14159265358979323846;
}
bool aircraft_in_base_station_radio_range(const std::optional<SSR::Position_3D> &base_position,
const SSR::Position_Info &position,
double factor) {
if (!base_position) {
return true;
}
return SSR::CPR::in_radio_line_of_sight_range(*base_position, base_position->alt, position, position.alt, factor);
}
double track_heading_from_points(const SSR::Position_Info &first,
const SSR::Position_Info &second) {
auto lat1 = to_radians(first.lat);
auto lat2 = to_radians(second.lat);
auto dlon = to_radians(second.lon - first.lon);
auto y = std::sin(dlon) * std::cos(lat2);
auto x = std::cos(lat1) * std::sin(lat2) -
std::sin(lat1) * std::cos(lat2) * std::cos(dlon);
auto angle = to_degrees(std::atan2(y, x));
return angle < 0 ? angle + 360.0 : angle;
}
double track_pitch_from_points(const SSR::Position_Info &first,
const SSR::Position_Info &second) {
auto distance = SSR::CPR::haversine(first, second);
return std::atan2(second.alt - first.alt, distance) * 180.0 /
3.14159265358979323846;
}
Psc::JSON track_orientation_json_from_points(const SSR::Position_Info &first,
const SSR::Position_Info &second) {
auto ret = Psc::JSON::object();
ret.append({"heading", track_heading_from_points(first, second)});
ret.append({"pitch", track_pitch_from_points(first, second)});
ret.append({"roll", 0.0});
return ret;
}
}
Aircraft::Aircraft(std::string_view icao) : Aircraft_Info(icao) {
auto size = Global::instance()->mode_acs.max_track_point_size.load();
air_pos_track_list.init(size);
@@ -77,14 +121,34 @@ Psc::JSON DataBase::get_aircraftlist(int limit_msg_num) {
JSON DataBase::get_all_aircraft_json() {
JSON json = JSON::array();
auto g = Global::instance();
auto min_position_points =
g->mode_acs.aircraft_change_list_min_position_points.load();
auto range_filter = g->mode_acs.aircraft_change_list_adsb_range_filter.load();
auto range_factor = g->mode_acs.aircraft_change_list_adsb_range_factor.load();
auto source = g->source(get_key());
auto base_position = base_station.get_pos();
if (source) {
if (!base_position && source->base_station_has_valid_position.load()) {
base_position = SSR::Position_3D{source->lat, source->lon, source->alt};
}
}
size_t num = 0;
for (auto &it : aircraft_map.values()) {
auto vto = Flight_VTO::to_VTO(it.get());
bool have_pos = !it.get()->air_pos_track_list.empty();
if (have_pos) {
json.children.push_back(vto.to_json());
num++;
if (it->air_pos_track_list.size() < min_position_points) {
continue;
}
auto last_position = it->air_pos_track_list.last();
if (!last_position) {
continue;
}
if (range_filter &&
!aircraft_in_base_station_radio_range(base_position, *last_position,
range_factor)) {
continue;
}
auto vto = Flight_VTO::to_VTO(it.get());
json.children.push_back(vto.to_json());
num++;
}
have_pos_aircraft_num = num;
return json;
@@ -187,6 +251,16 @@ void database_server(Global *g) {
auto ret = JSON::object();
if (db) {
ret = db->base_station.to_Json();
auto target_height =
g->mode_acs.adsb_theoretical_target_altitude_meters.load();
auto range = BaseStation::theoretical_detection_range_meters(db->alt,
target_height);
ret.append({"latitude", db->lat});
ret.append({"longitude", db->lon});
ret.append({"height", db->alt});
ret.append({"adsb_theoretical_target_altitude_meters", target_height});
ret.append({"adsb_theoretical_detection_range_meters", range});
ret.append({"理论探测范围", range});
}
res->setBody(warp(ret).to_json_string());
});
@@ -198,7 +272,7 @@ void database_server(Global *g) {
JSON ret;
if (aircraft != nullptr) {
auto vto = Flight_VTO::to_VTO(aircraft.get());
ret = vto.to_json();
ret = vto.to_base_info_json();
}
res->setBody(ret.to_json_string());
});
@@ -222,6 +296,14 @@ void database_server(Global *g) {
JSON ret;
if (aircraft != nullptr) {
ret = aircraft->air_pos_track_list.get_last_array_json(last_size);
auto last_two = aircraft->air_pos_track_list.last_two();
if (last_two.has_value()) {
ret.children.insert(
ret.children.begin(),
JSON("track_orientation",
track_orientation_json_from_points(last_two->first,
last_two->second)));
}
ret.children.insert(ret.children.begin(), JSON("ds", data_source_key));
ret.children.insert(ret.children.begin(), JSON("icao", icao));
}
+35 -53
View File
@@ -1,8 +1,5 @@
#ifndef PSC_GLOBAL_INCLUDE_H
#define PSC_GLOBAL_INCLUDE_H
#include "Core/Statistics/Statistics.h"
#include "Core/socket/ByteOrder.h"
#include "SSR/export.h"
#include <functional>
#include <iostream>
#include <map>
@@ -11,10 +8,13 @@
#include <queue>
#include <stdexcept>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
#include <string_view>
#include "Core/Statistics/Statistics.h"
#include "Core/socket/ByteOrder.h"
#include "SSR/export.h"
class Global;
class Invalid_Http_Param final : public std::runtime_error {
public:
@@ -23,28 +23,28 @@ public:
[[noreturn]] inline void throw_invalid_http_param(const char* detail) {
throw Invalid_Http_Param(detail);
}
#define CHECK_JSON_PARAM \
auto o_params = Psc::try_parse_json(req->body().data()); \
if (!o_params.has_value()) { \
throw_invalid_http_param("request body is not valid JSON"); \
} \
auto &params = o_params.value(); \
auto that_json = &params;
#define HTTP_REQUIRE_VALUE(NAME, EXPRESSION) \
auto o_http_##NAME = (EXPRESSION); \
if (!o_http_##NAME.has_value()) { \
throw_invalid_http_param(#NAME); \
} \
auto NAME = std::move(o_http_##NAME).value();
#define HTTP_REQUIRE_PTR(NAME, EXPRESSION) \
auto NAME = (EXPRESSION); \
if ((NAME) == nullptr) { \
throw_invalid_http_param(#NAME); \
}
#define HTTP_REQUIRE_TRUE(EXPRESSION, DETAIL) \
if (!(EXPRESSION)) { \
throw_invalid_http_param(DETAIL); \
}
#define CHECK_JSON_PARAM \
auto o_params = Psc::try_parse_json(req->body().data()); \
if (!o_params.has_value()) { \
throw_invalid_http_param("request body is not valid JSON"); \
} \
auto& params = o_params.value(); \
auto that_json = &params;
#define HTTP_REQUIRE_VALUE(NAME, EXPRESSION) \
auto o_http_##NAME = (EXPRESSION); \
if (!o_http_##NAME.has_value()) { \
throw_invalid_http_param(#NAME); \
} \
auto NAME = std::move(o_http_##NAME).value();
#define HTTP_REQUIRE_PTR(NAME, EXPRESSION) \
auto NAME = (EXPRESSION); \
if ((NAME) == nullptr) { \
throw_invalid_http_param(#NAME); \
}
#define HTTP_REQUIRE_TRUE(EXPRESSION, DETAIL) \
if (!(EXPRESSION)) { \
throw_invalid_http_param(DETAIL); \
}
template <typename Value_Type>
class Ordered_List {
public:
@@ -218,9 +218,7 @@ public:
// Ret_J(speed);
std::string t = "null";
if (total_num != 0) {
t = std::to_string(static_cast<double>(crc_error_num) /
static_cast<double>(total_num)) +
"%";
t = std::to_string(static_cast<double>(crc_error_num) / static_cast<double>(total_num)) + "%";
}
ret.append({"crc误码率", t});
for (auto& cur : statistic_map) {
@@ -301,12 +299,8 @@ public:
if (iter != statistic.result_num.end()) {
num = iter->second;
}
double rate = static_cast<double>(num) /
static_cast<double>(statistic.total_num) * 100.0;
type_result.append({
Psc::to_string(result_type),
std::format("{} {:.2f}%", num, rate)
});
double rate = static_cast<double>(num) / static_cast<double>(statistic.total_num) * 100.0;
type_result.append({Psc::to_string(result_type), std::format("{} {:.2f}%", num, rate)});
}
result.append({Psc::to_string(cpr_type), type_result});
}
@@ -318,11 +312,8 @@ protected:
std::map<SSR::CPR_Ret_Type, size_t> result_num;
};
inline static constexpr std::array statistic_types{
SSR::CPR_Ret_Type::Speed_Error,
SSR::CPR_Ret_Type::Parse_OK,
SSR::CPR_Ret_Type::Inter_Error,
SSR::CPR_Ret_Type::Time_Space_Too_Long
};
SSR::CPR_Ret_Type::Speed_Error, SSR::CPR_Ret_Type::Parse_OK, SSR::CPR_Ret_Type::Inter_Error,
SSR::CPR_Ret_Type::Time_Space_Too_Long, SSR::CPR_Ret_Type::Out_of_Maximum_Detection_Range};
std::map<SSR::CPR_Type, Statistic_Data> statistic_map;
};
class Mode_S_Statistic_Data {
@@ -336,21 +327,14 @@ public:
Ret_J(total_num);
std::string t = "null";
if (total_num != 0) {
t = std::format(
"{}%",
static_cast<double>(crc_error_num) /
static_cast<double>(total_num)
);
t = std::format("{}%", static_cast<double>(crc_error_num) / static_cast<double>(total_num));
}
Ret_J(crc_error_num);
ret.append({"crc_error_rate", t});
Psc::JSON df_sub_type = Psc::JSON::object();
for (auto& cur : DF_Statistic_Data_map) {
df_sub_type.append({
Psc::to_string(cur.first) + " [" +
std::to_string(static_cast<int>(cur.first)) + "]",
cur.second.to_json()
});
df_sub_type.append({Psc::to_string(cur.first) + " [" + std::to_string(static_cast<int>(cur.first)) + "]",
cur.second.to_json()});
}
ret.append({"DF子类型", df_sub_type});
ret.append({"CPR统计信息", cpr_statistic_data.to_json()});
@@ -362,9 +346,7 @@ public:
void add_cpr_error(SSR::CPR_Type cpr_type, SSR::CPR_Ret_Type result_type) {
cpr_statistic_data.add(cpr_type, result_type);
}
DF_Statistic_Data* get_create_df_statistic_data(
const SSR::Downlink_Format& key
) {
DF_Statistic_Data* get_create_df_statistic_data(const SSR::Downlink_Format& key) {
DF_Statistic_Data* t;
auto iter = DF_Statistic_Data_map.find(key);
if (iter == DF_Statistic_Data_map.end()) {
+140 -1
View File
@@ -5,6 +5,7 @@
#include <algorithm>
#include <filesystem>
#include <string_view>
#include <utility>
#undef interface
std::string default_config_path;
@@ -49,6 +50,59 @@ static Psc::JSON write_string_array(std::string_view key, const std::vector<std:
}
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_optional_model_object_field(const Psc::JSON* json, std::string_view key, std::map<std::string, Map_Model_Item_Config> fallback) {
auto field = json->get(key);
if (field == nullptr) {
return fallback;
}
if (field->valueType != Psc::Object) {
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key));
}
for (const auto& child : field->children) {
if (child.valueType != Psc::Object) {
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), std::string(key));
}
fallback[child.key].from_base_json(&child);
}
return fallback;
}
static Psc::JSON write_model_object(std::string_view key, const std::map<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_string_field(const Psc::JSON* json, std::string_view key) {
auto field = json->get(key);
if (field == nullptr || field->valueType != Psc::String) {
@@ -242,6 +296,31 @@ Psc::JSON Map_Camera_Config::to_base_json() const {
bool Map_View_Config::is_scene_mode(std::string_view mode) {
return mode == "2d" || mode == "2.5d" || mode == "3d";
}
bool Map_Tile_View_Config::is_tile_zoom_mode(std::string_view mode) {
return mode == "native" || mode == "upscale" || mode == "both";
}
void Map_Tile_View_Config::from_base_json(const Psc::JSON* that_json) {
if (that_json == nullptr) {
return;
}
if (that_json->valueType != Psc::Object) {
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map_tile_view");
}
current_imagery_key = read_optional_string_field(that_json, "current_imagery_key", current_imagery_key);
auto next_tile_zoom_mode = read_optional_string_field(that_json, "tile_zoom_mode", tile_zoom_mode);
if (!is_tile_zoom_mode(next_tile_zoom_mode)) {
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "tile_zoom_mode");
}
tile_zoom_mode = next_tile_zoom_mode;
tile_display_maximum_level = std::clamp(read_optional_uint32_field(that_json, "tile_display_maximum_level", tile_display_maximum_level), 0u, 24u);
}
Psc::JSON Map_Tile_View_Config::to_base_json() const {
auto ret = Psc::JSON::object();
ret.append({"current_imagery_key", current_imagery_key});
ret.append({"tile_zoom_mode", tile_zoom_mode});
ret.append({"tile_display_maximum_level", tile_display_maximum_level});
return ret;
}
void Map_View_Config::from_base_json(const Psc::JSON* that_json) {
if (that_json == nullptr) {
return;
@@ -254,11 +333,15 @@ void Map_View_Config::from_base_json(const Psc::JSON* that_json) {
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "scene_mode");
}
scene_mode = next_scene_mode;
map2d.from_base_json(that_json->get("map2d"));
map3d.from_base_json(that_json->get("map3d"));
camera.from_base_json(that_json->get("camera"));
}
Psc::JSON Map_View_Config::to_base_json() const {
auto ret = Psc::JSON::object();
ret.append({"scene_mode", scene_mode});
ret.append({"map2d", map2d.to_base_json()});
ret.append({"map3d", map3d.to_base_json()});
ret.append({"camera", camera.to_base_json()});
return ret;
}
@@ -279,7 +362,7 @@ void Cesium_Graphics_Config::from_base_json(const Psc::JSON* that_json) {
enable_lighting = read_optional_bool_field(that_json, "enable_lighting", enable_lighting);
maximum_screen_space_error = std::clamp(read_optional_uint32_field(that_json, "maximum_screen_space_error", maximum_screen_space_error), 1u, 16u);
terrain_enabled_in_3d = read_optional_bool_field(that_json, "terrain_enabled_in_3d", terrain_enabled_in_3d);
terrain_exaggeration = std::clamp(read_optional_double_field(that_json, "terrain_exaggeration", terrain_exaggeration), 0.0, 5.0);
terrain_exaggeration = read_optional_double_field(that_json, "terrain_exaggeration", terrain_exaggeration);
terrain_limit_maximum_level = read_optional_bool_field(that_json, "terrain_limit_maximum_level", terrain_limit_maximum_level);
terrain_maximum_level = std::clamp(read_optional_uint32_field(that_json, "terrain_maximum_level", terrain_maximum_level), 0u, 24u);
terrain_cache_tiles = std::clamp(read_optional_uint32_field(that_json, "terrain_cache_tiles", terrain_cache_tiles), 16u, 2048u);
@@ -302,6 +385,49 @@ Psc::JSON Cesium_Graphics_Config::to_base_json() const {
ret.append({"terrain_cache_tiles", terrain_cache_tiles});
return ret;
}
Map_Model_Item_Config::Map_Model_Item_Config(std::string url)
: url(std::move(url)) {}
Map_Model_Item_Config::Map_Model_Item_Config(std::string url, double built_in_size, double heading, double pitch, double roll)
: url(std::move(url)), built_in_size(built_in_size), heading_offset_degrees(heading), pitch_offset_degrees(pitch), roll_offset_degrees(roll) {}
void Map_Model_Item_Config::from_base_json(const Psc::JSON* that_json) {
if (that_json == nullptr || that_json->valueType != Psc::Object) {
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map_model_item");
}
url = read_optional_string_field(that_json, "url", url);
built_in_size = read_optional_double_field(that_json, "built_in_size", built_in_size);
heading_offset_degrees = read_optional_double_field(that_json, "heading_offset_degrees", heading_offset_degrees);
pitch_offset_degrees = read_optional_double_field(that_json, "pitch_offset_degrees", pitch_offset_degrees);
roll_offset_degrees = read_optional_double_field(that_json, "roll_offset_degrees", roll_offset_degrees);
}
Psc::JSON Map_Model_Item_Config::to_base_json() const {
auto ret = Psc::JSON::object();
ret.append({"url", url});
ret.append({"built_in_size", built_in_size});
ret.append({"heading_offset_degrees", heading_offset_degrees});
ret.append({"pitch_offset_degrees", pitch_offset_degrees});
ret.append({"roll_offset_degrees", roll_offset_degrees});
return ret;
}
Map_Model_Config::Map_Model_Config()
: aircraft_models(make_default_aircraft_models()) {}
void Map_Model_Config::from_base_json(const Psc::JSON* that_json) {
if (that_json == nullptr) {
return;
}
if (that_json->valueType != Psc::Object) {
throw Psc::json_assign_error(std::make_error_code(std::errc::invalid_argument), "map_models");
}
aircraft_model.from_base_json(that_json->get("aircraft_model"));
aircraft_models = read_optional_model_object_field(that_json, "aircraft_models", make_default_aircraft_models());
base_station_model.from_base_json(that_json->get("base_station_model"));
}
Psc::JSON Map_Model_Config::to_base_json() const {
auto ret = Psc::JSON::object();
ret.append({"aircraft_model", aircraft_model.to_base_json()});
ret.append(write_model_object("aircraft_models", aircraft_models));
ret.append({"base_station_model", base_station_model.to_base_json()});
return ret;
}
void Mode_ACS_Config::server(Global *g) {
auto &svr = g->svr;
@@ -318,6 +444,19 @@ void Mode_ACS_Config::server(Global *g) {
CHECK_JSON_PARAM
auto &j = o_params.value();
init_base_json(&j);
if (auto min_points = j.try_get_number<std::size_t>("aircraft_change_list_min_position_points")) {
aircraft_change_list_min_position_points = min_points.value();
}
if (auto range_filter = j.try_get_bool("aircraft_change_list_adsb_range_filter")) {
aircraft_change_list_adsb_range_filter = range_filter.value();
}
if (auto range_factor = j.try_get_number<double>("aircraft_change_list_adsb_range_factor")) {
aircraft_change_list_adsb_range_factor = range_factor.value();
}
if (auto target_altitude = j.try_get_number<double>("adsb_theoretical_target_altitude_meters")) {
adsb_theoretical_target_altitude_meters = target_altitude.value();
}
Global::save();
res->setBody(warp(to_base_json()).to_json_string());
});
}
+38
View File
@@ -7,6 +7,7 @@
#include "MLAT.h"
#include "Source_Feed_Relation.h"
#include <fstream>
#include <map>
#include <vector>
inline bool delete_log = false;
extern std::string default_config_path;
@@ -56,6 +57,10 @@ struct Mode_ACS_Config_Data {
Psc::Copyable_Atomic<std::size_t> mode_other_max_num{};
Psc::Copyable_Atomic<std::size_t> report_data_feed_msg_mum{};
Psc::Copyable_Atomic<std::size_t> default_min_aircraft_list_num{};
Psc::Copyable_Atomic<std::size_t> aircraft_change_list_min_position_points = 2;
Psc::Copyable_Atomic<bool> aircraft_change_list_adsb_range_filter = true;
Psc::Copyable_Atomic<double> aircraft_change_list_adsb_range_factor = 1.2;
Psc::Copyable_Atomic<double> adsb_theoretical_target_altitude_meters = 10000.0;
Psc::Copyable_Atomic<bool> ignore_df_11{};
Psc::Copyable_Atomic<bool> monitor_msg_live{};
PSC_USE_JSON
@@ -148,8 +153,18 @@ struct Map_Camera_Config {
void from_base_json(const Psc::JSON* that_json);
[[nodiscard]] Psc::JSON to_base_json() const;
};
struct Map_Tile_View_Config {
std::string current_imagery_key = "imagery";
std::string tile_zoom_mode = "native";
std::uint32_t tile_display_maximum_level = 19;
void from_base_json(const Psc::JSON* that_json);
[[nodiscard]] Psc::JSON to_base_json() const;
[[nodiscard]] static bool is_tile_zoom_mode(std::string_view mode);
};
struct Map_View_Config {
std::string scene_mode = "3d";
Map_Tile_View_Config map2d;
Map_Tile_View_Config map3d;
Map_Camera_Config camera;
void from_base_json(const Psc::JSON* that_json);
[[nodiscard]] Psc::JSON to_base_json() const;
@@ -173,6 +188,26 @@ struct Cesium_Graphics_Config {
void from_base_json(const Psc::JSON* that_json);
[[nodiscard]] Psc::JSON to_base_json() const;
};
struct Map_Model_Item_Config {
std::string url;
double built_in_size = 50.0;
double heading_offset_degrees = 0.0;
double pitch_offset_degrees = 0.0;
double roll_offset_degrees = 0.0;
Map_Model_Item_Config() = default;
explicit Map_Model_Item_Config(std::string url);
Map_Model_Item_Config(std::string url, double built_in_size, double heading, double pitch, double roll);
void from_base_json(const Psc::JSON* that_json);
[[nodiscard]] Psc::JSON to_base_json() const;
};
struct Map_Model_Config {
Map_Model_Item_Config aircraft_model{"/ui/model/aircraft.glb", 50.0, -90.0, 0.0, 0.0};
std::map<std::string, Map_Model_Item_Config> aircraft_models;
Map_Model_Item_Config base_station_model{"/ui/model/base-station.glb", 100.0, 0.0, 0.0, 0.0};
Map_Model_Config();
void from_base_json(const Psc::JSON* that_json);
[[nodiscard]] Psc::JSON to_base_json() const;
};
struct Net_Config {
std::string key{};
std::string ip{};
@@ -229,6 +264,7 @@ public:
Map_Resources_Config map_resources_config;
Map_View_Config map_view_config;
Cesium_Graphics_Config cesium_graphics_config;
Map_Model_Config map_model_config;
Mode_ACS_Config mode_acs;
Log_Config log_config;
Console_Config console_config{};
@@ -261,6 +297,7 @@ public:
map_resources_config.from_base_json(that_json->get("map_resources"));
map_view_config.from_base_json(that_json->get("map_view"));
cesium_graphics_config.from_base_json(that_json->get("cesium_graphics"));
map_model_config.from_base_json(that_json->get("map_models"));
device_config.init(that_json->get("device"));
mode_acs.init(that_json->get("mode_acs"));
console_config.from_base_json(that_json->get("console"));
@@ -279,6 +316,7 @@ public:
{"web_server", web_server_config.to_base_json()},
{"map_resources", map_resources_config.to_base_json()},
{"map_view", map_view_config.to_base_json()},
{"map_models", map_model_config.to_base_json()},
{"device", device_config.to_base_json()},
{"mode_acs", mode_acs.to_base_json()},
//{"ais", ais.to_json()},
+6
View File
@@ -148,6 +148,12 @@ void Global::init_web_server() {
httplib::detail::read_file(path, content);
auto content_type = httplib::detail::find_content_type(
path, {}, "application/octet-stream");
if (path.ends_with(".glb")) {
content_type = "model/gltf-binary";
}
else if (path.ends_with(".gltf")) {
content_type = "model/gltf+json";
}
res->setContentTypeString(content_type);
res->setBody(content);
// res.set_file_content(path);
+134
View File
@@ -399,6 +399,135 @@ Psc::JSON make_map_view_response(Global* global) {
ret.append({"current_imagery_key", global->map_resources_config.current_imagery_key});
return ret;
}
bool is_multipart_request(const drogon::HttpRequestPtr& req) {
auto content_type = req->getHeader("content-type");
return content_type.find("multipart/form-data") != std::string::npos;
}
std::string normalize_upload_extension(std::string extension) {
std::ranges::transform(extension, extension.begin(), [](unsigned char ch) {
return static_cast<char>(std::tolower(ch));
});
if (!extension.empty() && extension.front() != '.') {
extension.insert(extension.begin(), '.');
}
return extension;
}
std::filesystem::path web_model_directory(Global* global) {
return std::filesystem::path(global->web_server_config.get_true_webapp()) / "model";
}
std::filesystem::path source_model_directory(Global* global) {
return std::filesystem::path(global->web_server_config.get_true_webapp()).parent_path() / "model";
}
bool is_safe_aircraft_model_key(std::string_view key) {
return !key.empty() && std::ranges::all_of(key, [](unsigned char ch) {
return std::isalnum(ch) != 0 || ch == '_';
});
}
std::string model_file_name(std::string_view model_type, std::string_view aircraft_model_key) {
if (model_type == "aircraft") {
return "aircraft-custom.glb";
}
if (model_type == "aircraft_type") {
if (!is_safe_aircraft_model_key(aircraft_model_key)) {
throw std::invalid_argument("invalid aircraft_model_key");
}
auto normalized_key = std::string(aircraft_model_key);
std::ranges::replace(normalized_key, '_', '-');
return "aircraft-" + normalized_key + ".glb";
}
if (model_type == "base_station") {
return "base-station-custom.glb";
}
throw std::invalid_argument("invalid model_type");
}
void save_uploaded_model_file(const drogon::HttpFile& upload,
std::string_view model_type,
std::string_view aircraft_model_key,
Global* global) {
auto extension = normalize_upload_extension(std::string(upload.getFileExtension()));
if (extension != ".glb") {
throw std::invalid_argument("model file must be .glb");
}
if (model_type == "aircraft_type" && !global->map_model_config.aircraft_models.contains(std::string(aircraft_model_key))) {
throw std::invalid_argument("unknown aircraft_model_key");
}
auto upload_dir = std::filesystem::path(drogon::app().getUploadPath());
std::filesystem::create_directories(upload_dir);
auto temp_name = std::string(model_type) + "_" + std::string(aircraft_model_key) + "_model_upload.tmp";
if (upload.saveAs(temp_name) != 0) {
throw std::runtime_error("cannot save uploaded model");
}
auto temp_path = upload_dir / temp_name;
auto file_name = model_file_name(model_type, aircraft_model_key);
auto web_dir = web_model_directory(global);
auto source_dir = source_model_directory(global);
std::filesystem::create_directories(web_dir);
std::filesystem::create_directories(source_dir);
std::filesystem::copy_file(temp_path, web_dir / file_name, std::filesystem::copy_options::overwrite_existing);
std::filesystem::copy_file(temp_path, source_dir / file_name, std::filesystem::copy_options::overwrite_existing);
std::filesystem::remove(temp_path);
auto url = "/ui/model/" + file_name;
if (model_type == "aircraft") {
global->map_model_config.aircraft_model.url = url;
}
else if (model_type == "aircraft_type") {
global->map_model_config.aircraft_models[std::string(aircraft_model_key)].url = url;
}
else {
global->map_model_config.base_station_model.url = url;
}
}
void handle_map_model_upload(const drogon::HttpRequestPtr& req, Global* global) {
drogon::MultiPartParser parser;
if (parser.parse(req) != 0) {
throw std::invalid_argument("invalid multipart model upload");
}
auto model_type = parser.getOptionalParameter<std::string>("model_type");
if (!model_type || parser.getFiles().size() != 1) {
throw std::invalid_argument("model_type or file");
}
auto aircraft_model_key = parser.getOptionalParameter<std::string>("aircraft_model_key").value_or("");
save_uploaded_model_file(parser.getFiles().front(), *model_type, aircraft_model_key, global);
Global::save();
}
void handle_map_model_config_update(const drogon::HttpRequestPtr& req, Global* global) {
auto parsed = Psc::try_parse_json(req->body().data());
if (!parsed.has_value()) {
throw std::invalid_argument("invalid map_models");
}
global->map_model_config.from_base_json(&parsed.value());
Global::save();
}
void register_map_models_handler() {
drogon::app().registerHandlerViaRegex(
R"(^/map/models$)",
[](const drogon::HttpRequestPtr& req,
std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
auto global = Global::instance();
auto response = drogon::HttpResponse::newHttpResponse();
try {
if (req->method() == drogon::Post) {
if (is_multipart_request(req)) {
handle_map_model_upload(req, global);
}
else {
handle_map_model_config_update(req, global);
}
}
response->setStatusCode(drogon::k200OK);
response->setContentTypeCode(drogon::CT_APPLICATION_JSON);
response->setBody(global->map_model_config.to_base_json().to_json_string());
}
catch (const std::exception& error) {
response->setStatusCode(drogon::k400BadRequest);
response->setContentTypeCode(drogon::CT_TEXT_PLAIN);
response->setBody(Psc::platform_2_utf8(error.what()));
}
response->addHeader("Cache-Control", "no-store");
callback(response);
},
{drogon::Get, drogon::Post});
}
void register_map_view_handler() {
drogon::app().registerHandlerViaRegex(
R"(^/map/view$)",
@@ -426,6 +555,10 @@ void register_map_view_handler() {
try {
auto next_view = global->map_view_config;
next_view.from_base_json(&parsed.value());
if (!global->map_resources_config.is_imagery_key(next_view.map2d.current_imagery_key) ||
!global->map_resources_config.is_imagery_key(next_view.map3d.current_imagery_key)) {
throw std::invalid_argument("invalid current_imagery_key");
}
global->map_resources_config.current_imagery_key = key.value();
global->map_view_config = next_view;
Global::save();
@@ -453,6 +586,7 @@ void Global::init_tiles() {
auto terrain = std::make_shared<Tile_Source>(map_resources_config.terrain);
register_map_resources_handler();
register_map_graphics_handler();
register_map_models_handler();
register_map_view_handler();
register_tile_handler(R"(^/map/imagery/.*$)", "/map/imagery/", imagery);
register_tile_handler(R"(^/tiles/.*$)", "/tiles/", google_imagery);