59 Commits
Author SHA1 Message Date
waiwaylee 80193c5e4c Merge branch 'feature' 2026-10-29 15:15:57 +08:00
waiwaylee fe7e194f18 更新:1、更新文档;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-29 15:15:47 +08:00
waiwaylee eb9f30c273 更新:1、修复航迹手动转TAS时,输出航迹更新信息时没有完整更新航迹信息的问题;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-29 14:43:35 +08:00
waiwaylee 0f3da9994c 更新:1、增加航迹指纹计算,TAS控制命令下发航迹指纹,不再下发批号,实现TAS正常回放;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-29 14:24:00 +08:00
waiwaylee 24ce1e6dbf 更新:1、修改日志组件,使用spdlog日志库;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-28 13:43:29 +08:00
waiwaylee 7e4cb2922d 更新:1、将参数配置信息对象改为全局可访问,并避免通过函数参数传入;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-27 15:56:02 +08:00
waiwaylee 76b0b460a5 更新:1、调整构建脚本,去除扫描模式配置项;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-27 14:48:16 +08:00
waiwaylee 019af7a229 更新:1、增加扫描模式配置项;
2、优化不必要的宏定义;
3、合并机扫和相扫版本;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-27 14:04:51 +08:00
waiwaylee 423c200659 更新:1、修改参数配置方式,改为由配置文件输入;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-27 10:36:14 +08:00
waiwaylee 0e325fb153 Merge branch 'master' into feature 2026-10-26 10:46:14 +08:00
waiwaylee a40b71bf57 更新:1、更新文档;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-26 10:45:25 +08:00
waiwaylee 3ecb210139 Merge branch 'feature' 2026-10-26 09:41:56 +08:00
waiwaylee 1ffb28770d 更新:1、增加航迹质量记录到数据库;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-22 11:21:58 +08:00
waiwaylee 693dbcbf89 Merge branch 'feature' 2026-10-22 10:56:08 +08:00
waiwaylee 44d3278cdc Merge branch 'fix' into feature 2026-10-22 10:55:03 +08:00
waiwaylee 6284f3f10a 更新:1、修改TAS模式下高度平滑点数;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-21 15:22:54 +08:00
waiwaylee 5d105d2076 更新:1、修改相扫模式下TAS航迹消亡圈数;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 17:35:38 +08:00
waiwaylee 17a3d998a6 更新:1、优化数据记录的性能,满足实时运行要求;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 17:31:50 +08:00
waiwaylee 3dee734d89 更新:1、优化配置构建脚本;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 13:40:39 +08:00
waiwaylee 2273841aa5 Merge branch 'master' into feature 2026-10-20 11:23:12 +08:00
waiwaylee 332f270b46 1、更新文档;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 11:22:33 +08:00
waiwaylee 3a9b1c4924 Merge branch 'feature' 2026-10-20 11:10:56 +08:00
waiwaylee 3088ce4910 更新:1、更新版本号至V1.5.8
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 11:10:45 +08:00
waiwaylee bd011d2434 Merge branch 'feature' 2026-10-20 11:08:41 +08:00
waiwaylee 782bff5e8e 更新:1、调整航迹质量下降逻辑;
2、普通航迹消亡条件改为由航迹质量确定;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 10:47:05 +08:00
waiwaylee 4b70b24515 更新:1、修改航迹质量下降策略,改为按点迹未更新时间计算;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-20 09:55:17 +08:00
waiwaylee d9a7bd0ee1 更新:1、优化调整临时航迹质量评估逻辑;
2、航迹起始时增加航迹质量门限;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-19 16:09:37 +08:00
waiwaylee 1ae0cf2512 更新:1、增加航迹质量信息输出;
2、航迹起始时计算初始航迹质量;
3、航迹关联时更新航迹质量;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-19 15:03:00 +08:00
waiwaylee 1b6cf62115 Merge branch 'master' into feature 2026-10-16 09:54:41 +08:00
waiwaylee 5f54d74ac3 Merge branch 'feature' 2026-10-14 09:25:02 +08:00
waiwaylee 3e6763c615 更新:1、更新扫描一圈判断条件,区分机扫和相扫不同的情况;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-14 09:24:49 +08:00
waiwaylee c01185bf9c Merge branch 'feature' 2026-10-13 14:50:10 +08:00
waiwaylee b4aed8a37b 更新:1、回调TWS外推时间差计算方法,避免TAS时同步出现TWS航迹的问题;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-13 14:49:48 +08:00
waiwaylee e98280409e 更新:1、调整临时航迹记录时机,在转为可靠航迹前记录,避免转为正式航迹后丢失当前帧的临时航迹记录;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-10 17:44:19 +08:00
waiwaylee 56b5449b9f 更新:1、修改TWS外推时,时间差计算方法,不再使用固定数据率计算时间差,而是使用最新时间戳来计算;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-09 14:04:22 +08:00
waiwaylee 5cb67ccef8 更新:1、增加TAS航迹信息、TAS关联信息记录到数据库;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-09 10:58:53 +08:00
waiwaylee 9f37fba3e7 Merge branch 'update' 2026-10-08 16:37:19 +08:00
waiwaylee 3cab2339d2 Merge branch 'feature' into update 2026-10-08 16:36:47 +08:00
waiwaylee a6a7877a86 更新:1、更新文档;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-08 16:13:15 +08:00
waiwaylee b227ae1957 更新:1、数据处理增加版本号输出;
2、增加Debug模式下可靠航迹、关联信息等数据写入数据库;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-10-08 16:01:53 +08:00
waiwaylee c3c7e668d2 更新:1、增加临时航迹录入数据库;
2、增加点迹信息的圈ID记录;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-30 16:33:18 +08:00
waiwaylee a4f32e4036 更新:1、新增数据记录功能,将过程数据录入数据库;
2、增加输入点迹数据、凝聚后点迹数据记录;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-30 09:02:28 +08:00
waiwaylee ff9b111d34 更新:1、升级编译器版本至MSVC2022,升级C++至C++17;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-29 11:00:43 +08:00
waiwaylee 2934e3b59b 更新:1、更新文档;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-29 10:25:56 +08:00
waiwaylee ed7d6d7902 Merge branch 'feature' 2026-09-28 10:20:49 +08:00
waiwaylee e8c56f914b 更新:1、适配输出点迹归一化幅度和速度维FFT点数;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-28 09:28:01 +08:00
waiwaylee 42b598e2e5 更新:1、调整向站/背站判断条件;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-24 15:40:52 +08:00
waiwaylee fc49707b08 更新:1、调整向站/背站的判断逻辑,解决速度模糊时判断有误的问题;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-23 15:40:16 +08:00
waiwaylee 50e0b5cb74 更新:1、增加手动设置重点目标的功能;
2、调整航迹关联时外推圈数限制,不过多外推;
3、调整航迹消批条件,重点航迹消批圈数提高;
4、更新文档;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-23 09:35:13 +08:00
waiwaylee e0bf227be3 更新:1、航迹关联时增加高度门限判断,同时加大统计距离门限,提高机动目标关联概率;
2、修复TAS波束控制时,数据率判断有误的问题;
3、更新文档;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-22 09:20:12 +08:00
waiwaylee a76da34756 更新:1、更新项目文档;
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-18 11:14:38 +08:00
waiwaylee f249fdc50d 更新:1、增加航机信息中靠近/远离标志位输出;
2、增加项目文档;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-18 11:09:57 +08:00
waiwaylee 7d5a56f3d2 更新:1、修改一周扫描完成的判断条件,原来采用判断波位号变小为一周扫描完成,但机扫模式下,可能存在转台反转的情况,导致波位号变小,但并未扫描一周,因此改为判断波位号为0时,表示扫描一周完成;
2、更新点迹和航迹头关联条件,限制部分切向高速短航迹;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-16 11:06:01 +08:00
waiwaylee 604f1585f6 更新:1、TAS航迹不关联时间戳更早的TWS点迹,但标记点迹已使用,避免生成TWS航迹;
2、调整目录结构,将过期的文档都放到archived_docs文件夹中留档;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-12 10:01:15 +08:00
waiwaylee d6cdbc0120 更新:1、更新项目文档;
2、删除一些无用的代码;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-09 09:35:55 +08:00
waiwaylee 15215926c5 更新:1、增加日志模块,将标准输出信息写入到日志文件;
2、将RadarPara中DATA_RATE_TAS的单位由Hz改为s;
3、修复TAS_Ctrl::tas_beam_output函数中给Tracking_beam->TAS_track_index赋值错误的问题;

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-07 19:16:32 +08:00
waiwaylee 5f820987a9 更新:1、修复全部C4819 / C4018 / C4244警告;
2、更新项目文档;
2026-09-07 14:29:24 +08:00
waiwaylee f438ac53d9 更新:1、优化TAS逻辑
2、优化宏定义

Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-07 09:48:58 +08:00
waiwaylee e729c5baee 更新:修改FREQ0宏定义为9.2GHz
Signed-off-by: waiwaylee <waiwaylee@foxmail.com>
2026-09-02 11:30:10 +08:00
268 changed files with 374050 additions and 2029 deletions
+3
View File
@@ -3,4 +3,7 @@ build-data_process_class_dll-Desktop_Qt_5_7_0_MSVC2013_32bit-Release/
debug/
release/
build/
rdp_logs/
rdp_records/
design_docs/
.mnemon/
+7 -30
View File
@@ -2,48 +2,25 @@
"version": 4,
"configurations": [
{
"compilerPath": "D:\\APP\\VisualStudio2013\\VC\\bin\\cl.exe",
"cStandard": "c11",
"cppStandard": "c++11",
"intelliSenseMode": "windows-msvc-x86",
"compilerPath": "D:\\APP\\VisualStudio2022\\VC\\Tools\\MSVC\\14.44.35207\\bin\\Hostx64\\x64\\cl.exe",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "windows-msvc-x64",
"defines": [
"WIN32",
"_WINDOWS",
"UNICODE",
"_UNICODE",
"NOMINMAX",
"DATA_PROCESS_CLASS_DLL_LIBRARY",
"PHASE_SCANNING"
"DATA_PROCESS_CLASS_DLL_LIBRARY"
],
"includePath": [
"${workspaceFolder}/data_process_class_dll",
"${workspaceFolder}/data_process_class_dll/Eigen",
"${workspaceFolder}/**"
],
"name": "X256_PS (MSVC2013 x86)",
"compileCommands": "${workspaceFolder}/build/X256_PS/compile_commands.json"
},
{
"compilerPath": "D:\\APP\\VisualStudio2013\\VC\\bin\\cl.exe",
"cStandard": "c11",
"cppStandard": "c++11",
"intelliSenseMode": "windows-msvc-x86",
"defines": [
"WIN32",
"_WINDOWS",
"UNICODE",
"_UNICODE",
"NOMINMAX",
"DATA_PROCESS_CLASS_DLL_LIBRARY",
"MECHANICAL_SCANNING"
],
"includePath": [
"${workspaceFolder}/data_process_class_dll",
"${workspaceFolder}/data_process_class_dll/Eigen",
"${workspaceFolder}/**"
],
"name": "X256_MS (MSVC2013 x86)",
"compileCommands": "${workspaceFolder}/build/X256_MS/compile_commands.json"
"name": "X256 (MSVC2022 x64)",
"compileCommands": "${workspaceFolder}/build/Debug/REC_ON/compile_commands.json"
}
]
}
+17 -12
View File
@@ -3,23 +3,28 @@ buildType:
choices:
release:
short: Release
long: Release (MSVC2013 x86)
long: Release (MSVC2022 x64, C++17)
buildType: Release
debug:
short: Debug
long: Debug (MSVC2013 x86)
long: Debug (MSVC2022 x64, C++17)
buildType: Debug
scanMode:
default: ps
recordMode:
default: REC_OFF
choices:
ps:
short: X256_PS
long: X256_PS - PHASE_SCANNING
REC_AUTO:
short: REC_AUTO
long: REC_AUTO - RDP_DATA_RECORD=AUTO (Debug 启用 / Release 关闭)
settings:
RADAR_SCAN_MODE: PHASE_SCANNING
ms:
short: X256_MS
long: X256_MS - MECHANICAL_SCANNING
RDP_DATA_RECORD: AUTO
REC_OFF:
short: REC_OFF
long: REC_OFF - RDP_DATA_RECORD=OFF (不编译记录模块)
settings:
RADAR_SCAN_MODE: MECHANICAL_SCANNING
RDP_DATA_RECORD: OFF
REC_ON:
short: REC_ON
long: REC_ON - RDP_DATA_RECORD=ON (强制启用记录模块)
settings:
RDP_DATA_RECORD: ON
+4 -4
View File
@@ -5,14 +5,14 @@
"name": "调试 DLL",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/build/X256_PS/bin/rdp_playback.exe",
"args": ["-d", "E:/Demo/QT/simianzhen256-v1.5.5-ms-vscode/build/X256_PS/bin/2026-07-18/15-34-32", "-i", "E:/Demo/QT/simianzhen256-v1.5.5-ms-vscode/build/X256_PS/bin/RadarConfigParam.ini", "-c", "E:/Demo/QT/simianzhen256-v1.5.5-ms-vscode/build/X256_PS/bin/2026-07-18/15-34-32/track.csv"],
"program": "${workspaceFolder}/build/Debug/REC_ON/bin/rdp_playback.exe",
"args": ["-d", "${workspaceFolder}/build/Debug/REC_ON/bin/2026-07-18/15-34-32", "-i", "${workspaceFolder}/build/Debug/REC_ON/bin/RadarConfigParam.ini", "-c", "${workspaceFolder}/build/Debug/REC_ON/bin/2026-07-18/15-34-32/track.csv"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"preLaunchTask": "CMake: 配置并构建 X256_PS (Debug)",
"symbolSearchPath": "${workspaceFolder}/build/X256_PS/bin"
"preLaunchTask": "配置并构建(Debug/REC_ON)",
"symbolSearchPath": "${workspaceFolder}/build/Debug/REC_ON/bin"
},
{
"name": "附加到宿主进程(调试 DLL)",
+37 -6
View File
@@ -1,11 +1,42 @@
@echo off
rem ===========================================================================
rem CMake build script (MSVC 2022 / v143, C++17, x64)
rem
rem Usage:
rem build.bat ^<Debug^|Release^> [AUTO^|ON^|OFF]
rem
rem Examples:
rem build.bat Release OFF
rem build.bat Debug ON
rem
rem The build tree must have been configured by configure.bat first (same
rem configuration): build\<Debug^|Release^>\<REC_AUTO^|REC_OFF^|REC_ON^>
rem
rem Encoding: keep this file ASCII-only (see msvc_env.bat).
rem ===========================================================================
setlocal
set "BUILD_DIR=%~1"
if "%BUILD_DIR%"=="" (
echo Usage: build.bat ^<build-dir^>
if "%~1"=="" goto :usage
call "%~dp0config_paths.bat" "%~1" "%~2"
if errorlevel 1 exit /b 1
rem Load the MSVC 2022 environment (vcvarsall.bat + cl.exe existence check).
call "%~dp0msvc_env.bat"
if errorlevel 1 exit /b 1
pushd "%~dp0..\.."
if not exist "%BUILD_DIR%\CMakeCache.txt" (
echo [ERROR] "%BUILD_DIR%" is not configured - run configure.bat first
popd
exit /b 1
)
call D:\APP\VisualStudio2013\VC\vcvarsall.bat x86 >nul
if errorlevel 1 exit /b %errorlevel%
echo [build] %BUILD_DIR%
cmake --build "%BUILD_DIR%"
exit /b %errorlevel%
set "CMAKE_EXIT=%errorlevel%"
popd
exit /b %CMAKE_EXIT%
:usage
echo Usage: build.bat ^<Debug^|Release^> [AUTO^|ON^|OFF^]
exit /b 1
+49
View File
@@ -0,0 +1,49 @@
@echo off
rem ===========================================================================
rem Configure and build one build configuration (build type + record mode).
rem
rem Usage:
rem build_config.bat ^<Debug^|Release^> [AUTO^|ON^|OFF]
rem
rem Examples:
rem build_config.bat Release OFF
rem build_config.bat Debug ON
rem build_config.bat Release AUTO
rem
rem Runs configure.bat + build.bat for the same configuration:
rem build\<Debug^|Release^>\<REC_AUTO^|REC_OFF^|REC_ON^>
rem The mechanical-scanning and phase-scanning variants are merged into one
rem build; the scan mode is a runtime config item (TOML [radar_params] scan_mode).
rem The run stops at the first failure and returns non-zero.
rem
rem Encoding: keep this file ASCII-only (see msvc_env.bat).
rem ===========================================================================
setlocal EnableExtensions
set "BUILD_TYPE=%~1"
set "RECORD_MODE=%~2"
if not defined BUILD_TYPE goto :usage
if not defined RECORD_MODE set "RECORD_MODE=AUTO"
rem Validate + normalize the configuration once.
call "%~dp0config_paths.bat" "%BUILD_TYPE%" "%RECORD_MODE%"
if errorlevel 1 exit /b 1
echo.
echo [build_config] ==================== %BUILD_TYPE% / %RECORD_MODE% ====================
call "%~dp0configure.bat" "%BUILD_TYPE%" "%RECORD_MODE%"
if errorlevel 1 goto :failed
call "%~dp0build.bat" "%BUILD_TYPE%" "%RECORD_MODE%"
if errorlevel 1 goto :failed
echo.
echo [build_config] all done: %BUILD_TYPE% / %RECORD_MODE%
exit /b 0
:failed
echo.
echo [build_config] FAILED - stopped at the first failure, see the log above
exit /b 1
:usage
echo Usage: build_config.bat ^<Debug^|Release^> [AUTO^|ON^|OFF^]
exit /b 1
+39 -7
View File
@@ -1,11 +1,43 @@
@echo off
rem ===========================================================================
rem CMake clean script - remove the build output of one configuration.
rem
rem Usage:
rem clean.bat ^<Debug^|Release^> [AUTO^|ON^|OFF]
rem
rem Examples:
rem clean.bat Release OFF
rem clean.bat Debug ON
rem
rem The directory removed is the same one configure.bat/build.bat use:
rem build\<Debug^|Release^>\<REC_AUTO^|REC_OFF^|REC_ON^>
rem Use clean_all.bat to clean every configuration at once.
rem
rem Encoding: keep this file ASCII-only (see msvc_env.bat).
rem ===========================================================================
setlocal
set "BUILD_DIR=%~1"
if "%BUILD_DIR%"=="" (
echo Usage: clean.bat ^<build-dir^>
if "%~1"=="" goto :usage
call "%~dp0config_paths.bat" "%~1" "%~2"
if errorlevel 1 exit /b 1
pushd "%~dp0..\.."
if not exist "%BUILD_DIR%" (
echo [clean] "%BUILD_DIR%" does not exist - nothing to do
popd
exit /b 0
)
echo [clean] remove "%BUILD_DIR%"
rmdir /s /q "%BUILD_DIR%"
if exist "%BUILD_DIR%" (
echo [ERROR] cannot remove "%BUILD_DIR%", close open files and retry.
popd
exit /b 1
)
call D:\APP\VisualStudio2013\VC\vcvarsall.bat x86 >nul
if errorlevel 1 exit /b %errorlevel%
cmake --build "%BUILD_DIR%" --target clean
exit /b %errorlevel%
popd
exit /b 0
:usage
echo Usage: clean.bat ^<Debug^|Release^> [AUTO^|ON^|OFF^]
exit /b 1
+54
View File
@@ -0,0 +1,54 @@
@echo off
rem ===========================================================================
rem Remove the build output of every build configuration.
rem
rem Usage:
rem clean_all.bat
rem
rem Build layout (see config_paths.bat):
rem build\<Debug^|Release^>\<REC_AUTO^|REC_OFF^|REC_ON^>
rem
rem The legacy per-scan-mode roots build\X256_PS and build\X256_MS (used before
rem the mechanical/phase variants were merged) are removed as a whole as well.
rem build\output (archived package export) is never touched.
rem
rem Encoding: keep this file ASCII-only (see msvc_env.bat).
rem ===========================================================================
setlocal EnableExtensions
pushd "%~dp0..\.."
set "FAILED="
rem legacy layout: one root per scan mode (contains its own build tree directly)
for %%S in (X256_PS X256_MS) do (
call :remove "build\%%S"
)
rem current layout: build\<Debug|Release>\<REC_*>
for %%T in (Debug Release) do (
for %%R in (REC_AUTO REC_OFF REC_ON) do (
call :remove "build\%%T\%%R"
)
rem drop the build type root when every configuration is gone
rmdir "build\%%T" 2>nul
)
echo.
echo [clean_all] archived package export "build\output" left untouched
if defined FAILED (
echo [clean_all] FAILED - some directories could not be removed, close open files and retry
popd
exit /b 1
)
echo [clean_all] all build configurations removed
popd
exit /b 0
:remove
if not exist "%~1" exit /b 0
echo [clean_all] remove "%~1"
rmdir /s /q "%~1"
if exist "%~1" (
echo [ERROR] cannot remove "%~1"
set "FAILED=1"
)
exit /b 0
+57
View File
@@ -0,0 +1,57 @@
@echo off
rem ===========================================================================
rem Derive the build directory of one build configuration.
rem
rem Internal helper, called by configure.bat / build.bat / clean.bat /
rem build_config.bat as:
rem call config_paths.bat ^<Debug^|Release^> [AUTO^|ON^|OFF]
rem
rem The mechanical-scanning and phase-scanning variants are merged into one
rem build since V1.5.9, so a configuration is only build type + record mode.
rem The scan mode is a runtime config item (TOML [radar_params] scan_mode).
rem
rem On success the following variables are exported into the caller scope:
rem BUILD_TYPE Debug / Release
rem RECORD_MODE AUTO / ON / OFF
rem REC_TAG REC_AUTO / REC_OFF / REC_ON
rem BUILD_DIR build\<BUILD_TYPE>\<REC_TAG>
rem
rem Exit code: 0 = ok, 1 = invalid configuration.
rem
rem Encoding: keep this file ASCII-only (see msvc_env.bat).
rem ===========================================================================
set "BUILD_TYPE="
set "RECORD_MODE="
set "REC_TAG="
set "BUILD_DIR="
if "%~1"=="" goto :usage
if /i "%~1"=="Debug" set "BUILD_TYPE=Debug"
if /i "%~1"=="Release" set "BUILD_TYPE=Release"
if not defined BUILD_TYPE (
echo [ERROR] invalid build type: "%~1"
echo expected Debug or Release
exit /b 1
)
if not "%~2"=="" set "RECORD_MODE=%~2"
if not defined RECORD_MODE set "RECORD_MODE=AUTO"
if /i "%RECORD_MODE%"=="AUTO" set "RECORD_MODE=AUTO"
if /i "%RECORD_MODE%"=="ON" set "RECORD_MODE=ON"
if /i "%RECORD_MODE%"=="OFF" set "RECORD_MODE=OFF"
if "%RECORD_MODE%"=="AUTO" set "REC_TAG=REC_AUTO"
if "%RECORD_MODE%"=="ON" set "REC_TAG=REC_ON"
if "%RECORD_MODE%"=="OFF" set "REC_TAG=REC_OFF"
if not defined REC_TAG (
echo [ERROR] invalid record mode: "%~2"
echo expected AUTO or ON or OFF
exit /b 1
)
set "BUILD_DIR=build\%BUILD_TYPE%\%REC_TAG%"
exit /b 0
:usage
echo Usage: config_paths.bat ^<Debug^|Release^> [AUTO^|ON^|OFF^]
exit /b 1
+69 -16
View File
@@ -1,21 +1,74 @@
@echo off
rem ===========================================================================
rem CMake configure script (MSVC 2022 / v143, C++17, x64, NMake Makefiles)
rem
rem Usage:
rem configure.bat ^<Debug^|Release^> [AUTO^|ON^|OFF]
rem
rem Examples:
rem configure.bat Release OFF
rem configure.bat Debug ON
rem
rem The build directory is derived from the configuration (see
rem config_paths.bat): build\<Debug^|Release^>\<REC_AUTO^|REC_OFF^|REC_ON^>
rem The mechanical-scanning and phase-scanning variants are merged into one
rem build; the scan mode is a runtime config item (TOML [radar_params] scan_mode).
rem
rem The 2nd argument selects the optional data-recording debug feature
rem (CMake cache variable RDP_DATA_RECORD). When it is omitted, CMake's AUTO
rem default applies: Debug = enabled, Release = disabled.
rem See docs/data_record_debug.md.
rem
rem A build directory that was configured by an older compiler (for example
rem the MSVC 2013 cache) is removed first: neither its CMake cache nor its
rem object files can be reused across a compiler upgrade.
rem
rem Encoding: keep this file ASCII-only (see msvc_env.bat).
rem ===========================================================================
setlocal
set "BUILD_DIR=%~1"
set "SCAN_MODE=%~2"
set "BUILD_TYPE=%~3"
if "%BUILD_DIR%"=="" (
echo Usage: configure.bat ^<build-dir^> ^<PHASE_SCANNING^|MECHANICAL_SCANNING^> [Debug^|Release]
exit /b 1
)
if "%SCAN_MODE%"=="" (
echo Usage: configure.bat ^<build-dir^> ^<PHASE_SCANNING^|MECHANICAL_SCANNING^> [Debug^|Release]
exit /b 1
)
if "%BUILD_TYPE%"=="" set "BUILD_TYPE=Release"
call D:\APP\VisualStudio2013\VC\vcvarsall.bat x86 >nul
if errorlevel 1 exit /b %errorlevel%
if "%~1"=="" goto :usage
call "%~dp0config_paths.bat" "%~1" "%~2"
if errorlevel 1 exit /b 1
echo [configure] build dir : %BUILD_DIR%
echo [configure] settings : %BUILD_TYPE% / RDP_DATA_RECORD=%RECORD_MODE%
rem Load the MSVC 2022 environment (vcvarsall.bat + cl.exe existence check).
call "%~dp0msvc_env.bat"
if errorlevel 1 exit /b 1
pushd "%~dp0..\.."
cmake -S . -B "%BUILD_DIR%" -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DRADAR_SCAN_MODE=%SCAN_MODE%
if not exist "%BUILD_DIR%\CMakeCache.txt" goto :do_configure
set "OLD_TOOLSET="
if exist "%BUILD_DIR%\.msvc_toolset" set /p OLD_TOOLSET=<"%BUILD_DIR%\.msvc_toolset"
if /i "%OLD_TOOLSET%"=="%MSVC_TOOLSET_VERSION%" goto :do_configure
echo [configure] "%BUILD_DIR%" was configured with MSVC toolset "%OLD_TOOLSET%" - rebuilding from scratch
rmdir /s /q "%BUILD_DIR%"
if exist "%BUILD_DIR%" (
echo [ERROR] cannot remove "%BUILD_DIR%", please delete it manually and retry.
popd
exit /b 1
)
:do_configure
echo [configure] compiler : %MSVC_CXX_COMPILER%
cmake -S . -B "%BUILD_DIR%" -G "NMake Makefiles" ^
-DCMAKE_BUILD_TYPE=%BUILD_TYPE% ^
-DRDP_DATA_RECORD=%RECORD_MODE% ^
-DCMAKE_CXX_COMPILER="%MSVC_CXX_COMPILER%"
set "CMAKE_EXIT=%errorlevel%"
if not "%CMAKE_EXIT%"=="0" (
popd
exit /b %CMAKE_EXIT%
)
rem Remember which toolset produced this build tree (checked on the next run).
> "%BUILD_DIR%\.msvc_toolset" echo %MSVC_TOOLSET_VERSION%
popd
exit /b %CMAKE_EXIT%
exit /b 0
:usage
echo Usage: configure.bat ^<Debug^|Release^> [AUTO^|ON^|OFF^]
exit /b 1
+50
View File
@@ -0,0 +1,50 @@
@echo off
rem ===========================================================================
rem Shared MSVC 2022 (v143) environment setup.
rem
rem Called by configure.bat / build.bat / clean.bat. It:
rem 1) locates the MSVC 2022 toolset and cl.exe (default Hostx64\x64);
rem 2) calls vcvarsall.bat to set PATH / INCLUDE / LIB (nmake comes with it).
rem
rem NOTE: this script does NOT use setlocal, so the variables it defines stay
rem in the caller's environment (all callers run under setlocal).
rem
rem Override the defaults with these environment variables:
rem VS2022_DIR Visual Studio 2022 install root
rem MSVC_TOOLSET_VERSION MSVC toolset version
rem MSVC_HOST_ARCH compiler host architecture (x64 / x86)
rem MSVC_TARGET_ARCH target architecture (x64 / x86)
rem
rem Exit code: 0 = ok, non-zero = setup failed (caller must abort).
rem
rem Encoding: keep this file ASCII-only (cmd.exe parses .bat in the OEM code
rem page, UTF-8 comments would break parsing on non-UTF-8 consoles).
rem ===========================================================================
if not defined VS2022_DIR set "VS2022_DIR=D:\APP\VisualStudio2022"
if not defined MSVC_TOOLSET_VERSION set "MSVC_TOOLSET_VERSION=14.44.35207"
if not defined MSVC_HOST_ARCH set "MSVC_HOST_ARCH=x64"
if not defined MSVC_TARGET_ARCH set "MSVC_TARGET_ARCH=x64"
set "MSVC_TOOLSET_DIR=%VS2022_DIR%\VC\Tools\MSVC\%MSVC_TOOLSET_VERSION%"
set "MSVC_CXX_COMPILER=%MSVC_TOOLSET_DIR%\bin\Host%MSVC_HOST_ARCH%\%MSVC_TARGET_ARCH%\cl.exe"
set "VCVARS_BAT=%VS2022_DIR%\VC\Auxiliary\Build\vcvarsall.bat"
if not exist "%VCVARS_BAT%" (
echo [ERROR] vcvarsall.bat not found: "%VCVARS_BAT%"
echo Please set VS2022_DIR to the Visual Studio 2022 install root.
exit /b 1
)
if not exist "%MSVC_CXX_COMPILER%" (
echo [ERROR] cl.exe not found: "%MSVC_CXX_COMPILER%"
echo Please check MSVC_TOOLSET_VERSION / MSVC_HOST_ARCH / MSVC_TARGET_ARCH.
exit /b 1
)
call "%VCVARS_BAT%" %MSVC_TARGET_ARCH% >nul
if errorlevel 1 (
echo [ERROR] "vcvarsall.bat %MSVC_TARGET_ARCH%" failed.
exit /b 1
)
exit /b 0
+10 -6
View File
@@ -1,12 +1,16 @@
{
"cmake.configureOnOpen": false,
"cmake.generator": "NMake Makefiles",
"cmake.buildDirectory": "${workspaceFolder}/build/X256_PS",
"cmake.buildDirectory": "${workspaceFolder}/build/${buildType}/${variant:recordMode}",
"cmake.configureArgs": [
"-DCMAKE_CXX_COMPILER=D:/APP/VisualStudio2022/VC/Tools/MSVC/14.44.35207/bin/Hostx64/x64/cl.exe"
],
"cmake.environment": {
"PATH": "D:\\APP\\VisualStudio2013\\VC\\BIN;D:\\APP\\VisualStudio2013\\Common7\\IDE;C:\\Program Files (x86)\\Windows Kits\\8.1\\bin\\x86;${env:PATH}",
"INCLUDE": "D:\\APP\\VisualStudio2013\\VC\\INCLUDE;D:\\APP\\VisualStudio2013\\VC\\ATLMFC\\INCLUDE;C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\shared;C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\um;C:\\Program Files (x86)\\Windows Kits\\8.1\\include\\winrt;${env:INCLUDE}",
"LIB": "D:\\APP\\VisualStudio2013\\VC\\LIB;D:\\APP\\VisualStudio2013\\VC\\ATLMFC\\LIB;C:\\Program Files (x86)\\Windows Kits\\8.1\\lib\\winv6.3\\um\\x86;${env:LIB}"
"PATH": "D:\\APP\\VisualStudio2022\\VC\\Tools\\MSVC\\14.44.35207\\bin\\Hostx64\\x64;${env:PATH}",
"INCLUDE": "D:\\APP\\VisualStudio2022\\VC\\Tools\\MSVC\\14.44.35207\\include;D:\\APP\\VisualStudio2022\\VC\\Tools\\MSVC\\14.44.35207\\ATLMFC\\include;D:\\APP\\VisualStudio2022\\VC\\Auxiliary\\VS\\include;C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.26100.0\\ucrt;C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.26100.0\\um;C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.26100.0\\shared;C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.26100.0\\winrt;C:\\Program Files (x86)\\Windows Kits\\10\\include\\10.0.26100.0\\cppwinrt;${env:INCLUDE}",
"LIB": "D:\\APP\\VisualStudio2022\\VC\\Tools\\MSVC\\14.44.35207\\ATLMFC\\lib\\x64;D:\\APP\\VisualStudio2022\\VC\\Tools\\MSVC\\14.44.35207\\lib\\x64;C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.26100.0\\ucrt\\x64;C:\\Program Files (x86)\\Windows Kits\\10\\lib\\10.0.26100.0\\um\\x64;${env:LIB}"
},
"C_Cpp.default.cppStandard": "c++11",
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/X256_PS/compile_commands.json"
"C_Cpp.default.cppStandard": "c++17",
"C_Cpp.default.intelliSenseMode": "windows-msvc-x64",
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/Debug/REC_ON/compile_commands.json"
}
+62 -137
View File
@@ -5,166 +5,91 @@
},
"tasks": [
{
"label": "CMake: 配置 X256_PS (Release, MSVC2013 x86)",
"label": "清理全部",
"detail": "删除 build/<Debug|Release>/<REC_*> 下所有配置的构建输出(含旧版 build/X256_PS、build/X256_MS;build/output 打包留档不动)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\configure.bat",
"args": [
"${workspaceFolder}/build/X256_PS",
"PHASE_SCANNING",
"Release"
],
"problemMatcher": []
"command": "${workspaceFolder}\\.vscode\\scripts\\clean_all.bat",
"problemMatcher": [],
"presentation": {
"reveal": "always",
"panel": "shared",
"clear": true
}
},
{
"label": "CMake: 配置 X256_PS (Debug, MSVC2013 x86)",
"label": "配置并构建(Release/REC_OFF)",
"detail": "Release + RDP_DATA_RECORD=OFF(机扫/相扫已合并为同一版本,单次构建一个配置)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\configure.bat",
"command": "${workspaceFolder}\\.vscode\\scripts\\build_config.bat",
"args": [
"${workspaceFolder}/build/X256_PS",
"PHASE_SCANNING",
"Debug"
],
"problemMatcher": []
},
{
"label": "CMake: 构建 X256_PS (Release)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build.bat",
"args": [
"${workspaceFolder}/build/X256_PS"
],
"group": "build",
"problemMatcher": [
"$msCompile"
]
},
{
"label": "CMake: 构建 X256_PS (Debug)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build.bat",
"args": [
"${workspaceFolder}/build/X256_PS"
],
"group": "build",
"problemMatcher": [
"$msCompile"
]
},
{
"label": "CMake: 清理 X256_PS",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\clean.bat",
"args": [
"${workspaceFolder}/build/X256_PS"
],
"problemMatcher": []
},
{
"label": "CMake: 配置 X256_MS (Release, MSVC2013 x86)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\configure.bat",
"args": [
"${workspaceFolder}/build/X256_MS",
"MECHANICAL_SCANNING",
"Release"
],
"problemMatcher": []
},
{
"label": "CMake: 配置 X256_MS (Debug, MSVC2013 x86)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\configure.bat",
"args": [
"${workspaceFolder}/build/X256_MS",
"MECHANICAL_SCANNING",
"Debug"
],
"problemMatcher": []
},
{
"label": "CMake: 构建 X256_MS (Release)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build.bat",
"args": [
"${workspaceFolder}/build/X256_MS"
],
"group": "build",
"problemMatcher": [
"$msCompile"
]
},
{
"label": "CMake: 构建 X256_MS (Debug)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build.bat",
"args": [
"${workspaceFolder}/build/X256_MS"
],
"group": "build",
"problemMatcher": [
"$msCompile"
]
},
{
"label": "CMake: 清理 X256_MS",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\clean.bat",
"args": [
"${workspaceFolder}/build/X256_MS"
],
"problemMatcher": []
},
{
"label": "CMake: 配置并构建 X256_PS (Release)",
"dependsOrder": "sequence",
"dependsOn": [
"CMake: 配置 X256_PS (Release, MSVC2013 x86)",
"CMake: 构建 X256_PS (Release)"
"Release",
"OFF"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
"problemMatcher": [
"$msCompile"
],
"presentation": {
"reveal": "always",
"panel": "shared"
}
},
{
"label": "CMake: 配置并构建 X256_PS (Debug)",
"dependsOrder": "sequence",
"dependsOn": [
"CMake: 配置 X256_PS (Debug, MSVC2013 x86)",
"CMake: 构建 X256_PS (Debug)"
"label": "配置并构建(Release/REC_ON)",
"detail": "Release + RDP_DATA_RECORD=ON(机扫/相扫已合并为同一版本,单次构建一个配置)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build_config.bat",
"args": [
"Release",
"ON"
],
"group": "build",
"problemMatcher": []
"problemMatcher": [
"$msCompile"
],
"presentation": {
"reveal": "always",
"panel": "shared"
}
},
{
"label": "CMake: 配置并构建 X256_MS (Release)",
"dependsOrder": "sequence",
"dependsOn": [
"CMake: 配置 X256_MS (Release, MSVC2013 x86)",
"CMake: 构建 X256_MS (Release)"
"label": "配置并构建(Debug/REC_OFF)",
"detail": "Debug + RDP_DATA_RECORD=OFF(机扫/相扫已合并为同一版本,单次构建一个配置)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build_config.bat",
"args": [
"Debug",
"OFF"
],
"group": "build",
"problemMatcher": []
"problemMatcher": [
"$msCompile"
],
"presentation": {
"reveal": "always",
"panel": "shared"
}
},
{
"label": "CMake: 配置并构建 X256_MS (Debug)",
"dependsOrder": "sequence",
"dependsOn": [
"CMake: 配置 X256_MS (Debug, MSVC2013 x86)",
"CMake: 构建 X256_MS (Debug)"
"label": "配置并构建(Debug/REC_ON)",
"detail": "Debug + RDP_DATA_RECORD=ON(机扫/相扫已合并为同一版本,单次构建一个配置)",
"type": "shell",
"command": "${workspaceFolder}\\.vscode\\scripts\\build_config.bat",
"args": [
"Debug",
"ON"
],
"group": "build",
"problemMatcher": []
},
{
"label": "CMake: 配置并构建全部 (X256_PS + X256_MS, Release)",
"dependsOrder": "sequence",
"dependsOn": [
"CMake: 配置并构建 X256_PS (Release)",
"CMake: 配置并构建 X256_MS (Release)"
"problemMatcher": [
"$msCompile"
],
"problemMatcher": []
"presentation": {
"reveal": "always",
"panel": "shared"
}
}
]
}
+212 -6
View File
@@ -9,13 +9,219 @@
### Added
- 新增 CHANGELOG.md,采用 Keep a Changelog 格式记录项目变更历史。
- `RadarPara` 结构体新增 `DATA_RATE_TAS`(TAS 模式数据率)字段,由 DLL 调用者通过 `track_process_parameters_initial()` 输入。
### Changed
### Fixed
### Removed
## [1.5.9] - 2026-10-29
> 本次发布把 1.5.8 之后的全部改动(提交 423c200 … eb9f30c)归档:参数配置方式改造 + 机扫/相扫合并为单一份构建、工作参数改为全局对象、日志组件改为 spdlog、构建脚本与 VS Code 任务适配、**航迹指纹**与手动转 TAS 输出修复。
> 版本号:`parameters.h` 的 `PATCH_VERSION` = 9、`VERSION_INFO` = `"GENERIC"`(合并后无体制后缀),运行时 `version_info` 为 **`V1.5.9.GENERIC`**(版本号于 019af7a 提升)。
### Added
- **航迹指纹(0f3da99)**:`Trust_Track` 新增 `int Track_fingerprint`(16 bit),起批转可靠航迹时用**末点**的 `r` / `azi` / `vr` / `RCS` / `CPI_time` 计算(`track_init.cpp`:`quantize` 量化到 0.01 m / 0.001° / 0.01 m·s⁻¹ / 0.1 dBsm,随后 splitmix64 混合并 `fold16` 折叠);TAS 波束输出的 `TrackingBeam::TAS_track_index` 改为下发**指纹**(不再下发可复用的航迹批号),TAS 数据帧的 `DataRev::TAS_track_index` 也被解释为指纹;`track_process(model=3)` 按指纹在航迹表中定位航迹(命中后置 `manual_tracking_flag=1`、`Track_Mode=1`)再做关联与外推。目的是让 TAS 控制命令在回放/重连后仍能唯一对应同一目标(批号会复用,指纹由目标特征生成)。
- **内置 spdlog v1.17.0 并重写日志模块(日志组件 spdlog 改造,24ce1e6)**:对应 `requirements.md` 三条需求——① 日志同时输出到标准输出与日志文件(文件仍为 `RDP_LOG_DIR` = `.\rdp_logs\`,文件名按日期+时间命名、每次启动一个文件);② 原 `RDP_LOG` 流式宏按 spdlog 级别拆为 `RDP_LOG_TRACE`/`RDP_LOG_DEBUG`/`RDP_LOG_INFO`/`RDP_LOG_WARN`/`RDP_LOG_ERROR`/`RDP_LOG_CRITICAL`(另提供 `RDP_LOG_ERR` 作为 `RDP_LOG_ERROR` 别名,对应 spdlog 的 `err` 级别命名),调用改为 fmt 风格,如 `RDP_LOG_INFO("parameters_reload ok: {}", path)`;③ 原 `RDP_LOG_ENABLE` 宏删除,改用 spdlog 的 `SPDLOG_ACTIVE_LEVEL` 在编译期控制日志级别。实现要点:新增内置第三方源码 `third_party/spdlog`(v1.17.0,commit `79524ddd08a4ec981b7fea76afd08ee05f83755d`,MIT;含内置 {fmt} 12.1.0;`include/` 105 文件 + `src/` 7 文件 + LICENSE,来源/复现/升级见 `third_party/spdlog/UPSTREAM.md`);`CMakeLists.txt` 新增静态库目标 `spdlog`(`SPDLOG_COMPILED_LIB`,第三方源码以 `/w` 编译,头文件目录与 `SPDLOG_COMPILED_LIB`/`SPDLOG_ACTIVE_LEVEL` 经 PUBLIC 接口同时提供给 `data_process_class_dll` 与 `rdp_record` 两个目标,保证跨 C++17/C++20 配置一致);新增 CMake 缓存变量 `RDP_LOG_LEVEL`(AUTO/TRACE/DEBUG/INFO/WARN/ERROR/CRITICAL/OFF,默认 AUTO = Debug 构建 TRACE、其余构建 INFO;OFF = 编译期关闭全部日志,`rdp_log::Init()` 也不再创建日志文件;非法值在 configure 阶段报错);`rdp_log.h/.cpp` 重写为 spdlog 双 sink(`stdout_sink_mt` + `basic_file_sink_mt`)默认 logger,日志行格式为 `[YYYY-MM-DD HH:MM:SS.mmm] [级别] [文件名:行号] 内容`(级别按 spdlog 默认名输出 trace/debug/info/warning/error/critical),`flush_on(trace)` 逐条落盘,日志文件创建失败时降级为仅标准输出并返回 false(不中断雷达处理),spdlog 内部错误经 error handler 写标准错误;日志文件命名沿用旧风格 `rdp_YYYYMMDD_HHMMSS.log`,同一秒内文件已存在(Destroy→GetB 重连、同目录多进程启动)时追加 `_1`~`_999` 后缀、不复用同一文件;新增内部辅助 `rdp_log::log_file_path()`。
- 对应本次日志改造的文档:新增 `third_party/spdlog/UPSTREAM.md`(内置 spdlog 的来源/版本/许可证/复现与升级约束);`README.md` 与 `docs/` 按源码同步(见 Fixed 中文档同步条目)。
- **新增配置参数 `scan_mode`(参数配置方式改造)**:`struct.h::RDP_parameter` 的雷达工作参数组新增 `int scan_mode`(紧跟 `radar_work_mode`),语义 **0=相扫模式 / 1=机扫模式**;`rdp_parameter_loader.cpp` 在 `[radar_params]` 登记该键为**必填 int**,并校验只允许 0/1;样例 `config/config_tmp.toml` 新增 `scan_mode = 0`(相扫)。该配置项决定 `data_preprocess()` 的 TWS 判圈逻辑(见 Changed)。
- **参数加载器(参数配置方式改造,423c200)**:新增内部模块 `data_process_class_dll/rdp_parameter_loader.h/.cpp`,把 TOML 配置文件解析到 `struct.h::RDP_parameter`(接口 `int rdp_parameter_load(const char* path, RDP_parameter& parameter, std::string* error_msg)`)。采用**严格校验**:11 个配置段(`radar_params` / `cohesion` / `track_associate` / `track_init` / `track_die` / `track_quality` / `track_index` / `beam_control` / `singer_model` / `track_init_prohibite_area` / `tas_prohibite_area`)全部必填;标量键全部必填且校验类型(数字/整数)与取值范围;拒绝未知配置段与未知键(拼错即报错);屏蔽区 4 个数组必须存在且元素个数等于对应 `*_num`(`*_num` 限 0~30,为 0 时数组可省略或为空数组),并逐区校验距离/方位范围。任一错误返回 -1 并给出首条可读原因,**不修改**调用方传入的参数(全部通过后才一次性赋值)。
- **新增公开虚接口 `parameters_reload`(参数配置方式改造)**:`data_process_class_dll.h` 的 `Data_process_class_dll` 新增 `virtual int parameters_reload(const char* parameter_file_path)`(基类空实现返回 -1);`Data_Process` 实现为「加载成功后整体赋值 `rdp_parameter`,失败保留当前参数并经 `RDP_LOG` 打印原因」。宿主改为在 `GetB()` 之后调用一次即可完成全部工作参数配置,重连/换配置时再次调用可热重载;不再需要构造并下发 `struct RadarPara`。
- **示例配置文件 `config/config_tmp.toml`(参数配置方式改造)**:11 个配置段与 `RDP_parameter` 的注释分组一一对应,键名与结构体成员名完全一致(`[radar_params]`、`[cohesion]`、`[track_associate]`、`[track_init]`、`[track_die]`、`[track_quality]`、`[track_index]`、`[beam_control]`、`[singer_model]` 为标量段,两个屏蔽区段用与成员同名的 TOML 数组,样例为 `*_num = 0` + 空数组);数值与改造前的宏/`RadarPara` 默认值一致。
- **内置 toml++ v3.4.0 单头文件(参数配置方式改造)**:新增 `third_party/toml/toml.hpp`(MIT,上游单头文件原样保存);`CMakeLists.txt` 的 `SOURCES` 增加 `data_process_class_dll/rdp_parameter_loader.cpp`、`target_include_directories` 增加 `third_party/toml`;`rdp_parameter_loader.cpp` 用 `#pragma warning(push, 0)` / `(pop)` 包裹第三方头,维持 `/W3` 零告警约定。
- 文档同步(参数配置方式改造):更新 `README.md`(1/2/3/4/5/6/7/8/9/10/11/12/13 节)与 `docs/data_process_flow_report.md` 第 9 节、`docs/track_init_readme.md` 第 13 节等——参数来源改为「TOML 配置文件 → `RDP_parameter`」、宿主调用改为 `parameters_reload`、新增旧参数→新字段对照表、编译期宏清单仅保留非配置常量、已知问题与版本历史同步。
- 新增 `.vscode/scripts/config_paths.bat`:按「构建类型 / 记录模式」推导并校验构建目录 `build/<Debug|Release>/<REC_AUTO|REC_OFF|REC_ON>`(机扫/相扫合并后不再有扫描体制维度),供所有构建脚本共用。
- 新增 `.vscode/scripts/build_config.bat`:对单个配置「配置 + 构建」,失败即中止并返回非 0(取代原 `build_all.bat`)。
- 新增 `.vscode/scripts/clean_all.bat`:删除全部配置的构建输出,并清理旧版 `build/X256_PS`、`build/X256_MS` 目录(`build/output` 打包留档不动)。
- `.vscode/tasks.json` 重建为 5 个任务:「清理全部」「配置并构建(Release/REC_OFF)」「配置并构建(Release/REC_ON)」「配置并构建(Debug/REC_OFF)」「配置并构建(Debug/REC_ON)」;后 4 个任务内部调用 `build_config.bat` 单次配置并构建,默认构建任务为「配置并构建(Release/REC_OFF)」。
- **数据记录改为异步写线程 + 有界队列(数据记录性能优化,17a3d99)**:`rdp_record.h` 新增 `rdp_record::Stats`(`enqueued_rows`/`dropped_rows`/`written_rows`/`transactions`/`write_errors`/`queue_peak_bytes`/`queue_peak_rows`)与 `stats()`,新增 6 个异步入队接口 `WritePoints`/`WriteCohesionTWS`/`WriteCohesionTAS`/`WriteTempTracks`/`WriteTrustTracks`/`WriteAssoInfos`(参数为源结构体 `std::vector` 按值、调用点 `std::move`)及对应宏 `RDP_RECORD_POINTS`/`RDP_RECORD_COHESION_TWS`/`RDP_RECORD_COHESION_TAS`/`RDP_RECORD_TEMP_TRACKS`/`RDP_RECORD_TRUST_TRACKS`/`RDP_RECORD_ASSO_INFOS`;`rdp_record.cpp` 的 `Init()` 建库后启动独立写线程(Windows 下 `THREAD_PRIORITY_BELOW_NORMAL`),处理线程只做「收集源结构体 → 一次入队」,实体构造(数组转文本)与写库全部迁移到写线程。
- **批量事务与 SQLite 配置(数据记录性能优化)**:写线程按表累积待提交数据,达到 `RDP_RECORD_WRITE_BATCH_ROWS`(默认 4096 行)或等待 `RDP_RECORD_WRITE_MAX_WAIT_MS`(默认 50 ms)后以事务批量提交;SQLite 启用 `journal_mode=WAL`(失败回退默认 journal)、`synchronous=OFF`、`cache_size=-8192`(8 MB)、`busy_timeout=2000`;建表后读取 `sqlite_master` 去掉 `AUTOINCREMENT` 重建为普通 `INTEGER PRIMARY KEY`(重建失败时回退并告警)。
- **有界队列与丢弃计数(数据记录性能优化)**:队列上限 `RDP_RECORD_QUEUE_CAP_BYTES`(默认 64 MB)与 `RDP_RECORD_QUEUE_MAX_BATCHES`(默认 4096 批),写线程跟不上时整批丢弃并累计 `dropped_rows`,日志按 `RDP_RECORD_DROP_LOG_INTERVAL`(默认每 1024 行)限频提示;处理线程不因记录阻塞。
- **记录参数宏(数据记录性能优化)**:`parameters.h` 新增可调宏(均以 `#ifndef` 保护,可用编译选项覆盖):`RDP_RECORD_QUEUE_CAP_BYTES`、`RDP_RECORD_QUEUE_MAX_BATCHES`、`RDP_RECORD_WRITE_BATCH_ROWS`、`RDP_RECORD_WRITE_MAX_WAIT_MS`、`RDP_RECORD_ASSO_CHUNK_ROWS`(默认 131072)、`RDP_RECORD_SQLITE_CACHE_KB`、`RDP_RECORD_SQLITE_BUSY_TIMEOUT_MS`、`RDP_RECORD_SQLITE_WAL`、`RDP_RECORD_MAX_CONSECUTIVE_ERRORS`(默认 10)、`RDP_RECORD_DROP_LOG_INTERVAL`。
- **记录健壮性与重连(数据记录性能优化)**:写失败每表只提示一次并永久跳过该表,连续错误达到 `RDP_RECORD_MAX_CONSECUTIVE_ERRORS` 次全局熔断并丢弃队列;`Shutdown()` 改为「停止入队 → 排空队列与 pending → 打印 stats → `wal_checkpoint(TRUNCATE)` → 关库」;支持 `Destroy()` → `GetB()` 重连重新建库(同一秒文件名冲突时追加 `_1`/`_2` 后缀)。
- **航迹质量信息入库(1ffb287)**:`RdpRecTempTrack` 新增 `quality`(临时航迹质量);`RdpRecTrustTrack` 新增 `Quality`(可靠航迹质量)、`Unupdated_time`(未更新累计毫秒)、`Point_sum`(累计点数)、`Valid_point_sum`(累计实点关联次数),`rdp_record.cpp` 注册顺序同步——质量纳入调试可查范围,可直接用 SQL 分析「起批质量 / 关联后质量变化 / 未关联衰减」。
- 文档:全面更新 `docs/data_record_debug.md`(异步运行期行为、接口与宏一览、新增记录点四步配方、6 张表记录点清单、已知限制、验证参考)与 `README.md` 相关章节(2、3、4.3、5.2、7.1、10.1、11.2、11.3、12、13 节)。
### Changed
- TAS 波束控制增加数据率门控:`Beam_Ctrl()` 仅在队首目标航迹时间 `T_track` 与最新时间戳 `latest_timestamp` 的差值大于 `1 / DATA_RATE_TAS` 秒时输出 TAS 跟踪波束,使 TAS 目标数据率不再随波束时间(Beam_Ctrl 调用频率)变化;队列轮转逻辑保持不变。
- 波束门控使用 `Work_Parameter.DATA_RATE_TAS`(运行时参数)而非 `parameters.h` 中的 `DATA_RATE_TAS` 宏;参数 ≤ 0 时不做门控,保持原输出行为。
- **日志组件行为改为 spdlog(日志组件 spdlog 改造,24ce1e6)**:`data_process_class_dll.cpp` 的 `Data_Process_Factory::GetB()` 每次创建实例时调用 `rdp_log::Init()`(在 `rdp_record::Init()` 之前,使记录模块初始化日志也写入同一文件),`Destroy()` 在 `rdp_record::Shutdown()` 之后调用 `rdp_log::Shutdown()`(flush 并关闭日志文件);不再使用 `rdbuf()` 重定向/恢复 `std::cout`,`std::cout` 保持宿主原始语义,日志的双写完全由 spdlog 双 sink 完成;`RDP_LOG_DIR` 路径与 `.\rdp_logs\` 命名风格保持与旧实现一致。全部 `RDP_LOG` 调用点按语义迁移为级别宏:`data_process.cpp`(`parameters_reload` 成功 INFO / 失败 ERROR)、`track_asso_tas.cpp`(ΔT≤0 告警 WARN)、`rdp_record.cpp`(19 处:初始化/统计/建表/关闭 INFO,PRAGMA 回退/队满丢弃/时间获取失败/数据库打开失败 WARN,写入失败/连续错误熔断/写线程异常 ERROR),其余注释代码中的旧宏引用同步改为对应级别宏注释;`parameters.h` 的日志说明改为 spdlog + `SPDLOG_ACTIVE_LEVEL`/`RDP_LOG_LEVEL` 描述。
- **日志改造的第三方告警隔离**:spdlog 静态库第三方源码以目标级 `/w` 编译,包含其头文件的 `rdp_log.h` 用 `#pragma warning(push, 0)` / `(pop)` 包裹(README 4.5 已同步为第三个第三方告警例外),项目源码仍保持 `/W3` 零告警。
- **本次日志改造的验证(Lead 实测,2026-10-28)**:① 构建:4 个配置(Release/Debug × REC_OFF/REC_ON)经 `.vscode/scripts/build_config.bat` 全部成功、退出码 0、**0 warning / 0 error**;重复执行无编译步骤(增量幂等);`RDP_LOG_LEVEL=BAD` 在 configure 阶段被拒绝(退出码 1)。② compile_commands:Debug 配置 `-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE`、Release 配置 `-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO`(AUTO),各目标均含 `-DSPDLOG_COMPILED_LIB`,REC_ON 的 `rdp_record.cpp`(C++20)与 DLL 源文件定义一致。③ DLL 体积:Release REC_OFF 537,088 B(较改造前 331,264 B +205,824 B)、Release REC_ON 1,855,488 B(+202,752 B)、Debug REC_OFF 3,317,760 B(+1,602,560 B)、Debug REC_ON 5,867,520 B(+1,175,552 B),增量来自内置 spdlog/{fmt}(DLL 公开接口与业务数值行为不变);导出表仍 **31 项**(无 spdlog 符号导出),导入表仅 KERNEL32/MSVCP140/VCRUNTIME140*/api-ms-win-crt-*,无新增第三方 DLL 依赖(spdlog 为静态库)。④ 运行期(4 配置逐一编译并运行 `build/verify/build_api_test.bat`,各 38 项断言 0 失败):同一批日志消息在 stdout 与日志文件中逐行一致;日志文件名匹配 `rdp_\d{8}_\d{6}(_\d+)?\.log`,每次 GetB 一个文件、同秒 Destroy→GetB 重连产生 `_1` 后缀新文件;日志行匹配 `[YYYY-MM-DD HH:MM:SS.mmm] [级别] [文件:行号] 内容`(中文消息正常),REC_ON 配置可见 `[rdp_record]` 的 `journal_mode`/`enabled`/`table ... created without AUTOINCREMENT`/`stats`/`shutdown` 等日志并随双写落盘。⑤ 编译期级别隔离实测:在 `build/verify/` 下独立配置构建 `RDP_LOG_LEVEL=WARN` 与 `=OFF`(不污染标准构建目录)均 0 warning;WARN 构建运行期 stdout/日志无 `[info]` 行、仅有 `[error]`(失败路径)且日志文件仍创建;OFF 构建运行期不创建 `rdp_logs` 目录/文件、stdout 无任何日志行、接口退出码正常。⑥ 静态检查:源码已无 `RDP_LOG <<` 与 `RDP_LOG_ENABLE` 残留;`rdp_log.h` 提供 6 个级别宏 + `RDP_LOG_ERR` 别名;`GetB()/Destroy()` 已实际调用 `rdp_log::Init()/Shutdown()`(非注释)。验证程序/日志位于 git 忽略的 `build/verify/`,未入库;另由独立验证者以冻结源码快照复验全部验收项(4 配置干净构建 0 warning/0 error、双写/文件名/行格式/逐行一致、WARN/OFF 隔离、dumpbin 导出与依赖、幂等与非法值),结论一致,报告见 `build/verify/verification_report.md`(未入库)。
- **工作参数改为全局对象(全局工作参数对象改造,7e4cb29)**:`struct.h` 在 `struct RDP_parameter` 之后新增 `extern RDP_parameter rdp_parameter;`(附注释:所有处理模块直接访问、定义在 `data_process.cpp`、由 `parameters_reload()` 在配置完整校验通过后整体赋值、处理期间只读、配置重载应与处理互斥);`data_process.cpp` 定义并零初始化 `RDP_parameter rdp_parameter{};`(当前 L14);`Data_Process` 删除 `RDP_parameter rdp_parameter` 成员(`data_process.h`,保留注释说明工作参数为全局对象);`parameters_reload()` 改为直接调用 `rdp_parameter_load(parameter_file_path, rdp_parameter, &error)`——loader 仅在全部校验通过后写入目标对象,失败不写入,故「失败保持当前参数」语义不变。删除各处理模块(`kalman` 11 个方法、`dot_coh`/`dot_coh_tas`、`track_asso`/`track_asso_tas`/`track_asso_direct_tracking`、`track_init`/`track_init_direct_tracking`、`track_die`/`track_die_tas`、`track_index_mangement`、`tas_ctrl`,共 12 个模块的 24 个 .h/.cpp 文件)全部 `const RDP_parameter& rdp_parameter` 形参及调用实参,函数体内 `rdp_parameter.<field>` 表达式一处未改、现直接解析到全局对象;公开接口签名、公式、阈值与数值行为均不变。**注意**:全局对象是 DLL 内单实例可变状态,`parameters_reload()` 与处理调用之间非线程安全(与 BUG-03 的线程模型约定一致,宿主应按单线程接口使用、重载配置时不得并发处理)。
- **本次全局工作参数对象改造的验证(Lead 实测,2026-10-27)**:① 构建:4 个配置(Release/Debug × REC_OFF/REC_ON;机扫/相扫已合并为单一构建)全部成功、退出码 0、**0 warning / 0 error**。② DLL 体积:Release REC_OFF 331,264 B(较改造前 331,776 B 略减 512 B)、Release REC_ON 1,652,736 B、Debug REC_OFF 1,715,200 B、Debug REC_ON 4,691,968 B。③ 导出表(Release REC_OFF,共 31 项):`parameters_reload` 仍在列(`?parameters_reload@Data_process_class_dll@@UEAAHPEBD@Z`);全局对象 `rdp_parameter` 未导出(DLL 内部符号),宿主接口不变。④ 加载器单元测试 233 项断言全部通过(加载器接口未变)。⑤ 公开接口运行期测试:4 配置逐一编译链接运行,各 38 项断言全部通过(TWS/TAS 主流程冒烟、`parameters_reload` 成功/失败、`scan_mode` 判圈运行时切换、`Destroy()`→`GetB()` 重连等)。⑥ 静态检查:处理模块已无 `const RDP_parameter&` 形参(仅 `rdp_parameter_loader.cpp` 的 `validate_parameter(const RDP_parameter&, ...)` 校验辅助函数保留);全局对象声明在 `struct.h:285`、定义在 `data_process.cpp:14`;所有模块内 `rdp_parameter` 仅以 `.field` 表达式出现;`data_process.cpp` 仅有的传参是 `parameters_reload()` 中的 `rdp_parameter_load(..., rdp_parameter, ...)`。⑦ 记录点当前行号:TWS `RDP_RECORD_POINTS` 在 `data_process.cpp:75`、TAS 分支在 `data_process.cpp:153`。验证程序与日志位于 git 忽略的构建目录中,未入库。
- **TWS 判圈改由配置项 `scan_mode` 运行时选择(参数配置方式改造)**:`data_process.cpp::data_preprocess()` 原用 `#ifdef PHASE_SCANNING / #else / #endif` 在编译期隔离两套判圈代码,现改为按 `rdp_parameter.scan_mode` 运行时选择——`scan_mode==0`(相扫)判 `Beam_index_aiz <= last_beam_num`,`scan_mode==1`(机扫)判 `Beam_index_aiz == 0`;两种情况都会更新 `last_beam_num`、仅判满圈时 `circle_id++` 并返回 1。行为与旧两套编译期分支**逐分支等价**(相扫首帧因 `last_beam_num` 初值 INT_MAX 仍判满圈;机扫仍以波位号 0 判满圈)。`PHASE_SCANNING` / `MECHANICAL_SCANNING` 宏与 `RADAR_SCAN_MODE` 缓存变量随后在「机扫/相扫版本合并」中一并移除(见 Removed),构建不再区分体制;判圈只由配置项 `scan_mode` 决定。
- **本次 `scan_mode` 改动的验证(Lead 实测,2026-10-27,机扫/相扫合并前)**:① 构建:8 个配置(X256_PS/X256_MS × Release/Debug × REC_OFF/REC_ON)全部重建成功、退出码 0、0 warning 0 error;Release REC_OFF DLL 仍为 331,776 字节、Debug REC_ON 4,694,016 字节(与 `scan_mode` 改动前一致)。② 加载器单元测试 233 项断言全部通过:新增覆盖「样例 `scan_mode=0` 取值正确」「`scan_mode=2` 时加载返回 -1 且错误信息包含 `scan_mode`」,其余既有成功/失败路径(缺文件、语法错、缺键、类型错、未知键、缺段、越界、数组长度不符等)保持通过。③ 公开接口运行期测试(8 配置逐一编译链接运行)各 38 项断言全部通过(原 29 项 + 新增 9 项判圈运行时切换断言):同一份二进制加载样例(`scan_mode=0` 相扫)后波位号 24 → `data_preprocess` 返回 1(判满圈);加载临时配置(`scan_mode=1` 机扫)后波位号 24 → 返回 2、波位号 0 → 返回 1;再切回样例配置并清空后行为一致;PS 与 MS 构建的测试结果相同,证明判圈已完全由配置决定。④ 静态检查:`data_process.cpp` 中 `PHASE_SCANNING`/`MECHANICAL_SCANNING` 已无有效代码引用(仅注释性提及);宏仍存在于 `parameters.h`(`VERSION_INFO` / `#pragma message`)与 `CMakeLists.txt`(`RADAR_SCAN_MODE` 编译定义、构建目录/任务命名);`RDP_parameter` 成员数 = 加载器键数 = TOML 键数 = **63**(新增 `scan_mode` 后),11 个配置段不变。⑤ 该轮记录点行号:TWS `RDP_RECORD_POINTS` 在 `data_process.cpp:72`、TAS 分支在 `data_process.cpp:150`(全局工作参数对象改造后变为 `:75` / `:153`,见本区上方新增验证)。验证程序与日志位于 git 忽略的构建目录中,未入库。
- **参数来源改为「TOML 配置文件 → `RDP_parameter`」(参数配置方式改造,423c200)**:原先「宿主下发 `struct RadarPara` + `parameters.h` 编译期宏」两条参数来源统一为运行期配置文件;`struct.h::RDP_parameter` 成为工作参数唯一来源,初始由 `Data_Process::rdp_parameter{}`(零初始化)持有,`parameters_reload()` 成功后才整体替换(**后于「全局工作参数对象改造」改为 `struct.h::rdp_parameter` 全局对象,见本区上方**)。改造只替换参数来源,不改变公式、阈值语义与处理流程,默认数值与改造前一致。
- **各模块参数传递方式(参数配置方式改造)**:`kalman`(使用量测误差/频点的 11 个方法:`kalman_filter_init_2dots`、`d_cal_track_init`、`kalman_filter_init_3dots`、`d_cal`、`d_cal_EKF`、`d_cal_with_doppler`、`kalman_filter_EKF`、`kalman_filter`、`d_cal_track_init_EKF`、`d_cal_track_init_with_doppler`、`Bind_speed`)、`dot_coh`/`dot_coh_tas`、`track_asso`/`track_asso_tas`/`track_asso_direct_tracking`、`track_init`/`track_init_direct_tracking`、`track_die`/`track_die_tas`、`track_index_mangement`、`tas_ctrl` 的入口与内部函数新增 `const RDP_parameter& rdp_parameter` 形参(原按值传递的 `struct RadarPara Work_Parameter` 全部删除);无参数依赖的 `kalman_pred` 保持原签名(**后于「全局工作参数对象改造」删除这些形参、改为直接访问全局对象,见本区上方**)。
- **旧宏与旧 `RadarPara` 字段的等价替换(参数配置方式改造)**:如 `SIGMA_R/A/E/V`→`sigma_range/azimuth/pitch/velocity`、`DOT_COH_RANGE/V/AZI`→`point_cohesion_range/velocity/azimuth`、`R_MIN/R_MAX`→`point_cohesion_range_min/max`、`FREQ0`→`freq_0_GHz`、`ASSO_THORD`→`associate_threshold`、`MAX_EXTRAPOLATE_NUM(_TAS)`→`extrapolate_max(_tas)`、`H_F_WIN_LEN`→`height_smooth_win`、`TRACK_START_THRESHOLD/ALPHA_START`→`track_init_threshold/track_init_angle`、`TRACK_START_QUALITY_L3~L7`→`track_init_quality_L3~L7`、`TRACK_DIE_QUALITY(_PRAMARY)/TRACK_DIE_ROUND(_PRIMARY/_TAS)`→`track_die_quality(_pramary)/track_die_round(_pramary/_tas)`、`TRACK_QUALITY_*`→`track_quality_*`、`work_mode/Height/Sys_delay`→`radar_work_mode/radar_height/radar_system_delay`、`track_start_point_num`→`track_init_point_num_min`、`V_MAX/V_MIN`→`track_init_velocity_max/min`、`track_prohibite_area_*`→`track_init_prohibite_area_*`、`TAS_prohibite_area_*`→`tas_prohibite_area_*`、`Model1/2/3_Q_fast/slow`→`model_1/2/3_Q_fast/slow`、`DATA_RATE_SHORT/MIDDLE/FAR`→`data_rate_tws`(契约确认的等价改法)、`DATA_RATE_TAS`→`data_rate_tas`;完整对照见 `README.md` 10.3 节。
- **航迹号、TAS 队列与跟踪波束类型改为按配置分配(参数配置方式改造)**:`track_index_mangement.cpp` 的定长数组 `int List[MAX_TRACK_INDEX]` 改为按 `rdp_parameter.track_index_max` 分配的 `std::vector<int>`;TAS 队列固定容量改用编译期常量 `RDP_TAS_QUEUE_CAPACITY`(30),运行期同时跟踪目标数上限改用配置项 `tas_track_num_max`(加载器校验 0~30,原 `MAX_TAS_NUM` 相扫 4 / 机扫 1);跟踪波束 `Tracking_beam->type` 由硬编码 1 改为配置项 `default_track_beam_type`。
- **`parameters.h` 精简为编译期常量(参数配置方式改造)**:删除全部雷达工作参数宏,仅保留 `PI`、版本宏(`MAJOR_VERSION`/`MINOR_VERSION`/`PATCH_VERSION`/`VERSION_INFO`/`VERSION_STRING`)、日志宏(`RDP_LOG_*`)、记录宏(`RDP_RECORD_*`)、接口容量常量 `MAX_TRACK_NUM`(500)/`RDP_TAS_QUEUE_CAPACITY`(30)与 `Round`;`requirements.md` 同时改写为本次参数配置方式改造的需求说明。
- **本次改造的验证(Lead 实测,2026-10-27,机扫/相扫合并前)**:① 构建:8 个配置(X256_PS/X256_MS × Release/Debug × REC_OFF/REC_ON)全部成功、退出码 0、0 warning 0 error,每个配置均生成 `bin/data_process_class_dll.dll`;Release REC_OFF DLL 体积 331,776 字节(约 324 KB,较改造前约 178 KB 增大,原因是纯头文件的 toml++ 解析器编入 DLL),Debug REC_ON 为 4,694,016 字节。② 导出表(Release REC_OFF,共 31 个导出):新增 `parameters_reload`(`?parameters_reload@Data_process_class_dll@@UEAAHPEBD@Z`),`track_process_parameters_initial`/`track_process_parameters_modify` 与 `RadarPara` 相关符号已不存在,`Data_Process_Factory::GetB/Destroy` 等其余接口不变。③ 加载器单元测试(229 项断言全部通过):样例配置 62 个键逐字段取值正确(含整数/浮点与负的二次/一次衰减系数;该轮为 `scan_mode` 新增前的验证,新增后样例共 63 个键,见上一条 `scan_mode` 验证);文件不存在、空路径、nullptr、TOML 语法错误、缺标量键、类型错误、未知键、缺配置段、取值越界(起批点数 2、`data_rate_tas=0`)、区域数 31、区域方位 min≥max、数组长度与 `*_num` 不一致等失败路径均返回 -1 且不修改当前参数,失败后可重新加载成功;屏蔽区数组按 `*_num` 读取、未使用位置保持 0。④ 公开接口运行期测试(8 个配置逐一编译链接运行,各 29 项断言全部通过):`GetB()`/`version_info`(`V1.5.8.PS`/`V1.5.8.MS`)、`parameters_reload` 成功/失败返回值与「失败后再加载成功」、TWS/TAS 主流程冒烟、`Beam_Ctrl`、`track_clear_all`、`track_delete`/`tracking_start`/`tracking_stop`/`primary_target_add`/`primary_target_cancel`/`tracking_point`、`Destroy()` 后重新 `GetB()` 并再次加载配置均正常。⑤ 静态审计:源码无 `Work_Parameter`/`RadarPara`/旧参数宏残留(区分大小写整词审计 0 命中);首轮验证时 62 个 `RDP_parameter` 成员 ↔ 62 个加载器键 ↔ 62 个 TOML 键一一对应(新增 `scan_mode` 后为 63 个),11 个配置段与加载器一致。验证程序与日志位于 git 忽略的构建目录中,未入库。
- **构建脚本适配机扫/相扫合并(本次构建配置调整)**:构建目录由「扫描体制 × 构建类型 × 记录模式」改为「构建类型 × 记录模式」——`build/<Debug|Release>/<REC_AUTO|REC_OFF|REC_ON>`,不再有 `X256_PS`/`X256_MS` 维度。
- `config_paths.bat` / `configure.bat` / `build.bat` / `clean.bat` 参数改为 `<Debug|Release> [AUTO|ON|OFF]`,构建目录自动推导;`build_all.bat` 由 `build_config.bat`(单配置「配置 + 构建」)取代,失败即中止并返回非 0;`clean_all.bat` 在清理 4 种配置的同时清理旧版 `build/X256_PS`、`build/X256_MS` 目录(`build/output` 保留)。
- VS Code 配置同步:`settings.json` 的 `cmake.buildDirectory` 改为 `${workspaceFolder}/build/${buildType}/${variant:recordMode}`、`compileCommands` 指向 `build/Debug/REC_ON/compile_commands.json`;`cmake-variants.yaml` 移除 `scanMode` 变体(仅保留 `buildType` + `recordMode`);`c_cpp_properties.json` 合并为单个「X256 (MSVC2022 x64)」配置(不再带 `PHASE_SCANNING`/`MECHANICAL_SCANNING` 宏);`launch.json` 的 `preLaunchTask` 改为「配置并构建(Debug/REC_ON)」,`program`/`symbolSearchPath` 指向 `build/Debug/REC_ON/bin`。
- 更新 README(3 节目录结构、4.2~4.5 节构建说明、7.1/10.4/11.2/12/13 节)与 `docs/data_record_debug.md` 第 2 节、`docs/data_process_flow_report.md` 判圈/参数说明、`docs/data_record_perf_report.md` 中的构建路径;`requirements.md` 仍为参数配置方式改造需求(本次未改需求文档)。
- **本次构建配置调整的验证(Lead 实测,2026-10-27)**:`CMakeLists.txt` 移除 `RADAR_SCAN_MODE` 后 4 个配置(Release/Debug × REC_OFF/REC_ON)重建成功、退出码 0、0 warning 0 error(已把 `rdp_record` 的 `/W3`、`/Zc:__cplusplus` 限定为 C++ 源文件,消除 sqlite3.c 上 `/w` 覆盖 `/W3` 的 D9025 命令行提示;C 文件仅保留 `/utf-8`/`/FS` 与源文件级 `/w`);产物 DLL 大小 Release REC_OFF 331,776 / Release REC_ON 1,653,248 / Debug REC_OFF 1,718,272 / Debug REC_ON 4,694,528 字节;`compile_commands.json` 中不再含 `PHASE_SCANNING`/`MECHANICAL_SCANNING`/`RADAR_SCAN_MODE`,REC_ON 配置含 `RDP_RECORD_ENABLE`;5 个任务实际执行返回 0;「清理全部」首次执行删除旧版 `build/X256_PS`、`build/X256_MS`,再次执行删除重建后的 4 个新配置目录(`build/output` 保留);已构建配置重复执行 `build_config.bat` 为 0 个编译/链接步骤(增量幂等);模拟 MSVC 环境无效时 `build_config.bat` 立即中止(退出码 1);非法构建类型/记录模式与未配置即构建均返回非 0。
- **12 个业务记录点由「同步构造实体写库」改为「帧/圈内收集源结构体 → 一次入队」(数据记录性能优化)**:`data_process.cpp`(TWS/TAS 输入点迹)、`dot_coh.cpp`、`dot_coh_tas.cpp`、`track_init.cpp`(临时航迹)、`track_asso.cpp`(可靠航迹 + 关联信息按 `RDP_RECORD_ASSO_CHUNK_ROWS` 分块)、`track_asso_tas.cpp`(同)共 12 处;记录时机、条件与记录行数/表列均保持不变;6 个业务文件删除 `#include "rdp_record_entity.h"`,所有新增代码仍在 `#ifdef RDP_RECORD_ENABLE` 内,记录关闭构建零代码、零影响。
- **记录线程模型与 Shutdown 语义(数据记录性能优化)**:由「处理线程单连接 + 互斥锁串行同步写」改为「处理线程只入队 + 独立写线程承担实体构造与全部写库」;`RDP_RECORD`/`RDP_RECORD_BATCH` 保留为遗留同步接口(在调用线程直接写库、会阻塞,仅供测试/特殊场景);`Shutdown()` 会**排空**队列与 pending 后再关库,并在关库前打印统计。
- **未改变记录语义(数据记录性能优化)**:不采用 P0-a(关联信息裁剪)与 P2(记录策略/轮转),⑥ 仍按每个候选一行写入;跨表写入顺序仍不保证、各表内顺序保持;`REC_OFF` 构建零影响与优化前一致。
- **本次优化的验证(可复查,机扫/相扫合并前)**:8 种配置(X256_PS/X256_MS × Release/Debug × REC_OFF/REC_ON)0 warning 0 error(合并后 4 种配置的构建验证见上文「构建脚本适配机扫/相扫合并」);dup 场景 6 圈/200 目标/每帧 150 点 REC_ON vs REC_OFF:普通帧每帧新增 p99 ≈ 0.09~0.11 ms(≤1 ms 阈值内)、判圈帧 `track` 新增中位 ≈ 2.9~4.1 ms(压测 p99 ≈ 13~24 ms,因未采用 P0-a,属压测上限),受控 50 目标/50 点场景判圈帧新增中位约 3 ms、最大 5.96 ms;记录行数与改动前完全一致(`RdpRecPoint` 90,000 / `RdpRecPointCohesionTWS` 4,170 / `RdpRecAssoInfo` 211,584 / `RdpRecTempTrack` 8,330 / `RdpRecTrustTrack` 304,合计 314,388),`integrity_check=ok`、DDL 无 `AUTOINCREMENT`、`journal_mode=wal`、id 严格递增、`dropped_rows=0`;60 圈长跑 9,987,370 行、`dropped_rows=0`、`write_errors=0`、队列峰值约 19.8 MB、DB 约 1.06 GB;故障注入(记录目录不可建时 `Init` 静默失败、驱动正常;小队列测试构建丢弃 314,388 行且计数正确)与 `Destroy()`→`GetB()` 重连(两个独立 db 均正常写入)测试通过。原始日志与验收脚本位于 git 忽略的 `build/analysis/verify/`。
- **相扫 TAS 航迹消亡圈数放宽(5d105d2,`parameters.h`)**:`TRACK_DIE_ROUND_TAS` 由 10 调整为 **20**(仅相扫;机扫保持 96)。因判据为 `Extrapolate_round > 阈值`,相扫 TAS 航迹实际由「连续 11 圈无实点关联消亡」放宽为「连续 21 圈」才消亡,便于 TAS 目标在短时丢点后重新关联。
- **TAS 高度平滑窗口加长(6284f3f)**:`track_asso_tas.cpp::track_hight_update()` 的分段偏移由 `H_F_WIN_LEN + {2,3,4,6}` 改为 `H_F_WIN_LEN + {6,8,10,12}`;同时相扫 `H_F_WIN_LEN` 由 3 调整为 **6**(机扫仍为 3)。合并效果:相扫 TAS 窗口 12/14/16/18 点、机扫 TAS 窗口 9/11/13/15 点(原为 5/6/7/9);TWS 仍用 `H_F_WIN_LEN + {2,3,4,6}`,相扫因此由 5/6/7/9 变为 **8/9/10/12**、机扫保持 5/6/7/9。
### Fixed
- **修复手动转 TAS 时输出航迹更新信息不完整(eb9f30c,`tas_ctrl.cpp::tas_target_add()`)**:目标由 TWS 手动转 TAS 的当圈,原先只填 `Point_Sum`/`Range`/`Azimuth`/`Track_Index`/`Range_V`/`Amplitude`/`Flag_Point`/`Track_Mode`/`Elevation`/`z`/`Target_Type`/`track_snr` 等少数字段,其余字段(`track_time`/`GNSS_time`/`Quality`/`Direction_Angle`/关联点信息/`Flag_Direction`/`track_rcs`/`pitch_num`/`x,y,v_x,v_y`/十字星数据等)保持宿主旧值;现改为与常规关联输出**同一套字段填充**(含按 `radar_system_delay` 补偿后的极坐标、`Quality × track_quality_max`、`Flag_Direction` 的航向-位置夹角判据、十字星 `memcpy`),并补写 `point_type=0`,避免宿主在转跟踪当圈读到过期字段。
- 文档同步「航迹指纹」(本次文档更新):README(2/6.1/6.2/7.1/8.2/9.10/新增 9.12/11.2/12 节)、`docs/data_process_flow_report.md`(2.2/7.1/7.2/8.1/8.2 与第 10 节)、`docs/track_init_readme.md`(起批时计算指纹)、`docs/data_record_debug.md`(记录实体未含指纹的说明)同步指纹的生成、下发与匹配语义,并明确 `DataRev::TAS_track_index` / `TrackingBeam::TAS_track_index` 字段名未变、语义已改为指纹。
- 文档同步「日志组件 spdlog 改造」(本次文档更新):`README.md`(1 技术栈/2 功能特性/3 目录结构/4.1 环境/4.3 构建选项/4.4 产物体积/4.5 第三方告警例外/5.2 模块职责/7.1 日志说明/9.6 历史注/10.1 失败日志/10.3 旧宏说明/10.4 宏表/12 版本历史/13 参考文档)、`docs/data_process_flow_report.md`、`docs/data_record_debug.md` 及相关 docs 中关于 `RDP_LOG`/`RDP_LOG_ENABLE`/`rdbuf()` 重定向的过期表述统一改为 spdlog 双写 + 级别宏 + `SPDLOG_ACTIVE_LEVEL`/`RDP_LOG_LEVEL` 编译期控制;`requirements.md` 说明同步为本次日志改造需求(其文件本身保持需求原文未改)。
- 文档同步「全局工作参数对象改造」改动(本次文档更新):README 3/4.4/5.1/5.2/6.2/7.1/10.1/11.2/12 节、`docs/data_process_flow_report.md` 第 9 节、`docs/track_init_readme.md` 1.1/2.3/8.1~8.3/16.2 节、`docs/data_record_debug.md` 记录点行号——工作参数表述由「`Data_Process::rdp_parameter` 成员 / 函数形参逐层传入」改为「`struct.h::rdp_parameter` 全局对象(`data_process.cpp` 定义)」,同步删除文中传递实参的代码片段与表格行,记录点行号更新为 `data_process.cpp:75`(TWS)/ `:153`(TAS)。
- 文档同步「`scan_mode` 运行时判圈」改动(本次文档更新):README 1/2/3/4.2/7.2/8.1/10.1/10.2/10.3/10.4/11.2/12/13 节、`docs/data_process_flow_report.md` 2.2 与第 9 节、`docs/track_init_readme.md` 1.2 与输出字段表、`docs/data_record_debug.md` 记录点行号(`data_process.cpp:72` TWS / `:150` TAS)——判圈表述由「编译期 `PHASE_SCANNING`/`MECHANICAL_SCANNING` 隔离」改为「由配置项 `scan_mode` 运行时选择」,并同步 `UNCONF_TARGET` 宏删除后的语义写法。
- 修正文档中把参数来源写成「`parameters.h` 编译期宏 / 宿主下发 `RadarPara`」的过期描述(本次文档更新,对应参数配置方式改造):README 2、3、4.2、5.1、5.2、6.1、6.2、7.1、7.3、8.1~8.3、9.1~9.11、10、11、12、13 节与 `docs/data_process_flow_report.md` 第 9/10 节、`docs/track_init_readme.md` 第 1/2/4/6/8/10/11~16 节按源码同步——参数来源改为 TOML 配置文件 → `RDP_parameter`、旧宏改记为「原 …」并给出新字段名、新增 `parameters_reload` 调用方式与旧→新对照,数值未变。
- 修正文档中 TAS 航迹消亡圈数与高度平滑窗口的过期数值(本次文档更新):README 2/5.2/9.7/9.9/10.1 节与 `docs/data_process_flow_report.md` 4.6、6.1、9.1 节按 5d105d2、6284f3f 同步为「相扫 TAS 20 圈 / 机扫 96」、「相扫 H_F_WIN_LEN=6、TAS 偏移 +6/+8/+10/+12」。
- 修正 `docs/data_record_debug.md` 中「质量字段尚未加入记录实体」的过期描述(本次文档更新,对应 1ffb287):字段速查与记录点说明补齐 `quality` / `Quality` / `Unupdated_time` / `Point_sum` / `Valid_point_sum`。
- 文档同步「机扫/相扫版本合并」后的构建配置(本次文档更新):README 3/4.2~4.5/7.1/10.4/11.2/12/13 节、`docs/data_record_debug.md` 第 2 节、`docs/data_process_flow_report.md` 2.2 与第 9 节、`docs/data_record_perf_report.md` 构建路径——构建目录去掉扫描体制维度、脚本签名与任务名同步、版本串改为 `V1.5.9.GENERIC`、删除「构建体制宏」相关说明。
### Removed
- **删除旧日志开关与旧日志宏接口(日志组件 spdlog 改造)**:`parameters.h` 删除 `RDP_LOG_ENABLE` 宏(改由 `SPDLOG_ACTIVE_LEVEL` 编译期控制级别);`rdp_log` 不再提供 `RDP_LOG` 流式宏与 `rdp_log::prefix()`(改为 `RDP_LOG_*` 级别宏,时间戳/级别/文件:行号由 spdlog pattern 统一输出);删除 `rdp_log::Init()` 中 `std::cout.rdbuf()` 重定向及 `rdp_log::Shutdown()` 中的恢复逻辑(内部状态改为持有 spdlog logger 与日志路径)。
- **删除 `struct RadarPara` 与两个旧公开虚接口(参数配置方式改造)**:`data_process_class_dll.h` 中的 `struct RadarPara` 及 `track_process_parameters_initial(RadarPara)` / `track_process_parameters_modify(RadarPara)` 整体删除,由 `parameters_reload(const char*)` 取代;`data_process.h` 中的 `struct RadarPara Work_Parameter` 删除。旧 `RadarPara` 中实现未使用的字段(`R_TAS_max/min`、`V_TAS_max/min`、`TAS_Height_1~5`、`cfar_th`、`north_angle`、`Beam_num`)未进入新结构体(相应自动 TAS 逻辑仍保持停用)。
- **删除 `parameters.h` 中的雷达工作参数宏(参数配置方式改造)**:`SIGMA_R/A/E/V`、`DOT_COH_RANGE/V/AZI`、`R_MIN`/`R_MAX`、`FREQ0`、`ASSO_THORD`、`MAX_EXTRAPOLATE_NUM(_TAS)`、`H_F_WIN_LEN`、`TRACK_START_THRESHOLD`、`ALPHA_START`、`TRACK_START_QUALITY_L3~L7`、`TRACK_DIE_*`、`TRACK_QUALITY_*`、`MAX_TRACK_INDEX`、`MAX_TAS_NUM`、`TAS_QUEUE_LENGTH`、`DATA_RATE_*`(含 `H_F_WIN_LEN`、`MAX_BEAM_NUM`、`ASSOCIATE_THRESHOLD_MIN/MID/MAX`、`DIRECT_TRACKING_QUEUE_LENGTH` 等)以及已注释的 `V_MAX`/`V_MIN` 残留;这些参数改由配置文件提供(见 `README.md` 10.1~10.3)。
- **删除 `UNCONF_TARGET` 宏(参数配置方式改造)**:`parameters.h` 的未确认目标类型语义常量 `UNCONF_TARGET`(0)删除,`track_init.cpp` / `track_init_direct_tracking.cpp` 起批处 `Target_Type` 改为直接写 0,语义不变。
- **移除构建期的机扫/相扫版本区分(机扫/相扫版本合并)**:`CMakeLists.txt` 删除 `RADAR_SCAN_MODE` 缓存变量、取值校验与 `target_compile_definitions` 中的 `${RADAR_SCAN_MODE}`;构建不再定义 `PHASE_SCANNING` / `MECHANICAL_SCANNING`,`parameters.h` 的 `VERSION_INFO` 由 PS/MS 改为固定 `"GENERIC"`(版本号 1.5.9,版本串 `V1.5.9.GENERIC`);扫描模式只由运行期配置项 `[radar_params] scan_mode` 决定。构建目录/脚本/任务随之不再有扫描体制参数与 `X256_PS`/`X256_MS` 维度。
- 删除 `tasks.json` 中旧的 `CMake: 配置/构建/清理/配置并构建...` 任务(15 个),改为上述 5 个任务(机扫/相扫合并后为「清理全部」+ 4 个「配置并构建(...)」)。
## [1.5.8] - 2026-10-20
> 本次发布把 [1.5.6] 之后、文档未及同步的全部改动一并归档(提交 5cb67cc … 3088ce4)。
> 说明:中间代码状态 b4aed8a 曾把 `PATCH_VERSION` 提升为 7,但该版本未单独发布/记录,故本区块按 1.5.8 汇总;
> 版本号宏位于 `parameters.h`,本次最终为 **1.5.8**(3088ce4「更新版本号至V1.5.8」)。
### Added
- **航迹质量(Track Quality)(1ae0cf2 / d9a7bd0 / 4b70b24 / 782bff5)**:新增归一化的航迹质量体系(0~1),并在三处维护——① **临时航迹阶段**(`track_init.cpp::point_temp_track_asso()`)每延长一个点即重算质量:`d_tmp = exp(−d)`(`d` 为末点统计距离)、`snr_tmp = 1/(1+exp(−(SNR−16)/1.2))`,按 `TRACK_QUALITY_D_WEIGHT`(0.5) 与 `TRACK_QUALITY_SNR_WEIGHT`(0.3) 归一化加权;长度恰为 3 时对三个点取平均 SNR 并写满 3 点,长度 > 3 时只对末点做指数平滑(`前值×0.8 + 新值×0.2`);② **关联更新**(`track_asso.cpp::model_filter()`)在 IMM/EKF 更新后按 `d`(`exp(−d_min)`)、点迹 SNR 与「有效点数/有效点占比」三项加权(权重 0.5/0.3/0.2)得即时质量,再与历史质量指数平滑;③ **外推递减**(见 Changed)。质量在起批时随临时航迹末点带入可靠航迹,并作为普通 TWS 航迹的消亡依据。
- **航迹质量对外输出(1ae0cf2)**:`Track` 结构体在 `Amplitude` 之后新增 `float Quality`(航迹质量),输出时按 `Quality × TRACK_QUALITY_MAX`(×100)折算为 **0~100**;TWS 关联/外推输出(`track_asso.cpp`)与起批历史点输出(`track_init.cpp`)均已写入。⚠ 该字段插在结构体中部,**宿主必须用同一版本头文件重新编译**。
- **起批质量门限(d9a7bd0)**:`track_init.cpp::tmp_track_to_trust_track()` 的起批条件在「长度 3~10 点 + 不在起批屏蔽区」之外新增**按长度分级的最低质量**——3 点 ≥0.7、4 点 ≥0.6、5 点 ≥0.5、6 点 ≥0.4、7 点及以上 ≥0.3(`TRACK_START_QUALITY_L3~L7`),质量不达标的本圈不起批、留在临时航迹表继续积累。
- **TAS 航迹与关联信息入库(5cb67cc)**:TAS 通道补齐数据记录——`track_asso_tas.cpp::model_output()` 在写出航迹后回填 `Trust_Track::Range/Azimuth/Elevation/Velocity` 并逐条记录到 `RdpRecTrustTrack`(记录范围由「仅 TWS」扩展为 TWS + TAS,两通道共用一张表);`model_filter()` 把原来的局部 `asso_info` 替换为统一的 `Asso_Info`,填充 `point_id`/`track_id`/`circle_id`/`delta_T`/`delta_h` 与门限量,并在「未通过门限的候选」「被取中候选及其余候选」两处写入 `RdpRecAssoInfo`。
- `Asso_Info` / `RdpRecAssoInfo` 新增 `tas_flag` 字段(5cb67cc,`struct.h`、`rdp_record_entity.h`、`rdp_record.cpp` 注册同步):取点迹的 `point_type`,用于在同一张关联信息表中区分 TWS(0)与 TAS(1)来源。
- `Trust_Track` 新增质量相关状态(1ae0cf2 / 4b70b24,`struct.h`):`Quality`(归一化航迹质量)、`Point_sum`(本条航迹累计点数,含外推圈)、`Valid_point_sum`(累计关联到实点的次数)、`Unupdated_time`(连续未关联实点的累计毫秒数);`Temp_track` 新增 `quality`。
### Changed
- **普通 TWS 航迹的消亡条件改为按航迹质量(782bff5,`track_die.cpp`)**:`Track_Mode==0 && primary_target_flag==0` 的航迹不再按外推圈数消亡,改为 `Quality < TRACK_DIE_QUALITY`(0.3)即消亡;**重点航迹仍按 `Extrapolate_round > TRACK_DIE_ROUND_PRIMARY`(10)**、**TAS 航迹仍按 `Extrapolate_round > TRACK_DIE_ROUND_TAS`**(相扫 10 / 机扫 96;相扫阈值其后于 5d105d2 调整为 20)消亡。因此 `TRACK_DIE_ROUND`(6)在 TWS 普通航迹上已不再被引用(宏仍在 `parameters.h` 中定义),普通航迹的存活时长改由质量衰减速度决定。
- 航迹质量递减策略两度调整并最终定为「按未更新时间二次衰减」(4b70b24、782bff5,`track_asso.cpp`):先由「按外推圈数线性递减」改为「按 `Unupdated_time` 线性递减」,再改为按未更新时长的二次函数累计——记 `t = Unupdated_time/1000`(秒),每次执行 `Quality += f(t_new) − f(t_old)`,其中 `f(t) = TRACK_QUALITY_DECREASE_COEFF_O2·t² + TRACK_QUALITY_DECREASE_COEFF_O1·t`(**系数取负值** −0.0008 / −0.0025),即未关联时间越长、每圈衰减越快,结果钳位到 ≥0。
- 外推的 ΔT 计算移到外推上限判断之外(4b70b24):未关联航迹的 `delta_T` 统一按 `work_mode` 取 `DATA_RATE_SHORT/MIDDLE/FAR` 计算,不再只在上限内计算,使 `Extrapolate_round` 超限后(不再预测、不再输出)仍能累计 `Unupdated_time` 并继续降低质量,直至触发质量消亡——这是「质量消亡取代圈数消亡」能够生效的前提。
- **扫描一圈完成判据按扫描体制区分(3e6763c,`data_process.cpp::data_preprocess()`)**:相扫(`PHASE_SCANNING`)恢复「前后两帧方位波位号比较」——当前帧 `Beam_index_aiz <= last_beam_num` 判为一圈完成返回 1,否则返回 2;机扫(`MECHANICAL_SCANNING`)保持 7d5a56f 起的「方位波位号 == 0」。两分支以 `#ifdef PHASE_SCANNING` 编译期隔离,`last_beam_num` 在相扫分支重新参与判断(初值 INT_MAX,故相扫首帧即返回 1)。
- TWS 外推时间差恢复为按数据率取固定值(56b5449 → b4aed8a,`track_asso.cpp::model_filter()`):56b5449 曾改为按 `latest_timestamp − T_track` 实时计算并对 `ΔT ≤ 0` 跳过,b4aed8a 回调为按 `work_mode` 取固定数据率(避免 TAS 工作时同步外推出 TWS 航迹);`track_asso_process()`/`model_filter()` 新增的 `latest_timestamp` 形参保留但当前不参与 ΔT 计算(实时计算代码以注释保留)。
- 临时航迹记录时机前移(e982804,`track_init.cpp::track_init_process_logic()`):`RdpRecTempTrack` 的写入由函数末尾(消亡之后)移到**转为可靠航迹之前**(清空点迹之后、`tmp_track_to_trust_track()` 之前),避免本圈起批的临时航迹被转走后在记录中丢失当前帧的临时航迹数据。
### Fixed
- 修正文档中「TWS 以方位波位号 == 0 判满圈」的过期描述(本次文档更新):判圈自 3e6763c 起按体制区分,相扫已恢复比较前后波位号并重新使用 `last_beam_num`;同步更正 README 2、7.2、8.1 节与 `docs/data_process_flow_report.md` 2.2 节、`docs/track_init_readme.md` 1.2 节。
- 修正文档中记录点覆盖范围的过期描述(本次文档更新):`RdpRecTrustTrack` / `RdpRecAssoInfo` 已覆盖 TWS + TAS 两通道(此前写作「仅 TWS」),临时航迹记录点位置已前移。
### Removed
- 无。
## [1.5.6] - 2026-10-08
> 本次发布把上一轮「工作区(未提交)」条目整体归档,并补充 a4f32e4 / c3c7e66 / b227ae1 三次提交的改动。
> 版本号宏位于 `parameters.h`(`MAJOR_VERSION` / `MINOR_VERSION` / `PATCH_VERSION`),当前为 **1.5.6**。
### Added
- **新增版本号输出(b227ae1)**:`parameters.h` 新增版本宏组——`MAJOR_VERSION` / `MINOR_VERSION` / `PATCH_VERSION`(当前 1 / 5 / 6)、扫描体制后缀 `VERSION_INFO`(相扫 `PS` / 机扫 `MS`)以及拼装宏 `VERSION_STRING`(`STRINGIFY` 两级宏展开,输出形如 `V1.5.6.PS`)。`Data_process_class_dll` 基类新增公开成员 `const char* version_info`,`Data_Process_Factory::GetB()` 创建实例后赋值为 `VERSION_STRING`;宿主可在拿到实例指针后直接读取该字段上报/核对 DLL 版本。
- **数据记录表扩充为 6 张在用表(7 个实体定义)(a4f32e4 / c3c7e66 / b227ae1)**:记录实体由「2 个示例」扩展为覆盖主要中间数据的 6 张在用表——`RdpRecPoint`(TWS/TAS 输入点迹,用 `point_type` 区分,逐点写入)、`RdpRecPointCohesionTWS` / `RdpRecPointCohesionTAS`(凝聚后点迹,分别写入)、`RdpRecTempTrack`(临时航迹,`track_init_process_logic()` 末尾在消亡临时航迹之后逐点写入)、`RdpRecTrustTrack`(可靠航迹,TWS 输出循环内按本圈更新航迹逐条写入)、`RdpRecAssoInfo`(点航关联信息,TWS 关联的候选/取中/删表三处写入)。原示例实体 `RdpRecTrackOut`(本圈输出航迹)与其在 `track_process()` 中的批量记录点已整体注释停用(实体定义保留注释,故 `rdp_record_entity.h` 中共 7 个实体定义),改由 `RdpRecTrustTrack` 记录内部真实航迹。
- **点迹/航迹记录新增 ID 与圈次维度(c3c7e66、b227ae1)**:`Data_Process` 新增自增成员 `point_id` / `frame_id` / `circle_id`(`data_process.h`)——每接收一帧 `frame_id` 自增;每个点迹 `point_id` 自增并随 `PointRecv` 落库;`circle_id` 在 TWS 满一圈、TAS 每帧处理结束时自增(TWS 判圈条件其后于 3e6763c 改为按体制区分),写入该圈/帧的所有点迹。内部结构同步新增标识字段:`PointRecv` 增 `circle_id`;`Temp_track` 增 `temp_track_id` / `point_id` / `circle_id`(`Track_Init` 新增自增成员 `temp_track_id`);`Trust_Track` 增 `track_id` / `point_id` / `circle_id`(起批时取自临时航迹末点,关联成功时刷新为被关联点迹的 ID)。据此可把点迹、凝聚点迹、临时航迹、可靠航迹、关联信息按 `point_id` / `track_id` / `circle_id` 串成一条完整链路。
- **新增 `Asso_Info` 关联信息结构体(b227ae1)**:原 `Track_Asso::model_filter()` 内定义的局部结构体 `asso_info` 提升为 `struct.h` 中的全局 `Asso_Info`,在原有 `d1/d2/d3/d_min/point_index/track_index` 基础上补充 `point_id` / `track_id` / `circle_id`、门限相关量(`delta_T` / `delta_h` / `R` / `VT`、三个布尔量 `thd_d` / `thd_h` / `thd_r`)以及「本候选是否被最近邻取中」标志 `min_d_flag`,使关联信息可被完整记录与分析。
- **可靠航迹补充极坐标量快照(b227ae1)**:`Trust_Track` 新增 `Range` / `Azimuth` / `Elevation` / `Velocity` 四个字段,在 TWS 输出环节由本圈写入输出结构的距离/方位/俯仰/合速度回填(`Velocity` 取 `Range_V`,即 IMM 融合速度的模),供记录表落库;注意它们只在航迹当圈**有输出**时被刷新。
- 新增 CHANGELOG.md,采用 Keep a Changelog 格式记录项目变更历史。
- `RadarPara` 结构体新增 `DATA_RATE_TAS`(TAS 模式数据率)字段,由 DLL 调用者通过 `track_process_parameters_initial()` 输入(b3e2f52)。
- `Tracking_Target` 新增 `last_track_time` 字段,记录各 TAS 目标最近一次波束输出时间(f438ac5)。
- 新增 requirements.md,说明 TAS 波束控制逻辑的修改背景与约定(b3e2f52)。
- 更新 README.md:目录结构补充 CHANGELOG.md / requirements.md;版本历史表补充 93d3924、b3e2f52、e729c5b、f438ac5 提交记录。
- 新增日志文件输出功能:`parameters.h` 增加 `RDP_LOG_ENABLE` 开关(定义时启用;**当前源码中该宏为注释状态,即默认不重定向、日志输出到控制台**)与 `RDP_LOG_DIR` 目录宏(`.\\rdp_logs\\`);新增 `rdp_log.h/.cpp` 日志模块,首次 `Data_Process_Factory::GetB()` 时按 `RDP_LOG_ENABLE` 决定是否用 `rdbuf()` 将 `std::cout` 重定向到 `.\rdp_logs\` 下按日期+时间命名的日志文件(`rdp_YYYYMMDD_HHMMSS.log`,目录不存在自动创建),`Destroy()` 销毁实例时恢复 `std::cout` 并关闭文件;开关注释关闭时不重定向、日志仍输出到标准输出;日志文件在 `CMakeLists.txt` 的 SOURCES 中注册。
- 新增 `RDP_LOG` 日志输出宏(`rdp_log.h`):自动在每条日志前附加 `[时间戳][代码文件:行号]` 信息后再输出到标准输出(重定向开启时随之写入日志文件),供后续新增日志统一使用。
- 更新 README.md(604f158、d6cdbc0):目录结构补充 `archived_docs/` 归档目录;9.6 节改写 TWS 关联 ΔT≤0 的处理说明;功能特性、模块职责、已知问题与参考文档的文档引用路径同步指向 `archived_docs/`;版本历史表补充 604f158、d6cdbc0 提交记录。
- TWS 关联新增**高度门限**(e0bf227,`track_asso.cpp`):原实现中分距离段的高度门限代码块整体为注释状态、`d_h` 恒为 1(等于不生效),现改为独立判定 `thd_h = |h_track − h_point|` 与航迹距离分段比较后再参与关联(分段值见 Changed),与 TAS 通道的高度门限取值一致。
- 关联门限判定重构为三个显式布尔量(e0bf227):`track_asso.cpp` 与 `track_asso_tas.cpp` 的 `model_filter()` 原先把统计距离、高度、距离三类判据混写在一个长 `if` 条件里(高度门限在 TWS 侧整体被注释),现分别计算 `thd_d`(统计距离门限,按距离分段)、`thd_h`(高度门限)、`thd_r`(TWS 距离门限)后再以 `thd_d && thd_h && thd_r`(TAS 为 `thd_d && thd_h`)判定候选,逻辑等价性更易核对;TWS 侧同时移除了 `manual_tracking_flag==0` 的注释残留代码块与其缩进。
- 更新 README.md(e0bf227):目录结构(第 3 节)删除已移除的 `docs/data_process_flow_report.html` 并补充 `design_docs/` 留档目录(被 .gitignore 忽略);2、9.6、9.10、10.1、11.1、12、13 节按源码同步 `ASSO_THORD = 4`(关联波门等效 d < 4、TWS 分段比例门限 16/1000 与 16/100)、TWS/TAS 高度门限的分段取值、`thd_d/thd_h/thd_r` 三类门限判定结构、TAS 波束数据率门控与 e0bf227 提交记录;13 节参考文档表删除已不存在的 HTML 行。
- 同步 `docs/data_process_flow_report.md`(e0bf227):4.2 节波门由「统计距离 + 距离 + 恒通过的俯仰门限」改为「统计距离 thd_d + 高度 thd_h + 距离 thd_r」并给出 TWS 高度门限分段表,7 节 TAS 高度门限 2000~3000 m 段由 200 m 更正为 250 m;7.2 节判据与 9.1 节参数表按源码更新(`ASSO_THORD = 4`、`d² < 16`);8.2 节改为单条件数据率门控并在选中目标后按 CPI 时间计算外推步长。
- 输出航迹补充「靠近/远离」方向标志 `Flag_Direction`(f249fdc):TWS 关联输出(`track_asso.cpp`)与 TAS 关联输出(`track_asso_tas.cpp`)在写入 `vr_point` 后追加 `Flag_Direction = vr_point > 0 ? 1 : 0`,即径向速度为正判为靠近(1)、为零或负判为远离(0)。该字段此前已在 `data_process_class_dll.h` 的 `Track` 结构体中声明(注释「方向标志 1 靠近/0 远离」)但从未被赋值,宿主读到的是未初始化/旧值。
- 新增三份流程与专项文档(f249fdc):`docs/data_process_flow_report.md`(数据处理流程与关键逻辑说明:总体数据流、前处理与判圈、凝聚、TWS/TAS 关联与滤波、航迹起始、外推消亡与航迹号管理、TAS 波束控制、参数脉络与关键约定)、`docs/data_process_flow_report.html`(同内容的 HTML 渲染版)、`docs/track_init_readme.md`(航迹起始 Track Init 详细逻辑说明:分阶段流程、起始条件汇总、参数调参、输出字段映射、已知风险与代码索引)。
- 更新 README.md(7d5a56f、f249fdc):目录结构新增 `docs/` 目录;2、7.2、8.1 节满圈判据改为「方位波位号 == 0」并说明 `last_beam_num` 不再参与判断;8.3、9.3 节改写点-航迹头关联判据与起批输出历史点数;9.6 节补充方向标志输出与点迹消费说明;2、10.1 节 `RDP_LOG_ENABLE` 说明改为当前实际状态(注释停用、默认不重定向);11.4 节新增航迹起始未修复观察;版本历史表补充 7d5a56f、f249fdc,参考文档表补充 `docs/` 三份文档。
- 新增手动重点目标接口(50e0b5c):`data_process_class_dll.h` 增加 `primary_target_add(int track_index)`(将指定批号转为重点目标)与 `primary_target_cancel(int track_index)`(取消重点目标)两个虚接口(基类为空实现、返回 0),`Data_Process` 中按批号把 `Trust_Track::primary_target_flag` 置 1 / 清 0;重点航迹的消亡改用独立阈值 `TRACK_DIE_ROUND_PRIMARY`(= 10,两体制相同),普通航迹仍用 `TRACK_DIE_ROUND`。
- 新增未关联航迹的外推圈数上限宏(50e0b5c,`parameters.h`):`MAX_EXTRAPOLATE_NUM = 3`(TWS 通道,两体制相同)、`MAX_EXTRAPOLATE_NUM_TAS`(TAS 通道,相扫 5 / 机扫 48)。
- 输入/输出结构新增「归一化幅度」与「速度维 FFT 点数」相关字段(e8c56f9):`DataRev` 增加 `amp_asv`(目标归一化幅度 dB)与 `prn_fftn`(速度维 FFT 点数);`Track` 增加 `amp_asv_point` / `prn_fftn_point`;内部 `PointRecv`、`Trust_Track` 同步增加对应字段,`track_asso.cpp` 在关联实点时写入航迹、TWS 输出航迹时写回输出结构。⚠ 当时输入侧未打通(该描述后于本次发布更正为「已打通」,见 Changed 末条):`data_preprocess()` 未把 `DataRev.amp_asv` / `DataRev.prn_fftn` 拷入内部 `PointRecv`;**TAS 通道至今未涉及这两个字段**,TAS 输出中它们仍无有效数据来源(详见 README 11.2)。
- 更新 README.md(本次文档更新):2 节补充重点目标/外推上限/输出门控与结构体字段扩展;3 节把 `requirements.md` 移回仓库根目录;5.2、6.1、6.2、7.1、8.1、8.2、9.6、9.9、10.1、11.1、11.2、12、13 节按源码同步 `Flag_Direction` 新判据、外推圈数上限、消亡圈数(普通 6 / 重点 10 / TAS 相扫 10、机扫 96)、`Track_Update_Flag` 输出门控、新增字段与结构体布局变更提示;12 节版本历史补充 50e0b5c、fc49707、42b598e、e8c56f9 四条记录。
- 同步 `docs/data_process_flow_report.md` 与 `docs/track_init_readme.md`(本次文档更新):前者更新总体数据流表(⑥ 外推/消亡)、4.7 外推、6.1 消亡条件、7.2 TAS 外推门控、9.1 参数表与第 10 节观察;后者更新第 14 节 `Flag_Direction` 判据并补充 `amp_asv_point` / `prn_fftn_point` 两个未赋值字段。
- **新增数据记录调试功能(可选编译)**:新增 `rdp_record.h`(`rdp_record::Init()/Shutdown()/db_path()` 与 `RDP_RECORD`/`RDP_RECORD_BATCH` 记录点宏;功能关闭时宏展开为 `((void)0)`、实参不求值,头文件不含任何第三方依赖)、`rdp_record_entity.h`(记录实体=数据表定义区,含 2 个示例实体与 `RDP_RECORD_DECLARE` 实例化声明宏)、`rdp_record.cpp`(实体注册区:`YLT_REFL` + `RDP_RECORD_DEFINE`;pimpl 隔离 ormpp/SQLite;首次写入自动 `CREATE TABLE IF NOT EXISTS`;单条写入与 `std::vector` 批量事务写入;建表/插入失败每个实体只提示一次并降级为空操作;内部互斥锁串行化单连接)。`data_process_class_dll.cpp` 的 `Data_Process_Factory::GetB()/Destroy()` 分别调用 `rdp_record::Init()/Shutdown()`(关闭时为空实现),`parameters.h` 新增记录目录宏 `RDP_RECORD_DB_DIR`(`.\rdp_records\`),`.gitignore` 增加 `rdp_records/`。(实体定义区当时的「2 个示例实体」已扩展为 7 个,见本版 Added 首条。)
- 新增 2 个示例记录点(`data_process.cpp`,字段为示例可按需增删):① `data_preprocess()` 的 TWS 分支逐点写入表 **`RdpRecPointTws`**(输入点迹字段:CPI/GNSS 时间 ms、方位/俯仰波位、PRF/频点、点迹号/总数、距离/方位/俯仰/速度/幅度/SNR/RCS 等,当前实体为 22 个字段);② `track_process()` 的 TWS/TAS 处理帧结束后批量写入表 **`RdpRecTrackOut`**(输出航迹 20 个字段,两种模式共用一张表并用 `track_mode` 区分;每行取输出数组 `[i][0]`,行数超过 `MAX_TRACK_NUM` 时钳位)。⚠ 这两个实体名已随 a4f32e4 / b227ae1 演进:输入点迹表更名并扩展为 `RdpRecPoint`(TWS/TAS 共用、增 `point_id`/`frame_id`/`circle_id`),输出航迹示例整体停用、改由 `RdpRecTrustTrack` 记录内部航迹(详见本版 Added 首条的 6 张在用表清单)。
- 内置第三方源码 `third_party/ormpp/`(按上游原样保存,含 `UPSTREAM.md` 记录来源与升级方式):ormpp 0.2.0(commit `acf23989…`,Apache-2.0)+ 随其内置的 iguana 1.0.5、frozen 与 SQLite 3.49.1 amalgamation(公有领域),并保留两份 LICENSE 文件(与上游 git blob 逐字节一致)。
- 新增 `docs/data_record_debug.md`(数据记录调试功能专项说明):开关与构建(`RDP_DATA_RECORD` 取值、`configure.bat` 第 4 参数、记录模块以 C++20 独立编译与 C 编译器要求、关闭时零代码保证)、运行期行为(记录库目录与文件名、`synchronous=OFF` 取舍、`Init`/`Shutdown` 时机、失败降级、单连接 + 互斥锁与线程模型)、记录实体(表)约定(全局命名空间、结构体名即表名、首列 `int64_t id` 自增主键、类型映射表、**禁止数组字段**及原因)、**新增记录点四步配方**(含 `RDP_RECORD_DECLARE` → `YLT_REFL` + `RDP_RECORD_DEFINE` → 记录点写法 → 自动建表,以及「忘记注册报 LNK2019」自检点)、当时的 2 个示例记录点(现已扩展为 6 张在用表 + 1 个停用实体,见本版 Added 首条)、Python/DB Browser 查询示例与已知限制。
- 更新 README.md(本次文档更新):2 节新增「数据记录调试功能(可选编译)」特性条目;3 节目录结构补充 `third_party/ormpp/`、`rdp_record.h/.cpp`、`rdp_record_entity.h`、`docs/data_record_debug.md`、`rdp_records/` 并更新 `requirements.md` 说明;4.1 节环境要求补充内置依赖与 C 编译器;4.3 节补充 `RDP_DATA_RECORD` 与 `configure.bat` 第 4 参数用法;4.4 节产物补充 `rdp_record.lib`;4.5 节新增「第三方库告警例外(仅记录功能启用时)」;5.2 节模块职责新增 `rdp_record` 行;10.1 节编译期宏新增 `RDP_RECORD_ENABLE` / `RDP_RECORD_DB_DIR`;11.3 节补充「记录功能默认只在 Debug 启用」;12 节版本历史新增本次工作区条目;13 节参考文档新增 `docs/data_record_debug.md` 与 `third_party/ormpp/UPSTREAM.md`、更新 `requirements.md` 行。
### Changed
> 以下条目为上一轮「工作区(未提交)」状态的改动,随本次 1.5.6 发布一并归档(内容未改写)。
- TAS 波束控制改为按固定数据率调度:`Beam_Ctrl()` 仅当目标满足 `1 / DATA_RATE_TAS` 秒的数据率间隔(相对航迹时间 `T_track` 与上次波束输出 `last_track_time`)时才输出 TAS 跟踪波束,使 TAS 目标数据率不再随波束时间(Beam_Ctrl 调用频率)变化。
- `CMakeLists.txt` 新增缓存变量 `RDP_DATA_RECORD`(`AUTO`/`ON`/`OFF`,AUTO = Debug 启用 / Release 关闭;多配置生成器下 AUTO 直接 `FATAL_ERROR` 要求显式取值)与可选静态库 `rdp_record`:该目标以 `CXX_STANDARD 20` 独立编译(ormpp 要求 `CMAKE_CXX_STANDARD 20`,而内置 Eigen 3.3.7 在 `/std:c++20` 下会因 `std::result_of` 被 MSVC 移除而编译失败,故**雷达处理代码保持 C++17**)、经 `enable_language(C)` 编译内置 sqlite3 amalgamation(`sqlite3.c` 以源文件级 `/w` 单独降级第三方告警)、`RDP_RECORD_ENABLE` 与 `ORMPP_ENABLE_SQLITE3` 经 `PUBLIC` 传递给 DLL 并将静态库链接进 DLL;关闭时不添加任何目标、`SOURCES` 列表保持不变(Release 零痕迹的关键)。配置阶段打印启用/关闭状态。
- `parameters.h` 新增记录库目录宏 `RDP_RECORD_DB_DIR`(`.\rdp_records\`,相对宿主进程当前工作目录,目录不存在时自动创建)与开关说明注释:记录开关由构建系统定义 `RDP_RECORD_ENABLE`,**不提供手工开关宏**(避免 Release 误开)。
- `configure.bat` 新增可选第 4 参数 `AUTO|ON|OFF`(缺省 `AUTO`,即 CMake 侧默认值)并作为 `-DRDP_DATA_RECORD` 传入,回显新增 `record` 行;脚本仍保持纯 ASCII 编码。
- `.gitignore` 增加 `rdp_records/`(数据记录库输出目录)。
- `requirements.md` 内容更新为数据记录调试功能需求(结构体经 ormpp 映射写入 SQLite、Release 版可屏蔽、数据分类入不同表、宏记录点、便于快速新增结构体、不使用异步写入),本功能框架已按该说明实现。
- 本次改动的编译与运行验证:X256_PS/X256_MS × Release(记录关闭)/Debug(记录启用)四种配置均 **0 warning、0 error**;Release 产物的导出表与依赖表与改动前**逐行一致**、DLL 体积不变(PS 178,688 / MS 178,176 字节)、构建树中无记录模块目标文件、二进制内无 `sqlite` 字样;Debug(AUTO→启用)下 DLL 产物为 3,611,648 字节并额外生成 `rdp_record.lib`;运行时用临时校验程序(不提交)配合 Python `sqlite3` 验证:建库成功、一实体一表、单条与批量(事务)写入行数与取值正确、`id` 自增、`PRAGMA integrity_check` = ok、无 `-journal`/`-wal` 残留;记录目录无法创建时 DLL 接口照常返回、仅打印一次错误、后续写入静默忽略。
- TAS 波束输出目标改为队列内调度:不再固定取队首,而是遍历 TAS 队列,选择满足数据率门控且 CPI 时间最早的目标输出;原每次 Beam_Ctrl 末尾的整队循环移位(队列轮转)已停用(代码注释保留)。
- 数据率门控统一使用运行时参数 `Work_Parameter.DATA_RATE_TAS`(由 DLL 调用者经 `track_process_parameters_initial()` 输入,需为 >0 的有效值);`parameters.h` 中 `DATA_RATE_TAS` 编译期宏(相扫 0.3 / 机扫 0.0625)已注释停用,引导跟踪外推同步改用运行时参数。
- 编译时以 `#pragma message` 提示当前启用的扫描体制宏(MECHANICAL_SCANNING / PHASE_SCANNING)。
- 频点宏由 `FREQ0~FREQ20`(16.8 GHz)精简为单个 `FREQ0`,取值改为 9.2 GHz(e729c5b)。
- MSVC 编译选项按编译器版本条件追加 `/utf-8`(VS2015 及以上);MSVC 2013 不支持该选项,改用源码文件带 BOM 的 UTF-8 编码解决 C4819。
- 存量调试输出收敛为告警级 `RDP_LOG` 日志(1521592):`track_asso.cpp` / `track_asso_tas.cpp` 的 `model_filter()` 在 ΔT ≤ 0(乱序/重复时间戳)分支新增告警日志(含航迹/点迹/时间差信息,其中 `track_asso.cpp` 侧后于 604f158 注释停用);`tas_ctrl.cpp` 中 TAS 波束最早目标调度的 `printf` 调试输出移除,`data_process.cpp` 手动删除与 `track_die_tas.cpp` TAS 目标消亡的 `std::cout` 调试输出注释停用。重定向开启时上述日志随 `std::cout` 一并写入日志文件。
- `RadarPara.DATA_RATE_TAS` 单位由 Hz(1/s)改为 s(秒)(1521592):`tas_beam_output()` 的 TAS 波束输出门控由 `1 / DATA_RATE_TAS` 秒改为直接按 `DATA_RATE_TAS` 秒比较,`data_process_class_dll.h` 字段注释同步更新;调用者改按「波束输出间隔(秒)」输入该参数(原 0.3 Hz 的含义不再适用,需按新单位换算后传入)。
- requirements.md 内容更新为「雷达数据处理项目修改说明」(1521592):现描述日志文件输出功能需求(rdbuf() 重定向、日志开关宏、日期+时间命名日志、前缀日志宏,附参考示例)。
- TWS 点航关联中 ΔT ≤ 0(点迹 CPI 时间不晚于航迹时间,如 TAS 航迹遇上时间戳更早的 TWS 点迹、或重复时间戳)的候选不再被剔除(604f158):此类候选仍参与最近邻最小 d 的比较与选取,但不执行 IMM/EKF 更新、不刷新航迹时间与新鲜度(`T_track`、`Extrapolate_round`、`point_flag`、`associate_point_number` 均保持原值,该航迹仍按未关联航迹外推),同时把该点迹标记为已使用(`Use_Flag=1`)消费掉,使其无法再参与航迹起始,避免由此生成虚假 TWS 航迹;`track_asso.cpp` 中 ΔT≤0 的 `RDP_LOG` 告警日志随之注释停用(`track_asso_tas.cpp` 中同名告警日志仍启用)。
- 过期文档归档(604f158):`BUG_REPORT.md`、`BUG_FIX_REPORT.md`、`requirements.md` 由仓库根目录移入 `archived_docs/` 留档,仓库内对它们的引用路径同步改为 `archived_docs/...`。
- 扫描一圈完成判据改为「方位波位号 == 0」(7d5a56f):原判据「当前方位波位号 < 上一帧波位号」在机扫模式下遇到转台反转时波位号会变小但并未扫完一圈,造成误判;现 `data_preprocess()` 仅在 `Data_Input[0].Beam_index_aiz == 0` 时返回 1(TWS 满一圈,执行航迹处理),否则返回 2(继续蓄数据,只做滑窗凝聚)。原判据代码注释保留,`last_beam_num` 仍被赋值但不再参与判断。调用方需保证点迹的方位波位号按 0~Beam_num-1 编号且每圈必然出现 0。
- 点迹-航迹头关联判据改为统一速度区间并增加多普勒-几何一致性约束(7d5a56f,`track_init.cpp`):原先按扫描体制与距离分段收紧速度门限的分支(近程 `r≥1000` 全速 / `r<1000` 半速,中远程 `r≥2000` 全速 / `r<2000` 取 1/2~1/3 速)整体注释停用,改为 `dis ≤ V_MAX·ΔT` 且 `dis ≥ V_MIN·ΔT`,同时要求 `vr_point·v_track_head > 0`,并新增 `dis ≥ |vr_point|·ΔT` 与 `dis ≥ |v_track_head|·ΔT` 两条约束——切向高速目标在 ΔT 内的实际位移远小于 `|vr|·ΔT`,据此被排除,用于限制由切向高速点迹起批出的短航迹。其中 `vmax` 的近程/中远程分支取值相同,均取 `Work_Parameter.V_MAX`。
- 同步 `docs/` 内两份专项文档(本次文档更新):`docs/data_process_flow_report.md` 附录与 `docs/track_init_readme.md` 第 12.1 节的「与 README 的差异」说明改为「差异已按结论同步到 README」并保留更新前的旧表述作为改动缘由;其中「起始输入过滤」一行更正为「上游 `track_asso_process()` 已删除 `Use_Flag==1` 的点迹 + `track_init` 入口再次剔除」,「初始化用点」一行更正为「取下标 `track_start_point_num-3 ~ -1`,航迹更长时不是真正的末三点」;`docs/track_init_readme.md` 第 15 节第 2 条相应更正(`track_init.cpp` L505 的 `L` 覆盖为 `track_start_point_num`,第 L 个点之后的尾部点既不参与初始化也不输出),第 14 节补充说明 `Flag_Direction` 仅由关联输出路径赋值、起批输出不写该字段。
- 关联波门 `ASSO_THORD` 由 3 调大至 4(e0bf227,`parameters.h` 相扫/机扫两组宏同步):判据写作 `d*d < ASSO_THORD*ASSO_THORD`,而 `d` 本身已是马氏距离平方,等效门限由 `d < 3` 放宽为 `d < 4`(TWS 分距离段的比例门限随之由 9/1000、9/100 变为 16/1000、16/100),配合高度门限判定提高机动目标的关联概率;调参时仍需注意这一层「二次方」语义(BUG-20 有意保留)。
- TWS 与 TAS 关联的高度门限统一为同一组按航迹距离分段的取值(e0bf227):`r_track ≤ 1000 m` 时 `|h_track − h_point| ≤ 150 m`、`1000~2000 m` 时 ≤ 200 m、`2000~3000 m` 时 ≤ 250 m、`3000~5000 m` 时 ≤ 300 m、`> 5000 m` 不约束。其中 TWS 侧为该门限首次生效(原代码块被注释),TAS 侧 `2000~3000 m` 段由 200 m 放宽到 250 m。
- TAS 波束输出的数据率门控条件修正(e0bf227,`tas_ctrl.cpp::tas_beam_output()`):原判据同时要求「距上次波束输出超过 DATA_RATE_TAS」与「航迹时间 T_track 与最新时间戳之差超过 DATA_RATE_TAS」,后者与「选取 CPI 时间最早目标」的语义冲突,会额外抑制本可输出的目标;现仅保留 `delta_T_last_track = (latest_timestamp − last_track_time)/1000 > DATA_RATE_TAS` 一条门控,`delta_T` 改为在选中该目标后按 `(latest_timestamp − CPI_time)/1000` 计算并用于外推,使 TAS 目标输出间隔严格跟随数据率。
- 输出航迹改为「只输出本圈有更新的航迹」(50e0b5c):TWS `track_asso_process()` 与 TAS `track_asso_process_tas()` 的输出循环新增 `Track_Update_Flag == 1` 门控,输出后立即清零;该标志在关联到实点或外推成功时置 1。此前每个 TWS 周期都会把航迹表中的全部可靠航迹各输出一行(`Point_Sum = 1`),宿主不能再据输出行数推断航迹总数(当圈新起批的航迹仍由 `track_init` 的输出路径直接写入)。
- TWS 未关联航迹外推增加圈数上限(50e0b5c,`track_asso.cpp::model_filter()`):仅当 `Extrapolate_round <= MAX_EXTRAPOLATE_NUM`(=3)时按 `DATA_RATE_SHORT/MIDDLE/FAR` 预测一帧、推进航迹时间并置 `Track_Update_Flag = 1`;超过上限后不再推进位置与时间,只累加 `Extrapolate_round`、置 `point_flag = 0` 与 `Track_Update_Flag = 0`(该航迹此后不再输出,位置冻结在最后一次预测处),直到重新关联到实点才恢复。避免长时间未关联的航迹被反复外推到远处。
- TAS 未关联航迹外推同样增加上限(50e0b5c,`track_asso_tas.cpp`),但判定方向与 TWS 相反:`Extrapolate_round >= MAX_EXTRAPOLATE_NUM_TAS`(相扫 5 / 机扫 48)时才做预测、推进航迹时间并输出,未达该轮数时只累加 `Extrapolate_round` 且不输出(位置与航迹时间停在最后一次关联处),达到上限后才恢复逐帧外推输出(见 README 11.2 的观察)。
- 航迹消亡圈数与判据调整(50e0b5c,`parameters.h`、`track_die.cpp`、`track_die_tas.cpp`):`TRACK_DIE_ROUND` 由 4 调整为 6(两体制),新增重点航迹专用 `TRACK_DIE_ROUND_PRIMARY = 10`,`TRACK_DIE_ROUND_TAS` 相扫由 5 调整为 10(机扫保持 96);消亡判据由 `Extrapolate_round >= 阈值` 改为 `> 阈值`,并把 `manual_delete_flag == 1` 与轮数条件合并为统一的 `die_flag` 判定,TWS 侧按 `primary_target_flag` 在 6 / 10 之间二选一。因每圈未关联只自增 1,普通 TWS 航迹实际连续 7 圈无实点关联才消亡(重点航迹 11 圈,TAS 相扫 11 圈 / 机扫 97 圈)。(普通 TWS 航迹的该判据于 1.5.8 改为质量消亡,见 1.5.8 区块。)
- 「靠近/远离」方向标志 `Flag_Direction` 改为按滤波速度矢量判定(fc49707 引入、42b598e 调整,`track_asso.cpp` 与 `track_asso_tas.cpp` 同步):不再使用受多普勒速度模糊影响、符号可能取反的实测径向速度(原 `Flag_Direction = vr_point > 0 ? 1 : 0`),改为用航迹 IMM 融合状态的速度方向角 `Direction_Angle = atan2(X[4], X[1])` 与位置矢量判定:`acos((-x·cos(Direction_Angle) − y·sin(Direction_Angle)) / r) < π/2` 时置 1(靠近),否则 0(远离),其中 `x`/`y` 为经 `Sys_delay` 补偿后的位置、`r` 为其模。该判据等价于「滤波速度矢量的径向分量指向雷达(径向接近)即判为靠近」;中间版本曾按合速度归一化并把门限取为 π/4(45° 锥角),42b598e 去掉归一化并把门限放宽到 π/2(90°)。
- 手动操作接口遍历命中目标后立即 `break`(fc49707,`data_process.cpp`):`primary_target_add` / `primary_target_cancel` / `tracking_start` / `tracking_stop` 在写入标志后退出循环,不再继续扫描整张航迹表(批号唯一,结果等价)。
- `requirements.md` 由 `archived_docs/` 移回仓库根目录(工作区改动,文件内容未变):README 目录结构(第 3 节)与参考文档表(第 13 节)中的引用路径同步改回 `requirements.md`。
- **构建工具链升级:编译器由 Microsoft Visual C++ 2013(Visual Studio 12,`_MSC_VER` 1800,x86)升级为 Microsoft Visual C++ 2022(v143 工具集,`_MSC_VER` 1944,cl 19.44.35228,x64),C++ 标准由 C++11 提升为 C++17**。`CMakeLists.txt` 的 `cmake_minimum_required` 由 3.10 提升到 3.21(VS2022 生成器自 3.21 起支持),新增 `CMAKE_CXX_STANDARD 17`(含 `CMAKE_CXX_STANDARD_REQUIRED ON`)与「必须使用 MSVC 2022(`_MSC_VER >= 1930`)」的配置期强校验(不满足直接 `FATAL_ERROR`),并在配置阶段打印实际编译器路径、`_MSC_VER` 与所选 C++ 标准。
- `CMakeLists.txt` 编译选项调整:`/utf-8` 由「MSVC >= 1900 条件追加」改为对 MSVC 无条件启用;新增 `/Zc:__cplusplus`(MSVC 默认把 `__cplusplus` 报为 `199711L`,加该选项后正确报 `201703L`,使依赖该宏判断的第三方库——内置 Eigen 3.3.7——走标准分支);显式追加 `/W3`(CMake 3.15 起策略 CMP0092 不再把 `/W3` 放入 `CMAKE_CXX_FLAGS`,显式恢复以保持原有警告级别);`/Zc:wchar_t`、`/FS` 与 Release 专用的 `/Zc:strictStrings` 保持不变。
- **全部构建脚本改为 MSVC 2022**:新增 `.vscode/scripts/msvc_env.bat`(定位 VS2022 工具集与 `cl.exe`、校验路径存在后调用 `vcvarsall.bat x64`,可用 `VS2022_DIR` / `MSVC_TOOLSET_VERSION` / `MSVC_HOST_ARCH` / `MSVC_TARGET_ARCH` 环境变量覆盖默认值),`configure.bat` / `build.bat` / `clean.bat` 统一改为调用该脚本并显式传入 `-DCMAKE_CXX_COMPILER`;四个脚本一律保持**纯 ASCII 编码**(cmd.exe 按 OEM 代码页解析 .bat,中文注释会破坏其解析)。
- `configure.bat` 新增「旧工具集构建树自动清理」:构建目录存在 `CMakeCache.txt` 但缺少 `.msvc_toolset` 标记(或标记与当前工具集版本不一致)时,先整体删除该目录再重新配置。原因是 MSVC 2013 的 CMake 缓存与目标文件不能跨编译器复用——nmake 仅按时间戳判断,会把旧目标文件当作最新而跳过重编译,导致新旧工具集目标文件混链。
- VS Code 工程配置同步为 MSVC 2022 x64 + C++17:`settings.json` 的 `cmake.environment` 改为 VS2022 v143 x64 与 Windows SDK 10.0.26100.0 的 `PATH`/`INCLUDE`/`LIB`,新增 `cmake.configureArgs` 固定 `cl.exe` 路径,`C_Cpp.default.cppStandard` 改为 `c++17` 并新增 `C_Cpp.default.intelliSenseMode`;`c_cpp_properties.json` 的 `compilerPath` 指向新 `cl.exe`、`intelliSenseMode` 改为 `windows-msvc-x64`、配置名改为「X256_PS/X256_MS (MSVC2022 x64)」;`cmake-variants.yaml` 与 `tasks.json` 的变体/任务描述同步为 MSVC2022 x64。
- 产物架构由 x86 变为 **x64(AMD64)**:宿主程序必须为 64 位、并使用同一版本头文件重新编译后才能加载新 DLL(`rdp_playback.exe` 回放工具同样需升级为 x64 版本)。
- TWS 关联中 ΔT 判定的分支结构理顺(b227ae1,`track_asso.cpp::model_filter()`):原写法为 `if (delta_T <= 0) { 空分支 } else { IMM/EKF 更新 }`,现改为 `if (delta_T > 0) { 置 min_d_flag、IMM/EKF 更新 }`,行为与原来等价(ΔT > 0 才做滤波更新,ΔT ≤ 0 仍只消费点迹),但空分支被移除、可读性提高;原 ΔT≤0 的告警日志注释块一并删除。
- 记录点由「输出结构」改为「内部结构」(b227ae1):`data_process.cpp` 中按输出数组批量记录航迹的辅助函数 `rdp_record_output_tracks()` 与 `RDP_RECORD_BATCH` 调用整体移除,改在 `track_asso.cpp` 的输出循环内对内部 `Trust_Track` 逐条 `RDP_RECORD(RdpRecTrustTrack(...))`——记录的是航迹的完整内部状态(含 IMM 的 `X`/`P`/`X1`~`X3`/`P2`/`P3`/`u`),不再受输出数组容量与列映射的限制;范围仍限于本圈有输出(`Track_Update_Flag==1`)的航迹,且仅 TWS 通道。
- `Temp_track` 字段命名规范化(c3c7e66,`struct.h`):`snr` → `SNR`、`Amp` → `amplitude`、`T` → `CPI_time`、`Temp_track_section_idx` → `temp_track_section_idx`,并新增 `temp_track_id` / `point_id` / `circle_id` / `pitch_num`;`track_init.cpp`、`track_init_direct_tracking.cpp` 中的引用同步更名(含注释停用代码)。
- 输入点迹的两个「新字段」已打通(实际始于 a4f32e4 的 `data_process.cpp` 改写,本次发布确认并更正文档):`data_preprocess()` 现已把 `DataRev.amp_asv` / `DataRev.prn_fftn` 拷入内部 `PointRecv`(TWS 与 TAS 两条分支均赋值),并随 `RdpRecPoint` 落库;此前 README/CHANGELOG 记录的「输入侧未打通、输出字段无有效数据来源」已不成立(TAS 关联/输出路径仍未写回 `Track` 的这两个字段,见 11.2)。
### Fixed
- 修正文档中「`data_preprocess()` 未搬入 `amp_asv` / `prn_fftn`」的过期描述(本次文档更新):README 11.2、`docs/data_process_flow_report.md`、`docs/track_init_readme.md` 三处同步更正为「已搬入」,并保留「TAS 输出路径未写回」这一仍然成立的部分。
- 修复 MSVC `/W3` 下全部 C4819 / C4018 / C4244 编译警告:源码文件改为带 BOM 的 UTF-8 编码;signed/unsigned 不匹配处改用 `size_t` 循环变量或显式 `static_cast<int>` 比较;double/float、__int64/double 等窄化转换改为 `static_cast` 显式转换,保持原有计算逻辑不变(涉及 `data_process.cpp`、`dot_coh.cpp`、`dot_coh_tas.cpp`、`tas_ctrl.cpp`、`track_asso.cpp`、`track_asso_tas.cpp`、`track_init.cpp`、`track_init_direct_tracking.cpp`、`track_index_mangement.cpp` 等)。
- 修复 `TAS_Ctrl::tas_beam_output()` 把航迹向量下标 `earliest_track_idx` 误赋给 `Tracking_beam->TAS_track_index` 的问题(1521592):现输出实际航迹号 `(*trust_track)[earliest_track_idx].Track_Index`,避免跟踪波束批号与目标不符。
- 修正 README.md(第 2、5.2、8.1、8.3、9.3、13 节)与 `docs/data_process_flow_report.md`(第 0、1、6.1~6.4 节、附录)中航迹起始的步骤顺序描述(纯文档勘误,无代码改动):两份文档原写作「航迹头(1 点)→ 点-航迹头关联(2 点)→ 点-临时航迹关联(≥3 点)→ 起批」,与 `track_init.cpp`(L49-126)每圈的实际执行顺序相反;现更正为「① 点迹先与 ≥2 点临时航迹关联(延长已有临时航迹)→ ② 再与 1 点航迹头关联(两点卡尔曼初始化出 2 点临时航迹)→ ③ 两个阶段都结束后统一删除被消费(`Use_Flag==1`)的点迹、剩余点迹逐条转为新航迹头 → ④ 长度达标的临时航迹经三点卡尔曼初始化转为可靠航迹」,并同步修正流程图与状态机;另补充两点此前的描述缺失:两个关联阶段互不排斥(航迹头阶段不检查临时航迹阶段留下的 `Use_Flag`,同一点迹可同时延长一条临时航迹并开启一条 2 点临时航迹)、两类关联都不做一对一分配(同一条临时航迹可被同圈多个点延长)。
- 修复速度模糊时「向站/背站」判断有误的问题(fc49707、42b598e):`Flag_Direction` 不再由受多普勒模糊影响、符号可能取反的实测径向速度 `vr_point` 决定,改为按航迹滤波速度矢量与雷达视线的几何关系判定(判据见 Changed),TWS、TAS 两通道行为一致。
- 修复升级到 MSVC 2022 后在 `/W3` 下新增报出的 **17 处 C4267**(`size_t` → `int` 窄化,可能丢失数据)告警:`track_asso.cpp`(7 处)、`track_init.cpp`(5 处)、`track_init_direct_tracking.cpp`(4 处)、`track_asso_tas.cpp`(1 处)、`tas_ctrl.cpp`(1 处)按项目既有约定改为 `static_cast<int>(...)` 显式转换,不改变任何计算逻辑、对外接口与结构体布局;被编辑的源文件均已恢复「带 BOM 的 UTF-8」编码,与仓库其余文件保持一致。修复后 X256_PS/X256_MS × Release/Debug 四种配置均为 **0 warning、0 error**。
### Removed
- 删除已注释停用的无用代码残留(d6cdbc0):`data_process.cpp` 中 TWS/TAS 分支的 `std::cout` 调试输出注释块,以及 `dot_coh.cpp` 中早期「1 km 以内 SNR < 20 dB 点迹过滤」的注释代码块。仅清理注释,不改变任何运行逻辑。
- 删除 `docs/data_process_flow_report.html`(e0bf227):该 HTML 渲染版与 `docs/data_process_flow_report.md` 内容重复且已明显滞后,保留价值不足;同内容的 HTML 已在 git 忽略的 `design_docs/` 内另行留档。README 目录结构、参考文档表与版本历史表中指向该文件的条目同步移除。
- 删除 `track_asso.cpp` TWS 输出循环中已注释停用的 `manual_tracking_flag == 0` 过滤代码块(fc49707):该过滤从未生效,删除后 TWS 输出仍会把手动转 TAS(`Track_Mode==1`)的航迹一并输出(BUG-25 未修复,见 README 11.2)。
## [1.5.5] - 2026-08-27
@@ -46,11 +252,11 @@
### Fixed
- 修复若干逻辑 BUG(数组越界/下溢、局部变量未初始化、时间差无效、方位角 ±π 环绕、IMM 概率奇异、输出数组容量越界等),明细见 `BUG_FIX_REPORT.md`。
- 修复若干逻辑 BUG(数组越界/下溢、局部变量未初始化、时间差无效、方位角 ±π 环绕、IMM 概率奇异、输出数组容量越界等),明细见 `archived_docs/BUG_FIX_REPORT.md`。
- 修复 TAS 外推时间差可能为无效值的问题。
- 修复航迹起始时未输出跟踪类型、可靠航迹手动转跟踪标志未清零的问题。
- 修复量测噪声协方差矩阵 R 赋值错误、数据预处理波位号判断规则、点迹数量上限、雅可比矩阵取值、起批负索引等问题。
### Removed
- 移除 `requirements.md`、`CLAUDE.md`、Makefile 系列、Qt 工程用户文件、内置 Eigen 3.3.7 冗余文件及「航迹点迹区分区说明」等不必要文件。
- 移除 `requirements.md`(93d3924 移除,后于 b3e2f52 重新加入并改为 TAS 波束控制说明)、`CLAUDE.md`、Makefile 系列、Qt 工程用户文件、内置 Eigen 3.3.7 冗余文件及「航迹点迹区分区说明」等不必要文件。
+226 -17
View File
@@ -1,30 +1,171 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.21)
# 雷达数据处理动态链接库
# 开发环境已由 Qt/qmake 迁移到 VS Code + CMake。
# 编译器保持为 Microsoft Visual C++ Compiler 12.0 (x86)。
# 编译器已由 Microsoft Visual C++ Compiler 12.0 (VS2013) 升级为
# Microsoft Visual C++ Compiler 2022(v143 工具集,_MSC_VER 19.4x,x64)。
# 语言标准同步提升为 C++17。
#
# 注意:VS2022 生成器("Visual Studio 17 2022")需要 CMake >= 3.21,
# 因此本工程的最低 CMake 版本随之提高;NMake/Ninja 生成器同样满足。
project(data_process_class_dll LANGUAGES CXX)
# 扫描模式:
# PHASE_SCANNING -> 相扫雷达(X256_PS,原 Qt 工程默认激活配置)
# MECHANICAL_SCANNING -> 机扫雷达(X256_MS)
# 用法:cmake -S . -B build -DRADAR_SCAN_MODE=PHASE_SCANNING
set(RADAR_SCAN_MODE "PHASE_SCANNING" CACHE STRING "Radar scan mode (PHASE_SCANNING or MECHANICAL_SCANNING)")
set_property(CACHE RADAR_SCAN_MODE PROPERTY STRINGS PHASE_SCANNING MECHANICAL_SCANNING)
# 机扫 / 相扫版本已合并为同一份 DLL:构建不再区分扫描体制,也不再定义
# PHASE_SCANNING / MECHANICAL_SCANNING 编译期宏,也没有 RADAR_SCAN_MODE 缓存变量。
# 扫描模式改由运行期配置项的 `[radar_params] scan_mode` 决定(0 相扫 / 1 机扫),
# 随全部雷达工作参数一起由 TOML 配置文件经 parameters_reload() 载入,详见 README 4.2、10。
if(NOT RADAR_SCAN_MODE STREQUAL "PHASE_SCANNING" AND NOT RADAR_SCAN_MODE STREQUAL "MECHANICAL_SCANNING")
message(FATAL_ERROR "RADAR_SCAN_MODE 必须为 PHASE_SCANNING 或 MECHANICAL_SCANNING,当前值:${RADAR_SCAN_MODE}")
# ---------------------------------------------------------------------------
# 数据记录调试功能开关(可选编译,详见 docs/data_record_debug.md)
# AUTO : Debug 构建启用、Release 构建关闭(默认;交付构建不做任何记录)
# ON : 强制启用(可用于 Release 排查,需自行承担性能开销)
# OFF : 强制关闭
# 启用时新增独立静态库 rdp_record:以 C++20 编译(ormpp 要求)、编译内置的 sqlite3
# amalgamation,并链接进 DLL;源码中的 RDP_RECORD 宏由 RDP_RECORD_ENABLE 控制,关闭时
# 展开为空操作(实参不求值),记录模块完全不参与编译。
# 依赖来源与升级方式见 third_party/ormpp/UPSTREAM.md。
# ---------------------------------------------------------------------------
set(RDP_DATA_RECORD "AUTO" CACHE STRING "数据记录调试功能:AUTO(Debug=ON/Release=OFF) / ON / OFF")
set_property(CACHE RDP_DATA_RECORD PROPERTY STRINGS AUTO ON OFF)
if(RDP_DATA_RECORD STREQUAL "AUTO")
if(CMAKE_CONFIGURATION_TYPES)
message(FATAL_ERROR "多配置生成器下 RDP_DATA_RECORD=AUTO 无法判定,请显式传入 ON 或 OFF")
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(RDP_RECORD_ENABLED TRUE)
else()
set(RDP_RECORD_ENABLED FALSE)
endif()
elseif(RDP_DATA_RECORD STREQUAL "ON")
set(RDP_RECORD_ENABLED TRUE)
elseif(RDP_DATA_RECORD STREQUAL "OFF")
set(RDP_RECORD_ENABLED FALSE)
else()
message(FATAL_ERROR "RDP_DATA_RECORD 必须为 AUTO / ON / OFF,当前值:${RDP_DATA_RECORD}")
endif()
if(RDP_RECORD_ENABLED)
message(STATUS "数据记录调试功能:启用(C++20 静态库 rdp_record + 内置 SQLite)")
else()
message(STATUS "数据记录调试功能:关闭(不编译记录模块,RDP_RECORD 为空操作)")
endif()
# ---------------------------------------------------------------------------
# 日志编译期级别(spdlog)
# 原 parameters.h 的 RDP_LOG_ENABLE 布尔开关已移除:日志级别改由 spdlog 的
# SPDLOG_ACTIVE_LEVEL 在编译期控制(低于该级别的 RDP_LOG_* 日志在编译期被
# 剔除、实参不求值,OFF 即等价于原「关闭日志模块」)。本缓存变量把级别映射
# 到 SPDLOG_ACTIVE_LEVEL,并同时用于 data_process_class_dll 与 rdp_record
# 两个目标(两个目标使用同一份 spdlog 头文件配置,必须保持一致)。
# AUTO : Debug 构建 = TRACE,其余构建 = INFO(默认)
# TRACE / DEBUG / INFO / WARN / ERROR / CRITICAL : 显式指定最低编译级别
# OFF : 编译期关闭全部 RDP_LOG_* 日志(rdp_log::Init() 也不再创建日志文件)
# 修改方式:cmake -DRDP_LOG_LEVEL=WARN ...(或在 CMakeCache 中修改后重新配置)
# ---------------------------------------------------------------------------
set(RDP_LOG_LEVEL "AUTO" CACHE STRING "日志编译期级别:AUTO(Debug=TRACE/其它=INFO) / TRACE / DEBUG / INFO / WARN / ERROR / CRITICAL / OFF")
set_property(CACHE RDP_LOG_LEVEL PROPERTY STRINGS AUTO TRACE DEBUG INFO WARN ERROR CRITICAL OFF)
if(RDP_LOG_LEVEL STREQUAL "AUTO")
if(CMAKE_CONFIGURATION_TYPES)
set(RDP_LOG_ACTIVE_LEVEL_DEFS
"$<$<CONFIG:Debug>:SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE>"
"$<$<NOT:$<CONFIG:Debug>>:SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO>")
set(RDP_LOG_LEVEL_DESC "AUTO(多配置生成器:Debug=TRACE,其它=INFO)")
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(RDP_LOG_ACTIVE_LEVEL_DEFS "SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE")
set(RDP_LOG_LEVEL_DESC "AUTO(Debug 构建 → TRACE)")
else()
set(RDP_LOG_ACTIVE_LEVEL_DEFS "SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO")
if(CMAKE_BUILD_TYPE)
set(RDP_LOG_LEVEL_DESC "AUTO(${CMAKE_BUILD_TYPE} 构建 → INFO)")
else()
set(RDP_LOG_LEVEL_DESC "AUTO(未指定构建类型 → INFO)")
endif()
endif()
elseif(RDP_LOG_LEVEL STREQUAL "OFF")
set(RDP_LOG_ACTIVE_LEVEL_DEFS "SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_OFF")
set(RDP_LOG_LEVEL_DESC "OFF(编译期关闭全部 RDP_LOG_* 日志)")
elseif(RDP_LOG_LEVEL MATCHES "^(TRACE|DEBUG|INFO|WARN|ERROR|CRITICAL)$")
set(RDP_LOG_ACTIVE_LEVEL_DEFS "SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_${RDP_LOG_LEVEL}")
set(RDP_LOG_LEVEL_DESC "${RDP_LOG_LEVEL}")
else()
message(FATAL_ERROR
"RDP_LOG_LEVEL 必须为 AUTO / TRACE / DEBUG / INFO / WARN / ERROR / CRITICAL / OFF,"
"当前值:${RDP_LOG_LEVEL}")
endif()
message(STATUS "日志编译期级别:${RDP_LOG_LEVEL_DESC}(映射为 SPDLOG_ACTIVE_LEVEL)")
# ---------------------------------------------------------------------------
# 语言标准:C++17
# ---------------------------------------------------------------------------
# CMAKE_CXX_STANDARD 由 CMake 转换为 MSVC 的 /std:c++17(CMake >= 3.10 起支持)。
# CMAKE_CXX_EXTENSIONS OFF 在 MSVC 下没有对应开关(只影响 GCC/Clang 的 -std=gnu++xx),
# 保留它是为了在换用其它编译器时同样不启用语言扩展。
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# ---------------------------------------------------------------------------
# 编译器约束:必须使用 MSVC 2022(v143,_MSC_VER >= 1930)
# ---------------------------------------------------------------------------
if(NOT MSVC)
message(FATAL_ERROR "本工程仅支持 MSVC (cl.exe) 编译器,当前编译器:${CMAKE_CXX_COMPILER_ID}")
endif()
if(MSVC_VERSION LESS 1930)
message(FATAL_ERROR
"本工程要求 MSVC 2022 (v143) 及以上版本(_MSC_VER >= 1930),"
"当前 _MSC_VER=${MSVC_VERSION}(编译器:${CMAKE_CXX_COMPILER})。\n"
"请先运行 VS2022 的 vcvarsall.bat x64(见 .vscode/scripts/msvc_env.bat)后再配置。")
endif()
message(STATUS "编译器:${CMAKE_CXX_COMPILER}(MSVC _MSC_VER=${MSVC_VERSION})")
message(STATUS "C++ 标准:C++${CMAKE_CXX_STANDARD}")
# 导出编译命令,便于 VS Code C/C++ 插件 IntelliSense 使用。
# Visual Studio 生成器不支持该选项,因此仅对 Makefile/Ninja 生成器开启。
if(NOT CMAKE_GENERATOR MATCHES "Visual Studio")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
endif()
# ---------------------------------------------------------------------------
# 内置第三方库 spdlog(日志,v1.17.0,见 third_party/spdlog/UPSTREAM.md)
# 以静态库方式编译(SPDLOG_COMPILED_LIB),供 data_process_class_dll 与
# rdp_record(C++20 静态库,rdp_record.cpp 内也有日志)链接:
# - PUBLIC 编译定义 SPDLOG_COMPILED_LIB / SPDLOG_ACTIVE_LEVEL 与头文件
# 目录,保证所有使用方(含跨 C++17/C++20 的两目标)配置一致;
# - SPDLOG_ACTIVE_LEVEL 由上方 RDP_LOG_LEVEL 决定;
# - 第三方源码以 /w 编译,维持本项目 /W3 零告警约定(见 README 4.5)。
# ---------------------------------------------------------------------------
set(SPDLOG_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/spdlog")
add_library(spdlog STATIC
third_party/spdlog/src/spdlog.cpp
third_party/spdlog/src/async.cpp
third_party/spdlog/src/cfg.cpp
third_party/spdlog/src/color_sinks.cpp
third_party/spdlog/src/file_sinks.cpp
third_party/spdlog/src/stdout_sinks.cpp
third_party/spdlog/src/bundled_fmtlib_format.cpp
)
target_include_directories(spdlog PUBLIC ${SPDLOG_ROOT}/include)
target_compile_definitions(spdlog PUBLIC
SPDLOG_COMPILED_LIB
${RDP_LOG_ACTIVE_LEVEL_DEFS}
)
if(MSVC)
target_compile_options(spdlog PRIVATE /w /utf-8 /Zc:__cplusplus /Zc:wchar_t /FS)
endif()
set_target_properties(spdlog PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)
message(STATUS "第三方日志库:spdlog v1.17.0(静态库,third_party/spdlog)")
set(SOURCES
data_process_class_dll/rdp_log.cpp
data_process_class_dll/data_process_class_dll.cpp
data_process_class_dll/data_process.cpp
data_process_class_dll/rdp_parameter_loader.cpp
data_process_class_dll/dot_coh.cpp
data_process_class_dll/kalman.cpp
data_process_class_dll/coor_trans.cpp
@@ -46,10 +187,63 @@ set(PUBLIC_HEADERS
add_library(data_process_class_dll SHARED ${SOURCES})
# 日志库(静态库 spdlog):
# 目标级 PUBLIC 接口提供 spdlog 头文件目录、SPDLOG_COMPILED_LIB 与 SPDLOG_ACTIVE_LEVEL,
# 使 rdp_log.cpp 及各业务模块(RDP_LOG_* 宏)以一致的配置编译。
target_link_libraries(data_process_class_dll PRIVATE spdlog)
# ---------------------------------------------------------------------------
# 数据记录模块(RDP_DATA_RECORD 启用时构建):独立静态库、C++20(ormpp 要求),
# 与雷达处理代码(C++17)通过 rdp_record.h 的「仅声明 + 显式实例化」跨标准边界。
# ---------------------------------------------------------------------------
if(RDP_RECORD_ENABLED)
enable_language(C) # 用于编译 sqlite3 amalgamation
add_library(rdp_record STATIC
data_process_class_dll/rdp_record.cpp
third_party/ormpp/thirdparty/sqlite3/sqlite3.c
)
set_target_properties(rdp_record PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)
# RDP_RECORD_ENABLE 经 PUBLIC 传递给 DLL:data_process.cpp 等源码中的 RDP_RECORD 宏随之生效
target_compile_definitions(rdp_record PUBLIC
RDP_RECORD_ENABLE
ORMPP_ENABLE_SQLITE3
)
target_include_directories(rdp_record
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/data_process_class_dll
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/third_party/ormpp
${CMAKE_CURRENT_SOURCE_DIR}/third_party/ormpp/iguana
${CMAKE_CURRENT_SOURCE_DIR}/third_party/ormpp/ormpp
${CMAKE_CURRENT_SOURCE_DIR}/third_party/ormpp/thirdparty/sqlite3
)
target_compile_options(rdp_record PRIVATE
# /W3 与 /Zc:__cplusplus 只用于 C++ 源文件:sqlite3.c 由下方源文件级 /w 关闭告警,
# 若 C 文件也带 /W3,cl 会打印 D9025(overriding '/W3' with '/w')命令行提示。
$<$<COMPILE_LANGUAGE:CXX>:/W3>
/utf-8
$<$<COMPILE_LANGUAGE:CXX>:/Zc:__cplusplus>
/FS
)
# 第三方 amalgamation 单独降到 /w(源文件级选项在编译命令行中位于目标级之后,优先生效)
set_source_files_properties(third_party/ormpp/thirdparty/sqlite3/sqlite3.c
PROPERTIES COMPILE_OPTIONS "/w")
# rdp_record.cpp 内的 RDP_LOG_* 日志经 rdp_log.h 使用 spdlog:
# 链接 spdlog 目标以获得其 PUBLIC 头文件目录与 SPDLOG_COMPILED_LIB /
# SPDLOG_ACTIVE_LEVEL 定义(与 DLL 目标保持一致)。
target_link_libraries(rdp_record PRIVATE spdlog)
target_link_libraries(data_process_class_dll PRIVATE rdp_record)
endif()
# 编译 DLL 时定义导出宏;引用 DLL 的工程不应定义该宏。
target_compile_definitions(data_process_class_dll PRIVATE
DATA_PROCESS_CLASS_DLL_LIBRARY
${RADAR_SCAN_MODE}
)
# 与 qmake 工程保持一致的 Windows 编译定义
@@ -64,20 +258,35 @@ if(WIN32)
endif()
# Eigen 头文件位于源码目录下(<Eigen/Dense>)。
# toml++ 为单头文件第三方库(<toml.hpp>,仅 rdp_parameter_loader.cpp 使用)。
target_include_directories(data_process_class_dll PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/data_process_class_dll
${CMAKE_CURRENT_SOURCE_DIR}/third_party/toml
)
# 原工程使用 MSVC 2013(Visual Studio 12)编译器;保持兼容,不强制更高 C++ 标准。
# 源码使用 C++11 特性(nullptr、类内成员初始化等),MSVC 2013 默认即可支持。
if(MSVC)
# 与原 qmake 生成的编译选项保持一致。
# 注意:MSVC 2013 的 Debug 运行库与 /Zc:strictStrings 同时使用会触发 STL 编译错误,
# 原 qmake 工程也仅在 Release 配置中使用该选项。
target_compile_options(data_process_class_dll PRIVATE
# 显式恢复 /W3 警告级别。
# CMake 3.15(策略 CMP0092)起不再把 /W3 放进 CMAKE_CXX_FLAGS,
# 而本工程此前(cmake_minimum_required 3.10)一直以 /W3 构建,
# README 4.5 记录的全部 C4819/C4018/C4244 告警清理也基于该级别,
# 因此这里显式指定,保持警告级别不变。
/W3
# MSVC 2022 完整支持 /utf-8(VS2015 Update 2 起),
# 源码本身为 UTF-8 with BOM,显式指定后中文字符串/注释不再受系统代码页影响(消除 C4819)。
/utf-8
# 让 __cplusplus 正确报告 201703L(MSVC 默认始终为 199711L)。
# C++17 下依赖 __cplusplus 判断的第三方库(Eigen 3.3.7 等)据此走标准分支。
/Zc:__cplusplus
/Zc:wchar_t
# NMake 生成器下并行/增量编译时统一 PDB 写入。
/FS
)
# 与原 qmake 生成的编译选项保持一致:Release 配置额外开启严格字符串字面量检查。
# (原 MSVC 2013 因 Debug 运行库与 /Zc:strictStrings 冲突而仅限 Release,
# 升级到 MSVC 2022 后该限制已不存在,此处仍沿用“仅 Release”以保持行为不变。)
target_compile_options(data_process_class_dll PRIVATE
$<$<CONFIG:Release>:/Zc:strictStrings>
)
endif()
+707 -198
View File
File diff suppressed because it is too large Load Diff
+163
View File
@@ -0,0 +1,163 @@
# 雷达数据处理配置文件(TOML 格式)
#
# 说明:
# 1) 配置段与 struct.h::RDP_parameter 的注释分组一一对应,配置键名与结构体成员名完全一致;
# 2) 所有标量键均为必填项,且会做类型与取值范围校验(详见 rdp_parameter_loader.cpp);
# 3) 屏蔽区数组使用与结构体成员同名的 TOML 数组,元素个数必须等于对应 *_num(0~30);
# *_num = 0 时数组可省略,存在时必须为空数组。
## 雷达工作参数
[radar_params]
# 工作模式:0 近程 / 1 中程 / 2 远程
radar_work_mode = 0
# 扫描模式:0 相扫模式 / 1 机扫模式
scan_mode = 0
# 频点0频率(GHz)
freq_0_GHz = 9.2
# 雷达平台高度(m)
radar_height = 0
# 系统延迟(s)
radar_system_delay = 0
# TWS模式数据率(s)
data_rate_tws = 3
# TAS模式数据率(s)
data_rate_tas = 1
# 纯跟踪模式数据率(s)
data_rate_track_only = 0.2
## 点迹凝聚参数
[cohesion]
# 距离凝聚范围(m)
point_cohesion_range = 80
# 速度凝聚范围(m/s)
point_cohesion_velocity = 2
# 方位凝聚范围(度)
point_cohesion_azimuth = 6
# 最小凝聚距离(m)
point_cohesion_range_min = 100
# 最大凝聚距离(m)
point_cohesion_range_max = 100000
## 航迹关联参数
[track_associate]
# 距离量测精度(m)
sigma_range = 10.0
# 方位量测精度(弧度)
sigma_azimuth = 0.02
# 俯仰量测精度(弧度)
sigma_pitch = 0.2
# 速度量测精度(m/s)
sigma_velocity = 2.0
# 关联门限
associate_threshold = 4.0
# 最大外推点数
extrapolate_max = 3
# TAS最大外推点数
extrapolate_max_tas = 5
# 高度平滑窗长
height_smooth_win = 6
## 航迹起始参数
[track_init]
# 起航波门
track_init_threshold = 8
# 起航夹角(度)
track_init_angle = 60
# 最少起批点数(3~9)
track_init_point_num_min = 3
# 3点起批质量门限
track_init_quality_L3 = 0.7
# 4点起批质量门限
track_init_quality_L4 = 0.6
# 5点起批质量门限
track_init_quality_L5 = 0.5
# 6点起批质量门限
track_init_quality_L6 = 0.4
# 7点起批质量门限
track_init_quality_L7 = 0.3
# 航迹起始最小速度(m/s)
track_init_velocity_min = 1
# 航迹起始最大速度(m/s)
track_init_velocity_max = 50
## 航迹消亡参数
[track_die]
# 航迹消亡圈数
track_die_round = 6
# 重点航迹消亡圈数
track_die_round_pramary = 10
# TAS航迹消亡圈数
track_die_round_tas = 20
# 航迹消批质量条件
track_die_quality = 0.3
# 重点航迹消批质量条件
track_die_quality_pramary = 0.3
## 航迹质量管理参数
[track_quality]
# 航迹质量最大值
track_quality_max = 100
# 航迹质量最小值
track_quality_min = 0
# 马氏距离权重
track_quality_d_weight = 0.5
# 信噪比权重
track_quality_snr_weight = 0.3
# 航迹长度权重
track_quality_len_weight = 0.2
# 航迹质量迭代系数
track_quality_iter_coeff = 0.8
# 航迹质量下降系数2次项(必须 <= 0)
track_quality_decrease_coeff_o2 = -0.0008
# 航迹质量下降系数1次项(必须 <= 0)
track_quality_decrease_coeff_o1 = -0.0025
## 航迹批号管理
[track_index]
# 最大航迹批号(1~500)
track_index_max = 500
## 波束调度
[beam_control]
# 默认跟踪波束类型
default_track_beam_type = 1
# 最大同时跟踪目标数(TAS 队列容量上限 30)
tas_track_num_max = 4
## singer模型机动参数(均必须 > 0)
[singer_model]
# 模型1
model_1_Q_fast = 0.000001
model_1_Q_slow = 0.000001
# 模型2
model_2_Q_fast = 0.01
model_2_Q_slow = 0.01
# 模型3
model_3_Q_fast = 1.0
model_3_Q_slow = 1.0
## 航迹起始屏蔽区(数组元素个数必须等于 track_init_prohibite_area_num)
[track_init_prohibite_area]
# 航迹起始屏蔽区数(0~30)
track_init_prohibite_area_num = 0
# 最小距离 (m)
track_init_prohibite_area_range_min = []
# 最大距离 (m)
track_init_prohibite_area_range_max = []
# 最小方位角 (度)
track_init_prohibite_area_azimuth_min = []
# 最大方位角 (度)
track_init_prohibite_area_azimuth_max = []
## 禁止TAS跟踪区域(数组元素个数必须等于 tas_prohibite_area_num)
[tas_prohibite_area]
# 禁止TAS的区域数(0~30)
tas_prohibite_area_num = 0
# 最小距离 (m)
tas_prohibite_area_range_min = []
# 最大距离 (m)
tas_prohibite_area_range_max = []
# 最小方位角 (度)
tas_prohibite_area_azimuth_min = []
# 最大方位角 (度)
tas_prohibite_area_azimuth_max = []
+1 -1
View File
@@ -1,4 +1,4 @@
#include "coor_trans.h"
#include "coor_trans.h"
#include <cmath>
#include <cstring>
#include <vector>
+1 -1
View File
@@ -1,4 +1,4 @@
#ifndef COOR_TRANS_H
#ifndef COOR_TRANS_H
#define COOR_TRANS_H
#include "parameters.h"
+152 -71
View File
@@ -1,10 +1,17 @@
#include "data_process.h"
#include "data_process.h"
#include "rdp_log.h"
#include "rdp_record.h"
#include "rdp_parameter_loader.h"
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
#include <iostream>
using namespace std;
// 全局工作参数对象(声明见 struct.h)。所有处理模块直接访问,由 parameters_reload() 加载。
RDP_parameter rdp_parameter{};
/*******************************************************************************/
/*******************************************************************************/
/************************************用户接口函数*******************************/
@@ -16,75 +23,119 @@ int Data_Process::data_preprocess(struct DataRev Data_Input[150])
{
// 更新当前系统时间
latest_timestamp = Data_Input[0].CPI_time;
frame_id++;
//数据存入缓存区域 Data_buffer
if(Data_Input[0].point_type == 0) //tws数据
{
data_num=min(Data_Input[0].Point_Sum, 150);
//std::cout << "TARGET azi beam idx:" <<Data_Input[0].Beam_index_aiz;
#ifdef RDP_RECORD_ENABLE
//记录输入点迹:处理线程内先收集源结构体,循环结束后一次入队(构造实体与写库由记录写线程完成)
std::vector<PointRecv> rec_points;
rec_points.reserve(static_cast<std::size_t>(data_num));
#endif
for (int i=0; i<data_num;i++)
{
PointRecv Data_buffer_temp;
Data_buffer_temp.Amplitude=Data_Input[i].Amplitude;
Data_buffer_temp.Azimuth=Data_Input[i].Azimuth/180*PI;
Data_buffer_temp.snr = Data_Input[i].Snr;
Data_buffer_temp.point_id = point_id++;
Data_buffer_temp.frame_id = frame_id;
Data_buffer_temp.circle_id = circle_id;
Data_buffer_temp.amplitude=Data_Input[i].Amplitude;
Data_buffer_temp.azimuth=Data_Input[i].Azimuth/180*PI;
Data_buffer_temp.SNR = Data_Input[i].Snr;
Data_buffer_temp.RCS = Data_Input[i].RCS;
Data_buffer_temp.CPI_Time = Data_Input[i].CPI_time;
Data_buffer_temp.CPI_time = Data_Input[i].CPI_time;
Data_buffer_temp.GNSS_time = Data_Input[i].GNSS_time;
Data_buffer_temp.Freq_index = Data_Input[i].Freq_index;
Data_buffer_temp.freq_index = Data_Input[i].Freq_index;
Data_buffer_temp.PRF_index = Data_Input[i].PRI;
Data_buffer_temp.Range = Data_Input[i].Range;
Data_buffer_temp.Height= Data_Input[i].Range*sin(Data_Input[i].Elevation/180*PI);
Data_buffer_temp.Use_Flag = 0;
Data_buffer_temp.Velocity = Data_Input[i].Velocity;
Data_buffer_temp.pitch_num = Data_Input[i].pitch_num;
Data_buffer_temp.range = Data_Input[i].Range;
Data_buffer_temp.height= Data_Input[i].Range*sin(Data_Input[i].Elevation/180*PI);
Data_buffer_temp.use_flag = 0;
Data_buffer_temp.velocity = Data_Input[i].Velocity;
Data_buffer_temp.encoder_value = Data_Input[i].Encoder_value;
Data_buffer_temp.pitch_beam_index = Data_Input[i].pitch_num;
Data_buffer_temp.azimuth_beam_index = Data_Input[i].Beam_index_aiz;
Data_buffer_temp.point_type = Data_Input[i].point_type;
Data_buffer_temp.amp_asv_point = Data_Input[i].amp_asv;
Data_buffer_temp.prn_fftn_point = Data_Input[i].prn_fftn;
std::memcpy(Data_buffer_temp.speed_dim, Data_Input[i].speed_dim, sizeof(Data_buffer_temp.speed_dim));
std::memcpy(Data_buffer_temp.range_dim, Data_Input[i].range_dim, sizeof(Data_buffer_temp.range_dim));
#ifdef RDP_RECORD_ENABLE
rec_points.push_back(Data_buffer_temp);
#endif
Data_buffer.push_back(Data_buffer_temp);
}
if(Data_Input[0].Beam_index_aiz < last_beam_num) //TWS
{
last_beam_num = Data_Input[0].Beam_index_aiz;
#ifdef RDP_RECORD_ENABLE
//判圈之前(所有 return 之前)把本帧收集的点迹一次入队
RDP_RECORD_POINTS(rec_points);
#endif
// 判圈:扫描模式由配置参数 scan_mode 决定(0=相扫,1=机扫),替代原编译期 PHASE_SCANNING/MECHANICAL_SCANNING 分支。
// 相扫:判断前后两帧波位号的大小,来判断是否扫描一周完成(当前波位号 <= 上一帧波位号即判满圈);
// 机扫:可能存在转台反转导致波位号变小但并未扫描一周,因此改为波位号为 0 时判满圈。
const bool circle_done = (rdp_parameter.scan_mode == 0)
? (Data_Input[0].Beam_index_aiz <= last_beam_num) //TWS 相扫
: (Data_Input[0].Beam_index_aiz == 0); //TWS 机扫
last_beam_num = Data_Input[0].Beam_index_aiz;
if(circle_done)
{
circle_id++;
// std::cout << Data_Input[0].CPI_time << std::endl;
// RDP_LOG_DEBUG("CPI_time: {}", Data_Input[0].CPI_time);
return 1;
}
else
{
last_beam_num = Data_Input[0].Beam_index_aiz;
return 2;
}
}
else if(Data_Input[0].point_type == 1) //tas数据
{
// std::cout << "TAS TARGET :" <<Data_Input[0].TAS_track_index;
//std::cout << "TAS time :" <<Data_Input[0].CPI_time;
data_num = min(max(Data_Input[0].Point_Sum, 0), 150);
TAS_track_idx = Data_Input[0].TAS_track_index;
latest_timestamp = Data_Input[0].CPI_time;
TAS_track_fingerprint = Data_Input[0].TAS_track_index;
#ifdef RDP_RECORD_ENABLE
//记录输入点迹:处理线程内先收集源结构体,循环结束后一次入队(构造实体与写库由记录写线程完成)
std::vector<PointRecv> rec_points;
rec_points.reserve(static_cast<std::size_t>(data_num));
#endif
for (int i=0; i<data_num;i++)
{
PointRecv Data_buffer_temp;
Data_buffer_temp.Amplitude=Data_Input[i].Amplitude;
Data_buffer_temp.Azimuth=Data_Input[i].Azimuth/180*PI;
Data_buffer_temp.snr = Data_Input[i].Snr;
Data_buffer_temp.point_id = point_id++;
Data_buffer_temp.frame_id = frame_id;
Data_buffer_temp.circle_id = circle_id;
Data_buffer_temp.amplitude=Data_Input[i].Amplitude;
Data_buffer_temp.azimuth=Data_Input[i].Azimuth/180*PI;
Data_buffer_temp.SNR = Data_Input[i].Snr;
Data_buffer_temp.RCS = Data_Input[i].RCS;
Data_buffer_temp.CPI_Time = Data_Input[i].CPI_time;
Data_buffer_temp.CPI_time = Data_Input[i].CPI_time;
Data_buffer_temp.GNSS_time = Data_Input[i].GNSS_time;
Data_buffer_temp.Freq_index = Data_Input[i].Freq_index;
Data_buffer_temp.freq_index = Data_Input[i].Freq_index;
Data_buffer_temp.PRF_index = Data_Input[i].PRI;
Data_buffer_temp.Range = Data_Input[i].Range;
Data_buffer_temp.Height= Data_Input[i].Range*sin(Data_Input[i].Elevation/180*PI);
Data_buffer_temp.Use_Flag = 0;
Data_buffer_temp.Velocity = Data_Input[i].Velocity;
Data_buffer_temp.pitch_num = Data_Input[i].pitch_num;
Data_buffer_temp.range = Data_Input[i].Range;
Data_buffer_temp.height= Data_Input[i].Range*sin(Data_Input[i].Elevation/180*PI);
Data_buffer_temp.use_flag = 0;
Data_buffer_temp.velocity = Data_Input[i].Velocity;
Data_buffer_temp.pitch_beam_index = Data_Input[i].pitch_num;
Data_buffer_temp.encoder_value = Data_Input[i].Encoder_value;
Data_buffer_temp.azimuth_beam_index = Data_Input[i].Beam_index_aiz;
Data_buffer_temp.point_type = Data_Input[i].point_type;
Data_buffer_temp.amp_asv_point = Data_Input[i].amp_asv;
Data_buffer_temp.prn_fftn_point = Data_Input[i].prn_fftn;
std::memcpy(Data_buffer_temp.speed_dim, Data_Input[i].speed_dim, sizeof(Data_buffer_temp.speed_dim));
std::memcpy(Data_buffer_temp.range_dim, Data_Input[i].range_dim, sizeof(Data_buffer_temp.range_dim));
#ifdef RDP_RECORD_ENABLE
rec_points.push_back(Data_buffer_temp);
#endif
Data_buffer_tas.push_back(Data_buffer_temp);
// std::cout << "Azimuth:" << Data_Input[i].Azimuth
@@ -94,7 +145,11 @@ int Data_Process::data_preprocess(struct DataRev Data_Input[150])
// << "data_num:" << data_num;
}
//circle_id++ 之前把本帧收集的点迹一次入队
#ifdef RDP_RECORD_ENABLE
RDP_RECORD_POINTS(rec_points);
#endif
circle_id++;
return 3;
}
@@ -117,8 +172,7 @@ int model)
if(model==2)
{
dot_coh.dot_coh_process_buff(&Data_buffer,&point_recv,Work_Parameter);
//std::cout << "point_recv :" <<point_recv.size();
dot_coh.dot_coh_process_buff(&Data_buffer,&point_recv);
std::vector<PointRecv>().swap(Data_buffer);
}
@@ -131,7 +185,7 @@ int model)
&trust_track,
Trust_Track_Output,
Trust_track_num_Output,
Work_Parameter
latest_timestamp
);
track_die.track_die_process( &trust_track,
@@ -142,13 +196,7 @@ int model)
&trust_track,
&temp_track,
Trust_Track_Output,
Trust_track_num_Output,
Work_Parameter);
// std::cout << "point_recv: " <<point_recv.size();
// std::cout << "trust_track: " <<trust_track.size();
// std::cout << "temp_track: " <<temp_track.size();
// std::cout << "Trust_track_num_Output: " <<*Trust_track_num_Output;
// std::cout << "Track_die_num_Output: " <<*Track_die_num_Output;
Trust_track_num_Output);
}
@@ -158,13 +206,18 @@ int model)
*Trust_track_num_Output=0; //航迹更新数
*Track_die_num_Output=0; //航迹消亡数目
dot_coh_tas.dot_coh_tas_process(&Data_buffer_tas,&point_recv_tas); //点迹凝聚
//查找指定指纹的Track_idx
int tas_track_idx = -1;
for(auto& t : trust_track){
if(t.Track_fingerprint == TAS_track_fingerprint){
tas_track_idx = t.Track_Index;
t.manual_tracking_flag = 1;
t.Track_Mode = 1;
break;
}
}
// if(point_recv_tas.size()>0)
// {
// std::cout << "point_recv_tas :" <<point_recv_tas[0].Azimuth/PI*180<<" "<<point_recv_tas[0].Range;
// }
//std::cout << "TAS process :" << TAS_track_idx << "p a r:"<<point_recv_tas[0].Point_Sum<<" " <<point_recv_tas[0].Azimuth/PI*180<<" "<<point_recv_tas[0].Range;
dot_coh_tas.dot_coh_tas_process(&Data_buffer_tas,&point_recv_tas); //点迹凝聚
std::vector<PointRecv>().swap(Data_buffer_tas);//凝聚完毕 清除Data_buffer
@@ -172,8 +225,7 @@ int model)
&trust_track, //航迹文件
Trust_Track_Output, //更新航迹信息
Trust_track_num_Output, //更新航迹数
Work_Parameter, //工作参数
TAS_track_idx,
tas_track_idx,
latest_timestamp //最新时间戳
);
@@ -195,27 +247,21 @@ void Data_Process::Beam_Ctrl(struct TrackingBeam *Tracking_beam,
int *Trust_track_num_Output)
{
tas_ctrl.tas_ctrl_process(&trust_track,Tracking_beam, Trust_Track_Output, Trust_track_num_Output,Work_Parameter, latest_timestamp);
tas_ctrl.tas_ctrl_process(&trust_track,Tracking_beam, Trust_Track_Output, Trust_track_num_Output, latest_timestamp);
}
//数据处理参数初始化
int Data_Process::track_process_parameters_initial(struct RadarPara Radar_Parameter)
//数据处理参数加载(从 TOML 配置文件加载全部工作参数到全局 rdp_parameter)
int Data_Process::parameters_reload(const char* parameter_file_path)
{
//工作参数设置
if (Radar_Parameter.track_start_point_num < 3 || Radar_Parameter.track_start_point_num > 9)
std::string error;
// rdp_parameter_load 仅在完整校验通过后写入目标对象,失败时保持原参数不变
if (rdp_parameter_load(parameter_file_path, rdp_parameter, &error) != 0)
{
RDP_LOG_ERROR("parameters_reload failed: {}", error);
return -1;
memcpy(&Work_Parameter, &Radar_Parameter, sizeof(RadarPara));
}
return 0;
}
//数据处理参数修改
int Data_Process::track_process_parameters_modify(struct RadarPara Radar_Parameter)
{
if (Radar_Parameter.track_start_point_num < 3 || Radar_Parameter.track_start_point_num > 9)
return -1;
memcpy(&Work_Parameter, &Radar_Parameter, sizeof(RadarPara));
RDP_LOG_INFO("parameters_reload ok: {}", parameter_file_path != nullptr ? parameter_file_path : "");
return 0;
}
@@ -247,13 +293,13 @@ int Data_Process:: track_delete(int delete_track_num, //手动
{
for (int i=0;i<delete_track_num ;i++)
{
for (int j=0;j<trust_track.size();j++)
for (size_t j=0;j<trust_track.size();j++)
{
if(trust_track[j].Track_Index == delete_track_index[i])
{
trust_track[j].manual_delete_flag=1;
std::cout << "Delete :" << trust_track[j].Track_Index << std::endl;
// RDP_LOG_DEBUG("Delete : {}", trust_track[j].Track_Index);
}
}
@@ -262,15 +308,49 @@ int Data_Process:: track_delete(int delete_track_num, //手动
return 0;
}
//手动添加重点目标
int Data_Process::primary_target_add(int track_index)
{
for ( size_t i=0; i<trust_track.size();i++){
if(trust_track[i].Track_Index==track_index)
{
trust_track[i].primary_target_flag = 1;
//std::cout<< "Add primary target :" << trust_track[i].Track_Index << std::endl;
break;
}
}
return 0;
}
//手动取消重点目标
int Data_Process::primary_target_cancel(int track_index)
{
for ( size_t i=0; i<trust_track.size();i++){
if(trust_track[i].Track_Index==track_index)
{
trust_track[i].primary_target_flag = 0;
//std::cout<< "Cancel primary target :" << trust_track[i].Track_Index << std::endl;
break;
}
}
return 0;
}
//手动转TAS跟踪函数
int Data_Process:: tracking_start(int track_index) //需要手动转入TAS跟踪的航迹号
{
for ( int i=0; i<trust_track.size();i++)
for ( size_t i=0; i<trust_track.size();i++){
if(trust_track[i].Track_Index==track_index)
{
trust_track[i].manual_tracking_flag = 1;
break;
}
}
return 0;
}
@@ -279,11 +359,12 @@ int Data_Process:: tracking_start(int track_index) //需要手动
int Data_Process:: tracking_stop(int track_index) //需要手动转入TAS跟踪的航迹号
{
for ( int i=0; i<trust_track.size();i++){
for ( size_t i=0; i<trust_track.size();i++){
if(trust_track[i].Track_Index==track_index)
{
trust_track[i].manual_tracking_flag = 0;
trust_track[i].Track_Mode = 0;
break;
}
}
+18 -8
View File
@@ -1,4 +1,4 @@
#ifndef DATA_PROCESS_H
#ifndef DATA_PROCESS_H
#define DATA_PROCESS_H
#pragma once
#include "data_process_class_dll.h"
@@ -59,14 +59,19 @@ public:
//手动取消TAS跟踪函数
int tracking_stop(int track_index); //需要取消TAS跟踪的航迹号
//手动添加重点目标
int primary_target_add(int track_index); //将指定目标转为重点目标
//手动取消重点目标
int primary_target_cancel(int track_index); //将指定重点目标取消
//手动打跟踪波束
int tracking_point(float Azimuth); //方位角
//参数设置初始化
int track_process_parameters_initial(struct RadarPara Radar_Parameter);
//参数设置修改函数
int track_process_parameters_modify(struct RadarPara Radar_Parameter);
//参数加载函数(从 TOML 配置文件加载全部工作参数到 rdp_parameter)
//参数:parameter_file_path - 配置文件路径
//返回:成功返回 0;失败返回 -1(失败时不改变当前参数)
int parameters_reload(const char* parameter_file_path) override;
private:
@@ -78,13 +83,14 @@ private:
std::vector <Trust_Track> trust_track; //可靠航迹 按航迹区存入
std::vector <std::vector<Temp_track>> temp_track; //临时航迹 按临时航迹区存入
// 工作参数为全局对象 struct.h::rdp_parameter(由 parameters_reload() 加载),不再作为类成员。
int Beam_num; //波位数
int beam_count; //波位计数
int data_num; //波位1点数
int beam_index[3]; //3个波位号(当前帧)
int TAS_track_idx; //TAS数据的目标批号
int TAS_track_fingerprint; //TAS数据的目标批号
int Track_Mode; //跟踪模式 1TAS 0TWS
@@ -92,7 +98,11 @@ private:
long long latest_timestamp = 0; //最新时间戳
struct RadarPara Work_Parameter; //工作参数 参数设置函数外部输入
long long point_id = 0; //点迹id
long long frame_id = 0; //帧id
long long circle_id = 0; //圈id
Dot_Coh dot_coh; //点迹凝聚
@@ -1,8 +1,11 @@
#ifndef DATA_PROCESS_CLASS_DLL_LIBRARY
#ifndef DATA_PROCESS_CLASS_DLL_LIBRARY
#define DATA_PROCESS_CLASS_DLL_LIBRARY
#endif
#include "data_process_class_dll.h"
#include "data_process.h"
#include "rdp_log.h"
#include "rdp_record.h"
#include "parameters.h"
/*
Data_process_class_dll::Data_process_class_dll()
@@ -16,7 +19,15 @@ Data_process_class_dll *Data_Process_Factory::GetB()
{
if(!p)
{
p=new Data_Process();
//初始化日志输出:标准输出 + 日志文件(.\rdp_logs\rdp_YYYYMMDD_HHMMSS.log)
rdp_log::Init();
//初始化数据记录:RDP_DATA_RECORD 开启时创建记录目录、打开本次运行的记录库;
//关闭时 rdp_record::Init() 为空实现,不产生任何开销
rdp_record::Init();
p=new Data_Process();
p->version_info = VERSION_STRING;
}
return p;
@@ -30,5 +41,11 @@ void Data_Process_Factory::Destroy()
if (p) {
delete p;
p = nullptr;
//关闭记录库(RDP_DATA_RECORD 关闭时为空实现)
rdp_record::Shutdown();
//flush 并关闭日志文件(须在记录模块关闭之后:其 Shutdown 仍会写日志)
rdp_log::Shutdown();
}
}
+26 -75
View File
@@ -1,8 +1,9 @@
#pragma once
#pragma once
#ifndef DATA_PROCESS_CLASS_DLL_H
#define DATA_PROCESS_CLASS_DLL_H
#include "data_process_class_dll_global.h"
#include <string>
/***************************************************** 结构体 ********************************************************/
// 输入点迹的数据结构 用户可见
@@ -28,8 +29,10 @@ struct DATA_PROCESS_CLASS_DLLSHARED_EXPORT DataRev
int Point_Num; //点迹号(1,2,3,4...)
int UseFlag; //点迹使用标记 使用1/未使用0 主程序存入数据时置为0
int TAS_track_index; //TAS的航迹号
int point_type; //点迹类型 TAS 1/TWS 0
int point_type; //点迹类型 TAS 1/TWS 0
int pitch_num; // 俯仰波位号
float amp_asv; //目标归一化幅度(dB)
float prn_fftn; //速度维FFT点数
float speed_dim[129]; // 目标的十字星数据,目标点速度维129个点
float range_dim[8]; // 目标的十字星数据,目标点距离维8个点
};
@@ -48,6 +51,7 @@ struct DATA_PROCESS_CLASS_DLLSHARED_EXPORT Track
float Elevation; //俯仰
float Range_V; //速度
float Amplitude; //幅度
float Quality; //航迹质量
int Track_Index; //航迹号
int Point_Sum; //本条航迹更新点的数目
int Flag_Direction; //方向标志1靠近/0远离
@@ -69,6 +73,8 @@ struct DATA_PROCESS_CLASS_DLLSHARED_EXPORT Track
float elev_point;
float vr_point;
float prf_point;
float amp_asv_point; //关联上的点迹信息(归一化幅度)
float prn_fftn_point; //点迹速度维FFT点数
float track_time; //航迹时间 (单位 s)
float track_snr; //信噪比
@@ -103,75 +109,12 @@ struct DATA_PROCESS_CLASS_DLLSHARED_EXPORT Target_direct_tracking
int open_flag; // 1 引导跟踪 0 结束引导跟踪
};
// 雷达参数结构体
struct DATA_PROCESS_CLASS_DLLSHARED_EXPORT RadarPara
{
int Beam_num; //波位数
float Height; //雷达平台高度 (m)
float Sys_delay; //系统延迟 (s)
float R_TAS_max; //TAS最大距离 (m)
float R_TAS_min; //TAS最小距离 (m)
float V_TAS_max; //TAS最大速度 (m/s)
float V_TAS_min; //TAS最小速度 (m/s)
float TAS_Height_1; //TAS跟踪高度1 (m)
float TAS_Height_2; //TAS跟踪高度2 (m)
float TAS_Height_3; //TAS跟踪高度3 (m)
float TAS_Height_4; //TAS跟踪高度4 (m)
float TAS_Height_5; //TAS跟踪高度5 (m)
int track_prohibite_area_num; //禁止航迹起始的区域数目
float R_max_track_prohibited[30]; //最大距离 (m)
float R_min_track_prohibited[30]; //最小距离 (m)
float Azimuth_max_track_prohibited[30]; //最大方位角 (度)
float Azimuth_min_track_prohibited[30]; //最小方位角 (度)
int TAS_prohibite_area_num; //禁止TAS的区域数目
float R_max_TAS_prohibited[30]; //最大距离 (m)
float R_min_TAS_prohibited[30]; //最小距离 (m)
float Azimuth_max_TAS_prohibited[30]; //最大方位角 (度)
float Azimuth_min_TAS_prohibited[30]; //最小方位角 (度)
int cfar_th; //cfar门限
int work_mode; //工作模式 0进程 1中程 2远程
int north_angle; //北偏角
float V_MAX; //最大速度
float V_MIN; //最小速度
float DATA_RATE_SHORT; //三种不同模式下的 数据率
float DATA_RATE_MIDDLE;
float DATA_RATE_FAR;
float DATA_RATE_TAS; //TAS模式数据率,由dll调用者输入;TAS波束输出间隔阈值为 1/DATA_RATE_TAS 秒
//数据关联参数
int track_start_point_num; //起批点数(典型值 3或4)
float track_start_threshold; //起航波门大小(典型值 3)
float track_asso_threshold; //关联波门大小(典型值 3)
float track_asso_threshold_tas; //关联波门大小tas (典型值 3)
//singer模型机动参数
float Model1_Q_fast; //0.00001
float Model1_Q_slow; //0.00001
float Model2_Q_fast; //0.1
float Model2_Q_slow; //0.001
float Model3_Q_fast; //0.8
float Model3_Q_slow; //0.4
};
class DATA_PROCESS_CLASS_DLLSHARED_EXPORT Data_process_class_dll
{
public:
// Data_process_class_dll();
const char* version_info;
//数据预处理函数
virtual int data_preprocess(struct DataRev Data_Input[150]) //雷达的输入点迹 主程序创建全局变量 输入数据
@@ -236,22 +179,30 @@ public:
return 0;
}
//手动添加重点目标
virtual int primary_target_add(int track_index) //将指定目标转为重点目标
{
return 0;
}
//手动取消重点目标
virtual int primary_target_cancel(int track_index) //将指定重点目标取消
{
return 0;
}
//手动打跟踪波束
virtual int tracking_point(float Azimuth) //方位角(当前接口未启用,保留空实现)
{
return 0;
}
//参数设置初始化函数
virtual int track_process_parameters_initial(struct RadarPara Radar_Parameter)
//数据处理参数加载函数(从 TOML 配置文件加载全部工作参数)
//参数:parameter_file_path - 配置文件路径(宿主进程当前工作目录可达的路径)
//返回:成功返回 0;失败返回 -1(失败时不改变当前参数)
virtual int parameters_reload(const char*)
{
return 0;
}
//参数设置修改函数
virtual int track_process_parameters_modify(struct RadarPara Radar_Parameter)
{
return 0;
return -1;
}
};
@@ -1,4 +1,4 @@
#ifndef DATA_PROCESS_CLASS_DLL_GLOBAL_H
#ifndef DATA_PROCESS_CLASS_DLL_GLOBAL_H
#define DATA_PROCESS_CLASS_DLL_GLOBAL_H
#if defined(DATA_PROCESS_CLASS_DLL_LIBRARY)
+79 -76
View File
@@ -1,12 +1,13 @@
#include "dot_coh.h"
#include "dot_coh.h"
#include <cmath>
#include <cstring>
#include <vector>
#include "rdp_record.h"
using namespace std;
int Dot_Coh::dot_coh_process(std::vector <PointRecv> *data_input,
std::vector <PointRecv> *point_recv,
struct RadarPara Work_Parameter)
std::vector <PointRecv> *point_recv)
{
if(data_input->size()>1)
@@ -14,56 +15,56 @@ int Dot_Coh::dot_coh_process(std::vector <PointRecv> *data_input,
for (unsigned int loop_of_point=0; loop_of_point<data_input->size()-1;loop_of_point++ )
{
if( (*data_input)[loop_of_point].Use_Flag!=1)
if( (*data_input)[loop_of_point].use_flag!=1)
{
float point_0_R=(*data_input)[loop_of_point].Range;
float point_0_V=(*data_input)[loop_of_point].Velocity;
float point_0_F=(*data_input)[loop_of_point].Azimuth;
float point_0_A=(*data_input)[loop_of_point].Amplitude;
float point_0_R=static_cast<float>((*data_input)[loop_of_point].range);
float point_0_V=static_cast<float>((*data_input)[loop_of_point].velocity);
float point_0_F=static_cast<float>((*data_input)[loop_of_point].azimuth);
float point_0_A=static_cast<float>((*data_input)[loop_of_point].amplitude);
for (unsigned int i=loop_of_point+1;i<data_input->size();i++)
{
if((*data_input)[loop_of_point].Use_Flag!=1 && (*data_input)[i].Use_Flag!=1)
if((*data_input)[loop_of_point].use_flag!=1 && (*data_input)[i].use_flag!=1)
{
float point_1_R=(*data_input)[i].Range;
float point_1_V=(*data_input)[i].Velocity;
float point_1_F=(*data_input)[i].Azimuth;
float point_1_A=(*data_input)[i].Amplitude;
float point_1_R=static_cast<float>((*data_input)[i].range);
float point_1_V=static_cast<float>((*data_input)[i].velocity);
float point_1_F=static_cast<float>((*data_input)[i].azimuth);
float point_1_A=static_cast<float>((*data_input)[i].amplitude);
//凝聚条件: 距离、方位接近
if(Work_Parameter.work_mode == 0)
if(rdp_parameter.radar_work_mode == 0)
{
float delta_F = fabs(point_0_F-point_1_F)<2*PI-fabs(point_0_F-point_1_F) ? fabs(point_0_F-point_1_F) :2*PI-fabs(point_0_F-point_1_F);
if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI && fabs(point_0_V-point_1_V)<=DOT_COH_V)//
if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI && fabs(point_0_V-point_1_V)<=rdp_parameter.point_cohesion_velocity)//
&& point_0_A <point_1_A )//
{
point_0_R=(*data_input)[i].Range;
point_0_V=(*data_input)[i].Velocity;
point_0_A=(*data_input)[i].Amplitude;
point_0_F=(*data_input)[i].Azimuth;
(*data_input)[loop_of_point].Use_Flag=1;
point_0_R=static_cast<float>((*data_input)[i].range);
point_0_V=static_cast<float>((*data_input)[i].velocity);
point_0_A=static_cast<float>((*data_input)[i].amplitude);
point_0_F=static_cast<float>((*data_input)[i].azimuth);
(*data_input)[loop_of_point].use_flag=1;
}
else if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI && fabs(point_0_V-point_1_V)<=DOT_COH_V)//
else if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI && fabs(point_0_V-point_1_V)<=rdp_parameter.point_cohesion_velocity)//
&& point_0_A >= point_1_A)//
{
(*data_input)[i].Use_Flag=1;
(*data_input)[i].use_flag=1;
}
}
else
{
float delta_F = fabs(point_0_F-point_1_F)<2*PI-fabs(point_0_F-point_1_F) ? fabs(point_0_F-point_1_F) :2*PI-fabs(point_0_F-point_1_F);
if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI )//
if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI )//
&& point_0_A <point_1_A )//
{
point_0_R=(*data_input)[i].Range;
point_0_V=(*data_input)[i].Velocity;
point_0_A=(*data_input)[i].Amplitude;
point_0_F=(*data_input)[i].Azimuth;
(*data_input)[loop_of_point].Use_Flag=1;
point_0_R=static_cast<float>((*data_input)[i].range);
point_0_V=static_cast<float>((*data_input)[i].velocity);
point_0_A=static_cast<float>((*data_input)[i].amplitude);
point_0_F=static_cast<float>((*data_input)[i].azimuth);
(*data_input)[loop_of_point].use_flag=1;
}
else if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI )//
else if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI )//
&& point_0_A >= point_1_A)//
{
(*data_input)[i].Use_Flag=1;
(*data_input)[i].use_flag=1;
}
}
@@ -77,7 +78,7 @@ int Dot_Coh::dot_coh_process(std::vector <PointRecv> *data_input,
std::vector <PointRecv>::iterator Iter;
for (Iter=data_input->begin(); Iter!=data_input->end();)
{
if((*Iter).Use_Flag==1 || (*Iter).Range <R_MIN || (*Iter).Range > R_MAX )
if((*Iter).use_flag==1 || (*Iter).range <rdp_parameter.point_cohesion_range_min || (*Iter).range > rdp_parameter.point_cohesion_range_max )
{
data_input->erase(Iter);
Iter=data_input->begin();
@@ -88,7 +89,7 @@ int Dot_Coh::dot_coh_process(std::vector <PointRecv> *data_input,
}
}
for (int i=0;i<data_input->size();i++) //data_buffer_2 ---> point_recv
for (size_t i=0;i<data_input->size();i++) //data_buffer_2 ---> point_recv
{
(*point_recv).push_back((*data_input)[i]);
}
@@ -97,8 +98,7 @@ int Dot_Coh::dot_coh_process(std::vector <PointRecv> *data_input,
}
int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input, //输入的点迹
std::vector <PointRecv> *point_recv, //输出点迹
struct RadarPara Work_Parameter //工作参数
std::vector <PointRecv> *point_recv //输出点迹
)
{
@@ -106,26 +106,18 @@ int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input,
//1.1 data_input、data_input_buff中的数据放在一起
std::vector <PointRecv> data_tmp;
for (int i=0;i<data_input_buff.size();i++)
for (size_t i=0;i<data_input_buff.size();i++)
{
data_tmp.push_back(data_input_buff[i]);
data_tmp[data_tmp.size()-1].point_section_asso = 1;
}
for (int i=0;i<data_input->size();i++)
for (size_t i=0;i<data_input->size();i++)
{
data_tmp.push_back((*data_input)[i]);
data_tmp[data_tmp.size()-1].point_section_asso = 2;
}
// //去除点迹中,1km以内,SNR小于20dB的点
// for (int i=0;i<data_input->size();i++)
// {
// if((*data_input)[i].Range > 1000 || (*data_input)[i].snr >= 20){
// data_tmp.push_back((*data_input)[i]);
// data_tmp[data_tmp.size()-1].point_section_asso = 2;
// }
// }
//1.2 把data_input、data_input_buff清空
@@ -138,56 +130,56 @@ int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input,
for (unsigned int loop_of_point=0; loop_of_point<data_tmp.size()-1;loop_of_point++ )
{
if( data_tmp[loop_of_point].Use_Flag!=1)
if( data_tmp[loop_of_point].use_flag!=1)
{
float point_0_R=data_tmp[loop_of_point].Range;
float point_0_V=data_tmp[loop_of_point].Velocity;
float point_0_F=data_tmp[loop_of_point].Azimuth;
float point_0_A=data_tmp[loop_of_point].Amplitude;
float point_0_R=static_cast<float>(data_tmp[loop_of_point].range);
float point_0_V=static_cast<float>(data_tmp[loop_of_point].velocity);
float point_0_F=static_cast<float>(data_tmp[loop_of_point].azimuth);
float point_0_A=static_cast<float>(data_tmp[loop_of_point].amplitude);
for (unsigned int i=loop_of_point+1;i<data_tmp.size();i++)
{
if(data_tmp[loop_of_point].Use_Flag!=1 && data_tmp[i].Use_Flag!=1)
if(data_tmp[loop_of_point].use_flag!=1 && data_tmp[i].use_flag!=1)
{
float point_1_R=data_tmp[i].Range;
float point_1_V=data_tmp[i].Velocity;
float point_1_F=data_tmp[i].Azimuth;
float point_1_A=data_tmp[i].Amplitude;
float point_1_R=static_cast<float>(data_tmp[i].range);
float point_1_V=static_cast<float>(data_tmp[i].velocity);
float point_1_F=static_cast<float>(data_tmp[i].azimuth);
float point_1_A=static_cast<float>(data_tmp[i].amplitude);
//凝聚条件: 距离、方位接近
if(Work_Parameter.work_mode == 0)
if(rdp_parameter.radar_work_mode == 0)
{
float delta_F = fabs(point_0_F-point_1_F)<2*PI-fabs(point_0_F-point_1_F) ? fabs(point_0_F-point_1_F) :2*PI-fabs(point_0_F-point_1_F);
if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI && fabs(point_0_V-point_1_V)<=DOT_COH_V)//
if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI && fabs(point_0_V-point_1_V)<=rdp_parameter.point_cohesion_velocity)//
&& point_0_A <point_1_A )//
{
point_0_R=data_tmp[i].Range;
point_0_V=data_tmp[i].Velocity;
point_0_A=data_tmp[i].Amplitude;
point_0_F=data_tmp[i].Azimuth;
data_tmp[loop_of_point].Use_Flag=1;
point_0_R=static_cast<float>(data_tmp[i].range);
point_0_V=static_cast<float>(data_tmp[i].velocity);
point_0_A=static_cast<float>(data_tmp[i].amplitude);
point_0_F=static_cast<float>(data_tmp[i].azimuth);
data_tmp[loop_of_point].use_flag=1;
}
else if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI && fabs(point_0_V-point_1_V)<=DOT_COH_V)//
else if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI && fabs(point_0_V-point_1_V)<=rdp_parameter.point_cohesion_velocity)//
&& point_0_A >= point_1_A)//
{
data_tmp[i].Use_Flag=1;
data_tmp[i].use_flag=1;
}
}
else
{
float delta_F = fabs(point_0_F-point_1_F)<2*PI-fabs(point_0_F-point_1_F) ? fabs(point_0_F-point_1_F) :2*PI-fabs(point_0_F-point_1_F);
if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI )//
if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI )//
&& point_0_A <point_1_A )//
{
point_0_R=data_tmp[i].Range;
point_0_V=data_tmp[i].Velocity;
point_0_A=data_tmp[i].Amplitude;
point_0_F=data_tmp[i].Azimuth;
data_tmp[loop_of_point].Use_Flag=1;
point_0_R=static_cast<float>(data_tmp[i].range);
point_0_V=static_cast<float>(data_tmp[i].velocity);
point_0_A=static_cast<float>(data_tmp[i].amplitude);
point_0_F=static_cast<float>(data_tmp[i].azimuth);
data_tmp[loop_of_point].use_flag=1;
}
else if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI )//
else if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI )//
&& point_0_A >= point_1_A)//
{
data_tmp[i].Use_Flag=1;
data_tmp[i].use_flag=1;
}
}
@@ -201,7 +193,7 @@ int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input,
std::vector <PointRecv>::iterator Iter;
for (Iter=data_tmp.begin(); Iter!=data_tmp.end();)
{
if((*Iter).Use_Flag==1 || (*Iter).Range <R_MIN || (*Iter).Range > R_MAX )
if((*Iter).use_flag==1 || (*Iter).range <rdp_parameter.point_cohesion_range_min || (*Iter).range > rdp_parameter.point_cohesion_range_max )
{
data_tmp.erase(Iter);
Iter=data_tmp.begin();
@@ -213,7 +205,7 @@ int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input,
}
//1.5 将data_tmp中凝聚后的点再分到 data_input_buff和data_input中
for (int i=0;i<data_tmp.size();i++)
for (size_t i=0;i<data_tmp.size();i++)
{
if(data_tmp[i].point_section_asso==1)
{
@@ -229,15 +221,26 @@ int Dot_Coh::dot_coh_process_buff( std::vector <PointRecv> *data_input,
//2.data_input_buff数据输出给point_recv
for (int i=0;i<data_input_buff.size();i++)
#ifdef RDP_RECORD_ENABLE
//记录凝聚后点迹:先收集,输出循环结束后一次入队
std::vector<PointRecv> rec_coh;
rec_coh.reserve(data_input_buff.size());
#endif
for (size_t i=0;i<data_input_buff.size();i++)
{
(*point_recv).push_back(data_input_buff[i]);
(*point_recv).push_back(data_input_buff[i]);
#ifdef RDP_RECORD_ENABLE
rec_coh.push_back(data_input_buff[i]);
#endif
}
#ifdef RDP_RECORD_ENABLE
RDP_RECORD_COHESION_TWS(rec_coh);
#endif
std::vector<PointRecv>().swap(data_input_buff);
//3.data_input数据输出给data_input_buff
for (int i=0;i<data_input->size();i++)
for (size_t i=0;i<data_input->size();i++)
{
data_input_buff.push_back((*data_input)[i]);
}
+3 -5
View File
@@ -1,4 +1,4 @@
#ifndef DOT_COH_H
#ifndef DOT_COH_H
#define DOT_COH_H
#include "data_process_class_dll.h"
#include "parameters.h"
@@ -14,14 +14,12 @@ public:
// 点迹凝聚函数
int dot_coh_process( std::vector <PointRecv> *data_input, //输入的点迹
std::vector <PointRecv> *point_recv, //输出点迹
struct RadarPara Work_Parameter //工作参数
std::vector <PointRecv> *point_recv //输出点迹
);
//点迹凝聚函数,缓存一帧,一边输出,一边进行滑窗凝聚
int dot_coh_process_buff( std::vector <PointRecv> *data_input, //输入的点迹
std::vector <PointRecv> *point_recv, //输出点迹
struct RadarPara Work_Parameter //工作参数
std::vector <PointRecv> *point_recv //输出点迹
);
//构造函数
+36 -25
View File
@@ -1,49 +1,49 @@
#include "dot_coh_tas.h"
#include "dot_coh_tas.h"
#include <cmath>
#include <cstring>
#include <vector>
#include "rdp_record.h"
using namespace std;
int Dot_Coh_TAS::dot_coh_tas_process(std::vector <PointRecv> *data_input, //输入点迹
std::vector <PointRecv> *point_recv_tas //输出点迹
)
std::vector <PointRecv> *point_recv_tas) //输出点迹
{
if (data_input->size() == 0) {
return 1;
}
for (int loop_of_point=0; loop_of_point<data_input->size()-1;loop_of_point++ )
for (size_t loop_of_point=0; loop_of_point<data_input->size()-1;loop_of_point++ )
{
if( (*data_input)[loop_of_point].Use_Flag!=1)
if( (*data_input)[loop_of_point].use_flag!=1)
{
float point_0_R=(*data_input)[loop_of_point].Range;
float point_0_V=(*data_input)[loop_of_point].Velocity;
float point_0_F=(*data_input)[loop_of_point].Azimuth;
float point_0_A=(*data_input)[loop_of_point].Amplitude;
for ( int i=loop_of_point+1;i<(*data_input).size();i++)
float point_0_R=static_cast<float>((*data_input)[loop_of_point].range);
float point_0_V=static_cast<float>((*data_input)[loop_of_point].velocity);
float point_0_F=static_cast<float>((*data_input)[loop_of_point].azimuth);
float point_0_A=static_cast<float>((*data_input)[loop_of_point].amplitude);
for ( size_t i=loop_of_point+1;i<(*data_input).size();i++)
{
if((*data_input)[loop_of_point].Use_Flag!=1 && (*data_input)[i].Use_Flag!=1)
if((*data_input)[loop_of_point].use_flag!=1 && (*data_input)[i].use_flag!=1)
{
float point_1_R=(*data_input)[i].Range;
float point_1_V=(*data_input)[i].Velocity;
float point_1_F=(*data_input)[i].Azimuth;
float point_1_A=(*data_input)[i].Amplitude;
float point_1_R=static_cast<float>((*data_input)[i].range);
float point_1_V=static_cast<float>((*data_input)[i].velocity);
float point_1_F=static_cast<float>((*data_input)[i].azimuth);
float point_1_A=static_cast<float>((*data_input)[i].amplitude);
//凝聚条件: 距离、方位接近
float delta_F = fabs(point_0_F-point_1_F)<2*PI-fabs(point_0_F-point_1_F) ? fabs(point_0_F-point_1_F) :2*PI-fabs(point_0_F-point_1_F);
if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI && fabs(point_0_V-point_1_V)<=DOT_COH_V)
if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI && fabs(point_0_V-point_1_V)<=rdp_parameter.point_cohesion_velocity)
&& point_0_A <point_1_A )
{
point_0_R=(*data_input)[i].Range;
point_0_V=(*data_input)[i].Velocity;
point_0_A=(*data_input)[i].Amplitude;
point_0_F=(*data_input)[i].Azimuth;
(*data_input)[loop_of_point].Use_Flag=1;
point_0_R=static_cast<float>((*data_input)[i].range);
point_0_V=static_cast<float>((*data_input)[i].velocity);
point_0_A=static_cast<float>((*data_input)[i].amplitude);
point_0_F=static_cast<float>((*data_input)[i].azimuth);
(*data_input)[loop_of_point].use_flag=1;
}
else if( (fabs(point_0_R-point_1_R)<=DOT_COH_RANGE && delta_F <=DOT_COH_AZI/180.0*PI && fabs(point_0_V-point_1_V)<=DOT_COH_V)
else if( (fabs(point_0_R-point_1_R)<=rdp_parameter.point_cohesion_range && delta_F <=rdp_parameter.point_cohesion_azimuth/180.0*PI && fabs(point_0_V-point_1_V)<=rdp_parameter.point_cohesion_velocity)
&& point_0_A >= point_1_A)
{
(*data_input)[i].Use_Flag=1;
(*data_input)[i].use_flag=1;
}
}
}
@@ -54,7 +54,7 @@ int Dot_Coh_TAS::dot_coh_tas_process(std::vector <PointRecv> *dat
std::vector <PointRecv>::iterator Iter;
for (Iter=data_input->begin(); Iter!=data_input->end();)
{
if((*Iter).Use_Flag==1 || (*Iter).Range <R_MIN || (*Iter).Range > R_MAX )
if((*Iter).use_flag==1 || (*Iter).range <rdp_parameter.point_cohesion_range_min || (*Iter).range > rdp_parameter.point_cohesion_range_max )
{
data_input->erase(Iter);
Iter=data_input->begin();
@@ -65,10 +65,21 @@ int Dot_Coh_TAS::dot_coh_tas_process(std::vector <PointRecv> *dat
}
}
for (int i=0;i<data_input->size();i++)
#ifdef RDP_RECORD_ENABLE
//记录凝聚后点迹:先收集,输出循环结束后一次入队
std::vector<PointRecv> rec_coh;
rec_coh.reserve(data_input->size());
#endif
for (size_t i=0;i<data_input->size();i++)
{
(*point_recv_tas).push_back((*data_input)[i]);
#ifdef RDP_RECORD_ENABLE
rec_coh.push_back((*data_input)[i]);
#endif
}
#ifdef RDP_RECORD_ENABLE
RDP_RECORD_COHESION_TAS(rec_coh);
#endif
return 1;
}
+1 -1
View File
@@ -1,4 +1,4 @@
#ifndef DOT_COH_TAS_H
#ifndef DOT_COH_TAS_H
#define DOT_COH_TAS_H
#include "parameters.h"
#include "struct.h"
+54 -54
View File
@@ -1,4 +1,4 @@
#include "kalman.h"
#include "kalman.h"
#include "coor_trans.h"
#include "parameters.h"
#include <cmath>
@@ -26,13 +26,13 @@ void kalman:: kalman_filter_init_2dots(double Z0[2], double Z1[2], double T,doub
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z1[0],Z1[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
double R[2][2];
R[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R[1][0]=R[0][1];
P[0][0]=R[0][0];
@@ -92,13 +92,13 @@ double kalman::d_cal_track_init(double Z[2],double X[4],double P[4][4],double T)
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z[0],Z[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
Matrix2d R;
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R(1,0)=R(0,1);
Matrix4d P_present;
@@ -145,23 +145,23 @@ void kalman::kalman_filter_init_3dots(double Z0[2], double Z1[2],double Z2[2], d
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z0[0],Z0[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
R0[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R0[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R0[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
R0[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R0[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R0[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R0[1][0]=R0[0][1];
Coor_trans.cart2polar(Z1[0],Z1[1],&rho,&theta);
R1[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R1[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R1[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R1[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R1[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R1[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R1[1][0]=R1[0][1];
Coor_trans.cart2polar(Z2[0],Z2[1],&rho,&theta);
R2[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R2[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R2[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R2[0][0]=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R2[1][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R2[0][1]=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R2[1][0]=R2[0][1];
double P11[3][3]={{R2[0][0], R2[0][0]/T2, (R2[0][0]/T2)/((T2+T1)/2)} ,
@@ -218,13 +218,13 @@ double kalman::d_cal(double Z[2],double X[6],double P[6][6])
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z[0],Z[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
Matrix2d R;
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R(1,0)=R(0,1);
//S
@@ -284,9 +284,9 @@ double kalman::d_cal_EKF(double F[6][6], double Q[6][6] ,double Z[3],double X[6]
H(2,3) = -x*(vy*x-vx*y)/((x*x+y*y)*sqrt(x*x+y*y)); H(2,4)=-y/sqrt(x*x+y*y); H(2,5)=0;
Matrix3d R;
R(0,0)=SIGMA_R*SIGMA_R;
R(1,1)=SIGMA_A*SIGMA_A;
R(2,2)=SIGMA_V*SIGMA_V;
R(0,0)=rdp_parameter.sigma_range*rdp_parameter.sigma_range;
R(1,1)=rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth;
R(2,2)=rdp_parameter.sigma_velocity*rdp_parameter.sigma_velocity;
R(0,1)=0;
R(1,0)=0;
R(2,0)=0;
@@ -362,15 +362,15 @@ double kalman:: d_cal_with_doppler(double F[6][6], double Q[6][6] , double Z[2],
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z[0],Z[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
Matrix3d R;
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R(1,0)=R(0,1);
R(2,2)=SIGMA_V*SIGMA_V;
R(2,2)=rdp_parameter.sigma_velocity*rdp_parameter.sigma_velocity;
R(2,0)=0;
R(2,1)=0;
R(0,2)=0;
@@ -477,9 +477,9 @@ void kalman::kalman_filter_EKF(double F[6][6], double Q[6][6], double X_cur[6],d
H(2,3) = -x*(vy*x-vx*y)/((x*x+y*y)*sqrt(x*x+y*y)); H(2,4)=-y/sqrt(x*x+y*y); H(2,5)=0;
Matrix3d R;
R(0,0)=SIGMA_R*SIGMA_R;
R(1,1)=SIGMA_A*SIGMA_A;
R(2,2)=SIGMA_V*SIGMA_V;
R(0,0)=rdp_parameter.sigma_range*rdp_parameter.sigma_range;
R(1,1)=rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth;
R(2,2)=rdp_parameter.sigma_velocity*rdp_parameter.sigma_velocity;
R(0,1)=0;
R(1,0)=0;
R(2,0)=0;
@@ -570,13 +570,13 @@ void kalman::kalman_filter(double F[6][6], double Q[6][6],
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z[0],Z[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
Matrix2d R;
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R(1,0)=R(0,1);
//S
@@ -663,9 +663,9 @@ double kalman::d_cal_track_init_EKF(double Z[3],double X[4],double P[4][4],doubl
H(2,2) = -x*(vy*x-vx*y)/((x*x+y*y)*sqrt(x*x+y*y)); H(2,3)=-y/sqrt(x*x+y*y);
Matrix3d R;
R(0,0)=SIGMA_R*SIGMA_R;
R(1,1)=SIGMA_A*SIGMA_A;
R(2,2)=SIGMA_V*SIGMA_V;
R(0,0)=rdp_parameter.sigma_range*rdp_parameter.sigma_range;
R(1,1)=rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth;
R(2,2)=rdp_parameter.sigma_velocity*rdp_parameter.sigma_velocity;
R(0,1)=0;
R(1,0)=0;
R(2,0)=0;
@@ -744,15 +744,15 @@ double kalman::d_cal_track_init_with_doppler(double Z[2],double X[4],double P[4]
double rho,theta;
coor_trans Coor_trans;
Coor_trans.cart2polar(Z[0],Z[1],&rho,&theta);
double lambda_theta=exp(-SIGMA_A*SIGMA_A/2);
double lambda_theta1=exp(-2*SIGMA_A*SIGMA_A);
double lambda_theta=exp(-rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth/2);
double lambda_theta1=exp(-2*rdp_parameter.sigma_azimuth*rdp_parameter.sigma_azimuth);
Matrix3d R;
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+SIGMA_R*SIGMA_R)*lambda_theta1*sin(2*theta);
R(0,0)=(pow(lambda_theta,-2)-2)*rho*rho*cos(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1+lambda_theta1*cos(2*theta));
R(1,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*sin(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*(1-lambda_theta1*cos(2*theta));
R(0,1)=(pow(lambda_theta,-2)-2)*rho*rho*sin(theta)*cos(theta)+0.5*(rho*rho+rdp_parameter.sigma_range*rdp_parameter.sigma_range)*lambda_theta1*sin(2*theta);
R(1,0)=R(0,1);
R(2,2)=SIGMA_V*SIGMA_V;
R(2,2)=rdp_parameter.sigma_velocity*rdp_parameter.sigma_velocity;
R(2,0)=0;
R(2,1)=0;
R(0,2)=0;
@@ -779,7 +779,7 @@ double kalman::d_cal_track_init_with_doppler(double Z[2],double X[4],double P[4]
double kalman::Bind_speed(double prt,double freq_ind)
{
double freq=FREQ0+freq_ind*0.02;
double freq=rdp_parameter.freq_0_GHz+freq_ind*0.02;
if (prt <= 0.0 || freq <= 0.0)
return 1.0; // 避免除零;正常流程 PRI 不允许为 0
+1 -1
View File
@@ -1,4 +1,4 @@
#ifndef KALMAN_H
#ifndef KALMAN_H
#define KALMAN_H
#include "parameters.h"
#include "struct.h"
+80 -107
View File
@@ -1,125 +1,98 @@
#ifndef PARAMETERS_H
#ifndef PARAMETERS_H
#define PARAMETERS_H
#pragma once
/**********************************雷达参数*************************************************/
#define PI 3.1415926f
//频点(GHz)
#define FREQ0 16.8
#define FREQ1 16.8
#define FREQ2 16.8
#define FREQ3 16.8
#define FREQ4 16.8
#define FREQ5 16.8
#define FREQ6 16.8
#define FREQ7 16.8
#define FREQ8 16.8
#define FREQ9 16.8
#define FREQ10 16.8
#define FREQ11 16.8
#define FREQ12 16.8
#define FREQ13 16.8
#define FREQ14 16.8
#define FREQ15 16.8
#define FREQ16 16.8
#define FREQ17 16.8
#define FREQ18 16.8
#define FREQ19 16.8
#define FREQ20 16.8
/**********************************版本信息*************************************************/
#define MAJOR_VERSION 1
#define MINOR_VERSION 5
#define PATCH_VERSION 9
#define VERSION_INFO "GENERIC"
//#define MECHANICAL_SCANNING
//#define PHASE_SCANNING
#define STRINGIFY_IMPL(x) #x
#define STRINGIFY(x) STRINGIFY_IMPL(x)
#ifdef MECHANICAL_SCANNING
/******************************数据处理参数**************************************/
#define DATA_RATE_TAS 0.0625
#define VERSION_STRING \
"V" STRINGIFY(MAJOR_VERSION) "." \
STRINGIFY(MINOR_VERSION) "." \
STRINGIFY(PATCH_VERSION) "." \
VERSION_INFO
#define SIGMA_R 10.0 //测量误差
#define SIGMA_A 0.02
#define SIGMA_E 0.2
#define SIGMA_V 2.0
/**********************************日志输出*************************************************/
// 日志基于内置 spdlog v1.17.0(third_party/spdlog,来源见其 UPSTREAM.md):
// 1) 每次调用 Data_Process_Factory::GetB() 创建实例时,rdp_log::Init() 同时输出到
// 标准输出与日志文件(RDP_LOG_DIR 目录),日志文件名按当前日期+时间自动命名
// (如 rdp_20240826_153000.log,每次初始化一个文件);Destroy() 时关闭日志文件;
// 2) 原 RDP_LOG_ENABLE 布尔开关已移除:日志级别改由 spdlog 的 SPDLOG_ACTIVE_LEVEL
// 在编译期控制,由 CMake 选项 RDP_LOG_LEVEL(AUTO/TRACE/DEBUG/INFO/WARN/ERROR/
// CRITICAL/OFF)映射;AUTO 时 Debug 构建 = TRACE、其余构建 = INFO,OFF 表示
// 编译期关闭全部 RDP_LOG_* 日志。低于该级别的日志实参不求值。详见 README 10.4。
#define DOT_COH_RANGE 80 //点迹凝聚
#define DOT_COH_V 2
#define DOT_COH_AZI 6
//日志文件输出目录(相对当前工作目录的路径,目录不存在时自动创建)
#define RDP_LOG_DIR ".\\rdp_logs\\"
#define MAX_BEAM_NUM 100 //最大TWS波位数
#define MAX_TRACK_NUM 500
#define MAX_TRACK_INDEX 500 //最大航迹批号
#define ASSOCIATE_THRESHOLD_MIN 1
#define ASSOCIATE_THRESHOLD_MID 3 //关联波门
#define ASSOCIATE_THRESHOLD_MAX 10
#define R_MIN 100
#define R_MAX 100000
#define TRACK_START_THRESHOLD 8 //起航波门 3 越大越容易起批
#define ALPHA_START 60 //起航夹角 120 越大越容易起批
#define TRACK_DIE_ROUND 4 // 航迹消亡时间
#define TRACK_DIE_ROUND_TAS 96 //
#define ASSO_THORD 3
#define H_F_WIN_LEN 3
#define TAS_QUEUE_LENGTH 1 //Tas调用的cpi数
#define MAX_TAS_NUM 1
#define UNCONF_TARGET 0
#define DIRECT_TRACKING_QUEUE_LENGTH 10
/**********************************数据记录调试开关*******************************************/
// 数据记录调试功能(结构体 -> SQLite 数据表,ORM 由内置 ormpp 完成)由 CMake 选项
// RDP_DATA_RECORD(AUTO/ON/OFF)控制:AUTO 时 Debug 构建启用、Release 构建关闭(交付默认)。
// 启用时由构建系统定义 RDP_RECORD_ENABLE,记录模块(rdp_record.cpp + sqlite3)才参与编译;
// 关闭时 RDP_RECORD 宏展开为 ((void)0),实参不求值,不做任何记录、不留任何运行开销。
// 注意:此处不提供手工开关宏,请在构建配置中选择(详见 docs/data_record_debug.md)。
//记录数据库存放目录(相对宿主进程当前工作目录,不存在时自动创建)
#define RDP_RECORD_DB_DIR ".\\rdp_records\\"
//数据记录性能参数(V1.5.8 优化:批量事务 + 异步写线程 + WAL)
//说明:以下宏只被 rdp_record.cpp 使用;用 #ifndef 保护,便于用编译选项临时覆盖做测试。
//队列字节上限:处理线程入队总量超过该值时整批丢弃(不阻塞处理线程)
#ifndef RDP_RECORD_QUEUE_CAP_BYTES
#define RDP_RECORD_QUEUE_CAP_BYTES (64u * 1024u * 1024u)
#endif
//队列批数上限(防止小批过多导致 deque 开销/延迟)
#ifndef RDP_RECORD_QUEUE_MAX_BATCHES
#define RDP_RECORD_QUEUE_MAX_BATCHES 4096u
#endif
//写线程每次事务提交的目标行数(累积到该行数或等待超时即提交)
#ifndef RDP_RECORD_WRITE_BATCH_ROWS
#define RDP_RECORD_WRITE_BATCH_ROWS 4096u
#endif
//写线程为凑批而等待的最长时间(毫秒)
#ifndef RDP_RECORD_WRITE_MAX_WAIT_MS
#define RDP_RECORD_WRITE_MAX_WAIT_MS 50u
#endif
//关联信息单次入队分块上限(⑥ 单圈行数可能很大,分块限制处理线程内存峰值;
//取值应大于常见单圈候选数,避免同一圈内多次重新分配;131072 行 Asso_Info 约 11 MB)
#ifndef RDP_RECORD_ASSO_CHUNK_ROWS
#define RDP_RECORD_ASSO_CHUNK_ROWS 131072u
#endif
//SQLite 页缓存大小(KB,负数表示 KB;默认 8 MB)
#ifndef RDP_RECORD_SQLITE_CACHE_KB
#define RDP_RECORD_SQLITE_CACHE_KB 8192
#endif
//SQLite 忙等待超时(毫秒)
#ifndef RDP_RECORD_SQLITE_BUSY_TIMEOUT_MS
#define RDP_RECORD_SQLITE_BUSY_TIMEOUT_MS 2000
#endif
//是否尝试开启 WAL(1=尝试;失败/不支持时自动回退 DELETE 日志模式)
#ifndef RDP_RECORD_SQLITE_WAL
#define RDP_RECORD_SQLITE_WAL 1
#endif
//连续写入错误达到该次数后全局熔断(停止记录,避免持续消耗写线程)
#ifndef RDP_RECORD_MAX_CONSECUTIVE_ERRORS
#define RDP_RECORD_MAX_CONSECUTIVE_ERRORS 10
#endif
//队列丢弃日志的限频间隔(每丢弃多少行提示一次)
#ifndef RDP_RECORD_DROP_LOG_INTERVAL
#define RDP_RECORD_DROP_LOG_INTERVAL 1024u
#endif
#ifdef PHASE_SCANNING
/******************************数据处理参数**************************************/
#define DATA_RATE_TAS 0.3
#define SIGMA_R 10.0 //测量误差
#define SIGMA_A 0.02
#define SIGMA_E 0.2
#define SIGMA_V 2.0
#define DOT_COH_RANGE 80 //点迹凝聚
#define DOT_COH_V 2
#define DOT_COH_AZI 6
#define MAX_BEAM_NUM 100 //最大TWS波位数
/******************************编译期容量常量***********************************************/
// 说明:下列常量是「接口/内存布局」需要的编译期容量,不是雷达工作参数,
// 雷达工作参数统一由 TOML 配置文件经 parameters_reload() 载入 struct.h::RDP_parameter。
//输出航迹数组的容量(宿主接口固定维度,见 data_process_class_dll.h)
#define MAX_TRACK_NUM 500
#define MAX_TRACK_INDEX 500 //最大航迹批号
#define ASSOCIATE_THRESHOLD_MIN 1
#define ASSOCIATE_THRESHOLD_MID 3 //关联波门
#define ASSOCIATE_THRESHOLD_MAX 10
#define R_MIN 100
#define R_MAX 100000
//#define V_MAX 500 //最大速度
//#define V_MIN 1 //速度最小
#define TRACK_START_THRESHOLD 8 //起航波门 3 越大越容易起批
#define ALPHA_START 60 //起航夹角 120 越大越容易起批
#define TRACK_DIE_ROUND 4 // 航迹消亡时间
#define TRACK_DIE_ROUND_TAS 5
#define ASSO_THORD 3
#define H_F_WIN_LEN 3
#define TAS_QUEUE_LENGTH 4 //Tas调用的cpi数
#define MAX_TAS_NUM 4
#define UNCONF_TARGET 0
#define DIRECT_TRACKING_QUEUE_LENGTH 10
#endif
//TAS 跟踪队列容量上限(运行期配置项 tas_track_num_max 不得超过该值)
#define RDP_TAS_QUEUE_CAPACITY 30
#define Round(x) (((x) > 0) \
? (((int)((x) + 0.5) > (int)(x)) ? ((int)(x) + 1) : ((int)(x))) \
+196
View File
@@ -0,0 +1,196 @@
#include "rdp_log.h"
#include "parameters.h"
#include <cstdio>
#include <memory>
#include <string>
#include <vector>
#include <time.h>
#include <direct.h>
#include <io.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_sinks.h>
namespace
{
//日志行格式:与旧版 [时间戳][文件:行号] 风格一致,另加 spdlog 级别
const char *const kPattern = "[%Y-%m-%d %H:%M:%S.%e] [%l] [%s:%#] %v";
//日志模块内部状态:默认 logger(标准输出 + 日志文件)与当前日志文件路径
struct RdpLogState
{
std::shared_ptr<spdlog::logger> logger; //默认 logger(含标准输出 sink,成功时另含文件 sink)
std::string log_path; //当前日志文件全路径(降级为仅标准输出时为空)
bool initialized; //是否已初始化(含文件创建失败时的降级初始化)
RdpLogState() : initialized(false) {}
};
RdpLogState &State()
{
static RdpLogState s;
return s;
}
//创建日志目录(目录已存在时 _mkdir 返回 EEXIST,忽略;逻辑与原 rdp_log/rdp_record 一致)
void EnsureDir(const std::string &dir_with_separator)
{
std::string dir = dir_with_separator;
while (dir.size() > 0 && (dir[dir.size() - 1] == '\\' || dir[dir.size() - 1] == '/'))
{
dir.erase(dir.size() - 1);
}
if (dir.size() > 0)
{
_mkdir(dir.c_str());
}
}
//当前本地时间字符串,格式 "YYYYMMDD_HHMMSS"
bool TimeStamp(char *buf, size_t size)
{
time_t now = time(NULL);
struct tm local;
if (localtime_s(&local, &now) != 0)
{
return false;
}
return strftime(buf, size, "%Y%m%d_%H%M%S", &local) != 0;
}
//生成日志文件路径:RDP_LOG_DIR + rdp_YYYYMMDD_HHMMSS.log;
//同一秒内文件已存在(Destroy→GetB 重连、同目录多进程启动)时追加 _1~_999 后缀,不复用同一文件
bool MakeLogPath(std::string *path)
{
char stamp[32] = {0};
if (!TimeStamp(stamp, sizeof(stamp)))
{
return false;
}
const std::string base = std::string(RDP_LOG_DIR) + "rdp_" + stamp;
for (int suffix = 0; suffix < 1000; ++suffix)
{
std::string candidate = base;
if (suffix > 0)
{
candidate += "_" + std::to_string(suffix);
}
candidate += ".log";
if (_access(candidate.c_str(), 0) != 0)
{
*path = candidate;
return true;
}
}
return false;
}
//初始化 logger:统一日志格式,运行期级别放开到 trace(实际输出级别由编译期
//SPDLOG_ACTIVE_LEVEL 过滤),所有级别逐条 flush(日志量小,且调试场景要求异常退出也尽量不丢日志)
void ConfigureLogger(const std::shared_ptr<spdlog::logger> &logger)
{
logger->set_pattern(kPattern);
logger->set_level(spdlog::level::trace);
logger->flush_on(spdlog::level::trace);
spdlog::set_default_logger(logger);
}
//仅标准输出 sink 的降级 logger(日志文件创建失败时使用)
std::shared_ptr<spdlog::logger> MakeStdoutLogger()
{
std::vector<spdlog::sink_ptr> sinks;
sinks.push_back(std::make_shared<spdlog::sinks::stdout_sink_mt>());
std::shared_ptr<spdlog::logger> logger =
std::make_shared<spdlog::logger>("rdp", sinks.begin(), sinks.end());
ConfigureLogger(logger);
return logger;
}
}
namespace rdp_log
{
bool Init()
{
RdpLogState &st = State();
if (st.initialized)
{
return true; //已经初始化,避免重复创建日志文件
}
#if SPDLOG_ACTIVE_LEVEL > SPDLOG_LEVEL_CRITICAL
//编译期关闭日志(RDP_LOG_LEVEL=OFF):不初始化 logger、不创建日志文件
return false;
#else
try
{
EnsureDir(RDP_LOG_DIR);
std::string log_path;
if (!MakeLogPath(&log_path))
{
throw spdlog::spdlog_ex("no usable log file name in RDP_LOG_DIR");
}
std::vector<spdlog::sink_ptr> sinks;
sinks.push_back(std::make_shared<spdlog::sinks::stdout_sink_mt>());
//truncate=false:日志文件名已保证不存在(极端并发下万一被抢占则追加而非覆盖已有内容)
sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_path, false));
std::shared_ptr<spdlog::logger> logger =
std::make_shared<spdlog::logger>("rdp", sinks.begin(), sinks.end());
ConfigureLogger(logger);
//spdlog 内部错误(如日志文件写失败)打到标准错误,不影响雷达数据处理
spdlog::set_error_handler([](const std::string &msg) {
std::fprintf(stderr, "[rdp_log] spdlog error: %s\n", msg.c_str());
});
st.logger = logger;
st.log_path = log_path;
st.initialized = true;
RDP_LOG_INFO("rdp_log enabled, log file: {}", log_path);
return true;
}
catch (const std::exception &e)
{
//日志文件创建失败(目录不可写等):降级为仅标准输出,不中断雷达数据处理
try
{
st.logger = MakeStdoutLogger();
st.log_path.clear();
st.initialized = true;
std::fprintf(stderr, "[rdp_log] cannot create log file, fallback to stdout only: %s\n",
e.what());
}
catch (...)
{
std::fprintf(stderr, "[rdp_log] logger init failed\n");
}
return false;
}
#endif
}
void Shutdown()
{
RdpLogState &st = State();
if (!st.initialized || !st.logger)
{
return;
}
RDP_LOG_INFO("rdp_log shutdown");
//flush 并释放默认 logger(随之关闭日志文件)
spdlog::shutdown();
st.logger.reset();
st.log_path.clear();
st.initialized = false;
}
std::string log_file_path()
{
return State().log_path;
}
}
+47
View File
@@ -0,0 +1,47 @@
#ifndef RDP_LOG_H
#define RDP_LOG_H
#pragma once
/*************************************日志输出模块*******************************************/
// 基于 spdlog v1.17.0(third_party/spdlog,来源/版本见该目录 UPSTREAM.md,以静态库目标
// spdlog 链接,见 CMakeLists.txt):
// 1) rdp_log::Init():创建 RDP_LOG_DIR 下按「日期+时间」命名的日志文件(每次初始化一个
// 文件;同一秒内文件已存在时追加 _1~_999 后缀),默认 logger 同时输出到 标准输出 与
// 日志文件 两个 sink。由 Data_Process_Factory::GetB() 调用;
// 2) rdp_log::Shutdown():打印结束标记、flush 并释放 logger(关闭日志文件),由
// Data_Process_Factory::Destroy() 调用;
// 3) 日志宏 RDP_LOG_TRACE / RDP_LOG_DEBUG / RDP_LOG_INFO / RDP_LOG_WARN /
// RDP_LOG_ERROR / RDP_LOG_CRITICAL:级别与 spdlog 一致(RDP_LOG_ERR 为
// RDP_LOG_ERROR 的别名),支持 fmt 风格的格式化,用法示例:
// RDP_LOG_INFO("目标批号:{},时间:{}", track_index, cpi_time);
// 低于 SPDLOG_ACTIVE_LEVEL 的日志调用在编译期被剔除(实参不求值),该级别由 CMake
// 选项 RDP_LOG_LEVEL 控制(见 CMakeLists.txt 与 README 10.4),即原 RDP_LOG_ENABLE
// 开关改由 SPDLOG_ACTIVE_LEVEL 在编译期控制日志级别(OFF = 编译期关闭全部日志)。
//
// 日志文件路径/名称与旧实现保持一致:RDP_LOG_DIR(.\rdp_logs\)+ rdp_YYYYMMDD_HHMMSS.log。
// 日志行格式:"[YYYY-MM-DD HH:MM:SS.mmm] [级别] [文件名:行号] 内容",级别为 spdlog 默认
// 名称 trace/debug/info/warning/error/critical。
#include <string>
// 第三方头文件告警隔离:spdlog 按 third_party 依赖处理(见 README 4.5)
#pragma warning(push, 0)
#include <spdlog/spdlog.h>
#pragma warning(pop)
namespace rdp_log
{
bool Init(); //初始化日志输出(标准输出 + 日志文件;由 GetB() 调用)
void Shutdown(); //flush、释放 logger 并关闭日志文件(由 Destroy() 调用)
std::string log_file_path(); //当前日志文件全路径(未初始化或文件创建失败时为空串)
}
//日志级别宏(spdlog 编译期级别过滤:低于 SPDLOG_ACTIVE_LEVEL 的调用被剔除、实参不求值)
#define RDP_LOG_TRACE(...) SPDLOG_TRACE(__VA_ARGS__)
#define RDP_LOG_DEBUG(...) SPDLOG_DEBUG(__VA_ARGS__)
#define RDP_LOG_INFO(...) SPDLOG_INFO(__VA_ARGS__)
#define RDP_LOG_WARN(...) SPDLOG_WARN(__VA_ARGS__)
#define RDP_LOG_ERROR(...) SPDLOG_ERROR(__VA_ARGS__)
#define RDP_LOG_CRITICAL(...) SPDLOG_CRITICAL(__VA_ARGS__)
//兼容别名:需求中的级别名 err 对应 spdlog::level::err,宏名同时提供 RDP_LOG_ERR
#define RDP_LOG_ERR(...) RDP_LOG_ERROR(__VA_ARGS__)
#endif // RDP_LOG_H
@@ -0,0 +1,617 @@
#include "rdp_parameter_loader.h"
#include "parameters.h"
#include <cfloat>
#include <cmath>
#include <cstdint>
#include <limits>
#include <string>
#include <string_view>
#include <vector>
// toml++ 为纯头文件第三方库(third_party/toml/toml.hpp,v3.4.0)。
// 第三方头文件不参与本工程的告警治理:包含期间临时屏蔽告警(/W3 下仍要求 0 告警)。
#if defined(_MSC_VER)
#pragma warning(push, 0)
#endif
#include <toml.hpp>
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
/************************************************************************************************
* 实现说明
*
* 配置文件结构与 RDP_parameter 一一对应:
* [radar_params] 雷达工作参数
* [cohesion] 点迹凝聚参数
* [track_associate] 航迹关联参数
* [track_init] 航迹起始参数
* [track_die] 航迹消亡参数
* [track_quality] 航迹质量管理参数
* [track_index] 航迹批号管理
* [beam_control] 波束调度参数
* [singer_model] singer 模型机动参数
* [track_init_prohibite_area] 航迹起始屏蔽区(数组)
* [tas_prohibite_area] 禁止 TAS 跟踪区(数组)
*
* 标量参数用「字段描述表」集中登记(段名 + 键名 + 成员指针),避免逐项重复代码;
* 屏蔽区数组因为「长度由 *_num 决定」单独处理。加载流程:
* 解析 TOML -> 校验段/键集合 -> 读取标量与数组到临时对象 -> 取值校验 -> 一次性赋值。
* 任一步失败都不会修改调用方传入的 parameter。
************************************************************************************************/
namespace
{
// 屏蔽区数组容量,必须与 struct.h::RDP_parameter 中的数组长度一致。
constexpr int kProhibiteAreaCapacity = 30;
// 带上下文的错误收集:只记录第一条错误,统一返回 false 便于链式返回。
class LoadContext
{
public:
explicit LoadContext(std::string* error_msg) : error_msg_(error_msg) {}
bool fail(const std::string& message)
{
if (!failed_)
{
failed_ = true;
if (error_msg_ != nullptr)
*error_msg_ = message;
}
return false;
}
bool failed() const { return failed_; }
private:
std::string* error_msg_;
bool failed_ = false;
};
// 标量字段描述:TOML 段名、键名、RDP_parameter 成员指针。
struct FloatField
{
const char* section;
const char* key;
float RDP_parameter::*member;
};
struct IntField
{
const char* section;
const char* key;
int RDP_parameter::*member;
};
// ------------------------------ 标量字段登记表 ------------------------------
const FloatField kFloatFields[] = {
// [radar_params] 雷达工作参数
{"radar_params", "freq_0_GHz", &RDP_parameter::freq_0_GHz},
{"radar_params", "radar_height", &RDP_parameter::radar_height},
{"radar_params", "radar_system_delay", &RDP_parameter::radar_system_delay},
{"radar_params", "data_rate_tws", &RDP_parameter::data_rate_tws},
{"radar_params", "data_rate_tas", &RDP_parameter::data_rate_tas},
{"radar_params", "data_rate_track_only", &RDP_parameter::data_rate_track_only},
// [cohesion] 点迹凝聚参数
{"cohesion", "point_cohesion_range", &RDP_parameter::point_cohesion_range},
{"cohesion", "point_cohesion_velocity", &RDP_parameter::point_cohesion_velocity},
{"cohesion", "point_cohesion_azimuth", &RDP_parameter::point_cohesion_azimuth},
{"cohesion", "point_cohesion_range_min", &RDP_parameter::point_cohesion_range_min},
{"cohesion", "point_cohesion_range_max", &RDP_parameter::point_cohesion_range_max},
// [track_associate] 航迹关联参数
{"track_associate", "sigma_range", &RDP_parameter::sigma_range},
{"track_associate", "sigma_azimuth", &RDP_parameter::sigma_azimuth},
{"track_associate", "sigma_pitch", &RDP_parameter::sigma_pitch},
{"track_associate", "sigma_velocity", &RDP_parameter::sigma_velocity},
{"track_associate", "associate_threshold", &RDP_parameter::associate_threshold},
// [track_init] 航迹起始参数
{"track_init", "track_init_threshold", &RDP_parameter::track_init_threshold},
{"track_init", "track_init_angle", &RDP_parameter::track_init_angle},
{"track_init", "track_init_quality_L3", &RDP_parameter::track_init_quality_L3},
{"track_init", "track_init_quality_L4", &RDP_parameter::track_init_quality_L4},
{"track_init", "track_init_quality_L5", &RDP_parameter::track_init_quality_L5},
{"track_init", "track_init_quality_L6", &RDP_parameter::track_init_quality_L6},
{"track_init", "track_init_quality_L7", &RDP_parameter::track_init_quality_L7},
{"track_init", "track_init_velocity_min", &RDP_parameter::track_init_velocity_min},
{"track_init", "track_init_velocity_max", &RDP_parameter::track_init_velocity_max},
// [track_die] 航迹消亡参数
{"track_die", "track_die_quality", &RDP_parameter::track_die_quality},
{"track_die", "track_die_quality_pramary", &RDP_parameter::track_die_quality_pramary},
// [track_quality] 航迹质量管理参数
{"track_quality", "track_quality_max", &RDP_parameter::track_quality_max},
{"track_quality", "track_quality_min", &RDP_parameter::track_quality_min},
{"track_quality", "track_quality_d_weight", &RDP_parameter::track_quality_d_weight},
{"track_quality", "track_quality_snr_weight", &RDP_parameter::track_quality_snr_weight},
{"track_quality", "track_quality_len_weight", &RDP_parameter::track_quality_len_weight},
{"track_quality", "track_quality_iter_coeff", &RDP_parameter::track_quality_iter_coeff},
{"track_quality", "track_quality_decrease_coeff_o2", &RDP_parameter::track_quality_decrease_coeff_o2},
{"track_quality", "track_quality_decrease_coeff_o1", &RDP_parameter::track_quality_decrease_coeff_o1},
// [singer_model] singer 模型机动参数
{"singer_model", "model_1_Q_fast", &RDP_parameter::model_1_Q_fast},
{"singer_model", "model_1_Q_slow", &RDP_parameter::model_1_Q_slow},
{"singer_model", "model_2_Q_fast", &RDP_parameter::model_2_Q_fast},
{"singer_model", "model_2_Q_slow", &RDP_parameter::model_2_Q_slow},
{"singer_model", "model_3_Q_fast", &RDP_parameter::model_3_Q_fast},
{"singer_model", "model_3_Q_slow", &RDP_parameter::model_3_Q_slow},
};
const IntField kIntFields[] = {
// [radar_params] 雷达工作参数
{"radar_params", "radar_work_mode", &RDP_parameter::radar_work_mode},
{"radar_params", "scan_mode", &RDP_parameter::scan_mode},
// [track_associate] 航迹关联参数
{"track_associate", "extrapolate_max", &RDP_parameter::extrapolate_max},
{"track_associate", "extrapolate_max_tas", &RDP_parameter::extrapolate_max_tas},
{"track_associate", "height_smooth_win", &RDP_parameter::height_smooth_win},
// [track_init] 航迹起始参数
{"track_init", "track_init_point_num_min", &RDP_parameter::track_init_point_num_min},
// [track_die] 航迹消亡参数
{"track_die", "track_die_round", &RDP_parameter::track_die_round},
{"track_die", "track_die_round_pramary", &RDP_parameter::track_die_round_pramary},
{"track_die", "track_die_round_tas", &RDP_parameter::track_die_round_tas},
// [track_index] 航迹批号管理
{"track_index", "track_index_max", &RDP_parameter::track_index_max},
// [beam_control] 波束调度参数
{"beam_control", "default_track_beam_type", &RDP_parameter::default_track_beam_type},
{"beam_control", "tas_track_num_max", &RDP_parameter::tas_track_num_max},
// 屏蔽区数目(对应数组长度由它决定)
{"track_init_prohibite_area", "track_init_prohibite_area_num", &RDP_parameter::track_init_prohibite_area_num},
{"tas_prohibite_area", "tas_prohibite_area_num", &RDP_parameter::tas_prohibite_area_num},
};
// 屏蔽区数组描述:段名 + 键名 + 成员指针(数组长度为 kProhibiteAreaCapacity)。
struct AreaArrayField
{
const char* section;
const char* key;
float (RDP_parameter::*member)[kProhibiteAreaCapacity];
};
const AreaArrayField kAreaArrayFields[] = {
{"track_init_prohibite_area", "track_init_prohibite_area_range_min", &RDP_parameter::track_init_prohibite_area_range_min},
{"track_init_prohibite_area", "track_init_prohibite_area_range_max", &RDP_parameter::track_init_prohibite_area_range_max},
{"track_init_prohibite_area", "track_init_prohibite_area_azimuth_min", &RDP_parameter::track_init_prohibite_area_azimuth_min},
{"track_init_prohibite_area", "track_init_prohibite_area_azimuth_max", &RDP_parameter::track_init_prohibite_area_azimuth_max},
{"tas_prohibite_area", "tas_prohibite_area_range_min", &RDP_parameter::tas_prohibite_area_range_min},
{"tas_prohibite_area", "tas_prohibite_area_range_max", &RDP_parameter::tas_prohibite_area_range_max},
{"tas_prohibite_area", "tas_prohibite_area_azimuth_min", &RDP_parameter::tas_prohibite_area_azimuth_min},
{"tas_prohibite_area", "tas_prohibite_area_azimuth_max", &RDP_parameter::tas_prohibite_area_azimuth_max},
};
const char* const kAllSections[] = {
"radar_params",
"cohesion",
"track_associate",
"track_init",
"track_die",
"track_quality",
"track_index",
"beam_control",
"singer_model",
"track_init_prohibite_area",
"tas_prohibite_area",
};
// ------------------------------ 基础读取工具 ------------------------------
bool node_to_double(const toml::node& node, double& value)
{
if (node.is_integer())
{
value = static_cast<double>(*node.value<std::int64_t>());
return true;
}
if (node.is_floating_point())
{
value = *node.value<double>();
return true;
}
return false;
}
bool read_float(const toml::table& section, const char* section_name, const char* key, float& out, LoadContext& ctx)
{
const toml::node* node = section.get(key);
if (node == nullptr)
return ctx.fail("缺少配置项 [" + std::string(section_name) + "] " + key);
double value = 0.0;
if (!node_to_double(*node, value))
return ctx.fail("配置项 [" + std::string(section_name) + "] " + key + " 必须是数字");
if (!std::isfinite(value) || std::fabs(value) > static_cast<double>(FLT_MAX))
return ctx.fail("配置项 [" + std::string(section_name) + "] " + key + " 超出 float 可表示范围");
out = static_cast<float>(value);
return true;
}
bool read_int(const toml::table& section, const char* section_name, const char* key, int& out, LoadContext& ctx)
{
const toml::node* node = section.get(key);
if (node == nullptr)
return ctx.fail("缺少配置项 [" + std::string(section_name) + "] " + key);
if (!node->is_integer())
return ctx.fail("配置项 [" + std::string(section_name) + "] " + key + " 必须是整数");
const std::int64_t value = *node->value<std::int64_t>();
if (value < std::numeric_limits<int>::min() || value > std::numeric_limits<int>::max())
return ctx.fail("配置项 [" + std::string(section_name) + "] " + key + " 超出 int 可表示范围");
out = static_cast<int>(value);
return true;
}
// 取配置段;缺失或不是表都算错误(严格模式:11 个配置段全部必填)。
const toml::table* get_section(const toml::table& root, const char* section_name, LoadContext& ctx)
{
const toml::node* node = root.get(section_name);
if (node == nullptr)
{
ctx.fail("缺少配置段 [" + std::string(section_name) + "]");
return nullptr;
}
const toml::table* table = node->as_table();
if (table == nullptr)
{
ctx.fail("配置项 [" + std::string(section_name) + "] 必须是 TOML 表");
return nullptr;
}
return table;
}
// 收集某个段允许出现的全部键(标量 + 数组)。
std::vector<std::string_view> allowed_keys_of(const std::string_view section)
{
std::vector<std::string_view> keys;
for (const FloatField& field : kFloatFields)
{
if (section == field.section)
keys.emplace_back(field.key);
}
for (const IntField& field : kIntFields)
{
if (section == field.section)
keys.emplace_back(field.key);
}
for (const AreaArrayField& field : kAreaArrayFields)
{
if (section == field.section)
keys.emplace_back(field.key);
}
return keys;
}
// 严格校验某个段中只出现允许的键(拼错的键按未知参数拒绝)。
bool check_keys_of_section(const toml::table& section, const char* section_name, LoadContext& ctx)
{
const std::vector<std::string_view> allowed = allowed_keys_of(section_name);
for (const auto& [key, value] : section)
{
(void)value;
bool known = false;
for (const std::string_view item : allowed)
{
if (key.str() == item)
{
known = true;
break;
}
}
if (!known)
return ctx.fail("配置段 [" + std::string(section_name) + "] 中存在未知配置项: " + std::string(key.str()));
}
return true;
}
// 读取一个屏蔽区段的 4 个数组。
// 约定:*_num > 0 时数组必须存在且元素个数等于 *_num;*_num == 0 时数组可省略,
// 存在时必须是空数组。数组元素写入 RDP_parameter 的定长数组(未使用位置保持 0)。
bool read_area_arrays(const toml::table& section, const char* section_name, int area_num,
RDP_parameter& parameter, LoadContext& ctx)
{
for (const AreaArrayField& field : kAreaArrayFields)
{
if (std::string_view(field.section) != section_name)
continue;
const toml::node* node = section.get(field.key);
if (node == nullptr)
{
if (area_num == 0)
continue;
return ctx.fail("缺少配置项 [" + std::string(section_name) + "] " + field.key);
}
const toml::array* array = node->as_array();
if (array == nullptr)
return ctx.fail("配置项 [" + std::string(section_name) + "] " + field.key + " 必须是数组");
if (static_cast<int>(array->size()) != area_num)
return ctx.fail("配置项 [" + std::string(section_name) + "] " + field.key + " 的元素个数(" +
std::to_string(array->size()) + ")与 " + std::string(section_name) + "_num(" +
std::to_string(area_num) + ")不一致");
float* destination = parameter.*(field.member);
int index = 0;
for (const toml::node& element : *array)
{
double value = 0.0;
if (!node_to_double(element, value))
return ctx.fail("配置项 [" + std::string(section_name) + "] " + field.key + " 的第 " +
std::to_string(index + 1) + " 个元素必须是数字");
if (!std::isfinite(value) || std::fabs(value) > static_cast<double>(FLT_MAX))
return ctx.fail("配置项 [" + std::string(section_name) + "] " + field.key + " 的第 " +
std::to_string(index + 1) + " 个元素超出 float 可表示范围");
destination[index] = static_cast<float>(value);
++index;
}
}
return true;
}
// ------------------------------ 取值校验 ------------------------------
bool validate_parameter(const RDP_parameter& p, LoadContext& ctx)
{
// 雷达工作参数
if (p.radar_work_mode < 0 || p.radar_work_mode > 2)
return ctx.fail("参数 radar_work_mode 必须在 0~2 之间(实际 " + std::to_string(p.radar_work_mode) + ")");
if (p.scan_mode != 0 && p.scan_mode != 1)
return ctx.fail("参数 scan_mode 必须为 0(相扫模式)或 1(机扫模式)(实际 " + std::to_string(p.scan_mode) + ")");
if (!(p.freq_0_GHz > 0.0f))
return ctx.fail("参数 freq_0_GHz 必须大于 0");
if (p.radar_system_delay < 0.0f)
return ctx.fail("参数 radar_system_delay 不能为负");
if (!(p.data_rate_tws > 0.0f))
return ctx.fail("参数 data_rate_tws 必须大于 0");
if (!(p.data_rate_tas > 0.0f))
return ctx.fail("参数 data_rate_tas 必须大于 0");
if (p.data_rate_track_only < 0.0f)
return ctx.fail("参数 data_rate_track_only 不能为负");
// 点迹凝聚参数
if (!(p.point_cohesion_range > 0.0f))
return ctx.fail("参数 point_cohesion_range 必须大于 0");
if (!(p.point_cohesion_velocity > 0.0f))
return ctx.fail("参数 point_cohesion_velocity 必须大于 0");
if (!(p.point_cohesion_azimuth > 0.0f))
return ctx.fail("参数 point_cohesion_azimuth 必须大于 0");
if (p.point_cohesion_range_min < 0.0f)
return ctx.fail("参数 point_cohesion_range_min 不能为负");
if (!(p.point_cohesion_range_max > p.point_cohesion_range_min))
return ctx.fail("参数 point_cohesion_range_max 必须大于 point_cohesion_range_min");
// 航迹关联参数
if (!(p.sigma_range > 0.0f))
return ctx.fail("参数 sigma_range 必须大于 0");
if (!(p.sigma_azimuth > 0.0f))
return ctx.fail("参数 sigma_azimuth 必须大于 0");
if (!(p.sigma_pitch > 0.0f))
return ctx.fail("参数 sigma_pitch 必须大于 0");
if (!(p.sigma_velocity > 0.0f))
return ctx.fail("参数 sigma_velocity 必须大于 0");
if (!(p.associate_threshold > 0.0f))
return ctx.fail("参数 associate_threshold 必须大于 0");
if (p.extrapolate_max < 0)
return ctx.fail("参数 extrapolate_max 不能为负");
if (p.extrapolate_max_tas < 0)
return ctx.fail("参数 extrapolate_max_tas 不能为负");
if (p.height_smooth_win < 1)
return ctx.fail("参数 height_smooth_win 必须大于等于 1");
// 航迹起始参数
if (!(p.track_init_threshold > 0.0f))
return ctx.fail("参数 track_init_threshold 必须大于 0");
if (!(p.track_init_angle > 0.0f) || p.track_init_angle > 180.0f)
return ctx.fail("参数 track_init_angle 必须在 (0, 180] 度范围内");
if (p.track_init_point_num_min < 3 || p.track_init_point_num_min > 9)
return ctx.fail("参数 track_init_point_num_min 必须在 3~9 之间(实际 " +
std::to_string(p.track_init_point_num_min) + ")");
const float init_quality[] = {
p.track_init_quality_L3, p.track_init_quality_L4, p.track_init_quality_L5,
p.track_init_quality_L6, p.track_init_quality_L7};
for (int i = 0; i < 5; ++i)
{
if (init_quality[i] < 0.0f || init_quality[i] > 1.0f)
return ctx.fail("参数 track_init_quality_L" + std::to_string(i + 3) + " 必须在 0~1 之间");
}
if (p.track_init_velocity_min < 0.0f)
return ctx.fail("参数 track_init_velocity_min 不能为负");
if (!(p.track_init_velocity_max > p.track_init_velocity_min))
return ctx.fail("参数 track_init_velocity_max 必须大于 track_init_velocity_min");
// 航迹消亡参数
if (p.track_die_round < 0)
return ctx.fail("参数 track_die_round 不能为负");
if (p.track_die_round_pramary < 0)
return ctx.fail("参数 track_die_round_pramary 不能为负");
if (p.track_die_round_tas < 0)
return ctx.fail("参数 track_die_round_tas 不能为负");
if (p.track_die_quality < 0.0f || p.track_die_quality > 1.0f)
return ctx.fail("参数 track_die_quality 必须在 0~1 之间");
if (p.track_die_quality_pramary < 0.0f || p.track_die_quality_pramary > 1.0f)
return ctx.fail("参数 track_die_quality_pramary 必须在 0~1 之间");
// 航迹质量管理参数
if (p.track_quality_min < 0.0f)
return ctx.fail("参数 track_quality_min 不能为负");
if (!(p.track_quality_max > p.track_quality_min))
return ctx.fail("参数 track_quality_max 必须大于 track_quality_min");
if (p.track_quality_d_weight < 0.0f || p.track_quality_snr_weight < 0.0f || p.track_quality_len_weight < 0.0f)
return ctx.fail("参数 track_quality_d_weight / track_quality_snr_weight / track_quality_len_weight 不能为负");
if (p.track_quality_iter_coeff < 0.0f || p.track_quality_iter_coeff > 1.0f)
return ctx.fail("参数 track_quality_iter_coeff 必须在 0~1 之间");
if (p.track_quality_decrease_coeff_o2 > 0.0f || p.track_quality_decrease_coeff_o1 > 0.0f)
return ctx.fail("参数 track_quality_decrease_coeff_o2 / track_quality_decrease_coeff_o1 必须小于等于 0");
// 航迹批号管理
if (p.track_index_max < 1 || p.track_index_max > MAX_TRACK_NUM)
return ctx.fail("参数 track_index_max 必须在 1~" + std::to_string(MAX_TRACK_NUM) + " 之间(实际 " +
std::to_string(p.track_index_max) + ")");
// 波束调度参数
if (p.default_track_beam_type < 0)
return ctx.fail("参数 default_track_beam_type 不能为负");
if (p.tas_track_num_max < 0 || p.tas_track_num_max > RDP_TAS_QUEUE_CAPACITY)
return ctx.fail("参数 tas_track_num_max 必须在 0~" + std::to_string(RDP_TAS_QUEUE_CAPACITY) +
" 之间(实际 " + std::to_string(p.tas_track_num_max) + ")");
// singer 模型机动参数
const float model_q[] = {
p.model_1_Q_fast, p.model_1_Q_slow, p.model_2_Q_fast,
p.model_2_Q_slow, p.model_3_Q_fast, p.model_3_Q_slow};
const char* const model_q_names[] = {
"model_1_Q_fast", "model_1_Q_slow", "model_2_Q_fast",
"model_2_Q_slow", "model_3_Q_fast", "model_3_Q_slow"};
for (int i = 0; i < 6; ++i)
{
if (!(model_q[i] > 0.0f))
return ctx.fail(std::string("参数 ") + model_q_names[i] + " 必须大于 0");
}
// 屏蔽区
if (p.track_init_prohibite_area_num < 0 || p.track_init_prohibite_area_num > kProhibiteAreaCapacity)
return ctx.fail("参数 track_init_prohibite_area_num 必须在 0~" +
std::to_string(kProhibiteAreaCapacity) + " 之间");
if (p.tas_prohibite_area_num < 0 || p.tas_prohibite_area_num > kProhibiteAreaCapacity)
return ctx.fail("参数 tas_prohibite_area_num 必须在 0~" + std::to_string(kProhibiteAreaCapacity) + " 之间");
for (int i = 0; i < p.track_init_prohibite_area_num; ++i)
{
const std::string prefix = "track_init_prohibite_area 第 " + std::to_string(i + 1) + " 区";
if (p.track_init_prohibite_area_range_min[i] < 0.0f ||
!(p.track_init_prohibite_area_range_max[i] > p.track_init_prohibite_area_range_min[i]))
return ctx.fail(prefix + " 的距离范围非法(要求 max > min >= 0)");
if (p.track_init_prohibite_area_azimuth_min[i] < 0.0f ||
p.track_init_prohibite_area_azimuth_max[i] > 360.0f ||
!(p.track_init_prohibite_area_azimuth_max[i] > p.track_init_prohibite_area_azimuth_min[i]))
return ctx.fail(prefix + " 的方位范围非法(要求 0 =< min < max <= 360)");
}
for (int i = 0; i < p.tas_prohibite_area_num; ++i)
{
const std::string prefix = "tas_prohibite_area 第 " + std::to_string(i + 1) + " 区";
if (p.tas_prohibite_area_range_min[i] < 0.0f ||
!(p.tas_prohibite_area_range_max[i] > p.tas_prohibite_area_range_min[i]))
return ctx.fail(prefix + " 的距离范围非法(要求 max > min >= 0)");
if (p.tas_prohibite_area_azimuth_min[i] < 0.0f ||
p.tas_prohibite_area_azimuth_max[i] > 360.0f ||
!(p.tas_prohibite_area_azimuth_max[i] > p.tas_prohibite_area_azimuth_min[i]))
return ctx.fail(prefix + " 的方位范围非法(要求 0 =< min < max <= 360)");
}
return true;
}
// ------------------------------ 加载主流程 ------------------------------
bool load_all_sections(const toml::table& root, RDP_parameter& parameter, LoadContext& ctx)
{
// 1) 顶层只允许已知配置段
for (const auto& [key, value] : root)
{
(void)value;
bool known = false;
for (const char* name : kAllSections)
{
if (key.str() == name)
{
known = true;
break;
}
}
if (!known)
return ctx.fail("配置文件中存在未知配置段: [" + std::string(key.str()) + "]");
}
// 2) 先确认 11 个配置段都存在且为表(缺段优先报错,便于定位)
for (const char* name : kAllSections)
{
if (get_section(root, name, ctx) == nullptr)
return false;
}
// 3) 再校验每段内只允许登记过的键
for (const char* name : kAllSections)
{
const toml::table* section = get_section(root, name, ctx);
if (section == nullptr)
return false;
if (!check_keys_of_section(*section, name, ctx))
return false;
}
// 4) 读取全部标量
for (const FloatField& field : kFloatFields)
{
const toml::table* section = get_section(root, field.section, ctx);
if (section == nullptr)
return false;
if (!read_float(*section, field.section, field.key, parameter.*(field.member), ctx))
return false;
}
for (const IntField& field : kIntFields)
{
const toml::table* section = get_section(root, field.section, ctx);
if (section == nullptr)
return false;
if (!read_int(*section, field.section, field.key, parameter.*(field.member), ctx))
return false;
}
// 4) 读取屏蔽区数组(长度依赖已读入的 *_num)
const toml::table* init_area = get_section(root, "track_init_prohibite_area", ctx);
if (init_area == nullptr)
return false;
if (!read_area_arrays(*init_area, "track_init_prohibite_area", parameter.track_init_prohibite_area_num, parameter, ctx))
return false;
const toml::table* tas_area = get_section(root, "tas_prohibite_area", ctx);
if (tas_area == nullptr)
return false;
if (!read_area_arrays(*tas_area, "tas_prohibite_area", parameter.tas_prohibite_area_num, parameter, ctx))
return false;
return true;
}
} // namespace
int rdp_parameter_load(const char* parameter_file_path, RDP_parameter& parameter, std::string* error_msg)
{
LoadContext ctx(error_msg);
if (parameter_file_path == nullptr || parameter_file_path[0] == '\0')
{
ctx.fail("配置文件路径为空");
return -1;
}
if (error_msg != nullptr)
error_msg->clear();
toml::table root;
try
{
root = toml::parse_file(parameter_file_path);
}
catch (const toml::parse_error& err)
{
ctx.fail(std::string("TOML 解析失败: ") + std::string(err.what()));
return -1;
}
catch (const std::exception& err)
{
ctx.fail(std::string("读取配置文件失败: ") + err.what());
return -1;
}
RDP_parameter loaded{};
if (!load_all_sections(root, loaded, ctx))
return -1;
if (!validate_parameter(loaded, ctx))
return -1;
// 全部校验通过后才一次性写入,保证失败时调用方参数不变。
parameter = loaded;
return 0;
}
@@ -0,0 +1,26 @@
#ifndef RDP_PARAMETER_LOADER_H
#define RDP_PARAMETER_LOADER_H
#pragma once
#include "struct.h"
#include <string>
/************************************************************************************************
* 雷达数据处理参数加载器(内部模块,不属于宿主接口)
*
* 功能:把 TOML 配置文件加载到 struct.h::RDP_parameter。
* 约定:
* 1) 配置文件的每个配置段与 RDP_parameter 的注释分组对应,配置键名与结构体成员名完全一致;
* 2) 屏蔽区(track_init_prohibite_area / tas_prohibite_area)的数组使用与成员同名的 TOML 数组,
* 数组长度必须等于对应的 *_num,且 *_num 不超过 30;
* 3) 所有标量键均必须存在且类型/取值合法(严格校验),任一错误返回 -1 且不修改 parameter;
* 4) 数组键在 *_num == 0 时可以省略,存在时必须为空数组;*_num > 0 时必须存在且长度等于 *_num。
*
* 参数:
* parameter_file_path - 配置文件路径(UTF-8,宿主进程当前工作目录可达)
* parameter - 输出参数;仅在成功时被写入
* error_msg - 可选输出;失败时写入可读的错误原因(UTF-8)
* 返回:成功返回 0;失败返回 -1
************************************************************************************************/
int rdp_parameter_load(const char* parameter_file_path, RDP_parameter& parameter, std::string* error_msg);
#endif // RDP_PARAMETER_LOADER_H
+989
View File
@@ -0,0 +1,989 @@
/***********************************数据记录调试模块实现****************************************/
// 本文件仅在构建系统定义 RDP_RECORD_ENABLE(CMake 选项 RDP_DATA_RECORD 开启)时参与编译,
// 且以 C++20 编译(ormpp 要求);雷达处理代码仍为 C++17,二者通过 rdp_record.h 的
// 「仅声明 + 显式实例化」跨标准边界,详见 docs/data_record_debug.md。
//
// 性能模型(V1.5.8 优化):
// 处理线程:记录点只做「收集源结构体 → 一次入队」(拷贝 + 互斥队列 push,不阻塞);
// 写线程 :从有界队列取批 → 构造记录实体(数组转文本在这里)→ ormpp 批量事务写 SQLite;
// 数据库 :WAL + synchronous=OFF + 8MB 页缓存 + busy_timeout;建表去 AUTOINCREMENT;
// 队列满 :整批丢弃并计数(实时性优先),Shutdown 时排空并打印统计。
#ifdef RDP_RECORD_ENABLE
//注意:windows.h 必须在业务/实体头文件之前包含 —— struct.h 在全局作用域有
//"using namespace std;",若之后再包含 windows.h,其 byte 类型会与 std::byte 产生歧义。
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h> //仅用于把记录写线程优先级降到 BELOW_NORMAL,避免与雷达处理线程争抢 CPU
#endif
#include "rdp_record.h"
#include "rdp_record_entity.h"
#include "parameters.h"
#include "rdp_log.h"
#include <atomic>
#include <chrono>
#include <climits>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <direct.h>
#include <io.h>
#include <iterator>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <time.h>
#include <tuple>
#include <utility>
#include <vector>
//第三方库(ormpp / iguana)模板代码在 /W3 下可能产生告警,按 third_party/ormpp/UPSTREAM.md
//的约定在此隔离;本文件自身的逻辑仍按工程 /W3 标准编译
#pragma warning(push, 0)
#include "dbng.hpp"
#include "sqlite.hpp"
#pragma warning(pop)
//记录实体写入函数的显式实例化宏(与 rdp_record_entity.h 的 RDP_RECORD_DECLARE 配套使用)
#define RDP_RECORD_DEFINE(Entity) \
template void rdp_record::Write<Entity>(const Entity&); \
template void rdp_record::Write<Entity>(const std::vector<Entity>&)
namespace
{
//============================== 基础类型 ==============================
enum class BatchKind : std::uint8_t
{
Point, //RdpRecPoint(输入点迹)
CohesionTws, //RdpRecPointCohesionTWS
CohesionTas, //RdpRecPointCohesionTAS
TempTrack, //RdpRecTempTrack
TrustTrack, //RdpRecTrustTrack
AssoInfo //RdpRecAssoInfo
};
//一批入队数据(一种记录表的一批源结构体;处理线程收集后整体 move 进队列)
struct Batch
{
BatchKind kind = BatchKind::Point;
std::vector<PointRecv> points; //Point/CohesionTws/CohesionTas 共用
std::vector<Temp_track> temp_tracks;
std::vector<Trust_Track> trust_tracks;
std::vector<Asso_Info> asso_infos;
};
//写线程内的待提交缓冲(按表分别累积,达到批大小或超时后各表分别提交)
struct Pending
{
std::vector<PointRecv> points;
std::vector<PointRecv> cohesion_tws;
std::vector<PointRecv> cohesion_tas;
std::vector<Temp_track> temp_tracks;
std::vector<Trust_Track> trust_tracks;
std::vector<Asso_Info> asso_infos;
};
//统计计数器(处理线程/写线程并发读写)
struct Counters
{
std::atomic<std::uint64_t> enqueued_rows{0};
std::atomic<std::uint64_t> dropped_rows{0};
std::atomic<std::uint64_t> written_rows{0};
std::atomic<std::uint64_t> transactions{0};
std::atomic<std::uint64_t> write_errors{0};
std::atomic<std::uint64_t> queue_peak_bytes{0};
std::atomic<std::uint64_t> queue_peak_rows{0};
};
//记录模块内部状态(Pimpl:ormpp / SQLite 类型不出现在 rdp_record.h 中)
struct Record_State
{
ormpp::dbng<ormpp::sqlite> db; //SQLite 连接(写线程独占;遗留同步接口经 db_mtx 串行)
std::string path; //当前 db 文件全路径
std::atomic<bool> ready{false};
std::atomic<bool> failed{false};
std::mutex db_mtx; //串行化数据库访问(写线程 + 遗留同步写)
std::map<std::string, int> tables; //表状态:0 未建 / 1 可用 / 2 失败(db_mtx 保护)
std::mutex q_mtx; //队列锁
std::condition_variable q_cv; //队列条件变量
std::deque<Batch> queue; //有界入队队列
std::size_t queued_bytes = 0;
std::size_t queued_rows = 0;
std::size_t queued_batches = 0;
bool stopping = false;
std::thread writer; //记录写线程
Counters counters;
std::atomic<int> consecutive_errors{0};
~Record_State();
};
std::unique_ptr<Record_State>& State()
{
//函数内静态:宿主未调用 Data_Process_Factory::Destroy() 时由析构兜底停止写线程/关库
static std::unique_ptr<Record_State> s;
return s;
}
//============================== 通用小工具 ==============================
//当前本地时间字符串,格式 "YYYYMMDD_HHMMSS"(与 rdp_log 的日志文件命名风格一致)
bool TimeStamp(char* buf, std::size_t size)
{
time_t now = time(NULL);
struct tm local;
if (localtime_s(&local, &now) != 0)
{
return false;
}
return strftime(buf, size, "%Y%m%d_%H%M%S", &local) != 0;
}
//写入失败提示
void ReportError(Record_State& st, const char* what, const std::string& detail)
{
RDP_LOG_ERROR("[rdp_record] {} failed ({}): {}", what, detail, st.db.get_last_error());
}
void LogStats(Record_State& st, const char* tag)
{
RDP_LOG_INFO("[rdp_record] stats({}): enqueued_rows={}, written_rows={}, dropped_rows={}, "
"transactions={}, write_errors={}, queue_peak_bytes={}, queue_peak_rows={}",
tag,
st.counters.enqueued_rows.load(),
st.counters.written_rows.load(),
st.counters.dropped_rows.load(),
st.counters.transactions.load(),
st.counters.write_errors.load(),
st.counters.queue_peak_bytes.load(),
st.counters.queue_peak_rows.load());
}
template <typename T>
std::string TableName()
{
//ormpp 以「短结构体名」作为表名
return std::string(ormpp::get_short_struct_name<T>());
}
//============================== 记录批次辅助 ==============================
template <typename T>
std::size_t BatchRowsOf(const std::vector<T>& v)
{
return v.size();
}
std::size_t BatchRows(const Batch& b)
{
return b.points.size() + b.temp_tracks.size() + b.trust_tracks.size() + b.asso_infos.size();
}
std::size_t BatchBytes(const Batch& b)
{
//近似统计(用于有界队列的字节上限;容器容量 + 对象本身)
std::size_t bytes = sizeof(Batch);
bytes += b.points.capacity() * sizeof(PointRecv);
bytes += b.temp_tracks.capacity() * sizeof(Temp_track);
bytes += b.trust_tracks.capacity() * sizeof(Trust_Track);
bytes += b.asso_infos.capacity() * sizeof(Asso_Info);
return bytes;
}
std::size_t PendingRows(const Pending& p)
{
return p.points.size() + p.cohesion_tws.size() + p.cohesion_tas.size()
+ p.temp_tracks.size() + p.trust_tracks.size() + p.asso_infos.size();
}
void ClearPending(Pending& p)
{
p.points.clear();
p.cohesion_tws.clear();
p.cohesion_tas.clear();
p.temp_tracks.clear();
p.trust_tracks.clear();
p.asso_infos.clear();
}
template <typename T>
void AppendMove(std::vector<T>& dst, std::vector<T>& src)
{
if (src.empty())
{
return;
}
if (dst.empty())
{
dst = std::move(src);
return;
}
dst.insert(dst.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
src.clear();
}
void AppendBatch(Pending& p, Batch& b)
{
switch (b.kind)
{
case BatchKind::Point: AppendMove(p.points, b.points); break;
case BatchKind::CohesionTws: AppendMove(p.cohesion_tws, b.points); break;
case BatchKind::CohesionTas: AppendMove(p.cohesion_tas, b.points); break;
case BatchKind::TempTrack: AppendMove(p.temp_tracks, b.temp_tracks); break;
case BatchKind::TrustTrack: AppendMove(p.trust_tracks, b.trust_tracks); break;
case BatchKind::AssoInfo: AppendMove(p.asso_infos, b.asso_infos); break;
}
}
//============================== 数据库初始化/建表 ==============================
//应用 SQLite PRAGMA(失败只记录,不阻止记录功能;调试用途)
void ApplyPragmas(Record_State& st)
{
#if RDP_RECORD_SQLITE_WAL
try
{
//PRAGMA journal_mode 会返回一行,ormpp 的 execute() 只接受不返回结果行的语句,
//因此用 query_s<std::tuple<...>> 消费返回行。
auto rows = st.db.query_s<std::tuple<std::string>>("PRAGMA journal_mode=WAL;");
std::string mode = rows.empty() ? std::string("<none>") : std::get<0>(rows[0]);
RDP_LOG_INFO("[rdp_record] journal_mode={}", mode);
}
catch (...)
{
RDP_LOG_WARN("[rdp_record] set journal_mode=WAL failed, fallback to default journal");
}
#endif
if (!st.db.execute("PRAGMA synchronous=OFF;"))
{
RDP_LOG_WARN("[rdp_record] set synchronous=OFF failed");
}
const std::string cache = "PRAGMA cache_size=-" + std::to_string(RDP_RECORD_SQLITE_CACHE_KB) + ";";
if (!st.db.execute(cache))
{
RDP_LOG_WARN("[rdp_record] set cache_size failed");
}
try
{
//busy_timeout 也返回一行
st.db.query_s<std::tuple<int>>(
"PRAGMA busy_timeout=" + std::to_string(RDP_RECORD_SQLITE_BUSY_TIMEOUT_MS) + ";");
}
catch (...)
{
RDP_LOG_WARN("[rdp_record] set busy_timeout failed");
}
}
//建表 + 去掉 AUTOINCREMENT(本项目每次运行新建库、只增不删,普通 INTEGER PRIMARY KEY 即可;
//去 AUTOINCREMENT 可省掉每行对 sqlite_sequence 的读写。ormpp 的 DDL 硬编码 AUTOINCREMENT,
//这里先用 ormpp 建表(同时登记自增主键映射),再按 sqlite_master 中的 DDL 去关键字重建空表。)
template <typename T>
bool CreateTable(Record_State& st)
{
if (!st.db.create_datatable<T>(::ormpp_auto_key{"id"}))
{
return false;
}
const std::string table = TableName<T>();
std::string ddl;
try
{
auto rows = st.db.query_s<std::tuple<std::string>>(
"select sql from sqlite_master where type='table' and name='" + table + "'");
if (!rows.empty())
{
ddl = std::get<0>(rows[0]);
}
}
catch (...)
{
return true; //查询失败不影响记录,保留 ormpp 建出的表
}
if (ddl.find("AUTOINCREMENT") == std::string::npos)
{
return true; //ormpp 版本变化后不再带 AUTOINCREMENT,直接使用
}
std::string stripped = ddl;
const std::string keyword = "AUTOINCREMENT";
for (std::size_t pos = stripped.find(keyword); pos != std::string::npos;
pos = stripped.find(keyword))
{
stripped.erase(pos, keyword.size());
}
if (st.db.execute("DROP TABLE \"" + table + "\";") &&
st.db.execute(stripped))
{
RDP_LOG_INFO("[rdp_record] table {} created without AUTOINCREMENT", table);
return true;
}
//回退:若重建失败(极端情况),尽量恢复一张可用表,保证记录功能继续
if (st.db.create_datatable<T>(::ormpp_auto_key{"id"}))
{
RDP_LOG_WARN("[rdp_record] warning: keep AUTOINCREMENT for {}", table);
return true;
}
return false;
}
//写入前置处理(调用方需持有 db_mtx):按表缓存建表结果,失败的表永久跳过
template <typename T>
bool PrepareWrite(Record_State& st)
{
const std::string table = TableName<T>();
std::map<std::string, int>::iterator it = st.tables.find(table);
const int state = (it == st.tables.end()) ? 0 : it->second;
if (state == 2)
{
return false;
}
if (state == 1)
{
return true;
}
if (CreateTable<T>(st))
{
st.tables[table] = 1;
return true;
}
st.tables[table] = 2;
ReportError(st, "create table", table);
return false;
}
//批量写入实体(一次 BEGIN/COMMIT 事务;调用方无需持锁)
template <typename T>
bool InsertEntities(Record_State& st, const std::vector<T>& v)
{
if (v.empty())
{
return true;
}
std::lock_guard<std::mutex> lock(st.db_mtx);
if (st.failed.load())
{
return false;
}
if (!PrepareWrite<T>(st))
{
return false;
}
const int rows = st.db.insert(v);
if (rows == INT_MIN)
{
st.counters.write_errors.fetch_add(1);
st.tables[TableName<T>()] = 2;
const int errors = st.consecutive_errors.fetch_add(1) + 1;
ReportError(st, "insert", TableName<T>());
if (errors >= RDP_RECORD_MAX_CONSECUTIVE_ERRORS)
{
st.failed.store(true);
RDP_LOG_ERROR("[rdp_record] too many consecutive write errors, recording stopped");
}
return false;
}
st.consecutive_errors.store(0);
st.counters.written_rows.fetch_add(v.size());
st.counters.transactions.fetch_add(1);
return true;
}
//按批大小分块构造实体并提交(实体构造 = 数组转文本,全部在写线程完成)
template <typename Entity, typename Source>
bool FlushKind(Record_State& st, std::vector<Source>& src)
{
if (src.empty())
{
return true;
}
const std::size_t chunk = (RDP_RECORD_WRITE_BATCH_ROWS == 0)
? 4096
: static_cast<std::size_t>(RDP_RECORD_WRITE_BATCH_ROWS);
bool ok = true;
for (std::size_t pos = 0; pos < src.size() && !st.failed.load();)
{
std::size_t n = src.size() - pos;
if (n > chunk)
{
n = chunk;
}
std::vector<Entity> entities;
entities.reserve(n);
for (std::size_t i = 0; i < n; ++i)
{
entities.emplace_back(src[pos + i]); //Entity(Source&) 构造:数组转文本在此完成
}
if (!InsertEntities<Entity>(st, entities))
{
ok = false;
}
pos += n;
}
src.clear();
return ok;
}
bool FlushPending(Record_State& st, Pending& p)
{
bool ok = true;
ok &= FlushKind<RdpRecPoint>(st, p.points);
ok &= FlushKind<RdpRecPointCohesionTWS>(st, p.cohesion_tws);
ok &= FlushKind<RdpRecPointCohesionTAS>(st, p.cohesion_tas);
ok &= FlushKind<RdpRecTempTrack>(st, p.temp_tracks);
ok &= FlushKind<RdpRecTrustTrack>(st, p.trust_tracks);
ok &= FlushKind<RdpRecAssoInfo>(st, p.asso_infos);
ClearPending(p);
return ok;
}
//============================== 入队/写线程 ==============================
void LogDroppedRows(std::uint64_t total_dropped, std::uint64_t newly_dropped)
{
const std::uint64_t interval = (RDP_RECORD_DROP_LOG_INTERVAL == 0)
? 0
: static_cast<std::uint64_t>(RDP_RECORD_DROP_LOG_INTERVAL);
if (interval == 0)
{
return;
}
if ((total_dropped / interval) != ((total_dropped - newly_dropped) / interval))
{
RDP_LOG_WARN("[rdp_record] queue full, dropped rows total={}", total_dropped);
}
}
void EnqueueBatch(Batch&& b)
{
const std::size_t rows = BatchRows(b);
if (rows == 0)
{
return;
}
std::unique_ptr<Record_State>& sp = State();
if (sp.get() == 0)
{
return;
}
if (!sp->ready || sp->failed.load())
{
sp->counters.dropped_rows.fetch_add(rows);
return;
}
const std::size_t bytes = BatchBytes(b);
std::lock_guard<std::mutex> lock(sp->q_mtx);
if (sp->stopping || sp->failed.load())
{
const std::uint64_t total = sp->counters.dropped_rows.fetch_add(rows) + rows;
LogDroppedRows(total, rows);
return;
}
if (sp->queued_batches + 1 > static_cast<std::size_t>(RDP_RECORD_QUEUE_MAX_BATCHES) ||
sp->queued_bytes + bytes > static_cast<std::size_t>(RDP_RECORD_QUEUE_CAP_BYTES))
{
const std::uint64_t total = sp->counters.dropped_rows.fetch_add(rows) + rows;
LogDroppedRows(total, rows);
return;
}
sp->queue.push_back(std::move(b));
sp->queued_batches += 1;
sp->queued_bytes += bytes;
sp->queued_rows += rows;
sp->counters.enqueued_rows.fetch_add(rows);
if (sp->counters.queue_peak_bytes.load() < sp->queued_bytes)
{
sp->counters.queue_peak_bytes.store(sp->queued_bytes);
}
if (sp->counters.queue_peak_rows.load() < sp->queued_rows)
{
sp->counters.queue_peak_rows.store(sp->queued_rows);
}
sp->q_cv.notify_one();
}
void DropQueued(Record_State& st)
{
std::size_t rows = 0;
{
std::lock_guard<std::mutex> lock(st.q_mtx);
for (std::size_t i = 0; i < st.queue.size(); ++i)
{
rows += BatchRows(st.queue[i]);
}
st.queue.clear();
st.queued_bytes = 0;
st.queued_rows = 0;
st.queued_batches = 0;
}
if (rows > 0)
{
const std::uint64_t total = st.counters.dropped_rows.fetch_add(rows) + rows;
RDP_LOG_INFO("[rdp_record] writer stopped, dropped queued rows={}, total dropped={}", rows, total);
}
}
void WriterLoopBody(Record_State& st)
{
Pending pending;
for (;;)
{
std::deque<Batch> local;
{
std::unique_lock<std::mutex> lock(st.q_mtx);
st.q_cv.wait(lock, [&st]() { return !st.queue.empty() || st.stopping; });
if (st.queue.empty() && st.stopping)
{
break; //停止且队列已空:退出(pending 已在上一轮提交)
}
local.swap(st.queue);
st.queued_bytes = 0;
st.queued_rows = 0;
st.queued_batches = 0;
}
for (std::size_t i = 0; i < local.size(); ++i)
{
AppendBatch(pending, local[i]);
}
local.clear();
//凑批:未达目标且未停止时,短暂等待更多数据,减少事务次数
while (!st.stopping && !st.failed.load() &&
PendingRows(pending) < static_cast<std::size_t>(RDP_RECORD_WRITE_BATCH_ROWS))
{
std::unique_lock<std::mutex> lock(st.q_mtx);
const bool more = st.q_cv.wait_for(
lock,
std::chrono::milliseconds(RDP_RECORD_WRITE_MAX_WAIT_MS),
[&st]() { return !st.queue.empty() || st.stopping; });
if (!more)
{
break; //超时:提交现有数据
}
if (st.queue.empty())
{
break; //停止且队列已空
}
local.swap(st.queue);
st.queued_bytes = 0;
st.queued_rows = 0;
st.queued_batches = 0;
lock.unlock();
for (std::size_t i = 0; i < local.size(); ++i)
{
AppendBatch(pending, local[i]);
}
local.clear();
}
FlushPending(st, pending);
if (st.failed.load())
{
DropQueued(st);
return;
}
}
FlushPending(st, pending);
}
void WriterLoop(Record_State& st)
{
#ifdef _WIN32
//记录写线程是后台任务:把线程 CPU 优先级降到 BELOW_NORMAL,避免与雷达处理线程争抢 CPU;
//I/O 保持正常优先级(否则写入积压会拉长 Shutdown 排空时间)。队列有界,写线程变慢时
//由丢弃策略兜底,不影响处理线程。
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
#endif
try
{
WriterLoopBody(st);
}
catch (...)
{
//写线程内任何异常都不能抛出 DLL 边界;记录失败即降级
st.failed.store(true);
RDP_LOG_ERROR("[rdp_record] writer thread exception, recording stopped");
DropQueued(st);
}
}
//============================== 生命周期辅助 ==============================
Record_State::~Record_State()
{
{
std::lock_guard<std::mutex> lock(q_mtx);
stopping = true;
}
q_cv.notify_all();
if (writer.joinable())
{
writer.join();
}
std::lock_guard<std::mutex> lock(db_mtx);
try
{
db.disconnect();
}
catch (...)
{
}
}
}
namespace rdp_record
{
bool Init()
{
std::unique_ptr<Record_State>& sp = State();
if (sp.get() != 0)
{
if (sp->ready)
{
return true; //已经初始化
}
if (sp->failed)
{
return false; //初始化失败(本次进程内不再重试)
}
sp.reset(); //上一次已 Shutdown:允许重新初始化(宿主销毁/重建工厂的场景)
}
sp.reset(new Record_State());
Record_State& st = *sp;
try
{
//创建记录目录(目录已存在时 _mkdir 返回 EEXIST,忽略;逻辑与 rdp_log.cpp 一致)
std::string dir = RDP_RECORD_DB_DIR;
while (dir.size() > 0 && (dir[dir.size() - 1] == '\\' || dir[dir.size() - 1] == '/'))
{
dir.erase(dir.size() - 1);
}
if (dir.size() > 0)
{
_mkdir(dir.c_str());
}
//按当前日期+时间生成记录库文件名:rdp_record_YYYYMMDD_HHMMSS.db
char stamp[32] = {0};
if (!TimeStamp(stamp, sizeof(stamp)))
{
RDP_LOG_WARN("[rdp_record] cannot get local time, recording disabled");
st.failed = true;
return false;
}
st.path = RDP_RECORD_DB_DIR;
st.path += "rdp_record_";
st.path += stamp;
st.path += ".db";
//同一秒内重复 Init(Destroy→GetB 重连)或同目录多进程启动时,避免复用同一个库文件
if (_access(st.path.c_str(), 0) == 0)
{
const std::string base = st.path.substr(0, st.path.size() - 3); //去掉 ".db"
for (int suffix = 1; suffix < 1000; ++suffix)
{
const std::string candidate = base + "_" + std::to_string(suffix) + ".db";
if (_access(candidate.c_str(), 0) != 0)
{
st.path = candidate;
break;
}
}
}
if (!st.db.connect(st.path))
{
RDP_LOG_WARN("[rdp_record] cannot open database: {}", st.path);
st.failed = true;
return false;
}
ApplyPragmas(st);
//启动记录写线程(处理线程之后只入队,不直接访问数据库)
st.writer = std::thread(WriterLoop, std::ref(st));
st.ready = true;
RDP_LOG_INFO("[rdp_record] enabled, database: {} (async writer, write_batch_rows={}, queue_cap_bytes={})",
st.path, RDP_RECORD_WRITE_BATCH_ROWS, RDP_RECORD_QUEUE_CAP_BYTES);
return true;
}
catch (const std::exception& e)
{
RDP_LOG_ERROR("[rdp_record] init exception: {}", e.what());
}
catch (...)
{
RDP_LOG_ERROR("[rdp_record] init exception");
}
st.failed = true;
st.ready = false;
try
{
st.db.disconnect();
}
catch (...)
{
}
return false;
}
void Shutdown()
{
std::unique_ptr<Record_State>& sp = State();
if (sp.get() == 0)
{
return;
}
Record_State& st = *sp;
if (!st.ready)
{
sp.reset();
return;
}
//1) 停止接收新数据;2) 等待写线程排空队列/pending 并退出;3) 检查点/关库;4) 打印统计
{
std::lock_guard<std::mutex> lock(st.q_mtx);
st.stopping = true;
}
st.q_cv.notify_all();
if (st.writer.joinable())
{
st.writer.join();
}
LogStats(st, "shutdown");
try
{
std::lock_guard<std::mutex> lock(st.db_mtx);
//WAL 模式下把 -wal 合并回主库(尽力而为;失败不影响关库)
st.db.query_s<std::tuple<int, int, int>>("PRAGMA wal_checkpoint(TRUNCATE);");
st.db.disconnect();
}
catch (...)
{
}
RDP_LOG_INFO("[rdp_record] shutdown, database: {}", st.path);
st.ready = false;
//保留状态对象(连接已断开、写线程已退出):下次 Init() 检测到 !ready && !failed 时复位并重新建库。
//不在 Shutdown 内直接释放,避免宿主在 Destroy 的同时仍有记录调用时出现悬空指针(防御性处理)。
}
std::string db_path()
{
std::unique_ptr<Record_State>& sp = State();
return (sp.get() == 0) ? std::string() : sp->path;
}
Stats stats()
{
Stats s;
std::unique_ptr<Record_State>& sp = State();
if (sp.get() == 0)
{
return s;
}
s.enqueued_rows = sp->counters.enqueued_rows.load();
s.dropped_rows = sp->counters.dropped_rows.load();
s.written_rows = sp->counters.written_rows.load();
s.transactions = sp->counters.transactions.load();
s.write_errors = sp->counters.write_errors.load();
s.queue_peak_bytes = sp->counters.queue_peak_bytes.load();
s.queue_peak_rows = sp->counters.queue_peak_rows.load();
return s;
}
//============================== 遗留同步接口(实体) ==============================
//说明:会阻塞调用线程,常规记录点请使用上面各表的异步入队接口;仅保留给测试/特殊场景。
template <typename T>
void Write(const T& v)
{
std::unique_ptr<Record_State>& sp = State();
if (sp.get() == 0 || !sp->ready || sp->failed.load())
{
return; //未初始化 / 初始化失败 / 已关库:静默忽略
}
std::lock_guard<std::mutex> lock(sp->db_mtx);
if (!PrepareWrite<T>(*sp))
{
return;
}
if (sp->db.insert(v) == INT_MIN)
{
sp->counters.write_errors.fetch_add(1);
sp->tables[TableName<T>()] = 2;
ReportError(*sp, "insert", TableName<T>());
return;
}
sp->counters.written_rows.fetch_add(1);
sp->counters.transactions.fetch_add(1);
}
template <typename T>
void Write(const std::vector<T>& v)
{
if (v.empty())
{
return;
}
std::unique_ptr<Record_State>& sp = State();
if (sp.get() == 0 || !sp->ready || sp->failed.load())
{
return;
}
InsertEntities<T>(*sp, v);
}
//============================== 异步入队接口(源结构体) ==============================
void WritePoints(std::vector<PointRecv> v)
{
if (v.empty())
{
return;
}
Batch b;
b.kind = BatchKind::Point;
b.points = std::move(v);
EnqueueBatch(std::move(b));
}
void WriteCohesionTWS(std::vector<PointRecv> v)
{
if (v.empty())
{
return;
}
Batch b;
b.kind = BatchKind::CohesionTws;
b.points = std::move(v);
EnqueueBatch(std::move(b));
}
void WriteCohesionTAS(std::vector<PointRecv> v)
{
if (v.empty())
{
return;
}
Batch b;
b.kind = BatchKind::CohesionTas;
b.points = std::move(v);
EnqueueBatch(std::move(b));
}
void WriteTempTracks(std::vector<Temp_track> v)
{
if (v.empty())
{
return;
}
Batch b;
b.kind = BatchKind::TempTrack;
b.temp_tracks = std::move(v);
EnqueueBatch(std::move(b));
}
void WriteTrustTracks(std::vector<Trust_Track> v)
{
if (v.empty())
{
return;
}
Batch b;
b.kind = BatchKind::TrustTrack;
b.trust_tracks = std::move(v);
EnqueueBatch(std::move(b));
}
void WriteAssoInfos(std::vector<Asso_Info> v)
{
if (v.empty())
{
return;
}
Batch b;
b.kind = BatchKind::AssoInfo;
b.asso_infos = std::move(v);
EnqueueBatch(std::move(b));
}
}
/***********************************记录实体注册区*********************************************/
// 新增记录实体时在此追加两行(字段顺序必须与 rdp_record_entity.h 中的结构体声明顺序一致):
// YLT_REFL(结构体名, 字段1, 字段2, ...);
// RDP_RECORD_DEFINE(结构体名);
//输入点迹
YLT_REFL(RdpRecPoint, id,point_id,frame_id,circle_id,range,azimuth, velocity,amplitude ,height,SNR,RCS,
encoder_value,amp_asv_point,prn_fftn_point,CPI_time,GNSS_time,use_flag,azimuth_beam_index,pitch_beam_index,PRF_index,freq_index,point_type,point_section_asso);
RDP_RECORD_DEFINE(RdpRecPoint);
//TWS凝聚后点迹
YLT_REFL(RdpRecPointCohesionTWS, id,point_id,frame_id,circle_id,range,azimuth, velocity,amplitude ,height,SNR,RCS,
encoder_value,amp_asv_point,prn_fftn_point,CPI_time,GNSS_time,use_flag,azimuth_beam_index,pitch_beam_index,PRF_index,freq_index,point_type,point_section_asso);
RDP_RECORD_DEFINE(RdpRecPointCohesionTWS);
//TAS凝聚后点迹
YLT_REFL(RdpRecPointCohesionTAS, id,point_id,frame_id,circle_id,range,azimuth, velocity,amplitude ,height,SNR,RCS,
encoder_value,amp_asv_point,prn_fftn_point,CPI_time,GNSS_time,use_flag,azimuth_beam_index,pitch_beam_index,PRF_index,freq_index,point_type,point_section_asso);
RDP_RECORD_DEFINE(RdpRecPointCohesionTAS);
//临时航迹
YLT_REFL(RdpRecTempTrack, id, temp_track_id,point_id,circle_id,X,P,r,azi,vr,height,SNR,RCS,amplitude, quality, CPI_time,GNSS_time,d,buff_round,temp_track_section_idx,asso_flag,pitch_num);
RDP_RECORD_DEFINE(RdpRecTempTrack);
//可靠航迹
YLT_REFL(RdpRecTrustTrack, id,track_id,point_id,circle_id,Track_Index,Range,Azimuth,Elevation,Velocity,Amplitude,Height,Quality,T_track,GNSS_time,Track_Sum,Unupdated_time,Point_Index,Point_sum,Valid_point_sum,Track_Mode,
Track_Update_Flag,Extrapolate_round,point_flag,approach_flag,manual_delete_flag,manual_tracking_flag,
associate_point_number,primary_target_flag,Target_Type,Track_section_idx,
range_point,azi_point,elev_point,vr_point,prf_point,point_type,snr_point,
amp_asv_point,prn_fftn_point,RCS,pitch_num,X,P,X1,X2,X3,P2,P3,u);
RDP_RECORD_DEFINE(RdpRecTrustTrack);
//关联信息
YLT_REFL(RdpRecAssoInfo, id,point_id,track_id,circle_id,d1,d2,d3,d_min,point_index,track_index,
delta_T,delta_h,R,VT,
tas_flag,thd_d,thd_h,thd_r,
min_d_flag);
RDP_RECORD_DEFINE(RdpRecAssoInfo);
// // 示例②:本圈输出的航迹
// YLT_REFL(RdpRecTrackOut, id, track_index, track_mode, point_sum, flag_point, flag_direction,
// gnss_time_ms, track_time_s, x, y, z, v_x, v_y, v_z, range_m, azi_deg, elev_deg,
// range_v_mps, snr, rcs);
// RDP_RECORD_DEFINE(RdpRecTrackOut);
#endif // RDP_RECORD_ENABLE
+103
View File
@@ -0,0 +1,103 @@
#ifndef RDP_RECORD_H
#define RDP_RECORD_H
#pragma once
/***********************************数据记录调试模块********************************************/
// 功能(完整说明见 docs/data_record_debug.md):
// 1) 把结构体数据按「一个记录实体一张表」的方式写入 SQLite 数据库,结构体到数据表的映射由
// 内置的 ormpp 完成(third_party/ormpp,见该目录 UPSTREAM.md);
// 2) 性能模型(V1.5.8 优化后):
// - 记录点只做「收集源结构体 → 一次入队」,在雷达处理线程上只有拷贝 + 队列 push 的开销;
// - 实体构造(X/P 等数组转文本)与 SQLite 写入由独立的写线程完成,批量事务提交;
// - 队列有界,写线程跟不上时整批丢弃并计数,**绝不阻塞雷达处理线程**;
// 3) 整个功能由构建开关控制:CMake 选项 RDP_DATA_RECORD(AUTO/ON/OFF)开启时由构建系统定义
// RDP_RECORD_ENABLE,rdp_record.cpp 与 sqlite3 amalgamation 才参与编译;关闭(交付/Release
// 构建默认)时本模块完全不编译,所有记录宏展开为 ((void)0)、实参不求值,不留任何开销;
// 4) 生命周期:Data_Process_Factory::GetB() 调用 rdp_record::Init()(建目录、开库、启动写线程),
// Data_Process_Factory::Destroy() 调用 rdp_record::Shutdown()(停止入队、排空队列、关库);
// Init 之前、初始化失败之后或 Shutdown 之后的写入一律静默忽略,不影响雷达数据处理;
// 5) 记录点用法(新增数据 = 加记录实体 + 注册 + 一行批量记录点,配方见 docs/data_record_debug.md):
// 帧/圈内收集:std::vector<源结构体> recs;
// 记录点: RDP_RECORD_POINTS(recs); // 按表选择对应宏,内部 std::move
// 需要语句/局部变量的记录点请整体用 #ifdef RDP_RECORD_ENABLE ... #endif 包裹,
// 保证关闭记录功能时既无代码也无「变量未使用」告警;
// 6) 记录实体类型必须先在 rdp_record_entity.h 中声明并注册,否则链接期报 LNK2019
// (extern template 已阻止 C++17 调用方隐式实例化)。
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
//记录源结构体(定义在 struct.h;此处只做声明,避免业务头文件依赖 struct.h 的全部内容)
struct PointRecv;
struct Temp_track;
struct Trust_Track;
struct Asso_Info;
namespace rdp_record
{
//记录统计(仅用于调试/验收;Shutdown 时自动打印,也可随时读取)
struct Stats
{
std::uint64_t enqueued_rows = 0; //已入队行数
std::uint64_t dropped_rows = 0; //队列满/停止后丢弃的行数
std::uint64_t written_rows = 0; //已成功写入数据库的行数
std::uint64_t transactions = 0; //提交事务数
std::uint64_t write_errors = 0; //写入失败次数
std::uint64_t queue_peak_bytes = 0; //队列峰值字节数
std::uint64_t queue_peak_rows = 0; //队列峰值行数
};
bool Init(); //初始化记录库并启动写线程(由 Data_Process_Factory::GetB() 调用)
void Shutdown(); //停止写线程、排空队列并关闭记录库(由 Data_Process_Factory::Destroy() 调用)
std::string db_path(); //当前记录库文件全路径(未启用或未成功打开时返回空串)
Stats stats(); //当前统计快照(任何时刻可调用,线程安全)
//异步入队接口(处理线程只拷贝源结构体并送入有界队列;参数按值接收,调用点用 std::move)
//仅在记录功能开启(RDP_RECORD_ENABLE)时有实现;关闭构建由对应宏展开为空。
void WritePoints(std::vector<PointRecv> v); //RdpRecPoint(输入点迹,TWS/TAS 共用)
void WriteCohesionTWS(std::vector<PointRecv> v); //RdpRecPointCohesionTWS
void WriteCohesionTAS(std::vector<PointRecv> v); //RdpRecPointCohesionTAS
void WriteTempTracks(std::vector<Temp_track> v); //RdpRecTempTrack(临时航迹)
void WriteTrustTracks(std::vector<Trust_Track> v); //RdpRecTrustTrack(可靠航迹)
void WriteAssoInfos(std::vector<Asso_Info> v); //RdpRecAssoInfo(点航关联信息)
//遗留接口:同步写入记录实体(在调用线程内直接写库,会阻塞;仅供测试/特殊场景使用)。
//T 为记录实体类型,须在 rdp_record.cpp 中显式实例化。
template <typename T> void Write(const T& v);
template <typename T> void Write(const std::vector<T>& v);
}
#ifdef RDP_RECORD_ENABLE
//按记录表批量入队(参数为源结构体 vector,宏内部 std::move;调用后 vector 为空)
#define RDP_RECORD_POINTS(v) ::rdp_record::WritePoints(std::move(v))
#define RDP_RECORD_COHESION_TWS(v) ::rdp_record::WriteCohesionTWS(std::move(v))
#define RDP_RECORD_COHESION_TAS(v) ::rdp_record::WriteCohesionTAS(std::move(v))
#define RDP_RECORD_TEMP_TRACKS(v) ::rdp_record::WriteTempTracks(std::move(v))
#define RDP_RECORD_TRUST_TRACKS(v) ::rdp_record::WriteTrustTracks(std::move(v))
#define RDP_RECORD_ASSO_INFOS(v) ::rdp_record::WriteAssoInfos(std::move(v))
//遗留宏:记录实体单条/批量同步写(会阻塞调用线程;新记录点请使用上面的批量入队接口)
#define RDP_RECORD(...) ::rdp_record::Write(__VA_ARGS__)
#define RDP_RECORD_BATCH(...) ::rdp_record::Write(__VA_ARGS__)
#else
//记录功能关闭:记录模块不参与编译,所有宏展开为空(实参不求值)
#define RDP_RECORD_POINTS(v) ((void)0)
#define RDP_RECORD_COHESION_TWS(v) ((void)0)
#define RDP_RECORD_COHESION_TAS(v) ((void)0)
#define RDP_RECORD_TEMP_TRACKS(v) ((void)0)
#define RDP_RECORD_TRUST_TRACKS(v) ((void)0)
#define RDP_RECORD_ASSO_INFOS(v) ((void)0)
#define RDP_RECORD(...) ((void)0)
#define RDP_RECORD_BATCH(...) ((void)0)
namespace rdp_record
{
inline bool Init() { return false; }
inline void Shutdown() {}
inline std::string db_path() { return std::string(); }
inline Stats stats() { return Stats(); }
template <typename T> inline void Write(const T&) {}
template <typename T> inline void Write(const std::vector<T>&) {}
}
#endif
#endif // RDP_RECORD_H
+496
View File
@@ -0,0 +1,496 @@
#ifndef RDP_RECORD_ENTITY_H
#define RDP_RECORD_ENTITY_H
#pragma once
/*******************************数据记录实体(=数据库表)定义区*********************************/
// 使用说明(完整配方见 docs/data_record_debug.md):
// 1) 新增一张记录表:在本文件「记录实体定义区」添加结构体,并在其后紧跟一行
// RDP_RECORD_DECLARE(结构体名);
// 再到 rdp_record.cpp 的「记录实体注册区」添加两行
// YLT_REFL(结构体名, 字段1, 字段2, ...); // 顺序必须与结构体声明顺序一致
// RDP_RECORD_DEFINE(结构体名);
// 最后在需要记录的位置写 RDP_RECORD(结构体名{0, 值1, 值2, ...}); 或 RDP_RECORD_BATCH(向量);
// 建表无需任何代码:首次写入该实体时自动 CREATE TABLE IF NOT EXISTS;
// 2) 命名与建表约定:结构体定义在全局命名空间(勿放入 namespace),结构体名即数据库表名
// (ormpp 取「短结构体名」),建议以 RdpRec 开头;首个字段固定为 int64_t id(自增主键,
// 写入时固定填 0,由 SQLite 分配);
// 3) 字段类型仅限标量:int / int64_t / float / double / bool(SQLite 侧映射 INTEGER / INTEGER /
// FLOAT / DOUBLE / INTEGER)。禁止使用数组(含 float speed_dim[129]、char[N] 等)——ormpp 对
// C 数组按 C 字符串语义(strlen)绑定,会读越界或写入错误数据;大批量数组请另建「元素展开表」
// 或改用 blob 字段;
// 4) 表结构变更(增删字段)不会迁移已存在的旧表:本模块每次运行生成新的 db 文件,天然规避。
#include "rdp_record.h"
#include <cstdint>
#include <vector>
#include <iostream>
#include <sstream>
#include <string>
#include <type_traits>
#include <cstddef>
#include "struct.h"
//记录实体的写入函数实例化声明宏:声明「该实体的 Write 在 rdp_record.cpp 中显式实例化」,
//阻止 C++17 调用方隐式实例化(本模块的 ormpp 实现以 C++20 编译,见 docs/data_record_debug.md)
#define RDP_RECORD_DECLARE(Entity) \
extern template void rdp_record::Write<Entity>(const Entity&); \
extern template void rdp_record::Write<Entity>(const std::vector<Entity>&)
// 基本类型元素转字符串
template <typename T>
std::string elementToString(const T& value);
// 数组版本
template <typename T, std::size_t N>
std::string arrayToString(const T (&arr)[N]);
// 非数组元素:直接转字符串
template <typename T>
std::string arrayToString(const T& value);
// ============================ 记录实体定义区(在此新增结构体) ============================
// 输入点迹 —— 输入的原始点迹数据
struct RdpRecPoint
{
int64_t id; //自增主键(写入时固定填 0)
long long point_id; //点迹ID
long long frame_id; //帧ID
long long circle_id; //圈ID
double range; //径向距离
double azimuth; //方位 (弧度)
double velocity ; //速度
double amplitude ; //幅度
double height; //高度
double SNR; //信噪比
double RCS; //目标RCS
float encoder_value; //码盘值
float amp_asv_point; //归一化幅度dB
float prn_fftn_point; //点迹速度维FFT点数
long long CPI_time; //CPI时间
long long GNSS_time; //GNSS时间
int use_flag; //点迹使用标志 1使用 0未使用
int azimuth_beam_index; //方位波位号
int pitch_beam_index; //俯仰波位号
int PRF_index; //PRF号
int freq_index; //频点
int point_type; //TAS 1 TWS0
int point_section_asso; //点迹区
RdpRecPoint(){
id = 0;
}
RdpRecPoint(PointRecv& p){
id = 0;
point_id = p.point_id;
frame_id = p.frame_id;
circle_id = p.circle_id;
range = p.range;
azimuth = p.azimuth;
velocity = p.velocity;
amplitude = p.amplitude;
height = p.height;
SNR = p.SNR;
RCS = p.RCS;
encoder_value = p.encoder_value;
amp_asv_point = p.amp_asv_point;
prn_fftn_point = p.prn_fftn_point;
CPI_time = p.CPI_time;
GNSS_time = p.GNSS_time;
use_flag = p.use_flag;
azimuth_beam_index = p.azimuth_beam_index;
pitch_beam_index = p.pitch_beam_index;
PRF_index = p.PRF_index;
freq_index = p.freq_index;
point_type = p.point_type;
point_section_asso = p.point_section_asso;
}
};
RDP_RECORD_DECLARE(RdpRecPoint);
// TWS凝聚后点迹 —— 凝聚后的点迹数据
struct RdpRecPointCohesionTWS
{
int64_t id; //自增主键(写入时固定填 0)
long long point_id; //点迹ID
long long frame_id; //帧ID
long long circle_id; //圈ID
double range; //径向距离
double azimuth; //方位 (弧度)
double velocity ; //速度
double amplitude ; //幅度
double height; //高度
double SNR; //信噪比
double RCS; //目标RCS
float encoder_value; //码盘值
float amp_asv_point; //归一化幅度dB
float prn_fftn_point; //点迹速度维FFT点数
long long CPI_time; //CPI时间
long long GNSS_time; //GNSS时间
int use_flag; //点迹使用标志 1使用 0未使用
int azimuth_beam_index; //方位波位号
int pitch_beam_index; //俯仰波位号
int PRF_index; //PRF号
int freq_index; //频点
int point_type; //TAS 1 TWS0
int point_section_asso; //点迹区
RdpRecPointCohesionTWS(){
id = 0;
}
RdpRecPointCohesionTWS(PointRecv& p){
id = 0;
point_id = p.point_id;
frame_id = p.frame_id;
circle_id = p.circle_id;
range = p.range;
azimuth = p.azimuth;
velocity = p.velocity;
amplitude = p.amplitude;
height = p.height;
SNR = p.SNR;
RCS = p.RCS;
encoder_value = p.encoder_value;
amp_asv_point = p.amp_asv_point;
prn_fftn_point = p.prn_fftn_point;
CPI_time = p.CPI_time;
GNSS_time = p.GNSS_time;
use_flag = p.use_flag;
azimuth_beam_index = p.azimuth_beam_index;
pitch_beam_index = p.pitch_beam_index;
PRF_index = p.PRF_index;
freq_index = p.freq_index;
point_type = p.point_type;
point_section_asso = p.point_section_asso;
}
};
RDP_RECORD_DECLARE(RdpRecPointCohesionTWS);
// TAS凝聚后点迹 —— 凝聚后的点迹数据
struct RdpRecPointCohesionTAS
{
int64_t id; //自增主键(写入时固定填 0)
long long point_id; //点迹ID
long long frame_id; //帧ID
long long circle_id; //圈ID
double range; //径向距离
double azimuth; //方位 (弧度)
double velocity ; //速度
double amplitude ; //幅度
double height; //高度
double SNR; //信噪比
double RCS; //目标RCS
float encoder_value; //码盘值
float amp_asv_point; //归一化幅度dB
float prn_fftn_point; //点迹速度维FFT点数
long long CPI_time; //CPI时间
long long GNSS_time; //GNSS时间
int use_flag; //点迹使用标志 1使用 0未使用
int azimuth_beam_index; //方位波位号
int pitch_beam_index; //俯仰波位号
int PRF_index; //PRF号
int freq_index; //频点
int point_type; //TAS 1 TWS0
int point_section_asso; //点迹区
RdpRecPointCohesionTAS(){
id = 0;
}
RdpRecPointCohesionTAS(PointRecv& p){
id = 0;
point_id = p.point_id;
frame_id = p.frame_id;
circle_id = p.circle_id;
range = p.range;
azimuth = p.azimuth;
velocity = p.velocity;
amplitude = p.amplitude;
height = p.height;
SNR = p.SNR;
RCS = p.RCS;
encoder_value = p.encoder_value;
amp_asv_point = p.amp_asv_point;
prn_fftn_point = p.prn_fftn_point;
CPI_time = p.CPI_time;
GNSS_time = p.GNSS_time;
use_flag = p.use_flag;
azimuth_beam_index = p.azimuth_beam_index;
pitch_beam_index = p.pitch_beam_index;
PRF_index = p.PRF_index;
freq_index = p.freq_index;
point_type = p.point_type;
point_section_asso = p.point_section_asso;
}
};
RDP_RECORD_DECLARE(RdpRecPointCohesionTAS);
// 临时航迹
struct RdpRecTempTrack
{
int64_t id; //自增主键(写入时固定填 0)
long long temp_track_id; //临时航迹ID
long long point_id; //关联上的点迹ID
long long circle_id; //处理圈数ID
std::string X; //状态
std::string P; //协方差
double r; //距离
double azi; //方位
double vr; //径向速度
double height; //高度
double SNR; //信噪比
double RCS; //RCS
double amplitude; //幅度
double quality; //航迹质量
long long CPI_time; //时间戳
long long GNSS_time; //GNSS时间戳
double d; //关联上的点的d(马氏距离)
int buff_round; //已缓存圈数
int temp_track_section_idx; //临时航迹区号 与点迹区的划分相同
int asso_flag; //关联标志 已关联:1 未关联:0
int pitch_num; // 俯仰波位号
RdpRecTempTrack(){
id = 0;
}
RdpRecTempTrack(Temp_track& p){
id = 0;
temp_track_id = p.temp_track_id;
point_id = p.point_id;
circle_id = p.circle_id;
X = arrayToString(p.X);
P = arrayToString(p.P);
r = p.r;
azi = p.azi;
vr = p.vr;
height = p.height;
RCS = p.RCS;
SNR = p.SNR;
amplitude = p.amplitude;
quality = p.quality;
CPI_time = p.CPI_time;
GNSS_time = p.GNSS_time;
d = p.d;
buff_round = p.buff_round;
temp_track_section_idx = p.temp_track_section_idx;
asso_flag = p.asso_flag;
pitch_num = p.pitch_num;
}
};
RDP_RECORD_DECLARE(RdpRecTempTrack);
// 可靠航迹
struct RdpRecTrustTrack
{
int64_t id; //自增主键(写入时固定填 0)
long long track_id; //航迹ID
long long point_id; //关联上的点迹ID
long long circle_id; //处理圈数ID
int Track_Index; //航迹号
double Range; //距离
double Azimuth; //方位
double Elevation; //俯仰
double Velocity; //航向速度
double Amplitude; //幅度
double Height; //高度
double Quality; //航迹质量
long long T_track; //航迹时间
long long GNSS_time; //GNSS时间
long long Unupdated_time; //航迹未更新时间
int Track_Sum; //航迹总数
int Point_Index; //该航迹上的第几个点
int Point_sum; //本条航迹更新点的数目
int Valid_point_sum; //本条航迹有效点的数目
int Track_Mode; //跟踪模式
int Track_Update_Flag; //航迹更新标志 1已更新/0未更新
int Extrapolate_round; //航迹持续外推时间
int point_flag; //是否实点 1实点 0外推
int approach_flag; //接近标志 1接近 0远离
int manual_delete_flag; //手动航迹删除
int manual_tracking_flag; //手动TAS
int associate_point_number; //关联的点数
int primary_target_flag; //重点目标标记
int Target_Type; //目标类型 搜索目标 低空目标 地面目标
int Track_section_idx; //航迹区号
double range_point; //关联上的点信息(距离、方位、俯仰)
double azi_point;
double elev_point;
double vr_point;
double prf_point;
int point_type; //TWS TAS
double snr_point; //信噪比
float amp_asv_point; //关联上的点迹信息(归一化幅度)
float prn_fftn_point; //点迹速度维FFT点数
double RCS;
int pitch_num; // 俯仰波位号
std::string X; //IMM
std::string P;
std::string X1;
std::string X2;
std::string X3;
std::string P2;
std::string P3;
std::string u;
RdpRecTrustTrack(){
id = 0;
}
RdpRecTrustTrack(Trust_Track& p){
id = 0;
track_id = p.track_id;
point_id = p.point_id;
circle_id = p.circle_id;
Track_Index = p.Track_Index;
Range = p.Range;
Azimuth = p.Azimuth;
Elevation = p.Elevation;
Velocity = p.Velocity;
Amplitude = p.Amplitude;
Height = p.Height;
Quality = p.Quality;
T_track = p.T_track;
GNSS_time = p.GNSS_time;
Track_Sum = p.Track_Sum;
Unupdated_time = p.Unupdated_time;
Point_Index = p.Point_Index;
Point_sum = p.Point_sum;
Valid_point_sum = p.Valid_point_sum;
Track_Mode = p.Track_Mode;
Track_Update_Flag = p.Track_Update_Flag;
Extrapolate_round = p.Extrapolate_round;
point_flag = p.point_flag;
approach_flag = p.approach_flag;
manual_delete_flag = p.manual_delete_flag;
manual_tracking_flag = p.manual_tracking_flag;
associate_point_number = p.associate_point_number;
primary_target_flag = p.primary_target_flag;
Target_Type = p.Target_Type; //目标类型 搜索目标 低空目标 地面目标
Track_section_idx = p.Track_section_idx; //航迹区号
range_point = p.range_point; //关联上的点信息(距离、方位、俯仰)
azi_point = p.azi_point;
elev_point = p.elev_point;
vr_point = p.vr_point;
prf_point = p.prf_point;
point_type = p.point_type; //TWS TAS
snr_point = p.snr_point; //信噪比
amp_asv_point = p.amp_asv_point; //关联上的点迹信息(归一化幅度)
prn_fftn_point = p.prn_fftn_point; //点迹速度维FFT点数
RCS = p.RCS;
pitch_num = p.pitch_num; // 俯仰波位号
X = arrayToString(p.X); //IMM
P = arrayToString(p.P);
X1 = arrayToString(p.X1);
X2 = arrayToString(p.X2);
X3 = arrayToString(p.X3);
P2 = arrayToString(p.P2);
P3 = arrayToString(p.P3);
u = arrayToString(p.u);
}
};
RDP_RECORD_DECLARE(RdpRecTrustTrack);
// 关联信息
struct RdpRecAssoInfo
{
int64_t id; //自增主键(写入时固定填 0)
long long point_id;
long long track_id;
long long circle_id;
double d1;
double d2;
double d3;
double d_min;
int point_index;
int track_index;
double delta_T;
double delta_h;
double R;
double VT;
bool tas_flag; //TAS关联标志 1是 0否
bool thd_d;
bool thd_h;
bool thd_r;
bool min_d_flag;
RdpRecAssoInfo(){
id = 0;
}
RdpRecAssoInfo(Asso_Info& p){
id = 0;
point_id = p.point_id;
track_id = p.track_id;
circle_id = p.circle_id;
d1 = p.d1;
d2 = p.d2;
d3 = p.d3;
d_min = p.d_min;
point_index = p.point_index;
track_index = p.track_index;
delta_T = p.delta_T;
delta_h = p.delta_h;
R = p.R;
VT = p.VT;
tas_flag = p.tas_flag;
thd_d = p.thd_d;
thd_h = p.thd_h;
thd_r = p.thd_r;
min_d_flag = p.min_d_flag;
}
};
RDP_RECORD_DECLARE(RdpRecAssoInfo);
// ============================ 数组转换函数 ============================
// 基本类型元素转字符串
template <typename T>
std::string elementToString(const T& value) {
std::ostringstream oss;
oss << value;
return oss.str();
}
// 前置声明数组版本
template <typename T, std::size_t N>
std::string arrayToString(const T (&arr)[N]);
// 非数组元素:直接转字符串
template <typename T>
std::string arrayToString(const T& value) {
return elementToString(value);
}
// 数组版本
template <typename T, std::size_t N>
std::string arrayToString(const T (&arr)[N]) {
std::string result;
// 如果 T 仍然是数组,说明当前不是最内层,用分号;
// 否则当前就是最内层,用逗号。
const char sep = std::is_array<T>::value ? ';' : ',';
for (std::size_t i = 0; i < N; ++i) {
if (i > 0) {
result += sep;
}
result += arrayToString(arr[i]);
}
return result;
}
#endif // RDP_RECORD_ENTITY_H
+180 -35
View File
@@ -1,4 +1,4 @@
#ifndef STRUCT_H
#ifndef STRUCT_H
#define STRUCT_H
#pragma once
@@ -9,25 +9,29 @@ using namespace std;
// 点迹数据结构
struct PointRecv
{
double Range; //径向距离
double Azimuth; //方位 (弧度)
double Velocity ; //速度
double Amplitude ; //幅度
double Height; //高度
double snr; //信噪比
long long point_id; //点迹ID
long long frame_id; //帧ID
long long circle_id; //圈ID
double range; //径向距离
double azimuth; //方位 (弧度)
double velocity ; //速度
double amplitude ; //幅度
double height; //高度
double SNR; //信噪比
double RCS; //目标RCS
long long CPI_Time; //CPI时间
long long GNSS_time; //GNSS时间
int Point_index; //点迹号 1~50
int Point_Sum; //点迹总数
int Use_Flag; //点迹使用标志 1使用 0未使用
int beam_index; //波位号
float encoder_value; //码盘值
float amp_asv_point; //归一化幅度dB
float prn_fftn_point; //点迹速度维FFT点数
long long CPI_time; //CPI时间
long long GNSS_time; //GNSS时间
int use_flag; //点迹使用标志 1使用 0未使用
int azimuth_beam_index; //方位波位号
int pitch_beam_index; //俯仰波位号
int PRF_index; //PRF号
int Freq_index; //频点
int track_mode; //TAS 1 TWS0
int freq_index; //频点
int point_type; //TAS 1 TWS0
int point_section_asso; //点迹区
int pitch_num; // 俯仰波位号
float speed_dim[129]; // 目标的十字星数据,目标点速度维129个点
float range_dim[8]; // 目标的十字星数据,目标点距离维8个点
};
@@ -35,16 +39,31 @@ struct PointRecv
// 可靠航迹数据结构
struct Trust_Track
{
int Track_Index; //航迹号
long long track_id; //航迹ID
long long point_id; //关联上的点迹ID
long long circle_id; //处理圈数ID
int Track_Index; //航迹号
int Track_fingerprint; //航迹指纹
double Range; //距离
double Azimuth; //方位
double Elevation; //俯仰
double Velocity; //航向速度
double Amplitude; //幅度
double Height; //高度
double RCS; //RCS
double Quality; //航迹质量
long long T_track; //航迹时间
long long GNSS_time; //GNSS时间
long long T_track; //航迹时间(CPI时间)
long long GNSS_time; //GNSS时间
long long Unupdated_time; //航迹未更新时间
int Track_Sum; //航迹总数
int Point_Index; //该航迹上的第几个点
int Point_sum; //本条航迹更新点的数目
int Valid_point_sum; //本条航迹有效点的数目
int associate_point_number; //关联的点数
int Track_Mode; //跟踪模式
int Track_Update_Flag; //航迹更新标志 1已更新/0未更新
@@ -54,8 +73,8 @@ struct Trust_Track
int approach_flag; //接近标志 1接近 0远离
int manual_delete_flag; //手动航迹删除
int manual_tracking_flag; //手动TAS
int associate_point_number; //关联的点数
int manual_tracking_flag; //手动TAS
int primary_target_flag; //重点目标标记
int Target_Type; //目标类型 搜索目标 低空目标 地面目标
@@ -69,9 +88,9 @@ struct Trust_Track
double vr_point;
double prf_point;
int point_type; //TWS TAS
double snr_point; //信噪比
double RCS;
double snr_point; //信噪比
float amp_asv_point; //关联上的点迹信息(归一化幅度)
float prn_fftn_point; //点迹速度维FFT点数
int pitch_num; // 俯仰波位号
float speed_dim[129]; // 目标的十字星数据,目标点速度维129个点
@@ -86,32 +105,35 @@ struct Trust_Track
double P1[6][6];
double P2[6][6];
double P3[6][6];
double u[3];
double u[3];
};
//临时航迹结构体
struct Temp_track
{
long long temp_track_id; //临时航迹ID
long long point_id; //关联上的点迹ID
long long circle_id; //处理圈数ID
double X[4]; //状态
double X[4]; //状态
double P[4][4]; //协方差
double r; //距离
double azi; //方位
double vr; //径向速度
double height; //高度
double snr; //信噪比
double SNR; //信噪比
double RCS; //RCS
double Amp; //幅度
long long T; //时间戳
long long GNSS_time; //GNSS时间戳
double amplitude; //幅度
double quality; //航迹质量
long long CPI_time; //时间戳
long long GNSS_time; //GNSS时间戳
double d; //关联上的点的d
double d; //关联上的点的d(马氏距离)
int buff_round; //已缓存圈数
int Temp_track_section_idx; //临时航迹区号 与点迹区的划分相同
int temp_track_section_idx; //临时航迹区号 与点迹区的划分相同
int asso_flag; //关联标志 已关联:1 未关联:0
@@ -120,12 +142,40 @@ struct Temp_track
float range_dim[8]; // 目标的十字星数据,目标点距离维8个点
};
//存储关联信息
struct Asso_Info
{
long long point_id;
long long track_id;
long long circle_id;
double d1;
double d2;
double d3;
double d_min;
int point_index;
int track_index;
double delta_T;
double delta_h;
double R;
double VT;
bool tas_flag; //TAS关联标志 1是 0否
bool thd_d;
bool thd_h;
bool thd_r;
bool min_d_flag;
};
// TAS跟踪目标结构体
struct Tracking_Target
{
int Index; //目标批号
int empty_flag; //是否为空标志位 1非空 0空
long long last_track_time; //最新跟踪时间
};
//引导跟踪目标结构体
@@ -140,4 +190,99 @@ struct Direct_Tracking_Target
};
// 系统工作参数结构体
struct RDP_parameter
{
// ******************* 雷达工作参数 ************************//
int radar_work_mode; //工作模式
int scan_mode; //扫描模式 0=相扫模式 1=机扫模式
float freq_0_GHz; //频点0频率
float radar_height; //雷达平台高度 (m)
float radar_system_delay; //系统延迟 (s)
float data_rate_tws; //TWS模式数据率
float data_rate_tas; //TAS模式数据率
float data_rate_track_only; //纯跟踪模式数据率
// ******************* 点迹凝聚参数 ************************//
float point_cohesion_range; //距离凝聚范围
float point_cohesion_velocity; //速度凝聚范围
float point_cohesion_azimuth; //方位凝聚范围
float point_cohesion_range_min; //最小凝聚距离
float point_cohesion_range_max; //最大凝聚距离
// ******************* 航迹关联参数 ************************//
float sigma_range; //距离量测精度
float sigma_azimuth; //方位量测精度
float sigma_pitch; //俯仰量测精度
float sigma_velocity; //速度量测精度
float associate_threshold; //关联门限
int extrapolate_max; //最大外推点数
int extrapolate_max_tas; //TAS最大外推点数
int height_smooth_win; //高度平滑窗长
// ******************* 航迹起始参数 ************************//
float track_init_threshold; //起航门限
float track_init_angle; //起航夹角
int track_init_point_num_min; //最少起批点数
float track_init_quality_L3; //3点起批质量门限
float track_init_quality_L4; //4点起批质量门限
float track_init_quality_L5; //5点起批质量门限
float track_init_quality_L6; //6点起批质量门限
float track_init_quality_L7; //7点起批质量门限
float track_init_velocity_min; //航迹起始最小速度
float track_init_velocity_max; //航迹起始最大速度
// ******************* 航迹消亡参数 ************************//
int track_die_round; //航迹消亡圈数
int track_die_round_pramary; //重点航迹消亡圈数
int track_die_round_tas; //TAS航迹消亡圈数
float track_die_quality; //航迹消批质量条件
float track_die_quality_pramary; //重点航迹消批质量条件
// ******************* 航迹质量管理参数 ************************//
float track_quality_max; //航迹质量最大值
float track_quality_min; //航迹质量最小值
float track_quality_d_weight; //马氏距离权重
float track_quality_snr_weight; //信噪比权重
float track_quality_len_weight; //航迹长度权重
float track_quality_iter_coeff; //航迹质量迭代系数
float track_quality_decrease_coeff_o2; //航迹质量下降系数2次项
float track_quality_decrease_coeff_o1; //航迹质量下降系数1次项
// ******************* 航迹批号管理 ************************//
int track_index_max; //最大航迹批号
// ******************* 波束调度参数 ************************//
int default_track_beam_type; //默认跟踪波束类型
int tas_track_num_max; //最大同时跟踪目标数
// ******************* singer模型机动参数 ************************//
float model_1_Q_fast; //模型1
float model_1_Q_slow; //模型1
float model_2_Q_fast; //模型2
float model_2_Q_slow; //模型2
float model_3_Q_fast; //模型3
float model_3_Q_slow; //模型3
// ******************* 航迹起始屏蔽区 ************************//
int track_init_prohibite_area_num; //航迹起始屏蔽区数
float track_init_prohibite_area_range_min[30]; //最小距离 (m)
float track_init_prohibite_area_range_max[30]; //最大距离 (m)
float track_init_prohibite_area_azimuth_min[30]; //最小方位角 (度)
float track_init_prohibite_area_azimuth_max[30]; //最大方位角 (度)
// ******************* 禁止TAS跟踪区 ************************//
int tas_prohibite_area_num; //禁止TAS的区域数
float tas_prohibite_area_range_min[30]; //最小距离 (m)
float tas_prohibite_area_range_max[30]; //最大距离 (m)
float tas_prohibite_area_azimuth_min[30]; //最小方位角 (度)
float tas_prohibite_area_azimuth_max[30]; //最大方位角 (度)
};
// ******************* 全局工作参数对象 ************************//
// 所有处理模块直接访问该全局对象,不再通过函数参数逐层传递。
// 定义在 data_process.cpp;由 Data_Process::parameters_reload() 在配置文件完整校验通过后整体赋值
// (失败时保持原值)。处理期间只读;配置重载应与处理互斥,宿主按单线程接口使用(见 README BUG-03)。
extern RDP_parameter rdp_parameter;
#endif // STRUCT_H
+184 -157
View File
@@ -1,20 +1,17 @@
#include "tas_ctrl.h"
#include "tas_ctrl.h"
#include "coor_trans.h"
#include "rdp_log.h"
#include <cmath>
#include <cstring>
#include <vector>
#include <iomanip>
#include <sstream>
using namespace std;
// parameters.h 中定义了 DATA_RATE_TAS 宏(相扫0.3/机扫0.0625),
// 而本模块的波束门控必须使用 RadarPara 结构体成员 Work_Parameter.DATA_RATE_TAS
// (由dll调用者经 track_process_parameters_initial 输入),故取消该宏定义,
// 否则 Work_Parameter.DATA_RATE_TAS 会被预处理器展开为非法常量表达式。
#undef DATA_RATE_TAS
TAS_Ctrl::TAS_Ctrl()
{
memset(tas_target_queue,0,TAS_QUEUE_LENGTH*sizeof(Tracking_Target));
memset(tas_target_queue,0,RDP_TAS_QUEUE_CAPACITY*sizeof(Tracking_Target));
tas_target_num=0;
}
@@ -30,48 +27,46 @@ void TAS_Ctrl::tas_ctrl_process(std::vector<Trust_Track> *trust_track
struct TrackingBeam *Tracking_beam,
struct Track Trust_Track_Output[][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter,
long long latest_timestamp)
{
if (Trust_track_num_Output == 0) return;
*Trust_track_num_Output = 0;
tas_target_add(trust_track, Trust_Track_Output, Trust_track_num_Output,Work_Parameter);
tas_target_del(trust_track, Trust_Track_Output, Trust_track_num_Output,Work_Parameter);
tas_beam_output(trust_track,Tracking_beam,Work_Parameter,latest_timestamp);
tas_target_add(trust_track, Trust_Track_Output, Trust_track_num_Output);
tas_target_del(trust_track, Trust_Track_Output, Trust_track_num_Output);
tas_beam_output(trust_track,Tracking_beam,latest_timestamp);
//跟踪队列移位
struct Tracking_Target tas_target_tmp;
memcpy(&tas_target_tmp, &tas_target_queue[TAS_QUEUE_LENGTH-1], sizeof(Tracking_Target));
for (int i=TAS_QUEUE_LENGTH-1;i>0;i--)
{
memcpy(&tas_target_queue[i],&tas_target_queue[i-1],sizeof(Tracking_Target));
}
memcpy(&tas_target_queue[0], &tas_target_tmp, sizeof(Tracking_Target));
// //跟踪队列移位
// struct Tracking_Target tas_target_tmp;
// memcpy(&tas_target_tmp, &tas_target_queue[RDP_TAS_QUEUE_CAPACITY-1], sizeof(Tracking_Target));
// for (int i=RDP_TAS_QUEUE_CAPACITY-1;i>0;i--)
// {
// memcpy(&tas_target_queue[i],&tas_target_queue[i-1],sizeof(Tracking_Target));
// }
// memcpy(&tas_target_queue[0], &tas_target_tmp, sizeof(Tracking_Target));
};
void TAS_Ctrl::tas_target_add(std::vector<Trust_Track> *trust_track,
struct Track Trust_Track_Output[][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter)
int *Trust_track_num_Output)
{
for (int i=0;i<trust_track->size();i++)
for (size_t i=0;i<trust_track->size();i++)
{
double r=sqrt(pow((*trust_track)[i].X[0],2)+pow((*trust_track)[i].X[3],2));
double v=sqrt(pow((*trust_track)[i].X[1],2)+pow((*trust_track)[i].X[4],2));
double h=(*trust_track)[i].Height+Work_Parameter.Height;
double h=(*trust_track)[i].Height+rdp_parameter.radar_height;
double azi = atan2((*trust_track)[i].X[3],(*trust_track)[i].X[0]);
if(azi<0)
azi=azi+2*PI;
azi=azi/PI*180;
//进入跟踪的条件: 1.手动跟踪的目标 或 满足速度、距离、高度条件满足 关联点数大于2 2.不在禁止跟踪区域
if( ( (*trust_track)[i].manual_tracking_flag == 1)//|| (tas_auto_start( v, r, h, Work_Parameter)==1 && (*trust_track)[i][k].associate_point_number>=2)
&& tas_target_num < MAX_TAS_NUM
if( ( (*trust_track)[i].manual_tracking_flag == 1)//|| (tas_auto_start( v, r, h)==1 && (*trust_track)[i][k].associate_point_number>=2)
&& tas_target_num < rdp_parameter.tas_track_num_max
&& (*trust_track)[i].Track_Mode == 0
&& tas_prohibite_area( r, azi, v, h, Work_Parameter) == 0)
&& tas_prohibite_area( r, azi, v, h) == 0)
{
for(int j=0;j<TAS_QUEUE_LENGTH;j++)
for(int j=0;j<RDP_TAS_QUEUE_CAPACITY;j++)
{
if(tas_target_queue[j].empty_flag==0)// && j%2==0)
{
@@ -81,6 +76,7 @@ void TAS_Ctrl::tas_target_add(std::vector<Trust_Track> *trust_track,
//插入跟踪队列
tas_target_queue[j].Index=(*trust_track)[i].Track_Index;
tas_target_queue[j].empty_flag=1;
tas_target_queue[j].last_track_time=(*trust_track)[i].T_track;
//跟踪目标数目加一
tas_target_num=tas_target_num+1;
@@ -90,22 +86,80 @@ void TAS_Ctrl::tas_target_add(std::vector<Trust_Track> *trust_track,
//目标跟踪状态发生改变 对外输出航迹更新信息
*Trust_track_num_Output= *Trust_track_num_Output+1;
double r_output,azmi_output;
coor_trans Coor_trans;
Coor_trans.cart2polar((*trust_track)[i].X[0],(*trust_track)[i].X[3],&r_output,&azmi_output);
Trust_Track_Output[*Trust_track_num_Output-1][0].Point_Sum=1;
Trust_Track_Output[*Trust_track_num_Output-1][0].Range=r_output;
Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth=azmi_output/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Index=(*trust_track)[i].Track_Index; //航迹号
Trust_Track_Output[*Trust_track_num_Output-1][0].Range_V=
sqrt((*trust_track)[i].X[1]*(*trust_track)[i].X[1]+(*trust_track)[i].X[4]*(*trust_track)[i].X[4]);
Trust_Track_Output[*Trust_track_num_Output-1][0].Amplitude=(*trust_track)[i].Amplitude;
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Point=(*trust_track)[i].point_flag;
double x,y;
x=(*trust_track)[i].X[0]+(*trust_track)[i].X[1]*rdp_parameter.radar_system_delay;
y=(*trust_track)[i].X[3]+(*trust_track)[i].X[4]*rdp_parameter.radar_system_delay;
coor_trans Coor_trans;
double r, azi;
Coor_trans.cart2polar(x, y, &r, &azi);
Trust_Track_Output[*Trust_track_num_Output-1][0].Range=static_cast<float>(r);
Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth=static_cast<float>(azi/PI*180);
if((*trust_track)[i].Height<r)
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = static_cast<float>(asin((*trust_track)[i].Height/r)/PI*180);
else
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = static_cast<float>((*trust_track)[i].elev_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Index=(*trust_track)[i].Track_Index;
Trust_Track_Output[*Trust_track_num_Output-1][0].Range_V = static_cast<float>(sqrt((*trust_track)[i].X[1]*(*trust_track)[i].X[1]+(*trust_track)[i].X[4]*(*trust_track)[i].X[4]));
Trust_Track_Output[*Trust_track_num_Output-1][0].Amplitude = static_cast<float>((*trust_track)[i].Amplitude);
Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Mode=(*trust_track)[i].Track_Mode;
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation=asin((*trust_track)[i].Height/r_output)/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=(*trust_track)[i].Height+Work_Parameter.Height;
Trust_Track_Output[*Trust_track_num_Output-1][0].Target_Type=(*trust_track)[i].Target_Type;
Trust_Track_Output[*Trust_track_num_Output-1][0].track_snr = (*trust_track)[i].snr_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].track_time=static_cast<float>((*trust_track)[i].T_track/1000.0);
Trust_Track_Output[*Trust_track_num_Output-1][0].GNSS_time=(*trust_track)[i].GNSS_time;
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Point = (*trust_track)[i].point_flag;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=static_cast<float>((*trust_track)[i].Height+rdp_parameter.radar_height);
Trust_Track_Output[*Trust_track_num_Output-1][0].Quality = static_cast<float>((*trust_track)[i].Quality * rdp_parameter.track_quality_max);
double Direction_Angle;
Direction_Angle=atan2((*trust_track)[i].X[4], (*trust_track)[i].X[1]);
if(Direction_Angle<0){Direction_Angle=Direction_Angle+2*PI;}
Trust_Track_Output[*Trust_track_num_Output-1][0].Direction_Angle=static_cast<float>(Direction_Angle/PI*180);
//关联点信息
Trust_Track_Output[*Trust_track_num_Output-1][0].range_point=static_cast<float>((*trust_track)[i].range_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].azi_point=static_cast<float>((*trust_track)[i].azi_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].elev_point=static_cast<float>((*trust_track)[i].elev_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].vr_point=static_cast<float>((*trust_track)[i].vr_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].point_type=0;
Trust_Track_Output[*Trust_track_num_Output-1][0].prf_point = static_cast<float>((*trust_track)[i].prf_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].track_snr = static_cast<float>((*trust_track)[i].snr_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].amp_asv_point = static_cast<float>((*trust_track)[i].amp_asv_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].prn_fftn_point = static_cast<float>((*trust_track)[i].prn_fftn_point);
//输出平滑后的高度
Trust_Track_Output[*Trust_track_num_Output-1][0].z = static_cast<float>((*trust_track)[i].Height+rdp_parameter.radar_height);
Trust_Track_Output[*Trust_track_num_Output-1][0].x=static_cast<float>((*trust_track)[i].X[0]);
Trust_Track_Output[*Trust_track_num_Output-1][0].y=static_cast<float>((*trust_track)[i].X[3]);
Trust_Track_Output[*Trust_track_num_Output-1][0].v_x=static_cast<float>((*trust_track)[i].X[1]);
Trust_Track_Output[*Trust_track_num_Output-1][0].v_y=static_cast<float>((*trust_track)[i].X[4]);
if(acos((-x * cos(Direction_Angle) - y * sin(Direction_Angle)) / r ) < PI / 2)
{
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=1;
// if(r*abs(sin(azi - Direction_Angle))<500)
// {
// Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=1;
// }
// else
// {
// Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=0;
// }
}
else
{
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=0;
}
Trust_Track_Output[*Trust_track_num_Output-1][0].track_rcs = static_cast<float>((*trust_track)[i].RCS);
Trust_Track_Output[*Trust_track_num_Output-1][0].pitch_num = (*trust_track)[i].pitch_num;
std::memcpy(Trust_Track_Output[*Trust_track_num_Output-1][0].speed_dim, (*trust_track)[i].speed_dim, sizeof(Trust_Track_Output[*Trust_track_num_Output-1][0].speed_dim));
std::memcpy(Trust_Track_Output[*Trust_track_num_Output-1][0].range_dim, (*trust_track)[i].range_dim, sizeof(Trust_Track_Output[*Trust_track_num_Output-1][0].range_dim));
break;
}
@@ -115,32 +169,16 @@ void TAS_Ctrl::tas_target_add(std::vector<Trust_Track> *trust_track,
};
//int TAS_Ctrl:: tas_auto_start(double v, double r, double h, struct RadarPara Work_Parameter)
//{
// 说明:TAS 自动开始(tas_auto_start)的原实现依赖改造前雷达参数结构体中的
// TAS 距离/速度/高度门限,这些参数未纳入新的 RDP_parameter,故原实现保持停用;
// 具体逻辑不再以注释代码形式保留(tas_ctrl.h 中保留声明,后续如需启用再按新参数补齐)。
//// if(r>Work_Parameter.R_TAS_min && r<Work_Parameter.R_TAS_max && v<Work_Parameter.V_TAS_max && v>Work_Parameter.V_TAS_min
//// && ( (r<=1000 && h>=Work_Parameter.TAS_Height_1) || (r<=2000 && r>1000 && h>=Work_Parameter.TAS_Height_2) || (r<=3000 && r>2000 && h>=Work_Parameter.TAS_Height_3) || (r<=4000 && r>3000 && h>=Work_Parameter.TAS_Height_4) || (r>4000 && h>=Work_Parameter.TAS_Height_5) )
//// )
// if( r>Work_Parameter.R_TAS_min && r<Work_Parameter.R_TAS_max && v<Work_Parameter.V_TAS_max && v>Work_Parameter.V_TAS_min
// && ( (r<=1000 && h>=Work_Parameter.TAS_Height_1) || (r<=2000 && r>1000 && h>=Work_Parameter.TAS_Height_2) || (r<=3000 && r>2000 && h>=Work_Parameter.TAS_Height_3) || (r<=4000 && r>3000 && h>=Work_Parameter.TAS_Height_4) || (r>4000 && h>=Work_Parameter.TAS_Height_5) ) )
// {
// return 1;
// }
// else
// {
// return 0;
// }
//// return 0;
//};
int TAS_Ctrl::tas_prohibite_area(double r,double azi, double v, double h,struct RadarPara Work_Parameter)
int TAS_Ctrl::tas_prohibite_area(double r,double azi, double v, double h)
{
for (int i=0;i<Work_Parameter.TAS_prohibite_area_num;i++)
for (int i=0;i<rdp_parameter.tas_prohibite_area_num;i++)
{
if(r<Work_Parameter.R_max_TAS_prohibited[i] && r>Work_Parameter.R_min_TAS_prohibited[i] && azi<Work_Parameter.Azimuth_max_TAS_prohibited[i] && azi>Work_Parameter.Azimuth_min_TAS_prohibited[i])
if(r<rdp_parameter.tas_prohibite_area_range_max[i] && r>rdp_parameter.tas_prohibite_area_range_min[i] && azi<rdp_parameter.tas_prohibite_area_azimuth_max[i] && azi>rdp_parameter.tas_prohibite_area_azimuth_min[i])
{
return 1;
}
@@ -152,18 +190,17 @@ int TAS_Ctrl::tas_prohibite_area(double r,double azi, double v, double h,struct
void TAS_Ctrl::tas_target_del(std::vector<Trust_Track> *trust_track,
struct Track Trust_Track_Output[][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter)
int *Trust_track_num_Output)
{
//查找TAS目标是否已经消批 若消批则移出TAS队列
for (int i=0;i<TAS_QUEUE_LENGTH;i++)
for (int i=0;i<RDP_TAS_QUEUE_CAPACITY;i++)
{
if(tas_target_queue[i].empty_flag == 1)
{
//查找TAS队列里的目标是否存在
int flag=0;
for (int j=0;j<trust_track->size();j++){
for (size_t j=0;j<trust_track->size();j++){
{
if(tas_target_queue[i].Index==(*trust_track)[j].Track_Index && (*trust_track)[j].Track_Mode == 1 && (*trust_track)[j].manual_tracking_flag == 1)
{
@@ -185,14 +222,14 @@ void TAS_Ctrl::tas_target_del(std::vector<Trust_Track> *trust_track,
// {
// double r=sqrt(pow((*trust_track)[i].X[0],2)+pow((*trust_track)[i].X[3],2));
// double v=sqrt(pow((*trust_track)[i].X[1],2)+pow((*trust_track)[i].X[4],2));
// double h=(*trust_track)[i].Height+Work_Parameter.Height;
// double h=(*trust_track)[i].Height+rdp_parameter.radar_height;
// if((*trust_track)[i].manual_tracking_flag == 0 && (*trust_track)[i].Track_Mode == 1 && tas_auto_end(v,r,h,Work_Parameter) == 1) //满足结束跟踪条件
// if((*trust_track)[i].manual_tracking_flag == 0 && (*trust_track)[i].Track_Mode == 1 && tas_auto_end(v,r,h) == 1) //满足结束跟踪条件
// {
// int track_index=(*trust_track)[i].Track_Index;
// for (int ii=0;ii<TAS_QUEUE_LENGTH;ii++)
// for (int ii=0;ii<RDP_TAS_QUEUE_CAPACITY;ii++)
// {
// if(tas_target_queue[ii].Index == track_index)
// {
@@ -212,17 +249,17 @@ void TAS_Ctrl::tas_target_del(std::vector<Trust_Track> *trust_track,
// coor_trans Coor_trans;
// Coor_trans.cart2polar((*trust_track)[i].X[0],(*trust_track)[i].X[3],&r_output,&azmi_output);
// Trust_Track_Output[*Trust_track_num_Output-1][0].Point_Sum=1;
// Trust_Track_Output[*Trust_track_num_Output-1][0].Range=r_output;
// Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth=azmi_output/PI*180;
// Trust_Track_Output[*Trust_track_num_Output-1][0].Range=static_cast<float>(r_output);
// Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth=static_cast<float>(azmi_output/PI*180);
// Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Index=(*trust_track)[i].Track_Index; //航迹号
// Trust_Track_Output[*Trust_track_num_Output-1][0].Range_V=sqrt((*trust_track)[i].X[1]*(*trust_track)[i].X[1]+(*trust_track)[i].X[4]*(*trust_track)[i].X[4]);
// Trust_Track_Output[*Trust_track_num_Output-1][0].Amplitude=(*trust_track)[i].Amplitude;
// Trust_Track_Output[*Trust_track_num_Output-1][0].Range_V=static_cast<float>(sqrt((*trust_track)[i].X[1]*(*trust_track)[i].X[1]+(*trust_track)[i].X[4]*(*trust_track)[i].X[4]));
// Trust_Track_Output[*Trust_track_num_Output-1][0].Amplitude=static_cast<float>((*trust_track)[i].Amplitude);
// Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Point=(*trust_track)[i].point_flag;
// Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Mode=(*trust_track)[i].Track_Mode;
// Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation=asin((*trust_track)[i].Height/r_output)/PI*180;
// Trust_Track_Output[*Trust_track_num_Output-1][0].z=(*trust_track)[i].Height+Work_Parameter.Height;
// Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation=static_cast<float>(asin((*trust_track)[i].Height/r_output)/PI*180);
// Trust_Track_Output[*Trust_track_num_Output-1][0].z=static_cast<float>((*trust_track)[i].Height+rdp_parameter.radar_height);
// Trust_Track_Output[*Trust_track_num_Output-1][0].Target_Type=(*trust_track)[i].Target_Type;
// Trust_Track_Output[*Trust_track_num_Output-1][0].track_snr = (*trust_track)[i].snr_point;
// Trust_Track_Output[*Trust_track_num_Output-1][0].track_snr = static_cast<float>((*trust_track)[i].snr_point);
// }
// }
// }
@@ -231,85 +268,82 @@ void TAS_Ctrl::tas_target_del(std::vector<Trust_Track> *trust_track,
};
//int TAS_Ctrl::tas_auto_end(double v, double r, double h, struct RadarPara Work_Parameter)
//{
// if((r<=1000 && h<Work_Parameter.TAS_Height_1) || (r<=2000 && r>1000 && h<Work_Parameter.TAS_Height_2) || (r<=3000 && r>2000 && h<Work_Parameter.TAS_Height_3) || (r<=4000 && r>3000 && h<Work_Parameter.TAS_Height_4) || (r>4000 && h<Work_Parameter.TAS_Height_5)
// || v<Work_Parameter.V_TAS_min || v>Work_Parameter.V_TAS_max
// )
// {
// return 1;
// }
// else
// {
// return 0;
// }
// return 0;
//};
// 说明:TAS 自动结束(tas_auto_end)的原实现同样依赖未纳入 RDP_parameter 的
// TAS 距离/速度/高度门限,故保持停用(tas_ctrl.h 中保留声明,实现待后续补齐)。
void TAS_Ctrl::tas_beam_output(std::vector<Trust_Track> *trust_track,
struct TrackingBeam *Tracking_beam,
struct RadarPara Work_Parameter,
long long latest_timestamp)
{
if(tas_target_queue[0].empty_flag==1)
long long CPI_time = 0;
long long earliest_CPI_time = LLONG_MAX;
float delta_T = 0;
float delta_T_last_track = 0;
double H_track = 0;
int earliest_track_idx=-1;
int earliest_tas_queue_idx=-1;
double X_now[6] = {0};
//查找TAS队列中CPI时间最早的目标且满足TAS数据率的目标
for(int tas_idx=0;tas_idx<RDP_TAS_QUEUE_CAPACITY;tas_idx++)
{
double H_track = 0;
double X_now[6] = {0};
long long CPI_time = 0;
bool found_track = false;
for (int i=0;i<trust_track->size();i++ )
if(tas_target_queue[tas_idx].empty_flag==0)
{
if((*trust_track)[i].Track_Index == tas_target_queue[0].Index)
continue;
}
for (size_t track_idx=0;track_idx<trust_track->size();track_idx++ )
{
if((*trust_track)[track_idx].Track_Index == tas_target_queue[tas_idx].Index)
{
H_track = (*trust_track)[i].Height;
CPI_time = (*trust_track)[i].T_track;
for(int ii=0;ii<6;ii++)
X_now[ii]=(*trust_track)[i].X[ii];
found_track = true;
CPI_time = (*trust_track)[track_idx].T_track;
delta_T_last_track = (latest_timestamp - tas_target_queue[tas_idx].last_track_time) / 1000.0f;
//查找CPI时间最早,且满足TAS数据率的目标
if(CPI_time < earliest_CPI_time && (delta_T_last_track > rdp_parameter.data_rate_tas))
{
earliest_CPI_time = CPI_time;
H_track = (*trust_track)[track_idx].Height;
delta_T = (latest_timestamp - CPI_time) / 1000.0f;
earliest_track_idx = static_cast<int>(track_idx);
earliest_tas_queue_idx = tas_idx;
for(int ii=0;ii<6;ii++){
X_now[ii]=(*trust_track)[track_idx].X[ii];
}
}
break;
}
}
}
if (!found_track)
{
Tracking_beam->open_flag = 0;
return;
}
//没有满足条件的TAS目标,关闭跟踪波束
if(earliest_track_idx == -1)
{
Tracking_beam->open_flag = 0;
return;
}
//数据率门控:航迹时间与当前最新时间戳的差值大于 1/DATA_RATE_TAS 时才输出TAS跟踪波束,
//保证TAS目标数据率不随波束时间变化(使用Work_Parameter中的DATA_RATE_TAS参数值)。
//DATA_RATE_TAS<=0 时不做门控,按原逻辑每次输出(避免除零,兼容未配置该参数的调用者)。
if (Work_Parameter.DATA_RATE_TAS > 0)
{
double delta_T_track = (double)(latest_timestamp - CPI_time) / 1000.0;
if (delta_T_track <= 1.0 / Work_Parameter.DATA_RATE_TAS)
{
//更新间隔未到 不输出跟踪波束
Tracking_beam->open_flag = 0;
return;
}
}
//更新目标最后跟踪时间
tas_target_queue[earliest_tas_queue_idx].last_track_time = latest_timestamp;
float delta_T = (latest_timestamp - CPI_time) / 1000.0f;
//预测目标位置 计算跟踪波束波位号 俯仰角
double x_track=X_now[0]+X_now[1]*delta_T;
double y_track=X_now[3]+X_now[4]*delta_T;
double amzi,range;
coor_trans Coor_trans;
Coor_trans.cart2polar(x_track,y_track,&range,&amzi);
//预测目标位置 计算跟踪波束波位号 俯仰角
double x_track=X_now[0]+X_now[1]*delta_T;
double y_track=X_now[3]+X_now[4]*delta_T;
double amzi,range;
coor_trans Coor_trans;
Coor_trans.cart2polar(x_track,y_track,&range,&amzi);
//目标距离
Tracking_beam->Range=range;
//目标方位
Tracking_beam->Azi=amzi/PI*180;
//目标距离
Tracking_beam->Range=(float)range;
//目标方位
Tracking_beam->Azi=(float)amzi/PI*180;
// Tracking_beam->Azi+=6;
//目标俯仰角
double elev=asin(H_track/range)/PI*180;
//目标俯仰角
double elev=asin(H_track/range)/PI*180;
// if(elev<=0)
// elev=0;
@@ -318,23 +352,16 @@ void TAS_Ctrl::tas_beam_output(std::vector<Trust_Track> *trust_track
// else
// elev=elev;
Tracking_beam->Elev=elev;
Tracking_beam->Elev=(float)elev;
//跟踪波束类型
Tracking_beam->type=1;
//跟踪波束类型
Tracking_beam->type=rdp_parameter.default_track_beam_type;
//跟踪目标批号
Tracking_beam->TAS_track_index = tas_target_queue[0].Index;
//跟踪目标批号
Tracking_beam->TAS_track_index = (*trust_track)[earliest_track_idx].Track_fingerprint;
//跟踪波束开关开启
Tracking_beam->open_flag=1;
//std::cout << "TAS: range: " <<Tracking_beam->Range << "azi: " <<Tracking_beam->Azi <<"pit: " <<Tracking_beam->Elev;
}
else
{
//跟踪波束开关关闭
Tracking_beam->open_flag=0;
}
//跟踪波束开关开启
Tracking_beam->open_flag=1;
// RDP_LOG_INFO("TAS <{}> output: range: {} azi: {} pit: {}", Tracking_beam->TAS_track_index, Tracking_beam->Range, Tracking_beam->Azi, Tracking_beam->Elev);
}
+7 -11
View File
@@ -1,4 +1,4 @@
#ifndef TAS_CTRL_H
#ifndef TAS_CTRL_H
#define TAS_CTRL_H
#include "data_process_class_dll.h"
#include "parameters.h"
@@ -18,7 +18,6 @@ public:
struct TrackingBeam *Tracking_beam,
struct Track Trust_Track_Output[][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter,
long long latest_timestamp);
private:
@@ -26,31 +25,28 @@ private:
//添加TAS目标
void tas_target_add(std::vector<Trust_Track> *trust_track,
struct Track Trust_Track_Output[][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter);
int *Trust_track_num_Output);
//删除TAS目标
void tas_target_del(std::vector<Trust_Track> *trust_track,
struct Track Trust_Track_Output[][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter);
int *Trust_track_num_Output);
//TAS队列信息输出
void tas_beam_output(std::vector<Trust_Track> *trust_track,
struct TrackingBeam *Tracking_beam,
struct RadarPara Work_Parameter,
long long latest_timestamp);
//TAS 自动开启条件
int tas_auto_start(double v, double r, double h, struct RadarPara Work_Parameter);
int tas_auto_start(double v, double r, double h);
//TAS 自动退出条件
int tas_auto_end(double v, double r, double h, struct RadarPara Work_Parameter);
int tas_auto_end(double v, double r, double h);
// 禁止跟踪区域判断
int tas_prohibite_area(double r,double azi, double v, double h,struct RadarPara Work_Parameter);
int tas_prohibite_area(double r,double azi, double v, double h);
struct Tracking_Target tas_target_queue[TAS_QUEUE_LENGTH]; //跟踪目标信息队列
struct Tracking_Target tas_target_queue[RDP_TAS_QUEUE_CAPACITY]; //跟踪目标信息队列
int tas_target_num; //跟踪目标数量
+359 -329
View File
@@ -1,10 +1,14 @@
#include "track_asso.h"
#include "track_asso.h"
#include "kalman.h"
#include "coor_trans.h"
#include "rdp_log.h"
#include <cmath>
#include <cstring>
#include <vector>
#include <Eigen/Dense>
#include "rdp_record.h"
using namespace Eigen;
using namespace std;
@@ -15,22 +19,22 @@ int Track_Asso:: track_asso_process(std::vector<PointRecv> *point_
std::vector <Trust_Track> *trust_track, //航迹文件
struct Track Trust_Track_Output[MAX_TRACK_NUM][10], //更新航迹信息
int *Trust_track_num_Output, //更新航迹数
struct RadarPara Work_Parameter //雷达参数
long long latest_timestamp //最新时间戳
)
{
//取出对应点迹区的点
for (int i=0;i< point_recv->size();i++)
for (size_t i=0;i< point_recv->size();i++)
{
point_process.push_back((*point_recv)[i]);
int n=point_process.size();
int n=static_cast<int>(point_process.size());
point_process[n-1].point_section_asso = 1;
}
//IMM算法
model_interaction(trust_track);
model_filter(trust_track, Work_Parameter);
model_filter(trust_track, latest_timestamp);
model_output(trust_track);
@@ -38,7 +42,7 @@ int Track_Asso:: track_asso_process(std::vector<PointRecv> *point_
std::vector <PointRecv>::iterator Iter;
for (Iter= point_process.begin(); Iter!=point_process.end();)
{
if((*Iter).Use_Flag==1 )
if((*Iter).use_flag==1 )
{
point_process.erase(Iter);
Iter=point_process.begin();
@@ -51,7 +55,7 @@ int Track_Asso:: track_asso_process(std::vector<PointRecv> *point_
//剩余点重新存入点迹
std::vector<PointRecv>().swap((*point_recv));
for (int i=0;i<point_process.size();i++)
for (size_t i=0;i<point_process.size();i++)
{
(*point_recv).push_back(point_process[i]);
@@ -62,74 +66,113 @@ int Track_Asso:: track_asso_process(std::vector<PointRecv> *point_
std::vector<PointRecv>().swap(point_process);
//输出航迹
for (int i=0;i<trust_track->size();i++)
#ifdef RDP_RECORD_ENABLE
//记录可靠航迹:先收集,输出循环结束后一次入队
std::vector<Trust_Track> rec_trust;
rec_trust.reserve(trust_track->size());
#endif
for (size_t i=0;i<trust_track->size();i++)
{ // 输出更新航迹条件:
// if((*trust_track)[i].manual_tracking_flag==0)
// {
if((*trust_track)[i].Track_Update_Flag==1){
(*trust_track)[i].Track_Update_Flag=0;
*Trust_track_num_Output= *Trust_track_num_Output+1;
Trust_Track_Output[*Trust_track_num_Output-1][0].Point_Sum=1;
double x,y;
x=(*trust_track)[i].X[0]+(*trust_track)[i].X[1]*Work_Parameter.Sys_delay;
y=(*trust_track)[i].X[3]+(*trust_track)[i].X[4]*Work_Parameter.Sys_delay;
x=(*trust_track)[i].X[0]+(*trust_track)[i].X[1]*rdp_parameter.radar_system_delay;
y=(*trust_track)[i].X[3]+(*trust_track)[i].X[4]*rdp_parameter.radar_system_delay;
coor_trans Coor_trans;
double r, azi;
Coor_trans.cart2polar(x, y, &r, &azi);
Trust_Track_Output[*Trust_track_num_Output-1][0].Range=r;
Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth=azi/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][0].Range=static_cast<float>(r);
Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth=static_cast<float>(azi/PI*180);
if((*trust_track)[i].Height<r)
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = asin((*trust_track)[i].Height/r)/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = static_cast<float>(asin((*trust_track)[i].Height/r)/PI*180);
else
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = (*trust_track)[i].elev_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = static_cast<float>((*trust_track)[i].elev_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Index=(*trust_track)[i].Track_Index;
Trust_Track_Output[*Trust_track_num_Output-1][0].Range_V =
sqrt((*trust_track)[i].X[1]*(*trust_track)[i].X[1]+(*trust_track)[i].X[4]*(*trust_track)[i].X[4]);
Trust_Track_Output[*Trust_track_num_Output-1][0].Amplitude = (*trust_track)[i].Amplitude;
static_cast<float>(sqrt((*trust_track)[i].X[1]*(*trust_track)[i].X[1]+(*trust_track)[i].X[4]*(*trust_track)[i].X[4]));
Trust_Track_Output[*Trust_track_num_Output-1][0].Amplitude = static_cast<float>((*trust_track)[i].Amplitude);
Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Mode=(*trust_track)[i].Track_Mode;
Trust_Track_Output[*Trust_track_num_Output-1][0].track_time=(*trust_track)[i].T_track/1000.0;
Trust_Track_Output[*Trust_track_num_Output-1][0].track_time=static_cast<float>((*trust_track)[i].T_track/1000.0);
Trust_Track_Output[*Trust_track_num_Output-1][0].GNSS_time=(*trust_track)[i].GNSS_time;
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Point = (*trust_track)[i].point_flag;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=(*trust_track)[i].Height+Work_Parameter.Height;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=static_cast<float>((*trust_track)[i].Height+rdp_parameter.radar_height);
Trust_Track_Output[*Trust_track_num_Output-1][0].Quality = static_cast<float>((*trust_track)[i].Quality * rdp_parameter.track_quality_max);
double Direction_Angle;
Direction_Angle=atan2((*trust_track)[i].X[4], (*trust_track)[i].X[1]);
if(Direction_Angle<0){Direction_Angle=Direction_Angle+2*PI;}
Trust_Track_Output[*Trust_track_num_Output-1][0].Direction_Angle=Direction_Angle/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][0].Direction_Angle=static_cast<float>(Direction_Angle/PI*180);
(*trust_track)[i].Range = Trust_Track_Output[*Trust_track_num_Output-1][0].Range;
(*trust_track)[i].Azimuth = Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth;
(*trust_track)[i].Elevation = Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation;
(*trust_track)[i].Velocity = Trust_Track_Output[*Trust_track_num_Output-1][0].Range_V;
//关联点信息
Trust_Track_Output[*Trust_track_num_Output-1][0].range_point=(*trust_track)[i].range_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].azi_point=(*trust_track)[i].azi_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].elev_point=(*trust_track)[i].elev_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].vr_point=(*trust_track)[i].vr_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].range_point=static_cast<float>((*trust_track)[i].range_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].azi_point=static_cast<float>((*trust_track)[i].azi_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].elev_point=static_cast<float>((*trust_track)[i].elev_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].vr_point=static_cast<float>((*trust_track)[i].vr_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].point_type=0;
Trust_Track_Output[*Trust_track_num_Output-1][0].prf_point = (*trust_track)[i].prf_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].track_snr = (*trust_track)[i].snr_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].prf_point = static_cast<float>((*trust_track)[i].prf_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].track_snr = static_cast<float>((*trust_track)[i].snr_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].amp_asv_point = static_cast<float>((*trust_track)[i].amp_asv_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].prn_fftn_point = static_cast<float>((*trust_track)[i].prn_fftn_point);
//直接输出点迹高度-20260605
//Trust_Track_Output[*Trust_track_num_Output-1][0].z = (*trust_track)[i].range_point * sin((*trust_track)[i].elev_point/180.0*PI);
//输出平滑后的高度
Trust_Track_Output[*Trust_track_num_Output-1][0].z = (*trust_track)[i].Height+Work_Parameter.Height;
Trust_Track_Output[*Trust_track_num_Output-1][0].z = static_cast<float>((*trust_track)[i].Height+rdp_parameter.radar_height);
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = (*trust_track)[i].elev_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = static_cast<float>((*trust_track)[i].elev_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].x=(*trust_track)[i].X[0];
Trust_Track_Output[*Trust_track_num_Output-1][0].y=(*trust_track)[i].X[3];
Trust_Track_Output[*Trust_track_num_Output-1][0].v_x=(*trust_track)[i].X[1];
Trust_Track_Output[*Trust_track_num_Output-1][0].v_y=(*trust_track)[i].X[4];
Trust_Track_Output[*Trust_track_num_Output-1][0].x=static_cast<float>((*trust_track)[i].X[0]);
Trust_Track_Output[*Trust_track_num_Output-1][0].y=static_cast<float>((*trust_track)[i].X[3]);
Trust_Track_Output[*Trust_track_num_Output-1][0].v_x=static_cast<float>((*trust_track)[i].X[1]);
Trust_Track_Output[*Trust_track_num_Output-1][0].v_y=static_cast<float>((*trust_track)[i].X[4]);
Trust_Track_Output[*Trust_track_num_Output-1][0].track_rcs = (*trust_track)[i].RCS;
// Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=Trust_Track_Output[*Trust_track_num_Output-1][0].vr_point>0?1:0;
if(acos((-x * cos(Direction_Angle) - y * sin(Direction_Angle)) / r ) < PI / 2)
{
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=1;
// if(r*abs(sin(azi - Direction_Angle))<500)
// {
// Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=1;
// }
// else
// {
// Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=0;
// }
}
else
{
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=0;
}
Trust_Track_Output[*Trust_track_num_Output-1][0].track_rcs = static_cast<float>((*trust_track)[i].RCS);
Trust_Track_Output[*Trust_track_num_Output-1][0].pitch_num = (*trust_track)[i].pitch_num;
std::memcpy(Trust_Track_Output[*Trust_track_num_Output-1][0].speed_dim, (*trust_track)[i].speed_dim, sizeof(Trust_Track_Output[*Trust_track_num_Output-1][0].speed_dim));
std::memcpy(Trust_Track_Output[*Trust_track_num_Output-1][0].range_dim, (*trust_track)[i].range_dim, sizeof(Trust_Track_Output[*Trust_track_num_Output-1][0].range_dim));
// }
#ifdef RDP_RECORD_ENABLE
rec_trust.push_back((*trust_track)[i]);
#endif
}
}
#ifdef RDP_RECORD_ENABLE
RDP_RECORD_TRUST_TRACKS(rec_trust);
#endif
return 0;
}
@@ -137,7 +180,7 @@ int Track_Asso:: track_asso_process(std::vector<PointRecv> *point_
void Track_Asso:: model_interaction(std::vector <Trust_Track> *trust_track)
{
for (int loop_of_track=0;loop_of_track<trust_track->size();loop_of_track++)
for (size_t loop_of_track=0;loop_of_track<trust_track->size();loop_of_track++)
{
// if((*trust_track)[loop_of_track].manual_tracking_flag == 0)
@@ -244,7 +287,7 @@ void Track_Asso:: model_interaction(std::vector <Trust_Track> *trust_track)
}
// 产生 F Q 矩阵
void Track_Asso::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6],struct RadarPara Work_Parameter)
void Track_Asso::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6])
{
//F
@@ -272,7 +315,7 @@ void Track_Asso::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], doub
//模型1
//Q
double q1=Work_Parameter.Model1_Q_slow;
double q1=rdp_parameter.model_1_Q_slow;
double q111=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
double q121=1/(2*pow(alpha,4))*(exp(-2*alpha*delta_T)+1-2*exp(-alpha*delta_T)+2*alpha*delta_T*exp(-alpha*delta_T)-2*alpha*delta_T+pow(alpha,2)*pow(delta_T,2));
double q131=1/(2*pow(alpha,3))*(1-exp(-2*alpha*delta_T)-2*alpha*delta_T*exp(-alpha*delta_T));
@@ -293,24 +336,24 @@ void Track_Asso::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], doub
double q2;
if(v_track<=5)
{
q2=Work_Parameter.Model2_Q_slow/20;
q2=rdp_parameter.model_2_Q_slow/20;
}
else if(v_track<=15&&v_track>5)
{
q2=Work_Parameter.Model2_Q_slow;
q2=rdp_parameter.model_2_Q_slow;
}
else if(v_track<=30&&v_track>15)
{
q2=Work_Parameter.Model2_Q_slow;
q2=rdp_parameter.model_2_Q_slow;
}
else if(v_track<=100&&v_track>30)
{
q2=Work_Parameter.Model2_Q_fast;
q2=rdp_parameter.model_2_Q_fast;
}
else
{
q2=Work_Parameter.Model2_Q_fast;
q2=rdp_parameter.model_2_Q_fast;
}
double q112=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
@@ -334,29 +377,29 @@ void Track_Asso::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], doub
double q3;
if(v_track<=5)
{
q3=Work_Parameter.Model3_Q_slow/20;
q3=rdp_parameter.model_3_Q_slow/20;
}
else if(v_track<=15&&v_track>5)
{
q3=Work_Parameter.Model3_Q_slow;
q3=rdp_parameter.model_3_Q_slow;
}
else if(v_track<=20&&v_track>15)
{
q3=Work_Parameter.Model3_Q_slow;
q3=rdp_parameter.model_3_Q_slow;
}
else if(v_track<=50&&v_track>20)
{
q3=Work_Parameter.Model3_Q_fast;
q3=rdp_parameter.model_3_Q_fast;
}
else if(v_track<=100&&v_track>50)
{
q3=2*Work_Parameter.Model3_Q_fast;
q3=2*rdp_parameter.model_3_Q_fast;
}
else
{
q3=10*Work_Parameter.Model3_Q_fast;
q3=10*rdp_parameter.model_3_Q_fast;
}
double q113=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
@@ -384,11 +427,10 @@ void Track_Asso::IMM_d_cal(double v_track,
double X3[6], double P3[6][6],
double Z[3], double prt,double freq_ind,
double delta_T,
double *d1,double *d2,double *d3,
struct RadarPara Work_Parameter)
double *d1,double *d2,double *d3)
{
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3);
kalman Kalman;
*d1=Kalman.d_cal_EKF(F,Q1,Z,X1,P1,prt,freq_ind);
@@ -397,181 +439,153 @@ void Track_Asso::IMM_d_cal(double v_track,
}
void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct RadarPara Work_Parameter)
void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track, long long latest_timestamp)
{
//存储关联信息
struct asso_info
std::vector <Asso_Info> associated_info;
#ifdef RDP_RECORD_ENABLE
//记录未关联上的点航信息:收集到分块上限即入队,函数末尾再 flush 剩余
//按本圈候选数(点迹数×航迹数,封顶到分块上限)预留,避免收集期间多次扩容
std::vector<Asso_Info> rec_asso;
{
double d1;
double d2;
double d3;
double d_min;
int point_index;
int track_index;
};
std::vector <asso_info> associated_info;
std::size_t est = point_process.size() * trust_track->size();
if (est > static_cast<std::size_t>(RDP_RECORD_ASSO_CHUNK_ROWS))
{
est = static_cast<std::size_t>(RDP_RECORD_ASSO_CHUNK_ROWS);
}
rec_asso.reserve(est);
}
#endif
//遍历所有点迹航迹 计算点迹和航迹的统计距离
for (int loop_of_track = 0; loop_of_track<trust_track->size();loop_of_track++)
for (size_t loop_of_track = 0; loop_of_track<trust_track->size();loop_of_track++)
{
// if((*trust_track)[loop_of_track].manual_tracking_flag==0)
// {
(*trust_track)[loop_of_track].point_flag = 0; //航迹的point_flag置为0 关联上点后再置为1
(*trust_track)[loop_of_track].point_flag = 0; //航迹的point_flag置为0 关联上点后再置为1
for (int loop_of_point = 0; loop_of_point<point_process.size();loop_of_point++)
{
for (size_t loop_of_point = 0; loop_of_point<point_process.size();loop_of_point++)
{
//时间差
double delta_T = (point_process[loop_of_point].CPI_Time - (*trust_track)[loop_of_track].T_track)/1000.0;
//航迹X P
double X1[6],X2[6],X3[6],P1[6][6],P2[6][6],P3[6][6];
memcpy(X1,(*trust_track)[loop_of_track].X1,6*sizeof(double));
memcpy(X2,(*trust_track)[loop_of_track].X2,6*sizeof(double));
memcpy(X3,(*trust_track)[loop_of_track].X3,6*sizeof(double));
memcpy(P1,(*trust_track)[loop_of_track].P1,6*6*sizeof(double));
memcpy(P2,(*trust_track)[loop_of_track].P2,6*6*sizeof(double));
memcpy(P3,(*trust_track)[loop_of_track].P3,6*6*sizeof(double));
double v_track = sqrt(pow((*trust_track)[loop_of_track].X[1],2)+pow((*trust_track)[loop_of_track].X[4],2));
double h_track = (*trust_track)[loop_of_track].Height;
double r_track = sqrt(pow((*trust_track)[loop_of_track].X[0],2)+pow((*trust_track)[loop_of_track].X[3],2));
//时间差
double delta_T = (point_process[loop_of_point].CPI_time - (*trust_track)[loop_of_track].T_track)/1000.0;
//航迹X P
double X1[6],X2[6],X3[6],P1[6][6],P2[6][6],P3[6][6];
memcpy(X1,(*trust_track)[loop_of_track].X1,6*sizeof(double));
memcpy(X2,(*trust_track)[loop_of_track].X2,6*sizeof(double));
memcpy(X3,(*trust_track)[loop_of_track].X3,6*sizeof(double));
memcpy(P1,(*trust_track)[loop_of_track].P1,6*6*sizeof(double));
memcpy(P2,(*trust_track)[loop_of_track].P2,6*6*sizeof(double));
memcpy(P3,(*trust_track)[loop_of_track].P3,6*6*sizeof(double));
double v_track = sqrt(pow((*trust_track)[loop_of_track].X[1],2)+pow((*trust_track)[loop_of_track].X[4],2));
double h_track = (*trust_track)[loop_of_track].Height;
double r_track = sqrt(pow((*trust_track)[loop_of_track].X[0],2)+pow((*trust_track)[loop_of_track].X[3],2));
//量测信息
double Z[3] = {point_process[loop_of_point].Range,
point_process[loop_of_point].Azimuth,
point_process[loop_of_point].Velocity};
double prt = point_process[loop_of_point].PRF_index;
double freq_ind = point_process[loop_of_point].Freq_index;
double h_point = point_process[loop_of_point].Height;
//量测信息
double Z[3] = {point_process[loop_of_point].range,
point_process[loop_of_point].azimuth,
point_process[loop_of_point].velocity};
double prt = point_process[loop_of_point].PRF_index;
double freq_ind = point_process[loop_of_point].freq_index;
double h_point = point_process[loop_of_point].height;
//计算统计距离d
double d1,d2,d3;
IMM_d_cal(v_track,X1,P1,X2,P2, X3,P3,Z,prt,freq_ind,delta_T,&d1,&d2,&d3,Work_Parameter);
//计算统计距离d
double d1,d2,d3;
IMM_d_cal(v_track,X1,P1,X2,P2, X3,P3,Z,prt,freq_ind,delta_T,&d1,&d2,&d3);
//计算高度门限
// double d_h;
// if (r_track<1000)
// {
// if(abs(h_track-h_point)<=150)
// {
// d_h = 1;
// }
// else
// {
// d_h = 0;
// }
//判定统计距离门限,thd_d
bool thd_d = false;
// }
// else if (r_track<2000 && r_track>=1000)
// {
// if(abs(h_track-h_point)<=200)
// {
// d_h = 1;
// }
// else
// {
// d_h = 0;
// }
// }
// else if(r_track>=2000 && r_track<3000 )
// {
// if(abs(h_track-h_point)<=200)
// {
// d_h = 1;
// }
// else
// {
// d_h = 0;
// }
// }
// else if(r_track>=3000 && r_track<5000)
// {
// if(abs(h_track-h_point)<=300)
// {
// d_h = 1;
// }
// else
// {
// d_h = 0;
// }
// }
// else
// {
// d_h = 1;
// }
// std::cout << delta_T<<" "<<d1<<" "<<d2<<" "<<d3;
// std::cout << h_track<<" "<<h_point<<" "<<d_h;
// d_h = 1;
//计算俯仰门限
bool d_p = true;
// double p_track = atan2(h_track, r_track); //航迹俯仰
// double p_point = asin(h_point/point_process[loop_of_point].Range); //点迹俯仰
// if( (r_track > 3000 && abs(p_track - p_point)*180/PI <= 7) || \
// (r_track > 1000 && r_track <= 3000 && abs(p_track - p_point)*180/PI <= 5) || \
// (r_track <= 1000 && abs(p_track - p_point)*180/PI <= 10)){
// d_p = true;
// }
//计算距离门限,R=航迹点与量测点的距离,VT=航向速度乘以扫描时间差
double& t_x0 = (*trust_track)[loop_of_track].X[0];
double& t_y0 = (*trust_track)[loop_of_track].X[3];
double a_track = atan2(t_y0, t_x0); //航迹方位角
double& r_point = point_process[loop_of_point].Range; //点迹距离
double a_delta = abs(a_track - point_process[loop_of_point].Azimuth); //方位差
if(a_delta > PI){
a_delta = 2*PI - a_delta;
}
double R = sqrt(r_track * r_track + r_point*r_point - 2*r_track*r_point*cos(a_delta));
double VT = v_track*delta_T;
bool d_r = true;
if(VT < 100 && R > 300){
//VT<100m,使用固定门限300m
d_r = false;
}else if((VT>=100 && VT<=300 && R/VT>3)){
d_r = false;
}else if(VT>300 && R > 1200){
d_r = false;
}
//小于关联门限 保存关联信息
if(
( r_track<=500 && (d1*d1<ASSO_THORD*ASSO_THORD || d2*d2<ASSO_THORD*ASSO_THORD || d3*d3<ASSO_THORD*ASSO_THORD/1000.0) && d_p && d_r)//
||( (r_track>500&&r_track<1000) && (d1*d1<ASSO_THORD*ASSO_THORD || d2*d2<ASSO_THORD*ASSO_THORD || d3*d3<ASSO_THORD*ASSO_THORD/100.0) && d_p && d_r)//
||( r_track>=1000 && (d1*d1<ASSO_THORD*ASSO_THORD || d2*d2<ASSO_THORD*ASSO_THORD || d3*d3<ASSO_THORD*ASSO_THORD) && d_p && d_r)//
)
{
//std::cout << "asooooooooooo";
struct asso_info associated_info_tmp;
associated_info_tmp.d1=d1;
associated_info_tmp.d2=d2;
associated_info_tmp.d3=d3;
associated_info_tmp.point_index=loop_of_point+1;
associated_info_tmp.track_index=loop_of_track+1;
if(d1<=d2&&d1<=d3)
associated_info_tmp.d_min=d1;
else if(d2<=d1&& d2<=d3)
associated_info_tmp.d_min=d2;
else
associated_info_tmp.d_min=d3;
associated_info.push_back(associated_info_tmp);
}
if( (r_track <= 500 && \
(d1*d1 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d2*d2 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d3*d3 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold / 1000.0)) || \
((r_track > 500 && r_track < 1000) && \
(d1*d1 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d2*d2 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d3*d3 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold / 100.0)) || \
(r_track >= 1000 && \
(d1*d1 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d2*d2 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d3*d3 < rdp_parameter.associate_threshold*rdp_parameter.associate_threshold))\
){
thd_d = true;
}
// }
//判定高度门限,thd_h
bool thd_h = false;
double delta_h = abs(h_track-h_point);
if( (r_track <= 1000 && delta_h <= 150) || \
((r_track > 1000 && r_track <= 2000) && delta_h <= 200) || \
((r_track > 2000 && r_track <= 3000) && delta_h <= 250) || \
((r_track > 3000 && r_track <= 5000) && delta_h <= 300) || \
r_track > 5000
){
thd_h = true;
}
//判定距离门限,R=航迹点与量测点的距离,VT=航向速度乘以扫描时间差
double& t_x0 = (*trust_track)[loop_of_track].X[0];
double& t_y0 = (*trust_track)[loop_of_track].X[3];
double a_track = atan2(t_y0, t_x0); //航迹方位角
double& r_point = point_process[loop_of_point].range; //点迹距离
double a_delta = abs(a_track - point_process[loop_of_point].azimuth); //方位差
if(a_delta > PI){
a_delta = 2*PI - a_delta;
}
double R = sqrt(r_track * r_track + r_point*r_point - 2*r_track*r_point*cos(a_delta));
double VT = v_track*delta_T;
bool thd_r = true;
if(VT < 100 && R > 300){
//VT<100m,使用固定门限300m
thd_r = false;
}else if((VT>=100 && VT<=300 && R/VT>3)){
thd_r = false;
}else if(VT>300 && R > 1200){
thd_r = false;
}
struct Asso_Info associated_info_tmp;
associated_info_tmp.d1=d1;
associated_info_tmp.d2=d2;
associated_info_tmp.d3=d3;
associated_info_tmp.point_index=static_cast<int>(loop_of_point)+1;
associated_info_tmp.track_index=static_cast<int>(loop_of_track)+1;
if(d1<=d2&&d1<=d3)
associated_info_tmp.d_min=d1;
else if(d2<=d1&& d2<=d3)
associated_info_tmp.d_min=d2;
else
associated_info_tmp.d_min=d3;
associated_info_tmp.point_id=point_process[loop_of_point].point_id;
associated_info_tmp.track_id=(*trust_track)[loop_of_track].track_id;
associated_info_tmp.circle_id=point_process[loop_of_point].circle_id;
associated_info_tmp.delta_T=delta_T;
associated_info_tmp.delta_h=delta_h;
associated_info_tmp.R=R;
associated_info_tmp.VT=VT;
associated_info_tmp.tas_flag=point_process[loop_of_point].point_type;
associated_info_tmp.thd_d=thd_d;
associated_info_tmp.thd_h=thd_h;
associated_info_tmp.thd_r=thd_r;
associated_info_tmp.min_d_flag=false;
//小于关联门限 保存关联信息
if(thd_d && thd_h && thd_r)
{
associated_info.push_back(associated_info_tmp);
}else{
#ifdef RDP_RECORD_ENABLE
rec_asso.push_back(associated_info_tmp);
if (rec_asso.size() >= RDP_RECORD_ASSO_CHUNK_ROWS)
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
}
}
}
//最近邻法关联
int associated_num=associated_info.size();
for (int i=0;i<trust_track->size();i++)
int associated_num=static_cast<int>(associated_info.size());
for (size_t i=0;i<trust_track->size();i++)
{
//寻找最小d
if(associated_num>0)
@@ -594,9 +608,9 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
double d3=associated_info[min_index-1].d3;
//卡尔曼滤波
double Z[3]={point_process[point_index-1].Range,point_process[point_index-1].Azimuth,point_process[point_index-1].Velocity};
double Z[3]={point_process[point_index-1].range,point_process[point_index-1].azimuth,point_process[point_index-1].velocity};
double prt = point_process[point_index-1].PRF_index;
double freq_ind = point_process[point_index-1].Freq_index;
double freq_ind = point_process[point_index-1].freq_index;
double X1[6],X2[6],X3[6],P1[6][6],P2[6][6],P3[6][6];
memcpy(X1,(*trust_track)[track_index-1].X1,6*sizeof(double));
memcpy(X2,(*trust_track)[track_index-1].X2,6*sizeof(double));
@@ -605,54 +619,28 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
memcpy(P2,(*trust_track)[track_index-1].P2,6*6*sizeof(double));
memcpy(P3,(*trust_track)[track_index-1].P3,6*6*sizeof(double));
double v_track = sqrt(pow((*trust_track)[track_index-1].X[1],2)+pow((*trust_track)[track_index-1].X[4],2));
double delta_T = (point_process[point_index-1].CPI_Time - (*trust_track)[track_index-1].T_track)/1000.0;
double delta_T = (point_process[point_index-1].CPI_time - (*trust_track)[track_index-1].T_track)/1000.0;
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3);
if (delta_T <= 0)
if (delta_T > 0)
{
// 乱序/重复时间戳:不作为有效关联,不标记点迹已使用、不刷新航迹新鲜度
for (int ii=0;ii<associated_num;)
{
if(associated_info[ii].track_index==track_index)
{
associated_info.erase(associated_info.begin()+ii);
associated_num=associated_info.size();
}
else
ii++;
}
associated_num=associated_info.size();
for (int ii=0;ii<associated_num;)
{
if(associated_info[ii].point_index==point_index)
{
associated_info.erase(associated_info.begin()+ii);
associated_num=associated_info.size();
}
else
ii++;
}
associated_num=associated_info.size();
continue;
}
associated_info[min_index-1].min_d_flag=true;
if(delta_T>0)
{
double X1_filter[6],X2_filter[6],X3_filter[6],P1_filter[6][6],P2_filter[6][6],P3_filter[6][6];
double S1[3][3] = {{0}},S2[3][3] = {{0}},S3[3][3] = {{0}};
kalman Kalman;
Kalman.kalman_filter_EKF(F,Q1,X1,P1,Z,X1_filter,P1_filter,S1, prt, freq_ind );
Kalman.kalman_filter_EKF(F,Q2,X2,P2,Z,X2_filter,P2_filter,S2, prt, freq_ind );
Kalman.kalman_filter_EKF(F,Q3,X3,P3,Z,X3_filter,P3_filter,S3, prt, freq_ind );
Kalman.kalman_filter_EKF(F,Q1,X1,P1,Z,X1_filter,P1_filter,S1, prt, freq_ind);
Kalman.kalman_filter_EKF(F,Q2,X2,P2,Z,X2_filter,P2_filter,S2, prt, freq_ind);
Kalman.kalman_filter_EKF(F,Q3,X3,P3,Z,X3_filter,P3_filter,S3, prt, freq_ind);
Matrix3f S1_out,S2_out,S3_out;
for (int ii=0;ii<3;ii++)
for (int jj=0;jj<3;jj++)
{
S1_out(ii,jj)=S1[ii][jj];
S2_out(ii,jj)=S2[ii][jj];
S3_out(ii,jj)=S3[ii][jj];
S1_out(ii,jj)=static_cast<float>(S1[ii][jj]);
S2_out(ii,jj)=static_cast<float>(S2[ii][jj]);
S3_out(ii,jj)=static_cast<float>(S3[ii][jj]);
}
double det_S1=S1_out.determinant();
double Possibility1=(det_S1>1e-12)?(1.0/sqrt(pow(2*PI,3)*det_S1)*exp(-0.5*d1)):0.0;
@@ -696,126 +684,168 @@ void Track_Asso:: model_filter(std::vector <Trust_Track> *trust_track,struct Ra
(*trust_track)[track_index-1].P3[ii][jj]=P3_filter[ii][jj];
}
//计算航迹质量
double d_tmp = exp(-associated_info[min_index-1].d_min);
double snr_tmp = 1/(1+exp(-(point_process[point_index-1].SNR - 16) / 1.2));
double len_tmp = 1/(1+exp(-((*trust_track)[track_index-1].Valid_point_sum - 4) / 1.2)) * 0.5 + (1 - (*trust_track)[track_index-1].Valid_point_sum / (*trust_track)[track_index-1].Point_sum) * 0.5;
double tq_tmp = rdp_parameter.track_quality_d_weight * d_tmp + rdp_parameter.track_quality_snr_weight * snr_tmp + rdp_parameter.track_quality_len_weight * len_tmp;
(*trust_track)[track_index-1].Quality = (*trust_track)[track_index-1].Quality * rdp_parameter.track_quality_iter_coeff + tq_tmp * (1 - rdp_parameter.track_quality_iter_coeff);
//更新航迹时间
(*trust_track)[track_index-1].T_track = point_process[point_index-1].CPI_Time;
(*trust_track)[track_index-1].T_track = point_process[point_index-1].CPI_time;
(*trust_track)[track_index-1].GNSS_time = point_process[point_index-1].GNSS_time;
(*trust_track)[track_index-1].Unupdated_time = 0;
//更新关联上的点迹信息
(*trust_track)[track_index-1].range_point=point_process[point_index-1].Range;
(*trust_track)[track_index-1].azi_point=point_process[point_index-1].Azimuth/PI*180;
(*trust_track)[track_index-1].elev_point= asin(point_process[point_index-1].Height/point_process[point_index-1].Range)/PI*180;
(*trust_track)[track_index-1].vr_point=point_process[point_index-1].Velocity;
(*trust_track)[track_index-1].point_id = point_process[point_index-1].point_id;
(*trust_track)[track_index-1].circle_id = point_process[point_index-1].circle_id;
(*trust_track)[track_index-1].range_point=point_process[point_index-1].range;
(*trust_track)[track_index-1].azi_point=point_process[point_index-1].azimuth/PI*180;
(*trust_track)[track_index-1].elev_point= asin(point_process[point_index-1].height/point_process[point_index-1].range)/PI*180;
(*trust_track)[track_index-1].vr_point=point_process[point_index-1].velocity;
(*trust_track)[track_index-1].point_type = 0;
(*trust_track)[track_index-1].prf_point = point_process[point_index-1].PRF_index;
(*trust_track)[track_index-1].snr_point = point_process[point_index-1].snr;
(*trust_track)[track_index-1].Amplitude= point_process[point_index-1].Amplitude; //幅度
(*trust_track)[track_index-1].snr_point = point_process[point_index-1].SNR;
(*trust_track)[track_index-1].Amplitude= point_process[point_index-1].amplitude; //幅度
(*trust_track)[track_index-1].RCS = point_process[point_index-1].RCS;
(*trust_track)[track_index-1].amp_asv_point = point_process[point_index-1].amp_asv_point;
(*trust_track)[track_index-1].prn_fftn_point = point_process[point_index-1].prn_fftn_point;
(*trust_track)[track_index-1].pitch_num = point_process[point_index-1].pitch_num;
(*trust_track)[track_index-1].pitch_num = point_process[point_index-1].pitch_beam_index;
std::memcpy((*trust_track)[track_index-1].speed_dim, point_process[point_index-1].speed_dim, sizeof((*trust_track)[track_index-1].speed_dim));
std::memcpy((*trust_track)[track_index-1].range_dim, point_process[point_index-1].range_dim, sizeof((*trust_track)[track_index-1].range_dim));
//更新高度
track_hight_update(track_index,point_index,trust_track);
(*trust_track)[track_index-1].Extrapolate_round = 0; //连续未用实点更新时间
(*trust_track)[track_index-1].point_flag=1; //实点
(*trust_track)[track_index-1].associate_point_number = (*trust_track)[track_index-1].associate_point_number+1; //关联点数+1
(*trust_track)[track_index-1].Track_Update_Flag = 1; //航迹更新标志
(*trust_track)[track_index-1].Point_sum = (*trust_track)[track_index-1].Point_sum + 1;
(*trust_track)[track_index-1].Valid_point_sum = (*trust_track)[track_index-1].Valid_point_sum + 1;
}
(*trust_track)[track_index-1].Extrapolate_round = 0; //连续未用实点更新时间
(*trust_track)[track_index-1].point_flag=1; //实点
(*trust_track)[track_index-1].associate_point_number = (*trust_track)[track_index-1].associate_point_number+1; //关联点数+1
//删除associated_info中关联上的航迹 点迹信息
for (int ii=0;ii<associated_num;)
{
if(associated_info[ii].track_index==track_index)
{
#ifdef RDP_RECORD_ENABLE
rec_asso.push_back(associated_info[ii]);
if (rec_asso.size() >= RDP_RECORD_ASSO_CHUNK_ROWS)
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
associated_info.erase(associated_info.begin()+ii);
associated_num=associated_info.size();
associated_num=static_cast<int>(associated_info.size());
}
else
ii++;
}
associated_num=associated_info.size();
associated_num=static_cast<int>(associated_info.size());
for (int ii=0;ii<associated_num;)
{
if(associated_info[ii].point_index==point_index)
{
#ifdef RDP_RECORD_ENABLE
rec_asso.push_back(associated_info[ii]);
if (rec_asso.size() >= RDP_RECORD_ASSO_CHUNK_ROWS)
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
associated_info.erase(associated_info.begin()+ii);
associated_num=associated_info.size();
associated_num=static_cast<int>(associated_info.size());
}
else
ii++;
}
associated_num=associated_info.size();
associated_num=static_cast<int>(associated_info.size());
//在接收点迹中 将关联上的点迹的使用标记位置为1 等待删除
point_process[point_index-1].Use_Flag = 1;
point_process[point_index-1].use_flag = 1;
}
}
//未关联上的航迹 进行外推 tws
for (int i =0; i<(*trust_track).size();i++ )
for (size_t i =0; i<(*trust_track).size();i++ )
{
if((*trust_track)[i].point_flag == 0 && (*trust_track)[i].manual_tracking_flag == 0)
if((*trust_track)[i].point_flag == 0 && (*trust_track)[i].manual_tracking_flag == 0) //未关联上点迹的航迹进行外推
{
double v_track = sqrt(pow((*trust_track)[i].X[1],2)+pow((*trust_track)[i].X[4],2));
double delta_T;
if(Work_Parameter.work_mode == 0)
double delta_T = 0;
delta_T = rdp_parameter.data_rate_tws;
//外推点数未到上限,则继续外推,否则不外推
if((*trust_track)[i].Extrapolate_round <= rdp_parameter.extrapolate_max)
{
delta_T = Work_Parameter.DATA_RATE_SHORT;
}
else if(Work_Parameter.work_mode == 1)
{
delta_T = Work_Parameter.DATA_RATE_MIDDLE;
}
else if(Work_Parameter.work_mode == 2)
{
delta_T = Work_Parameter.DATA_RATE_FAR;
}
else
{
delta_T = Work_Parameter.DATA_RATE_SHORT;
double v_track = sqrt(pow((*trust_track)[i].X[1],2)+pow((*trust_track)[i].X[4],2));
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T, F, Q1, Q2, Q3);
//航迹X P
double X1[6],X2[6],X3[6],P1[6][6],P2[6][6],P3[6][6];
memcpy(X1,(*trust_track)[i].X1,6*sizeof(double));
memcpy(X2,(*trust_track)[i].X2,6*sizeof(double));
memcpy(X3,(*trust_track)[i].X3,6*sizeof(double));
memcpy(P1,(*trust_track)[i].P1,6*6*sizeof(double));
memcpy(P2,(*trust_track)[i].P2,6*6*sizeof(double));
memcpy(P3,(*trust_track)[i].P3,6*6*sizeof(double));
//外推
double X1_pred[6],X2_pred[6],X3_pred[6],P1_pred[6][6],P2_pred[6][6],P3_pred[6][6];
kalman Kalman;
Kalman.kalman_pred(F, Q1, X1, P1, X1_pred, P1_pred);
Kalman.kalman_pred(F, Q2, X2, P2, X2_pred, P2_pred);
Kalman.kalman_pred(F, Q3, X3, P3, X3_pred, P3_pred);
memcpy((*trust_track)[i].X1,X1_pred,6*sizeof(double));
memcpy((*trust_track)[i].X2,X2_pred,6*sizeof(double));
memcpy((*trust_track)[i].X3,X3_pred,6*sizeof(double));
memcpy((*trust_track)[i].P1,P1_pred,6*6*sizeof(double));
memcpy((*trust_track)[i].P2,P2_pred,6*6*sizeof(double));
memcpy((*trust_track)[i].P3,P3_pred,6*6*sizeof(double));
(*trust_track)[i].circle_id++;
(*trust_track)[i].T_track = static_cast<long long>((*trust_track)[i].T_track+delta_T*1000.0); //更新航迹时间
(*trust_track)[i].GNSS_time = static_cast<long long>((*trust_track)[i].GNSS_time+delta_T*1000.0); //更新航迹时间
(*trust_track)[i].Extrapolate_round =(*trust_track)[i].Extrapolate_round+1; //连续未用实点更新时间
(*trust_track)[i].point_flag=0; //虚点
(*trust_track)[i].Track_Update_Flag = 1; //航迹更新标志
}else{
(*trust_track)[i].Extrapolate_round =(*trust_track)[i].Extrapolate_round+1; //连续未用实点更新时间
(*trust_track)[i].point_flag=0; //虚点
(*trust_track)[i].Track_Update_Flag = 0; //航迹更新标志
}
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T, F, Q1, Q2, Q3,Work_Parameter);
//航迹X P
double X1[6],X2[6],X3[6],P1[6][6],P2[6][6],P3[6][6];
memcpy(X1,(*trust_track)[i].X1,6*sizeof(double));
memcpy(X2,(*trust_track)[i].X2,6*sizeof(double));
memcpy(X3,(*trust_track)[i].X3,6*sizeof(double));
memcpy(P1,(*trust_track)[i].P1,6*6*sizeof(double));
memcpy(P2,(*trust_track)[i].P2,6*6*sizeof(double));
memcpy(P3,(*trust_track)[i].P3,6*6*sizeof(double));
//外推
double X1_pred[6],X2_pred[6],X3_pred[6],P1_pred[6][6],P2_pred[6][6],P3_pred[6][6];
kalman Kalman;
Kalman.kalman_pred(F, Q1, X1, P1, X1_pred, P1_pred);
Kalman.kalman_pred(F, Q2, X2, P2, X2_pred, P2_pred);
Kalman.kalman_pred(F, Q3, X3, P3, X3_pred, P3_pred);
memcpy((*trust_track)[i].X1,X1_pred,6*sizeof(double));
memcpy((*trust_track)[i].X2,X2_pred,6*sizeof(double));
memcpy((*trust_track)[i].X3,X3_pred,6*sizeof(double));
memcpy((*trust_track)[i].P1,P1_pred,6*6*sizeof(double));
memcpy((*trust_track)[i].P2,P2_pred,6*6*sizeof(double));
memcpy((*trust_track)[i].P3,P3_pred,6*6*sizeof(double));
(*trust_track)[i].T_track = (*trust_track)[i].T_track+delta_T*1000.0; //更新航迹时间
(*trust_track)[i].GNSS_time = (*trust_track)[i].GNSS_time+delta_T*1000.0; //更新航迹时间
(*trust_track)[i].Extrapolate_round =(*trust_track)[i].Extrapolate_round+1; //连续未用实点更新时间
(*trust_track)[i].point_flag=0; //虚点
//航迹质量递减
double t = (*trust_track)[i].Unupdated_time / 1000.0;
(*trust_track)[i].Quality -= t * t * rdp_parameter.track_quality_decrease_coeff_o2 + t * rdp_parameter.track_quality_decrease_coeff_o1;
(*trust_track)[i].Unupdated_time += static_cast<long long>(delta_T*1000.0);
t = (*trust_track)[i].Unupdated_time / 1000.0;
(*trust_track)[i].Quality += t * t * rdp_parameter.track_quality_decrease_coeff_o2 + t * rdp_parameter.track_quality_decrease_coeff_o1;
(*trust_track)[i].Quality = (*trust_track)[i].Quality < 0.0 ? 0.0 : (*trust_track)[i].Quality;
(*trust_track)[i].Point_sum = (*trust_track)[i].Point_sum + 1;
}
}
#ifdef RDP_RECORD_ENABLE
//函数返回前把剩余未入队的关联信息 flush 掉(宏内部 std::move,调用后 rec_asso 为空)
if (!rec_asso.empty())
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
}
void Track_Asso:: model_output(std::vector <Trust_Track> *trust_track)
{
for(int loop_of_track=0; loop_of_track<trust_track->size(); loop_of_track++)
for(size_t loop_of_track=0; loop_of_track<trust_track->size(); loop_of_track++)
{
// if((*trust_track)[loop_of_track].manual_tracking_flag==0)
@@ -915,39 +945,39 @@ void Track_Asso:: model_output(std::vector <Trust_Track> *trust_track)
//高度维更新
void Track_Asso:: track_hight_update(int updata_track_index, //更新的航迹号
int asso_point_index, //点迹号
std::vector <Trust_Track> *trust_track //航迹
std::vector <Trust_Track> *trust_track //航迹
)
{
if(updata_track_index<=trust_track->size() && asso_point_index<=point_process.size())
if(updata_track_index<=static_cast<int>(trust_track->size()) && asso_point_index<=static_cast<int>(point_process.size()))
{
(*trust_track)[updata_track_index-1].Hight_smooth.push_back(point_process[asso_point_index-1].Height);
(*trust_track)[updata_track_index-1].Hight_smooth.push_back(point_process[asso_point_index-1].height);
int height_win_length;
if((*trust_track)[updata_track_index-1].range_point <= 1000)
{
height_win_length = H_F_WIN_LEN+2;
height_win_length = rdp_parameter.height_smooth_win+2;
}
else if((*trust_track)[updata_track_index-1].range_point <= 2000 && (*trust_track)[updata_track_index-1].range_point > 1000)
{
height_win_length = H_F_WIN_LEN+3;
height_win_length = rdp_parameter.height_smooth_win+3;
}
else if((*trust_track)[updata_track_index-1].range_point <= 4000 && (*trust_track)[updata_track_index-1].range_point > 2000)
{
height_win_length = H_F_WIN_LEN+4;
height_win_length = rdp_parameter.height_smooth_win+4;
}
else
{
height_win_length = H_F_WIN_LEN+6;
height_win_length = rdp_parameter.height_smooth_win+6;
}
double sum=0;
if((*trust_track)[updata_track_index-1].Hight_smooth.size()<height_win_length)
if((*trust_track)[updata_track_index-1].Hight_smooth.size()<static_cast<size_t>(height_win_length))
{
for (int i=0;i<(*trust_track)[updata_track_index-1].Hight_smooth.size();i++)
for (size_t i=0;i<(*trust_track)[updata_track_index-1].Hight_smooth.size();i++)
{
sum=sum+(*trust_track)[updata_track_index-1].Hight_smooth[i];
@@ -956,10 +986,10 @@ void Track_Asso:: track_hight_update(int updata_
(*trust_track)[updata_track_index-1].Height=sum/(*trust_track)[updata_track_index-1].Hight_smooth.size();
}
else if((*trust_track)[updata_track_index-1].Hight_smooth.size()>=height_win_length)
else if((*trust_track)[updata_track_index-1].Hight_smooth.size()>=static_cast<size_t>(height_win_length))
{
int N=(*trust_track)[updata_track_index-1].Hight_smooth.size();
int N=static_cast<int>((*trust_track)[updata_track_index-1].Hight_smooth.size());
for (int i=0;i<height_win_length;i++)
{
sum=sum+(*trust_track)[updata_track_index-1].Hight_smooth[N-1-i];
+6 -7
View File
@@ -1,4 +1,4 @@
#ifndef TRACK_ASSO_H
#ifndef TRACK_ASSO_H
#define TRACK_ASSO_H
#include "data_process_class_dll.h"
@@ -16,7 +16,7 @@ public:
std::vector <Trust_Track> *trust_track, //航迹文件
struct Track Trust_Track_Output[MAX_TRACK_NUM][10], //更新航迹信息
int *Trust_track_num_Output, //更新航迹数
struct RadarPara Work_Parameter //工作参数
long long latest_timestamp //最新时间戳
);
Track_Asso();
@@ -30,7 +30,7 @@ private:
void model_interaction(std::vector <Trust_Track> *trust_track); //模型交互
void model_filter(std::vector <Trust_Track> *trust_track, //滤波
struct RadarPara Work_Parameter
long long latest_timestamp
);
void model_output(std::vector <Trust_Track> *trust_track ); //模型输出
@@ -41,17 +41,16 @@ private:
double X3[6], double P3[6][6],
double Z[3], double prt,double freq_ind,
double delta_T,
double *d1,double *d2,double *d3,
struct RadarPara Work_Parameter);
double *d1,double *d2,double *d3);
void IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6],struct RadarPara Work_Parameter);
void IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6]);
double Pt[3][3]; //模型转移概率
//高度维更新
void track_hight_update(int updata_track_index, //更新的航迹号
int asso_point_index, //点迹号
std::vector <Trust_Track> *trust_track //航迹
std::vector <Trust_Track> *trust_track //航迹
);
};
#endif // TRACK_ASSO_H
@@ -1,4 +1,4 @@
#include "track_asso_direct_tracking.h"
#include "track_asso_direct_tracking.h"
#include "kalman.h"
#include "coor_trans.h"
#include <cmath>
@@ -11,8 +11,7 @@ using namespace std;
int Track_Asso_Direct_Tracking::track_asso_process_direct_tracking(std::vector<PointRecv> *point_recv, //点迹
Trust_Track *trust_track, //航迹
struct Track Trust_Track_Output[MAX_TRACK_NUM][10], //更新航迹信息
int *Trust_track_num_Output, //更新航迹数
struct RadarPara Work_Parameter //工作参数
int *Trust_track_num_Output //更新航迹数
)
{
@@ -25,7 +24,7 @@ int Track_Asso_Direct_Tracking::track_asso_process_direct_tracking(std::vector<P
//IMM算法
model_interaction(trust_track);
model_filter(trust_track, Work_Parameter);
model_filter(trust_track);
model_output(trust_track);
@@ -38,8 +37,8 @@ int Track_Asso_Direct_Tracking::track_asso_process_direct_tracking(std::vector<P
Trust_Track_Output[*Trust_track_num_Output-1][0].Point_Sum=1;
double x,y;
x=(*trust_track).X[0]+(*trust_track).X[1]*Work_Parameter.Sys_delay;
y=(*trust_track).X[3]+(*trust_track).X[4]*Work_Parameter.Sys_delay;
x=(*trust_track).X[0]+(*trust_track).X[1]*rdp_parameter.radar_system_delay;
y=(*trust_track).X[3]+(*trust_track).X[4]*rdp_parameter.radar_system_delay;
coor_trans Coor_trans;
double r, azi;
Coor_trans.cart2polar(x, y, &r, &azi);
@@ -58,7 +57,7 @@ int Track_Asso_Direct_Tracking::track_asso_process_direct_tracking(std::vector<P
Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Mode=(*trust_track).Track_Mode;
Trust_Track_Output[*Trust_track_num_Output-1][0].track_time=(*trust_track).T_track/1000.0;
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Point = (*trust_track).point_flag;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=(*trust_track).Height+Work_Parameter.Height;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=(*trust_track).Height+rdp_parameter.radar_height;
double Direction_Angle;
Direction_Angle=atan2((*trust_track).X[4], (*trust_track).X[1]);
@@ -185,8 +184,7 @@ void Track_Asso_Direct_Tracking::model_interaction(Trust_Track *trust_track)
}
void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track, //滤波
struct RadarPara Work_Parameter)
void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track) //滤波
{
//存储关联信息
struct asso_info
@@ -223,18 +221,18 @@ void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track,
//量测信息
// double Z[2]={point_process[loop_of_point].Range*cos(point_process[loop_of_point].Azimuth),
// point_process[loop_of_point].Range*sin(point_process[loop_of_point].Azimuth)};
double Z[3] = {point_process[loop_of_point].Range,
point_process[loop_of_point].Azimuth,
point_process[loop_of_point].Velocity};
double Z[3] = {point_process[loop_of_point].range,
point_process[loop_of_point].azimuth,
point_process[loop_of_point].velocity};
// double v_point = point_process[loop_of_point].Velocity;
double prt = point_process[loop_of_point].PRF_index;
double freq_ind = point_process[loop_of_point].Freq_index;
double delta_T = (point_process[loop_of_point].CPI_Time - T_track)/1000.0;
double h_point = point_process[loop_of_point].Height;
double freq_ind = point_process[loop_of_point].freq_index;
double delta_T = (point_process[loop_of_point].CPI_time - T_track)/1000.0;
double h_point = point_process[loop_of_point].height;
//计算统计距离d
double d1,d2,d3;
IMM_d_cal(v_track,X1,P1,X2,P2, X3,P3,Z,prt,freq_ind,delta_T,&d1,&d2,&d3,Work_Parameter);
IMM_d_cal(v_track,X1,P1,X2,P2, X3,P3,Z,prt,freq_ind,delta_T,&d1,&d2,&d3);
//计算高度门限
double d_h;
@@ -290,7 +288,7 @@ void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track,
}
//小于关联门限 保存关联信息
if((d1*d1<ASSO_THORD*ASSO_THORD || d2*d2<ASSO_THORD*ASSO_THORD || d3*d3<ASSO_THORD*ASSO_THORD)
if((d1*d1<rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d2*d2<rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d3*d3<rdp_parameter.associate_threshold*rdp_parameter.associate_threshold)
&& d_h == 1 )
{
struct asso_info associated_info_tmp;
@@ -329,15 +327,15 @@ void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track,
double d3=associated_info[min_index-1].d3;
//卡尔曼滤波
double Z[3]={point_process[point_index-1].Range,point_process[point_index-1].Azimuth,point_process[point_index-1].Velocity};
double Z[3]={point_process[point_index-1].range,point_process[point_index-1].azimuth,point_process[point_index-1].velocity};
double prt = point_process[point_index-1].PRF_index;
double freq_ind = point_process[point_index-1].Freq_index;
double freq_ind = point_process[point_index-1].freq_index;
// double Z[2];
// Z[0]=point_process[point_index-1].Range*cos(point_process[point_index-1].Azimuth);
// Z[1]=point_process[point_index-1].Range*sin(point_process[point_index-1].Azimuth);
double delta_T = (point_process[point_index-1].CPI_Time - T_track)/1000.0;
double delta_T = (point_process[point_index-1].CPI_time - T_track)/1000.0;
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3);
double X1_filter[6],X2_filter[6],X3_filter[6],P1_filter[6][6],P2_filter[6][6],P3_filter[6][6];
double S1[3][3] = {{0}},S2[3][3] = {{0}},S3[3][3] = {{0}};
@@ -345,9 +343,9 @@ void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track,
// Kalman.kalman_filter(F,Q1,X1,P1,Z,X1_filter,P1_filter,S1);
// Kalman.kalman_filter(F,Q2,X2,P2,Z,X2_filter,P2_filter,S2);
// Kalman.kalman_filter(F,Q3,X3,P3,Z,X3_filter,P3_filter,S3);
Kalman.kalman_filter_EKF(F,Q1,X1,P1,Z,X1_filter,P1_filter,S1, prt, freq_ind );
Kalman.kalman_filter_EKF(F,Q2,X2,P2,Z,X2_filter,P2_filter,S2, prt, freq_ind );
Kalman.kalman_filter_EKF(F,Q3,X3,P3,Z,X3_filter,P3_filter,S3, prt, freq_ind );
Kalman.kalman_filter_EKF(F,Q1,X1,P1,Z,X1_filter,P1_filter,S1, prt, freq_ind);
Kalman.kalman_filter_EKF(F,Q2,X2,P2,Z,X2_filter,P2_filter,S2, prt, freq_ind);
Kalman.kalman_filter_EKF(F,Q3,X3,P3,Z,X3_filter,P3_filter,S3, prt, freq_ind);
Matrix3f S1_out,S2_out,S3_out;
for (int ii=0;ii<3;ii++)
for (int jj=0;jj<3;jj++)
@@ -401,19 +399,19 @@ void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track,
}
//更新航迹时间
(*trust_track).T_track = point_process[point_index-1].CPI_Time;
(*trust_track).T_track = point_process[point_index-1].CPI_time;
//更新关联上的点迹信息
(*trust_track).range_point=point_process[point_index-1].Range;
(*trust_track).azi_point=point_process[point_index-1].Azimuth/PI*180;
(*trust_track).elev_point= asin(point_process[point_index-1].Height/point_process[point_index-1].Range)/PI*180;
(*trust_track).vr_point=point_process[point_index-1].Velocity;
(*trust_track).range_point=point_process[point_index-1].range;
(*trust_track).azi_point=point_process[point_index-1].azimuth/PI*180;
(*trust_track).elev_point= asin(point_process[point_index-1].height/point_process[point_index-1].range)/PI*180;
(*trust_track).vr_point=point_process[point_index-1].velocity;
(*trust_track).point_type = 1;
(*trust_track).prf_point = point_process[point_index-1].PRF_index;
(*trust_track).snr_point = point_process[point_index-1].snr;
(*trust_track).snr_point = point_process[point_index-1].SNR;
(*trust_track).Extrapolate_round = 0; //连续未用实点更新时间
(*trust_track).point_flag=1; //实点
(*trust_track).Amplitude= point_process[point_index-1].Amplitude; //幅度
(*trust_track).Amplitude= point_process[point_index-1].amplitude; //幅度
(*trust_track).associate_point_number = (*trust_track).associate_point_number+1; //关联点数+1
(*trust_track).RCS = point_process[point_index-1].RCS;
@@ -424,9 +422,9 @@ void Track_Asso_Direct_Tracking::model_filter(Trust_Track *trust_track,
//未关联上,航迹外推
else
{
double delta_T = DATA_RATE_TAS;
double delta_T = rdp_parameter.data_rate_tas/1000.0;
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T, F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T, F, Q1, Q2, Q3);
double X1_pred[6],X2_pred[6],X3_pred[6],P1_pred[6][6],P2_pred[6][6],P3_pred[6][6];
kalman Kalman;
@@ -510,7 +508,7 @@ void Track_Asso_Direct_Tracking::model_output(Trust_Track *trust_track)
}
// 产生 F Q 矩阵
void Track_Asso_Direct_Tracking::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6],struct RadarPara Work_Parameter)
void Track_Asso_Direct_Tracking::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6])
{
//F
@@ -538,7 +536,7 @@ void Track_Asso_Direct_Tracking::IMM_F_Q_gen(double v_track, double delta_T,doub
//模型1
//Q
double q1=Work_Parameter.Model1_Q_slow;
double q1=rdp_parameter.model_1_Q_slow;
double q111=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
double q121=1/(2*pow(alpha,4))*(exp(-2*alpha*delta_T)+1-2*exp(-alpha*delta_T)+2*alpha*delta_T*exp(-alpha*delta_T)-2*alpha*delta_T+pow(alpha,2)*pow(delta_T,2));
double q131=1/(2*pow(alpha,3))*(1-exp(-2*alpha*delta_T)-2*alpha*delta_T*exp(-alpha*delta_T));
@@ -559,15 +557,15 @@ void Track_Asso_Direct_Tracking::IMM_F_Q_gen(double v_track, double delta_T,doub
double q2;
if(v_track<=5)
{
q2=Work_Parameter.Model2_Q_slow/20;
q2=rdp_parameter.model_2_Q_slow/20;
}
else if(v_track<=100&&v_track>50)
{
q2=10.0*Work_Parameter.Model2_Q_fast;
q2=10.0*rdp_parameter.model_2_Q_fast;
}
else
{
q2=Work_Parameter.Model2_Q_fast;
q2=rdp_parameter.model_2_Q_fast;
}
double q112=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
@@ -591,15 +589,15 @@ void Track_Asso_Direct_Tracking::IMM_F_Q_gen(double v_track, double delta_T,doub
double q3;
if(v_track<=5)
{
q3=Work_Parameter.Model3_Q_slow/20;
q3=rdp_parameter.model_3_Q_slow/20;
}
else if(v_track<=100&&v_track>50)
{
q3=10.0*Work_Parameter.Model3_Q_fast;
q3=10.0*rdp_parameter.model_3_Q_fast;
}
else
{
q3=Work_Parameter.Model3_Q_fast;
q3=rdp_parameter.model_3_Q_fast;
}
double q113=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
double q123=1/(2*pow(alpha,4))*(exp(-2*alpha*delta_T)+1-2*exp(-alpha*delta_T)+2*alpha*delta_T*exp(-alpha*delta_T)-2*alpha*delta_T+pow(alpha,2)*pow(delta_T,2));
@@ -626,11 +624,10 @@ void Track_Asso_Direct_Tracking::IMM_d_cal(double v_track,
double X3[6], double P3[6][6],
double Z[3], double prt,double freq_ind,
double delta_T,
double *d1,double *d2,double *d3,
struct RadarPara Work_Parameter)
double *d1,double *d2,double *d3)
{
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3);
kalman Kalman;
// *d1=Kalman.d_cal_with_doppler(F,Q1,Z,X1,P1,v_point,prt,freq_ind);
@@ -644,28 +641,28 @@ void Track_Asso_Direct_Tracking::IMM_d_cal(double v_track,
//高度维更新
void Track_Asso_Direct_Tracking::track_hight_update(int asso_point_index, //点迹号
Trust_Track *trust_track //航迹
Trust_Track *trust_track //航迹
)
{
(*trust_track).Hight_smooth.push_back(point_process[asso_point_index-1].Height);
(*trust_track).Hight_smooth.push_back(point_process[asso_point_index-1].height);
int height_win_length;
if((*trust_track).range_point <= 1000)
{
height_win_length = H_F_WIN_LEN+2;
height_win_length = rdp_parameter.height_smooth_win+2;
}
else if((*trust_track).range_point <= 2000 && (*trust_track).range_point > 1000)
{
height_win_length = H_F_WIN_LEN+3;
height_win_length = rdp_parameter.height_smooth_win+3;
}
else if((*trust_track).range_point <= 4000 && (*trust_track).range_point > 2000)
{
height_win_length = H_F_WIN_LEN+4;
height_win_length = rdp_parameter.height_smooth_win+4;
}
else
{
height_win_length = H_F_WIN_LEN+6;
height_win_length = rdp_parameter.height_smooth_win+6;
}
double sum=0;
@@ -1,4 +1,4 @@
#ifndef TRACK_ASSO_DIRECT_TRACKING_H
#ifndef TRACK_ASSO_DIRECT_TRACKING_H
#define TRACK_ASSO_DIRECT_TRACKING_H
#include "data_process_class_dll.h"
#include "parameters.h"
@@ -12,8 +12,7 @@ class Track_Asso_Direct_Tracking
int track_asso_process_direct_tracking(std::vector<PointRecv> *point_recv, //点迹
Trust_Track *trust_track, //航迹
struct Track Trust_Track_Output[MAX_TRACK_NUM][10], //更新航迹信息
int *Trust_track_num_Output, //更新航迹数
struct RadarPara Work_Parameter //工作参数
int *Trust_track_num_Output //更新航迹数
);
Track_Asso_Direct_Tracking();
@@ -27,8 +26,7 @@ class Track_Asso_Direct_Tracking
//IMM
void model_interaction(Trust_Track *trust_track); //模型交互
void model_filter(Trust_Track *trust_track, //滤波
struct RadarPara Work_Parameter
void model_filter(Trust_Track *trust_track //滤波
);
void model_output(Trust_Track *trust_track); //模型输出
@@ -39,16 +37,15 @@ class Track_Asso_Direct_Tracking
double X3[6], double P3[6][6],
double Z[3], double prt,double freq_ind,
double delta_T,
double *d1,double *d2,double *d3,
struct RadarPara Work_Parameter);
double *d1,double *d2,double *d3);
void IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6],struct RadarPara Work_Parameter);
void IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6]);
double Pt[3][3]; //模型转移概率
//高度维更新
void track_hight_update(int asso_point_index, //点迹号
Trust_Track *trust_track //航迹
Trust_Track *trust_track //航迹
);
};
+234 -188
View File
@@ -1,10 +1,14 @@
#include "track_asso_tas.h"
#include "track_asso_tas.h"
#include "kalman.h"
#include "coor_trans.h"
#include "rdp_log.h"
#include <cmath>
#include <cstring>
#include <vector>
#include <Eigen/Dense>
#include "rdp_record.h"
using namespace Eigen;
using namespace std;
@@ -12,7 +16,6 @@ int Track_Asso_Tas::track_asso_process_tas(std::vector<PointRecv>
std::vector<Trust_Track> *trust_track, //航迹文件
struct Track Trust_Track_Output[MAX_TRACK_NUM][10], //更新航迹信息
int *Trust_track_num_Output, //更新航迹数
struct RadarPara Work_Parameter, //工作参数
int tas_track_idx,
long long latest_timestamp //最新时间戳
)
@@ -20,7 +23,7 @@ long long latest_timestamp //最新时间戳
{
//取出点迹
for (int i=0;i< point_recv_tas->size();i++)
for (size_t i=0;i< point_recv_tas->size();i++)
{
point_process.push_back((*point_recv_tas)[i]);
}
@@ -28,7 +31,7 @@ long long latest_timestamp //最新时间戳
//IMM算法
model_interaction(trust_track,tas_track_idx);
model_filter(trust_track, Work_Parameter,tas_track_idx, latest_timestamp);
model_filter(trust_track,tas_track_idx, latest_timestamp);
model_output(trust_track,tas_track_idx);
@@ -36,63 +39,100 @@ long long latest_timestamp //最新时间戳
std::vector<PointRecv>().swap(point_process);
//输出航迹
for (int i=0;i<trust_track->size();i++)
#ifdef RDP_RECORD_ENABLE
//记录可靠航迹:先收集,输出循环结束后一次入队
std::vector<Trust_Track> rec_trust;
rec_trust.reserve(trust_track->size());
#endif
for (size_t i=0;i<trust_track->size();i++)
{ // 输出更新航迹条件:
if((*trust_track)[i].Track_Index == tas_track_idx)
if((*trust_track)[i].Track_Index == tas_track_idx && (*trust_track)[i].Track_Update_Flag == 1)
{
(*trust_track)[i].Track_Update_Flag = 0; //航迹更新标志
*Trust_track_num_Output= *Trust_track_num_Output+1;
Trust_Track_Output[*Trust_track_num_Output-1][0].Point_Sum=1;
double x,y;
x=(*trust_track)[i].X[0]+(*trust_track)[i].X[1]*Work_Parameter.Sys_delay;
y=(*trust_track)[i].X[3]+(*trust_track)[i].X[4]*Work_Parameter.Sys_delay;
x=(*trust_track)[i].X[0]+(*trust_track)[i].X[1]*rdp_parameter.radar_system_delay;
y=(*trust_track)[i].X[3]+(*trust_track)[i].X[4]*rdp_parameter.radar_system_delay;
coor_trans Coor_trans;
double r, azi;
Coor_trans.cart2polar(x, y, &r, &azi);
Trust_Track_Output[*Trust_track_num_Output-1][0].Range=r;
Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth=azi/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][0].Range=static_cast<float>(r);
Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth=static_cast<float>(azi/PI*180);
{
double elev_ratio = (r > 0.0) ? (*trust_track)[i].Height / r : 0.0;
if (elev_ratio > 1.0) elev_ratio = 1.0;
if (elev_ratio < -1.0) elev_ratio = -1.0;
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = asin(elev_ratio)/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation = static_cast<float>(asin(elev_ratio)/PI*180);
}
Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Index=(*trust_track)[i].Track_Index;
Trust_Track_Output[*Trust_track_num_Output-1][0].Range_V =
sqrt((*trust_track)[i].X[1]*(*trust_track)[i].X[1]+(*trust_track)[i].X[4]*(*trust_track)[i].X[4]);
Trust_Track_Output[*Trust_track_num_Output-1][0].Amplitude = (*trust_track)[i].Amplitude;
static_cast<float>(sqrt((*trust_track)[i].X[1]*(*trust_track)[i].X[1]+(*trust_track)[i].X[4]*(*trust_track)[i].X[4]));
Trust_Track_Output[*Trust_track_num_Output-1][0].Amplitude = static_cast<float>((*trust_track)[i].Amplitude);
Trust_Track_Output[*Trust_track_num_Output-1][0].Track_Mode=(*trust_track)[i].Track_Mode;
Trust_Track_Output[*Trust_track_num_Output-1][0].track_time=(*trust_track)[i].T_track/1000.0;
Trust_Track_Output[*Trust_track_num_Output-1][0].track_time=static_cast<float>((*trust_track)[i].T_track/1000.0);
Trust_Track_Output[*Trust_track_num_Output-1][0].GNSS_time=(*trust_track)[i].GNSS_time;
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Point = (*trust_track)[i].point_flag;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=(*trust_track)[i].Height+Work_Parameter.Height;
Trust_Track_Output[*Trust_track_num_Output-1][0].z=static_cast<float>((*trust_track)[i].Height+rdp_parameter.radar_height);
double Direction_Angle;
Direction_Angle=atan2((*trust_track)[i].X[4], (*trust_track)[i].X[1]);
if(Direction_Angle<0){Direction_Angle=Direction_Angle+2*PI;}
Trust_Track_Output[*Trust_track_num_Output-1][0].Direction_Angle=Direction_Angle/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][0].Direction_Angle=static_cast<float>(Direction_Angle/PI*180);
//关联点信息
Trust_Track_Output[*Trust_track_num_Output-1][0].range_point=(*trust_track)[i].range_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].azi_point=(*trust_track)[i].azi_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].elev_point=(*trust_track)[i].elev_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].vr_point=(*trust_track)[i].vr_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].range_point=static_cast<float>((*trust_track)[i].range_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].azi_point=static_cast<float>((*trust_track)[i].azi_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].elev_point=static_cast<float>((*trust_track)[i].elev_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].vr_point=static_cast<float>((*trust_track)[i].vr_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].point_type=1;
Trust_Track_Output[*Trust_track_num_Output-1][0].prf_point = (*trust_track)[i].prf_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].track_snr = (*trust_track)[i].snr_point;
Trust_Track_Output[*Trust_track_num_Output-1][0].x=(*trust_track)[i].X[0];
Trust_Track_Output[*Trust_track_num_Output-1][0].y=(*trust_track)[i].X[3];
Trust_Track_Output[*Trust_track_num_Output-1][0].v_x=(*trust_track)[i].X[1];
Trust_Track_Output[*Trust_track_num_Output-1][0].v_y=(*trust_track)[i].X[4];
Trust_Track_Output[*Trust_track_num_Output-1][0].track_rcs = (*trust_track)[i].RCS;
Trust_Track_Output[*Trust_track_num_Output-1][0].prf_point = static_cast<float>((*trust_track)[i].prf_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].track_snr = static_cast<float>((*trust_track)[i].snr_point);
Trust_Track_Output[*Trust_track_num_Output-1][0].x=static_cast<float>((*trust_track)[i].X[0]);
Trust_Track_Output[*Trust_track_num_Output-1][0].y=static_cast<float>((*trust_track)[i].X[3]);
Trust_Track_Output[*Trust_track_num_Output-1][0].v_x=static_cast<float>((*trust_track)[i].X[1]);
Trust_Track_Output[*Trust_track_num_Output-1][0].v_y=static_cast<float>((*trust_track)[i].X[4]);
Trust_Track_Output[*Trust_track_num_Output-1][0].track_rcs = static_cast<float>((*trust_track)[i].RCS);
Trust_Track_Output[*Trust_track_num_Output-1][0].pitch_num = (*trust_track)[i].pitch_num;
// Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=Trust_Track_Output[*Trust_track_num_Output-1][0].vr_point>0?1:0;
if(acos((-x * cos(Direction_Angle) - y * sin(Direction_Angle)) / r ) < PI / 2)
{
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=1;
// if(r*abs(sin(azi - Direction_Angle))<500)
// {
// Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=1;
// }
// else
// {
// Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=0;
// }
}
else
{
Trust_Track_Output[*Trust_track_num_Output-1][0].Flag_Direction=0;
}
std::memcpy(Trust_Track_Output[*Trust_track_num_Output-1][0].speed_dim, (*trust_track)[i].speed_dim, sizeof(Trust_Track_Output[*Trust_track_num_Output-1][0].speed_dim));
std::memcpy(Trust_Track_Output[*Trust_track_num_Output-1][0].range_dim, (*trust_track)[i].range_dim, sizeof(Trust_Track_Output[*Trust_track_num_Output-1][0].range_dim));
(*trust_track)[i].Range = Trust_Track_Output[*Trust_track_num_Output-1][0].Range;
(*trust_track)[i].Azimuth = Trust_Track_Output[*Trust_track_num_Output-1][0].Azimuth;
(*trust_track)[i].Elevation = Trust_Track_Output[*Trust_track_num_Output-1][0].Elevation;
(*trust_track)[i].Velocity = Trust_Track_Output[*Trust_track_num_Output-1][0].Range_V;
#ifdef RDP_RECORD_ENABLE
rec_trust.push_back((*trust_track)[i]);
#endif
}
}
#ifdef RDP_RECORD_ENABLE
RDP_RECORD_TRUST_TRACKS(rec_trust);
#endif
return 0;
}
@@ -101,7 +141,7 @@ long long latest_timestamp //最新时间戳
void Track_Asso_Tas:: model_interaction( std::vector<Trust_Track> *trust_track, int tas_track_idx)
{
for (int loop_of_track=0;loop_of_track<trust_track->size();loop_of_track++)
for (size_t loop_of_track=0;loop_of_track<trust_track->size();loop_of_track++)
{
if((*trust_track)[loop_of_track].Track_Index == tas_track_idx)
@@ -215,21 +255,16 @@ void Track_Asso_Tas:: model_interaction( std::vector<Trust_Track> *trust_track
}
void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *trust_track, //滤波
struct RadarPara Work_Parameter,
int tas_track_idx,
long long latest_timestamp //最新时间戳
)
{
//存储关联信息
struct asso_info
{
double d1;
double d2;
double d3;
double d_min;
int point_index;
};
std::vector <asso_info> associated_info;
std::vector <Asso_Info> associated_info;
#ifdef RDP_RECORD_ENABLE
//记录未关联上的点航信息:收集到分块上限即入队,函数末尾再 flush 剩余
std::vector<Asso_Info> rec_asso;
rec_asso.reserve(point_process.size());
#endif
//航迹信息
double X1[6] = {0},X2[6] = {0},X3[6] = {0},P1[6][6] = {{0}},P2[6][6] = {{0}},P3[6][6] = {{0}};
@@ -238,17 +273,12 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
double r_track = 0;
double h_track = 0;
bool found_tas_track = false;
for (int loop_of_track=0;loop_of_track<trust_track->size();loop_of_track++)
for (size_t loop_of_track=0;loop_of_track<trust_track->size();loop_of_track++)
{
if((*trust_track)[loop_of_track].Track_Index == tas_track_idx)
{
found_tas_track = true;
// if(point_process.size()>0)
// {
// std::cout << "model_filter point_process :" <<point_process[0].Azimuth/PI*180<<" "<<point_process[0].Range;
// }
(*trust_track)[loop_of_track].point_flag = 0; //航迹的point_flag置为0 关联上点后再置为1
memcpy(X1,(*trust_track)[loop_of_track].X1,6*sizeof(double));
memcpy(X2,(*trust_track)[loop_of_track].X2,6*sizeof(double));
@@ -257,7 +287,7 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
memcpy(P2,(*trust_track)[loop_of_track].P2,6*6*sizeof(double));
memcpy(P3,(*trust_track)[loop_of_track].P3,6*6*sizeof(double));
T_track = (*trust_track)[loop_of_track].T_track;
T_track = static_cast<double>((*trust_track)[loop_of_track].T_track);
v_track = sqrt(pow((*trust_track)[loop_of_track].X[1],2)+pow((*trust_track)[loop_of_track].X[4],2));
r_track = sqrt(pow((*trust_track)[loop_of_track].X[0],2)+pow((*trust_track)[loop_of_track].X[3],2));
h_track = (*trust_track)[loop_of_track].Height;
@@ -269,119 +299,105 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
return;
// 计算量测和航迹统计距离
for (int loop_of_point = 0; loop_of_point<point_process.size();loop_of_point++)
for (size_t loop_of_point = 0; loop_of_point<point_process.size();loop_of_point++)
{
//量测信息
// double Z[2]={point_process[loop_of_point].Range*cos(point_process[loop_of_point].Azimuth),
// point_process[loop_of_point].Range*sin(point_process[loop_of_point].Azimuth)};
double Z[3] = {point_process[loop_of_point].Range,
point_process[loop_of_point].Azimuth,
point_process[loop_of_point].Velocity};
double Z[3] = {point_process[loop_of_point].range,
point_process[loop_of_point].azimuth,
point_process[loop_of_point].velocity};
// double v_point = point_process[loop_of_point].Velocity;
double prt = point_process[loop_of_point].PRF_index;
double freq_ind = point_process[loop_of_point].Freq_index;
double delta_T = (point_process[loop_of_point].CPI_Time - T_track)/1000.0;
if (delta_T <= 0)
double freq_ind = point_process[loop_of_point].freq_index;
double delta_T = (point_process[loop_of_point].CPI_time - T_track)/1000.0;
if (delta_T <= 0){
RDP_LOG_WARN("Warning: delta_T <= 0, skipping point association for point index {} {}", point_process[loop_of_point].CPI_time, T_track);
continue;
double h_point = point_process[loop_of_point].Height;
}
double h_point = point_process[loop_of_point].height;
//计算统计距离d
double d1,d2,d3;
IMM_d_cal(v_track,X1,P1,X2,P2, X3,P3,Z,prt,freq_ind,delta_T,&d1,&d2,&d3,Work_Parameter);
IMM_d_cal(v_track,X1,P1,X2,P2, X3,P3,Z,prt,freq_ind,delta_T,&d1,&d2,&d3);
//计算高度门限
double d_h;
if (r_track<1000)
{
if(abs(h_track-h_point)<=150)
{
d_h = 1;
}
else
{
d_h = 0;
}
//判定统计距离门限,thd_d
bool thd_d = false;
if(d1*d1<rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d2*d2<rdp_parameter.associate_threshold*rdp_parameter.associate_threshold || d3*d3<rdp_parameter.associate_threshold*rdp_parameter.associate_threshold){
thd_d = true;
}
else if (r_track<2000 && r_track>=1000)
{
if(abs(h_track-h_point)<=200)
{
d_h = 1;
}
else
{
d_h = 0;
}
//判定高度门限,thd_h
bool thd_h = false;
double delta_h = abs(h_track-h_point);
if( (r_track <= 1000 && delta_h <= 150) || \
((r_track > 1000 && r_track <= 2000) && delta_h <= 200) || \
((r_track > 2000 && r_track <= 3000) && delta_h <= 250) || \
((r_track > 3000 && r_track <= 5000) && delta_h <= 300) || \
r_track > 5000
){
thd_h = true;
}
else if(r_track>=2000 && r_track<3000 )
{
if(abs(h_track-h_point)<=200)
{
d_h = 1;
}
else
{
d_h = 0;
}
}
else if(r_track>=3000 && r_track<5000)
{
if(abs(h_track-h_point)<=300)
{
d_h = 1;
}
else
{
d_h = 0;
}
}
struct Asso_Info associated_info_tmp;
associated_info_tmp.d1=d1;
associated_info_tmp.d2=d2;
associated_info_tmp.d3=d3;
associated_info_tmp.point_index=static_cast<int>(loop_of_point)+1;
associated_info_tmp.track_index=tas_track_idx;
if(d1<=d2&&d1<=d3)
associated_info_tmp.d_min=d1;
else if(d2<=d1&& d2<=d3)
associated_info_tmp.d_min=d2;
else
{
d_h = 1;
}
associated_info_tmp.d_min=d3;
associated_info_tmp.point_id=point_process[loop_of_point].point_id;
associated_info_tmp.track_id=(*trust_track)[tas_track_idx].track_id;
associated_info_tmp.circle_id=point_process[loop_of_point].circle_id;
associated_info_tmp.delta_T=delta_T;
associated_info_tmp.delta_h=delta_h;
associated_info_tmp.R=0;
associated_info_tmp.VT=0;
associated_info_tmp.tas_flag=point_process[loop_of_point].point_type;
associated_info_tmp.thd_d=thd_d;
associated_info_tmp.thd_h=thd_h;
associated_info_tmp.thd_r=true;
associated_info_tmp.min_d_flag=false;
// std::cout << "T d" <<point_process[loop_of_point].CPI_Time/1000<<" "<<T_track/1000<<" "<<delta_T<<" "<<d1<<" "<<d2<<" "<<d3;
// std::cout << "h_track" <<h_track<<" h_point"<<h_point<<" d_h"<<d_h;
//小于关联门限 保存关联信息
if((d1*d1<ASSO_THORD*ASSO_THORD || d2*d2<ASSO_THORD*ASSO_THORD || d3*d3<ASSO_THORD*ASSO_THORD)
&& d_h == 1)//
if(thd_d && thd_h)
{
// std::cout << "TAS asoooooo111" ;
struct asso_info associated_info_tmp;
associated_info_tmp.d1=d1;
associated_info_tmp.d2=d2;
associated_info_tmp.d3=d3;
associated_info_tmp.point_index=loop_of_point+1;
if(d1<=d2&&d1<=d3)
associated_info_tmp.d_min=d1;
else if(d2<=d1&& d2<=d3)
associated_info_tmp.d_min=d2;
else
associated_info_tmp.d_min=d3;
associated_info.push_back(associated_info_tmp);
}else{
#ifdef RDP_RECORD_ENABLE
rec_asso.push_back(associated_info_tmp);
if (rec_asso.size() >= RDP_RECORD_ASSO_CHUNK_ROWS)
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
}
}
//有关联 滤波
if (associated_info.size()>0)
{
//RDP_LOG_DEBUG("associated point for track index {}", tas_track_idx);
// std::cout << "TAS asoooooo222" ;
//找最近点
int min_index=1;
double min_d=associated_info[0].d_min;
for (int ii=0;ii<associated_info.size();ii++)
for (size_t ii=0;ii<associated_info.size();ii++)
{
if(associated_info[ii].d_min<min_d)
{
min_d=associated_info[ii].d_min;
min_index=ii+1;
min_index=static_cast<int>(ii)+1;
}
}
int point_index=associated_info[min_index-1].point_index;
@@ -389,16 +405,18 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
double d2=associated_info[min_index-1].d2;
double d3=associated_info[min_index-1].d3;
associated_info[min_index-1].min_d_flag=true;
//卡尔曼滤波
double Z[3]={point_process[point_index-1].Range,point_process[point_index-1].Azimuth,point_process[point_index-1].Velocity};
double Z[3]={point_process[point_index-1].range,point_process[point_index-1].azimuth,point_process[point_index-1].velocity};
double prt = point_process[point_index-1].PRF_index;
double freq_ind = point_process[point_index-1].Freq_index;
double freq_ind = point_process[point_index-1].freq_index;
// double Z[2];
// Z[0]=point_process[point_index-1].Range*cos(point_process[point_index-1].Azimuth);
// Z[1]=point_process[point_index-1].Range*sin(point_process[point_index-1].Azimuth);
double delta_T = (point_process[point_index-1].CPI_Time - T_track)/1000.0;
double delta_T = (point_process[point_index-1].CPI_time - T_track)/1000.0;
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3);
double X1_filter[6],X2_filter[6],X3_filter[6],P1_filter[6][6],P2_filter[6][6],P3_filter[6][6];
double S1[3][3] = {{0}},S2[3][3] = {{0}},S3[3][3] = {{0}};
@@ -406,16 +424,16 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
// Kalman.kalman_filter(F,Q1,X1,P1,Z,X1_filter,P1_filter,S1);
// Kalman.kalman_filter(F,Q2,X2,P2,Z,X2_filter,P2_filter,S2);
// Kalman.kalman_filter(F,Q3,X3,P3,Z,X3_filter,P3_filter,S3);
Kalman.kalman_filter_EKF(F,Q1,X1,P1,Z,X1_filter,P1_filter,S1, prt, freq_ind );
Kalman.kalman_filter_EKF(F,Q2,X2,P2,Z,X2_filter,P2_filter,S2, prt, freq_ind );
Kalman.kalman_filter_EKF(F,Q3,X3,P3,Z,X3_filter,P3_filter,S3, prt, freq_ind );
Kalman.kalman_filter_EKF(F,Q1,X1,P1,Z,X1_filter,P1_filter,S1, prt, freq_ind);
Kalman.kalman_filter_EKF(F,Q2,X2,P2,Z,X2_filter,P2_filter,S2, prt, freq_ind);
Kalman.kalman_filter_EKF(F,Q3,X3,P3,Z,X3_filter,P3_filter,S3, prt, freq_ind);
Matrix3f S1_out,S2_out,S3_out;
for (int ii=0;ii<3;ii++)
for (int jj=0;jj<3;jj++)
{
S1_out(ii,jj)=S1[ii][jj];
S2_out(ii,jj)=S2[ii][jj];
S3_out(ii,jj)=S3[ii][jj];
S1_out(ii,jj)=static_cast<float>(S1[ii][jj]);
S2_out(ii,jj)=static_cast<float>(S2[ii][jj]);
S3_out(ii,jj)=static_cast<float>(S3[ii][jj]);
}
double det_S1=S1_out.determinant();
double Possibility1=(det_S1>1e-12)?(1.0/sqrt(pow(2*PI,3)*det_S1)*exp(-0.5*d1)):0.0;
@@ -425,7 +443,7 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
double Possibility3=(det_S3>1e-12)?(1.0/sqrt(pow(2*PI,3)*det_S3)*exp(-0.5*d3)):0.0;
//更新航迹
for(int i=0;i<trust_track->size();i++)
for(size_t i=0;i<trust_track->size();i++){
if((*trust_track)[i].Track_Index == tas_track_idx)
{
@@ -465,24 +483,27 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
}
//更新航迹时间
(*trust_track)[i].T_track = point_process[point_index-1].CPI_Time;
(*trust_track)[i].T_track = point_process[point_index-1].CPI_time;
(*trust_track)[i].GNSS_time = point_process[point_index-1].GNSS_time;
//更新关联上的点迹信息
(*trust_track)[i].range_point=point_process[point_index-1].Range;
(*trust_track)[i].azi_point=point_process[point_index-1].Azimuth/PI*180;
(*trust_track)[i].elev_point= asin(point_process[point_index-1].Height/point_process[point_index-1].Range)/PI*180;
(*trust_track)[i].vr_point=point_process[point_index-1].Velocity;
(*trust_track)[i].point_id=point_process[point_index-1].point_id;
(*trust_track)[i].circle_id=point_process[point_index-1].circle_id;
(*trust_track)[i].range_point=point_process[point_index-1].range;
(*trust_track)[i].azi_point=point_process[point_index-1].azimuth/PI*180;
(*trust_track)[i].elev_point= asin(point_process[point_index-1].height/point_process[point_index-1].range)/PI*180;
(*trust_track)[i].vr_point=point_process[point_index-1].velocity;
(*trust_track)[i].point_type = 1;
(*trust_track)[i].prf_point = point_process[point_index-1].PRF_index;
(*trust_track)[i].snr_point = point_process[point_index-1].snr;
(*trust_track)[i].snr_point = point_process[point_index-1].SNR;
(*trust_track)[i].Extrapolate_round = 0; //连续未用实点更新时间
(*trust_track)[i].point_flag=1; //实点
(*trust_track)[i].Amplitude= point_process[point_index-1].Amplitude; //幅度
(*trust_track)[i].Amplitude= point_process[point_index-1].amplitude; //幅度
(*trust_track)[i].associate_point_number = (*trust_track)[i].associate_point_number+1; //关联点数+1
(*trust_track)[i].RCS = point_process[point_index-1].RCS;
(*trust_track)[i].Track_Update_Flag = 1; //航迹更新标志
(*trust_track)[i].pitch_num = point_process[point_index-1].pitch_num;
(*trust_track)[i].pitch_num = point_process[point_index-1].pitch_beam_index;
std::memcpy((*trust_track)[i].speed_dim, point_process[point_index-1].speed_dim, sizeof((*trust_track)[i].speed_dim));
std::memcpy((*trust_track)[i].range_dim, point_process[point_index-1].range_dim, sizeof((*trust_track)[i].range_dim));
@@ -490,46 +511,72 @@ void Track_Asso_Tas::model_filter( std::vector<Trust_Track> *t
//更新高度
track_hight_update(tas_track_idx,point_index,trust_track);
}
}
for(auto& info : associated_info)
{
#ifdef RDP_RECORD_ENABLE
rec_asso.push_back(info);
if (rec_asso.size() >= RDP_RECORD_ASSO_CHUNK_ROWS)
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
}
}
//未关联上,航迹外推
else
{
for(int i=0;i<trust_track->size();i++)
//RDP_LOG_DEBUG("unassociated point for track index {}", tas_track_idx);
for(size_t i=0;i<trust_track->size();i++){
if((*trust_track)[i].Track_Index == tas_track_idx)
{
double delta_T = (latest_timestamp - T_track)/1000.0;
if (delta_T <= 0)
continue;
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T, F, Q1, Q2, Q3,Work_Parameter);
if((*trust_track)[i].Extrapolate_round >= rdp_parameter.extrapolate_max_tas)
{
double delta_T = (latest_timestamp - T_track)/1000.0;
if (delta_T <= 0)
continue;
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T, F, Q1, Q2, Q3);
double X1_pred[6],X2_pred[6],X3_pred[6],P1_pred[6][6],P2_pred[6][6],P3_pred[6][6];
kalman Kalman;
Kalman.kalman_pred(F, Q1, X1, P1, X1_pred, P1_pred);
Kalman.kalman_pred(F, Q2, X2, P2, X2_pred, P2_pred);
Kalman.kalman_pred(F, Q3, X3, P3, X3_pred, P3_pred);
memcpy((*trust_track)[i].X1,X1_pred,6*sizeof(double));
memcpy((*trust_track)[i].X2,X2_pred,6*sizeof(double));
memcpy((*trust_track)[i].X3,X3_pred,6*sizeof(double));
memcpy((*trust_track)[i].P1,P1_pred,6*6*sizeof(double));
memcpy((*trust_track)[i].P2,P2_pred,6*6*sizeof(double));
memcpy((*trust_track)[i].P3,P3_pred,6*6*sizeof(double));
double X1_pred[6],X2_pred[6],X3_pred[6],P1_pred[6][6],P2_pred[6][6],P3_pred[6][6];
kalman Kalman;
Kalman.kalman_pred(F, Q1, X1, P1, X1_pred, P1_pred);
Kalman.kalman_pred(F, Q2, X2, P2, X2_pred, P2_pred);
Kalman.kalman_pred(F, Q3, X3, P3, X3_pred, P3_pred);
memcpy((*trust_track)[i].X1,X1_pred,6*sizeof(double));
memcpy((*trust_track)[i].X2,X2_pred,6*sizeof(double));
memcpy((*trust_track)[i].X3,X3_pred,6*sizeof(double));
memcpy((*trust_track)[i].P1,P1_pred,6*6*sizeof(double));
memcpy((*trust_track)[i].P2,P2_pred,6*6*sizeof(double));
memcpy((*trust_track)[i].P3,P3_pred,6*6*sizeof(double));
(*trust_track)[i].T_track = (*trust_track)[i].T_track+delta_T*1000.0; //更新航迹时间
(*trust_track)[i].GNSS_time = (*trust_track)[i].GNSS_time+delta_T*1000.0; //更新航迹时间
(*trust_track)[i].point_flag=0; //虚点
(*trust_track)[i].Extrapolate_round =(*trust_track)[i].Extrapolate_round+1;
(*trust_track)[i].T_track = static_cast<long long>((*trust_track)[i].T_track+delta_T*1000.0); //更新航迹时间
(*trust_track)[i].GNSS_time = static_cast<long long>((*trust_track)[i].GNSS_time+delta_T*1000.0); //更新航迹时间
(*trust_track)[i].point_flag=0; //虚点
(*trust_track)[i].Extrapolate_round =(*trust_track)[i].Extrapolate_round+1;
(*trust_track)[i].Track_Update_Flag = 1; //航迹更新标志
}else{
(*trust_track)[i].point_flag=0; //虚点
(*trust_track)[i].Extrapolate_round =(*trust_track)[i].Extrapolate_round+1;
(*trust_track)[i].Track_Update_Flag = 0; //航迹更新标志
}
}
}
}
#ifdef RDP_RECORD_ENABLE
//函数返回前把剩余未入队的关联信息 flush 掉(宏内部 std::move,调用后 rec_asso 为空)
if (!rec_asso.empty())
{
RDP_RECORD_ASSO_INFOS(rec_asso);
}
#endif
}
void Track_Asso_Tas::model_output(std::vector<Trust_Track> *trust_track, int tas_track_idx )
{
for(int i=0; i<trust_track->size(); i++)
for(size_t i=0; i<trust_track->size(); i++)
if((*trust_track)[i].Track_Index==tas_track_idx)
{
@@ -599,7 +646,7 @@ void Track_Asso_Tas::model_output(std::vector<Trust_Track> *trust_track, int
}
// 产生 F Q 矩阵
void Track_Asso_Tas::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6],struct RadarPara Work_Parameter)
void Track_Asso_Tas::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6])
{
//F
@@ -627,7 +674,7 @@ void Track_Asso_Tas::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6],
//模型1
//Q
double q1=Work_Parameter.Model1_Q_slow;
double q1=rdp_parameter.model_1_Q_slow;
double q111=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
double q121=1/(2*pow(alpha,4))*(exp(-2*alpha*delta_T)+1-2*exp(-alpha*delta_T)+2*alpha*delta_T*exp(-alpha*delta_T)-2*alpha*delta_T+pow(alpha,2)*pow(delta_T,2));
double q131=1/(2*pow(alpha,3))*(1-exp(-2*alpha*delta_T)-2*alpha*delta_T*exp(-alpha*delta_T));
@@ -648,15 +695,15 @@ void Track_Asso_Tas::IMM_F_Q_gen(double v_track, double delta_T,double F[6][6],
double q2;
if(v_track<=5)
{
q2=Work_Parameter.Model2_Q_slow/20;
q2=rdp_parameter.model_2_Q_slow/20;
}
else if(v_track<=100&&v_track>50)
{
q2=10.0*Work_Parameter.Model2_Q_fast;
q2=10.0*rdp_parameter.model_2_Q_fast;
}
else
{
q2=Work_Parameter.Model2_Q_fast;
q2=rdp_parameter.model_2_Q_fast;
}
double q112=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
@@ -680,15 +727,15 @@ Q2[5][3]=q2*q132; Q2[5][4]=q2*q232; Q2[5][5]=q2*q332;
double q3;
if(v_track<=5)
{
q3=Work_Parameter.Model3_Q_slow/20;
q3=rdp_parameter.model_3_Q_slow/20;
}
else if(v_track<=100&&v_track>50)
{
q3=10.0*Work_Parameter.Model3_Q_fast;
q3=10.0*rdp_parameter.model_3_Q_fast;
}
else
{
q3=Work_Parameter.Model3_Q_fast;
q3=rdp_parameter.model_3_Q_fast;
}
double q113=1/(2*pow(alpha,5))*(1-exp(-2*alpha*delta_T)+2*alpha*delta_T+2*pow(alpha,3)*pow(delta_T,3)/3-2*pow(alpha,2)*pow(delta_T,2)-4*alpha*delta_T*exp(-alpha*delta_T));
double q123=1/(2*pow(alpha,4))*(exp(-2*alpha*delta_T)+1-2*exp(-alpha*delta_T)+2*alpha*delta_T*exp(-alpha*delta_T)-2*alpha*delta_T+pow(alpha,2)*pow(delta_T,2));
@@ -715,11 +762,10 @@ double X2[6], double P2[6][6],
double X3[6], double P3[6][6],
double Z[3], double prt,double freq_ind,
double delta_T,
double *d1,double *d2,double *d3,
struct RadarPara Work_Parameter)
double *d1,double *d2,double *d3)
{
double F[6][6], Q1[6][6], Q2[6][6], Q3[6][6];
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3,Work_Parameter);
IMM_F_Q_gen( v_track, delta_T,F, Q1, Q2, Q3);
kalman Kalman;
// *d1=Kalman.d_cal_with_doppler(F,Q1,Z,X1,P1,v_point,prt,freq_ind);
@@ -734,37 +780,37 @@ struct RadarPara Work_Parameter)
//高度维更新
void Track_Asso_Tas::track_hight_update(int tas_track_idx, //更新的航迹号
int asso_point_index, //点迹号
std::vector<Trust_Track> *trust_track //航迹
std::vector<Trust_Track> *trust_track //航迹
)
{
for(int i=0; i<trust_track->size(); i++)
for(size_t i=0; i<trust_track->size(); i++)
if((*trust_track)[i].Track_Index==tas_track_idx)
{
(*trust_track)[i].Hight_smooth.push_back(point_process[asso_point_index-1].Height);
(*trust_track)[i].Hight_smooth.push_back(point_process[asso_point_index-1].height);
int height_win_length;
if((*trust_track)[i].range_point <= 1000)
{
height_win_length = H_F_WIN_LEN+2;
height_win_length = rdp_parameter.height_smooth_win+6;
}
else if((*trust_track)[i].range_point <= 2000 && (*trust_track)[i].range_point > 1000)
{
height_win_length = H_F_WIN_LEN+3;
height_win_length = rdp_parameter.height_smooth_win+8;
}
else if((*trust_track)[i].range_point <= 4000 && (*trust_track)[i].range_point > 2000)
{
height_win_length = H_F_WIN_LEN+4;
height_win_length = rdp_parameter.height_smooth_win+10;
}
else
{
height_win_length = H_F_WIN_LEN+6;
height_win_length = rdp_parameter.height_smooth_win+12;
}
double sum=0;
if((*trust_track)[i].Hight_smooth.size()<height_win_length)
if((*trust_track)[i].Hight_smooth.size()<static_cast<size_t>(height_win_length))
{
for (int ii=0;ii<(*trust_track)[i].Hight_smooth.size();ii++)
for (size_t ii=0;ii<(*trust_track)[i].Hight_smooth.size();ii++)
{
sum=sum+(*trust_track)[i].Hight_smooth[ii];
@@ -773,10 +819,10 @@ void Track_Asso_Tas::track_hight_update(int tas_
(*trust_track)[i].Height=sum/(*trust_track)[i].Hight_smooth.size();
}
else if((*trust_track)[i].Hight_smooth.size()>=height_win_length)
else if((*trust_track)[i].Hight_smooth.size()>=static_cast<size_t>(height_win_length))
{
int N=(*trust_track)[i].Hight_smooth.size();
int N=static_cast<int>((*trust_track)[i].Hight_smooth.size());
for (int ii=0;ii<height_win_length;ii++)
{
sum=sum+(*trust_track)[i].Hight_smooth[N-1-ii];
+5 -8
View File
@@ -1,4 +1,4 @@
#ifndef TRACK_ASSO_TAS_H
#ifndef TRACK_ASSO_TAS_H
#define TRACK_ASSO_TAS_H
#include "data_process_class_dll.h"
#include "parameters.h"
@@ -15,7 +15,6 @@ public:
std::vector<Trust_Track> *trust_track, //航迹文件
struct Track Trust_Track_Output[MAX_TRACK_NUM][10], //更新航迹信息
int *Trust_track_num_Output, //更新航迹数
struct RadarPara Work_Parameter, //工作参数
int tas_track_idx,
long long latest_timestamp //最新时间戳
);
@@ -31,7 +30,6 @@ private:
void model_interaction(std::vector<Trust_Track> *trust_track, int tas_track_idx); //模型交互
void model_filter(std::vector<Trust_Track> *trust_track, //滤波
struct RadarPara Work_Parameter,
int tas_track_idx,
long long latest_timestamp //最新时间戳
);
@@ -44,17 +42,16 @@ private:
double X3[6], double P3[6][6],
double Z[3], double prt,double freq_ind,
double delta_T,
double *d1,double *d2,double *d3,
struct RadarPara Work_Parameter);
double *d1,double *d2,double *d3);
void IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6],struct RadarPara Work_Parameter);
void IMM_F_Q_gen(double v_track, double delta_T,double F[6][6], double Q1[6][6], double Q2[6][6], double Q3[6][6]);
double Pt[3][3]; //模型转移概率
//高度维更新
void track_hight_update(int tas_track_index, //更新的航迹号
void track_hight_update(int tas_track_idx, //更新的航迹号
int asso_point_index, //点迹号
std::vector<Trust_Track> *trust_track //航迹
std::vector<Trust_Track> *trust_track //航迹
);
};
+21 -4
View File
@@ -1,4 +1,4 @@
#include "track_die.h"
#include "track_die.h"
#include "kalman.h"
#include "coor_trans.h"
#include <cmath>
@@ -14,10 +14,27 @@ void Track_Die::track_die_process( std::vector <Trust_Track> *trust_track,
std::vector <Trust_Track>::iterator Iter;
for (Iter=trust_track->begin(); Iter!=trust_track->end();)
{
if( ((*Iter).Track_Mode == 0 && (*Iter).Extrapolate_round >= TRACK_DIE_ROUND)
// ||((*Iter).Track_Mode == 1 && (*Iter).Extrapolate_round >= TRACK_DIE_ROUND_TAS)
||(*Iter).manual_delete_flag == 1)
bool die_flag = false;
//手动删除
if((*Iter).manual_delete_flag == 1)
{
die_flag = true;
}
//非重点航迹自动消亡
if((*Iter).Track_Mode == 0 && (*Iter).primary_target_flag == 0 && (*Iter).Quality < rdp_parameter.track_die_quality)
{
die_flag = true;
}
//重点航迹自动消亡
if((*Iter).Track_Mode == 0 && (*Iter).primary_target_flag == 1 && (*Iter).Extrapolate_round > rdp_parameter.track_die_round_pramary)
{
die_flag = true;
}
if(die_flag)
{
//输出消亡信息
*Track_die_num_Output=*Track_die_num_Output+1;
+1 -1
View File
@@ -1,4 +1,4 @@
#ifndef TRACK_DIE_H
#ifndef TRACK_DIE_H
#define TRACK_DIE_H
#include "data_process_class_dll.h"
#include "parameters.h"
+15 -4
View File
@@ -1,6 +1,7 @@
#include "track_die_tas.h"
#include "track_die_tas.h"
#include "kalman.h"
#include "coor_trans.h"
#include "rdp_log.h"
#include <iostream>
#include <cmath>
#include <cstring>
@@ -16,11 +17,21 @@ void Track_Die_Tas::track_die_process_tas( std::vector <Trust_Track> *trust_trac
std::vector <Trust_Track>::iterator Iter;
for (Iter=trust_track->begin(); Iter!=trust_track->end();)
{
if( ((*Iter).Track_Mode == 1 && (*Iter).Extrapolate_round >= TRACK_DIE_ROUND_TAS)
||(*Iter).manual_delete_flag == 1)
bool die_flag = false;
if((*Iter).manual_delete_flag == 1)
{
std::cout << "remove tas target :" << (*Iter).Track_Index << std::endl;
die_flag = true;
}
if((*Iter).Track_Mode == 1 && (*Iter).Extrapolate_round > rdp_parameter.track_die_round_tas)
{
die_flag = true;
}
if(die_flag)
{
// RDP_LOG_DEBUG("remove tas target :{}", (*Iter).Track_Index);
//输出消亡信息
*Track_die_num_Output=*Track_die_num_Output+1;
Track_die_Index_Output[*Track_die_num_Output-1]=(*Iter).Track_Index;
+1 -1
View File
@@ -1,4 +1,4 @@
#ifndef TRACK_DIE_TAS_H
#ifndef TRACK_DIE_TAS_H
#define TRACK_DIE_TAS_H
#include "data_process_class_dll.h"
#include "parameters.h"
@@ -1,4 +1,4 @@
#include "track_index_mangement.h"
#include "track_index_mangement.h"
#include <cmath>
#include <cstring>
#include <vector>
@@ -11,7 +11,7 @@ void Track_Ind_Mangement::reset()
lastest_index = 0;
}
int Track_Ind_Mangement ::track_ind_get( std::vector<Trust_Track> *trust_track)
int Track_Ind_Mangement::track_ind_get( std::vector<Trust_Track> *trust_track)
{
int isempty = 1;
@@ -29,18 +29,19 @@ int Track_Ind_Mangement ::track_ind_get( std::vector<Trust_Track> *trust_track
else
{
//建立航迹号列表
int List[MAX_TRACK_INDEX]={0};
for (int i=0;i<trust_track->size();i++)
const int track_index_max = rdp_parameter.track_index_max;
std::vector<int> List(static_cast<std::size_t>(track_index_max), 0);
for (size_t i=0;i<trust_track->size();i++)
{
int ti = (*trust_track)[i].Track_Index;
if (ti >= 1 && ti <= MAX_TRACK_INDEX)
if (ti >= 1 && ti <= track_index_max)
List[ti-1]=1;
}
//分配航迹号
if(lastest_index<MAX_TRACK_INDEX)
if(lastest_index<track_index_max)
{
for (int i=lastest_index+1;i<=MAX_TRACK_INDEX;i++)
for (int i=lastest_index+1;i<=track_index_max;i++)
{
if(List[i-1]==0)
{
@@ -49,7 +50,7 @@ int Track_Ind_Mangement ::track_ind_get( std::vector<Trust_Track> *trust_track
}
}
for (int i=1;i<=MAX_TRACK_INDEX;i++)
for (int i=1;i<=track_index_max;i++)
{
if(List[i-1]==0)
{
@@ -59,9 +60,9 @@ int Track_Ind_Mangement ::track_ind_get( std::vector<Trust_Track> *trust_track
}
}
else if(lastest_index == MAX_TRACK_INDEX)
else if(lastest_index == track_index_max)
{
for (int i=1;i<=MAX_TRACK_INDEX;i++)
for (int i=1;i<=track_index_max;i++)
{
if(List[i-1]==0)
{
@@ -1,4 +1,4 @@
#ifndef TRACK_INDEX_MANGEMENT_H
#ifndef TRACK_INDEX_MANGEMENT_H
#define TRACK_INDEX_MANGEMENT_H
#include "data_process_class_dll.h"
#include "parameters.h"
+305 -221
View File
@@ -1,4 +1,4 @@
#include "track_init.h"
#include "track_init.h"
#include "kalman.h"
#include "coor_trans.h"
#include <cmath>
@@ -6,41 +6,26 @@
#include <vector>
#include <iostream>
#include<iomanip>
#include "rdp_record.h"
using namespace std;
int Track_Init::track_init_process_logic( std::vector <PointRecv> *point_recv, //输入点迹
std::vector<Trust_Track> *trust_track, //可靠航迹
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter
int *Trust_track_num_Output
)
{
// for(int i=0;i<(*point_recv).size();i++)
// {
// if((*point_recv)[i].CPI_Time == 30675948 || (*point_recv)[i].CPI_Time == 30682320
// || (*point_recv)[i].CPI_Time == 30682132 || (*point_recv)[i].CPI_Time == 30688692
// || (*point_recv)[i].CPI_Time == 30691692 || (*point_recv)[i].CPI_Time == 30691880)
// {
// std::cout << "azi=" << (*point_recv)[i].Azimuth
// << "dis=" << (*point_recv)[i].Range
// << "h=" << (*point_recv)[i].Height
// << "v=" << (*point_recv)[i].Velocity
// << "wave=" << (*point_recv)[i].beam_index
// << "time=" << (*point_recv)[i].CPI_Time;
// }
// }
//取出待起航的点迹数据
for (int i=0;i<(*point_recv).size();i++)
for (size_t i=0;i<(*point_recv).size();i++){
point_process.push_back((*point_recv)[i]);
}
//临时航迹的buff_round+1
for (int i=0;i<temp_track->size();i++)
for (size_t i=0;i<temp_track->size();i++)
{
for (int j=0;j<(*temp_track)[i].size();j++)
for (size_t j=0;j<(*temp_track)[i].size();j++)
{
(*temp_track)[i][j].buff_round = (*temp_track)[i][j].buff_round+1;
}
@@ -49,16 +34,16 @@ int Track_Init::track_init_process_logic( std::vector <PointRecv>
if(point_process.size()>0)
{
//点迹与临时航迹关联
point_temp_track_asso(temp_track,Work_Parameter);
point_temp_track_asso(temp_track);
//点迹与航迹头关联
point_track_head_asso(temp_track,Work_Parameter);
point_track_head_asso(temp_track);
//删除关联上的点迹
std::vector <PointRecv>::iterator Iter;
for (Iter=point_process.begin(); Iter!=point_process.end();)
{
if((*Iter).Use_Flag==1)
if((*Iter).use_flag==1)
{
point_process.erase(Iter);
Iter=point_process.begin();
@@ -71,34 +56,37 @@ int Track_Init::track_init_process_logic( std::vector <PointRecv>
//剩余点重新存入点迹
std::vector<PointRecv>().swap((*point_recv));
for (int i=0;i<point_process.size();i++)
for (size_t i=0;i<point_process.size();i++)
(*point_recv).push_back(point_process[i]);
//剩余点转为航迹头
for (unsigned int i=0;i<point_process.size();i++)
{
Temp_track temp_track_tmp = {};
temp_track_tmp.r = point_process[i].Range;
temp_track_tmp.azi = point_process[i].Azimuth;
temp_track_tmp.height = point_process[i].Height;
temp_track_tmp.Amp = point_process[i].Amplitude;
temp_track_tmp.vr = point_process[i].Velocity;
temp_track_tmp.snr = point_process[i].snr;
temp_track_tmp.temp_track_id = temp_track_id++;
temp_track_tmp.point_id = point_process[i].point_id;
temp_track_tmp.circle_id = point_process[i].circle_id;
temp_track_tmp.r = point_process[i].range;
temp_track_tmp.azi = point_process[i].azimuth;
temp_track_tmp.height = point_process[i].height;
temp_track_tmp.amplitude = point_process[i].amplitude;
temp_track_tmp.vr = point_process[i].velocity;
temp_track_tmp.SNR = point_process[i].SNR;
temp_track_tmp.RCS = point_process[i].RCS;
temp_track_tmp.pitch_num = point_process[i].pitch_num;
temp_track_tmp.pitch_num = point_process[i].pitch_beam_index;
std::memcpy(temp_track_tmp.speed_dim, point_process[i].speed_dim, sizeof(temp_track_tmp.speed_dim));
std::memcpy(temp_track_tmp.range_dim, point_process[i].range_dim, sizeof(temp_track_tmp.range_dim));
temp_track_tmp.asso_flag = 0;
temp_track_tmp.X[0]=point_process[i].Range*cos(point_process[i].Azimuth);
temp_track_tmp.X[0]=point_process[i].range*cos(point_process[i].azimuth);
temp_track_tmp.X[1]=0;
temp_track_tmp.X[2]=point_process[i].Range*sin(point_process[i].Azimuth);
temp_track_tmp.X[2]=point_process[i].range*sin(point_process[i].azimuth);
temp_track_tmp.X[3]=0;
temp_track_tmp.T = point_process[i].CPI_Time;
temp_track_tmp.CPI_time = point_process[i].CPI_time;
temp_track_tmp.GNSS_time = point_process[i].GNSS_time;
temp_track_tmp.buff_round = 1;
// temp_track_tmp.Temp_track_section_idx = floor(point_process[i].beam_index/BEAM_NUM_DOT_SECTION)+1;
temp_track->push_back( std::vector <Temp_track> ());
int n=temp_track->size();
int n=static_cast<int>(temp_track->size());
(*temp_track)[n-1].push_back(temp_track_tmp);
// if(point_process[i].CPI_Time == 30675948 || point_process[i].CPI_Time == 30682320)
@@ -117,33 +105,30 @@ int Track_Init::track_init_process_logic( std::vector <PointRecv>
std::vector<PointRecv>().swap(point_process);
std::vector<PointRecv>().swap((*point_recv));
#ifdef RDP_RECORD_ENABLE
//记录临时航迹
std::vector<Temp_track> rec_temp;
for(auto& vec_t : *temp_track){
for(auto& t : vec_t){
rec_temp.push_back(t);
}
}
RDP_RECORD_TEMP_TRACKS(rec_temp);
#endif
//临时航迹满足起始长度 转为可靠航迹
tmp_track_to_trust_track(trust_track,temp_track,Trust_Track_Output, Trust_track_num_Output,Work_Parameter);
tmp_track_to_trust_track(trust_track,temp_track,Trust_Track_Output, Trust_track_num_Output);
}
//消亡临时航迹
tmp_track_die(temp_track);
// std::cout << "-----------------------------------------------";
// for(int i=0;i<temp_track->size();i++)
// {
// for(int j=0;j<(*temp_track)[i].size();j++)
// {
// std::cout << "temp_track_azi=" << (*temp_track)[i][j].azi
// << "temp_track_dis=" << (*temp_track)[i][j].r
// << "temp_track_h=" << (*temp_track)[i][j].height
// << "temp_track_v=" << (*temp_track)[i][j].vr
// << "temp_track_time=" << (*temp_track)[i][j].T;
// }
// }
return 0;
}
void Track_Init::point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter)
void Track_Init::point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track)
{
//关联信息
@@ -155,26 +140,26 @@ void Track_Init::point_temp_track_asso(std::vector <std::vector<Temp_track>>
};
std::vector <Asso_info> asso_info;
for ( int i=0;i<point_process.size();i++)
for ( size_t i=0;i<point_process.size();i++)
{
for ( int j=0;j<(*temp_track).size();j++)
for ( size_t j=0;j<(*temp_track).size();j++)
{
int L = (*temp_track)[j].size();
int L = static_cast<int>((*temp_track)[j].size());
if( (*temp_track)[j].size() > 1 && (*temp_track)[j][L-1].buff_round >= 2)
{
//点迹信息
// double Z[2]={ point_process[i].Range*cos(point_process[i].Azimuth),point_process[i].Range*sin(point_process[i].Azimuth)} ;
double Z[3]={point_process[i].Range,
point_process[i].Azimuth,
point_process[i].Velocity};
double vr_point=point_process[i].Velocity;
double r_point=point_process[i].Range;
double Z[3]={point_process[i].range,
point_process[i].azimuth,
point_process[i].velocity};
double vr_point=point_process[i].velocity;
double r_point=point_process[i].range;
double prt = point_process[i].PRF_index;
double freq_ind = point_process[i].Freq_index;
double h_point = point_process[i].Height;
double T_point = point_process[i].CPI_Time;
double freq_ind = point_process[i].freq_index;
double h_point = point_process[i].height;
double T_point = static_cast<double>(point_process[i].CPI_time);
//航迹信息
double X[4];
@@ -184,7 +169,7 @@ void Track_Init::point_temp_track_asso(std::vector <std::vector<Temp_track>>
double v_temp_track=(*temp_track)[j][L-1].vr;
double r_temp_track=(*temp_track)[j][L-1].r;
double h_temp_track=(*temp_track)[j][L-1].height;
double T_track_head = (*temp_track)[j][L-1].T;
double T_track_head = static_cast<double>((*temp_track)[j][L-1].CPI_time);
double delta_T = (T_point - T_track_head)/1000.0;
@@ -204,29 +189,15 @@ void Track_Init::point_temp_track_asso(std::vector <std::vector<Temp_track>>
double y2=Z[0]*sin(Z[1]);
double alpha = alpha_cal_track_init(x0,y0,x1,y1,x2,y2);
// if(T_track_head == 33855816 && T_point == 33859004 )
// {
// std::cout << "r_point=" << r_point
// << "h_point=" << h_point
// << "T_point=" << T_point
// << "r_temp_track=" << r_temp_track
// << "v_temp_track=" << v_temp_track
// << "h_temp_track=" << h_temp_track
// << "T_track_head=" << T_track_head
// << "d=" << d
// << "alpha=" << alpha;
// }
if(d*d<=TRACK_START_THRESHOLD*TRACK_START_THRESHOLD && alpha<ALPHA_START && vr_point*v_temp_track>0)// && fabs(h_point-h_temp_track )<= r_point*SIGMA_E&& abs(vr_point-v_temp_track)/abs(v_temp_track)<0.8
if(d*d<=rdp_parameter.track_init_threshold*rdp_parameter.track_init_threshold && alpha<rdp_parameter.track_init_angle && vr_point*v_temp_track>0)// && fabs(h_point-h_temp_track )<= r_point*rdp_parameter.sigma_pitch&& abs(vr_point-v_temp_track)/abs(v_temp_track)<0.8
{
struct Asso_info asso_info_tmp;
asso_info_tmp.point_idx = i+1;
asso_info_tmp.track_idx = j+1;
asso_info_tmp.point_idx = static_cast<int>(i)+1;
asso_info_tmp.track_idx = static_cast<int>(j)+1;
asso_info_tmp.d = d;
asso_info.push_back(asso_info_tmp);
(*temp_track)[j][L-1].asso_flag = 1;
point_process[i].Use_Flag = 1;
point_process[i].use_flag = 1;
//std::cout << "v_temp_track : " << v_temp_track << "vr_point : "<< vr_point;
}
}
@@ -235,49 +206,87 @@ void Track_Init::point_temp_track_asso(std::vector <std::vector<Temp_track>>
}
// temp_track中加入新关联上的临时航迹
for (int i = 0 ; i<asso_info.size();i++ )
for (size_t i = 0 ; i<asso_info.size();i++ )
{
(*temp_track).push_back(std::vector <Temp_track> ());
//前L个点
int L = (*temp_track)[asso_info[i].track_idx-1].size();
int L = static_cast<int>((*temp_track)[asso_info[i].track_idx-1].size());
for (int j=0;j < L;j++ )
{
(*temp_track)[(*temp_track).size()-1].push_back((*temp_track)[asso_info[i].track_idx-1][j]);
(*temp_track)[(*temp_track).size()-1][j].asso_flag = 0;
(*temp_track)[(*temp_track).size()-1][j].temp_track_id = temp_track_id;
}
//关联上的点
Temp_track asso_track_info_tmp = {};
asso_track_info_tmp.r = point_process[asso_info[i].point_idx-1].Range;
asso_track_info_tmp.azi = point_process[asso_info[i].point_idx-1].Azimuth;
asso_track_info_tmp.height = point_process[asso_info[i].point_idx-1].Height;
asso_track_info_tmp.Amp = point_process[asso_info[i].point_idx-1].Amplitude;
asso_track_info_tmp.T = point_process[asso_info[i].point_idx-1].CPI_Time;
asso_track_info_tmp.temp_track_id = temp_track_id++;
asso_track_info_tmp.point_id = point_process[asso_info[i].point_idx-1].point_id;
asso_track_info_tmp.circle_id = point_process[asso_info[i].point_idx-1].circle_id;
asso_track_info_tmp.r = point_process[asso_info[i].point_idx-1].range;
asso_track_info_tmp.azi = point_process[asso_info[i].point_idx-1].azimuth;
asso_track_info_tmp.height = point_process[asso_info[i].point_idx-1].height;
asso_track_info_tmp.amplitude = point_process[asso_info[i].point_idx-1].amplitude;
asso_track_info_tmp.CPI_time = point_process[asso_info[i].point_idx-1].CPI_time;
asso_track_info_tmp.GNSS_time = point_process[asso_info[i].point_idx-1].GNSS_time;
asso_track_info_tmp.vr = point_process[asso_info[i].point_idx-1].Velocity;
asso_track_info_tmp.snr = point_process[asso_info[i].point_idx-1].snr;
asso_track_info_tmp.vr = point_process[asso_info[i].point_idx-1].velocity;
asso_track_info_tmp.SNR = point_process[asso_info[i].point_idx-1].SNR;
asso_track_info_tmp.RCS = point_process[asso_info[i].point_idx-1].RCS;
asso_track_info_tmp.asso_flag=0;
asso_track_info_tmp.buff_round = 1;
asso_track_info_tmp.pitch_num = point_process[asso_info[i].point_idx-1].pitch_num;
asso_track_info_tmp.pitch_num = point_process[asso_info[i].point_idx-1].pitch_beam_index;
std::memcpy(asso_track_info_tmp.speed_dim, point_process[asso_info[i].point_idx-1].speed_dim, sizeof(asso_track_info_tmp.speed_dim));
std::memcpy(asso_track_info_tmp.range_dim, point_process[asso_info[i].point_idx-1].range_dim, sizeof(asso_track_info_tmp.range_dim));
asso_track_info_tmp.d = asso_info[i].d;
// asso_track_info_tmp.Temp_track_section_idx = floor(point_process[asso_info[i].point_idx-1].beam_index/BEAM_NUM_DOT_SECTION)+1;
double Z0[2]={(*temp_track)[asso_info[i].track_idx-1][L-1].r*cos((*temp_track)[asso_info[i].track_idx-1][L-1].azi),
(*temp_track)[asso_info[i].track_idx-1][L-1].r*sin((*temp_track)[asso_info[i].track_idx-1][L-1].azi)};
double Z1[2]={point_process[asso_info[i].point_idx-1].Range*cos(point_process[asso_info[i].point_idx-1].Azimuth),
point_process[asso_info[i].point_idx-1].Range*sin(point_process[asso_info[i].point_idx-1].Azimuth)};
double Z1[2]={point_process[asso_info[i].point_idx-1].range*cos(point_process[asso_info[i].point_idx-1].azimuth),
point_process[asso_info[i].point_idx-1].range*sin(point_process[asso_info[i].point_idx-1].azimuth)};
double X[4];
double P[4][4];
kalman Kalman;
double T = ( point_process[asso_info[i].point_idx-1].CPI_Time - (*temp_track)[asso_info[i].track_idx-1][L-1].T)/1000.0;
double T = ( point_process[asso_info[i].point_idx-1].CPI_time - (*temp_track)[asso_info[i].track_idx-1][L-1].CPI_time)/1000.0;
Kalman.kalman_filter_init_2dots(Z0,Z1,T,X,P);
memcpy(asso_track_info_tmp.X, X, 4*sizeof(double));
memcpy(asso_track_info_tmp.P, P, 4*4*sizeof(double));
(*temp_track)[(*temp_track).size()-1].push_back(asso_track_info_tmp);
//计算航迹质量
auto& temp_track_ref = (*temp_track)[(*temp_track).size()-1];
L = static_cast<int>(temp_track_ref.size());
double snr_tmp = 0;
double d_tmp = 0;
double quality_tmp = 0;
d_tmp = exp(-temp_track_ref[L-1].d);
if(L == 3){
double average_snr = 0;
for(int i=0;i<L;i++)
{
average_snr += temp_track_ref[i].SNR;
}
average_snr /= L;
snr_tmp = 1/(1+exp(-(average_snr - 16) / 1.2));
}else{
snr_tmp = 1/(1+exp(-(temp_track_ref.back().SNR - 16) / 1.2));
}
quality_tmp = d_tmp * rdp_parameter.track_quality_d_weight + snr_tmp * rdp_parameter.track_quality_snr_weight;
quality_tmp /= (rdp_parameter.track_quality_d_weight + rdp_parameter.track_quality_snr_weight);
if(L == 3){
for(int i=0;i<L;i++)
{
temp_track_ref[i].quality = quality_tmp;
}
}else{
temp_track_ref[L-1].quality = temp_track_ref[L-2].quality * rdp_parameter.track_quality_iter_coeff + quality_tmp * (1 - rdp_parameter.track_quality_iter_coeff);
}
}
//temp_track中删除关联上的临时航迹
@@ -303,8 +312,7 @@ void Track_Init::point_temp_track_asso(std::vector <std::vector<Temp_track>>
}
}
void Track_Init::point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter)
void Track_Init::point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track)
{
//关联上的信息
@@ -316,10 +324,10 @@ void Track_Init::point_track_head_asso( std::vector <std::vector<Temp_track>>
std::vector <Asso_info> asso_info;
//关联
for ( int i=0;i<point_process.size();i++)
for ( size_t i=0;i<point_process.size();i++)
{
for ( int j=0;j<temp_track->size();j++)
for ( size_t j=0;j<temp_track->size();j++)
{
if((*temp_track)[j].size()==1 && (*temp_track)[j][0].buff_round >= 2)
@@ -328,11 +336,11 @@ void Track_Init::point_track_head_asso( std::vector <std::vector<Temp_track>>
//点迹信息
double x_point, y_point, vr_point;
coor_trans Coor_trans;
Coor_trans.polar2cart(&x_point,&y_point,point_process[i].Range,point_process[i].Azimuth);
vr_point=point_process[i].Velocity;
double r_point=point_process[i].Range;
double h_point = point_process[i].Height;
double T_point = point_process[i].CPI_Time;
Coor_trans.polar2cart(&x_point,&y_point,point_process[i].range,point_process[i].azimuth);
vr_point=point_process[i].velocity;
double r_point=point_process[i].range;
double h_point = point_process[i].height;
double T_point = static_cast<double>(point_process[i].CPI_time);
//航迹信息
double x_track_head, y_track_head;
@@ -340,101 +348,75 @@ void Track_Init::point_track_head_asso( std::vector <std::vector<Temp_track>>
y_track_head=(*temp_track)[j][0].X[2];
double v_track_head=(*temp_track)[j][0].vr;
double h_track_head = (*temp_track)[j][0].height;
double T_track_head = (*temp_track)[j][0].T;
double T_track_head = static_cast<double>((*temp_track)[j][0].CPI_time);
// if(T_track_head == 30675948)
// {
// std::cout << "r_point=" << r_point
// << "h_point=" << h_point
// << "T_point=" << T_point
// << "x_track_head=" << x_track_head
// << "y_track_head=" << y_track_head
// << "v_track_head=" << v_track_head
// << "T_track_head=" << T_track_head;
// }
//距离差
double dis = sqrt(pow(x_point-x_track_head,2)+pow(y_point-y_track_head,2));
// if(T_point == 30682320)
// {
// std::cout <<"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";
// std::cout << "r_point=" << r_point
// << "h_point=" << h_point
// << "T_point=" << T_point
// << "x_track_head=" << x_track_head
// << "y_track_head=" << y_track_head
// << "v_track_head=" << v_track_head
// << "T_track_head=" << T_track_head;
// }
double vmax;
if(rdp_parameter.radar_work_mode == 0) //近程模式 最大速度减小一点
{
vmax = rdp_parameter.track_init_velocity_max;
}
else //中远程模式 最大速度正常用
{
vmax = rdp_parameter.track_init_velocity_max;
}
//距离差
double dis = sqrt(pow(x_point-x_track_head,2)+pow(y_point-y_track_head,2));
double delta_T = (T_point - T_track_head)/1000.0;
double vmax;
if(Work_Parameter.work_mode == 0) //近程模式 最大速度减小一点
{
vmax = Work_Parameter.V_MAX;
}
else //中远程模式 最大速度正常用
{
vmax = Work_Parameter.V_MAX;
}
double delta_T = (T_point - T_track_head)/1000.0;
//满足关联条件的点航
// if( ( (r_point>=1000 && dis<=vmax*delta_T && dis>=V_MIN*delta_T) || (r_point<1000 && dis<=vmax*delta_T/2.0 && dis>=V_MIN*delta_T/2.0) )
// && vr_point*v_track_head>0 )//&& fabs(vr_point-v_track_head)/fabs(v_track_head)<0.2 && fabs(h_point-h_track_head)<=r_point*SIGMA_E
if(
( (Work_Parameter.work_mode == 0&&( (r_point>=1000 && dis<=vmax*delta_T && dis>=Work_Parameter.V_MIN*delta_T) || (r_point<1000 && dis<=vmax*delta_T/2.0 && dis>=Work_Parameter.V_MIN*delta_T/2.0) ))
|| (Work_Parameter.work_mode != 0&&( (r_point>=2000 && dis<=vmax*delta_T && dis>=Work_Parameter.V_MIN*delta_T) || (r_point<2000 && dis<=vmax*delta_T/3.0 && dis>=Work_Parameter.V_MIN*delta_T/2.0) ))
)
&& vr_point*v_track_head>0 )
{
// if(T_track_head == 30675948)
// {
// std::cout << "r_point=" << r_point
// << "h_point=" << h_point
// << "T_point=" << T_point
// << "x_track_head=" << x_track_head
// << "y_track_head=" << y_track_head
// << "v_track_head=" << v_track_head
// << "T_track_head=" << T_track_head;
// }
// if(
// ( (rdp_parameter.radar_work_mode == 0&&( (r_point>=1000 && dis<=vmax*delta_T && dis>=rdp_parameter.track_init_velocity_min*delta_T) || (r_point<1000 && dis<=vmax*delta_T/2.0 && dis>=rdp_parameter.track_init_velocity_min*delta_T/2.0) ))
// || (rdp_parameter.radar_work_mode != 0&&( (r_point>=2000 && dis<=vmax*delta_T && dis>=rdp_parameter.track_init_velocity_min*delta_T) || (r_point<2000 && dis<=vmax*delta_T/3.0 && dis>=rdp_parameter.track_init_velocity_min*delta_T/2.0) ))
// )
// && vr_point*v_track_head>0 )
//点航关联条件
if( dis <= vmax* delta_T && dis >= rdp_parameter.track_init_velocity_min * delta_T && \
vr_point*v_track_head>0 && \
dis >= abs(vr_point) * delta_T && \
dis >= abs(v_track_head) * delta_T
)
{
struct Asso_info asso_info_tmp;
asso_info_tmp.point_idx = i+1;
asso_info_tmp.track_idx = j+1;
asso_info_tmp.point_idx = static_cast<int>(i)+1;
asso_info_tmp.track_idx = static_cast<int>(j)+1;
asso_info.push_back(asso_info_tmp);
(*temp_track)[j][0].asso_flag = 1;
point_process[i].Use_Flag = 1;
point_process[i].use_flag = 1;
//std::cout << "v_track_head: " << v_track_head << "vr_point: " << vr_point;
}
}
}
}
}
// temp_track中加入新关联上的临时航迹
for (int i = 0 ; i<asso_info.size();i++ )
for (size_t i = 0 ; i<asso_info.size();i++ )
{
(*temp_track).push_back(std::vector <Temp_track> ());
//第一个点
(*temp_track)[(*temp_track).size()-1].push_back((*temp_track)[asso_info[i].track_idx-1][0]);
(*temp_track)[(*temp_track).size()-1][0].asso_flag = 0;
(*temp_track)[(*temp_track).size()-1][0].temp_track_id = temp_track_id;
//第二个点
struct Temp_track asso_track_info_tmp = {};
asso_track_info_tmp.r = point_process[asso_info[i].point_idx-1].Range;
asso_track_info_tmp.azi = point_process[asso_info[i].point_idx-1].Azimuth;
asso_track_info_tmp.height = point_process[asso_info[i].point_idx-1].Height;
asso_track_info_tmp.Amp = point_process[asso_info[i].point_idx-1].Amplitude;
asso_track_info_tmp.vr = point_process[asso_info[i].point_idx-1].Velocity;
asso_track_info_tmp.snr = point_process[asso_info[i].point_idx-1].snr;
asso_track_info_tmp.temp_track_id = temp_track_id++;
asso_track_info_tmp.point_id = point_process[asso_info[i].point_idx-1].point_id;
asso_track_info_tmp.circle_id = point_process[asso_info[i].point_idx-1].circle_id;
asso_track_info_tmp.r = point_process[asso_info[i].point_idx-1].range;
asso_track_info_tmp.azi = point_process[asso_info[i].point_idx-1].azimuth;
asso_track_info_tmp.height = point_process[asso_info[i].point_idx-1].height;
asso_track_info_tmp.amplitude = point_process[asso_info[i].point_idx-1].amplitude;
asso_track_info_tmp.vr = point_process[asso_info[i].point_idx-1].velocity;
asso_track_info_tmp.SNR = point_process[asso_info[i].point_idx-1].SNR;
asso_track_info_tmp.RCS = point_process[asso_info[i].point_idx-1].RCS;
asso_track_info_tmp.T = point_process[asso_info[i].point_idx-1].CPI_Time;
asso_track_info_tmp.CPI_time = point_process[asso_info[i].point_idx-1].CPI_time;
asso_track_info_tmp.GNSS_time = point_process[asso_info[i].point_idx-1].GNSS_time;
asso_track_info_tmp.pitch_num = point_process[asso_info[i].point_idx-1].pitch_num;
asso_track_info_tmp.pitch_num = point_process[asso_info[i].point_idx-1].pitch_beam_index;
std::memcpy(asso_track_info_tmp.speed_dim, point_process[asso_info[i].point_idx-1].speed_dim, sizeof(asso_track_info_tmp.speed_dim));
std::memcpy(asso_track_info_tmp.range_dim, point_process[asso_info[i].point_idx-1].range_dim, sizeof(asso_track_info_tmp.range_dim));
asso_track_info_tmp.asso_flag = 0;
@@ -442,12 +424,12 @@ void Track_Init::point_track_head_asso( std::vector <std::vector<Temp_track>>
// asso_track_info_tmp.Temp_track_section_idx = floor(point_process[asso_info[i].point_idx-1].beam_index/BEAM_NUM_DOT_SECTION)+1;;
double Z0[2]={(*temp_track)[asso_info[i].track_idx-1][0].r*cos((*temp_track)[asso_info[i].track_idx-1][0].azi),
(*temp_track)[asso_info[i].track_idx-1][0].r*sin((*temp_track)[asso_info[i].track_idx-1][0].azi)};
double Z1[2]={point_process[asso_info[i].point_idx-1].Range*cos(point_process[asso_info[i].point_idx-1].Azimuth),
point_process[asso_info[i].point_idx-1].Range*sin(point_process[asso_info[i].point_idx-1].Azimuth)};
double Z1[2]={point_process[asso_info[i].point_idx-1].range*cos(point_process[asso_info[i].point_idx-1].azimuth),
point_process[asso_info[i].point_idx-1].range*sin(point_process[asso_info[i].point_idx-1].azimuth)};
double X[4];
double P[4][4];
kalman Kalman;
double T = (point_process[asso_info[i].point_idx-1].CPI_Time - (*temp_track)[asso_info[i].track_idx-1][0].T)/1000.0;
double T = (point_process[asso_info[i].point_idx-1].CPI_time - (*temp_track)[asso_info[i].track_idx-1][0].CPI_time)/1000.0;
Kalman.kalman_filter_init_2dots(Z0,Z1,T,X,P);
memcpy(asso_track_info_tmp.X, X, 4*sizeof(double));
@@ -488,8 +470,7 @@ double Track_Init::alpha_cal_track_init(double x0,double y0,double x1,double y1,
void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_track,
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter)
int *Trust_track_num_Output)
{
std::vector <std::vector<Temp_track>> Track_to_start;
@@ -498,20 +479,67 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
std::vector <std::vector<Temp_track>>::iterator Iter;
for (Iter=temp_track->begin(); Iter!=temp_track->end();)
{
int L=(*Iter).size();
int L=static_cast<int>((*Iter).size());
double range=(*Iter)[L-1].r;
double azi=(*Iter)[L-1].azi/PI*180;
double quality = (*Iter)[L-1].quality;
//(已停用)1km以内,SNR小于20dB的临时航迹不转为可靠航迹
// bool snr_flag = true;
// for(int p_idx = 0; p_idx < L; p_idx++){
// if((*Iter)[p_idx].r < 1000 && (*Iter)[p_idx].snr < 20){
// snr_flag = false;
// break;
// }
// }
//航迹起批条件判断
bool track_init_flag = true;
if( (L <=10 && L>=Work_Parameter.track_start_point_num)&& track_init_prohibit(range,azi,Work_Parameter)==0) //按长度查找TRUST_TRACK_POINT
//航迹长度大于10点,不起批
if(L > 10){
track_init_flag = false;
}
//航迹长度小于起始点数,不起批
if(L < rdp_parameter.track_init_point_num_min){
track_init_flag = false;
}
//航迹起批禁止区域判断
if(track_init_prohibit(range,azi)!=0)
{
track_init_flag = false;
}
//航迹质量起批条件判断
switch(L){
case 3:
if(quality < rdp_parameter.track_init_quality_L3){
track_init_flag = false;
}
break;
case 4:
if(quality < rdp_parameter.track_init_quality_L4){
track_init_flag = false;
}
break;
case 5:
if(quality < rdp_parameter.track_init_quality_L5){
track_init_flag = false;
}
break;
case 6:
if(quality < rdp_parameter.track_init_quality_L6){
track_init_flag = false;
}
break;
case 7:
if(quality < rdp_parameter.track_init_quality_L7){
track_init_flag = false;
}
break;
default:
if(quality < rdp_parameter.track_init_quality_L7){
track_init_flag = false;
}
break;
}
//TODO: 杂波密度判断
if(track_init_flag)
{
//加入Track_to_start中
Track_to_start.push_back(std::vector <Temp_track> ());
@@ -532,7 +560,7 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
return;
//2.两两比较Track_to_start中的航迹信息,删除重复的航迹
int L = Work_Parameter.track_start_point_num;
int L = rdp_parameter.track_init_point_num_min;
for (size_t i=0; i+1<Track_to_start.size();i++)
{
if(Track_to_start[i][0].asso_flag!=2)
@@ -544,9 +572,9 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
if (Track_to_start[i].size() < 3 || Track_to_start[j].size() < 3)
continue;
if( (Track_to_start[i][0].T == Track_to_start[j][0].T && Track_to_start[i][0].r == Track_to_start[j][0].r)
|| (Track_to_start[i][1].T == Track_to_start[j][1].T && Track_to_start[i][1].r == Track_to_start[j][1].r)
|| (Track_to_start[i][2].T == Track_to_start[j][2].T && Track_to_start[i][2].r == Track_to_start[j][2].r)
if( (Track_to_start[i][0].CPI_time == Track_to_start[j][0].CPI_time && Track_to_start[i][0].r == Track_to_start[j][0].r)
|| (Track_to_start[i][1].CPI_time == Track_to_start[j][1].CPI_time && Track_to_start[i][1].r == Track_to_start[j][1].r)
|| (Track_to_start[i][2].CPI_time == Track_to_start[j][2].CPI_time && Track_to_start[i][2].r == Track_to_start[j][2].r)
)
{
if(Track_to_start[i][L-1].d<Track_to_start[j][L-1].d)
@@ -583,7 +611,7 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
for (size_t i=0; i<Track_to_start.size();i++)
{
if (Track_to_start[i].size() < 3) continue;
if(trust_track->size()<MAX_TRACK_INDEX)
if(trust_track->size() < static_cast<std::size_t>(rdp_parameter.track_index_max))
{
int index = track_index_mangement.track_ind_get(trust_track);
if(index>0)
@@ -595,8 +623,8 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
double Z2[2]={(Track_to_start[i][L-1].r)*cos(Track_to_start[i][L-1].azi),(Track_to_start[i][L-1].r)*sin(Track_to_start[i][L-1].azi) };
double X[6];
double P[6][6];
double T1=(Track_to_start[i][L-2].T-Track_to_start[i][L-3].T)/1000.0;
double T2=(Track_to_start[i][L-1].T-Track_to_start[i][L-2].T)/1000.0;
double T1=(Track_to_start[i][L-2].CPI_time-Track_to_start[i][L-3].CPI_time)/1000.0;
double T2=(Track_to_start[i][L-1].CPI_time-Track_to_start[i][L-2].CPI_time)/1000.0;
kalman Kalman;
Kalman.kalman_filter_init_3dots(Z0, Z1, Z2, T1,T2, X ,P);
@@ -604,17 +632,24 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
memcpy(trust_track_tmp.X, X, 6*sizeof(double));
memcpy(trust_track_tmp.P, P, 6*6*sizeof(double));
trust_track_tmp.Amplitude=Track_to_start[i][L-1].Amp;
trust_track_tmp.snr_point = Track_to_start[i][L-1].snr;
//计算航迹指纹
trust_track_tmp.Track_fingerprint = computeFingerprint(Track_to_start[i][L-1].r, Track_to_start[i][L-1].azi, Track_to_start[i][L-1].vr, Track_to_start[i][L-1].RCS, Track_to_start[i][L-1].CPI_time);
trust_track_tmp.track_id = Track_to_start[i][L-1].temp_track_id;
trust_track_tmp.point_id = Track_to_start[i][L-1].point_id;
trust_track_tmp.circle_id = Track_to_start[i][L-1].circle_id;
trust_track_tmp.Amplitude=Track_to_start[i][L-1].amplitude;
trust_track_tmp.snr_point = Track_to_start[i][L-1].SNR;
trust_track_tmp.RCS = Track_to_start[i][L-1].RCS;
trust_track_tmp.pitch_num = Track_to_start[i][L-1].pitch_num;
std::memcpy(trust_track_tmp.speed_dim, Track_to_start[i][L-1].speed_dim, sizeof(trust_track_tmp.speed_dim));
std::memcpy(trust_track_tmp.range_dim, Track_to_start[i][L-1].range_dim, sizeof(trust_track_tmp.range_dim));
trust_track_tmp.T_track = Track_to_start[i][L-1].T;
trust_track_tmp.T_track = Track_to_start[i][L-1].CPI_time;
trust_track_tmp.GNSS_time = Track_to_start[i][L-1].GNSS_time;
trust_track_tmp.Unupdated_time = 0;
trust_track_tmp.point_flag=1; //实点
trust_track_tmp.Track_Mode=0; //跟踪模式 TWS 0
trust_track_tmp.Target_Type=UNCONF_TARGET;
trust_track_tmp.Target_Type=0;
trust_track_tmp.Track_Index=index;
trust_track_tmp.Height=Track_to_start[i][L-1].height;
trust_track_tmp.Extrapolate_round=0;
@@ -623,6 +658,9 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
trust_track_tmp.associate_point_number = 0;
trust_track_tmp.manual_tracking_flag = 0;
trust_track_tmp.point_type = 0;
trust_track_tmp.Quality = Track_to_start[i][L-1].quality;
trust_track_tmp.Point_sum = L;
trust_track_tmp.Valid_point_sum = L;
trust_track_tmp.u[0]=0.3333;
trust_track_tmp.u[1]=0.3333;
trust_track_tmp.u[2]=0.3333;
@@ -646,19 +684,20 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
Trust_Track_Output[*Trust_track_num_Output-1][j].Track_Index=index;
Trust_Track_Output[*Trust_track_num_Output-1][j].Point_Sum=L;
Trust_Track_Output[*Trust_track_num_Output-1][j].Range=Track_to_start[i][j].r;
Trust_Track_Output[*Trust_track_num_Output-1][j].Azimuth=Track_to_start[i][j].azi/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][j].Range=static_cast<float>(Track_to_start[i][j].r);
Trust_Track_Output[*Trust_track_num_Output-1][j].Azimuth=static_cast<float>(Track_to_start[i][j].azi/PI*180);
{
double elev_ratio = (Track_to_start[i][j].r > 0.0) ? Track_to_start[i][j].height / Track_to_start[i][j].r : 0.0;
if (elev_ratio > 1.0) elev_ratio = 1.0;
if (elev_ratio < -1.0) elev_ratio = -1.0;
Trust_Track_Output[*Trust_track_num_Output-1][j].Elevation=asin(elev_ratio)/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][j].Elevation=static_cast<float>(asin(elev_ratio)/PI*180);
}
Trust_Track_Output[*Trust_track_num_Output-1][j].Range_V=sqrt(pow(Track_to_start[i][j].X[1],2)+pow(Track_to_start[i][j].X[3],2));
Trust_Track_Output[*Trust_track_num_Output-1][j].z=Track_to_start[i][j].height;
Trust_Track_Output[*Trust_track_num_Output-1][j].Amplitude=Track_to_start[i][j].Amp;
Trust_Track_Output[*Trust_track_num_Output-1][j].track_snr = Track_to_start[i][j].snr;
Trust_Track_Output[*Trust_track_num_Output-1][j].track_rcs = Track_to_start[i][j].RCS;
Trust_Track_Output[*Trust_track_num_Output-1][j].Range_V=static_cast<float>(sqrt(pow(Track_to_start[i][j].X[1],2)+pow(Track_to_start[i][j].X[3],2)));
Trust_Track_Output[*Trust_track_num_Output-1][j].z=static_cast<float>(Track_to_start[i][j].height);
Trust_Track_Output[*Trust_track_num_Output-1][j].Amplitude=static_cast<float>(Track_to_start[i][j].amplitude);
Trust_Track_Output[*Trust_track_num_Output-1][j].track_snr = static_cast<float>(Track_to_start[i][j].SNR);
Trust_Track_Output[*Trust_track_num_Output-1][j].track_rcs = static_cast<float>(Track_to_start[i][j].RCS);
Trust_Track_Output[*Trust_track_num_Output-1][j].Quality = static_cast<float>(Track_to_start[i][j].quality * rdp_parameter.track_quality_max);
Trust_Track_Output[*Trust_track_num_Output-1][j].pitch_num = Track_to_start[i][j].pitch_num;
@@ -668,25 +707,25 @@ void Track_Init::tmp_track_to_trust_track(std::vector<Trust_Track> *trust_tra
Direction_Angle=atan2(Track_to_start[i][j].X[3],Track_to_start[i][j].X[1]);
if(Direction_Angle<0)
Direction_Angle=Direction_Angle+2*PI;
Trust_Track_Output[*Trust_track_num_Output-1][j].Direction_Angle=Direction_Angle/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][j].Direction_Angle=static_cast<float>(Direction_Angle/PI*180);
Trust_Track_Output[*Trust_track_num_Output-1][j].track_time=(Track_to_start[i][j].T)/1000.0;
Trust_Track_Output[*Trust_track_num_Output-1][j].track_time=static_cast<float>((Track_to_start[i][j].CPI_time)/1000.0);
Trust_Track_Output[*Trust_track_num_Output-1][j].GNSS_time=Track_to_start[i][j].GNSS_time;
Trust_Track_Output[*Trust_track_num_Output-1][j].x=Track_to_start[i][j].X[0];
Trust_Track_Output[*Trust_track_num_Output-1][j].v_x=Track_to_start[i][j].X[1];
Trust_Track_Output[*Trust_track_num_Output-1][j].y=Track_to_start[i][j].X[2];
Trust_Track_Output[*Trust_track_num_Output-1][j].v_y=Track_to_start[i][j].X[3];
Trust_Track_Output[*Trust_track_num_Output-1][j].x=static_cast<float>(Track_to_start[i][j].X[0]);
Trust_Track_Output[*Trust_track_num_Output-1][j].v_x=static_cast<float>(Track_to_start[i][j].X[1]);
Trust_Track_Output[*Trust_track_num_Output-1][j].y=static_cast<float>(Track_to_start[i][j].X[2]);
Trust_Track_Output[*Trust_track_num_Output-1][j].v_y=static_cast<float>(Track_to_start[i][j].X[3]);
Trust_Track_Output[*Trust_track_num_Output-1][j].range_point=Track_to_start[i][j].r;
Trust_Track_Output[*Trust_track_num_Output-1][j].azi_point=Track_to_start[i][j].azi/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][j].range_point=static_cast<float>(Track_to_start[i][j].r);
Trust_Track_Output[*Trust_track_num_Output-1][j].azi_point=static_cast<float>(Track_to_start[i][j].azi/PI*180);
{
double elev_ratio = (Track_to_start[i][j].r > 0.0) ? Track_to_start[i][j].height / Track_to_start[i][j].r : 0.0;
if (elev_ratio > 1.0) elev_ratio = 1.0;
if (elev_ratio < -1.0) elev_ratio = -1.0;
Trust_Track_Output[*Trust_track_num_Output-1][j].elev_point=asin(elev_ratio)/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][j].elev_point=static_cast<float>(asin(elev_ratio)/PI*180);
}
Trust_Track_Output[*Trust_track_num_Output-1][j].vr_point=Track_to_start[i][j].vr;
Trust_Track_Output[*Trust_track_num_Output-1][j].vr_point=static_cast<float>(Track_to_start[i][j].vr);
Trust_Track_Output[*Trust_track_num_Output-1][j].point_type=0;
Trust_Track_Output[*Trust_track_num_Output-1][j].Flag_Point=1;
Trust_Track_Output[*Trust_track_num_Output-1][j].Track_Mode=0;//跟踪模式 TWS 0
@@ -706,7 +745,7 @@ void Track_Init::tmp_track_die(std::vector <std::vector<Temp_track>> *temp_t
std::vector <std::vector<Temp_track>>::iterator Iter;
for (Iter=temp_track->begin(); Iter!=temp_track->end();)
{
int n=(*Iter).size();
int n=static_cast<int>((*Iter).size());
if (n <= 0)
{
temp_track->erase(Iter);
@@ -726,12 +765,12 @@ void Track_Init::tmp_track_die(std::vector <std::vector<Temp_track>> *temp_t
}
};
int Track_Init::track_init_prohibit(double r, double azi, struct RadarPara Work_Parameter)
int Track_Init::track_init_prohibit(double r, double azi)
{
for (int i=0;i<Work_Parameter.track_prohibite_area_num;i++)
for (int i=0;i<rdp_parameter.track_init_prohibite_area_num;i++)
{
if(r<Work_Parameter.R_max_track_prohibited[i] && r>Work_Parameter.R_min_track_prohibited[i] && azi<Work_Parameter.Azimuth_max_track_prohibited[i] && azi>Work_Parameter.Azimuth_min_track_prohibited[i])
if(r<rdp_parameter.track_init_prohibite_area_range_max[i] && r>rdp_parameter.track_init_prohibite_area_range_min[i] && azi<rdp_parameter.track_init_prohibite_area_azimuth_max[i] && azi>rdp_parameter.track_init_prohibite_area_azimuth_min[i])
{
return 1;
}
@@ -741,6 +780,51 @@ int Track_Init::track_init_prohibit(double r, double azi, struct RadarPara
}
uint64_t Track_Init::mix64(uint64_t x) {
x ^= x >> 30; x *= 0xBF58476D1CE4E5B9ULL;
x ^= x >> 27; x *= 0x94D049BB133111EBULL;
x ^= x >> 31;
return x;
}
uint64_t Track_Init::quantize(double v, double scale) {
if (std::isnan(v)) return 0x9E3779B97F4A7C15ULL;
if (std::isinf(v))
return v > 0 ? 0x2C08A6D5E3F1B907ULL : 0xD5E3F1B9072C08A6ULL;
double q = std::round(v * scale); // half away from zero
constexpr double kLim = 9.0e18; // < 2^63,避免转 int64 溢出
if (q > kLim) q = kLim;
if (q < -kLim) q = -kLim;
return static_cast<uint64_t>(static_cast<int64_t>(q));
}
uint64_t Track_Init::bits_of(double v) {
if (std::isnan(v)) return 0x7FF8000000000000ULL;
if (v == 0.0) return 0ULL;
uint64_t b;
std::memcpy(&b, &v, sizeof(b));
return b;
}
uint16_t Track_Init::fold16(uint64_t h) {
h ^= h >> 32;
h ^= h >> 16;
return static_cast<uint16_t>(h);
}
uint16_t Track_Init::computeFingerprint(double r, double azi, double vr, double RCS,
long long CPI_time)
{
uint64_t h = kSeed;
h = mix64(h ^ quantize(r, 100.0)); // 距离:保留 0.01 m
h = mix64(h ^ quantize(azi, 1000.0)); // 方位:保留 0.001°
h = mix64(h ^ quantize(vr, 100.0)); // 径向速度:保留 0.01 m/s
h = mix64(h ^ quantize(RCS, 10.0)); // RCS:保留 0.1 dBsm
h = mix64(h ^ static_cast<uint64_t>(CPI_time));
return fold16(h);
}
Track_Init::Track_Init()
{
+15 -10
View File
@@ -1,4 +1,4 @@
#ifndef TRACK_INIT_H
#ifndef TRACK_INIT_H
#define TRACK_INIT_H
#include "data_process_class_dll.h"
#include "track_index_mangement.h"
@@ -18,8 +18,7 @@ public:
std::vector<Trust_Track> *trust_track, //可靠航迹
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter
int *Trust_track_num_Output
);
Track_Init();
@@ -27,25 +26,31 @@ public:
private:
long long temp_track_id = 0; //临时航迹id
std::vector<PointRecv> point_process; //要处理的点迹
void point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter); //临时航迹与点迹关联
void point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track); //临时航迹与点迹关联
void point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter); //航迹头与点迹关联
void point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track); //航迹头与点迹关联
void tmp_track_to_trust_track( std::vector <Trust_Track> *trust_track, //临时航迹转可靠航迹
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter);
int *Trust_track_num_Output);
void tmp_track_die(std::vector <std::vector<Temp_track>> *temp_track);//临时航迹消亡
double alpha_cal_track_init(double x0,double y0,double x1,double y1,double x2,double y2); //计算夹角
int track_init_prohibit(double r, double azi, struct RadarPara Work_Parameter); //判断目标是否位于航迹起始屏蔽区 是返回1 否返回0
int track_init_prohibit(double r, double azi); //判断目标是否位于航迹起始屏蔽区 是返回1 否返回0
const uint64_t kSeed = 0x243F6A8885A308D3ULL;
uint64_t mix64(uint64_t x); // splitmix64 混合函数:对任意 64bit 输入产生雪崩均匀的 64bit 输出
uint64_t quantize(double v, double scale); // 量化:把 double 映射到整数域,可容忍 ±0.5/scale 的浮点噪声
uint64_t bits_of(double v); // bit-exact:直接使用 IEEE754 位模式(-0.0 归一为 +0.0,NaN 归一)
uint16_t fold16(uint64_t h); // 把 64bit 折叠成 16bit,充分使用全部熵
uint16_t computeFingerprint(double r, double azi, double vr, double RCS, long long CPI_time); //计算航迹指纹
Track_Ind_Mangement track_index_mangement;
@@ -1,4 +1,4 @@
#include "track_init_direct_tracking.h"
#include "track_init_direct_tracking.h"
#include "kalman.h"
#include "coor_trans.h"
#include <cmath>
@@ -13,7 +13,6 @@ int Track_Init_Direct_Tracking::track_init_process_logic( std::vector<PointRecv>
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter,
int track_ID
)
{
@@ -22,8 +21,7 @@ int Track_Init_Direct_Tracking::track_init_process_logic( std::vector<PointRecv>
}
void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter) //临时航迹与点迹关联
void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track) //临时航迹与点迹关联
{
//关联信息
struct Asso_info
@@ -33,26 +31,26 @@ void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<
};
std::vector <Asso_info> asso_info;
for ( int i=0;i<point_process.size();i++)
for ( size_t i=0;i<point_process.size();i++)
{
for ( int j=0;j<(*temp_track).size();j++)
for ( size_t j=0;j<(*temp_track).size();j++)
{
int L = (*temp_track)[j].size();
int L = static_cast<int>((*temp_track)[j].size());
if( (*temp_track)[j].size() > 1 && (*temp_track)[j][L-1].buff_round >= 2)
{
//点迹信息
// double Z[2]={ point_process[i].Range*cos(point_process[i].Azimuth),point_process[i].Range*sin(point_process[i].Azimuth)} ;
double Z[3]={point_process[i].Range,
point_process[i].Azimuth,
point_process[i].Velocity};
double vr_point=point_process[i].Velocity;
double r_point=point_process[i].Range;
double Z[3]={point_process[i].range,
point_process[i].azimuth,
point_process[i].velocity};
double vr_point=point_process[i].velocity;
double r_point=point_process[i].range;
double prt = point_process[i].PRF_index;
double freq_ind = point_process[i].Freq_index;
double h_point = point_process[i].Height;
double T_point = point_process[i].CPI_Time;
double freq_ind = point_process[i].freq_index;
double h_point = point_process[i].height;
double T_point = static_cast<double>(point_process[i].CPI_time);
//航迹信息
double X[4];
@@ -62,7 +60,7 @@ void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<
double v_temp_track=(*temp_track)[j][L-1].vr;
double r_temp_track=(*temp_track)[j][L-1].r;
double h_temp_track=(*temp_track)[j][L-1].height;
double T_track_head = (*temp_track)[j][L-1].T;
double T_track_head = static_cast<double>((*temp_track)[j][L-1].CPI_time);
double delta_T = (T_point - T_track_head)/1000.0;
@@ -82,14 +80,14 @@ void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<
double y2=Z[0]*sin(Z[1]);
double alpha = alpha_cal_track_init(x0,y0,x1,y1,x2,y2);
if(d*d<=TRACK_START_THRESHOLD*TRACK_START_THRESHOLD && alpha<ALPHA_START && vr_point*v_temp_track>0)// && fabs(h_point-h_temp_track )<= r_point*SIGMA_E&& abs(vr_point-v_temp_track)/abs(v_temp_track)<0.8
if(d*d<=rdp_parameter.track_init_threshold*rdp_parameter.track_init_threshold && alpha<rdp_parameter.track_init_angle && vr_point*v_temp_track>0)// && fabs(h_point-h_temp_track )<= r_point*rdp_parameter.sigma_pitch&& abs(vr_point-v_temp_track)/abs(v_temp_track)<0.8
{
struct Asso_info asso_info_tmp;
asso_info_tmp.point_idx = i+1;
asso_info_tmp.track_idx = j+1;
asso_info_tmp.point_idx = static_cast<int>(i)+1;
asso_info_tmp.track_idx = static_cast<int>(j)+1;
asso_info.push_back(asso_info_tmp);
(*temp_track)[j][L-1].asso_flag = 1;
point_process[i].Use_Flag = 1;
point_process[i].use_flag = 1;
}
}
}
@@ -97,12 +95,12 @@ void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<
}
// temp_track中加入新关联上的临时航迹
for (int i = 0 ; i<asso_info.size();i++ )
for (size_t i = 0 ; i<asso_info.size();i++ )
{
(*temp_track).push_back(std::vector <Temp_track> ());
//前L个点
int L = (*temp_track)[asso_info[i].track_idx-1].size();
int L = static_cast<int>((*temp_track)[asso_info[i].track_idx-1].size());
for (int j=0;j < L;j++ )
{
(*temp_track)[(*temp_track).size()-1].push_back((*temp_track)[asso_info[i].track_idx-1][j]);
@@ -111,15 +109,15 @@ void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<
//关联上的点
Temp_track asso_track_info_tmp = {};
asso_track_info_tmp.r = point_process[asso_info[i].point_idx-1].Range;
asso_track_info_tmp.azi = point_process[asso_info[i].point_idx-1].Azimuth;
asso_track_info_tmp.height = point_process[asso_info[i].point_idx-1].Height;
asso_track_info_tmp.Amp = point_process[asso_info[i].point_idx-1].Amplitude;
asso_track_info_tmp.T = point_process[asso_info[i].point_idx-1].CPI_Time;
asso_track_info_tmp.vr = point_process[asso_info[i].point_idx-1].Velocity;
asso_track_info_tmp.snr = point_process[asso_info[i].point_idx-1].snr;
asso_track_info_tmp.r = point_process[asso_info[i].point_idx-1].range;
asso_track_info_tmp.azi = point_process[asso_info[i].point_idx-1].azimuth;
asso_track_info_tmp.height = point_process[asso_info[i].point_idx-1].height;
asso_track_info_tmp.amplitude = point_process[asso_info[i].point_idx-1].amplitude;
asso_track_info_tmp.CPI_time = point_process[asso_info[i].point_idx-1].CPI_time;
asso_track_info_tmp.vr = point_process[asso_info[i].point_idx-1].velocity;
asso_track_info_tmp.SNR = point_process[asso_info[i].point_idx-1].SNR;
asso_track_info_tmp.RCS = point_process[asso_info[i].point_idx-1].RCS;
asso_track_info_tmp.pitch_num = point_process[asso_info[i].point_idx-1].pitch_num;
asso_track_info_tmp.pitch_num = point_process[asso_info[i].point_idx-1].pitch_beam_index;
std::memcpy(asso_track_info_tmp.speed_dim, point_process[asso_info[i].point_idx-1].speed_dim, sizeof(asso_track_info_tmp.speed_dim));
std::memcpy(asso_track_info_tmp.range_dim, point_process[asso_info[i].point_idx-1].range_dim, sizeof(asso_track_info_tmp.range_dim));
asso_track_info_tmp.asso_flag=0;
@@ -127,12 +125,12 @@ void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<
// asso_track_info_tmp.Temp_track_section_idx = floor(point_process[asso_info[i].point_idx-1].beam_index/BEAM_NUM_DOT_SECTION)+1;
double Z0[2]={(*temp_track)[asso_info[i].track_idx-1][L-1].r*cos((*temp_track)[asso_info[i].track_idx-1][L-1].azi),
(*temp_track)[asso_info[i].track_idx-1][L-1].r*sin((*temp_track)[asso_info[i].track_idx-1][L-1].azi)};
double Z1[2]={point_process[asso_info[i].point_idx-1].Range*cos(point_process[asso_info[i].point_idx-1].Azimuth),
point_process[asso_info[i].point_idx-1].Range*sin(point_process[asso_info[i].point_idx-1].Azimuth)};
double Z1[2]={point_process[asso_info[i].point_idx-1].range*cos(point_process[asso_info[i].point_idx-1].azimuth),
point_process[asso_info[i].point_idx-1].range*sin(point_process[asso_info[i].point_idx-1].azimuth)};
double X[4];
double P[4][4];
kalman Kalman;
double T = ( point_process[asso_info[i].point_idx-1].CPI_Time - (*temp_track)[asso_info[i].track_idx-1][L-1].T)/1000.0;
double T = ( point_process[asso_info[i].point_idx-1].CPI_time - (*temp_track)[asso_info[i].track_idx-1][L-1].CPI_time)/1000.0;
Kalman.kalman_filter_init_2dots(Z0,Z1,T,X,P);
memcpy(asso_track_info_tmp.X, X, 4*sizeof(double));
@@ -163,8 +161,7 @@ void Track_Init_Direct_Tracking::point_temp_track_asso(std::vector <std::vector<
}
}
void Track_Init_Direct_Tracking::point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter) //航迹头与点迹关联
void Track_Init_Direct_Tracking::point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track) //航迹头与点迹关联
{
//关联上的信息
struct Asso_info
@@ -175,10 +172,10 @@ void Track_Init_Direct_Tracking::point_track_head_asso( std::vector <std::vector
std::vector <Asso_info> asso_info;
//关联
for ( int i=0;i<point_process.size();i++)
for ( size_t i=0;i<point_process.size();i++)
{
for ( int j=0;j<temp_track->size();j++)
for ( size_t j=0;j<temp_track->size();j++)
{
if((*temp_track)[j].size()==1 && (*temp_track)[j][0].buff_round >= 2)
@@ -187,11 +184,11 @@ void Track_Init_Direct_Tracking::point_track_head_asso( std::vector <std::vector
//点迹信息
double x_point, y_point, vr_point;
coor_trans Coor_trans;
Coor_trans.polar2cart(&x_point,&y_point,point_process[i].Range,point_process[i].Azimuth);
vr_point=point_process[i].Velocity;
double r_point=point_process[i].Range;
double h_point = point_process[i].Height;
double T_point = point_process[i].CPI_Time;
Coor_trans.polar2cart(&x_point,&y_point,point_process[i].range,point_process[i].azimuth);
vr_point=point_process[i].velocity;
double r_point=point_process[i].range;
double h_point = point_process[i].height;
double T_point = static_cast<double>(point_process[i].CPI_time);
//航迹信息
double x_track_head, y_track_head;
@@ -199,37 +196,37 @@ void Track_Init_Direct_Tracking::point_track_head_asso( std::vector <std::vector
y_track_head=(*temp_track)[j][0].X[2];
double v_track_head=(*temp_track)[j][0].vr;
double h_track_head = (*temp_track)[j][0].height;
double T_track_head = (*temp_track)[j][0].T;
double T_track_head = static_cast<double>((*temp_track)[j][0].CPI_time);
//距离差
double dis = sqrt(pow(x_point-x_track_head,2)+pow(y_point-y_track_head,2));
double vmax;
if(Work_Parameter.work_mode == 0) //近程模式 最大速度减小一点
if(rdp_parameter.radar_work_mode == 0) //近程模式 最大速度减小一点
{
//vmax = V_MAX/5;
vmax = Work_Parameter.V_MAX;
//vmax = rdp_parameter.track_init_velocity_max/5;
vmax = rdp_parameter.track_init_velocity_max;
}
else //中远程模式 最大速度正常用
{
// vmax = V_MAX;
vmax = Work_Parameter.V_MAX;
// vmax = rdp_parameter.track_init_velocity_max;
vmax = rdp_parameter.track_init_velocity_max;
}
double delta_T = (T_point - T_track_head)/1000.0;
//满足关联条件的点航
if( ( (r_point>=1000 && dis<=vmax*delta_T && dis>=Work_Parameter.V_MIN*delta_T) || (r_point<1000 && dis<=vmax*delta_T/2.0 && dis>=Work_Parameter.V_MIN*delta_T/2.0) )
&& vr_point*v_track_head>0 )//&& fabs(vr_point-v_track_head)/fabs(v_track_head)<0.2 && fabs(h_point-h_track_head)<=r_point*SIGMA_E
if( ( (r_point>=1000 && dis<=vmax*delta_T && dis>=rdp_parameter.track_init_velocity_min*delta_T) || (r_point<1000 && dis<=vmax*delta_T/2.0 && dis>=rdp_parameter.track_init_velocity_min*delta_T/2.0) )
&& vr_point*v_track_head>0 )//&& fabs(vr_point-v_track_head)/fabs(v_track_head)<0.2 && fabs(h_point-h_track_head)<=r_point*rdp_parameter.sigma_pitch
{
struct Asso_info asso_info_tmp;
asso_info_tmp.point_idx = i+1;
asso_info_tmp.track_idx = j+1;
asso_info_tmp.point_idx = static_cast<int>(i)+1;
asso_info_tmp.track_idx = static_cast<int>(j)+1;
asso_info.push_back(asso_info_tmp);
(*temp_track)[j][0].asso_flag = 1;
point_process[i].Use_Flag = 1;
point_process[i].use_flag = 1;
}
}
@@ -237,7 +234,7 @@ void Track_Init_Direct_Tracking::point_track_head_asso( std::vector <std::vector
}
// temp_track中加入新关联上的临时航迹
for (int i = 0 ; i<asso_info.size();i++ )
for (size_t i = 0 ; i<asso_info.size();i++ )
{
(*temp_track).push_back(std::vector <Temp_track> ());
@@ -247,15 +244,15 @@ void Track_Init_Direct_Tracking::point_track_head_asso( std::vector <std::vector
//第二个点
struct Temp_track asso_track_info_tmp = {};
asso_track_info_tmp.r = point_process[asso_info[i].point_idx-1].Range;
asso_track_info_tmp.azi = point_process[asso_info[i].point_idx-1].Azimuth;
asso_track_info_tmp.height = point_process[asso_info[i].point_idx-1].Height;
asso_track_info_tmp.Amp = point_process[asso_info[i].point_idx-1].Amplitude;
asso_track_info_tmp.vr = point_process[asso_info[i].point_idx-1].Velocity;
asso_track_info_tmp.snr = point_process[asso_info[i].point_idx-1].snr;
asso_track_info_tmp.r = point_process[asso_info[i].point_idx-1].range;
asso_track_info_tmp.azi = point_process[asso_info[i].point_idx-1].azimuth;
asso_track_info_tmp.height = point_process[asso_info[i].point_idx-1].height;
asso_track_info_tmp.amplitude = point_process[asso_info[i].point_idx-1].amplitude;
asso_track_info_tmp.vr = point_process[asso_info[i].point_idx-1].velocity;
asso_track_info_tmp.SNR = point_process[asso_info[i].point_idx-1].SNR;
asso_track_info_tmp.RCS = point_process[asso_info[i].point_idx-1].RCS;
asso_track_info_tmp.T = point_process[asso_info[i].point_idx-1].CPI_Time;
asso_track_info_tmp.pitch_num = point_process[asso_info[i].point_idx-1].pitch_num;
asso_track_info_tmp.CPI_time = point_process[asso_info[i].point_idx-1].CPI_time;
asso_track_info_tmp.pitch_num = point_process[asso_info[i].point_idx-1].pitch_beam_index;
std::memcpy(asso_track_info_tmp.speed_dim, point_process[asso_info[i].point_idx-1].speed_dim, sizeof(asso_track_info_tmp.speed_dim));
std::memcpy(asso_track_info_tmp.range_dim, point_process[asso_info[i].point_idx-1].range_dim, sizeof(asso_track_info_tmp.range_dim));
asso_track_info_tmp.asso_flag = 0;
@@ -263,12 +260,12 @@ void Track_Init_Direct_Tracking::point_track_head_asso( std::vector <std::vector
// asso_track_info_tmp.Temp_track_section_idx = floor(point_process[asso_info[i].point_idx-1].beam_index/BEAM_NUM_DOT_SECTION)+1;;
double Z0[2]={(*temp_track)[asso_info[i].track_idx-1][0].r*cos((*temp_track)[asso_info[i].track_idx-1][0].azi),
(*temp_track)[asso_info[i].track_idx-1][0].r*sin((*temp_track)[asso_info[i].track_idx-1][0].azi)};
double Z1[2]={point_process[asso_info[i].point_idx-1].Range*cos(point_process[asso_info[i].point_idx-1].Azimuth),
point_process[asso_info[i].point_idx-1].Range*sin(point_process[asso_info[i].point_idx-1].Azimuth)};
double Z1[2]={point_process[asso_info[i].point_idx-1].range*cos(point_process[asso_info[i].point_idx-1].azimuth),
point_process[asso_info[i].point_idx-1].range*sin(point_process[asso_info[i].point_idx-1].azimuth)};
double X[4];
double P[4][4];
kalman Kalman;
double T = (point_process[asso_info[i].point_idx-1].CPI_Time - (*temp_track)[asso_info[i].track_idx-1][0].T)/1000.0;
double T = (point_process[asso_info[i].point_idx-1].CPI_time - (*temp_track)[asso_info[i].track_idx-1][0].CPI_time)/1000.0;
Kalman.kalman_filter_init_2dots(Z0,Z1,T,X,P);
memcpy(asso_track_info_tmp.X, X, 4*sizeof(double));
@@ -299,14 +296,13 @@ void Track_Init_Direct_Tracking::tmp_track_to_trust_track( std::vector<Trust_Tr
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter,
int track_ID)
{
//temp_track中的临时航迹转为可靠航迹
std::vector <std::vector<Temp_track>>::iterator Iter;
for (Iter=temp_track->begin(); Iter!=temp_track->end();)
{
int L=(*Iter).size();
int L=static_cast<int>((*Iter).size());
if (L < 3)
{
++Iter;
@@ -314,7 +310,7 @@ void Track_Init_Direct_Tracking::tmp_track_to_trust_track( std::vector<Trust_Tr
}
double range=(*Iter)[L-1].r;
double azi=(*Iter)[L-1].azi/PI*180;
if( L==Work_Parameter.track_start_point_num) //按长度查找TRUST_TRACK_POINT
if( L==rdp_parameter.track_init_point_num_min) //按长度查找TRUST_TRACK_POINT
{
//加入本地航迹文件
@@ -323,8 +319,8 @@ void Track_Init_Direct_Tracking::tmp_track_to_trust_track( std::vector<Trust_Tr
double Z2[2]={((*Iter)[L-1].r)*cos((*Iter)[L-1].azi),((*Iter)[L-1].r)*sin((*Iter)[L-1].azi) };
double X[6];
double P[6][6];
double T1=((*Iter)[L-2].T-(*Iter)[L-3].T)/1000.0;
double T2=((*Iter)[L-1].T-(*Iter)[L-2].T)/1000.0;
double T1=((*Iter)[L-2].CPI_time-(*Iter)[L-3].CPI_time)/1000.0;
double T2=((*Iter)[L-1].CPI_time-(*Iter)[L-2].CPI_time)/1000.0;
kalman Kalman;
Kalman.kalman_filter_init_3dots(Z0, Z1, Z2, T1,T2, X ,P);
Trust_Track trust_track_tmp = {};
@@ -332,13 +328,13 @@ void Track_Init_Direct_Tracking::tmp_track_to_trust_track( std::vector<Trust_Tr
memcpy(trust_track_tmp.P, P, 6*6*sizeof(double));
trust_track_tmp.Track_Index = track_ID;
trust_track_tmp.Amplitude=(*Iter)[L-1].Amp;
trust_track_tmp.snr_point = (*Iter)[L-1].snr;
trust_track_tmp.Amplitude=(*Iter)[L-1].amplitude;
trust_track_tmp.snr_point = (*Iter)[L-1].SNR;
trust_track_tmp.RCS = (*Iter)[L-1].RCS;
trust_track_tmp.T_track = (*Iter)[L-1].T;
trust_track_tmp.T_track = (*Iter)[L-1].CPI_time;
trust_track_tmp.point_flag=1; //实点
trust_track_tmp.Track_Mode=1; //跟踪模式 TWS 0
trust_track_tmp.Target_Type=UNCONF_TARGET;
trust_track_tmp.Target_Type=0;
trust_track_tmp.Height=(*Iter)[L-1].height;
trust_track_tmp.Extrapolate_round=0;
trust_track_tmp.approach_flag = -1;
@@ -370,29 +366,29 @@ void Track_Init_Direct_Tracking::tmp_track_to_trust_track( std::vector<Trust_Tr
Trust_Track_Output[*Trust_track_num_Output-1][j].Track_Index=track_ID;
Trust_Track_Output[*Trust_track_num_Output-1][j].Point_Sum=L;
Trust_Track_Output[*Trust_track_num_Output-1][j].Range=(*Iter)[j].r;
Trust_Track_Output[*Trust_track_num_Output-1][j].Azimuth=(*Iter)[j].azi/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][j].Elevation=asin((*Iter)[j].height/(*Iter)[j].r)/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][j].Range_V=sqrt(pow((*Iter)[j].X[1],2)+pow((*Iter)[j].X[3],2));
Trust_Track_Output[*Trust_track_num_Output-1][j].z=(*Iter)[j].height;
Trust_Track_Output[*Trust_track_num_Output-1][j].Amplitude=(*Iter)[j].Amp;
Trust_Track_Output[*Trust_track_num_Output-1][j].track_snr = (*Iter)[j].snr;
Trust_Track_Output[*Trust_track_num_Output-1][j].track_rcs = (*Iter)[j].RCS;
Trust_Track_Output[*Trust_track_num_Output-1][j].Range=static_cast<float>((*Iter)[j].r);
Trust_Track_Output[*Trust_track_num_Output-1][j].Azimuth=static_cast<float>((*Iter)[j].azi/PI*180);
Trust_Track_Output[*Trust_track_num_Output-1][j].Elevation=static_cast<float>(asin((*Iter)[j].height/(*Iter)[j].r)/PI*180);
Trust_Track_Output[*Trust_track_num_Output-1][j].Range_V=static_cast<float>(sqrt(pow((*Iter)[j].X[1],2)+pow((*Iter)[j].X[3],2)));
Trust_Track_Output[*Trust_track_num_Output-1][j].z=static_cast<float>((*Iter)[j].height);
Trust_Track_Output[*Trust_track_num_Output-1][j].Amplitude=static_cast<float>((*Iter)[j].amplitude);
Trust_Track_Output[*Trust_track_num_Output-1][j].track_snr = static_cast<float>((*Iter)[j].SNR);
Trust_Track_Output[*Trust_track_num_Output-1][j].track_rcs = static_cast<float>((*Iter)[j].RCS);
double Direction_Angle;
Direction_Angle=atan2((*Iter)[j].X[3],(*Iter)[j].X[1]);
if(Direction_Angle<0)
Direction_Angle=Direction_Angle+2*PI;
Trust_Track_Output[*Trust_track_num_Output-1][j].Direction_Angle=Direction_Angle/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][j].Direction_Angle=static_cast<float>(Direction_Angle/PI*180);
Trust_Track_Output[*Trust_track_num_Output-1][j].track_time=((*Iter)[j].T)/1000.0;
Trust_Track_Output[*Trust_track_num_Output-1][j].x=(*Iter)[j].X[0];
Trust_Track_Output[*Trust_track_num_Output-1][j].v_x=(*Iter)[j].X[1];
Trust_Track_Output[*Trust_track_num_Output-1][j].y=(*Iter)[j].X[2];
Trust_Track_Output[*Trust_track_num_Output-1][j].v_y=(*Iter)[j].X[3];
Trust_Track_Output[*Trust_track_num_Output-1][j].range_point=(*Iter)[j].r;
Trust_Track_Output[*Trust_track_num_Output-1][j].azi_point=(*Iter)[j].azi/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][j].elev_point=asin((*Iter)[j].height/(*Iter)[j].r)/PI*180;
Trust_Track_Output[*Trust_track_num_Output-1][j].vr_point=(*Iter)[j].vr;
Trust_Track_Output[*Trust_track_num_Output-1][j].track_time=static_cast<float>(((*Iter)[j].CPI_time)/1000.0);
Trust_Track_Output[*Trust_track_num_Output-1][j].x=static_cast<float>((*Iter)[j].X[0]);
Trust_Track_Output[*Trust_track_num_Output-1][j].v_x=static_cast<float>((*Iter)[j].X[1]);
Trust_Track_Output[*Trust_track_num_Output-1][j].y=static_cast<float>((*Iter)[j].X[2]);
Trust_Track_Output[*Trust_track_num_Output-1][j].v_y=static_cast<float>((*Iter)[j].X[3]);
Trust_Track_Output[*Trust_track_num_Output-1][j].range_point=static_cast<float>((*Iter)[j].r);
Trust_Track_Output[*Trust_track_num_Output-1][j].azi_point=static_cast<float>((*Iter)[j].azi/PI*180);
Trust_Track_Output[*Trust_track_num_Output-1][j].elev_point=static_cast<float>(asin((*Iter)[j].height/(*Iter)[j].r)/PI*180);
Trust_Track_Output[*Trust_track_num_Output-1][j].vr_point=static_cast<float>((*Iter)[j].vr);
Trust_Track_Output[*Trust_track_num_Output-1][j].point_type=0;
Trust_Track_Output[*Trust_track_num_Output-1][j].Flag_Point=1;
Trust_Track_Output[*Trust_track_num_Output-1][j].Track_Mode=1;//跟踪模式 TWS 0
@@ -419,7 +415,7 @@ void Track_Init_Direct_Tracking::tmp_track_die(std::vector <std::vector<Temp_tra
std::vector <std::vector<Temp_track>>::iterator Iter;
for (Iter=temp_track->begin(); Iter!=temp_track->end();)
{
int n=(*Iter).size();
int n=static_cast<int>((*Iter).size());
if (n <= 0)
{
temp_track->erase(Iter);
@@ -1,4 +1,4 @@
#ifndef TRACK_INIT_DIRECT_TRACKING_H
#ifndef TRACK_INIT_DIRECT_TRACKING_H
#define TRACK_INIT_DIRECT_TRACKING_H
#include "data_process_class_dll.h"
#include "parameters.h"
@@ -14,7 +14,6 @@ public:
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter,
int track_ID
);
@@ -25,17 +24,14 @@ private:
std::vector<PointRecv> point_process; //要处理的点迹
void point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter); //临时航迹与点迹关联
void point_temp_track_asso(std::vector <std::vector<Temp_track>> *temp_track); //临时航迹与点迹关联
void point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track,
struct RadarPara Work_Parameter); //航迹头与点迹关联
void point_track_head_asso( std::vector <std::vector<Temp_track>> *temp_track); //航迹头与点迹关联
void tmp_track_to_trust_track( std::vector<Trust_Track> *trust_track, //临时航迹转可靠航迹
std::vector <std::vector<Temp_track>> *temp_track,
struct Track Trust_Track_Output[MAX_TRACK_NUM][10],
int *Trust_track_num_Output,
struct RadarPara Work_Parameter,
int track_ID
);
+655
View File
@@ -0,0 +1,655 @@
# 雷达数据处理流程与关键逻辑说明
> 适用对象:本仓库 `data_process_class_dll`(X256 雷达数据处理动态链接库)
---
## 0. 一页速览
| 环节 | 输入 | 处理 | 输出 |
|---|---|---|---|
| ① 前处理 | 一帧原始点迹(TWS 或 TAS) | 单位换算、缓存、判圈 | 处理模式码 0/1/2/3 |
| ② 调度 | 模式码 | 分流到 TWS / TAS 通道 | — |
| ③ 凝聚 | 单 CPI 或相邻两 CPI 点迹 | 同类点合并(保幅度大者) | 凝聚点迹表 |
| ④ 关联 | 凝聚点迹 + 已有航迹 | IMM 交互 → 波门筛选 → 最近邻一对一 → EKF 更新 | 更新航迹 + 剩余点迹 |
| ⑤ 起始 | 剩余(未关联)点迹 | 逻辑法(每圈顺序固定):点 × ≥2 点临时航迹关联 → 点 × 1 点航迹头关联 → 剩余点转新航迹头 → 达标临时航迹起批 | 新可靠航迹 + 更新后的临时航迹表 |
| ⑥ 外推/消亡 | 未关联航迹 | 未到外推上限则按数据率预测一帧并输出,超限只累加轮数、不再预测也不再输出(TWS 上限 3 轮;TAS 判据方向相反,相扫 5 / 机扫 48 轮起才预测) | 本圈有更新的航迹(含补点行)/ 消亡批号 |
| ⑦ 波束控制 | TAS 队列 + 航迹 | 数据率门控 + 队列调度 → 位置外推 | 跟踪波束指令 |
**贯穿全流程的三条主线**
1. **时间主线**:所有时间戳为 64 位毫秒(CPI/GNSS),参与运算时除 1000 转秒;ΔT = 点迹时间 − 航迹时间,ΔT 的正负决定"有效更新 / 只消费不更新 / 跳过"。
2. **状态主线**:每条可靠航迹维护一套 IMM(3 个 Singer 模型)状态 [x, vx, ax, y, vy, ay],x/y 两轴解耦,量测为「距离-方位-径向速度」三维 EKF。
3. **生命周期主线**:可靠航迹的消亡分两种判据——**TWS 普通航迹自 V1.5.8 起按航迹质量**(`Quality < track_die_quality`,配置项 0.3),**重点目标按外推轮数**(判据 `> 10`)、**TAS 按外推轮数**(判据 `> 阈值`:相扫 20 / 机扫 96 圈);临时航迹靠"缓存圈数 buff_round"消亡。
---
## 1. 总体数据流
```mermaid
flowchart TD
IN["宿主逐波位/逐CPI送入 DataRev[150]"] --> DP["data_preprocess 前处理与判圈"]
DP -->|"model=2 TWS未满圈"| M2["滑窗凝聚(只凝聚,不关联)"]
DP -->|"model=1 TWS满一圈"| M1["TWS 通道"]
DP -->|"model=3 TAS数据"| M3["TAS 通道"]
M2 --> BUF["本帧凝聚结果留作下一圈缓存"]
M2 --> PR["上一帧凝聚点 → point_recv(参与下一圈关联)"]
M1 --> A1["IMM 交互 + 波门筛选 + 最近邻关联 + EKF 更新"]
A1 --> A2["未关联航迹外推 / 超限消亡"]
A2 --> A3["剩余点迹 → 逻辑法起始:点×临时航迹 → 点×航迹头 → 剩余点转航迹头 → 起批"]
A3 --> OUT1["输出更新航迹 + 起批航迹历史点 + 消亡批号"]
M3 --> B1["TAS 凝聚(单帧)"]
B1 --> B2["按航迹指纹定位航迹,仅对它做关联/EKF 或外推"]
B2 --> B3["TAS 超时消亡"]
B3 --> OUT2["输出该航迹"]
OUT1 --> BC["Beam_Ctrl 波束控制"]
OUT2 --> BC
BC --> OUT3["TrackingBeam:距离/方位/俯仰/航迹指纹"]
```
---
## 2. 入口前处理与"判圈"
### 2.1 前处理动作
对每一个输入点迹统一做下面几件事(TWS 与 TAS 相同):
| 动作 | 说明 |
|---|---|
| 单位换算 | 方位 度 → 弧度(内部统一弧度,输出再转度) |
| 高度折算 | `Height = Range · sin(Elevation)`(由量测俯仰直接折算,不经坐标滤波) |
| 字段搬运 | 幅度、SNR、RCS、时间戳、频点号、PRI、俯仰波位、十字星数据(`speed_dim[129]`/`range_dim[8]`)全部透传 |
| 使用标记 | 全部置 0(未使用) |
| 数量保护 | TWS:`min(Point_Sum, 150)`;TAS:`clamp(Point_Sum, 0, 150)` |
| 全局时间 | 每次调用都用本帧首点的 CPI 时间刷新"最新时间戳",供 TAS 外推/波束使用 |
### 2.2 判圈逻辑(决定走哪条通道)
```mermaid
flowchart TD
A["收到一帧点迹"] --> B{"point_type ?"}
B -->|"0 = TWS"| C["写入 TWS 缓存"]
C --> D{"判圈条件成立(由配置 scan_mode 选择)?<br/>scan_mode=0 相扫: Beam_index_aiz <= last_beam_num<br/>scan_mode=1 机扫: Beam_index_aiz == 0"}
D -->|"是"| E["return 1:满一圈,执行 TWS 航迹处理"]
D -->|"否"| F["return 2:未满圈,仅做滑窗凝聚"]
B -->|"1 = TAS"| G["写入 TAS 缓存<br/>记录 TAS 航迹指纹与本帧时间戳"]
G --> H["return 3:TAS 处理"]
B -->|"其它"| I["return 0:无效数据,丢弃"]
```
**要点**
- 判圈条件由配置项 `[radar_params] scan_mode` **运行时**选择(0 相扫 / 1 机扫;加载器校验仅允许 0/1),替代原先 `#ifdef PHASE_SCANNING` 的编译期分支:
- **scan_mode=0(相扫)**:比较前后两帧方位波位号,当前帧 `Beam_index_aiz <= last_beam_num` 判为满一圈;`last_beam_num` 参与判断,`track_clear_all()` 复位为 INT_MAX,故**清空后/首帧即判满一圈**;
- **scan_mode=1(机扫)**:判 **波位号为 0**(7d5a56f 起),避免转台反转时波位号变小导致误判一圈完成(代码中保留了旧判断的注释);该模式下 `last_beam_num` 仍被更新但不参与判断。
- 两种模式下 `last_beam_num` 每帧都会更新,仅判满圈时 `circle_id++` 并返回 1;判圈行为不再由构建期区分(机扫/相扫已合并为同一份构建),仅由配置项 `scan_mode` 决定(见 README 4.2)。
- 机扫下波位号 0 是每圈的起始波位,因此**每圈恰好触发一次 model=1**。
- TAS 数据不参与判圈,**每来一帧就处理一次**(TAS 是调度的跟踪波位,本身就是单目标 CPI)。
- TAS 帧携带的是**航迹指纹**(`DataRev::TAS_track_index`,字段名沿用,V1.5.9 起语义为指纹),`track_process(model=3)` 先用它在航迹表中定位航迹,再进入 TAS 关联(见 7.1、7.2 与 README 9.12)。
### 2.3 调度:model 与动作的对应
| model | 触发 | 实际执行的动作(顺序固定) |
|---|---|---|
| 2 | TWS 未满圈 | 两帧滑窗凝聚;缓存本帧点迹 |
| 1 | TWS 满一圈 | ① 关联/滤波/外推 ② 航迹消亡 ③ 航迹起始 |
| 3 | TAS 帧 | ① TAS 凝聚 ② TAS 关联/外推 ③ TAS 消亡 |
| 0 | 非法类型 | 无 |
> 顺序很关键:**消散在起始之前**,消亡腾出的资源不会与本圈更新冲突;**起始在最后**,只有"没被任何可靠航迹用掉"的点迹才有资格起批。
---
## 3. 点迹凝聚
### 3.1 目的与判据
同一目标在相邻波位/相邻 CPI 上会分裂成多个点,需先合并(保留幅度最大者,其余标记删除)。
| 模式 | 距离门限 | 方位门限 | 速度门限 |
|---|---|---|---|
| 近程 `work_mode==0` | ≤ 80 m | ≤ 6°(按环绕最小角差) | ≤ 2 m/s |
| 中/远程 `work_mode!=0` | ≤ 80 m | ≤ 6° | 不约束 |
方位差按环绕处理:
```
Δθ = min( |θ0 − θ1| , 2π − |θ0 − θ1| )
```
### 3.2 合并规则(链式比较)
```mermaid
flowchart TD
A["两两比较点迹 (0, i)"] --> B{"距离/方位/速度<br/>均在门限内?"}
B -- 否 --> Z["跳过"]
B -- 是 --> C{"幅度:点0 < 点i ?"}
C -- 是 --> D["点0 标记删除<br/>并把点i 的 R/方位/V/幅度<br/>接管为当前基准值"]
C -- 否 --> E["点i 标记删除"]
D --> F["后续比较以幅度更大者为基准(链式凝聚)"]
E --> F
F --> G["统一删除:被标记点 + 量程外点<br/>r < 100 m 或 r > 100000 m"]
```
### 3.3 TWS 两帧滑窗(关键机制)
```mermaid
flowchart TD
A["上一帧缓存点(区号=1)"] --> C["合并成同一张表"]
B["本帧新点(区号=2)"] --> C
C --> D["整表凝聚 + 量程过滤"]
D --> E{"点的区号"}
E -->|"1(较早的点)"| F["输出到 point_recv<br/>→ 本圈参与关联/起始"]
E -->|"2(本帧的点)"| G["留在缓存中<br/>→ 与下一帧合并后再输出"]
F --> H["缓存清空,本帧剩余点成为新缓存"]
```
**为什么这样设计**
- 相邻两个 CPI 的同一目标点会落在不同帧里,单帧凝聚无法合并 → 延迟一帧输出,换取跨 CPI 的分裂点合并;
- 代价是关联/起始用的是"上一帧"的点,时间上滞后一圈,但由于后续所有 ΔT 都按真实 CPI 时间戳计算,滤波本身不受影响;
- 因此 TWS 通道的正常节奏是:**未满圈 model=2 只做凝聚 + 缓存;满圈 model=1 用上一帧凝聚结果做关联与起始**。
> TAS 凝聚为**单帧**不缓存,门限恒为「距离 ≤ 80 m、方位 ≤ 6°、速度 ≤ 2 m/s」,之后同样做量程过滤。
---
## 4. TWS 点航关联与滤波(model=1 的核心)
### 4.1 处理骨架
```mermaid
flowchart TD
A["全部可靠航迹"] --> A1["IMM 交互(mix)"]
A1 --> B["滤波前先统一置 point_flag=0(本圈默认未关联实点)"]
B --> C["逐航迹 × 逐点:算 ΔT、三个模型马氏距离 d1/d2/d3"]
C --> D{"波门判定<br/>统计距离波门 thd_d + 高度门限 thd_h + 距离门限 thd_r"}
D -- 通过 --> E["记入候选表(含 d_min = min(d1,d2,d3))"]
D -- 不通过 --> F["丢弃该候选对"]
E --> G["最近邻:反复取全表 d_min 最小者"]
G --> H["该航迹本圈候选全部移除<br/>该点迹候选全部移除<br/>(一对一)"]
H --> I{"ΔT > 0 ?"}
I -- 是 --> J["三模型 EKF 更新 → 模型概率更新 → 融合输出<br/>更新航迹时间/点信息/高度平滑<br/>Extrapolate_round=0, point_flag=1"]
I -- 否 --> K["不更新状态、不刷新航迹时间<br/>但把该点迹标记为已使用(消费掉)"]
J --> L{"还有候选?"}
K --> L
L -- 有 --> G
L -- 无 --> M{"未关联航迹(point_flag==0 且非手动TAS):<br/>Extrapolate_round ≤ extrapolate_max ?"}
M -- 是 --> M1["按数据率 Singer 预测一帧<br/>推进航迹时间,Track_Update_Flag=1"]
M -- 否 --> M2["不预测、不推进时间<br/>仅 Extrapolate_round+1,Track_Update_Flag=0"]
M1 --> N["只输出 Track_Update_Flag==1 的航迹行并清零该标志<br/>(其后由 track_init 追加起批历史点)"]
M2 --> N
```
### 4.2 波门(统计距离、高度、距离三类都通过才算候选)
**(1)统计距离波门 thd_d**——按航迹距离分段收紧高机动模型(`associate_threshold`,样例配置 4):
| 航迹距离 r_track | 模型 1 / 2 | 模型 3 |
|---|---|---|
| ≤ 500 m | d² < 16 | d² < 16/1000(≈ 关闭,抑制近程高机动模型的乱关联) |
| 500 ~ 1000 m | d² < 16 | d² < 16/100(收紧) |
| ≥ 1000 m | d² < 16 | d² < 16 |
> ⚠ **门限语义**:代码里 d 已经是马氏距离平方,判据又写作 `d*d < associate_threshold*associate_threshold`,样例配置 4 时等效于 **d < 4**(而非 √d < 4)。调整 `associate_threshold` 时务必记住这一层"二次方"(该行为在 BUG-20 中被识别,有意保留)。
**(2)高度门限 thd_h**(e0bf227 起在 TWS 侧生效)——|h_track − h_point| 按航迹距离分段:
| 航迹距离 r_track | 高度差门限 |
|---|---|
| ≤ 1000 m | 150 m |
| 1000 ~ 2000 m | 200 m |
| 2000 ~ 3000 m | 250 m |
| 3000 ~ 5000 m | 300 m |
| > 5000 m | 不约束 |
> 该门限此前在 `track_asso.cpp` 中整块被注释、`d_h` 恒为 1(等于不生效),现改为独立布尔量 `thd_h` 参与判定,取值与 TAS 通道一致,用于压制俯仰测量跳变引起的错误关联。
**(3)距离门限 thd_r**——抑制远距离突跳。用航迹-点迹弦距 R 与航迹位移 VT = v_track·ΔT 判断:
```
VT < 100 且 R > 300 → 拒绝(慢速目标不允许 300 m 以上的跳变)
100 ≤ VT ≤ 300 且 R/VT > 3 → 拒绝(允许的跳变与速度位移成比例)
VT > 300 且 R > 1200 → 拒绝(高速目标给固定 1200 m 上限)
```
**(4)俯仰门限 d_p**:分距离段 7°/5°/10° 的逻辑仍被注释停用,不参与判定(其作用已由高度门限覆盖)。
> 实现要点:三类判据分别算成 `thd_d` / `thd_h` / `thd_r` 三个布尔量,最后以 `thd_d && thd_h && thd_r` 决定是否记入候选表(e0bf227 起;此前三者混写在一个长 `if` 条件里)。
### 4.3 最近邻(NN)选取规则
- 候选对按 `d_min = min(d1, d2, d3)` 排序,**每轮取全表最小值**;
- 选中后,候选表中"该航迹的全部候选"和"该点迹的全部候选"一并删除 → **每条航迹每圈最多关联一个点,每个点最多被一条航迹使用**;
- 直到候选表为空或遍历完航迹数。
### 4.4 ΔT ≤ 0 的特殊处理(当前行为)
ΔT = (点迹 CPI 时间 − 航迹 T_track)/1000 ≤ 0,典型场景是"航迹刚被 TAS 点迹更新过,本圈 TWS 点迹时间更早"或"重复时间戳"。
| 环节 | 行为 |
|---|---|
| 是否参与最近邻比较 | **参与**(仍可能与最小 d 一起被选中) |
| IMM/EKF 更新 | **不执行**,X1/X2/X3、P1/P2/P3、模型概率均保持原值 |
| 航迹时间/点信息 | **不刷新**(T_track、GNSS_time、关联点信息、幅度/SNR/RCS 都不变) |
| 新鲜度标志 | `point_flag` 保持 0、`Extrapolate_round` 不归零、`associate_point_number` 不递增 |
| 本圈后续 | 该航迹本圈不再关联其它点迹;由于 point_flag 仍为 0,**在随后的外推环节被推进一帧**(未超外推上限时;超限后只累加轮数、不再预测也不输出该行) |
| 点迹归属 | 该点迹被标记"已使用"消费掉 → **不会再进入航迹起始**,避免生成虚假 TWS 航迹 |
### 4.5 关联成功后的更新内容
1. 三个模型的 EKF 状态/协方差更新;
2. 模型概率按新息协方差似然更新(见第 5 节);
3. 航迹时间 T_track / GNSS 时间跟进到该点迹;
4. 关联点信息(距离/方位/俯仰/径向速度/PRI)、幅度、SNR、RCS、归一化幅度 `amp_asv_point`、速度维 FFT 点数 `prn_fftn_point`、俯仰波位、十字星数据刷新;
5. **高度维平滑**(见 4.6);
6. `Extrapolate_round = 0`、`point_flag = 1`、`associate_point_number += 1`、`Track_Update_Flag = 1`(输出门控,见 4.7 末)。
### 4.6 高度平滑
关联实点后把点迹高度压入滑窗,输出高度取最近 w 个点的均值(不足 w 个则全平均)。窗口按距离分段,TWS/TAS 偏移不同、窗长基数取配置项 `height_smooth_win`(样例配置为相扫 6;机扫需配 3。TAS 偏移于 6284f3f 由 +2/+3/+4/+6 改为 +6/+8/+10/+12):
| 点迹距离 | TWS 窗口(相扫/机扫) | TAS 窗口(相扫/机扫) |
|---|---|---|
| ≤ 1000 m | 8 / 5 | 12 / 9 |
| 1000 ~ 2000 m | 9 / 6 | 14 / 11 |
| 2000 ~ 4000 m | 10 / 7 | 16 / 13 |
| > 4000 m | 12 / 9 | 18 / 15 |
> 距离越远俯仰测量跳变越大,窗口随之放长;仅 TWS/TAS 关联到实点时才刷新,外推补点不改变高度。
### 4.7 未关联航迹的外推(含外推圈数上限)
对 `point_flag == 0` 且 `manual_tracking_flag == 0` 的航迹(含 ΔT≤0 被消费的情形),按 `Extrapolate_round` 分两支(50e0b5c):
**(1)未到上限**:`Extrapolate_round <= extrapolate_max`(配置项,默认 3)时照常外推:
- 推算步长取配置项 `data_rate_tws`(秒;原实现按 `work_mode` 在 `DATA_RATE_SHORT/MIDDLE/FAR` 三档中三选一,改造后统一为单字段);
- 用各子模型的 Singer F、Q 推进一帧(**不做量测更新**);
- `T_track`、`GNSS_time` 各推进 ΔT×1000 毫秒;
- `Extrapolate_round += 1`,`point_flag = 0`(补点/虚点),`Track_Update_Flag = 1` → 本圈作为补点行输出。
**(2)已超上限**:判据不再成立时**不再做 Singer 预测、不推进航迹时间**,只执行:
- `Extrapolate_round += 1`、`point_flag = 0`、`Track_Update_Flag = 0` → 本圈**不输出**该航迹(航迹仍留在航迹表中,等消亡判据命中)。
**输出门控(50e0b5c)**:TWS 输出航迹的循环改为「`Track_Update_Flag == 1` 才输出,输出后清零」,即每圈只输出**本圈关联到实点或成功外推**的航迹;此前是每圈把航迹表中全部可靠航迹各输出一行。门控只判更新标志、不判 `manual_tracking_flag`,因此手动转 TAS 的航迹若本圈在 TWS 侧关联到实点,仍会经 TWS 通道输出。
> **普通 TWS 航迹的消亡改由质量决定(V1.5.8)**:`track_die.cpp` 对 `Track_Mode==0 && primary_target_flag==0` 的航迹改用 `Quality < track_die_quality`(配置项 0.3)判消亡,因此外推超限后航迹仍会驻留,直到质量衰减到门限以下;`Extrapolate_round` 继续每圈 +1 但不再决定普通 TWS 航迹的存活。重点航迹(10 圈)与 TAS(相扫 20 / 机扫 96)不变。
>
> 质量在外推环节按未更新时长二次衰减(`Unupdated_time` 累计,ΔT 由 `work_mode` 数据率决定),且该计算位于外推上限判断之外,故上限内、外都会衰减。
>
> **TAS 通道的判据方向相反**(`track_asso_tas.cpp`):`Extrapolate_round >= extrapolate_max_tas`(配置项,样例相扫 5 / 机扫需配 48)时才做预测、推进航迹时间并置 `Track_Update_Flag = 1`;未达该轮数时只累加圈数(不预测、不输出)。即 TWS 是"先外推、到上限后停",TAS 是"先不预测、到轮数后才开始外推",详见 7.2 与第 10 节;TAS 不维护航迹质量。
---
## 5. 航迹起始(逻辑法)
### 5.1 状态机(每圈按 ①→⑤ 的固定顺序执行)
```mermaid
flowchart TD
A["本圈未被可靠航迹关联的点迹"] --> A0["临时航迹所有点的 buff_round +1"]
A0 --> B["① 点 × ≥2 点临时航迹关联(三点及以上)<br/>EKF 统计距离 + 折角 + 多普勒同号<br/>命中 → 复制原航迹 + 追加新点"]
B --> C["② 点 × 1 点航迹头关联(两点起始)<br/>速度区间 + 多普勒一致性<br/>命中 → 两点卡尔曼初始化 → 2 点临时航迹"]
C --> D["③ 删除 Use_Flag==1 的点迹<br/>剩余点全部转成新航迹头(1 点,buff_round=1)<br/>当圈不再参与关联"]
D --> E{"④ 长度 ≥ 起批点数<br/>且 ≤10 且不在起批屏蔽区?"}
E -- 是 --> F["同源去重(保留末点 d 更小者)"]
F --> G["三点卡尔曼初始化 → 分配航迹号 1~500<br/>IMM 三模型同状态,u=1/3"]
G --> H["输出该航迹的历史点(前 N 个点)"]
E -- 否 --> I["留在临时航迹表,下一圈继续关联"]
H --> J["⑤ 临时航迹消亡(不对外输出)<br/>末点 buff_round > 2 或 长度 ≥ 10"]
I --> J
```
**每圈顺序不可颠倒(`track_init.cpp` L49-126)**:点迹**先**做点-临时航迹关联(延长已有 ≥2 点临时航迹),**再**做点-航迹头关联(产生新的 2 点临时航迹),两阶段都结束后才把未被消费的点迹转成新航迹头,最后才把长度达标的临时航迹转可靠航迹,消亡放在最末。由此可得:
- 当圈新建的 2 点临时航迹**不可能当圈起批**——长度不足,且新点 `buff_round=1` 要到下一圈才具备关联资格;
- 当圈刚被延长到达标长度的临时航迹**可以当圈起批**(第 ④ 步在第 ① 步之后执行);
- 两个关联阶段**互不排斥**:都只把命中的点迹标记 `Use_Flag=1`,航迹头阶段不检查临时航迹阶段留下的标记,因此同一点迹可同时延长一条临时航迹、又开启一条 2 点临时航迹,只有走完两阶段后才统一删除被标记的点迹。
> 第 ⑤ 步的消亡判据对**未起批**的临时航迹生效;已起批的航迹在第 ④ 步中已从临时航迹表移除。
### 5.2 关联判据汇总
每圈按 **① 点-临时航迹关联 → ② 点-航迹头关联** 的顺序依次执行,两阶段的关联判据如下。
**阶段①:点 ↔ ≥2 点临时航迹(三点及以上)**
```
|r_point − r_末点| ≥ 5 m 距离差硬门限(近距点不参与)
d ≤ 8(代码写作 d² ≤ track_init_threshold²=64) 3 维 EKF 统计距离波门
α < track_init_angle = 60° 折角:最近三点连线夹角
v_r,point · v_r,末点 > 0 多普勒同号
```
- d 由 4 维 CV 状态一步预测 + 「距离-方位-径向速度」EKF 得到(过程噪声 0.03²、同样做方位环绕与盲速折叠);
- 折角 α 为向量 D12(前一点→末点)与 D23(末点→新点)的夹角;α 越小越接近直线;
- 门限同样写作 `d² ≤ track_init_threshold²`,而 d 本身已是马氏距离平方,`track_init_threshold=8` 时实际生效为 d ≤ 8(与可靠航迹关联门限的语义一致)。
**阶段②:点 ↔ 航迹头(两点起始)**
用航迹头位置与点迹的平面距离 `dis` 与 ΔT(秒):
```
dis ≤ V_MAX · ΔT 平均速度不超上限
dis ≥ V_MIN · ΔT 平均速度不低于下限(滤除静止/慢速杂波)
v_r,point · v_r,head > 0 两点多普勒同号(同接近/同远离)
dis ≥ |v_r,point| · ΔT 几何位移不小于"点迹自身多普勒"位移
dis ≥ |v_r,head| · ΔT 同上,用航迹头的多普勒约束
```
- 后两条是**多普勒-几何一致性**约束,用于剔除"切向高速短航迹"(7d5a56f 新增):切向飞行时真实几何位移大而多普勒径向速度小,纯速度区间门限会放进虚假航迹;
- 判据隐含要求 ΔT > 0(ΔT ≤ 0 时 `dis ≤ V_MAX·ΔT` 必然不成立);
- 近程/中远程分支当前取值相同(都是 V_MAX),**实际不做距离分段**。
### 5.3 临时航迹的"圈数"生命周期
`buff_round` 只在起始流程里自增,并且**每次调用(= 每圈)先把所有临时航迹所有点的 buff_round +1,再做关联**;因此关联判据看到的是自增后的圈数,当圈刚建立/刚追加的点(buff_round=1)当圈不可能再被关联:
| 动作 | 效果 |
|---|---|
| 新点加入 | buff_round = 1 |
| 关联资格 | 只看**末点**,要求 buff_round ≥ 2 → **上一圈加入的点本圈才能被延续**;同一圈内一条临时航迹可被多个点同时延续(每个点各复制出一条新航迹,见 6.4),并非一对一 |
| 消亡条件 | 末点 buff_round > 2(连续 2 圈无新点)或长度 ≥ 10 |
**时间轴示例(以第 k 圈加入新点为起点)**
```
第 k 圈:新点加入 → 末点 buff_round=1(本圈不再关联)
第 k+1 圈:+1 → 2 → 允许关联;成功则新点 1,失败则 2(不 >2,存活)
第 k+2 圈:+1 → 3 → 仍允许关联;成功则新点 1,失败则 3>2 → 消亡
```
即:**允许中间空 1 圈**,最大关联间隔 2 圈;生命周期完全由"圈数"驱动,与绝对时间无关。
### 5.4 航迹分裂与去重
- 关联阶段**不做一对一分配**:一个点满足多条临时航迹条件时,每条都各自复制出一条新航迹;多个点命中同一条临时航迹时也各自复制;点-航迹头阶段同理(一个航迹头可与多个点各生成一条 2 点临时航迹)→ 同一圈会产生大量同源临时航迹;
- 两个关联阶段也**互不排斥**:点-航迹头阶段不检查点-临时航迹阶段留下的 `Use_Flag`,同一点迹可同时参与两边的分裂试探;
- 起批前做**同源去重**:比较两条候选航迹的**前三个点**,只要有任一点的"时间 T + 距离 r"完全相同即判为同源,保留末点统计距离 d 更小者;
- 这样既保证不漏关联(允许分裂试探),又不会让同一批点重复起批。
### 5.5 起批与输出
| 条件 | 说明 |
|---|---|
| 长度 | `track_init_point_num_min ≤ L ≤ 10`(配置加载器校验 3~9,典型 3 或 4) |
| 位置 | 末点不落在**起批屏蔽区**(距离-方位矩形,最多 30 个,用严格不等号判断) |
| 容量 | 可靠航迹数 < 500 且能分配到一个未占用航迹号 |
| 初始化 | 用**最后三个点**做三点卡尔曼初始化 → 6 维状态;三模型同状态、u=1/3 |
| 输出 | 该航迹的**前 N 个点**(N = 起批点数)作为历史点一次性输出,每条航迹最多 10 个历史点 |
> 达到长度即当圈起批,没有"连续 M 圈确认"的环节;若被屏蔽区拦住,航迹会继续在临时航迹表里增长,直到离开屏蔽区(此时长度已大于起批点数)或长度达到 10 被判消亡。
---
## 6. 外推、消亡与航迹号管理
### 6.1 消亡条件
| 通道 | 条件(满足其一即消亡) |
|---|---|
| TWS 普通航迹 | `Track_Mode==0` 且 `primary_target_flag==0` 且 **`Quality < track_die_quality`(配置项 0.3)** —— V1.5.8(782bff5)起改按航迹质量,不再看外推圈数 |
| TWS 重点航迹 | `Track_Mode==0` 且 `primary_target_flag==1` 且 `Extrapolate_round > track_die_round_pramary`(配置项 10) |
| TAS 航迹 | `Track_Mode==1` 且 `Extrapolate_round > track_die_round_tas`(配置项;样例相扫 20 / 机扫需配 96) |
| 手动删除 | `manual_delete_flag == 1`(各通道单独判定,置位即消亡,由外部删除接口 `track_delete` 置位) |
轮数类判据都改为 `die_flag` 组合("或"关系),且比较符由 `>=` 改为 `>`(50e0b5c):即允许的外推圈数比旧版各多 1 圈,且**重点航迹与普通航迹走不同门限**。由于每圈未关联只自增 1,判据为 `>` 时实际是"连续 N+1 圈没有实点关联"才消亡:重点航迹 11 圈、TAS 相扫 21 圈(阈值于 5d105d2 由 10 提高到 20)/ 机扫 97 圈。
> **普通 TWS 航迹的消亡时长(V1.5.8)**:改按质量后不再有固定圈数,存活时长由质量衰减速度决定——未关联期间按 `Unupdated_time` 的二次函数加速衰减(见 9.9 / README 9.11),`Quality` 跌破 `track_die_quality`(配置项 0.3)即消亡;`track_die_round`(配置项 6)已不再被引用。
> `primary_target_flag`(重点目标标记)由外部接口 `Data_Process::primary_target_add / primary_target_cancel` 按批号置 1 / 清 0(`data_process.cpp`);新起批航迹在 `track_init.cpp` 中以 `Trust_Track trust_track_tmp = {}` 零初始化,故恒为 0。该标记在 TWS 上决定走「质量消亡(普通)」还是「10 圈消亡(重点)」。
消亡时把批号写入消亡输出数组并从航迹表删除;**临时航迹消亡不对外输出**(起批失败的中间态对宿主不可见)。
### 6.2 航迹号分配
```mermaid
flowchart TD
A["需要起批"] --> B{"航迹表为空?"}
B -- 是 --> C["分配 1"]
B -- 否 --> D["用航迹表建立 1~500 占用位图"]
D --> E["从 lastest_index+1 向上找第一个空闲号(含 500)"]
E --> F{"找到?"}
F -- 是 --> G["分配并更新 lastest_index"]
F -- 否 --> H["从 1 回绕扫一遍找空闲号"]
H --> I{"找到?"}
I -- 是 --> G
I -- 否 --> J["返回 0:全满,本候选不起批"]
```
特点:**优先顺序递增分配**(便于人工按批号识别新老),只有走到表尾才回绕复用早期空号。
---
## 7. TAS 通道(model=3)
### 7.1 与 TWS 通道的差异
| 项 | TWS | TAS |
|---|---|---|
| 凝聚 | 相邻两帧滑窗,近程多速度维约束 | 单帧,恒有速度维约束 |
| 处理对象 | 全部可靠航迹 × 全部点 | **仅由指纹定位到的那一条航迹** × 全部点(旧版按批号指定) |
| 俯仰/高度门限 | 俯仰门限已注释停用;**高度门限生效**(按距离分段,e0bf227 起) | **高度门限生效**(按距离分段,取值与 TWS 相同) |
| ΔT ≤ 0 | 参与选取但只消费点迹、不更新 | **直接跳过该点**(并以 `RDP_LOG_WARN` 记告警日志) |
| 外推时间 | 按 work_mode 数据率固定步长 | 按 `最新时间戳 − 航迹时间` |
| 外推触发轮数 | `Extrapolate_round <= extrapolate_max`(配置项 3)才预测 | `Extrapolate_round >= extrapolate_max_tas`(配置项,样例相扫 5 / 机扫需配 48)才预测(方向与 TWS 相反) |
| 输出 | 本圈有更新的航迹(关联到实点或成功外推,输出后清 `Track_Update_Flag`) | 该航迹且本圈有更新;TAS 波束对外下发**航迹指纹**(`Track_fingerprint`),不再下发批号 |
### 7.2 TAS 关联与更新
```mermaid
flowchart TD
A["TAS 本帧点迹 + 航迹指纹"] --> B{"按指纹找到航迹?"}
B -- 否 --> Z["直接返回(不做任何处理)"]
B -- 是 --> B1["置 manual_tracking_flag=1、Track_Mode=1"]
B1 --> C["IMM 交互;航迹 point_flag 置 0"]
C --> D["逐点:ΔT ≤ 0 → 记告警并跳过"]
D --> E["逐点:三模型统计距离 d1/d2/d3 + 高度门限"]
E --> F{"d² < 16(任一模型)且高度门限通过?"}
F -- 是 --> G["记入候选表"]
F -- 否 --> H["丢弃"]
G --> I["取 d_min 最小的一点"]
I --> J["三模型 EKF 更新 → 概率更新 → 融合<br/>刷新时间/点信息/高度平滑"]
J --> K["输出该航迹(Track_Update_Flag==1 才输出,输出后清零)"]
E --> L{"候选表为空?"}
L -- 是 --> L1{"Extrapolate_round ≥ extrapolate_max_tas ?"}
L1 -- 是 --> M["按 ΔT = 最新时间戳 − 航迹时间 外推一帧<br/>(ΔT ≤ 0 则跳过),Track_Update_Flag=1"]
L1 -- 否 --> M2["不预测、不推进时间<br/>仅 Extrapolate_round+1,Track_Update_Flag=0"]
M --> K
M2 --> K
```
**高度门限**(|h_航迹 − h_点迹| 上限,按航迹距离分段):
| 航迹距离 | 门限 |
|---|---|
| ≤ 1000 m | 150 m |
| 1000 ~ 2000 m | 200 m |
| 2000 ~ 3000 m | 250 m |
| 3000 ~ 5000 m | 300 m |
| > 5000 m | 不约束 |
**TAS 后续处理**:外推不受 work_mode 数据率约束,而是直接推进到"本帧最新时间戳",保证 TAS 航迹时间与波束调度时刻同步。
**TAS 外推轮数门控(50e0b5c)**:与 TWS **方向相反**——只有 `Extrapolate_round >= extrapolate_max_tas`(配置项,样例相扫 5 / 机扫需配 48)时才做预测、推进航迹时间并置 `Track_Update_Flag = 1`;未达该轮数时只 `Extrapolate_round += 1`、`point_flag = 0`、`Track_Update_Flag = 0`,既不预测也不输出。因此 TAS 航迹在刚丢失点迹时并不会立刻按"最新时间戳"外推:相扫配置下前 5 次未关联不产生任何输出行,第 6 次起才开始外推输出;机扫配置(48)下前 48 次不输出。该不对称仅如实记录(成因不明,不作缺陷判定),连同 TWS/TAS 输出门控一并见第 10 节。
---
## 8. TAS 波束控制(外部接口 Beam_Ctrl)
### 8.1 队列管理
```mermaid
flowchart TD
A["Beam_Ctrl 调用"] --> B["输出航迹计数清零"]
B --> C["入队 tas_target_add"]
C --> D["出队 tas_target_del"]
D --> E["波束输出 tas_beam_output"]
E --> F["输出 TrackingBeam"]
```
**入队条件**(只支持手动转 TAS):
- 该航迹 `manual_tracking_flag == 1`(由外部 `tracking_start` 置位);
- 航迹当前 `Track_Mode == 0`(还不是 TAS);
- 队列未满(相扫最多 4 个 / 机扫最多 1 个);
- 不在 TAS 禁止区(距离-方位矩形)。
入队动作:写 `Track_Index`(队列内部键,仍是批号)、标记非空、`last_track_time = T_track`、**航迹 Track_Mode 置 1**,并**立即输出一次该航迹更新**(通知宿主跟踪状态变化;V1.5.9 起该行按常规关联输出的完整字段填充,见 README 9.10 与 CHANGELOG `[1.5.9]` 的 eb9f30c)。
**出队条件**:队列中的批号在航迹表中已不存在,或不再处于"手动 + TAS"状态 → 清空该队列项。
> 自动开启/退出 TAS 的逻辑(按距离/速度/高度阈值)已全部注释停用,**只支持手动**。
>
> **指纹与批号的分工(V1.5.9)**:队列、内部关联与出队仍以 `Track_Index` 为键;只有跨 DLL 边界的 `DataRev.TAS_track_index`(输入)与 `TrackingBeam.TAS_track_index`(输出)改用 **16 bit 航迹指纹** `Track_fingerprint`(起批时按末点特征生成,见 README 9.12)。
### 8.2 波束输出调度(关键)
```mermaid
flowchart TD
A["遍历 TAS 队列"] --> B["按队列项 Index(Track_Index,内部键)在航迹表中找到该航迹"]
B --> C{"距上次波束输出 > data_rate_tas 秒<br/>且 CPI 时间早于已选中的最早 CPI?"}
C -- 否 --> A
C -- 是 --> D["选中该目标(CPI 时间最早者)"]
D --> E["位置外推:ΔT = (最新时间戳 − CPI 时间)/1000<br/>x + vx·ΔT, y + vy·ΔT"]
E --> F["Range = √(xp²+yp²),Azi = atan2(yp,xp)<br/>Elev = asin(Height / Range)"]
F --> G["open_flag=1, type=default_track_beam_type, TAS_track_index=航迹指纹<br/>更新该目标 last_track_time = 最新时间戳"]
C -.->|"无任何合格目标"| H["open_flag = 0(其余字段保持旧值)"]
```
**要点**
- 门控只用一个条件(e0bf227 修正):目标「距上次波束输出的时间」(`latest_timestamp − last_track_time`)要大于配置项 `data_rate_tas` 秒(单位是秒,由 TOML 配置文件提供、加载器校验须 > 0)→ 保证 TAS 目标的数据率**不随 Beam_Ctrl 的调用频率变化**;修正前还要求「航迹时间与最新时间戳之差」也大于该门限,与"取最早 CPI 目标"的语义冲突,会漏掉本应输出的波束;
- 多目标时选**队列中 CPI 时间最早**的目标(同时要求其 CPI 时间早于当前已选者),等价于"按最久未更新者优先"的轮转;
- 选中后才按该目标的 CPI 时间算外推步长 `ΔT = (最新时间戳 − CPI 时间)/1000`;
- 原来的"每次调用整队循环移位"已停用;
- 俯仰用航迹高度直接算(叠加雷达平台高度的处理只影响对外输出的 z,不参与波束俯仰)。
---
## 9. 参数脉络(决定行为的旋钮)
> **参数配置方式改造后**:雷达工作参数不再来自 `parameters.h` 编译期宏或宿主下发的 `RadarPara`,而是由 TOML 配置文件(样例 `config/config_tmp.toml`)加载到**全局工作参数对象 `rdp_parameter`**(`struct.h` 中 `extern RDP_parameter rdp_parameter;` 声明、`data_process.cpp` 定义并零初始化),经公开接口 `parameters_reload(配置文件路径)` 在运行期一次性加载(失败返回 -1 且不改变当前参数;严格校验规则与完整参数表见 README 第 10 节)。各处理模块直接访问该全局对象,不再经函数形参逐层传递;全局对象为 DLL 内单实例、非线程安全,配置重载应与处理互斥。下表「样例值」为相扫配置;**机扫需在配置中改**的 5 项(`scan_mode` 及 4 个原按体制区分的参数)已单独标注。
### 9.1 配置文件参数(`[段] 键`,样例值 = 相扫)
| 配置键 | 样例值 | 作用点 |
|---|---|---|
| `[radar_params] radar_work_mode` | 0 | 凝聚是否带速度维约束(当前唯一生效点;航迹起始的远近程分支取值相同,属死分支;TWS 外推已改为固定 `data_rate_tws`,不再按模式三选一) |
| `[radar_params] scan_mode` | 0(**机扫配 1**) | **扫描模式 0 相扫 / 1 机扫**(必填,加载器校验仅允许 0/1):决定 `data_preprocess()` 的 TWS 判圈逻辑(0 用波位号比较、1 用波位号==0),替代原编译期 `PHASE_SCANNING`/`MECHANICAL_SCANNING` 分支;机扫/相扫已合并为同一份构建,同一份 DLL 通过该配置切换扫描模式 |
| `[radar_params] freq_0_GHz` | 9.2 | 盲速计算基准频率(原 `FREQ0`) |
| `[radar_params] radar_height` / `radar_system_delay` | 0 / 0 | 输出 z 叠加平台高度 / 输出位置系统延迟补偿(原 `Height` / `Sys_delay`) |
| `[radar_params] data_rate_tws` | 3 | TWS 未关联航迹外推步长(秒;原按 `work_mode` 在 `DATA_RATE_SHORT/MIDDLE/FAR` 中三选一,现统一单字段) |
| `[radar_params] data_rate_tas` | 1 | TAS 波束输出间隔门控(秒,须 > 0) |
| `[cohesion] point_cohesion_range/velocity/azimuth` | 80 / 2 / 6 | 凝聚门限:距离 m / 速度 m/s / 方位 °(原 `DOT_COH_*`) |
| `[cohesion] point_cohesion_range_min/max` | 100 / 100000 | 有效量程(凝聚后过滤;原 `R_MIN/R_MAX`) |
| `[track_associate] sigma_range/azimuth/pitch/velocity` | 10 / 0.02 / 0.2 / 2.0 | 量测噪声、EKF 的 R、转换量测协方差(原 `SIGMA_*`) |
| `[track_associate] associate_threshold` | 4 | 关联波门(判据 `d*d < 该值²`,d 已是马氏距离平方 → 等效 d < 4;原 `ASSO_THORD`) |
| `[track_associate] extrapolate_max` | 3 | TWS 未关联航迹最大外推轮数(`Extrapolate_round <= 该值` 才预测并输出;原 `MAX_EXTRAPOLATE_NUM`) |
| `[track_associate] extrapolate_max_tas` | 5(**机扫配 48**) | TAS 开始外推的轮数(判据 `>=`,方向与 TWS 相反;原 `MAX_EXTRAPOLATE_NUM_TAS`) |
| `[track_associate] height_smooth_win` | 6(**机扫配 3**) | 高度平滑窗长基数:TWS 取 `+2/+3/+4/+6`、TAS 取 `+6/+8/+10/+12`(原 `H_F_WIN_LEN`) |
| `[track_init] track_init_threshold` / `track_init_angle` | 8 / 60 | 起始统计距离波门(d ≤ 8)/ 起始折角上限(°)(原 `TRACK_START_THRESHOLD` / `ALPHA_START`) |
| `[track_init] track_init_point_num_min` | 3 | 起批所需点数(校验 3~9;原 `RadarPara.track_start_point_num`) |
| `[track_init] track_init_quality_L3~L7` | 0.7 / 0.6 / 0.5 / 0.4 / 0.3 | 起批最低质量(按临时航迹长度 3/4/5/6/≥7;原 `TRACK_START_QUALITY_L3~L7`) |
| `[track_init] track_init_velocity_min/max` | 1 / 50 | 航迹头-点关联的速度区间(原 `V_MIN/V_MAX`) |
| `[track_die] track_die_round` | 6 | **已不被引用**:V1.5.8 起 TWS 普通航迹改按质量消亡(保留备用;原 `TRACK_DIE_ROUND`) |
| `[track_die] track_die_round_pramary` | 10 | TWS 重点航迹(`primary_target_flag==1`)消亡轮数(原 `TRACK_DIE_ROUND_PRIMARY`) |
| `[track_die] track_die_round_tas` | 20(**机扫配 96**) | TAS 航迹消亡轮数(判据 `Extrapolate_round > 该值`;原 `TRACK_DIE_ROUND_TAS`) |
| `[track_die] track_die_quality` | 0.3 | TWS 普通航迹质量消亡门限(`Quality < 该值`;原 `TRACK_DIE_QUALITY`) |
| `[track_quality] track_quality_*` | 100 / 0 / 0.5 / 0.3 / 0.2 / 0.8 / −0.0008 / −0.0025 | 质量折算上限/下限、三项权重(D/SNR/LEN)、平滑系数、衰减系数 O2/O1(原 `TRACK_QUALITY_*`) |
| `[track_index] track_index_max` | 500 | 航迹号上限(原 `MAX_TRACK_INDEX`,内部航迹号表按该值分配) |
| `[beam_control] default_track_beam_type` / `tas_track_num_max` | 1 / 4(**机扫配 1**) | 跟踪波束类型(原硬编码 1)/ 最大同时跟踪 TAS 目标数(原 `MAX_TAS_NUM`) |
| `[singer_model] model_1/2/3_Q_fast/slow` | 0.000001 / 0.01 / 1.0 | 三个 Singer 模型的过程噪声强度(分档见 5.2;原 `RadarPara.Model*_Q_*`) |
| `[track_init_prohibite_area]` / `[tas_prohibite_area]` | `*_num = 0` + 空数组 | 起批屏蔽区 / TAS 禁止区(0~30 组,4 个数组长度必须等于 `*_num`;原 `RadarPara.track_prohibite_area_*` / `TAS_prohibite_area_*`) |
> 旧 `RadarPara` 中实现未使用的字段(`R_TAS_max/min`、`V_TAS_max/min`、`TAS_Height_1~5`、`cfar_th`、`north_angle`、`Beam_num`)未进入 `RDP_parameter`,TAS 自动开启/退出逻辑仍保持停用。
### 9.2 编译期常量(`parameters.h` 保留)
| 常量 / 宏 | 值 | 说明 |
|---|---|---|
| `MAX_TRACK_NUM` | 500 | 输出航迹数组固定容量(宿主接口维度) |
| `RDP_TAS_QUEUE_CAPACITY` | 30 | TAS 队列容量上限;运行期上限用 `tas_track_num_max` |
| `PI` / `Round(x)` | 3.1415926f / 宏 | 圆周率 / 四舍五入取整 |
| 版本宏 | 1.5.9 + `GENERIC` | `VERSION_STRING` → `V1.5.9.GENERIC`(机扫/相扫合并后无体制后缀) |
| `RDP_LOG_TRACE` / `DEBUG` / `INFO` / `WARN` / `ERROR` / `CRITICAL` | 宏(映射 spdlog) | 日志级别宏(fmt 风格格式化),`RDP_LOG_ERR` 为 `RDP_LOG_ERROR` 别名;低于 `SPDLOG_ACTIVE_LEVEL` 的调用在编译期被剔除(实参不求值) |
| `RDP_LOG_DIR` / `RDP_RECORD_*` | `.\rdp_logs\` / — | 日志文件目录(`rdp_YYYYMMDD_HHMMSS.log`,每次 `GetB()` 一个文件,同秒冲突追加 `_1`~`_999`);数据记录开关与性能参数。日志**无** `RDP_LOG_ENABLE` 开关,级别由 CMake 选项 `RDP_LOG_LEVEL` → `SPDLOG_ACTIVE_LEVEL` 在编译期控制(见 README 4.3、10.4) |
> 构建期扫描体制宏已随「机扫/相扫版本合并」移除:`CMakeLists.txt` 不再有 `RADAR_SCAN_MODE`,也不再定义 `PHASE_SCANNING` / `MECHANICAL_SCANNING`;`parameters.h` 的 `VERSION_INFO` 固定为 `GENERIC`(`V1.5.9.GENERIC`)。判圈由配置项 `scan_mode` 决定(见 2.2);`UNCONF_TARGET` 宏已删除(起批处 `Target_Type` 直接写 0)。
---
## 10. 关键约定与注意事项
**时序与时间戳**
- 时间戳为 64 位毫秒;`T_track` 输出为秒(`/1000`);所有 ΔT 用秒参与运算;
- TWS 关联中 ΔT ≤ 0 的候选"只消费点迹、不更新航迹"(点迹不会再去起批);
- TAS 关联中 ΔT ≤ 0 直接跳过该点;TAS 外推 ΔT ≤ 0 跳过本轮;
- 两点/三点卡尔曼初始化对 T 无除零保护 → **起批路径的时间戳必须严格递增**。
**量纲与坐标**
- 外部接口角度为度,内部统一弧度,输出再度;
- 平面坐标为水平投影,z = 目标高度 + 雷达平台高度;
- 点高度由 `R·sin(俯仰)` 直接折算,不参与滤波状态(由高度平滑单独维护)。
**门限语义**
- 关联与起始的波门写作 `d² < 门限²`,而 d 已是马氏距离平方 → 实际等效 **d < 门限**;
- 起始的"距离差 ≥ 5 m"为硬编码,对切向远距离慢速目标可能造成漏关联;
- 起批去重只比较**前三个点**,若起批点数 > 3,共享前三点后分叉的两条航迹会被判为同源。
**容量与边界**
- 点迹数上限 150(前处理已限幅);航迹数/航迹号上限 500,全满时新目标不再起批;
- 屏蔽区/禁止区的组数按 30 组数组定义;配置入口已由加载器强制 `*_num ∈ [0,30]` 且 4 个数组长度等于 `*_num`(非法配置在 `parameters_reload` 阶段即失败),内部实现不再二次校验;
- 高度平滑缓存只增不减,长时运行下每条航迹的内存占用会持续增长。
**外推、消亡与输出门控(50e0b5c / fc49707 / 42b598e / e8c56f9 / V1.5.8)**
- **外推圈数上限**:TWS 只在 `Extrapolate_round <= extrapolate_max`(配置项 3)时预测并输出;TAS 相反,`Extrapolate_round >= extrapolate_max_tas`(配置项,样例相扫 5 / 机扫需配 48)时才预测并输出。两处比较方向不一致,此处仅如实记录,**不判定为缺陷**;
- **普通 TWS 航迹消亡改按质量(V1.5.8)**:`Quality < track_die_quality`(配置项 0.3)即消亡,`track_die_round`(配置项 6)不再被引用;重点航迹仍按 `> track_die_round_pramary`(配置项 10)、TAS 仍按 `> track_die_round_tas`(配置项;样例相扫 20 / 机扫需配 96);`manual_delete_flag == 1` 单独判为消亡;
- **航迹质量(V1.5.8)**:内部 0~1,临时航迹每延长一点按「距离+SNR」更新、关联实点按「距离+SNR+有效点占比」平滑更新、未关联期间按 `Unupdated_time` 二次衰减;起批需达按长度分级的最低质量;输出 `Track.Quality` 为 0~100(×100)。TAS 通道不维护质量;
- **输出门控**:TWS 与 TAS 的输出航迹循环都新增 `Track_Update_Flag == 1` 判定并在输出后清零,只输出**本圈关联到实点或成功外推**的航迹(此前 TWS 每圈把航迹表中全部可靠航迹各输出一行);
- **接近/远离判据**(fc49707 + 42b598e):`Flag_Direction` 不再用实测径向速度 `vr_point > 0`(受多普勒速度模糊影响符号可能取反),改为用 IMM 融合速度方向角判定——`Direction_Angle = atan2(X[4], X[1])`(弧度转度后输出到 `Track.Direction_Angle`),`x`、`y` 为经配置项 `radar_system_delay` 补偿后的位置、`r` 为其模,`acos((-x·cosθ - y·sinθ)/r) < π/2` 判为靠近(1)、否则远离(0),等价于"速度方向与雷达→目标位置方向夹角 > 90°"。中间版本 fc49707 曾写为 `acos((-x·vx - y·vy)/r/Range_V) < π/4`(按合速度归一化、门限 45°),42b598e 改为上式;
- **归一化幅度 / 速度维 FFT 点数(e8c56f9 新增,输入侧已打通)**:`DataRev` 新增 `amp_asv`(归一化幅度 dB)、`prn_fftn`(速度维 FFT 点数),`Track`/内部 `PointRecv`/`Trust_Track` 也新增同名成对字段;`data_preprocess()` 现已在 TWS 与 TAS 两条分支都把这两个字段搬入 `PointRecv`(并随 `RdpRecPoint` 落库),因此 `track_asso.cpp` 写入航迹并在 TWS 输出行写回的 `amp_asv_point` / `prn_fftn_point` 有有效数据来源;**TAS 的关联与输出路径(`track_asso_tas.cpp`)仍未涉及这两个字段**,TAS 输出行中它们保持宿主原值。
- **版本号与记录标识(b227ae1 / c3c7e66 / 5cb67cc)**:`parameters.h` 新增版本宏并拼装为 `VERSION_STRING`(机扫/相扫合并后为 `V1.5.9.GENERIC`;历史上 b227ae1 时为 `V1.5.8.PS` / `V1.5.8.MS`),实例创建后可由基类公开成员 `version_info` 读取;`Data_Process` 新增 `point_id` / `frame_id` / `circle_id` 自增计数(`circle_id` 在 TWS 每判满一圈、TAS 每帧末自增),`PointRecv`/`Temp_track`/`Trust_Track` 新增对应的标识字段,记录功能据此把「输入点迹 → 凝聚点迹 → 临时航迹 → 可靠航迹 → 关联信息」串成同一条链路;可靠航迹与关联信息的记录点为 TWS/TAS 双通道(关联信息以 `Asso_Info::tas_flag` 区分来源)。
**航迹指纹(V1.5.9,0f3da99)**
- 起批时对**末点**计算 `Track_fingerprint`(16 bit):`r`/`azi`/`vr`/`RCS`/`CPI_time` 分别量化到 0.01 m / 0.001° / 0.01 m·s⁻¹ / 0.1 dBsm / 1 ms,经 splitmix64 逐个混合后折叠为 16 bit(`track_init.cpp::computeFingerprint()`);
- TAS 输入帧的 `DataRev::TAS_track_index` 与 TAS 波束输出的 `TrackingBeam::TAS_track_index` 均改为**指纹**(字段名未变);`track_process(model=3)` 按指纹定位航迹,队列与内部关联仍用 `Track_Index`;
- 16 bit 指纹存在碰撞可能,且同一目标消亡后重新起批通常得到不同指纹;记录实体 `RdpRecTrustTrack` 尚未包含该字段(详见 README 9.12、11.2)。
**未启用**
- 引导跟踪(direct tracking)、手动打跟踪波束接口为空实现;
- TAS 自动开启/退出逻辑停用,只支持手动转/停 TAS;
- `tracking_stop` 后,队列项要等到下一次 `Beam_Ctrl` 才真正移除。
---
## 附录:与现有 `README.md` 的差异提示
> 说明:下表是本文档成文时(README 尚未同步 7d5a56f 改动)发现的差异,保留以说明改动缘由;随后已按下表结论更新 README 第 2、7.2、8.1、8.3、9.3、9.6、10.1 等节,「README 描述」列对应更新前的旧表述,现以本文档与 README 的描述为准。
| 项 | README 描述(更新前) | 当前源码 |
|---|---|---|
| 满圈判据 | 当前方位波位号 < 上一帧波位号 | 判据由配置项 `scan_mode` 运行时选择(原按体制编译期分支):**scan_mode=0 相扫**当前波位号 ≤ 上一帧波位号(3e6763c,等价于原判据)、**scan_mode=1 机扫**方位波位号 == 0(7d5a56f,适应机扫转台反转) |
| 点-航迹头判据 | 按距离分段收紧速度门限(半速、1/2~1/3 速) | 该分支注释停用;改为统一速度区间 **+ 两条多普勒-几何一致性约束**(7d5a56f) |
| 起始输出历史点 | "临时航迹的全部 L 个点" | 实际输出**前 `track_init_point_num_min` 个点** |
| 起始输入过滤 | "track_init 跳过 Use_Flag==1 的点" | 上游 `track_asso_process()` 结束前已把 `Use_Flag==1` 的点迹从 `point_recv` 删除;`track_init` 入口(L59-70)也会再剔除一次 `Use_Flag==1` 的点,两道处理叠加,效果一致 |
| 日志开关 | "RDP_LOG_ENABLE 默认开启" | `RDP_LOG_ENABLE` 开关已删除(日志组件改为 spdlog):日志**始终**同时写标准输出与 `.\rdp_logs\` 下按日期+时间命名的日志文件(每次 `GetB()` 一个文件),级别由 CMake 选项 `RDP_LOG_LEVEL` 映射 `SPDLOG_ACTIVE_LEVEL` 在编译期控制(`OFF` 时编译期关闭且不创建日志文件,见 README 4.3、10.4) |
| 起始步骤顺序 | "航迹头(1 点)→ 点-航迹头关联(2 点)→ 点-临时航迹关联(≥3 点)→ 起批" | 每圈实际顺序为**点 × ≥2 点临时航迹关联 → 点 × 1 点航迹头关联 → 剩余点转新航迹头 → 长度达标的临时航迹起批**,消亡最后执行(`track_init.cpp` L49-126,见第 6 节) |
> 后续:README 与 `track_init_readme.md`(航迹起始专项说明)已按上表结论同步(见 CHANGELOG 对应版本的记录);流程落地实现仍以本文为准。
>
> 补充修订(本次):起始流程的**步骤顺序**原先在本文档(第 0、6 节)与 README(第 2、5.2、8.3、9.3 节)中都写作「航迹头 → 2 点 → ≥3 点 → 起批」,与源码每圈「点 × 临时航迹关联 → 点 × 航迹头关联 → 剩余点转航迹头 → 达标者起批」的顺序相反,现已按源码修正,并补充说明两个关联阶段互不排斥、均不做一对一分配。
+335
View File
@@ -0,0 +1,335 @@
# 数据记录调试功能(rdp_record)
> 面向调试的数据记录框架:把内部结构体按「一个记录实体一张表」写入 SQLite,供事后查询分析。
> 本功能**默认只在 Debug 构建中启用**,Release/交付构建完全不编译(无依赖、无代码、无性能损失)。
> V1.5.8 优化后,记录链路为「处理线程收集源结构体 → 有界队列 → 独立写线程构造实体并批量事务入库」,
> 雷达处理线程只承担拷贝与入队开销;写线程跟不上时丢弃数据而不是阻塞处理。
> 相关代码:`data_process_class_dll/rdp_record.h`、`rdp_record_entity.h`、`rdp_record.cpp`;
> 相关参数:`parameters.h` 的「数据记录性能参数」段;第三方依赖来源见 `third_party/ormpp/UPSTREAM.md`。
> 记录模块的日志经 `rdp_log`(内置 spdlog,`third_party/spdlog`)输出,与 DLL 日志同样双写标准输出与 `.\rdp_logs\` 日志文件(分级 info/warn/error,见 README 2、4.3)。
---
## 1. 目的与范围
- **目的**:调试期把关键中间数据落库(点迹、航迹、关联结果……),用于事后 SQL 查询与对比分析;
- **框架性设计**:本功能**不预设记录哪些数据**。新增一类记录数据的成本固定为四步:
加记录实体 → 加异步入队接口与宏 → 在写线程注册批次/构造/提交分支 → 业务侧收集后调用宏(配方见第 6 节);
- **性能模型(V1.5.8 优化)**:
- 记录点只做「帧/圈内收集源结构体 → 一次入队」,处理线程上只有结构体拷贝 + 队列加锁 push 的开销;
- 实体构造(`X`/`P` 等数组转文本)与 SQLite 写入由独立写线程完成,按表批量事务提交;
- 队列**有界**(默认 64 MB / 4096 批),写线程跟不上时**整批丢弃并计数**,绝不阻塞雷达处理线程;
- 数据库使用 WAL + `synchronous=OFF` + 8 MB 页缓存,建表后去掉 `AUTOINCREMENT`,以换取调试场景下的写入吞吐;
- **非目标(本轮明确不做)**:
- 不做关联信息裁剪(分析报告中记为 **P0-a**):⑥ 关联信息仍按「每个候选一行」落库,单圈行数为
「点迹数 × 航迹数」的 M×N 量级;
- 不做记录策略/轮转/在线回放(记为 **P2**):不按容量/时长切库、不清理旧库、不提供查询或回放工具;
- 不保证**跨表**写入顺序(同一张表内的行顺序保持);
- 不保证进程崩溃时的数据完整性:队列尾部与 WAL 尾部可能丢失(异步 + `synchronous=OFF` 的代价);
- 不改变记录行数与表列(与优化前完全一致,见第 10 节验证)。
- **性能摘要**(单机 dup 场景实测,详见第 10 节):普通帧每帧新增耗时 p99 ≈ 0.09~0.11 ms;
判圈帧 track 新增中位 ≈ 2.9~4.1 ms、p99 ≈ 13~24 ms(多次验收运行;200 目标/150 点压测场景,因未采用 P0-a,单圈约 12.3 万关联候选,
属该实现的压测上限;受控 50 目标/50 点场景最大 5.96 ms)。
## 2. 开关与构建
| 项目 | 说明 |
|---|---|
| 开关 | CMake 缓存变量 `RDP_DATA_RECORD`,取值 `AUTO`(默认)/ `ON` / `OFF` |
| AUTO 语义 | `Debug` 构建启用、`Release`(及其它)构建关闭 |
| 生效方式 | 启用时构建系统定义 `RDP_RECORD_ENABLE` 并额外编译静态库 `rdp_record`(`rdp_record.cpp` + 内置 `sqlite3.c`)并链接进 DLL(`CMakeLists.txt` 的 `if(RDP_RECORD_ENABLED)` 段) |
| 关闭时的保证 | 记录模块源码**不参与编译**(`CMakeLists.txt` 的 `if(RDP_RECORD_ENABLED)` 内才 `add_library`/链接);所有记录宏展开为 `((void)0)`(实参不求值,不产生「变量已初始化但未使用」告警);DLL 不新增依赖与导出符号 |
```bat
:: 交付/Release:关闭(AUTO 的默认结果,也可显式 OFF)
.vscode\scripts\configure.bat Release OFF
.vscode\scripts\build.bat Release OFF
:: Debug:启用(AUTO 的默认结果,也可显式 ON)
.vscode\scripts\configure.bat Debug ON
.vscode\scripts\build.bat Debug ON
:: 一次性「配置 + 构建」单个配置(VS Code 任务「配置并构建(...)」的内部实现)
.vscode\scripts\build_config.bat Debug ON
:: 直接用 cmake(需先进入 MSVC 2022 x64 环境)
cmake -S . -B build/Debug/REC_ON -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Debug ^
-DRDP_DATA_RECORD=ON
cmake --build build/Debug/REC_ON
```
构建目录按「构建类型 + 记录模式」隔离:`build/<Debug|Release>/<REC_AUTO|REC_OFF|REC_ON>`(机扫/相扫已合并为同一版本,无扫描体制维度),因此 REC_OFF 与 REC_ON 的产物可以并存;VS Code 任务「配置并构建(...)」与脚本 `build_config.bat`/`clean_all.bat` 均基于该布局(见 README 4.3)。
配置阶段会打印开关状态:`数据记录调试功能:启用(C++20 静态库 rdp_record + 内置 SQLite)` 或 `...:关闭(不编译记录模块,RDP_RECORD 为空操作)`。
**「关闭时零代码」的依据**:
1. 关闭时构建系统不创建 `rdp_record` 静态库,也不把 `sqlite3.c` 加入任何目标;
2. `rdp_record.h` 在未定义 `RDP_RECORD_ENABLE` 时把 `RDP_RECORD_POINTS`/`RDP_RECORD_COHESION_TWS`/`RDP_RECORD_COHESION_TAS`/`RDP_RECORD_TEMP_TRACKS`/`RDP_RECORD_TRUST_TRACKS`/`RDP_RECORD_ASSO_INFOS` 以及遗留的 `RDP_RECORD`/`RDP_RECORD_BATCH` 全部展开为 `((void)0)`,并提供 `Init/Shutdown/db_path/stats/Write` 的内联空实现;
3. 6 个业务文件(`data_process.cpp`、`dot_coh.cpp`、`dot_coh_tas.cpp`、`track_init.cpp`、`track_asso.cpp`、`track_asso_tas.cpp`)中的收集变量、收集语句与宏调用**整体包在 `#ifdef RDP_RECORD_ENABLE` 内**;这些文件已不再包含 `rdp_record_entity.h`,因此实体结构体、`arrayToString()` 与 `struct.h` 不会经记录模块被处理代码拖入;
4. 实测 4 种配置(Release/Debug × REC_ON/OFF)均 0 warning、0 error(机扫/相扫合并后不再有 PS/MS 维度),且 OFF 构建的 DLL 不新增依赖/导出符号。
**语言标准与依赖**:记录模块以 **C++20** 独立编译(ormpp 要求 `CMAKE_CXX_STANDARD 20`),并编译内置的 SQLite amalgamation(因此启用时需要 C 编译器,随 VS 2022 工具集提供);**雷达处理代码与内置 Eigen 仍为 C++17**,二者通过 `rdp_record.h` 的「`Write<T>` 仅声明 + 显式实例化」跨标准边界:调用方(C++17)不实例化记录实现,实现在 `rdp_record.cpp`(C++20)中显式实例化。V1.5.8 起常规记录点改走**非模板**的 `Write*` 异步入队接口,跨标准边界更简单;模板 `Write<T>` 仅作为遗留同步接口保留。
## 3. 运行期行为
| 项目 | 说明 |
|---|---|
| 存放目录 | `RDP_RECORD_DB_DIR`(`parameters.h`,默认 `.\rdp_records\`,相对宿主进程当前工作目录,不存在时自动创建) |
| 文件名 | `rdp_record_YYYYMMDD_HHMMSS.db`(**每次运行一个新文件**,与 `rdp_logs` 下日志文件(`rdp_YYYYMMDD_HHMMSS.log`)的命名风格一致);同一秒内重复 `Init`(如 Destroy→GetB 重连)或同目录多进程启动时,检测到文件已存在则自动追加 `_1`/`_2`…(最多尝试 999 个后缀),不复用同一库文件 |
| 初始化 | `Data_Process_Factory::GetB()` 内调用 `rdp_record::Init()`:建目录 → 生成文件名 → 打开 SQLite → 应用 PRAGMA → **启动记录写线程** → 置 ready;已初始化时幂等,初始化失败返回 `false` 且不抛异常 |
| 写线程 | 独立 `std::thread`,Windows 下用 `SetThreadPriority(..., THREAD_PRIORITY_BELOW_NORMAL)` 把 CPU 优先级降到后台(I/O 保持正常优先级,避免排空过慢);写线程是 SQLite 连接的主要使用者,处理线程不直接访问数据库(遗留同步接口经 `db_mtx` 与写线程串行化) |
| 队列与丢弃 | 处理线程把「一种表的一批源结构体」放进有界 `std::deque`;上限为 `RDP_RECORD_QUEUE_CAP_BYTES`(默认 64 MB,按容器容量近似统计)且 `RDP_RECORD_QUEUE_MAX_BATCHES`(默认 4096 批)。任一上限将超出时**整批丢弃**并累计 `dropped_rows`,不阻塞处理线程;丢弃日志按 `RDP_RECORD_DROP_LOG_INTERVAL`(默认每 1024 行)限频提示 |
| 批事务 | 写线程按表把队列数据追加到 pending 缓冲,当 pending 行数达到 `RDP_RECORD_WRITE_BATCH_ROWS`(默认 4096 行)或为凑批等待 `RDP_RECORD_WRITE_MAX_WAIT_MS`(默认 50 ms)超时后,按**表**分别提交;同一张表的一批数据在同一个 `BEGIN/COMMIT` 事务内插入 |
| 实体构造 | 「源结构体 → 记录实体」(`PointRecv`→`RdpRecPoint`、`Trust_Track`→`RdpRecTrustTrack` 等,含数组转文本)**全部在写线程完成**;处理线程只拷贝源结构体 |
| PRAGMA | `journal_mode=WAL`(`RDP_RECORD_SQLITE_WAL=1` 时尝试,失败回退默认 journal 并提示);`synchronous=OFF`;`cache_size=-8192`(8 MB);`busy_timeout=2000` ms。注:`PRAGMA journal_mode`/`busy_timeout` 会返回结果行,代码用 `query_s<std::tuple<...>>` 消费 |
| 建表 | 首次写入某记录实体时自动建表(每表只尝试一次);ormpp 的 DDL 硬编码 `AUTOINCREMENT`,建表后读 `sqlite_master` 中的 DDL 去掉该关键字并 `DROP` 重建为空表(普通 `INTEGER PRIMARY KEY`,省掉每行对 `sqlite_sequence` 的读写);重建失败时回退为保留 `AUTOINCREMENT` 的表并告警 |
| 失败降级 | 目录不可建/库打不开/取时间失败:`Init()` 返回 `false`,仅打印日志,之后所有写入静默忽略;建表或插入失败:**每张表只提示一次**并永久跳过该表,不重试、不抛出;异步批量写入连续失败达到 `RDP_RECORD_MAX_CONSECUTIVE_ERRORS`(默认 10)次则**全局熔断**(停止记录、丢弃队列并退出写线程) |
| 关闭 | `Data_Process_Factory::Destroy()` 内调用 `rdp_record::Shutdown()`:① 停止接收新数据 → ② 唤醒写线程**排空队列与 pending** → ③ 打印统计 → ④ `PRAGMA wal_checkpoint(TRUNCATE)` → ⑤ 关库;宿主未调用 `Destroy()` 时由静态对象析构兜底停止线程/关库 |
| 重连 | `Destroy()` 之后再 `GetB()`:`Shutdown()` 已释放连接与状态,`Init()` 会**重新建库**(新文件;同秒冲突追加后缀),记录继续写入新库 |
| 写入顺序 | 队列 FIFO;同一张表内的行顺序与采集顺序一致;**不同表之间的提交先后不保证**(写线程按固定表顺序 flush) |
| 运行统计 | `rdp_record::stats()` 任意时刻可读(原子计数、线程安全),`Shutdown()` 时自动打印一行 `[rdp_record] stats(shutdown): ...` |
| 静默忽略 | `Init()` 之前、初始化失败之后、`Shutdown()` 之后的写入一律静默忽略(未初始化时不计入统计;已初始化但已停止/失败时计入 `dropped_rows`) |
> 遗留接口:`RDP_RECORD(...)`/`RDP_RECORD_BATCH(...)` 仍映射到模板 `Write<T>`,在**调用线程内直接写库**(经 `db_mtx` 与写线程串行化)会阻塞,仅供测试/特殊场景;新记录点请使用第 4 节的异步入队接口与宏。
## 4. 接口与宏一览
`rdp_record.h` 对外接口:
| 接口 | 说明 |
|---|---|
| `bool Init()` | 建目录、开库、应用 PRAGMA、启动写线程;由 `GetB()` 调用 |
| `void Shutdown()` | 停止入队、排空队列、checkpoint、关库、打印统计;由 `Destroy()` 调用 |
| `std::string db_path()` | 当前记录库全路径(未启用/未成功打开时为空串) |
| `Stats stats()` | 当前统计快照,线程安全 |
| `void WritePoints(std::vector<PointRecv> v)` | 输入点迹 → `RdpRecPoint`(TWS/TAS 共用) |
| `void WriteCohesionTWS(std::vector<PointRecv> v)` | TWS 凝聚后点迹 → `RdpRecPointCohesionTWS` |
| `void WriteCohesionTAS(std::vector<PointRecv> v)` | TAS 凝聚后点迹 → `RdpRecPointCohesionTAS` |
| `void WriteTempTracks(std::vector<Temp_track> v)` | 临时航迹 → `RdpRecTempTrack` |
| `void WriteTrustTracks(std::vector<Trust_Track> v)` | 可靠航迹 → `RdpRecTrustTrack` |
| `void WriteAssoInfos(std::vector<Asso_Info> v)` | 点航关联信息 → `RdpRecAssoInfo` |
| `template <typename T> void Write(const T&)` / `(const std::vector<T>&)` | **遗留**同步写记录实体(会阻塞调用线程) |
| `struct Stats` | `enqueued_rows` 已入队行数、`dropped_rows` 丢弃行数、`written_rows` 已写库行数、`transactions` 提交事务数、`write_errors` 写失败次数、`queue_peak_bytes`/`queue_peak_rows` 队列峰值 |
记录点宏(参数均为**源结构体 vector 的左值**,宏内部 `std::move`,调用后该 vector 为空):
| 宏 | 记录的源结构体 | 目标表 |
|---|---|---|
| `RDP_RECORD_POINTS(v)` | `std::vector<PointRecv>` | `RdpRecPoint` |
| `RDP_RECORD_COHESION_TWS(v)` | `std::vector<PointRecv>` | `RdpRecPointCohesionTWS` |
| `RDP_RECORD_COHESION_TAS(v)` | `std::vector<PointRecv>` | `RdpRecPointCohesionTAS` |
| `RDP_RECORD_TEMP_TRACKS(v)` | `std::vector<Temp_track>` | `RdpRecTempTrack` |
| `RDP_RECORD_TRUST_TRACKS(v)` | `std::vector<Trust_Track>` | `RdpRecTrustTrack` |
| `RDP_RECORD_ASSO_INFOS(v)` | `std::vector<Asso_Info>` | `RdpRecAssoInfo` |
| `RDP_RECORD(...)` / `RDP_RECORD_BATCH(...)` | 记录实体(单条/批量,可变参数) | 对应实体表(**遗留同步接口**) |
记录功能关闭时上述宏全部展开为 `((void)0)`,实参不求值。
## 5. 记录实体(表)约定
1. 结构体定义在**全局命名空间**(勿放进 `namespace`),推荐以 `RdpRec` 开头;
2. **结构体名即表名**(ormpp 取「短结构体名」),无需任何建表代码;
3. 首字段固定为 `int64_t id`:SQLite 自增主键(写入时固定填 `0`,由 SQLite 分配);自 V1.5.8 起 **DDL 不再使用 `AUTOINCREMENT`**——建表后按 `sqlite_master` 的 DDL 去掉该关键字重建,语义仍是 `ROWID` 自增、`id` 单调递增,但不再维护 `sqlite_sequence`;
4. 字段类型仅限标量,映射关系:
| C++ 类型 | SQLite 类型 | 备注 |
|---|---|---|
| `int` | `INTEGER` | 枚举同理(按整数存) |
| `int64_t` / `long long` | `INTEGER` | 毫秒时间戳用它 |
| `float` | `FLOAT` | |
| `double` | `DOUBLE` | |
| `bool` | `INTEGER` | |
| `std::string` | `TEXT` | 用于把数组类状态(如 `X[6]`、`P[6][6]`、`u[3]`)经 `rdp_record_entity.h` 的 `arrayToString()` 先转成文本再入库——这是「禁止数组字段」的替代写法,见第 5 条 |
5. **禁止数组字段**(如 `float speed_dim[129]`、`char[N]`):ormpp 对 C 数组按 C 字符串语义(`strlen`)绑定,会读越界或写入错误数据。需要记录大批量数组时,按下列三种方式之一处理:① 用一个 `std::string` 字段存 `arrayToString(数组)` 的结果(`RdpRecTempTrack`/`RdpRecTrustTrack` 的 `X`/`P`/`X1`~`X3`/`P2`/`P3`/`u` 即此法,多维数组以 `;` 分隔内层、`,` 分隔元素);② 另建「元素展开表」(一行一个元素);③ 改用 blob 字段;
6. **实体需提供「源结构体 → 实体」的构造函数**(如 `RdpRecTrustTrack(Trust_Track& p)`):实体构造发生在写线程,源结构体 vector 已 move 进队列后才逐条转换;
7. 命名约定:字段名带单位后缀(`_ms` 毫秒、`_m` 米、`_deg` 度、`_mps` 米/秒、`_s` 秒),避免单位歧义;
8. **表结构变更不迁移旧表**:增删字段后不会修改已存在的表结构;由于每次运行生成新的 db 文件,正常调试流程天然规避该问题(若手工复用旧库,需自行建新表或删库)。
## 6. 新增记录点:四步配方
以「记录航迹号 + 高度」为例(现有 6 张表的记录点均按此模型实现):
**第 1 步 — 加记录实体**(`rdp_record_entity.h`「记录实体定义区」加结构体并紧跟 `RDP_RECORD_DECLARE`;再到 `rdp_record.cpp`「记录实体注册区」加 `YLT_REFL` + `RDP_RECORD_DEFINE`,字段顺序必须与结构体声明顺序一致):
```cpp
// rdp_record_entity.h
struct RdpRecTrackHeight
{
int64_t id; //自增主键(写入时固定填 0)
int Track_Index; //航迹号
double Height; //高度(m)
RdpRecTrackHeight() { id = 0; }
RdpRecTrackHeight(Trust_Track& p) : id(0), Track_Index(p.Track_Index), Height(p.Height) {}
};
RDP_RECORD_DECLARE(RdpRecTrackHeight);
// rdp_record.cpp 记录实体注册区
YLT_REFL(RdpRecTrackHeight, id, Track_Index, Height);
RDP_RECORD_DEFINE(RdpRecTrackHeight);
```
**第 2 步 — 确定源结构体并加异步入队接口/宏**(`rdp_record.h`):源结构体优先复用 `struct.h` 中已有的 `PointRecv`/`Temp_track`/`Trust_Track`/`Asso_Info`(头文件只做前置声明);接口参数为**按值** `std::vector<源结构体>`,宏内部 `std::move`:
```cpp
// rdp_record.h:namespace rdp_record 内追加
void WriteTrackHeights(std::vector<Trust_Track> v); //RdpRecTrackHeight
// 宏区(RDP_RECORD_ENABLE 开/关两套)
#ifdef RDP_RECORD_ENABLE
#define RDP_RECORD_TRACK_HEIGHTS(v) ::rdp_record::WriteTrackHeights(std::move(v))
#else
#define RDP_RECORD_TRACK_HEIGHTS(v) ((void)0)
#endif
```
**第 3 步 — 在 `rdp_record.cpp` 写线程侧增加批次/构造/提交分支**(新表需要独立的一条入队通道):
1. `BatchKind` 枚举加 `TrackHeight`;
2. `Batch` 结构体加 `std::vector<Trust_Track> track_heights;`;
3. `BatchRows`/`BatchBytes` 与 `PendingRows`/`Pending`/`ClearPending` 同步加上该成员(否则队列行数/字节统计失真);
4. `AppendBatch` 加 `case BatchKind::TrackHeight: AppendMove(p.track_heights, b.track_heights); break;`;
5. `FlushPending` 加 `ok &= FlushKind<RdpRecTrackHeight>(st, p.track_heights);`;
6. 在异步入队接口区实现 `WriteTrackHeights`(构造 `Batch`、设置 kind、`std::move` 后调用 `EnqueueBatch`,空 vector 直接返回)。
> 若只是复用现有 6 种源结构体之一,也不能跳过本步:一种入队通道对应一张表;要新增一张表就必须新增 `BatchKind` + `Write*` 接口 + flush 分支。
**第 4 步 — 业务侧收集后调用宏**(在雷达处理线程、数据仍有效时收集;需要语句/局部变量时整体用 `#ifdef` 包裹,保证关闭记录构建零代码):
```cpp
#ifdef RDP_RECORD_ENABLE
std::vector<Trust_Track> rec_heights;
rec_heights.reserve(trust_track->size());
for (size_t i = 0; i < trust_track->size(); ++i)
{
if ((*trust_track)[i].Track_Update_Flag == 1)
{
rec_heights.push_back((*trust_track)[i]);
}
}
RDP_RECORD_TRACK_HEIGHTS(rec_heights); //宏内部 std::move,调用后 rec_heights 为空
#endif
```
**无需建表**:首次写入该实体时自动建表;不同实体自动落到不同表。
> **自检点(忘记注册会链接失败)**:
> - 忘记 `RDP_RECORD_DEFINE`(或 `RDP_RECORD_DECLARE`)而使用了遗留同步宏 `RDP_RECORD(实体{...})`:链接期报 **LNK2019**(`rdp_record::Write<T>` 只有声明/显式实例化,`extern template` 阻止 C++17 调用方隐式实例化);
> - 忘记 `YLT_REFL`:`rdp_record.cpp` 编译期在 ormpp 反射处报错;
> - 新增表若忘了第 3 步的队列分支,编译期会因缺少对应 `Write*` 实现/`FlushKind` 而报错,或运行期该表永远没有数据。
> - 记录点必须在处理线程上、对**当前有效**的源结构体就地收集;不要保存指针/引用,也不要跨线程复用同一 vector。
## 7. 现有记录表与记录点
当前共 **6 个记录实体定义、6 张在用表**(`rdp_record_entity.h` 中 6 个结构体;历史示例 `RdpRecTrackOut` 的实体与记录点早已整体移除,仅在 `rdp_record.cpp` 注册区以注释保留两行备查)。12 个业务记录点全部改为「帧/圈内收集源结构体 → 一次入队」:
| # | 记录点 | 位置(文件:行) | 记录宏 | 表 | 说明 |
|---|---|---|---|---|---|
| ① | 输入点迹(TWS + TAS) | `data_process.cpp:75`(TWS 分支,判圈 return 之前)/ `data_process.cpp:153`(TAS 分支,`circle_id++` 之前) | `RDP_RECORD_POINTS` | `RdpRecPoint` | TWS/TAS **共用一张表**,用 `point_type` 区分;每个输入点迹一行,含 `point_id` / `frame_id` / `circle_id` 三个链路标识 |
| ② | TWS 凝聚后点迹 | `dot_coh.cpp:239`(`data_input_buff` 输出到 `point_recv` 的循环之后) | `RDP_RECORD_COHESION_TWS` | `RdpRecPointCohesionTWS` | 两帧滑窗凝聚后进入关联的点迹 |
| ③ | TAS 凝聚后点迹 | `dot_coh_tas.cpp:82`(输出到 `point_recv_tas` 的循环之后) | `RDP_RECORD_COHESION_TAS` | `RdpRecPointCohesionTAS` | 单帧凝聚后的 TAS 点迹 |
| ④ | 临时航迹 | `track_init.cpp:117`(TWS 圈内**转为可靠航迹之前**,清空点迹之后、`tmp_track_to_trust_track()` 之前,双层遍历 `temp_track` 收集) | `RDP_RECORD_TEMP_TRACKS` | `RdpRecTempTrack` | 每条临时航迹的每个点一行,含 `temp_track_id` / `point_id` / `circle_id`;记录时机于 e982804 前移,因此表中包含**本圈参与起始判定但可能随即起批转走**的临时航迹(原先在消亡之后记录会丢失这部分数据) |
| ⑤ | 可靠航迹 | TWS:`track_asso.cpp:174`(输出分支循环结束后);TAS:`track_asso_tas.cpp:134`(输出循环结束后) | `RDP_RECORD_TRUST_TRACKS` | `RdpRecTrustTrack` | 按实体字段定义落库内部 `Trust_Track`(含 IMM 的 `X`/`P`/`X1`~`X3`/`P2`/`P3`/`u`,数组经 `arrayToString()` 存为文本列)。**两通道共用一张表**,用 `Track_Mode`(TWS 0 / TAS 1)区分;均只记录**本圈有输出**的航迹,`track_init.cpp` 起批路径未埋此点。质量相关字段(`Quality` / `Unupdated_time` / `Point_sum` / `Valid_point_sum`)自 1ffb287 起已加入该实体,可直接 SQL 分析;注意 TAS 行不维护质量(值停留为起批初值),见 README 11.2 |
| ⑥ | 点航关联信息 | TWS:`track_asso.cpp:581`(未通过门限的候选)、`:743`(被最近邻取中的候选)、`:761`(因航迹/点迹已被消费而删表的候选),函数末尾 `:861` flush 剩余;TAS:`track_asso_tas.cpp:382`(未通过门限的候选)、`:524`(被取中候选及其余候选),函数末尾 `:574` flush 剩余 | `RDP_RECORD_ASSO_INFOS` | `RdpRecAssoInfo` | 每个候选一行(`d1`/`d2`/`d3`/`d_min`、`delta_T`/`delta_h`/`R`/`VT`、`thd_d`/`thd_h`/`thd_r`),以 `min_d_flag` 标记是否被最近邻取中、以 `tas_flag`(点迹 `point_type`,1 TAS / 0 TWS)区分来源通道,可用于复现关联决策。收集到 `RDP_RECORD_ASSO_CHUNK_ROWS`(默认 131072 行)即分块入队,限制单圈处理线程内存峰值 |
> 表清单即上表 ①②③④⑤⑥ 六张在用表。① 在判圈判定之前入队,因此即使本帧触发判圈/返回,输入点迹也不会漏记;②③④⑤ 在收集循环结束后一次入队;⑥ 按分块上限多次入队并在函数返回前 flush。
**链路串联**:①→②/③(点迹 → 凝聚后点迹)按 `point_id` 关联;②/③→④/⑤ 按 `point_id` 与 `circle_id` 关联;④→⑤ 通过起批时透传的 `track_id`(取自临时航迹末点的 `temp_track_id`)关联;⑤→⑥ 按 `track_id` 与 `point_id` 关联。TWS 与 TAS 可先用 `RdpRecPoint.point_type`、`RdpRecTrustTrack.Track_Mode`、`RdpRecAssoInfo.tas_flag` 三处之一区分通道。`circle_id` 在 TWS 每判满一圈(由配置项 `scan_mode` 选择判据:0 相扫为波位号不再增大、1 机扫为波位号 0,见 README 7.2)、TAS 每帧处理结束时自增,可用来划分「第几圈/第几帧」。
**字段速查**(字段顺序即建表列顺序;首列均为 `int64_t id` 自增主键):
- `RdpRecPoint` / `RdpRecPointCohesionTWS` / `RdpRecPointCohesionTAS`(三者字段一致):`point_id`、`frame_id`、`circle_id`、`range`、`azimuth`(弧度)、`velocity`、`amplitude`、`height`、`SNR`、`RCS`、`encoder_value`、`amp_asv_point`、`prn_fftn_point`、`CPI_time`、`GNSS_time`、`use_flag`、`azimuth_beam_index`、`pitch_beam_index`、`PRF_index`、`freq_index`、`point_type`、`point_section_asso`;
- `RdpRecTempTrack`:`temp_track_id`、`point_id`、`circle_id`、`X`、`P`(文本)、`r`、`azi`、`vr`、`height`、`SNR`、`RCS`、`amplitude`、`quality`(航迹质量,0~1,1ffb287 新增)、`CPI_time`、`GNSS_time`、`d`、`buff_round`、`temp_track_section_idx`、`asso_flag`、`pitch_num`;
- `RdpRecTrustTrack`:`track_id`、`point_id`、`circle_id`、`Track_Index`、`Range`、`Azimuth`、`Elevation`、`Velocity`、`Amplitude`、`Height`、`Quality`(航迹质量,0~1,1ffb287 新增)、`T_track`、`GNSS_time`、`Track_Sum`、`Unupdated_time`(未更新累计毫秒,1ffb287 新增)、`Point_Index`、`Point_sum`、`Valid_point_sum`(两项均为 1ffb287 新增)、`Track_Mode`、`Track_Update_Flag`、`Extrapolate_round`、`point_flag`、`approach_flag`、`manual_delete_flag`、`manual_tracking_flag`、`associate_point_number`、`primary_target_flag`、`Target_Type`、`Track_section_idx`、`range_point`、`azi_point`、`elev_point`、`vr_point`、`prf_point`、`point_type`、`snr_point`、`amp_asv_point`、`prn_fftn_point`、`RCS`、`pitch_num`、`X`、`P`、`X1`、`X2`、`X3`、`P2`、`P3`、`u`(后 8 项为文本);
- `RdpRecAssoInfo`:`point_id`、`track_id`、`circle_id`、`d1`、`d2`、`d3`、`d_min`、`point_index`、`track_index`、`delta_T`、`delta_h`、`R`、`VT`、`tas_flag`、`thd_d`、`thd_h`、`thd_r`、`min_d_flag`。
各表的字段为**调试用途**,按第 6 节增删即可,不影响框架。注意记录点中的字段错误只在启用记录功能的构建中才会被检查(关闭时宏丢弃实参)。
**四点使用提醒**:
- `RdpRecTrustTrack::Track_Update_Flag` **恒为 0**(TWS 与 TAS 两处记录点相同):写库发生在输出分支内,而该分支入口已先把标志清零(`track_asso.cpp` / `track_asso_tas.cpp` 均先置 0、再输出、最后收集)。判断「某航迹本圈是否更新」应改用该表是否出现对应 `track_id` / `circle_id` 的记录,或改用 `RdpRecAssoInfo::min_d_flag`。
- `Range` / `Azimuth` / `Elevation` / `Velocity` 四个内部快照**只在航迹当圈有输出时被回填**(两通道均在收集前回填),长期未输出(外推超限)的航迹在表中为 0 或旧值。
- **TAS 关联信息的三个字段为占位值**:`track_asso_tas.cpp` 收集的候选把 `R` / `VT` 置 0(TAS 关联只用统计距离与高度两个门限),并把 `thd_r` 恒置 true;`track_index` 取 TAS 目标批号 `tas_track_idx`(不是航迹表下标,TWS 行才是下标)。分析 TAS 行时不要按 TWS 的语义解读这三项。
- **记录表未含航迹指纹(V1.5.9)**:`Trust_Track::Track_fingerprint`(16 bit,TAS 输入/输出用它定位航迹)**尚未加入** `RdpRecTrustTrack` 实体;如需按指纹复盘 TAS 命令,按第 6 节配方给实体加字段并在 `rdp_record.cpp` 注册即可(同 `Quality` 的加法)。
## 8. 查询与分析
```python
# 取最近一次运行的记录库并查看表结构、行数与抽样数据(Python 自带 sqlite3 即可)
import glob, os, sqlite3
path = max(glob.glob(r'rdp_records\rdp_record_*.db'), key=os.path.getmtime)
con = sqlite3.connect(path)
print(path)
print(con.execute("select name from sqlite_master where type='table'").fetchall())
print(con.execute("pragma table_info(RdpRecPoint)").fetchall())
print(con.execute("select count(*) from RdpRecPoint").fetchone())
print(con.execute("select * from RdpRecPoint order by id desc limit 3").fetchall())
print(con.execute("pragma integrity_check").fetchone())
# 按点迹链路串联:输入点迹 -> 凝聚后点迹 -> 临时航迹
print(con.execute("""
select p.circle_id, p.point_id, p.range, c.range, t.temp_track_id, t.buff_round
from RdpRecPoint p
left join RdpRecPointCohesionTWS c on c.point_id = p.point_id
left join RdpRecTempTrack t on t.point_id = p.point_id
order by p.id desc limit 10
""").fetchall())
# 查看某圈的关联决策(min_d_flag=1 为最终取中的候选;tas_flag 区分通道)
print(con.execute("""
select track_id, point_id, d_min, delta_T, tas_flag, thd_d, thd_h, thd_r, min_d_flag
from RdpRecAssoInfo where circle_id = (select max(circle_id) from RdpRecAssoInfo)
order by tas_flag, min_d_flag desc, d_min asc limit 20
""").fetchall())
# TAS 航迹与 TWS 航迹共表,用 Track_Mode 区分(0 TWS / 1 TAS)
print(con.execute("""
select track_id, circle_id, Track_Index, Track_Mode, Range, Velocity, Extrapolate_round
from RdpRecTrustTrack where Track_Mode = 1 order by id desc limit 10
""").fetchall())
```
也可用 DB Browser for SQLite 直接打开 db 文件;同一 db 内所有表一一对应各记录实体。
查询时的三点注意:
- **`id` 是 SQLite 的主键/ROWID 自增列,单调但不保证连续**(批量插入与失败回滚都会产生空洞),排序用 `id` 没问题,但不要用它推断「第几行被记录」;
- **跨表不能按 `id` 判断先后**:各表有独立的 id 序列,且写线程按表分别提交,跨表写入顺序不保证;跨表关联请用 `point_id` / `track_id` / `circle_id`;
- 正常 `Destroy()` 后 WAL 已 `checkpoint(TRUNCATE)`,目录下通常只剩一个 `.db`;若进程异常退出,目录中可能残留 `-wal`/`-shm` 文件,需用 SQLite(而不是直接读文件)打开查看,尾部记录可能缺失。
## 9. 注意事项与已知限制
- **交付构建不要开启**:`Release` + `RDP_DATA_RECORD=ON` 仅用于临时排查,会带来 sqlite3 编译与运行时写入开销;正式交付保持 `AUTO`/`OFF`;
- **Release 下记录点不做类型检查**:关闭时宏丢弃实参,因此记录点里的字段拼写/类型错误只在启用记录功能的构建中才会暴露——新增记录点后请至少构建一次开启记录功能的配置;
- **异步尾部可能丢失**:记录是异步落库的,进程崩溃、被强杀或**未调用 `Destroy()`** 时,队列中尚未写库的数据与 WAL 尾部都可能丢失;正式采集结束时务必调用 `Data_Process_Factory::Destroy()` 让 `Shutdown()` 排空;
- **队列满会整批丢弃**:写线程跟不上(慢盘、杀软实时扫描、CPU 争抢)时,超出 `RDP_RECORD_QUEUE_CAP_BYTES`/`RDP_RECORD_QUEUE_MAX_BATCHES` 的**整批**数据被丢弃,`stats().dropped_rows` 可查,日志按每 1024 行限频提示;这是「实时性优先」的刻意取舍,不是缺陷;
- **记录期间关闭记录功能**:`RDP_RECORD_ENABLE` 是编译期开关,运行时无法关闭;若宿主需要停止记录,请调用 `Destroy()`(排空并关库)后不再 `GetB()`,`Shutdown()` 之后的写入静默忽略;
- **⑥ 未裁剪,仍是 M×N**:关联信息按每个候选一行记录;200 目标/150 点压测场景单圈约 12.3 万行,判圈帧 p99 ≈ 13~24 ms(超 5 ms 验收阈值),受控 50 目标/50 点场景最大 5.96 ms。若对判圈帧实时性有硬要求,需后续实现 P0-a(只记录被取中候选/按需裁剪),本轮未做;
- **WAL 需要本地盘**:WAL 依赖共享内存与文件锁,网络盘/不支持的文件系统上可能设置失败(代码会回退默认 journal 并提示);记录库本身也可能很大(60 圈约 1.06 GB),采集时需预留磁盘;
- **写线程是后台优先级**:Windows 下写线程 CPU 优先级为 BELOW_NORMAL,避免与雷达处理线程争抢;磁盘慢时记录先丢批而不是拖慢处理;
- `NaN`/`Inf` 会由 SQLite 存为 `NULL`(`sqlite3_bind_double` 行为),分析时需注意;
- **不保证跨表顺序、`id` 不连续**:见第 8 节;
- **`RDP_RECORD`/`RDP_RECORD_BATCH` 会阻塞**:这两个遗留宏在调用线程内同步写库(与写线程经 `db_mtx` 串行化),不要在实时性敏感的路径上使用;
- IntelliSense 可能对 `rdp_record.cpp` 按 `c_cpp_properties.json` 的 C++17 提示(该文件实际按 C++20 编译,`build/<...>/compile_commands.json` 中带有 `/std:c++20`),以实际构建结果为准;
- `third_party/ormpp/` 为上游原样内置(ormpp 0.2.0 / iguana 1.0.5 / SQLite 3.49.1,Apache-2.0 与公有领域),升级方式与校验值见该目录 `UPSTREAM.md`。
## 10. 验证与性能参考(2026-10-20 单机实测)
> 以下为优化实施后的实测结果,原始日志与可重复脚本位于 `build/analysis/verify/`(被 `.gitignore` 忽略,
> 不随交付;脚本用法见该目录 README.md)。数据随机器、磁盘与杀软环境变化,仅作量级参考。
| 项目 | 结果 |
|---|---|
| 构建 | 8 种配置(PS/MS × Release/Debug × REC_OFF/REC_ON,机扫/相扫合并前;合并后为 4 种配置)均 **0 warning、0 error** |
| 普通帧开销 | dup 场景(6 圈/200 目标/每帧 150 点/100 波位)REC_ON vs REC_OFF:每帧 `total` 新增 **p99 ≈ 0.09~0.11 ms**(验收阈值 ≤ 1 ms,PASS) |
| 判圈帧开销 | 同场景判圈帧 `track` 新增 **中位 ≈ 2.9~4.1 ms、p99 ≈ 13~24 ms**(多次验收运行)——已超 5 ms 验收阈值,因未采用 P0-a(单圈约 12.3 万关联候选),属压测上限;受控 50 目标/50 点场景判圈帧新增中位约 3 ms、**最大 5.96 ms** |
| 端到端耗时 | 同场景 600 帧:ON wall ≈ 2.1 s vs OFF wall ≈ 1.8 s |
| 行数一致性 | 与改动前完全一致:`RdpRecPoint` 90,000 / `RdpRecPointCohesionTWS` 4,170 / `RdpRecAssoInfo` 211,584 / `RdpRecTempTrack` 8,330 / `RdpRecTrustTrack` 304(合计 **314,388**) |
| 库健康度 | `PRAGMA integrity_check = ok`;DDL 无 `AUTOINCREMENT`;`journal_mode = wal`;5 张表 `id` 严格递增;`dropped_rows = 0` |
| 60 圈长跑 | 约 43.5 s、**9,987,370 行**、`dropped_rows=0`、`write_errors=0`、队列峰值 ≈ 19.8 MB(上限 64 MB)、DB 约 **1.06 GB** |
| 故障注入 | 记录目录不可建(同名普通文件占位):`Init()` 静默失败、驱动正常完成(exit=0、无崩溃、未产生 db);小队列测试构建(cap=1 KB)丢弃 314,388 行且 `dropped_rows` 有计数、处理正常 |
| 重连 | `Destroy()` → `GetB()` 生成两个独立 db 且均正常写入(同秒文件名冲突自动追加后缀) |
> 注:上表实测于 2026-10-20(性能优化验收),早于 1ffb287 增加航迹质量字段;新增列不改变行数,但当时的表结构不含 `quality` / `Quality` / `Unupdated_time` / `Point_sum` / `Valid_point_sum`,如需按这些列验收请重新跑一轮记录。
+741
View File
@@ -0,0 +1,741 @@
# 数据记录调试功能(RDP_DATA_RECORD)性能瓶颈分析与实时性优化报告
> **项目**:雷达数据处理动态库 `data_process_class_dll`(V1.5.8.PS / V1.5.8.MS,分支 `feature`,提交 `3dee734`)——本报告为**机扫/相扫合并前**的快照;合并后版本串为 `V1.5.9.GENERIC`,构建目录与产物路径见 README 4.3。
> **问题**:Release 构建下,`RDP_DATA_RECORD=ON` 开启数据记录调试功能后,数据处理不能实时完成;`RDP_DATA_RECORD=OFF` 时满足实时性。
> **本报告范围**:定位性能瓶颈、给出可落地的优化措施与验收标准。**本阶段不修改任何业务代码**——报告中的代码改动均为"建议方案",未实施(业务代码/CMake/第三方均无改动,仅新增本报告文件)。
> **方法**:源码走读 + 数据量建模 + Release 产物微基准 + `REC_ON`/`REC_OFF` 双 DLL 端到端 A/B 实测。
> **证据产物**(均为本报告的一次性分析产物,位于被 `.gitignore` 忽略的 `build/` 下):
> - 记录点与行数模型:`build/analysis/hotpath_findings.md`
> - ormpp/SQLite 单条写入链路:`build/analysis/ormpp_sqlite_findings.md`
> - 微基准:`build/analysis/bench_micro.md`、`build/perf_bench_micro/`
> - 端到端 A/B:`build/analysis/bench_pipeline.md`、`build/perf_bench_pipeline/`
> - 优化实施与验收结果:本报告第 11 节;验收脚本与日志:`build/analysis/verify/`
> - 本报告:`docs/data_record_perf_report.md`
---
## 0. 结论摘要(TL;DR)
1. **根因不是"Release 没优化好",而是记录路径的设计是"在雷达处理线程上逐条同步写 SQLite"**:12 个记录点全部是就地构造实体 + 单条 `Write()`,每条记录 = 一次**独立隐式事务**(创建/写/删除回滚日志 + 文件锁 + 脏页写回)。CPU 编译优化对文件系统/锁/事务这些开销无效,所以 Release 下依旧卡。
2. **实测单行写入成本 ≈1.1~1.3 ms(mean)、p50≈0.73~0.78 ms、p99≈5~6.3 ms**(本机:i7-10700K + NTFS 机械盘数据卷 + Defender 实时防护开启;3 次独立运行中位数,单行最大尖峰 81~496 ms)。**其中 rollback journal 文件的创建/写入/删除约占 96%~97%**:同一事务路径下把 journal 换成内存(MEMORY)或 WAL 后每行降到 38.7 µs / 17.2 µs(原生文件操作对照:空文件 create+close+delete ≈0.15~0.18 ms、写 4 KB 后删除 ≈0.29~0.31 ms)。`synchronous=OFF` 只去掉 fsync,不改变"每行一个事务"。
3. **端到端 A/B 实测**(同一合成场景,600 帧 × 150 点/帧):
- 仅输入点迹记录路径(未起批):`REC_OFF` 每帧合计 **0.21 ms**,`REC_ON` 每帧 **150.1 ms**(≈722 倍);每圈 **0.14 s → 16.8 s**(≈120 倍);仅"每帧 150 行输入点迹"就贡献约 144 ms/帧,**单帧即超 10 ms 实时预算 14 倍**。
- 含航迹场景(186 条可靠航迹,关联路径真实覆盖):`REC_OFF` 每帧 **0.269 ms** → `REC_ON` **218.03 ms(811 倍)**;判圈帧 `track_process` **318 ms → 59.2 s(max 170.3 s,227 倍)**;每圈 **0.345 s → 81.05 s(235 倍)**;关联候选记录 8.7 万~12.3 万行/圈、占判圈帧 99%,**这是现场"不能实时"的直接原因**。
4. **比"逐条事务"更危险的是数据量本身**:TWS 点航关联信息按**每圈 `M×N` 行**记录(`M`=可靠航迹数、`N`=本圈关联批凝聚点数,`N≈99×每帧凝聚点数`)。稀疏场景约 1.8 万行/圈,中等场景约 44.6 万行/圈,稠密场景可达 **5.4×10⁶ 行/圈(约 0.5 GB/圈)**——即使写库成本降到零,磁盘吞吐也支撑不了,**必须对关联候选做裁剪/采样**。
5. **第三瓶颈是处理线程上的实体构造**:`RdpRecTrustTrack` 每行要执行 **135 次 `std::ostringstream`(实测 133.6 µs/行、≈1.1 KB 文本)**,`RdpRecTempTrack` 每行 20 次(19.4 µs/行)。批处理只省事务,**省不掉这些构造**(批量后构造占 TrustTrack 端到端成本 94%、TempTrack 81%);必须把构造搬到后台线程或改列类型。
6. **优化收益已实测**:改为批量事务(框架已有 `RDP_RECORD_BATCH`/`Write(vector)` 能力,当前调用点为 0)后,DB 侧每行从 ~1.1 ms 降到 **3.8~8.8 µs(批 1000,按实体;约 95~336 倍)**;端到端(含构造)Point/Asso ≈207~335×、TempTrack ≈52×、TrustTrack ≈10×。WAL+`synchronous=OFF` 可把单条 autocommit 从 1120 µs 降到 17.2 µs(~55~65 倍,但仍远不够高行数场景);每帧小批量(≤150 行)时 WAL 还有 1.8× 价值(批 100:DELETE 9.83 µs/行 → WAL 5.53 µs/行);**不要用 `synchronous=NORMAL`**(本机机械盘 fsync ≈31 ms,checkpoint 抖动使 NORMAL 相对 `sync=OFF` 慢 8~12×)。
7. **推荐方案(分层)**:
- **P0-a 关联信息裁剪(决定性)**:点航关联只记录"被最近邻取中的候选 + 少量近失候选/采样",把 ⑥ 从 `M×N` 降到 `O(M+N)`;
- **P0-b 全量批处理**:①~⑤、TAS ⑥ 改为"帧末/圈末收集 → 一批一个事务",直接复用现有批量接口;
- **P0-c 异步写线程(实时性保证)**:处理线程只把 POD 快照压入有界队列,后台线程负责序列化 + 批量入库,队列满时丢弃并计数——把磁盘抖动与处理线程彻底解耦;
- **P1 补充**:信任航迹数组列改 blob/后台序列化、临时航迹只记增量、WAL/`synchronous`/`cache_size`/去 `AUTOINCREMENT`、失败熔断、记录库轮转;环境上排除 Defender 扫描目录、避免机械盘。
- 目标:开启记录功能后,**处理线程每帧新增耗时 p99 ≤ 1 ms、判圈帧 ≤ 5 ms、零丢帧**;交付构建仍保持 `OFF` 零开销。
8. **交付构建不受影响**:`RDP_DATA_RECORD=OFF` 时记录模块不参与编译、宏为空操作(`rdp_record.h:44-45`、`CMakeLists.txt:118-158`),DLL 体积 182,784 → 1,414,656 字节的差异只存在于 `ON` 构建。报告中的改动全部位于 `#ifdef RDP_RECORD_ENABLE` 保护的调试代码与记录模块内部,**不进入交付产物**。
---
## 1. 问题与背景
### 1.1 现象
| 配置(均为 Release;使用当时的 X256_PS 构建,现机扫/相扫已合并,等价产物在 `build/Release/REC_*`) | 现象 |
|---|---|
| `RDP_DATA_RECORD=OFF`(交付/AUTO 默认) | 数据处理满足实时性 |
| `RDP_DATA_RECORD=ON`(显式开启,用于 Release 现场排障) | 数据处理不能实时完成,且负载越密(点迹/航迹越多)越慢,最终表现为处理节拍被拖垮、数据积压 |
关键点:两套构建的**雷达处理算法源码完全相同**,差异只有 `RDP_RECORD_ENABLE` 宏是否定义、以及 `rdp_record.lib`(含 SQLite)是否链接进 DLL。因此问题一定出在"记录路径的运行期行为",而不是算法或编译优化级别。
### 1.2 数据记录功能与开关(为什么 OFF 能实时)
- 记录实现:`data_process_class_dll/rdp_record.h`、`rdp_record.cpp`、`rdp_record_entity.h`;
- 构建开关:CMake 缓存变量 `RDP_DATA_RECORD`(`AUTO`/`ON`/`OFF`,`CMakeLists.txt:34-57`),启用时定义 `RDP_RECORD_ENABLE` 并编译 `rdp_record` 静态库(`CMakeLists.txt:121-157`);
- 关闭时:`RDP_RECORD(...)` / `RDP_RECORD_BATCH(...)` 展开为 `((void)0)`(实参不求值,`rdp_record.h:42-45`),记录模块源码完全不参与编译(`CMakeLists.txt:118-158` 的 `if(RDP_RECORD_ENABLED)` 才创建/链接 `rdp_record` 目标)。**OFF 的实时性来自"代码不存在",不是"运行期判断跳过"**。
- 运行期:`Data_Process_Factory::GetB()` → `rdp_record::Init()` 建目录/开库;`Destroy()` → `Shutdown()`(`data_process_class_dll.cpp:18-51`);单连接 + 一把全局互斥锁(`rdp_record.cpp:35-44,183`);写入失败静默降级(`rdp_record.cpp:188-195`)。
- 功能定位与已知限制(`docs/data_record_debug.md:15,57,197-204`):**不做异步、不做数据裁剪、不追求多线程写入性能**,默认只在 Debug 启用。这正是本次问题的设计层根源——该功能按"调试期低频使用"设计,而现场需要在 Release 下开启它排障。
### 1.3 本报告的复现方式
由于没有宿主主程序,本次用两条独立证据链复现与量化:
1. **微基准**(`build/perf_bench_micro/`):直接链接已有 Release `REC_ON` 产物 `build/Release/REC_ON/lib/rdp_record.lib`(本报告测量时位于旧布局 `build/X256_PS/Release/REC_ON/lib/`;机扫/相扫合并后为 `build/Release/REC_ON/lib/`),调用与业务完全相同的 `rdp_record::Write<T>()`,测单条/批量/不同 SQLite 配置的每行成本;并单独测实体构造(`arrayToString`)成本。
2. **端到端 A/B**(`build/perf_bench_pipeline/`):编写只使用公开 DLL 接口的合成数据驱动程序,分别加载 `REC_OFF` 与 `REC_ON` 两个 `data_process_class_dll.dll`,按宿主调用序列(`data_preprocess` → 按返回的 `model` 调 `track_process`)投喂同一份合成 TWS 数据,用 `QueryPerformanceCounter` 逐帧计时。
> 证据链互相印证:微基准单条写入 mean 约 1.1~1.3 ms/行(p50 约 0.73~0.78 ms);首轮端到端 A/B 全运行 103,374 行 / ≈97.6 s 增量 ≈ **0.94 ms/行**;含航迹最终 A/B 为 314,388 行 / 426.7 s ≈ **1.39 ms/行**(大库/多表/页缓存压力下略高),三条口径一致。
---
## 2. 分析方法、环境与证据强度
### 2.1 方法总览
| 方法 | 内容 | 产物 |
|---|---|---|
| 源码走读 | 12 个 `RDP_RECORD` 记录点的循环嵌套、触发频率、行数公式;ormpp/SQLite 单条写入全链路(file:line) | `hotpath_findings.md`、`ormpp_sqlite_findings.md` |
| 数据量建模 | 以 `M`(航迹数)、`N`(关联批点数)、`p`(每帧点数)、`B`(每圈波位数)构造每圈行数公式与场景表 | 本报告第 5 节 |
| 微基准 | 单条 vs 批量写入、SQLite 事务/journal/WAL/语句复用/AUTOINCREMENT 对比、实体构造与 `arrayToString` 成本 | `bench_micro.md` |
| 端到端 A/B | `REC_ON` vs `REC_OFF` 双 DLL,同机同场景逐帧计时(含起批成功、关联路径覆盖的场景) | `bench_pipeline.md` |
| 记录库校验 | 对 A/B 产生的 db 做行数/体积/`integrity_check` 统计 | `build/analysis/count_db_rows.py` |
### 2.2 测试环境
| 项 | 值 |
|---|---|
| 主机 | Intel Core i7-10700K(8C16T,3.8 GHz),31.8 GB 内存 |
| OS | Windows 10 专业版 19045(x64) |
| 编译器/构建 | MSVC 2022 14.44(v143),CMake `Release`(`/O2 /Ob2 /DNDEBUG /MD`),NMake 单配置 |
| 被测 DLL | `REC_OFF` 182,784 B / `REC_ON` 1,414,656 B(均已有 Release 产物,未重建主工程) |
| 磁盘 | 工程与记录库位于 E:(WDC WD20EZAZ,**SATA 机械盘**);系统盘为 SATA SSD |
| 安全软件 | Windows Defender:`RealTimeProtectionEnabled=True`、`OnAccessProtectionEnabled=True` |
| SQLite | 3.49.1 amalgamation,默认编译选项(`SQLITE_THREADSAFE=1`、`cache_size=-2000`、`page_size=4096`、无额外 `SQLITE_*` 宏) |
| 计时 | `QueryPerformanceCounter`(10 MHz);进程绑核 + `HIGH_PRIORITY_CLASS`;预热 300 ms;首圈作为预热不计入统计 |
### 2.3 证据强度与局限(先声明,避免误读)
- **强证据**:单行/批量每行成本、SQLite 各配置对比、实体构造成本、DLL 体积、代码位置与行数公式。
- **中等证据**:端到端 A/B 的绝对毫秒数。合成场景的"每帧 150 点、100 波位/圈、200 目标"是**压力场景**,绝对耗时与真实雷达负载不同;但 **ON/OFF 的比值、每行成本、`M×N` 的行数规律**可迁移。A/B 已覆盖"未起批(仅点迹路径)"与"186 航迹(关联路径)"两种形态;只差"150 个独立点迹不经凝聚"的稠密形态(按 1.39 ms/行外推,未实跑)。
- **不在本次证据内**:真实宿主的多线程调用行为(README 的已知问题 BUG-03 声明"接口线程模型未定义,宿主应单线程调用",`README.md:1020`);现场磁盘/Defender 的真实配置;真实场景的 `M`、`N`、凝聚存活率;TAS 路径与 `Beam_Ctrl` 未做端到端实测(记录点同构,每行成本结论适用)。
- 因此本报告的优化措施同时给出"**本机实测收益**"与"**需现场复测校准**"两类数据;第 9.3 节给出复测方法。
---
## 3. 记录链路全貌:12 个同步单条写入点
### 3.1 调用链
```
宿主(单线程,按 README BUG-03 约定)
└─ data_preprocess(DataRev[150]) // 每帧
└─ 逐点循环 → RDP_RECORD(RdpRecPoint(...)) // data_process.cpp:60 / :132
└─ track_process(..., model) // 每帧或每圈
├─ model==2: dot_coh_process_buff()
│ └─ RDP_RECORD(RdpRecPointCohesionTWS) // dot_coh.cpp:230
├─ model==1: track_asso_process()
│ ├─ model_filter() 双层循环(航迹×点迹)
│ │ ├─ 未过门限: RDP_RECORD(RdpRecAssoInfo) // track_asso.cpp:554
│ │ └─ 最近邻消费: RDP_RECORD(RdpRecAssoInfo) // :710 / :722
│ ├─ 输出循环: RDP_RECORD(RdpRecTrustTrack) // :163
│ └─ track_init_process_logic()
│ └─ 临时航迹双层遍历: RDP_RECORD(RdpRecTempTrack) // track_init.cpp:114
└─ model==3(TAS): dot_coh_tas → track_asso_tas
└─ RDP_RECORD(...) // dot_coh_tas.cpp:73, track_asso_tas.cpp:122/363/499
RDP_RECORD 展开(ON 时):
构造实体(含 arrayToString)→ ::rdp_record::Write(const T&)
→ 全局 std::mutex 加锁
→ ormpp 单条 insert:重拼 INSERT SQL → sqlite3_prepare_v2 → 逐列 bind → sqlite3_step(隐式事务提交)→ finalize
→ 解锁,返回雷达处理线程
```
**关键事实:整条链路没有队列、没有线程边界、没有事务聚合**。每一条记录都同步占用雷达处理线程,雷达处理必须等磁盘事务完成才能继续(`data_process.cpp:18/155` 是同步接口)。
### 3.2 记录点清单(6 张在用表 / 12 个调用点 / 0 个批量调用)
| 表 | 埋点 | 所在循环 | 触发频率 | 每圈/每帧行数 |
|---|---|---|---|---|
| `RdpRecPoint`(①输入点迹) | `data_process.cpp:60`(TWS)、`:132`(TAS) | `data_preprocess` 逐点循环 | 每帧 | TWS `B·p`(≤15,000/圈);TAS ≤150/帧 |
| `RdpRecPointCohesionTWS`(②) | `dot_coh.cpp:230` | 凝聚输出循环 | 每圈 `B−1` 次(model=2) | ≈`N` 行/圈 |
| `RdpRecPointCohesionTAS`(③) | `dot_coh_tas.cpp:73` | TAS 凝聚输出循环 | 每 TAS 帧 | ≤150/帧 |
| `RdpRecTempTrack`(④) | `track_init.cpp:114` | 临时航迹双层遍历 | 每圈 1 次(model=1) | `ΣL`(全量快照,≈1~3×未关联点数) |
| `RdpRecTrustTrack`(⑤) | `track_asso.cpp:163`(TWS)、`track_asso_tas.cpp:122`(TAS) | 输出循环(`Track_Update_Flag==1`) | 每圈/每 TAS 帧 | ≤`M`≤500/圈;TAS ≤1/帧 |
| `RdpRecAssoInfo`(⑥) | `track_asso.cpp:554/710/722`、`track_asso_tas.cpp:363/499` | 关联候选双层循环(航迹×点迹) | 每圈 1 次(model=1) | **`M×N` 行/圈(精确等于候选对数)**;TAS ≤150/帧 |
> 全仓库 `RDP_RECORD_BATCH` 调用点为 **0**(`grep` 验证;宏与批量 `Write(vector)` 早已实现但从未被业务使用,`rdp_record.h:41`、`rdp_record.cpp:198-225`)。
### 3.3 频率与时间窗(决定了行数量级)
- TWS:每帧一次 `data_preprocess`;一圈 `B` 个波位(`MAX_BEAM_NUM=100`,`parameters.h:87/138`;实际圈长由宿主波位编排决定);每圈 **1 次 model=1**(关联/消亡/起始)+ **B−1 次 model=2**(滑窗凝聚)。
- **TWS 的关联批是一整圈的累积**:`point_recv` 在整圈内只增不清(`dot_coh.cpp:229` → `track_asso.cpp:29-68` → `track_init.cpp:60/108`),model=1 时关联的是"上一圈全部凝聚点",因此 `N ≈ (B−1)×每帧凝聚点数`,而不是单帧的 ≤150 点。这是行数量级被低估的常见原因。
- 注:`README.md:445` 的流程图把 TWS 凝聚画在 model=1 分支下,与代码不符;代码中凝聚只在 model=2 调用(`data_process.cpp:163-168`)。
- TAS:每来一帧处理一帧,`point_recv_tas` 每帧清空,关联仅针对单一批号航迹(`track_asso_tas.cpp:263-283`),记录量比 TWS 小 1~2 个数量级。
- 实时预算(本报告采用的参考值,需现场按实际 CPI 校准):帧周期 **10 ms**(100 Hz CPI)、圈周期 **1 s**(100 波位 × 10 ms)。记录功能新增的耗时直接叠加在这两个预算上。
---
## 4. 瓶颈一:每条记录一次同步事务(I/O 放大)
### 4.1 单行写入链路拆解(代码事实)
单条 `rdp_record::Write(const T&)`(`rdp_record.cpp:174-196`)每次调用都完整执行:
```
状态检查 → std::lock_guard<mutex>(全模块一把锁)
→ PrepareWrite<T>(静态 guard,建表只一次)
→ ormpp::dbng::insert → sqlite.hpp:834-856 insert_or_update_impl:
generate_insert_sql<T>() // 每次重新拼 SQL 字符串(utility.hpp:438-521)
sqlite3_prepare_v2() // 每次重新解析/编译(sqlite.hpp:842)
bind 逐列(22/22/19/44/18 次,id 自增列跳过) // sqlite.hpp:602-608, 645-696
sqlite3_step() // ← 隐式事务提交发生在这里
sqlite3_finalize() // 释放语句对象
→ 失败时每个实体仅提示一次
```
单条路径**没有任何 `BEGIN/COMMIT`**,因此每条 INSERT 处于 SQLite autocommit:**每条记录 = 一个独立隐式写事务**,在 Windows 上包含:
| 阶段 | 动作 |
|---|---|
| 加锁 | 获取 SHARED/PENDING/RESERVED/EXCLUSIVE 锁(`LockFileEx`) |
| 建 journal | 创建 rollback journal 文件(`CreateFileW`)+ 写 journal 头 |
| 写 journal | 修改页的改前镜像写入 journal |
| 写主库 | 提交时脏页写回 db 文件(表页 + `page1` + `AUTOINCREMENT` 的 `sqlite_sequence` 页) |
| 删 journal | 关闭并删除 journal 文件(`DeleteFileW`)+ 解锁 |
每次约 **10~15 个 Win32 文件/锁系统调用 + 20~40 KB 页写入**。`PRAGMA synchronous=OFF`(`rdp_record.cpp:147`)只去掉 `FlushFileBuffers`,**不减少 journal 文件的创建/写入/删除,也不减少锁**;在机械盘 + Defender 实时防护下,这条"新建文件→写入→关闭→删除"链被显著放大(SQLite 自身的 Windows VFS 都为杀软影响加了锁重试,`sqlite3.c:50289-50308`)。
### 4.2 实测:单行成本 ≈1.1~1.3 ms(mean)
微基准(Release `REC_ON` 库,本机环境,每实体按当前实现逐条写入;**3 次独立运行的中位数**,min~max 见 `bench_micro.md` §2):
| 实体(列数含 id) | 统计行数 | mean | p50 | p99 | 吞吐 | DB 体积/行 |
|---|---|---|---|---|---|---|
| `RdpRecPoint`(23) | 16,000 | **1265.5 µs** | 780.7 µs | 5643.2 µs | 790 行/s | 119.6 B |
| `RdpRecPointCohesionTWS`(23) | 16,000 | 1249.9 µs | 770.7 µs | 5073.4 µs | 800 行/s | 119.2 B |
| `RdpRecTempTrack`(20) | 16,000 | 1224.2 µs | 745.6 µs | 6027.5 µs | 817 行/s | 315.8 B |
| `RdpRecTrustTrack`(45) | 4,000 | 1326.5 µs | 761.5 µs | 6271.0 µs | 754 行/s | 1369.7 B |
| `RdpRecAssoInfo`(19) | 16,000 | 1228.7 µs | 734.8 µs | 5703.7 µs | 814 行/s | 96.5 B |
> 观察:**列数从 19 到 45、行大小从 96.5 B 到 1.37 KB,单条 p50 差异 <7%**——成本由"每条一个事务"决定,而不是 SQL/列数/序列化。mean 约为 p50 的 1.6 倍、p99 达 5~6.3 ms、单行最大尖峰 81~496 ms,说明文件系统/杀软/调度长尾直接落在同步调用栈上,这正是"偶发卡顿"的来源。
### 4.3 实测:批量事务的收益(框架已有能力,端到端 10~335×)
同一个 `rdp_record.lib`、同一数据库配置,改用 `Write(const std::vector<T>&)`(ormpp 内部一次 prepare + `BEGIN` + 逐行 bind/step/reset + `COMMIT`,`sqlite.hpp:859-902`;**若把批量实现成循环调单条 `Write` 则完全失去收益**):
| 实体(列数) | 批大小 | DB 侧每行成本 | 吞吐 | 相对单条 |
|---|---|---|---|---|
| `RdpRecPoint`(23) | 100 | 13.31 µs | 75,121 行/s | ~80~95× |
| `RdpRecPoint` | 1,000 | **3.77 µs** | 265,230 行/s | **275~336×** |
| `RdpRecPoint` | 10,000 | 2.95 µs | 339,508 行/s | 342~433× |
| `RdpRecAssoInfo`(19) | 100 | 13.90 µs | 71,921 行/s | 35~105× |
| `RdpRecAssoInfo` | 1,000 | **5.91 µs** | 169,183 行/s | **202~268×** |
| `RdpRecAssoInfo` | 10,000 | 3.68 µs | 271,844 行/s | 254~410× |
| `RdpRecTempTrack`(20,2 文本列) | 1,000 | 4.43 µs | 225,502 行/s | 208~287× |
| `RdpRecTrustTrack`(45,8 文本列) | 1,000 | 8.77 µs | 114,067 行/s | 95~154× |
- **批 1,000 是甜点**(批 10,000 提升有限);批 100 时事务固定成本仍未摊薄。
- **必须把"实体构造"也算进端到端**:单条 → 批 1000 的端到端加速比为 Point ≈335×、Asso ≈207×、TempTrack ≈52×、TrustTrack ≈10×(后两者构造占批量后成本 81%/94%,见第 6 节)——**对文本数组实体,光批处理不够**。
### 4.4 实测:SQLite 配置对比(journal/事务/语句复用/AUTOINCREMENT)
用 sqlite3 C API 在同一台机器上复现等价写入(45 列宽表 / 19/23 列窄表,每配置独立空库、20,000 行、多次运行中位数):
| 配置 | journal / sync | 批 | 语句复用 | 每行 mean | 吞吐 | 结论 |
|---|---|---|---|---|---|---|
| 当前实现等价(autocommit) | DELETE / OFF | 0 | 否 | **1120.1 µs** | 893 行/s | 基线:每行一个事务 |
| autocommit + 语句复用 | DELETE / OFF | 0 | 是 | 949.6 µs | 1,053 行/s | **无改善**(噪声内)——瓶颈不在 prepare |
| 批量 1,000 | DELETE / OFF | 1000 | 是 | **4.90 µs** | 203,991 行/s | 批处理 |
| 批量 10,000 | DELETE / OFF | 10000 | 是 | 4.55 µs | 219,761 行/s | 收益趋平 |
| 批量 1,000 + 每行重新 prepare | DELETE / OFF | 1000 | 否 | 13.46 µs | 74,270 行/s | 复用省 **7.2~10.6 µs/行** |
| autocommit + WAL + NORMAL | WAL / NORMAL | 0 | 是 | 209.7 µs | 4,770 行/s | 不要用 NORMAL |
| 批量 1,000 + WAL + NORMAL | WAL / NORMAL | 1000 | 是 | 53.68 µs | 18,630 行/s | 本机 checkpoint fsync 抖动:比 DELETE 批量差约 11×、比 WAL+OFF 批量差 8.6× |
| autocommit + WAL + OFF | WAL / OFF | 0 | 是 | **17.21 µs** | 58,116 行/s | 单条改 WAL 约 55~65× |
| 批量 1,000 + WAL + OFF | WAL / OFF | 1000 | 是 | 6.23 µs | 160,419 行/s | 与 DELETE 批量同量级 |
| 批量 100 + DELETE + OFF | DELETE / OFF | 100 | 是 | 9.83 µs | 101,775 行/s | 每帧小批量(≤150 行)参考 |
| 批量 100 + WAL + OFF | WAL / OFF | 100 | 是 | **5.53 µs** | 180,825 行/s | 小批量时 WAL 有 **1.8×** 价值 |
| **journal 放内存** + autocommit | MEMORY / OFF | 0 | 是 | **38.72 µs** | 25,825 行/s | 无 journal 文件 → 比 DELETE 快 28× |
| **journal 关闭** + autocommit | OFF / OFF | 0 | 是 | **36.45 µs** | 27,436 行/s | 同上(不推荐:崩溃可能损坏库) |
| 批量 1,000 + AUTOINCREMENT | DELETE / OFF | 1000 | 是 | 4.56 µs | 219,195 行/s | 与 4.90 差异在噪声内(≤10%) |
| 批量 1,000 + 窄表 19 列 / 23 列 | DELETE / OFF | 1000 | 是 | **1.55 / 1.73 µs** | 646,272 / 579,384 行/s | 列数/文本体积决定批量下限 |
**成本分解(三条独立证据)**:
1. **journal 文件 I/O ≈1.08 ms/行,占单条成本 96%~97%**:同一事务路径下,journal 从 DELETE 换成 MEMORY/OFF 后每行从 1120 µs 降到 ~37 µs,换 WAL+OFF 降到 17.2 µs ⇒ "journal 文件的创建/写入/删除"约 1.08 ms/行;
2. **原生文件操作基准**:空文件 `CreateFile+CloseHandle+DeleteFile` ≈0.15~0.18 ms/次、写 4 KB 后删除 ≈0.29~0.31 ms/次;带 `FlushFileBuffers` 在机械盘上 ≈**30.9 ms/次**(这解释了为什么 `synchronous=NORMAL` 在机械盘上表现极差);
3. **CPU 侧不是主因**:单条路径语句复用无改善;批量路径里"每行重 prepare"比复用多 7.2~10.6 µs/行,是批量模式下的次要优化项。
**结论**:
- 单条路径的性能由**每行一个事务**决定,`synchronous=OFF`/语句复用都救不了;要降低单条成本只能换 journal 方式(WAL/MEMORY,均有限)或**批处理**;
- **批处理是最有效手段**:DELETE+OFF+批 1000 达 4.90 µs/行(~229×);配合"每帧小批量"场景可再加 WAL(1.8×);
- **不要用 `synchronous=NORMAL`**;调试用途可接受 `synchronous=OFF` 的崩溃丢尾数据,但 `journal=MEMORY/OFF` 崩溃可能损坏库,只可作为诊断对照,不建议生产采用;
- `AUTOINCREMENT`(当前建表默认带 `PRIMARY KEY AUTOINCREMENT`,`sqlite.hpp:554`)在本机测量中差异 ≤10%(噪声内),语义上本项目不需要(每次运行新库、只增不删),去掉是"无风险的小优化"而非主要收益项;
- 本机磁盘是 SATA 机械盘,部署环境应优先 SSD,并把记录目录加入 Defender 排除(可显著降低单条绝对成本,但不能替代程序改造)。
### 4.5 端到端 A/B 实测(`REC_OFF` vs `REC_ON` 双 DLL)
#### 4.5.1 首轮场景(输入点迹路径,未形成可靠航迹)
场景:相扫 TWS;每帧 150 点、100 波位/圈、6 圈(首圈预热);杂波为簇状(凝聚后约 7 点/帧);目标上报速度与几何速度不匹配,本轮未起批(`trust_max=0`)——**这一轮隔离出了"输入点迹记录(150 行/帧)"单项开销**。原始数据:`build/perf_bench_pipeline/run_pilot_off|run_pilot_on/`(`frames.csv`/`circles.csv`/`summary.txt`/记录库)与汇总 `pilot_result.json`,可用 `rerun_pilot.ps1` 复跑(`--vr 60`)。
| 指标(统计第 1~5 圈,普通帧 n=495) | `REC_OFF` | `REC_ON` | 增量 | 倍数 |
|---|---|---|---|---|
| `data_preprocess` 每帧 mean | 0.0479 ms | **143.77 ms** | +143.72 ms | ~3000× |
| `data_preprocess` 每帧 p99 | 0.1297 ms | 462.08 ms | — | — |
| `track_process` 普通帧 mean | 0.1600 ms | 6.38 ms | +6.22 ms | ~40× |
| 每帧合计 mean | 0.2080 ms | **150.14 ms** | +149.93 ms | ~722× |
| 每帧合计 p99 | 0.4631 ms | 467.16 ms | — | — |
| 每圈合计 mean(100 帧) | 139.91 ms | **16,765.4 ms(16.8 s)** | +16.6 s | ~120× |
| 记录/圈 | 0 | 约 17,229 行 | — | — |
- 记录库:13,881,344 B;全运行(600 帧)行数:`RdpRecPoint` 90,000 + `RdpRecPointCohesionTWS` 4,169 + `RdpRecTempTrack` 9,205 = **103,374 行**(不含 `sqlite_sequence` 系统表)。全运行 ON/OFF 增量 = 98.30 s − 0.74 s ≈ **97.56 s**(含预热圈与判圈帧)→ **≈0.94 ms/行**;统计范围(第 1~5 圈,剔除预热圈)的增量为 83.1 s,与全运行口径一致;
- 每行成本随环境波动:本轮(机器空闲)≈0.94 ms/行;此前同场景在微基准并行压测时约 1.0 ms/行;微基准单条 mean 1.1~1.3 ms/行——量级一致,**结论不受影响**;
- **仅"每帧 150 行输入点迹"就产生约 144 ms/帧的额外耗时**(`data_preprocess` 增量 143.72 ms),单帧即超 10 ms 预算 14 倍;`track_process` 普通帧的 6.38 ms 来自每帧约 7 行凝聚点记录,按同一量级线性增长。
#### 4.5.2 含航迹场景(关联候选路径真实覆盖,最终 A/B 结果)
为覆盖 ⑥ 关联信息这条最大的路径,A/B 驱动修正了合成目标的速度一致性(几何径向速度 = 点迹上报径向速度,否则起批马氏距离被多普勒残差主导而无法起批),最终在 6 圈内形成 **186 条可靠航迹**,两配置的 `trust_num` 序列完全一致(124 → 180 → 186),**A/B 对照有效**。
**每帧耗时**(第 1~5 圈、普通帧 n=495,model==2):
| 指标 | `REC_OFF` mean | `REC_ON` mean | 增量 | 倍数 |
|---|---|---|---|---|
| `data_preprocess` | 0.0685 ms | **208.26 ms** | +208.19 ms | **3042×** |
| `track_process` | 0.2004 ms | 9.77 ms | +9.57 ms | 48.8× |
| 每帧合计 | 0.2689 ms | **218.03 ms** | +217.76 ms | **811×** |
- `REC_ON` 普通帧分布:**495/495 帧 > 10 ms**,489/495 帧 > 100 ms;最"好"的一帧 `data_preprocess` 仍要 89.5 ms(9× 帧预算)。
- `data_preprocess` 的 208 ms ≈ 150 行输入点迹 × **1.39 ms/行**,占普通帧 95.5%。
**判圈帧(model=1,每圈 1 帧,含关联/起始/消亡 + 记录)**:
| 指标 | `REC_OFF` | `REC_ON` |
|---|---|---|
| `track_process` mean | 318.07 ms | **59,218.01 ms(59.2 s)** |
| `track_process` p50 | 164.90 ms | 7,169.11 ms |
| `track_process` max | 751.04 ms | **170,269.92 ms(170.3 s,227×)** |
- **判圈帧尖峰的来源随圈变化**:第 4/5 圈(180/186 条航迹)的 **115.1 s / 170.3 s** 主要来自关联候选记录(`RdpRecAssoInfo` 逐圈 87,088 / 122,760 行);第 1~3 圈(0/0/124 条航迹)分别为 0.84 / 7.17 / 2.72 s,其中第 2 圈的 7.17 s **与关联无关**(该圈关联记录为 0 行),主要来自 ④ 临时航迹记录(约 2×10³~2.5×10³ 行,按 1.4 ms/行 约 3~3.5 s)叠加文件系统/杀软抖动。`REC_OFF` 同帧耗时分别为 2.4 / 50.1 / 164.9 / 621.9 / 751.0 ms(最大 751.0 ms,第 5 圈 186 航迹的算法计算)。
- **关联候选记录占判圈帧耗时约 99%**(第 4/5 圈 `RdpRecAssoInfo` 分别 87,088 / 122,760 行/圈;OFF 同帧算法本身 0.75 s)。
**每圈总耗时(100 帧合计)**:`REC_OFF` 均值 **344.79 ms** → `REC_ON` 均值 **81,050.64 ms(81.05 s)**,**235.1×**;未形成航迹的稳态圈 24.0~26.9 s/圈(超 1 s 圈预算 24~27×),形成航迹后 136.7 / 191.6 s/圈(137~192×)。整个 600 帧运行:`REC_OFF` 2.3 s / `REC_ON` 426.7 s(**185×**)。
**记录量**(`run_on/rdp_records/*.db`,35.32 MB):
| 记录表 | 行数 | 说明 |
|---|---|---|
| `RdpRecPoint` | 90,000 | 150 行/帧 × 600 帧 |
| `RdpRecPointCohesionTWS` | 4,170 | 约 7 行/帧 |
| `RdpRecAssoInfo` | **211,584** | 逐圈 1,736 / 87,088 / 122,760(随航迹数 124→180→186) |
| `RdpRecTempTrack` | 8,330 | 约 2,000~2,500 行/圈 |
| `RdpRecTrustTrack` | 304 | 可忽略 |
| **合计** | **314,388** | 平均 112.3 B/行;314,388 行 / 426.7 s ≈ **737 行/s(总量口径 1.36 ms/行)** |
> 三条独立口径互相印证"成本与行数严格线性":**总量口径 1.36 ms/行**(314,388 行 / 426.7 s)、**输入点迹口径 1.39 ms/行**(`data_preprocess` 208.26 ms / 150 行)、**凝聚点口径 1.40 ms/行**(`track_process` 9.77 ms / ~7 行)。A/B 的每行成本比微基准新库/单表的 1.1~1.3 ms/行略高,原因是 A/B 库增长到 35 MB、5 张表、页缓存压力更大且伴随 Defender 扫描——同一数量级,结论一致。
> 说明:该合成场景(200 目标、150 点/帧、凝聚后约 7 点/帧)的关联量级是**保守下限**(真实 150 个独立点迹时按 1.39 ms/行外推约 270 万行/圈、约 1 小时/圈);A/B 的绝对毫秒数不能直接当作现场实时预算,**可迁移的是 ON/OFF 比值、每行成本、以及 `M×N` 行数规律**。
### 4.6 为什么 Release 优化无效、为什么 OFF 就没有开销
1. **成本在 I/O 与事务,不在 CPU 计算**:`/O2` 优化的是算法指令,无法消除每条记录的文件创建/删除、锁、页写入;§4.4 中"语句复用对单条无效"也证明了这一点。
2. **记录点代码在 ON 构建中是实打实编译进去的**:`RDP_RECORD_ENABLE` 只控制"是否参与编译/是否链接",一旦定义,每条记录同步执行;`NDEBUG` 不会移除它们。
3. **Release 处理本身更快,使固定 I/O 开销占比更大**:OFF 时处理只需零点几毫秒/帧,ON 时每条记录 1 ms,相对放大即为数百倍(与 §4.5 实测一致)。
4. **OFF 的零开销来自编译期移除**(`CMakeLists.txt:118-158`、`rdp_record.h:42-45`):不编译、不链接、宏为空——这是"关闭即无性能损失"的正确实现方式,但不能替代"Release 排障时开启记录也要实时"的需求;后者必须靠**运行期架构改造**解决。
---
## 5. 瓶颈二:记录行数与负载成 `M×N` 关系(比逐条事务更致命)
### 5.1 行数模型
符号:`B`=每圈波位数(本项目参数 100,实际由宿主决定);`p`=每帧输入点数(≤150);`η`=凝聚存活率(输出/输入,未实测,场景表取 0.9);`N`=一次关联批凝聚点数(≈(B−1)·p·η,因为 `point_recv` 整圈累积);`M`=可靠航迹数(≤500);`A`=被关联消费的点数。
| 记录点 | 每 TWS 圈行数 | 上限 | 备注 |
|---|---|---|---|
| ① 输入点迹 | `B·p` | 15,000 | 每帧逐点 |
| ② 凝聚点迹 | ≈`N` | 14,850 | 每圈 99 次之和 = 下一圈关联批点数 |
| ④ 临时航迹 | `ΣL ≈ (1~3)(N−A)` | 病态 ~4.5×10⁵ | **全量快照**:一条 10 点临时航迹累计被写 1+2+…+10=55 行 |
| ⑤ 可靠航迹 | ≤`M` | 500 | 含外推成功航迹 |
| ⑥ 关联信息 | **`M×N`(精确等于候选对数)** | 500×14,850 = **7.425×10⁶** | 失败候选 `:554` + 被消费候选 `:710/:722`,当前最近邻实现下恰好每对候选一行 |
### 5.2 典型场景每圈行数(B=100,η=0.9,A≈M)
| 记录点 | 稀疏(p=10,M=20) | 中等(p=50,M=100) | 稠密(p=150,M=300~500) |
|---|---|---|---|
| `N`(关联批点数) | ≈891 | ≈4,455 | ≈13,365(上限 14,850) |
| ① 输入点迹 | 1,000 | 5,000 | 15,000 |
| ② 凝聚点迹 | 891 | 4,455 | 13,365 |
| ④ 临时航迹 | 0.9~2.6×10³ | 4.4~13×10³ | 13~39×10³ |
| ⑤ 可靠航迹 | ≤20 | ≤100 | 300~500 |
| ⑥ 关联信息 | 17,820 | 445,500 | **5,346,000~6,682,500** |
| **合计** | **≈2.1×10⁴ 行/圈** | **≈4.6×10⁵ 行/圈** | **≈5.4×10⁶~6.7×10⁶ 行/圈** |
| ⑥ 占比 | ≈86% | ≈97% | ≈99% |
### 5.3 按实测单行成本折算的每圈耗时
以本机 **~1.2~1.3 ms/行(微基准 3 次运行 mean 中位,§4.2)** 折算;若按 A/B 总量口径(1.36~1.40 ms/行,§4.5.2)则下表数值再高约 10%~15%,结论数量级不变:
| 场景 | ⑥ 关联信息 | 全部记录(合计) | 与 1 s 圈周期相比 |
|---|---|---|---|
| 稀疏(2.1×10⁴ 行) | 21~23 s | **≈25~27 s/圈** | 25~27× |
| 中等(4.6×10⁵ 行) | 535~579 s | **≈9.2~10.0 min/圈** | 550~600× |
| 稠密(5.4×10⁶~6.7×10⁶ 行) | 107~145 min | **≈1.8~2.4 h/圈** | 6400~8700× |
- **实测对照**:首轮 A/B(未起批)16.8 s/圈 对应约 1.72×10⁴ 行/圈(只有点迹/凝聚/临时航迹),落在上表稀疏量级;含航迹 A/B 的第 5 圈(186 航迹)约 14.0×10⁴ 行/圈(⑥ 122,760 行)+ 191.6 s → **1.37 ms/行**,行数与耗时的线性关系、以及 `M×N` 随航迹数增长(1,736 → 87,088 → 122,760 行/圈)均与模型一致。
- **关键推论:即使把写库优化到零成本,稠密场景 0.5 GB/圈的数据量也无法以 1 Hz 落盘**(需持续 ~500 MB/s 写带宽,超过本机 SATA 机械盘甚至 SATA SSD 的持续写能力)。所以 **⑥ 必须裁剪,不能只做性能调优**。
### 5.4 数据量估算
| 表 | 实测每行 DB 体积 | 说明 |
|---|---|---|
| `RdpRecPoint` | 119.6 B | 22 列标量 |
| `RdpRecAssoInfo` | 96.5~100.8 B | 18 列标量 |
| `RdpRecTempTrack` | 315.8 B | X/P 文本列(实测平均文本 196.7 B) |
| `RdpRecTrustTrack` | **1,369.7 B** | 8 个数组文本列(实测平均文本 **1,145.1 B**) |
- 稠密场景 ⑥ 5.35×10⁶ 行 × ~100 B ≈ **535 MB/圈**;④ 3.9×10⁴ 行 × 316 B ≈ 12 MB/圈;⑤ 500 × 1.37 KB ≈ 0.7 MB/圈。
- 记录库无轮转/无上限(每次运行一个新文件),长时间运行会到 GB~TB 级。
### 5.5 TAS 通道
TAS 每帧执行 ①③⑤⑥,但 `point_recv_tas` 每帧清空、关联只针对 1 条航迹(`track_asso_tas.cpp:263-283`),每 TAS 帧记录 **≤451 行**(①≤150 + ③≤150 + ⑤≤1 + ⑥≤150,见 §3.2);即使相扫每 TWS 圈有 4 个 TAS 帧,也只约 1.8×10³ 行/圈,是稠密 TWS ⑥ 的约 1/3000。**TAS 通道批处理后即可接受,不是本次瓶颈重点。**
---
## 6. 瓶颈三:处理线程上的实体构造(数组转文本)
### 6.1 实测构造成本
`RDP_RECORD(...)` 的实参在**调用方线程、进入 `Write` 之前**求值(`rdp_record.h:39`);`RdpRecTrustTrack`/`RdpRecTempTrack` 的构造函数要把 `X/P/...` 数组经 `arrayToString()` 转成文本,而 `arrayToString`(`rdp_record_entity.h:466-483`)对**每个元素调用 `elementToString` 构造一个 `std::ostringstream`**(`:450-453`)。
| 构造项 | 实测(3 次运行中位数) |
|---|---|
| `RdpRecTrustTrack` 构造(8 个数组共 135 个 double) | **133.56 µs/行** |
| `RdpRecTempTrack` 构造(X[4]+P[4][4] 共 20 个 double) | **19.44 µs/行** |
| `RdpRecPoint` 构造(22 标量) | 0.010 µs/行 |
| `RdpRecAssoInfo` 构造(18 标量) | 0.020 µs/行 |
| `arrayToString(double[6])` | 5.85 µs |
| `arrayToString(P[6][6])` | 35.73 µs(36 次转换,最贵) |
| `std::ostringstream` 格式化一个 double | 0.97 µs(其中 ostringstream 设施底价 ≈0.24 µs) |
| `std::to_string(double)` 格式化一个 double | 0.52 µs(约为前者 54%) |
| 手工 join `to_string(double[6])` | 3.17 µs(比 arrayToString 快 1.8×) |
其中 `RdpRecTrustTrack` 的 8 个数组:X 5.68 µs、P 35.73 µs、X1 5.93 µs、X2 5.94 µs、X3 5.81 µs、P2 36.02 µs、P3 34.36 µs、u 2.62 µs(P/P2/P3 三个 6×6 矩阵合计 ≈106 µs,占构造的 ~80%)。
**批量后构造成为主要成本**:端到端(构造 + 批 1000 写库)每行成本 TrustTrack ≈142.3 µs(构造占 94%)、TempTrack ≈23.9 µs(构造占 81%)、Point/Asso ≈3.8~5.9 µs(构造占 0.3%)。**这就是"为什么信任航迹/临时航迹不能只靠批处理"的实测依据**。
### 6.2 折算到每圈
| 表 | 行数(中等/稠密) | 构造耗时(中等/稠密) |
|---|---|---|
| ④ 临时航迹 | 4.4~13×10³ / 13~39×10³ | **86~253 ms / 253~758 ms 每圈** |
| ⑤ 可靠航迹 | 100 / 500 | **13.4 / 66.8 ms 每圈** |
- 注意:④⑤ 都发生在 **model=1 的同一帧**里,因此都会形成"单帧尖峰"。
- **批处理省不掉这部分**:批处理只把"每行一个事务"变成"每批一个事务",实体构造仍逐行在雷达线程完成(`hotpath_findings.md` §4.2)。要消掉它只能:
- 把构造/序列化搬到后台写线程(处理线程只拷贝源结构体快照);或
- 改列设计(数组存 blob、或元素展开表),不生成文本;或
- 减少记录行数(只记变化/末点/采样)。
---
## 7. 次要问题与放大因素
### 7.1 线程模型与互斥锁
- 接口线程模型未定义(`README.md:1020`,BUG-03:宿主应单线程调用),记录模块用**单连接 + 一把全局 `std::mutex`**(`rdp_record.cpp:41,183`)。单线程宿主的直接后果是:**磁盘事务的等待时间 100% 叠加到雷达处理线程**。
- 若宿主实际多线程调用(如收数据线程与处理线程并发),锁竞争会让记录开销进一步放大并造成优先级反转;`SQLITE_THREADSAFE=1` 还会在 SQLite 内部再加一层互斥。这一风险需现场确认宿主调用方式。
### 7.2 `AUTOINCREMENT`(语义上可去掉,收益不大)
- ormpp 的建表 SQL 对注册的自增主键**无条件追加 `PRIMARY KEY AUTOINCREMENT`**(`sqlite.hpp:552-560`,第 554 行),因此每行 INSERT 额外读+写 `sqlite_sequence` 表(多一个脏页 + 一条 journal 记录)。
- 本项目每次运行新建 db、只增不删,`INTEGER PRIMARY KEY` 的 rowid 本身就是单调的,**不需要 AUTOINCREMENT**。
- 实测:批量 1000 时带/不带 AUTOINCREMENT 的差异在测量噪声内(4.90 vs 4.56 µs/行,方向随运行翻转,≤10%),**属"无风险的小优化",不是主要收益项**;单条路径下该额外页会被 1 ms 级事务成本掩盖。
- 由于 ormpp 的 DDL 硬编码,去掉它需要记录模块自行预建表(`CREATE TABLE IF NOT EXISTS` 变 no-op,但 auto-key 映射仍会登记、insert 仍跳过 id 列)。
### 7.3 `journal_mode` 与 `synchronous=OFF` 的常见误解
- 当前只设置了 `synchronous=OFF`,未设 `journal_mode` → 默认 `DELETE`(提交时删除 journal 文件)。**这是单条路径最大的成本来源(实测占 96%~97%)**。
- `synchronous=OFF` 只省 `FlushFileBuffers`,**不省 journal 创建/写入/删除和锁**——所以"已经关了同步,为什么还慢"的疑问不成立:慢的是事务文件操作,不是 fsync。
- 设置 `PRAGMA journal_mode=WAL` 不能走 ormpp 的 `execute()`(它要求 `sqlite3_step` 返回 `SQLITE_DONE`,而该 PRAGMA 会返回一行,见 `sqlite.hpp:434-451`);可用同连接的 `query_s<std::tuple<int>>` 消费返回行,或 connect 前用 C API 设置。注意 WAL 默认 sync 仍是 FULL 语义,要同时设 `synchronous=NORMAL/OFF`;且本机机械盘上 `WAL+NORMAL` 因 fsync(≈31 ms/次)出现 checkpoint 抖动(§4.4)。
- 若引入批处理,`DELETE + synchronous=OFF` 已可达到 3.8~8.8 µs/行(批 1000),**WAL 不再是必需项**;只对"每帧 ≤150 行的小批量"再叠加 WAL 可再快 1.8×(§4.4 S11/S12)。
### 7.4 缓存、busy_timeout 与失败降级
- `cache_size` 默认仅 2 MB(`SQLITE_DEFAULT_CACHE_SIZE=-2000`);库变大、B-tree 变深后每行触页更多,可设 `PRAGMA cache_size=-8192`(8 MB)等(不返回行,可走 `execute()`)。
- `busy_timeout` 默认 0,Windows 上杀软/文件系统瞬时锁冲突会直接失败;当前无重试。
- **失败熔断缺失**:`rdp_record.cpp:189-195` 只在首次失败提示一次,但之后**每条记录仍然完整走"拼 SQL → prepare → step 失败"流程**;若库/磁盘异常,会持续消耗处理线程。建议失败后按实体或全局熔断。
### 7.5 环境放大因素(重要且常被忽略)
- 本机 `rdp_records` 位于 **SATA 机械盘**,且 **Defender 实时防护开启**;"每条记录新建+删除一个 journal 文件"的链条被文件系统元数据(NTFS `$LogFile`)与杀软扫描显著放大——1 ms 级单行成本中有很大比例来自环境而非 SQLite 本身(原生文件 create+write4K+close+delete 就需 0.29~0.31 ms/次)。
- 换到 SSD + 对记录目录加 Defender 排除/其他白名单,单行成本会明显下降;但**不能替代程序侧的批处理/裁剪/异步改造**(按 §5,即使降到 50 µs/行也仍不满足高行数场景)。
- 网络盘/共享盘上 SQLite 的 journal/WAL 行为更差(WAL 甚至可能不支持),记录目录应强制本地盘。
### 7.6 数据库增长与运维
- 每次运行一个新 db、无大小上限、无轮转、无定期清理;稠密场景 0.5 GB/圈,长时间开启会耗尽磁盘。
- 无 `VACUUM`(只追加场景一般不需要),但长时间运行后文件碎片/自由页问题需关注;调试库可接受。
---
## 8. 优化措施
### 8.1 措施总览
| # | 措施 | 针对瓶颈 | 实测/预期收益 | 风险/代价 | 改动面 |
|---|---|---|---|---|---|
| **P0-a** | **关联信息裁剪/采样**(⑥ 只记被取中候选 + 近失候选采样/白名单) | 数据量 `M×N` | 行数降 1~3 个数量级(稠密 5.4×10⁶ → 10³~10⁴ 行/圈) | 分析信息减少(可配置开关保留全量模式) | 业务记录点(`#ifdef` 内) |
| **P0-b** | **批量事务**(①~⑤、TAS ⑥ 帧末/圈末收集后 `RDP_RECORD_BATCH`) | 逐条事务 | DB 侧单行 **~1.1 ms → 3.8~8.8 µs(约 95~336×,批 1000,按实体)**;端到端(含构造)Point/Asso ≈207~335×、TempTrack ≈52×、TrustTrack ≈10×(实测) | 崩溃丢尾批、内存缓存 | 业务记录点 + 少量成员变量 |
| **P0-c** | **异步写线程 + 有界队列**(处理线程只入队 POD 快照,后台序列化+批量写) | 实时性保证(隔离磁盘抖动) | 处理线程开销降到"拷贝+入队"(~0.1~0.3 µs/行),磁盘抖动不再传导 | 实现复杂度、退出 flush、溢出丢弃策略 | 记录模块框架 + 记录点写法 |
| **P1-a** | **构造成本迁移/削减**(信任航迹数组列改 blob;或后台线程做 `arrayToString`;`to_string`/`to_chars` 替代 ostringstream) | 实体构造 133.6 µs/行 | 处理线程 ④⑤ 构造开销可降为 ~0(后台)或 ~1.8×(to_string);批量后构造占 TrustTrack 端到端 **94%** | 查询需解析 blob;改动实体/查询工具 | 记录模块 + 实体定义 |
| **P1-b** | **临时航迹只记增量/末点**(不再每圈全量快照) | ④ 重复行 | 行数降 10~55× | 丢失历史轨迹(可按圈采样保留) | 业务记录点 |
| **P1-c** | **SQLite 配置**:去 AUTOINCREMENT、`cache_size`、`synchronous`/WAL 按环境、`busy_timeout`、失败熔断 | 常数项 | 每帧小批量再叠加 WAL 约 1.8×;单条 WAL 55~65×(仅兜底);AUTOINCREMENT ≤10%(噪声内) | 崩溃一致性取舍 | 记录模块 |
| **P2-a** | **记录策略**:按圈采样(每 N 圈记 1 圈)、按表/字段白名单、运行期开关 | 数据量 | 任意比例降载 | 分析样本变少 | 记录模块 + 可选接口 |
| **P2-b** | **落盘/轮转/运维**:按圈或大小轮转、上限、Defender 排除、本地 SSD | 环境/磁盘 | 环境相关,幅度大 | 部署约束 | 部署 |
| **P2-c** | **备选:二进制原始日志 + 离线入库** | 全部 | 运行时近零成本(顺序写文件),离线转 SQLite | 需要额外工具链/查询步骤 | 记录模块 + 工具 |
### 8.2 P0-a:关联信息裁剪(决定性,必须做)
当前 ⑥ 在双层循环(`track_asso.cpp:441-445`)里逐候选写库:未过门限当场写(`:554`),过门限候选进 `associated_info`,最近邻消费时再写(`:710/:722`)——**每对候选恰好 1 行,总数 = M×N**。建议改为"收集 + 裁剪 + 分块批写":
1. **只记录被最近邻取中的候选**(`min_d_flag=true`,每圈约 `min(M,N)` 行)——这正好是复现关联决策所需的最小集合;
2. **近失候选采样**:`d_min < K×门限` 的候选按 1/N 采样(N 可配置,默认如 100),或每个点迹只保留 `d_min` 最小的前 K 个候选;
3. **白名单**:只对指定 `circle_id`/`track_index`/`point_id` 区间记录全量候选(复现特定问题时开启);
4. **运行期开关**:`RDP_RECORD_ASSO_FULL=0/1`(环境变量或新增接口),默认关闭全量失败候选;
5. 收集到 `std::vector<RdpRecAssoInfo>` 后,**每 4,096 行或圈末 flush 一次**(限制内存尖峰)。
预期:⑥ 从 `M×N` 降到 `O(N + M)`(稀疏 ~900 行、中等 ~4.5×10³ 行、稠密 ~1.3×10⁴ 行/圈),⑥ 单表写库降到 **约 5~80 ms**(按批 1000 实测 3.8~5.9 µs/行),数据量从 535 MB/圈降到 ~1.3 MB/圈;但**整圈(①~⑥ 合计)仍有约 0.2~0.7 s**(稠密场景约 5.5~8.1 万行),需继续用异步写线程/按圈采样消化。
### 8.3 P0-b:批量事务(改动最小、收益最大)
框架**已具备**批量能力:`RDP_RECORD_BATCH(recs)`(`rdp_record.h:41`)→ `Write(const std::vector<T>&)`(`rdp_record.cpp:198-225`)→ ormpp 批量插入(一次 prepare + 一个 `BEGIN/COMMIT`,`sqlite.hpp:859-902`)。**当前业务侧 0 个调用点**,只需按"天然批次边界"改造记录点:
| 记录点 | 建议批次边界 | 做法(示意) |
|---|---|---|
| ① 输入点迹 | 每帧 `data_preprocess` 循环结束 | `std::vector<RdpRecPoint> recs;` 循环内 `emplace_back(...)`(`#ifdef RDP_RECORD_ENABLE` 包裹),循环后 `RDP_RECORD_BATCH(recs);` |
| ② TWS 凝聚点 | 每次 `dot_coh_process_buff` 输出循环末(或跨帧累积到圈末) | 同上,一行/点 |
| ③ TAS 凝聚点 | `dot_coh_tas_process` 输出循环末 | 同上 |
| ④ 临时航迹 | `track_init.cpp:110-117` 双层遍历收集后一次批写 | 先按 P1-b 裁剪,再批写 |
| ⑤ 可靠航迹 | `track_asso.cpp:71-165` / `track_asso_tas.cpp:44-125` 输出循环收集后批写 | 行数 ≤500,批写 + P1-a |
| ⑥ TWS 关联信息 | `model_filter` 内先收集,圈末/每 4096 行 flush | 必须先做 P0-a 裁剪 |
| ⑥ TAS 关联信息 | `model_filter` 点循环结束(`:365` 后)/候选清理(`:497-500`)| 失败候选也先收集,帧末一批写 |
注意:
- 关掉记录功能时这些代码必须整体在 `#ifdef RDP_RECORD_ENABLE` 内(保证交付构建零代码、零告警);
- 批写时**内存占用要有上限**(⑥ 尤其);批过大时失败会丢整批,可接受(调试用途)但需记录丢批计数;
- 批处理**不改变**记录点的语义(先记录再消费/后记录),只是把"逐行提交"变成"一批提交"。
### 8.4 P0-c:异步写线程(实时性的最终保证)
```
雷达处理线程 记录写线程(独立)
──────────── ─────────────────
RDP_RECORD(...) 循环:
├─ 构造紧凑 POD 快照(或直接拷贝源结构体) ├─ 从队列取出一批(如 ≤2000 行 / 20 ms)
├─ 有界队列 push(无锁 SPSC 或细粒度锁) ├─ 序列化(原 ostringstream 逻辑移到这里)
└─ 返回(~0.1~0.3 µs/行) ├─ 一个事务批量 insert(现有 Write(vector) 路径)
└─ 失败熔断/丢批计数
队列满策略:丢弃(丢新或丢旧)+ 原子计数;Shutdown 时停止接收并排空(有超时)
```
- **收益**:磁盘抖动/Defender/事务尾延迟与雷达线程彻底解耦;处理线程只有"拷贝+入队"的线性小开销,**可给出确定的实时上界**;
- **前提**:⑥ 必须先裁剪(否则 10⁶ 行/圈的入队本身也会造成尖峰与内存爆炸);
- **实现要点**:
- 队列容量与高水位监控(如 64K~256K 行/16~64 MB,按行类型分队列);
- 丢弃策略与计数(`dropped_rows`),运行结束打印,保证"丢数据可解释";
- `Destroy()`/`Shutdown()` 时先 flush 再关库(`rdp_record.cpp:154-166` 需配套改造);
- 记录实体构造(数组转文本)**移到写线程**,用源结构体快照(`PointRecv`/`Trust_Track`/`Asso_Info`/`Temp_track` 拷贝)作为队列元素;注意 `Trust_Track` 含 `std::vector` 成员(`Hight_smooth`),拷贝成本略高,可只拷贝需要的字段。
- **建议只对"行数随负载线性增长"的 ①~⑤ 走异步;⑥ 裁剪后也可走异步**。
### 8.5 P1-a:构造成本的处理
- **方案 A(推荐)**:把 `arrayToString` 全部移到异步写线程(处理线程只入队源结构体),处理线程构造成本 → 0;写线程每行 133.6 µs(TrustTrack)可被后台线程吸收(500 行/圈 ≈ 67 ms,远小于 1 s 圈预算)。
- **方案 B**:改实体列设计——`X/P/...` 存 **blob**(如 `std::vector<double>`/定长二进制),避免文本化;查询时用 Python/工具解析。单行 1.37 KB → ~0.3 KB,同时省掉 ostringstream 与 SQLite 文本绑定。
- **方案 C(最小改动)**:`arrayToString` 用 `std::to_string`/`std::to_chars` + 预留 `reserve` 手工拼接替代 `ostringstream`(实测 1.8×:0.52 vs 0.97 µs/double),仍保留文本列。
- 附加:只记录真正需要的列(当前 ⑤ 未记 `P1`、`Quality` 等,勿盲目补列);④ 只记增量/末点/采样。
### 8.6 P1-b:SQLite 配置与健壮性
| 项 | 建议 | 依据/注意 |
|---|---|---|
| 批事务 | 必做(P0-b) | 实测 DB 侧约 95~336×(批 1000,按实体) |
| 语句复用 | 批处理路径已由 ormpp 复用;若自建写入层则按实体缓存 `sqlite3_stmt` | 批量内每行重 prepare 多花 **7.2~10.6 µs/行**(13.46→4.90) |
| `AUTOINCREMENT` | 预建表去掉(改普通 `INTEGER PRIMARY KEY`) | 差异在噪声内(≤10%),语义上无必要;每行少一次 `sqlite_sequence` 读写 |
| `journal_mode` | 有批处理时 `DELETE + synchronous=OFF` 已足够;每帧小批量(≤150 行)可加 `WAL + synchronous=OFF` 再快约 1.8×;单条兜底用 WAL(55~65×) | 设置方式见 §7.3;WAL 需本地盘;注意 `-wal/-shm` 与 checkpoint;**不要 `synchronous=NORMAL`** |
| `cache_size` | 视库大小设 8~32 MB(不返回行,可 `execute()`) | 减少 B-tree 页进出 |
| `busy_timeout` | 设 1~5 s + 有限重试 | 默认 0 会直接失败 |
| 失败熔断 | 插入失败后按实体/全局熔断,避免每条都走失败路径 | 当前 `rdp_record.cpp:189-195` 无熔断 |
| 同步策略 | 调试库接受 `synchronous=OFF` 的崩溃丢尾数据;如需更强一致性改 `NORMAL` 并接受性能下降(机械盘上可能差 8~12×) | 文档已有说明(`docs/data_record_debug.md:56`) |
### 8.7 P2:记录策略与运维
- **按圈采样**:`circle_id % N == 0` 时记录,N 可配置(如 1/5/10),负载与数据量按比例下降;分析时仍能覆盖多圈。
- **按表开关**:现场排障通常只需 1~2 张表(例如只看输入点迹 + 关联信息),其余表可关闭。
- **记录库轮转/上限**:按圈数或文件大小滚动(如每 100 圈一个新 db),并限制总目录大小;避免 GB~TB 级文件。
- **环境**:记录目录放**本地 SSD**、加入 Defender 排除(需合规审批);避免网络盘。
- **运行期开关**:增加"开始/停止记录"的接口或环境变量(不改变交付接口导出表的前提下谨慎设计),现场按需开启。
### 8.8 备选方案:二进制原始日志 + 离线入库
如果目标是"**彻底不影响实时性**"且可接受离线转换:
- 记录点只把定长 POD 快照顺序追加到二进制文件(`fwrite`/内存映射,写线程或缓冲),运行时开销 ~µs/行;
- 运行结束后用工具把二进制转成 SQLite(或直接供 Python/Pandas 读),分析体验与现在一致;
- 优点:实时性最好、无 SQLite 事务抖动;缺点:需要额外的转换工具与格式版本管理,查询前多一步。
> 对"Release 现场排障"这种低频、短期需求,方案 8.8 往往比"把 SQLite 在线写做快"更省事;对"长期在线记录"则推荐 8.2~8.4 的异步 + 裁剪 + 批处理。
### 8.9 不建议的做法
- ❌ 只把 `synchronous=OFF` 当作解决:它只省 fsync,实测单条仍 ~950~1120 µs;
- ❌ 只加 `cache_size`:对"每条一个事务"帮助有限;
- ❌ 只用 WAL 不批处理:单条 17~210 µs/行(`NORMAL` 更差),高行数场景仍超预算;
- ❌ 只做批处理不做 ⑥ 裁剪:中等场景仍约 2.5 s/圈、稠密约 30~40 s/圈(见 §4.3/§5.3),仍不满足 1 s 圈预算;
- ❌ 用"交付构建保持 OFF"回避问题:那是交付策略,不是 Release 排障场景的解决方案;
- ❌ 把记录点当日志用、在热循环里无条件记录(当前 ⑥ 即此模式)。
---
## 9. 推荐实施路线与验收标准
### 9.1 分阶段实施
| 阶段 | 内容 | 预期效果 | 风险 |
|---|---|---|---|
| **阶段 0(立即,不改代码)** | 只在必须排障时开启 `Release+ON`;优先用 Debug+ON 或短时采数;记录目录放 SSD、加 Defender 排除 | 缓解现场影响;不能解决实时性 | 低 |
| **阶段 1(P0-b 批处理)** | ①~⑤、TAS ⑥ 改帧末/圈末 `RDP_RECORD_BATCH`;⑥ 先分块收集 | ①~⑤ DB 写入从 ~1.1 ms/行降到 3.8~8.8 µs/行(实测约 95~336×,按实体);输入点迹每帧 150 行 → 约 0.6~2.1 ms(取决于批大小)。注意 ④⑤ 端到端仅 ~10~52×(构造仍是瓶颈,需阶段 3) | 低(接口已存在) |
| **阶段 2(P0-a 裁剪 + P1-b)** | ⑥ 只记取中/近失采样/白名单;④ 只记增量;去 AUTOINCREMENT、cache_size、失败熔断 | 稠密场景 ⑥ 从 5.4×10⁶ 行/圈降到 10³~10⁴ 行/圈(该表单表从 ~100 min 降到约 5~80 ms);整圈(①~⑥)仍约 0.2~0.7 s,需阶段 3 才能稳定实时 | 中(分析信息取舍需确认) |
| **阶段 3(P0-c 异步 + P1-a 构造迁移)** | 有界队列 + 写线程 + 后台序列化/批写;Shutdown flush;丢包计数 | 处理线程新增 p99 有确定上界(目标 ≤1 ms/帧,判圈帧 ≤5 ms);**§9.2 的 p99≤1 ms 验收目标需要阶段 2+3 共同达成,仅阶段 1 批处理时仍可能出现 ms 级尖峰** | 中高(框架改动,需要长跑验证) |
| **阶段 4(P2 运维)** | 按圈采样、记录库轮转、Defender 排除、监控指标 | 长时间运行可控 | 低 |
### 9.2 验收指标(建议)
| 指标 | 目标 |
|---|---|
| 处理线程每帧新增耗时(记录功能带来) p99 | **≤1 ms**(10 ms 帧周期的 10%) |
| 判圈帧(model=1)新增耗时 p99 | **≤5 ms**(不使该帧超出预算;需要时把 ④⑤⑥ 写线程化后进一步降低) |
| 连续运行 1 小时实时性 | 无因记录导致的帧超时/积压(用宿主现有节拍监控判定) |
| 记录完整性 | 行数与业务量模型一致;`pragma integrity_check = ok`;丢包计数可解释(丢弃率 0 或明确阈值内) |
| 记录库增长 | 按采样/轮转策略可控(如 ≤ X MB/圈、总目录上限) |
| 交付构建 | `RDP_DATA_RECORD=OFF` 时 DLL 导出表/依赖/体积与改动前一致,记录代码零参与(沿用现有 CI 校验) |
### 9.3 验证与复测方法
1. **处理线程打点**:在记录点前后嵌 `QueryPerformanceCounter`(仅 `RDP_RECORD_ENABLE` 构建),输出每帧/每圈直方图(mean/p50/p99/max),对比 ON/OFF;
2. **数据库侧校验**:用 SQL 统计每圈行数(`select circle_id,count(*) from RdpRecAssoInfo group by circle_id;` 等),与业务量模型对齐;`pragma integrity_check`;
3. **队列/丢弃监控**:异步方案输出队列高水位、丢弃行数、写线程积压时间;
4. **长时间运行**:1 h 连续跑,记录帧时延 p99 随时间变化与 DB 增长;
5. **现场复测**:把本报告 A/B 驱动替换为宿主真实数据(或短时抓包回放),在目标机器上重复 §4.5 的 ON/OFF 对比,校准 `M`、`N`、每行成本三个关键参数。
---
## 10. 风险与注意事项
| 风险 | 说明/缓解 |
|---|---|
| 异步丢数据 | 队列满丢弃/退出时未 flush 会丢记录;用计数与日志显式化,调试用途可接受,但必须可解释 |
| `synchronous=OFF`/WAL 的崩溃一致性 | 进程异常/断电可能丢尾部数据或损坏库;调试库可接受,若需更强一致性改 `NORMAL` 并接受性能下降 |
| 裁剪 ⑥ 导致分析信息减少 | 保留"全量候选"运行期开关,需要时短时开启;默认只记关键候选 |
| WAL 文件与 checkpoint 抖动 | `-wal` 增长;本机机械盘上 `WAL+NORMAL` 批量均值 53.7 µs/行(比 WAL+`sync=OFF` 差 8.6×、比 DELETE 批量差约 11×)且 checkpoint 周期出现 10~100 ms 级尖峰;优先 `DELETE + 批处理` 或 `WAL + sync=OFF` + SSD |
| 多线程宿主 | 若宿主实际多线程调用,单连接+全局锁会串行化并放大延迟;异步写线程方案需明确单写者 |
| 记录库无限增长 | 必须加轮转/上限,避免磁盘耗尽 |
| 记录功能与交付隔离 | 所有改动限制在 `#ifdef RDP_RECORD_ENABLE` 与记录模块内部;交付构建(OFF)必须保持零代码、零导出、零依赖 |
| 合成场景代表性 | 本报告 A/B 为压力场景,绝对毫秒数与真实现场不同;上线前需用真实宿主数据复测 |
---
## 11. 优化实施与验收结果(2026-10-20)
依据 `requirements.md` 的决策实施(P0-b 批量事务、P0-c 异步写线程+有界队列、P1-a 方案A 构造迁移、P1-b SQLite 配置与健壮性采用;P0-a 关联裁剪、P2 记录策略不采用)。
> 编号映射说明:`requirements.md` 中的 **P1-b = 本报告 §8.6(SQLite 配置与健壮性)**;§8.1 总览表中旧编号"P1-b = 临时航迹只记增量"**未纳入本次需求**(未实施)。
### 11.1 实现摘要
| 子系统 | 实现 |
|---|---|
| 记录框架 | 处理线程只做"收集源结构体 → 一次入队";独立写线程做实体构造(`arrayToString` 全部移入写线程)+ 批量事务;有界队列(默认 64 MB / 4096 批),满则整批丢弃并计数(不阻塞处理线程);写线程 Windows `BELOW_NORMAL` 优先级;`Shutdown()` 停止入队→排空→`wal_checkpoint(TRUNCATE)`→关库→打印 `Stats`;支持 Destroy→GetB 重连(新库;同秒文件名冲突追加 `_1` 后缀) |
| 业务记录点 | 12 处(6 个 cpp)改为按帧/圈收集后调用 `RDP_RECORD_POINTS`/`RDP_RECORD_COHESION_TWS`/`RDP_RECORD_COHESION_TAS`/`RDP_RECORD_TEMP_TRACKS`/`RDP_RECORD_TRUST_TRACKS`/`RDP_RECORD_ASSO_INFOS`;关联信息按 `RDP_RECORD_ASSO_CHUNK_ROWS`(131072)分块入队;全部位于 `#ifdef RDP_RECORD_ENABLE` 内,`OFF` 构建零代码 |
| SQLite | `journal_mode=WAL`(不支持时回退)、`synchronous=OFF`、`cache_size=-8192KB`、`busy_timeout=2000ms`;建表后去 `AUTOINCREMENT`(读取 `sqlite_master` DDL 后重建空表);每表建表/插入失败只提示一次,连续错误 ≥10 次全局熔断 |
| 参数 | `parameters.h` 新增 10 个可调宏(队列容量/批数、写批行数/等待、关联分块、缓存、busy_timeout、WAL 开关、熔断阈值、丢弃日志间隔),均带 `#ifndef` 可覆盖 |
| 遗留接口 | `RDP_RECORD`/`RDP_RECORD_BATCH`(实体同步写)保留,文档标注仅测试/特殊场景使用 |
### 11.2 验收结果
| # | 项目 | 结果 |
|---|---|---|
| A1 | 构建矩阵(PS/MS × Release/Debug × REC_ON/OFF,8 配置) | **PASS**:全部 0 warning 0 error |
| A2 | `OFF` 等价性(对比改动前 HEAD 构建的基线 DLL) | **PASS**:体积 182,784 B 相同;34 个导出符号完全一致;9 个依赖 DLL 完全一致 |
| A3 | 记录内容(dup/6 圈/186 航迹场景) | **PASS**:`RdpRecPoint` 90,000、`RdpRecPointCohesionTWS` 4,170、`RdpRecAssoInfo` 211,584、`RdpRecTempTrack` 8,330、`RdpRecTrustTrack` 304,合计 **314,388 行**,与改动前完全一致;`trust_num` 序列一致 |
| A4 | 数据库正确性 | **PASS**:`integrity_check=ok`;DDL 不含 `AUTOINCREMENT`;`journal_mode=wal`;各表 `id` 严格递增;`OFF`/`ON` 均无 `-wal/-shm` 残留(Shutdown 检查点后关闭) |
| A5a | 处理线程普通帧新增耗时(`REC_ON−REC_OFF`) | **PASS**:多次运行 p99 **0.08~0.17 ms**(阈值 ≤1 ms),p50 ≈ 0.02 ms |
| A5b | 处理线程判圈帧新增耗时(压测场景,186 航迹/单圈约 12.3 万关联候选) | **未达 5 ms 目标**:多次运行 p99 **12.8~24.0 ms**、p50 2.9~4.1 ms、mean 5.9~8.7 ms、max 24.5 ms(原因:P0-a 未采用,单圈需拷贝/记录约 12.3 万行关联候选;详见 §11.3) |
| A5c | 受控负载参考(50 目标 / 50 点每帧,49 航迹) | 判圈帧新增:中位 **3.07 ms**、最大 **5.96 ms**(基本达到 5 ms 量级,随负载线性变化) |
| A6 | 队列/长跑(60 圈压测,约 43.5 s) | **PASS**:9,987,370 行、`dropped_rows=0`、`write_errors=0`、队列峰值 19.8 MB(<64 MB)、DB 1.06 GB |
| A7a | 故障注入:`rdp_records` 为普通文件(目录不可建) | **PASS**:`Init()` 静默失败,驱动 exit=0、无崩溃、不产生记录库 |
| A7b | 故障注入:小队列构建(cap=1 KB) | **PASS**:`dropped_rows=314,388` 有计数、处理流程正常结束 |
| A7c | 重连(GetB→Destroy→GetB) | **PASS**:生成 2 个独立 db 文件(同秒冲突追加 `_1`),两轮均写入成功 |
| — | 端到端墙钟(600 帧) | `REC_OFF` 1.8 s / `REC_ON` 2.1 s(改动前 `REC_ON` 为 **426.7 s**) |
> 验收脚本与原始日志:`build/analysis/verify/run_acceptance.ps1`、`latency_eval.py`、`check_record_db.py`、`failure_injection.ps1`、`acceptance_final.log`、`latency_eval.log`、`check_record_db.log`。
### 11.3 遗留问题与决策点
1. **判圈帧压测开销仍高于原定 5 ms 目标(多次运行 p99 12.8~24.0 ms、中位 2.9~4.1 ms、均值 5.9~8.7 ms)**:这与 `requirements.md` 不采用 P0-a 的决策直接相关——压测场景单圈 `M×N≈12.3 万` 行关联候选,仅"收集+入队"就需在判圈帧复制十几 MB 数据;对照的受控负载(49 航迹)中位仅 3.1 ms、最大 6.0 ms。是否满足现场要求取决于实际 `M`/`N`;若现场也接近压测量级且要求判圈帧 ≤5 ms,需要重新评估 P0-a(关联候选裁剪/采样)或按圈采样。
2. **实时性总体结论**:普通帧几乎无感(p99 +0.11 ms);压测判圈帧 `REC_ON` 绝对耗时约 0.62 s(`REC_OFF` 同帧约 0.59 s),仍小于 1 s 圈周期,但相对开销(+24 ms)来自未裁剪的行数。建议现场用真实数据按 §9.3 复测。
3. **异步语义变化**:进程崩溃/未调用 `Destroy()` 时,队列与 WAL 尾部记录可能丢失;队列满时整批丢弃,均由 `Stats` 计数(`dropped_rows`)。调试用途可接受,但分析前应确认 `dropped_rows=0`。
4. **跨表写入顺序**不再保证(各表内顺序保持);依赖 `id` 跨表排序的分析脚本需改用业务时间/`circle_id` 等字段关联。
5. **版本号未提升**(仍为 V1.5.8),变更记录于 `CHANGELOG.md [Unreleased]`;正式发布时再归档。
---
## 附录 A:关键代码位置索引
**A.1 分析时(优化前)关键位置**
| 主题 | 位置 |
|---|---|
| 记录宏与开关 | `rdp_record.h:37-54`;`CMakeLists.txt:25-57,118-158` |
| 单条写入实现 | `rdp_record.cpp:174-196`;`third_party/ormpp/ormpp/sqlite.hpp:834-856` |
| 批量写入实现 | `rdp_record.cpp:198-225`;`sqlite.hpp:859-902`(`transaction_` 默认 true 在 `:926`) |
| SQL 字符串生成 | `third_party/ormpp/ormpp/utility.hpp:438-521` |
| 建表/AUTOINCREMENT | `third_party/ormpp/ormpp/sqlite.hpp:489-585`(`:554` 追加 `PRIMARY KEY AUTOINCREMENT`) |
| `synchronous=OFF` | `rdp_record.cpp:144-147` |
| 记录点 ① | `data_process.cpp:60,132` |
| 记录点 ② | `dot_coh.cpp:230` |
| 记录点 ③ | `dot_coh_tas.cpp:73` |
| 记录点 ④ | `track_init.cpp:110-117` |
| 记录点 ⑤ | `track_asso.cpp:163`;`track_asso_tas.cpp:122` |
| 记录点 ⑥ | `track_asso.cpp:554,710,722`;`track_asso_tas.cpp:363,499` |
| 数组转文本 | `rdp_record_entity.h:448-483`(`arrayToString`) |
| 实体定义与构造 | `rdp_record_entity.h:52-443`;`struct.h:40-170` |
| TWS 关联批累积 | `dot_coh.cpp:227-231`;`track_asso.cpp:29-68`;`track_init.cpp:60,108` |
| 实时性/线程模型已知问题 | `README.md:1020`(BUG-03) |
| 功能说明与已知限制 | `docs/data_record_debug.md:15,47-59,197-204` |
**A.2 优化实施后(V1.5.8 优化,2026-10-20)关键位置**
| 主题 | 位置 |
|---|---|
| 公共 API / 宏 / `Stats` | `rdp_record.h:31-101`(`Stats`、`stats`、6 个异步入队接口、6 个记录宏;遗留 `RDP_RECORD` 同步宏保留) |
| 队列与入队/丢弃 | `rdp_record.cpp:472-546`(`EnqueueBatch`:字节/批数上限、整批丢弃、峰值统计) |
| 写线程主循环 | `rdp_record.cpp:548-637`(drain、凑批、分块提交、异常兜底、Windows `BELOW_NORMAL` 优先级) |
| 批量事务/实体构造 | `rdp_record.cpp:367-455`(`InsertEntities`/`FlushKind`:实体在写线程构造、每批一个事务) |
| SQLite 初始化 | `rdp_record.cpp:247-344`(WAL/sync/cache/busy_timeout;`CreateTable` 去 AUTOINCREMENT);`parameters.h:50-92` 可调宏 |
| 生命周期/统计/重连 | `rdp_record.cpp:661-820`(`Init` 启动写线程、`Shutdown` 排空+检查点+打印 stats、同秒文件名后缀) |
| 业务记录点(批量入队) | `data_process.cpp:28-31,66-70,111-114,155-160`;`dot_coh.cpp:226-240`;`dot_coh_tas.cpp:69-83`;`track_init.cpp:108-118`;`track_asso.cpp:69-74,170-176,447-460,570-585,733-765,855-865`;`track_asso_tas.cpp:42-47,128-136,263-276,370-390,510-580` |
| 验收脚本/证据 | `build/analysis/verify/`(`run_acceptance.ps1`、`latency_eval.py`、`check_record_db.py`、`failure_injection.ps1`);`build/analysis/optim_baseline.md`、`build/analysis/exports_*.txt`、`build/analysis/build_matrix_final.log` |
## 附录 B:实测数据明细
### B.1 微基准(`build/analysis/bench_micro.md` + `build/perf_bench_micro/`)
- `build/analysis/bench_micro.md`:微基准报告(单条/批量/构造/SQLite 配置、3~5 次运行中位数与 min~max);
- `build/perf_bench_micro/bench_record.cpp`:单条/批量/构造基准(链接现有 `rdp_record.lib`);
- `build/perf_bench_micro/bench_sqlite.cpp`:16 种 SQLite 配置 + 原生文件操作基准(链接现有 `sqlite3.c.obj`);
- `results/`(含 `run1..run5`):逐行/逐批原始 CSV、环境信息、SQLite 编译选项、写后只读校验。
### B.2 端到端 A/B(`build/perf_bench_pipeline/`)
- `driver.cpp`:只使用公开 DLL 接口的合成数据驱动(TWS,100 波位/圈、150 点/帧、200 目标、相扫判圈);
- `run_off/`、`run_on/`:两套 DLL 的运行目录(各含 `frames.csv`、`circles.csv`、`summary.txt`);
- `run_on/rdp_records/*.db`:记录库(可对比行数/体积);
- `build/analysis/bench_pipeline.md`:A/B 报告。
### B.3 关键换算
- 首轮 A/B(未起批,`run_pilot_off|run_pilot_on`):全运行 103,374 行 → ≈97.6 s 增量 → **≈0.94 ms/行**(环境空闲轮次;并行压测时约 1.0 ms/行);
- 最终 A/B(186 航迹):314,388 行 → 426.7 s → **≈1.39 ms/行**(35.32 MB 库);
- 首轮 A/B 每圈记录数:`RdpRecPoint` 15,000/圈 + 凝聚 ~695/圈 + 临时航迹 ~1,534/圈 → 约 1.72×10⁴ 行/圈 → 16.8 s/圈;
- 微基准(3 次运行中位数):单条 mean 1.22~1.33 ms/行、p50 0.73~0.78 ms;批量 1000 = 3.8~8.8 µs/行;
- 微基准构造:`RdpRecTrustTrack` 133.56 µs/行、`RdpRecTempTrack` 19.44 µs/行。
## 附录 C:术语
| 术语 | 说明 |
|---|---|
| 帧/波位 | 一次 `data_preprocess` 调用;TWS 一圈含 `B` 个波位 |
| model | `data_preprocess` 返回值:1=TWS 满圈、2=TWS 蓄数据、3=TAS、0=无效 |
| `N` | 一次 TWS 关联批的凝聚点数(整圈累积) |
| `M` | 可靠航迹数(`trust_track.size()`,≤500) |
| 隐式事务 | 单条 SQL 在 SQLite autocommit 模式下自动开启/提交的事务(每条记录一次 journal 生命周期) |
| 批处理 | 把多行收集到一个 `vector` 后用一次 `BEGIN/COMMIT` 写入 |
| 异步写线程 | 处理线程只入队,独立线程负责序列化与批量入库 |
| 近失候选 | 未通过关联门限但 `d_min` 接近门限的候选(用于分析门限灵敏度) |
+792
View File
@@ -0,0 +1,792 @@
# 航迹起始(Track Init)详细逻辑说明
> 适用范围:`data_process_class_dll/track_init.cpp` / `track_init.h`(类 `Track_Init`),TWS 通道逻辑法航迹起始。
> 关联文件:`kalman.cpp`(滤波与统计距离)、`coor_trans.cpp`(坐标转换)、`track_index_mangement.cpp`(航迹号分配)、`data_process.cpp`(调用与参数)。
> 本文按**当前源码实际行为**梳理,行号均指当前版本文件行号;与 `README.md` 第 8.3 / 9.3 节存在差异之处,见第 12 节。
---
## 目录
1. [模块定位与调用时机](#1-模块定位与调用时机)
2. [数据结构与状态容器](#2-数据结构与状态容器)
3. [处理流程总览](#3-处理流程总览)
4. [阶段一:输入装填与圈数计数](#4-阶段一输入装填与圈数计数)
5. [阶段二:点迹与临时航迹关联](#5-阶段二点迹与临时航迹关联)
6. [阶段三:点迹与航迹头关联](#6-阶段三点迹与航迹头关联)
7. [阶段四:点迹消费与航迹头生成](#7-阶段四点迹消费与航迹头生成)
8. [阶段五:临时航迹转可靠航迹(起批)](#8-阶段五临时航迹转可靠航迹起批)
9. [阶段六:临时航迹消亡](#9-阶段六临时航迹消亡)
10. [起始条件汇总表](#10-起始条件汇总表)
11. [关键算法细节](#11-关键算法细节)
12. [与其他文档/实现的差异](#12-与其他文档实现的差异)
13. [参数与调参](#13-参数与调参)
14. [输出字段映射](#14-输出字段映射)
15. [已知问题与风险点](#15-已知问题与风险点)
16. [代码索引](#16-代码索引)
---
## 1. 模块定位与调用时机
### 1.1 在整体流程中的位置
航迹起始处于 TWS 处理链的**末端**:只有「当圈未被任何可靠航迹关联上」的剩余点迹才会进入起始流程。
```mermaid
flowchart LR
A["data_preprocess<br/>点迹入 Data_buffer"] --> B["dot_coh(model=2)<br/>两帧滑窗凝聚"]
B --> C["track_asso(model=1)<br/>IMM + EKF 点航关联<br/>已用点从 point_recv 删除"]
C --> D["track_die<br/>可靠航迹消亡"]
D --> E["<b>track_init</b><br/>逻辑法航迹起始"]
E --> F["Trust_Track_Output<br/>(起批航迹 + 历史点)"]
```
调用点(`data_process.cpp`):
```cpp
// track_process()(data_process.cpp L167 起),仅 model==1(TWS 满一圈)时执行
track_asso.track_asso_process(&point_recv, &trust_track, ...); // 入口删除 Use_Flag==1 的点
track_die.track_die_process(&trust_track, ...);
track_init.track_init_process_logic(&point_recv, &trust_track, &temp_track,
Trust_Track_Output, Trust_track_num_Output);
```
> 工作参数为**全局对象 `rdp_parameter`**(`struct.h` 中 `extern` 声明、`data_process.cpp` 定义并零初始化),由公开接口 `parameters_reload(配置文件路径)` 从 TOML 配置文件加载;各模块直接以 `rdp_parameter.<field>` 访问,不再作为函数形参逐层传递(参数来源见 README 第 10 节;改造前为宿主下发 `RadarPara` + `parameters.h` 宏,更早则由 `const RDP_parameter&` 形参传入)。
### 1.2 调用频率 = 扫描圈
`data_preprocess` 判为一圈完成时返回 1(`data_process.cpp`),宿主据此调用 `track_process(model=1)`。判据由配置项 `[radar_params] scan_mode` **运行时**选择(0 相扫 / 1 机扫;替代原编译期 `PHASE_SCANNING`/`MECHANICAL_SCANNING` 分支):**scan_mode=0** 比较前后两帧方位波位号(当前 ≤ 上一帧即满圈,`last_beam_num` 参与判断,清空后/首帧即满圈);**scan_mode=1** 判 `Data_Input[0].Beam_index_aiz == 0`(详见 README 7.2 与 `docs/data_process_flow_report.md` 2.2 节)。
因此 **`track_init_process_logic()` 每次调用对应 TWS 一圈**,`Temp_track::buff_round`(已缓存圈数)正是以「调用次数」为单位累加的。
### 1.3 类接口
| 成员 | 可见性 | 作用 |
|---|---|---|
| `track_init_process_logic(...)` | public | 起始主流程(唯一入口) |
| `reset()` | public | 清空内部点迹缓存 + 复位航迹号分配器 |
| `point_temp_track_asso(...)` | private | 点迹 ↔ ≥2 点临时航迹关联 |
| `point_track_head_asso(...)` | private | 点迹 ↔ 1 点航迹头关联 |
| `tmp_track_to_trust_track(...)` | private | 收集 → 去重 → 起批 → 输出 |
| `tmp_track_die(...)` | private | 临时航迹消亡 |
| `alpha_cal_track_init(...)` | private | 三点折角(度) |
| `track_init_prohibit(...)` | private | 起批屏蔽区判断 |
| `track_index_mangement` | private | 航迹号(1~`track_index_max`,默认 500)分配器 |
> `temp_track`(`std::vector<std::vector<Temp_track>>`)**不是** `Track_Init` 的成员,而是 `Data_Process::temp_track`,由调用方传入并跨圈保持;`track_clear_all()` 中清空(`data_process.cpp` L233),`Track_Init::reset()` 只清自己的 `point_process`。
---
## 2. 数据结构与状态容器
### 2.1 Temp_track(临时航迹点,`struct.h`)
| 字段 | 含义 | 起始流程中的作用 |
|---|---|---|
| `X[4]` | 4 维状态 **[x, vx, y, vy]** | EKF 一步预测的状态;由两点卡尔曼初始化填充 |
| `P[4][4]` | 状态协方差 | 同上的协方差 |
| `r / azi / vr / height / SNR / RCS / amplitude` | 该点的极坐标测量 | 初始化、输出、条件判据(`azi` 为**弧度**) |
| `CPI_time` / `GNSS_time` | CPI 时间 / GNSS 时间(ms) | ΔT 计算(除 1000 转秒) |
| `d` | 该点关联时的马氏统计距离 | 去重阶段用于「保留更优者」 |
| `buff_round` | **已缓存圈数** | 生命周期与关联时机控制(见 4.2) |
| `asso_flag` | 0 未关联 / 1 已关联(待删除)/ 2 去重淘汰 | 临时状态标记 |
| `pitch_num`、`speed_dim[129]`、`range_dim[8]` | 俯仰波位、十字星数据 | 纯透传 |
| `temp_track_id` / `point_id` / `circle_id` | 临时航迹 ID / 末关联点迹 ID / 处理圈 ID(c3c7e66 新增) | 数据记录链路标识;起批时透传给 `Trust_Track` |
| `quality` | **航迹质量(0~1,V1.5.8 新增)** | 每延长一点即重算(见 8.6);起批时取末点值作为初始质量,并参与起批质量门限判断 |
| `temp_track_section_idx` | 临时航迹区号 | 相关赋值已被注释停用,当前不参与逻辑 |
> 字段更名提示(c3c7e66):`snr`→`SNR`、`Amp`→`amplitude`、`T`→`CPI_time`、`Temp_track_section_idx`→`temp_track_section_idx`。阅读早期提交或旧文档时注意对应关系。
>
> ⚠ **下标陷阱**:`Temp_track::X` 是 4 维 `[x, vx, y, vy]`(y 在 index 2),而 `Trust_Track::X` 是 6 维 `[x, vx, ax, y, vy, ay]`(y 在 index 3)。两套结构混用下标必错。
### 2.2 容器语义
| 容器 | 语义 |
|---|---|
| `temp_track`(外层 vector) | 「临时航迹表」:外层每一项 = 一条临时航迹,**下标是不稳定的位置号**(擦除后会重排);内层 vector = 该航迹按时间正序的点序列 |
| `point_process`(`Track_Init` 成员) | 本次调用待处理的点迹工作副本 |
| `point_recv`(`Data_Process` 成员) | TWS 点迹文件:凝聚输出 → 被 `track_asso` 消费 → 剩余点进入 `track_init` → 被清空 |
### 2.3 输入与输出
| 参数 | 方向 | 说明 |
|---|---|---|
| `point_recv` | 输入/输出 | 输入:当圈未被可靠航迹关联的点迹;**输出:被清空**(`track_init.cpp` L118) |
| `trust_track` | 输入/输出 | 可靠航迹表;起批成功的临时航迹 `push_back` 到此 |
| `temp_track` | 输入/输出 | 临时航迹表(跨圈状态) |
| `Trust_Track_Output[MAX_TRACK_NUM][10]` | 输出 | 与 `track_asso` **共用同一数组与同一计数**;起始流程在 `track_asso` 已写入的行之后追加新航迹的历史点 |
| `Trust_track_num_Output` | 输出 | 累计行数(track_asso 输出 N 行后,track_init 在其后继续追加) |
| 全局 `rdp_parameter` | 读取 | 工作参数(TOML 配置加载的全局对象,`struct.h` 声明、`data_process.cpp` 定义);**不再经函数形参传入**,模块直接访问 |
> **输出行数核算**:`track_asso` 先为当圈**有更新**的可靠航迹写 N 行(输出循环判 `Track_Update_Flag==1` 并清零,即只含关联到实点或成功外推的航迹);`track_die` 随后删除 D 条(已写出的行与计数不变);起始流程每次起批前判 `trust_track->size() < track_index_max`(配置项,默认 500),故最多再追加 `499 − N + D` 行,**合计最多 499 + D 行**。正常情况下 D 很小,远小于 500;但理论上当 D ≥ 2 且当圈起批数量恰好占满配额(需数百条同时起批)时,行号会超过第一维容量 500,存在越界写入风险(极端场景,见 15.14)。第二维 `[j]`(j < L ≤ 10)始终安全。代价是可靠航迹数达 `track_index_max` 时不再允许新起批。
---
## 3. 处理流程总览
```mermaid
flowchart TD
S["track_init_process_logic 入口"] --> A1["① 取出 point_recv → point_process"]
A1 --> A2["② temp_track 全部点的 buff_round += 1"]
A2 --> Q{"point_process 空?"}
Q -- 是 --> DIE["⑥ tmp_track_die"]
Q -- 否 --> B["③ point_temp_track_asso<br/>点 ×(≥2 点临时航迹)"]
B --> C["④ point_track_head_asso<br/>点 ×(1 点航迹头)"]
C --> E["⑤ 删除 Use_Flag==1 的点<br/>剩余点 → 新建航迹头(buff_round=1)"]
E --> F["⑦ tmp_track_to_trust_track<br/>收集 → 去重 → 起批 → 输出"]
F --> G["清空 point_process / point_recv"]
G --> DIE
DIE --> R["return 0"]
```
要点顺序(不可颠倒):
1. **buff_round 先自增,再做关联** —— 这正是「同一圈内不重复关联同一条临时航迹」的实现方式(见 4.2)。
2. **先关联 ≥2 点临时航迹,再关联 1 点航迹头** —— 前者优先,但两者**不互斥**:同一圈可同时被两边关联(点迹可以既延续长航迹,又延续航迹头)。
3. **关联结果统一用 `Use_Flag` 标记,最后一次性物理删除** —— 只有 `Use_Flag==0` 的剩余点才会变成新航迹头。
4. **转可靠航迹在航迹头生成之后** —— 因此当圈新建的航迹头(1 点)不参与当圈起批。
5. **消亡在最后执行** —— 且即使当圈无剩余点迹(跳过 ③~⑦)也照常执行。
---
## 4. 阶段一:输入装填与圈数计数
### 4.1 装填(`track_init.cpp` L36-38)
```cpp
for (size_t i=0;i<(*point_recv).size();i++)
point_process.push_back((*point_recv)[i]); // 全量拷贝,不检查 Use_Flag
```
- 上游 `track_asso` 已把关联上的点从 `point_recv` 中删除,所以这里进来的都是「未关联点」;函数内部**不再**用 `Use_Flag` 过滤输入(`Use_Flag` 只用于本次调用内部两阶段关联的标记)。
- 点迹的 `Range` 已在凝聚阶段做过量程过滤(下限/上限为配置项 `point_cohesion_range_min` / `point_cohesion_range_max`,默认 100 m / 100000 m)。
### 4.2 buff_round 自增(L40-47)
```cpp
for (每一条临时航迹 j) (*temp_track)[i][j].buff_round += 1;
```
含义与后果(关键机制):
| 动作 | 效果 |
|---|---|
| 新建点(航迹头 / 新关联点) | `buff_round = 1` |
| 每经过一次 `track_init` 调用(一圈) | 该航迹内**所有**点的 `buff_round` +1;关联判据只看**末点** |
| 关联判据要求 `末点.buff_round >= 2` | 等价于:**上一圈加入的点才能被本圈延续** ⇒ 同一条临时航迹每圈最多吸收一个点(除分裂,见 5.5) |
| 消亡判据 `末点.buff_round > 2` | 等价于:**连续 2 圈没有新点**就消亡(允许中间空 1 圈,最大关联间隔 2 圈) |
> 时间戳只在关联时用 `ΔT = (T_point − T_末点)/1000` 秒表示,**不参与生命周期判断**;生命周期完全由「圈数」驱动。这也意味着临时航迹的存活时间与扫描周期成正比,与航迹的绝对时间无关。
**生命周期时间轴(以 scan k 为起点)**
```text
scan k : 新点加入 → 末点 buff_round = 1 (本圈不能再关联)
scan k+1 : 自增 → 2 → 允许关联;若关联成功 → 新点 1;若失败 → 2 > 2 不成立,存活
scan k+2 : 自增 → 3 → 仍然允许关联;若成功 → 新点 1;若失败 → 3 > 2 成立,消亡
```
---
## 5. 阶段二:点迹与临时航迹关联
函数:`point_temp_track_asso()`(L145-304)。双重循环:外层点迹、内层临时航迹,**无一对一竞争约束**。
### 5.1 参与资格(L164)
```cpp
if( (*temp_track)[j].size() > 1 && (*temp_track)[j][L-1].buff_round >= 2 )
```
- 只处理 **≥2 点** 的临时航迹(1 点的是航迹头,留给阶段三);
- 末点必须来自历史圈次。
### 5.2 关联判据(L167-221)
设点迹量测(`Z[3]`):
$$
Z=\begin{bmatrix} r_p \\ \theta_p \\ v_{r,p}\end{bmatrix},\qquad
\mathbf{X}_{k-1}=\text{末点状态}[x,v_x,y,v_y],\qquad
\Delta T=\frac{T_p-T_{\text{末点}}}{1000}\ \text{(s)}
$$
**前置硬门限(L191)**:`|r_p − r_末点| >= 5` —— 距离差小于 5 m 的点直接不参与(连 d 都不算)。
**统计距离(L194-195)**:调用 `kalman::d_cal_track_init_EKF(Z, X, P, ΔT, prt, freq_ind)` 得到 3 维量测的马氏距离平方 `d`(详见 11.2)。
**折角(L199-205)**:取末点前一点 → 末点 → 新点,计算折角 `α`(度,详见 11.3)。
**三条件同时成立(L221)**:
$$
\boxed{\;d^2 \le \text{TRACK\_START\_THRESHOLD}^2=64
\quad\wedge\quad
\alpha < \text{ALPHA\_START}=60^\circ
\quad\wedge\quad
v_{r,p}\cdot v_{r,\text{末点}} > 0\;}
$$
> ⚠ 由于 `d_cal_track_init_EKF` 返回的 `d` **本身就是马氏距离平方**,而判据又写作 `d*d <= 64`(即 `d <= 8`),实际生效的门限是「马氏距离平方 ≤ 8」。3 自由度卡方分布 95% 分位为 7.815,因此该写法恰好给出约 95.4% 的置信门限(详见 11.2 与 15.6)。
### 5.3 记录关联(L223-231)
命中后向 `asso_info` 记录 `(point_idx=i+1, track_idx=j+1, d)`,同时:
- `末点.asso_flag = 1` → 标记**原航迹**待删除;
- `point_process[i].Use_Flag = 1` → 标记该点已被消费。
索引在整个扫描循环中保持稳定(循环期间不修改两个容器),因此后面的 1-based 索引回查是安全的。
### 5.4 生成新临时航迹(L237-281)
对每一条关联记录:**复制原航迹的全部 L 个点**(并把各点 `asso_flag` 清 0)、再**追加关联上的新点**,作为一条**新的临时航迹** push 到 `temp_track` 末尾。
新点字段:极坐标测量、幅度、SNR、RCS、时间、俯仰波位、十字星数据全套透传;`asso_flag=0`、`buff_round=1`、`d = asso_info[i].d`;
状态与协方差由 **两点卡尔曼初始化** 得到(L268-279):
```cpp
Z0 = (末点.r·cos(末点.azi), 末点.r·sin(末点.azi)); // 原航迹末点
Z1 = (点.Range·cos(点.Azimuth), 点.Range·sin(点.Azimuth));
T = (点.CPI_Time − 末点.T)/1000.0;
Kalman.kalman_filter_init_2dots(Z0, Z1, T, X, P); // 4 维 CV 初始化
```
### 5.5 航迹分裂(重要特性)
关联循环**不做一对一分配**:
- 一个点若满足多条临时航迹的判据,会为**每一条**各自复制出一条新航迹 —— 一条母航迹可以派生多条子航迹;
- 一条临时航迹若被多个点同时命中,也会**各自复制**成多条子航迹(每个子航迹只带一个点);
- 因此同一圈内可能产生大量同源临时航迹,靠第 8.3 节的**去重**收敛。
### 5.6 删除被关联的原航迹(L283-303)
遍历 `temp_track`,凡 `size()>=2` 且 **末点 `asso_flag==1`** 者整体 `erase`(每次擦除后 `Iter = begin()` 重扫,O(n²) 但结果正确;1 点航迹不在此处理)。新生成的子航迹末点 `asso_flag==0`,不会被误删。
---
## 6. 阶段三:点迹与航迹头关联
函数:`point_track_head_asso()`(L306-445)。这是**两点起始**:航迹头 + 1 个点 → 2 点临时航迹。
### 6.1 参与资格(L325)
```cpp
if((*temp_track)[j].size()==1 && (*temp_track)[j][0].buff_round >= 2)
```
只处理 **1 点**且该点来自历史圈次的航迹头。
### 6.2 关联判据(L328-372)
点迹与航迹头都转直角坐标后取**平面距离**:
$$
x_p = r_p\cos\theta_p,\quad y_p=r_p\sin\theta_p;\qquad
x_h=\text{头.X}[0],\quad y_h=\text{头.X}[2]
$$
$$
dis=\sqrt{(x_p-x_h)^2+(y_p-y_h)^2},\qquad \Delta T=\frac{T_p-T_h}{1000}\ \text{(s)}
$$
**当前生效的判据(L368-372)**:
$$
\boxed{\;
dis \le V_{max}\Delta T
\;\wedge\;
dis \ge V_{min}\Delta T
\;\wedge\;
v_{r,p}\cdot v_{r,h} > 0
\;\wedge\;
dis \ge |v_{r,p}|\,\Delta T
\;\wedge\;
dis \ge |v_{r,h}|\,\Delta T\;}
$$
各条件的物理含义:
| 条件 | 含义 |
|---|---|
| `dis ≤ V_max·ΔT` | 平均速度不超过最大速度(`V_max = rdp_parameter.track_init_velocity_max`,配置项) |
| `dis ≥ V_min·ΔT` | 平均速度不低于最小速度(滤除静止/慢速点) |
| `v_r,p · v_r,h > 0` | 两点多普勒径向速度**同号**(同接近/同远离) |
| `dis ≥ abs(v_r,p)·ΔT` | 位置变化不小于**点迹自身多普勒**在这段时间内的径向位移(多普勒-几何一致性) |
| `dis ≥ abs(v_r,h)·ΔT` | 同上,用航迹头的多普勒约束 |
**近/远程分支(L348-356)当前是死分支**:`work_mode==0` 与 `else` 都取 `vmax = rdp_parameter.track_init_velocity_max`(注释"近程模式最大速度减小一点"未生效;原按 `V_MAX/5` 收紧的写法仅保留在未启用的 `track_init_direct_tracking.cpp` 注释里)。
**ΔT 语义**:判据隐含要求 `ΔT > 0`(若 `ΔT < 0`,则 `dis ≤ 负数` 必假)。`ΔT == 0` 时仅在 `dis == 0`(同时间同位置的点)才可能成立,属退化情形。
**与文档差异**:源码中被注释停用的旧判据按距离分段收紧门限(`r<1000` 时半速、中远程 `r<2000` 时 1/2~1/3 速),见第 12 节。
### 6.3 命中后的处理(L374-427)
- 记录 `(point_idx, track_idx)`,标记 `头.asso_flag=1`、`point_process[i].Use_Flag=1`;
- 逐条生成 2 点临时航迹:**第 1 点 = 原航迹头**(`asso_flag` 清 0),**第 2 点 = 新点**(全套测量透传,`buff_round=1`,`asso_flag=0`,**`d` 未赋值,保持 0**);
- 同样调用 `kalman_filter_init_2dots` 得到 4 维状态/协方差(`Z0` = 头点、`Z1` = 新点,`T = (点.CPI_Time − 头.T)/1000`)。
### 6.4 删除被关联的航迹头(L429-443)
凡 `[0].asso_flag==1` 的 1 点航迹头整条 `erase`(同样每次重扫)。
注意:本阶段遍历的是**全部** `temp_track`,其中由阶段二新建的 ≥2 点航迹因 `size()!=1` 不会被误处理。
---
## 7. 阶段四:点迹消费与航迹头生成
### 7.1 删除已消费点迹(L57-70)
```cpp
if((*Iter).Use_Flag==1) { point_process.erase(Iter); Iter = point_process.begin(); }
```
物理删除所有被阶段二或阶段三消费的点(两阶段共用同一 `Use_Flag`)。
> 结果:一个点迹**要么**参与延续已有临时航迹/航迹头(可同时延续两者),**要么**成为新航迹头,二者互斥。这保证不会「用同一个点既延续又起新头」。
### 7.2 剩余点回写(L72-75,等效空操作)
```cpp
std::vector<PointRecv>().swap((*point_recv));
for (...) (*point_recv).push_back(point_process[i]); // 回写未被使用的点
```
紧接着 L117-118 又把 `point_process` 与 `point_recv` 双双清空,因此这段回写**不产生任何对外效果**(`point_recv` 最终必然为空),属历史残留代码。
### 7.3 剩余点转航迹头(L77-114)
对每个剩余点构造 1 点 `Temp_track`:
| 字段 | 取值 |
|---|---|
| `r / azi / height / amplitude / vr / SNR / RCS` | 点迹对应量(`azi` 为弧度) |
| `pitch_num / speed_dim / range_dim` | 透传 |
| `X[0] = Range·cos(Azimuth)`、`X[1]=0`、`X[2] = Range·sin(Azimuth)`、`X[3]=0` | 位置赋值,速度置 0(**注意 4 维布局**) |
| `CPI_time`、`GNSS_time` | 点迹时间 |
| `temp_track_id = temp_track_id++`、`point_id`、`circle_id` | 取自该点迹(记录链路标识,c3c7e66 新增) |
| `asso_flag = 0`、`buff_round = 1` | 生命周期起点 |
| `d` | 未赋值 → 0 |
---
## 8. 阶段五:临时航迹转可靠航迹(起批)
函数:`tmp_track_to_trust_track()`(L458-671),四步。
### 8.1 步骤 1:收集候选(L467-499)
```cpp
int L = (*Iter).size();
double range = (*Iter)[L-1].r;
double azi = (*Iter)[L-1].azi/PI*180; // 转度,供屏蔽区比较
double quality = (*Iter)[L-1].quality; // V1.5.8:末点航迹质量
if( L <= 10 && L >= rdp_parameter.track_init_point_num_min
&& track_init_prohibit(range, azi) == 0
&& quality >= 按长度取的质量门限 ) // V1.5.8 新增
{ 复制到 Track_to_start; 从 temp_track 中 erase; }
else
Iter++;
```
**入选条件**:
1. **长度**:`track_init_point_num_min ≤ L ≤ 10`(配置项 `[track_init] track_init_point_num_min`,由 TOML 配置文件提供,加载器校验范围 **3~9**;改造前由宿主经 `track_process_parameters_initial` 传入);
2. **不在起批屏蔽区**内(用**末点**的距离/方位判断,见 8.5);
3. **航迹质量达标(V1.5.8,d9a7bd0)**:按长度取门限 `track_init_quality_L3~L7`(配置项)——L=3 →0.7、L=4 →0.6、L=5 →0.5、L=6 →0.4、L≥7 →0.3;比较对象是**末点** `quality`,不达标则本圈不起批、留在 `temp_track` 继续积累。
命中即从 `temp_track` 摘出(先复制后删除,`Track_to_start` 成为候选表)。
> 结果:**一旦长度达到阈值、不落在屏蔽区且末点质量达标,当圈即起批**(没有"连续 M 圈满足才起批"的确认期)。若因屏蔽区被拦住,航迹会继续在 `temp_track` 中增长,直到离开屏蔽区(此时 `L > track_init_point_num_min`,见 8.3 的副作用)或达到 `L=10` 被判消亡;若因质量门限被拦住,则继续积累直到质量回升或临时航迹消亡。
### 8.2 步骤 2:同源去重(L504-550)
```cpp
int L = rdp_parameter.track_init_point_num_min; // 注意:此 L 是"起批点数",不是航迹长度
for (i: 0 .. size-2) for (j: i+1 .. size-1) {
if (两者都未被标记 asso_flag==2) {
if (某条长度 < 3) continue; // 防御性判断(实际不会触发)
if (前三点中存在一点 "T 与 r 完全相同") { // 硬编码比较点 0/1/2
if (Track_to_start[i][L-1].d < Track_to_start[j][L-1].d) j 标记 asso_flag=2;
else i 标记 asso_flag=2;
}
}
}
// 然后统一删除 [0].asso_flag==2 的航迹
```
- **同源判定**:两条候选航迹的前三个点中,只要有一个点的 `T` 与 `r` 完全相等(同一点迹被复制而来),就视为同源重复;
- **择优准则**:比较 `[L-1].d`(第 `track_init_point_num_min` 个点的关联统计距离),**保留 d 更小者**;
- **标记位置**:只写 `[0].asso_flag = 2`,最后统一擦除;
- 去重仅看**前三点**,且无论航迹实际多长都用固定下标 0/1/2。
### 8.3 步骤 3:起批与构造可靠航迹(L552-667)
```cpp
if (Track_to_start[i].size() < 3) continue; // 防御
if (trust_track->size() < rdp_parameter.track_index_max) { // 默认 500 条上限
int index = track_index_mangement.track_ind_get(trust_track);
if (index > 0) { ... }
}
```
**三点卡尔曼初始化**(L563-571,注意使用的是 `L-3, L-2, L-1`,即**最后三个点**):
```cpp
Z0 = 极坐标→直角(点[L-3]); Z1 = (点[L-2]); Z2 = (点[L-1]);
T1 = (点[L-2].T − 点[L-3].T)/1000.0; T2 = (点[L-1].T − 点[L-2].T)/1000.0;
Kalman.kalman_filter_init_3dots(Z0, Z1, Z2, T1, T2, X, P); // 6 维 [x,vx,ax,y,vy,ay]
```
> ⚠ **不一致点**:初始化用「最后三点」,而输出历史点(8.4)只写「前 `L` 个点」。当航迹长度 = `track_init_point_num_min` 时两者一致;当因屏蔽区滞留使长度 > 阈值时,输出的历史点里**不包含**用于初始化的那三个点。
**Trust_Track 字段构造(L573-608)**:
| 字段 | 赋值 |
|---|---|
| `X[6] / P[6][6]` | 三点初始化结果 |
| `X1/X2/X3 = X`、`P1/P2/P3 = P` | 三个 IMM 子模型同状态、同协方差初始化 |
| `u[0..2] = 0.3333` | 模型概率均分 |
| `Track_Index` | `track_ind_get()` 分配的航迹号 |
| `T_track` / `GNSS_time` / `Height` | 取**末点**的对应值(`CPI_time` 字段) |
| `Amplitude / snr_point / RCS / pitch_num / speed_dim / range_dim` | 末点透传 |
| `track_id / point_id / circle_id` | 取末点的 `temp_track_id / point_id / circle_id`(记录链路标识,b227ae1 新增) |
| `Track_fingerprint` | **航迹指纹(V1.5.9,0f3da99)**:`computeFingerprint(末点.r, 末点.azi, 末点.vr, 末点.RCS, 末点.CPI_time)`,16 bit(量化 0.01 m / 0.001° / 0.01 m·s⁻¹ / 0.1 dBsm / 1 ms → splitmix64 混合 → 折叠 16 bit),TAS 波束与 TAS 输入均以它定位航迹(见 README 9.12) |
| `point_flag = 1` | 实点 |
| `Track_Mode = 0` | TWS |
| `Target_Type = 0` | 未确认目标(`UNCONF_TARGET` 宏已删除,直接写 0) |
| `Extrapolate_round = 0` | 外推计数清零 |
| `approach_flag = -1` | 接近标志复位 |
| `manual_delete_flag / manual_tracking_flag / associate_point_number / point_type / primary_target_flag = 0` | 各类标志复位(`Trust_Track trust_track_tmp = {}` 零初始化,故新航迹的重点目标标记为 0) |
> L606-608 有一处**死代码**:`coor_trans::cart2polar(X[0], X[3], &r, &azi)`(把 `X[3]=vy` 当 y 用),计算结果 `r/azi` 从未被使用。
**航迹号分配**(`track_index_mangement.cpp`):空表返回 1;否则从 `lastest_index+1` 向上找第一个未被占用的号(含 `track_index_max`,默认 500),找不到再从 1 回绕;全满返回 0(**不起批**,该候选被静默丢弃)。
### 8.4 输出更新航迹(L612-664)
- `*Trust_track_num_Output += 1`,在 `track_asso` 已写的行之后追加一行;
- 对 `j = 0 .. L-1`(`L = rdp_parameter.track_init_point_num_min`)写入该航迹的**历史点**:`Track_Index`、`Point_Sum = L`,以及每个点的距离/方位/俯仰、速度、幅度/SNR/RCS、俯仰波位、十字星数据、航向角、时间、直角坐标、原始点信息等(完整映射见第 14 节);
- 之后该航迹的后续更新由 `track_asso`(每次 `Point_Sum=1`)负责。
### 8.5 起批屏蔽区(L699-712)
```cpp
for (i = 0; i < rdp_parameter.track_init_prohibite_area_num; i++)
if (r < rdp_parameter.track_init_prohibite_area_range_max[i] && r > rdp_parameter.track_init_prohibite_area_range_min[i] &&
azi < rdp_parameter.track_init_prohibite_area_azimuth_max[i] && azi > rdp_parameter.track_init_prohibite_area_azimuth_min[i])
return 1; // 位于屏蔽区,禁止起批
return 0;
```
- 判据是**距离-方位矩形区域**(`azi` 已转成度),最多 30 个(`RDP_parameter` 定长数组长度;配置入口由加载器校验 `*_num ∈ [0,30]` 且 4 个数组长度等于 `*_num`,非法配置在 `parameters_reload` 阶段即失败);
- 采用**严格不等号**:恰好落在边界上的点不算屏蔽;
- 判断用**末点**极坐标,不使用高度。
### 8.6 临时航迹质量评估与起批门限(V1.5.8,1ae0cf2 / d9a7bd0)
在阶段二(点-临时航迹关联)每成功延长一个点后,`point_temp_track_asso()` 立即重算该临时航迹末点的质量:
```cpp
L = temp_track_ref.size();
d_tmp = exp(-temp_track_ref[L-1].d); // 末点统计距离项(d 已是马氏距离平方)
// L == 3:取三个点 SNR 的平均;L > 3:只取末点 SNR
snr_tmp = 1 / (1 + exp(-(SNR - 16) / 1.2)); // 以 16 dB 为界的 Sigmoid
// 权重为配置项 track_quality_d_weight(0.5) / track_quality_snr_weight(0.3)
quality_tmp = (w_d * d_tmp + w_snr * snr_tmp) / (w_d + w_snr); // 只用距离 + SNR 两项(不含长度项)
if (L == 3) 三点全部写 quality_tmp; // 质量起点
else 末点 quality = 前一点 quality * c + quality_tmp * (1 - c); // 指数平滑,c = track_quality_iter_coeff(0.8)
```
- `Temp_track::quality` 是 **0~1** 的归一化值;起批时 `Trust_Track.Quality = 末点 quality`,`Point_sum = Valid_point_sum = L`;
- 起批门限按**临时航迹长度 L** 取(比较对象是**末点**质量):L=3 → 0.7、L=4 → 0.6、L=5 → 0.5、L=6 → 0.4、L≥7 → 0.3;
- 质量不达标时该临时航迹**不进入 `Track_to_start`**,继续留在 `temp_track` 中参与下一圈关联(受 `buff_round`/长度 10 的消亡约束)。
> 注意:质量只在「点-临时航迹关联」路径更新,阶段三(点 × 1 点航迹头 → 2 点临时航迹)不写 `quality`;2 点航迹要到下一圈被延长成 3 点时才会首次获得质量值,这与其本来也不能起批(长度不足)是一致的。
---
## 9. 阶段六:临时航迹消亡
函数:`tmp_track_die()`(L673-697):
```cpp
if (n <= 0) erase; // 空航迹
if ((*Iter)[n-1].buff_round > 2 || n >= 10) erase;
```
| 消亡条件 | 含义 |
|---|---|
| 序列为空 | 防御性清理 |
| 末点 `buff_round > 2` | 连续 2 圈没有新点(允许空 1 圈) |
| 长度 `n >= 10` | 达到上限仍未完成起批(例如长期滞留屏蔽区) |
消亡**不输出任何信息**(与可靠航迹消亡不同,起批失败的临时航迹对宿主不可见)。
---
## 10. 起始条件汇总表
### 10.1 条件链(一条 3 点起批航迹的完整判据)
| 阶段 | 判据(源码位置) |
|---|---|
| **0. 输入资格** | 该点未被 `track_asso` 关联(已从 `point_recv` 删除);凝聚后 `100 m ≤ r ≤ 100000 m` |
| **1. 生成航迹头** | 未被阶段二/三消费(`Use_Flag==0`);创建时 `buff_round=1`(L77-114) |
| **2. 航迹头 → 2 点**(L325, L368-372) | 航迹头 `size==1` 且 `buff_round ≥ 2`;`dis ≤ track_init_velocity_max·ΔT`;`dis ≥ track_init_velocity_min·ΔT`;`dis ≥ abs(vr_p)·ΔT`;`dis ≥ abs(vr_h)·ΔT`;`vr_p·vr_h > 0`(隐含 `ΔT > 0`) |
| **3. 2 点 → 3 点及以上**(L164, L191, L221) | 临时航迹 `size > 1` 且末点 `buff_round ≥ 2`;`abs(r_p − r_末点) ≥ 5 m`;`d ≤ 8`(代码写 `d² ≤ 64`,`track_init_threshold=8`);`α < 60°`(`track_init_angle`);`vr_p·vr_末点 > 0` |
| **4. 转可靠航迹**(L484, L556) | `track_init_point_num_min ≤ L ≤ 10`;末点不在起批屏蔽区;**末点质量 ≥ 按 L 取的门限(V1.5.8)**;同源去重后保留;`trust_track.size() < track_index_max` 且成功分配航迹号 |
| **5. 消亡**(L686) | `末点.buff_round > 2`(连续 2 圈无新点)或 `长度 ≥ 10` |
### 10.2 门限汇总
| 判据 | 数值 | 来源 | 可调 |
|---|---|---|---|
| 最大速度 `vmax` | 50 m/s(样例) | 配置项 `track_init.track_init_velocity_max` | ✅(改配置文件) |
| 最小速度 `vmin` | 1 m/s(样例) | 配置项 `track_init.track_init_velocity_min` | ✅(改配置文件) |
| 距离差下限 `abs(Δr)` | **5 m(硬编码)** | `track_init.cpp` L191 | ❌ |
| 起批波门 `d ≤ 8` | `track_init_threshold=8` | 配置项 `track_init.track_init_threshold` | ✅(改配置文件) |
| 起批折角 | `track_init_angle=60°` | 配置项 `track_init.track_init_angle` | ✅(改配置文件) |
| 起批点数 | 3~9(典型 3~4) | 配置项 `track_init.track_init_point_num_min` | ✅(加载器校验 3~9) |
| 临时航迹长度上限 | 10(硬编码) | L484 / L686 | ❌ |
| 允许丢圈数 | 1 圈(`buff_round > 2` 消亡) | L686 | ❌ |
| 航迹头上限等待 | 2 圈 | L686(1 点航迹同判据) | ❌ |
| 起批屏蔽区 | ≤30 个矩形 | 配置段 `[track_init_prohibite_area]`(数组长度 = `*_num`) | ✅(加载器校验 0~30) |
| 起批最低质量(V1.5.8) | L3→0.7 / L4→0.6 / L5→0.5 / L6→0.4 / L≥7→0.3 | 配置项 `track_init.track_init_quality_L3~L7` | ✅(改配置文件) |
| 质量权重(V1.5.8) | 距离 0.5 / SNR 0.3 / 长度 0.2 | 配置段 `[track_quality]` | ✅(改配置文件) |
| 航迹数上限 | 500(默认) | 配置项 `track_index.track_index_max` | ✅(加载器校验 1~500) |
---
## 11. 关键算法细节
### 11.1 两点卡尔曼初始化(`kalman.cpp` L18-58)
输入两个直角坐标量测 `Z0, Z1` 与时间差 `T`(秒):
$$
X=\begin{bmatrix}x_1\\ (x_1-x_0)/T\\ y_1\\ (y_1-y_0)/T\end{bmatrix},
\qquad
P_{axis}=\begin{bmatrix}R & R/T\\ R/T & 2R/T^2\end{bmatrix}
$$
其中 `R` 取 `Z1` 处的**转换量测协方差**(极坐标→直角坐标,见 `README.md` 9.1 节),x/y 两轴独立填充 `P[4][4]`。
> 风险:`T → 0` 时速度与协方差同时发散,函数无除零保护(对应 BUG-18);临时航迹的两点初始化 `T` 来自相邻两圈,实际由时间戳决定,若宿主给出重复时间戳会异常。
### 11.2 EKF 统计距离(`kalman.cpp` L615-693)
对 4 维状态做 CV 一步预测(`F = [[1,T,0,0],[0,1,0,0],[0,0,1,T],[0,0,0,1]]`,过程噪声 `Q = 0.03²·I`,`G=[T²/2, T]ᵀ`):
$$
\hat X_{k|k-1}=F X,\qquad P_{k|k-1}=FPF^{T}+GQG^{T}
$$
非线性量测函数(距离-方位-径向速度):
$$
h(\hat X)=\begin{bmatrix}\sqrt{x^2+y^2}\\ \operatorname{atan2}(y,x)\bmod 2\pi\\ -\dfrac{x v_x+y v_y}{\sqrt{x^2+y^2}}\end{bmatrix},
\qquad H=\left.\frac{\partial h}{\partial X}\right|_{\hat X_{k|k-1}}
$$
量测噪声 `R = diag(σ_r², σ_a², σ_v²) = diag(10², 0.02², 2²)`,新息协方差 `S = H P Hᵀ + R`,残差做两项修正:
- **方位环绕**:`ν_θ = wrapToPi(θ_p − θ_pred)`,归一化到 `[−π, π)`;
- **多普勒盲速折叠**:`ν_v ← ν_v − round(ν_v / v_bind)·v_bind`,其中
$$
v_{bind}=\frac{150000}{f_{GHz}\cdot PRI_{\mu s}}\ \text{(m/s)},
\qquad f_{GHz}=9.2+0.02\cdot f_{ind}
$$
(`PRI ≤ 0` 或 `f ≤ 0` 时返回安全值 1.0;调用方约定 PRI 单位 µs,不允许为 0。)
返回马氏距离平方:
$$
d=\nu^{T}S^{-1}\nu,\qquad \nu=Z-h(\hat X_{k|k-1})
$$
**调用参数来源**:`prt = point.PRF_index`(实际存的是 `DataRev.PRI`,单位 µs);`freq_ind = point.Freq_index`。
### 11.3 折角计算(L448-456)
$$
\alpha=\arccos\!\left(\frac{\vec D_{12}\cdot\vec D_{23}}{\|\vec D_{12}\|\,\|\vec D_{23}\|}\right)\cdot\frac{180}{\pi}
$$
其中 `D12 = (末点 − 前一点)`、`D23 = (新点 − 末点)`,三点均取 **Temp_track 的直角坐标**(`X[0]` 为 x、`X[2]` 为 y)。`α` 越小表示越接近直线;`track_init_angle`(配置项,样例 60°)表示允许的航向偏折。
> 无 `acos` 定义域钳位:当两点重合(分母为 0)或浮点误差使比值略超 ±1 时结果为 `NaN`,`NaN < 60` 恒为 false → 表现为「该点无法关联」,不崩溃。
---
## 12. 与其他文档/实现的差异
### 12.1 与 `README.md` 第 8.3 / 9.3 节的差异(README 已同步本节结论)
> 说明:下表为本文档成文时发现的差异,当时 README 尚未同步 7d5a56f 的改动;随后已按本节结论更新 README 第 2、7.2、8.1、8.3、9.3、9.6、10.2 等节,「README 描述」列对应的是更新前的旧表述,保留以说明改动缘由。
| 项 | README 描述(更新前) | 当前源码 |
|---|---|---|
| 点-航迹头判据 | 按距离分段:近程 `r≥1000` 全速 / `r<1000` 半速;中远程 `r≥2000` 全速 / `r<2000` 1/2~1/3 速 | 该分支已**整体注释停用**,改为统一 `track_init_velocity_min·ΔT ≤ dis ≤ track_init_velocity_max·ΔT` **外加两条多普勒一致性条件** `dis ≥ abs(vr_p)·ΔT`、`dis ≥ abs(vr_h)·ΔT`(L361-372) |
| 起批触发长度 | 长度满足 `track_init_point_num_min ≤ L ≤ 10` | 一致 |
| 初始化用点 | 最近三点 | 不一致:取下标 `track_init_point_num_min-3 ~ -1` 的三个点,航迹长度大于起批点数时**不是**航迹真正的末三点(详见第 15 节第 2 条) |
| 输出历史点 | "将临时航迹的全部 L 个点写入" | 实际只写**前 `track_init_point_num_min` 个点**(L614 的 `L` 是起批点数,不是航迹长度) |
| use_flag 过滤 | "track_init 跳过 Use_Flag==1 的点" | 上游 `track_asso_process()` 已删除 `Use_Flag==1` 的点迹,`track_init` 入口(L59-70)也会再剔除一次,两道处理叠加,描述成立 |
### 12.2 与 `track_init_direct_tracking.cpp`(引导跟踪版,未启用)的差异
| 项 | `track_init.cpp`(TWS,启用) | `track_init_direct_tracking.cpp`(未启用) |
|---|---|---|
| 入口 | 完整实现 | `track_init_process_logic` 空实现(L11-23) |
| 点-航迹头判据 | 统一门限 + 两条多普勒一致性条件 | 仍为分距离段门限(原 `V_MAX/5` 亦在其注释中) |
| 起批长度 | `L ≥ track_init_point_num_min`(长度可大于阈值) | `L == track_init_point_num_min`(严格相等) |
| 去重 | 有同源去重 | 无 |
| 屏蔽区 | 有 | 无 |
| 航迹号 | `track_ind_get()` 分配 | 外部传入 `track_ID` |
| 消亡阈值 | `buff_round > 2` | `buff_round > 1`(更严格) |
| 输出模式 | `Track_Mode = 0`(TWS) | `Track_Mode = 1`(TAS) |
---
## 13. 参数与调参
> **参数配置方式改造后**:与起始流程相关的全部可调参数都在 TOML 配置文件里(样例 `config/config_tmp.toml`),由公开接口 `parameters_reload(配置文件路径)` 加载到 `RDP_parameter`;`parameters.h` 不再有这些宏,宿主也不再下发 `RadarPara`。改参数 = 改配置文件后重新调用 `parameters_reload()`(加载失败时参数保持不变)。完整参数表与严格校验规则见 README 第 10 节。
### 13.1 配置项(样例值,均为「配置段.键」)
| 配置项 | 样例值 | 含义 | 调大效果 |
|---|---|---|---|
| `track_init.track_init_threshold` | 8 | 起批波门,实际生效为 `d ≤ 8` | **更容易起批**(虚警增多) |
| `track_init.track_init_angle` | 60 | 起批折角(度) | 更容易起批(允许更急的转向) |
| `track_associate.sigma_range/azimuth/pitch/velocity` | 10 / 0.02 / 0.2 / 2.0 | EKF 量测噪声 | 影响 `S` 与 `d`,进而影响关联难度 |
| `track_index.track_index_max` | 500 | 航迹号/航迹数上限(1~500) | — |
| `cohesion.point_cohesion_range_min/max` | 100 / 100000 | 有效量程(凝聚阶段过滤) | — |
| `track_init.track_init_point_num_min` | 3 | 起批点数(加载器校验 3~9):调大 → 起批更慢、更抗虚警,但机动目标捕获更迟 | |
| `track_init.track_init_velocity_max/min` | 50 / 1 | 头-点关联的速度区间;`min` 调大可滤除静止/慢速点 | |
| `radar_params.radar_work_mode` | 0 | 当前对起始流程**无实际影响**(近/远程分支取值相同) | |
| `track_init_prohibite_area.*_num` + 4 个数组 | 0 + 空数组 | 起批屏蔽区(距离 m / 方位 度;≤30 组,数组长度 = `*_num`) | |
| `track_init.track_init_quality_L3~L7` | 0.7 / 0.6 / 0.5 / 0.4 / 0.3 | 按临时航迹长度的起批最低质量(V1.5.8) | 降低 → 更容易起批 |
| `track_quality.*` | 0.5 / 0.3 / 0.2 / 0.8 | 质量权重、平滑系数与衰减系数(V1.5.8) | 影响质量评估与消亡速度 |
> 旧 `RadarPara` 中的 `track_start_threshold` 字段原本就未被实现读取;新结构体只保留实现真正使用/保留的参数(见 README 10.3 对照表)。
### 13.2 调参直觉
- **虚警多、起批太容易**:先降 `track_init_threshold`(8 → 5 左右),再降 `track_init_angle`;必要时提高 `track_init_point_num_min`。
- **真实目标起批慢**:提高 `track_init_threshold` / `track_init_angle`,或降低 `track_init_point_num_min`(最小 3)。
- **静止/低速杂波起批**:提高 `track_init_velocity_min`。
- **某方位扇区/距离段禁止起批**(如地物、干扰区):配置起批屏蔽区,比调门限更精准。
- **注意**:`config/config_tmp.toml` 是相扫取值(`scan_mode = 0`);机扫部署时需配 `scan_mode = 1`,且 `extrapolate_max_tas`、`height_smooth_win`、`track_die_round_tas`、`tas_track_num_max` 按机扫取值配置(见 README 4.2、10.1)。
---
## 14. 输出字段映射
对每条新起批航迹的每个历史点 `j = 0 .. track_init_point_num_min-1`(L614-664):
| 输出字段 | 来源 | 备注 |
|---|---|---|
| `Track_Index` | 新分配的航迹号 | 同一行内所有点相同 |
| `Point_Sum` | `= track_init_point_num_min` | 与 `track_asso` 输出的 `Point_Sum=1` 可作区分 |
| `Range` / `Azimuth` | 点的 `r` / `azi`(→度) | 极坐标 |
| `Elevation` | `asin(h/r)`(度,钳位 ±1) | BUG-27 修复后的定义域钳位 |
| `z` | 点的 `height` | |
| `Range_V` | `sqrt(X[1]² + X[3]²)` = `sqrt(vx²+vy²)` | 速度**模值**(非径向速度) |
| `Direction_Angle` | `atan2(vy, vx)`(→度, `[0,360)`) | 航向角 |
| `x / v_x / y / v_y` | 点的 `X[0..3]` | 注意 4 维布局 |
| `range_point / azi_point / elev_point / vr_point` | 点的原始测量 | `point_type=0` |
| `Amplitude / track_snr / track_rcs` | 点的 `amplitude / SNR / RCS` | |
| `pitch_num / speed_dim / range_dim` | 点透传 | |
| `track_time` | `CPI_time/1000`(秒) | |
| `GNSS_time` | 点透传 | |
| `Flag_Point` | 恒为 **1**(实点) | |
| `Track_Mode` | 恒为 **0**(TWS) | |
| `Quality` | **该历史点的临时航迹质量 × 100**(V1.5.8) | 内部 `quality` 为 0~1,输出折算为 0~100;同一条航迹的各个历史点可能取值不同(L=3 时三点相同,之后只有末点更新) |
| **未赋值** | `Flag_Direction`、`x_Predict/y_Predict/z_Predict`、`Range_Predict/Azimuth_Predict/Elevation_Predict`、`Target_Type`、`prf_point`、`v_z`、`amp_asv_point`、`prn_fftn_point` | DLL 不清零该数组,宿主读到的是其自身缓存的旧值/未初始化值。注意 `Flag_Direction` 只在关联输出路径赋值(`track_asso.cpp` / `track_asso_tas.cpp`),起批输出不写该字段。该字段的判据已由 f249fdc 的 `vr_point > 0 ? 1 : 0`(实测径向速度,受多普勒速度模糊影响符号可能取反)改为 fc49707 + 42b598e 的**航向-位置夹角法**:`Direction_Angle = atan2(X[4], X[1])`(弧度转度后同时输出到 `Direction_Angle`),`x`、`y` 为经配置项 `radar_system_delay` 补偿后的位置、`r` 为其模,`acos((-x·cos(Direction_Angle) - y·sin(Direction_Angle))/r) < π/2` 判为靠近(1)、否则远离(0),即滤波速度方向与"雷达→目标"位置方向夹角超 90° 判为接近。另 `amp_asv_point` / `prn_fftn_point` 属 e8c56f9 新增,`data_preprocess()` **现已**把 `DataRev.amp_asv / prn_fftn` 搬入 `PointRecv`(TWS 与 TAS 分支均赋值,并随 `RdpRecPoint` 落库),因此 TWS 关联/输出路径的该字段已有有效来源;TAS 关联与输出路径(`track_asso_tas.cpp`)仍未涉及这两个字段,TAS 输出行中它们保持宿主原值 |
---
## 15. 已知问题与风险点
> 以下为阅读源码得到的观察,**不代表已确认的缺陷**,部分为有意保留的历史行为;修改前请结合实测数据评估。
1. **硬编码 5 m 距离差门限**(L191):`|r_p − r_末点| < 5` 的点永不参与关联。对切向飞行、远距离慢速目标可能造成漏关联(起批失败)。
2. **初始化用点不是航迹真正的末三点**(L505、L563-565 vs L614):起批流程中 `L = track_init_point_num_min`(L505 覆盖了前面循环里的航迹长度变量),Z0/Z1/Z2 取下标 `L-3 / L-2 / L-1`,输出循环也只写到 `j = L-1`。因此当航迹长度 N 大于起批点数时,参与初始化的只是**前 L 个点中的最后三点**,而不是航迹真正的末三点;第 L 个点之后的尾部点既未参与初始化也不出现在输出中(这些点通常来自滞留在起批屏蔽区期间的积累)。
3. **去重只比较前三个点**(L517-519 硬编码 0/1/2),且优度指标取 `[track_init_point_num_min-1].d`(长度大于阈值时非末点);若 `track_init_point_num_min > 3`,共享前三点但后续分叉的两条航迹会被误判为同源。
4. **无一对一关联约束**:一个点可同时为多条临时航迹续点(阶段二/三),产生航迹分裂,虚警场景下临时航迹表可能快速膨胀(靠去重与消亡收敛)。
5. **ΔT ≤ 0 无显式校验**(BUG-33 遗留):头-点关联靠 `dis ≤ track_init_velocity_max·ΔT` 隐式排除负 ΔT;EKF 关联路径 `d_cal_track_init_EKF(ΔT<0)` 会得到无物理意义的结果,只是通常会被 `d ≤ 8` 挡住。
6. **判据对马氏距离平方二次平方**(L221):`track_init_threshold=8` 时实际门限为 `d ≤ 8`(3 自由度约 95.4%)。若按字面 `d² ≤ 64` 理解会误判为 `d ≤ 64`(极宽松)。修改 `track_init_threshold` 时务必记得这一层。
7. **两点/三点初始化无 `T > 0` 保护**(BUG-18):`T` 极小(重复/乱序时间戳)时速度、加速度与协方差会爆炸性放大。
8. **死代码**:L72-75 的 `point_recv` 回写随即被 L117-118 清空;L606-608 的 `cart2polar` 结果未使用;L348-356 的近/远程分支取值相同。
9. **`acos` 无定义域钳位**(L452):浮点越界产生 `NaN`,表现为条件恒假(静默丢弃该关联),不报错也不崩溃。
10. **航迹数饱和静默丢弃**:`trust_track.size() ≥ track_index_max`(配置项,默认 500)或 `track_ind_get()` 返回 0 时,候选被直接跳过且该临时航迹已从 `temp_track` 摘除 → 起批机会永久丢失。
11. **未赋值输出字段**(第 14 节):宿主若直接使用 `x_Predict`、`Flag_Direction`、`Target_Type` 等字段,会读到非本次写入的数据。
12. **`PI` 为 float 常量**(3.1415926f)参与双精度运算,角度换算存在约 1e-7 量级偏差,属既有实现风格。
13. **精度/量纲提醒**:`Temp_track::vr` 直接取点的 `Velocity`(多普勒)作为径向速度,EKF 关联时用于残差折叠(盲速 `v_bind`)与符号判据;若宿主的 `Velocity` 存在模糊,符号判据 `vr_p·vr_末点 > 0` 可能失效。
14. **输出数组行号可能越界(极端场景)**:容量判据用 `trust_track->size() < track_index_max`,而 `Trust_Track_Output` 的行计数已在 `track_asso` 阶段按「消亡前」的航迹数推进,中间的 `track_die` 删除 D 条后腾出的配额会让起始流程多写 D 行;当 D ≥ 2 且当圈起批数占满配额时,写入行号可超过 `MAX_TRACK_NUM=500`(详见 2.3)。建议把判据改为同时校验 `*Trust_track_num_Output < MAX_TRACK_NUM` 与 `trust_track->size() < track_index_max`。
15. **起批质量门限比较的是「末点」质量而非整条航迹质量(V1.5.8)**:`tmp_track_to_trust_track()` 取 `(*Iter)[L-1].quality` 与按长度取的门限比较,而该质量在 L>3 时只是末点的指数平滑值(L=3 时三点同值),并不代表整条临时航迹的平均质量;长度越大门限越低,实际是「用更宽松的门限校验最新一点」。
16. **起批质量来源依赖阶段二**:`quality` 只在「点-临时航迹关联」成功后更新,阶段三产生的 2 点临时航迹不带质量值;长度 3 的临时航迹在第三次延长时写入三点同值,因此其起批质量有定义。若将来把起批点数放宽到 3 以下(当前加载器校验 3~9),则会出现质量恒为 0、永远无法通过质量门限的情况。
---
## 16. 代码索引
### 16.1 `track_init.cpp`
| 行号 | 内容 |
|---|---|
| L11-143 | `track_init_process_logic` 主流程 |
| L36-38 | 装填点迹 |
| L40-47 | `buff_round` 自增 |
| L49 | `if(point_process.size()>0)` 主分支 |
| L52 / L55 | 两阶段关联调用 |
| L57-70 | 删除已消费点迹 |
| L72-75 | 点迹回写(等效空操作) |
| L77-114 | 剩余点 → 航迹头 |
| L117-118 | 清空 `point_process` / `point_recv` |
| L121 | 转可靠航迹调用 |
| L126 | 临时航迹消亡调用 |
| L145-304 | `point_temp_track_asso` |
| L164 | ≥2 点 + `buff_round≥2` 资格 |
| L191 | `abs(Δr) ≥ 5 m` 门限 |
| L194-195 | EKF 统计距离 `d` |
| L199-205 | 折角 `α` |
| L221 | 三条件判据 |
| L237-281 | 生成新临时航迹(两点初始化) |
| L283-303 | 删除被关联的原航迹 |
| L306-445 | `point_track_head_asso` |
| L325 | 1 点 + `buff_round≥2` 资格 |
| L361-372 | 门限判据(含停用的旧分支注释) |
| L388-427 | 生成 2 点临时航迹 |
| L429-443 | 删除被关联的航迹头 |
| L448-456 | `alpha_cal_track_init` |
| L458-671 | `tmp_track_to_trust_track` |
| L484 | 起批长度 + 屏蔽区条件 |
| L504-550 | 同源去重 |
| L552-667 | 起批与输出 |
| L563-571 | 三点卡尔曼初始化 |
| L573-604 | `Trust_Track` 构造 |
| L612-664 | 输出历史点 |
| L673-697 | `tmp_track_die` |
| L699-712 | `track_init_prohibit` |
| L714-723 | 构造 / `reset()` |
### 16.2 依赖
| 文件 | 函数 | 用途 |
|---|---|---|
| `kalman.cpp` L18-58 | `kalman_filter_init_2dots` | 两点(4 维)初始化 |
| `kalman.cpp` L125-194 | `kalman_filter_init_3dots` | 三点(6 维)初始化 |
| `kalman.cpp` L615-693 | `d_cal_track_init_EKF` | 起始关联统计距离 |
| `kalman.cpp` L780-789 | `Bind_speed` | 盲速计算 |
| `coor_trans.cpp` L8-20 | `polar2cart` / `cart2polar` | 坐标转换 |
| `track_index_mangement.cpp` L14-77 | `track_ind_get` | 航迹号分配 |
| `data_process.cpp` L22 | `data_preprocess` | 一圈完成 → `model=1` |
| `data_process.cpp` L167 起 | `track_process` | 起始流程调用点(L187-201 调用 `track_asso`/`track_die`/`track_init`) |
| `data_process.cpp` L246-258 | 参数加载 | `parameters_reload()`:TOML 配置 → 全局对象 `rdp_parameter`(原「参数初始化/修改」已删除) |
-16
View File
@@ -1,19 +1,3 @@
# 雷达数据处理项目修改说明
## 概述
本次主要修改TAS波束控制逻辑,
原有逻辑为:
- 维护一个tas_target_queue队列,队列中每个元素可以保存一个TAS跟踪目标
- 每次调用Beam_Ctrl()函数时,判断队列首个元素是否为空(empty_flag),若不为空,则输出TAS跟踪波束信息,否则不输出
- 将队列进行循环移位,队列第一个目标移动至队尾,下一次Beam_Ctrl()调用将会跟踪下一个目标
原有逻辑的问题:
- 原有逻辑下,TAS跟踪目标的数据率(目标信息更新频率)与波束时间相关,波束时间越短,则调用Beam_Ctrl()的频率越高,TAS目标的数据率也越高
新的逻辑为保证TAS目标的数据率不变:
- 维护一个tas_target_queue队列,队列中每个元素可以保存一个TAS跟踪目标
- 每次调用Beam_Ctrl()函数时,判断队列首个元素是否为空(empty_flag),若不为空,则再判断目标的航迹时间(T_track)和当前最新时间戳(latest_timestamp)的差值,若差值大于1 / DATA_RATE_TAS,则输出TAS跟踪波束信息,否则不输出
- 将队列进行循环移位,队列第一个目标移动至队尾,下一次Beam_Ctrl()调用将会跟踪下一个目标
注意:在RadarPara结构体中新增一项:DATA_RATE_TAS(TAS模式数据率),由dll调用者输入该参数的值(track_process_parameters_initial()),上述逻辑中DATA_RATE_TAS使用Work_Parameter中的DATA_RATE_TAS参数值,而不是使用parameters.h中的DATA_RATE_TAS宏定义值。
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+35
View File
@@ -0,0 +1,35 @@
# 第三方库来源说明(ormpp / iguana / SQLite)
本目录为**按上游原样内置**的第三方源码(保留上游字节,含换行符,不做编码/格式转换),与源码目录下内置的 Eigen 3.3.7 同类处理:仓库自带依赖,离线可配置、可交付。
| 内容 | 本目录位置 | 上游 | 版本 / 标识 |
|---|---|---|---|
| ormpp(Modern C++ ORM 头文件库) | `ormpp/*.hpp` | <https://github.com/qicosmos/ormpp> | commit `acf23989f8e40f2c8671eaf209d014164f2d6319`(master,2026-08-29);`ormpp/version.hpp` = 0.2.0 |
| iguana(编译期反射,随 ormpp 内置) | `iguana/**` | 随 ormpp 内置;独立仓库 <https://github.com/qicosmos/iguana> | `iguana/version.hpp` = 1.0.5 |
| SQLite amalgamation | `thirdparty/sqlite3/{sqlite3.c,sqlite3.h,sqlite3ext.h}` | 随 ormpp 内置;<https://sqlite.org> | SQLite 3.49.1(`sqlite3.h` 的 `SQLITE_VERSION`),公共领域(Public Domain) |
| frozen(iguana 的 constexpr 容器依赖,随 ormpp 内置) | `frozen/**` | 随 ormpp 内置;独立仓库 <https://github.com/serge-sans-paille/frozen> | 18 个文件 / 86,136 字节(`iguana/field_reflection.hpp` 需要 `frozen/string.h`、`frozen/unordered_map.h`) |
| 许可证 | `LICENSE`、`iguana/LICENSE` | — | 均为 Apache License 2.0;与上游 git blob 逐字节一致(`261eeb9e…` / `8dada3ed…`) |
## 复现步骤(重新获取/升级时执行)
```bat
git -c core.autocrlf=false clone --depth 1 https://github.com/qicosmos/ormpp.git <tmp-ormpp>
git -c core.autocrlf=false clone --depth 1 https://github.com/qicosmos/iguana.git <tmp-iguana>
:: 仅拷贝以下内容到本目录(其余 tests/example/cfg/docs/lang/shell.c 等均不需要):
:: <tmp-ormpp>\ormpp\*.hpp + ormpp\pg_types.h -> third_party\ormpp\ormpp\
:: <tmp-ormpp>\iguana\ -> third_party\ormpp\iguana\
:: <tmp-ormpp>\frozen\ -> third_party\ormpp\frozen\ (iguana 依赖)
:: <tmp-ormpp>\thirdparty\sqlite3\sqlite3.c|sqlite3.h|sqlite3ext.h -> third_party\ormpp\thirdparty\sqlite3\
:: <tmp-ormpp>\LICENSE -> third_party\ormpp\LICENSE
:: <tmp-iguana>\LICENSE -> third_party\ormpp\iguana\LICENSE
```
注意:本机全局 `core.autocrlf=true` 会把源码转成 CRLF(`sqlite3.c` 由 9,461,193 字节变为 9,728,476 字节),因此克隆时必须显式 `-c core.autocrlf=false`。校验方式:与上游 blob 大小比对(`sqlite3.c` = 9,461,193、`dbng.hpp` = 11,681、`iguana/common.hpp` = 45,850)。
## 使用方式与约束
- **只被**可选目标 `rdp_record` 使用(见根目录 `CMakeLists.txt` 的 `RDP_DATA_RECORD` 选项);该目标以 **C++20** 编译(ormpp 的 `cmake/build.cmake` 强制 `CMAKE_CXX_STANDARD 20`),**雷达处理代码仍为 C++17**。
- 包含路径按上游约定给全(4 个):`third_party/ormpp`(`iguana/...`)、`third_party/ormpp/iguana`、`third_party/ormpp/ormpp`(`dbng.hpp`、`sqlite.hpp`)、`third_party/ormpp/thirdparty/sqlite3`(`sqlite3.h`)。
- 告警处理:`sqlite3.c` 以 `/w` 单独编译;首个包含第三方头的本项目文件(`data_process_class_dll/rdp_record.cpp`)用 `#pragma warning(push, 0)` / `(pop)` 隔离模板告警——这两处是 README 4.5「0 告警」的唯一例外,范围仅限本目录代码。
- **升级方式**:整目录替换为新的上游快照,同步更新本文件的 commit / 版本号 / blob 校验值,然后必须重新验证:`-DRDP_DATA_RECORD=ON` 构建通过(0 告警)+ 记录功能写入校验(见 `docs/data_record_debug.md`)。
+12
View File
@@ -0,0 +1,12 @@
target_sources(frozen-headers INTERFACE
"${prefix}/frozen/algorithm.h"
"${prefix}/frozen/map.h"
"${prefix}/frozen/random.h"
"${prefix}/frozen/set.h"
"${prefix}/frozen/string.h"
"${prefix}/frozen/unordered_map.h"
"${prefix}/frozen/unordered_set.h"
"${prefix}/frozen/bits/algorithms.h"
"${prefix}/frozen/bits/basic_types.h"
"${prefix}/frozen/bits/elsa.h"
"${prefix}/frozen/bits/pmh.h")
+203
View File
@@ -0,0 +1,203 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_ALGORITHM_H
#define FROZEN_LETITGO_ALGORITHM_H
#include "frozen/bits/basic_types.h"
#include "frozen/bits/version.h"
#include "frozen/string.h"
namespace frozen {
// 'search' implementation if C++17 is not available
// https://en.cppreference.com/w/cpp/algorithm/search
template <class ForwardIterator, class Searcher>
ForwardIterator search(ForwardIterator first, ForwardIterator last,
const Searcher &searcher) {
return searcher(first, last).first;
}
// text book implementation from
// https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
template <std::size_t size>
class knuth_morris_pratt_searcher {
bits::carray<std::ptrdiff_t, size> step_;
bits::carray<char, size> needle_;
static constexpr bits::carray<std::ptrdiff_t, size> build_kmp_cache(
char const (&needle)[size + 1]) {
std::ptrdiff_t cnd = 0;
bits::carray<std::ptrdiff_t, size> cache;
cache.fill(-1);
for (std::size_t pos = 1; pos < size; ++pos) {
if (needle[pos] == needle[cnd]) {
cache[pos] = cache[cnd];
cnd += 1;
}
else {
cache[pos] = cnd;
cnd = cache[cnd];
while (cnd >= 0 && needle[pos] != needle[cnd]) cnd = cache[cnd];
cnd += 1;
}
}
return cache;
}
public:
constexpr knuth_morris_pratt_searcher(char const (&needle)[size + 1])
: step_{build_kmp_cache(needle)}, needle_(needle) {}
template <class ForwardIterator>
constexpr std::pair<ForwardIterator, ForwardIterator> operator()(
ForwardIterator first, ForwardIterator last) const {
std::size_t i = 0;
ForwardIterator iter = first;
while (iter != last) {
if (needle_[i] == *iter) {
if (i == (size - 1))
return {iter - i, iter - i + size};
++i;
++iter;
}
else {
if (step_[i] > -1) {
i = step_[i];
}
else {
++iter;
i = 0;
}
}
}
return {last, last};
}
};
template <std::size_t N>
constexpr knuth_morris_pratt_searcher<N - 1> make_knuth_morris_pratt_searcher(
char const (&needle)[N]) {
return {needle};
}
// text book implementation from
// https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm
template <std::size_t size>
class boyer_moore_searcher {
using skip_table_type = bits::carray<std::ptrdiff_t, sizeof(char) << 8>;
using suffix_table_type = bits::carray<std::ptrdiff_t, size>;
skip_table_type skip_table_;
suffix_table_type suffix_table_;
bits::carray<char, size> needle_;
constexpr auto build_skip_table(char const (&needle)[size + 1]) {
skip_table_type skip_table;
skip_table.fill(size);
for (std::size_t i = 0; i < size - 1; ++i) skip_table[needle[i]] -= i + 1;
return skip_table;
}
constexpr bool is_prefix(char const (&needle)[size + 1], std::size_t pos) {
std::size_t suffixlen = size - pos;
for (std::size_t i = 0; i < suffixlen; i++) {
if (needle[i] != needle[pos + i])
return false;
}
return true;
}
constexpr std::size_t suffix_length(char const (&needle)[size + 1],
std::size_t pos) {
// increment suffix length slen to the first mismatch or beginning
// of the word
for (std::size_t slen = 0; slen < pos; slen++)
if (needle[pos - slen] != needle[size - 1 - slen])
return slen;
return pos;
}
constexpr auto build_suffix_table(char const (&needle)[size + 1]) {
suffix_table_type suffix;
std::ptrdiff_t last_prefix_index = size - 1;
// first loop
for (std::ptrdiff_t p = size - 1; p >= 0; p--) {
if (is_prefix(needle, p + 1))
last_prefix_index = p + 1;
suffix[p] = last_prefix_index + (size - 1 - p);
}
// second loop
for (std::size_t p = 0; p < size - 1; p++) {
auto slen = suffix_length(needle, p);
if (needle[p - slen] != needle[size - 1 - slen])
suffix[size - 1 - slen] = size - 1 - p + slen;
}
return suffix;
}
public:
constexpr boyer_moore_searcher(char const (&needle)[size + 1])
: skip_table_{build_skip_table(needle)},
suffix_table_{build_suffix_table(needle)},
needle_(needle) {}
template <class ForwardIterator>
constexpr std::pair<ForwardIterator, ForwardIterator> operator()(
ForwardIterator first, ForwardIterator last) const {
if (size == 0)
return {first, first + size};
ForwardIterator iter = first + size - 1;
while (iter < last) {
std::ptrdiff_t j = size - 1;
while (j > 0 && (*iter == needle_[j])) {
--iter;
--j;
}
if (*iter == needle_[0])
return {iter, iter + size};
iter += std::max(skip_table_[*iter], suffix_table_[j]);
}
return {last, last + size};
}
};
template <std::size_t N>
constexpr boyer_moore_searcher<N - 1> make_boyer_moore_searcher(
char const (&needle)[N]) {
return {needle};
}
} // namespace frozen
#endif
+240
View File
@@ -0,0 +1,240 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_BITS_ALGORITHMS_H
#define FROZEN_LETITGO_BITS_ALGORITHMS_H
#include <limits>
#include <tuple>
#include "frozen/bits/basic_types.h"
namespace frozen {
namespace bits {
auto constexpr next_highest_power_of_two(std::size_t v) {
// https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
constexpr auto trip_count = std::numeric_limits<decltype(v)>::digits;
v--;
for (std::size_t i = 1; i < trip_count; i <<= 1) v |= v >> i;
v++;
return v;
}
template <class T>
auto constexpr log(T v) {
std::size_t n = 0;
while (v > 1) {
n += 1;
v >>= 1;
}
return n;
}
constexpr std::size_t bit_weight(std::size_t n) {
return (n <= 8 * sizeof(unsigned int)) + (n <= 8 * sizeof(unsigned long)) +
(n <= 8 * sizeof(unsigned long long)) + (n <= 128);
}
unsigned int select_uint_least(std::integral_constant<std::size_t, 4>);
unsigned long select_uint_least(std::integral_constant<std::size_t, 3>);
unsigned long long select_uint_least(std::integral_constant<std::size_t, 2>);
template <std::size_t N>
unsigned long long select_uint_least(std::integral_constant<std::size_t, N>) {
static_assert(N < 2, "unsupported type size");
return {};
}
template <std::size_t N>
using select_uint_least_t = decltype(select_uint_least(
std::integral_constant<std::size_t, bit_weight(N)>()));
template <typename Iter, typename Compare>
constexpr auto min_element(Iter begin, const Iter end, Compare const &compare) {
auto result = begin;
while (begin != end) {
if (compare(*begin, *result)) {
result = begin;
}
++begin;
}
return result;
}
template <class T>
constexpr void cswap(T &a, T &b) {
auto tmp = a;
a = b;
b = tmp;
}
template <class T, class U>
constexpr void cswap(std::pair<T, U> &a, std::pair<T, U> &b) {
cswap(a.first, b.first);
cswap(a.second, b.second);
}
template <class... Tys, std::size_t... Is>
constexpr void cswap(std::tuple<Tys...> &a, std::tuple<Tys...> &b,
std::index_sequence<Is...>) {
using swallow = int[];
(void)swallow{(cswap(std::get<Is>(a), std::get<Is>(b)), 0)...};
}
template <class... Tys>
constexpr void cswap(std::tuple<Tys...> &a, std::tuple<Tys...> &b) {
cswap(a, b, std::make_index_sequence<sizeof...(Tys)>());
}
template <typename Iterator, class Compare>
constexpr Iterator partition(Iterator left, Iterator right,
Compare const &compare) {
auto pivot = left + (right - left) / 2;
auto value = *pivot;
cswap(*right, *pivot);
for (auto it = left; 0 < right - it; ++it) {
if (compare(*it, value)) {
cswap(*it, *left);
left++;
}
}
cswap(*right, *left);
return left;
}
template <typename Iterator, class Compare>
constexpr void quicksort(Iterator left, Iterator right,
Compare const &compare) {
while (0 < right - left) {
auto new_pivot = bits::partition(left, right, compare);
quicksort(left, new_pivot, compare);
left = new_pivot + 1;
}
}
template <typename T, std::size_t N, class Compare>
constexpr bits::carray<T, N> quicksort(bits::carray<T, N> const &array,
Compare const &compare) {
bits::carray<T, N> res = array;
quicksort(res.begin(), res.end() - 1, compare);
return res;
}
template <class T, class Compare>
struct LowerBound {
T const &value_;
Compare const &compare_;
constexpr LowerBound(T const &value, Compare const &compare)
: value_(value), compare_(compare) {}
template <class ForwardIt>
inline constexpr ForwardIt doit_fast(ForwardIt first,
std::integral_constant<std::size_t, 0>) {
return first;
}
template <class ForwardIt, std::size_t N>
inline constexpr ForwardIt doit_fast(ForwardIt first,
std::integral_constant<std::size_t, N>) {
auto constexpr step = N / 2;
static_assert(N / 2 == N - N / 2 - 1, "power of two minus 1");
auto it = first + step;
auto next_it = compare_(*it, value_) ? it + 1 : first;
return doit_fast(next_it, std::integral_constant<std::size_t, N / 2>{});
}
template <class ForwardIt, std::size_t N>
inline constexpr ForwardIt doitfirst(ForwardIt first,
std::integral_constant<std::size_t, N>,
std::integral_constant<bool, true>) {
return doit_fast(first, std::integral_constant<std::size_t, N>{});
}
template <class ForwardIt, std::size_t N>
inline constexpr ForwardIt doitfirst(ForwardIt first,
std::integral_constant<std::size_t, N>,
std::integral_constant<bool, false>) {
auto constexpr next_power = next_highest_power_of_two(N);
auto constexpr next_start = next_power / 2 - 1;
auto it = first + next_start;
if (compare_(*it, value_)) {
auto constexpr next = N - next_start - 1;
return doitfirst(
it + 1, std::integral_constant<std::size_t, next>{},
std::integral_constant<bool, next_highest_power_of_two(next) - 1 ==
next>{});
}
else
return doit_fast(first,
std::integral_constant<std::size_t, next_start>{});
}
template <class ForwardIt>
inline constexpr ForwardIt doitfirst(ForwardIt first,
std::integral_constant<std::size_t, 1>,
std::integral_constant<bool, false>) {
return doit_fast(first, std::integral_constant<std::size_t, 1>{});
}
};
template <std::size_t N, class ForwardIt, class T, class Compare>
constexpr ForwardIt lower_bound(ForwardIt first, const T &value,
Compare const &compare) {
return LowerBound<T, Compare>{value, compare}.doitfirst(
first, std::integral_constant<std::size_t, N>{},
std::integral_constant<bool, next_highest_power_of_two(N) - 1 == N>{});
}
template <std::size_t N, class Compare, class ForwardIt, class T>
constexpr bool binary_search(ForwardIt first, const T &value,
Compare const &compare) {
ForwardIt where = lower_bound<N>(first, value, compare);
return (!(where == first + N) && !(compare(value, *where)));
}
template <class InputIt1, class InputIt2>
constexpr bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2) {
for (; first1 != last1; ++first1, ++first2) {
if (!(*first1 == *first2)) {
return false;
}
}
return true;
}
template <class InputIt1, class InputIt2>
constexpr bool lexicographical_compare(InputIt1 first1, InputIt1 last1,
InputIt2 first2, InputIt2 last2) {
for (; (first1 != last1) && (first2 != last2); ++first1, ++first2) {
if (*first1 < *first2)
return true;
if (*first2 < *first1)
return false;
}
return (first1 == last1) && (first2 != last2);
}
} // namespace bits
} // namespace frozen
#endif
+224
View File
@@ -0,0 +1,224 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_BASIC_TYPES_H
#define FROZEN_LETITGO_BASIC_TYPES_H
#include <array>
#include <iterator>
#include <string>
#include <utility>
#include "frozen/bits/exceptions.h"
namespace frozen {
namespace bits {
// used as a fake argument for frozen::make_set and frozen::make_map in the case
// of N=0
struct ignored_arg {};
template <class T, std::size_t N>
class cvector {
T data[N] = {}; // zero-initialization for scalar type T, default-initialized
// otherwise
std::size_t dsize = 0;
public:
// Container typdefs
using value_type = T;
using reference = value_type &;
using const_reference = const value_type &;
using pointer = value_type *;
using const_pointer = const value_type *;
using iterator = pointer;
using const_iterator = const_pointer;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
// Constructors
constexpr cvector(void) = default;
constexpr cvector(size_type count, const T &value) : dsize(count) {
for (std::size_t i = 0; i < N; ++i) data[i] = value;
}
// Iterators
constexpr iterator begin() noexcept { return data; }
constexpr iterator end() noexcept { return data + dsize; }
// Capacity
constexpr size_type size() const { return dsize; }
// Element access
constexpr reference operator[](std::size_t index) { return data[index]; }
constexpr const_reference operator[](std::size_t index) const {
return data[index];
}
constexpr reference back() { return data[dsize - 1]; }
constexpr const_reference back() const { return data[dsize - 1]; }
// Modifiers
constexpr void push_back(const T &a) { data[dsize++] = a; }
constexpr void push_back(T &&a) { data[dsize++] = std::move(a); }
constexpr void pop_back() { --dsize; }
constexpr void clear() { dsize = 0; }
};
template <class T, std::size_t N>
class carray {
T data_[N] = {}; // zero-initialization for scalar type T,
// default-initialized otherwise
template <std::size_t M, std::size_t... I>
constexpr carray(T const (&init)[M], std::index_sequence<I...>)
: data_{init[I]...} {}
template <class Iter, std::size_t... I>
constexpr carray(Iter iter, std::index_sequence<I...>)
: data_{((void)I, *iter++)...} {}
public:
// Container typdefs
using value_type = T;
using reference = value_type &;
using const_reference = const value_type &;
using pointer = value_type *;
using const_pointer = const value_type *;
using iterator = pointer;
using const_iterator = const_pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
// Constructors
constexpr carray(void) = default;
template <std::size_t M>
constexpr carray(T const (&init)[M])
: carray(init, std::make_index_sequence<N>()) {
static_assert(M >= N, "Cannot initialize a carray with an smaller array");
}
template <std::size_t M>
constexpr carray(std::array<T, M> const &init)
: carray(&init[0], std::make_index_sequence<N>()) {
static_assert(M >= N, "Cannot initialize a carray with an smaller array");
}
constexpr carray(std::initializer_list<T> init)
: carray(init.begin(), std::make_index_sequence<N>()) {
// clang & gcc doesn't recognize init.size() as a constexpr
// static_assert(init.size() >= N, "Cannot initialize a carray with an
// smaller initializer list");
}
// Iterators
constexpr iterator begin() noexcept { return data_; }
constexpr const_iterator begin() const noexcept { return data_; }
constexpr const_iterator cbegin() const noexcept { return data_; }
constexpr iterator end() noexcept { return data_ + N; }
constexpr const_iterator end() const noexcept { return data_ + N; }
constexpr const_iterator cend() const noexcept { return data_ + N; }
constexpr reverse_iterator rbegin() noexcept {
return reverse_iterator(end());
}
constexpr const_reverse_iterator rbegin() const noexcept {
return const_reverse_iterator(end());
}
constexpr const_reverse_iterator crbegin() const noexcept {
return const_reverse_iterator(end());
}
constexpr reverse_iterator rend() noexcept {
return reverse_iterator(begin());
}
constexpr const_reverse_iterator rend() const noexcept {
return const_reverse_iterator(begin());
}
constexpr const_reverse_iterator crend() const noexcept {
return const_reverse_iterator(begin());
}
// Capacity
constexpr size_type size() const { return N; }
constexpr size_type max_size() const { return N; }
// Element access
constexpr reference operator[](std::size_t index) { return data_[index]; }
constexpr const_reference operator[](std::size_t index) const {
return data_[index];
}
constexpr reference at(std::size_t index) {
if (index > N)
FROZEN_THROW_OR_ABORT(
std::out_of_range("Index (" + std::to_string(index) +
") out of bound (" + std::to_string(N) + ')'));
return data_[index];
}
constexpr const_reference at(std::size_t index) const {
if (index > N)
FROZEN_THROW_OR_ABORT(
std::out_of_range("Index (" + std::to_string(index) +
") out of bound (" + std::to_string(N) + ')'));
return data_[index];
}
constexpr reference front() { return data_[0]; }
constexpr const_reference front() const { return data_[0]; }
constexpr reference back() { return data_[N - 1]; }
constexpr const_reference back() const { return data_[N - 1]; }
constexpr value_type *data() noexcept { return data_; }
constexpr const value_type *data() const noexcept { return data_; }
// Modifiers
constexpr void fill(const value_type &val) {
for (std::size_t i = 0; i < N; ++i) data_[i] = val;
}
};
template <class T>
class carray<T, 0> {
public:
// Container typdefs
using value_type = T;
using reference = value_type &;
using const_reference = const value_type &;
using pointer = value_type *;
using const_pointer = const value_type *;
using iterator = pointer;
using const_iterator = const_pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
// Constructors
constexpr carray(void) = default;
};
} // namespace bits
} // namespace frozen
#endif
+38
View File
@@ -0,0 +1,38 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_CONSTEXPR_ASSERT_H
#define FROZEN_LETITGO_CONSTEXPR_ASSERT_H
#include <cassert>
#ifdef _MSC_VER
// FIXME: find a way to implement that correctly for msvc
#define constexpr_assert(cond, msg)
#else
#define constexpr_assert(cond, msg) assert(cond &&msg);
#endif
#endif
+69
View File
@@ -0,0 +1,69 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_DEFINES_H
#define FROZEN_LETITGO_DEFINES_H
#if defined(_MSVC_LANG) && \
!(defined(__EDG__) && defined(__clang__)) // TRANSITION, VSO#273681
#define FROZEN_LETITGO_IS_MSVC
#endif
// Code taken from
// https://stackoverflow.com/questions/43639122/which-values-can-msvc-lang-have
#if defined(FROZEN_LETITGO_IS_MSVC)
#if _MSVC_LANG > 201402
#define FROZEN_LETITGO_HAS_CXX17 1
#else /* _MSVC_LANG > 201402 */
#define FROZEN_LETITGO_HAS_CXX17 0
#endif /* _MSVC_LANG > 201402 */
#else /* _MSVC_LANG etc. */
#if __cplusplus > 201402
#define FROZEN_LETITGO_HAS_CXX17 1
#else /* __cplusplus > 201402 */
#define FROZEN_LETITGO_HAS_CXX17 0
#endif /* __cplusplus > 201402 */
#endif /* _MSVC_LANG etc. */
// End if taken code
#if FROZEN_LETITGO_HAS_CXX17 == 1 && defined(FROZEN_LETITGO_IS_MSVC)
#define FROZEN_LETITGO_HAS_STRING_VIEW // We assume Visual Studio always has
// string_view in C++17
#else
#if FROZEN_LETITGO_HAS_CXX17 == 1 && __has_include(<string_view>)
#define FROZEN_LETITGO_HAS_STRING_VIEW
#endif
#endif
#ifdef __cpp_char8_t
#define FROZEN_LETITGO_HAS_CHAR8T
#endif
#if __cpp_deduction_guides >= 201703L
#define FROZEN_LETITGO_HAS_DEDUCTION_GUIDES
#endif
#if __cpp_lib_constexpr_string >= 201907L
#define FROZEN_LETITGO_HAS_CONSTEXPR_STRING
#endif
#endif // FROZEN_LETITGO_DEFINES_H
+60
View File
@@ -0,0 +1,60 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_ELSA_H
#define FROZEN_LETITGO_ELSA_H
#include <type_traits>
namespace frozen {
template <class T = void>
struct elsa {
static_assert(std::is_integral<T>::value || std::is_enum<T>::value,
"only supports integral types, specialize for other types");
constexpr std::size_t operator()(T const &value, std::size_t seed) const {
std::size_t key = seed ^ static_cast<std::size_t>(value);
key = (~key) + (key << 21); // key = (key << 21) - key - 1;
key = key ^ (key >> 24);
key = (key + (key << 3)) + (key << 8); // key * 265
key = key ^ (key >> 14);
key = (key + (key << 2)) + (key << 4); // key * 21
key = key ^ (key >> 28);
key = key + (key << 31);
return key;
}
};
template <>
struct elsa<void> {
template <class T>
constexpr std::size_t operator()(T const &value, std::size_t seed) const {
return elsa<T>{}(value, seed);
}
};
template <class T>
using anna = elsa<T>;
} // namespace frozen
#endif
+44
View File
@@ -0,0 +1,44 @@
#ifndef FROZEN_LETITGO_BITS_ELSA_STD_H
#define FROZEN_LETITGO_BITS_ELSA_STD_H
#include "elsa.h"
#include "hash_string.h"
#ifdef FROZEN_LETITGO_HAS_STRING_VIEW
#include <string_view>
#endif
#include <string>
namespace frozen {
#ifdef FROZEN_LETITGO_HAS_STRING_VIEW
template <typename CharT>
struct elsa<std::basic_string_view<CharT>> {
constexpr std::size_t operator()(
const std::basic_string_view<CharT> &value) const {
return hash_string(value);
}
constexpr std::size_t operator()(const std::basic_string_view<CharT> &value,
std::size_t seed) const {
return hash_string(value, seed);
}
};
#endif
template <typename CharT>
struct elsa<std::basic_string<CharT>> {
constexpr std::size_t operator()(
const std::basic_string<CharT> &value) const {
return hash_string(value);
}
constexpr std::size_t operator()(const std::basic_string<CharT> &value,
std::size_t seed) const {
return hash_string(value, seed);
}
};
} // namespace frozen
#endif // FROZEN_LETITGO_BITS_ELSA_STD_H
+40
View File
@@ -0,0 +1,40 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_EXCEPTIONS_H
#define FROZEN_LETITGO_EXCEPTIONS_H
#if defined(FROZEN_NO_EXCEPTIONS) || \
(defined(_MSC_VER) && !defined(_CPPUNWIND)) || \
(!defined(_MSC_VER) && !defined(__cpp_exceptions))
#include <cstdlib>
#define FROZEN_THROW_OR_ABORT(_) std::abort()
#else
#include <stdexcept>
#define FROZEN_THROW_OR_ABORT(err) throw err
#endif
#endif
+27
View File
@@ -0,0 +1,27 @@
#ifndef FROZEN_LETITGO_BITS_HASH_STRING_H
#define FROZEN_LETITGO_BITS_HASH_STRING_H
#include <cstddef>
namespace frozen {
template <typename String>
constexpr std::size_t hash_string(const String &value) {
std::size_t d = 5381;
for (const auto &c : value) d = d * 33 + static_cast<size_t>(c);
return d;
}
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
// With the lowest bits removed, based on experimental setup.
template <typename String>
constexpr std::size_t hash_string(const String &value, std::size_t seed) {
std::size_t d = (0x811c9dc5 ^ seed) * static_cast<size_t>(0x01000193);
for (const auto &c : value)
d = (d ^ static_cast<size_t>(c)) * static_cast<size_t>(0x01000193);
return d >> 8;
}
} // namespace frozen
#endif // FROZEN_LETITGO_BITS_HASH_STRING_H
+259
View File
@@ -0,0 +1,259 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// inspired from http://stevehanov.ca/blog/index.php?id=119
#ifndef FROZEN_LETITGO_PMH_H
#define FROZEN_LETITGO_PMH_H
#include <array>
#include <limits>
#include "frozen/bits/algorithms.h"
#include "frozen/bits/basic_types.h"
namespace frozen {
namespace bits {
// Function object for sorting buckets in decreasing order of size
struct bucket_size_compare {
template <typename B>
bool constexpr operator()(B const &b0, B const &b1) const {
return b0.size() > b1.size();
}
};
// Step One in pmh routine is to take all items and hash them into buckets,
// with some collisions. Then process those buckets further to build a perfect
// hash function.
// pmh_buckets represents the initial placement into buckets.
template <size_t M>
struct pmh_buckets {
// Step 0: Bucket max is 2 * sqrt M
// TODO: Come up with justification for this, should it not be O(log M)?
static constexpr auto bucket_max = 2 * (1u << (log(M) / 2));
using bucket_t = cvector<std::size_t, bucket_max>;
carray<bucket_t, M> buckets;
uint64_t seed;
// Represents a reference to a bucket. This is used because the buckets
// have to be sorted, but buckets are big, making it slower than sorting refs
struct bucket_ref {
unsigned hash;
const bucket_t *ptr;
// Forward some interface of bucket
using value_type = typename bucket_t::value_type;
using const_iterator = typename bucket_t::const_iterator;
constexpr auto size() const { return ptr->size(); }
constexpr const auto &operator[](std::size_t idx) const {
return (*ptr)[idx];
}
constexpr auto begin() const { return ptr->begin(); }
constexpr auto end() const { return ptr->end(); }
};
// Make a bucket_ref for each bucket
template <std::size_t... Is>
carray<bucket_ref, M> constexpr make_bucket_refs(
std::index_sequence<Is...>) const {
return {{bucket_ref{Is, &buckets[Is]}...}};
}
// Makes a bucket_ref for each bucket and sorts them by size
carray<bucket_ref, M> constexpr get_sorted_buckets() const {
carray<bucket_ref, M> result{
this->make_bucket_refs(std::make_index_sequence<M>())};
bits::quicksort(result.begin(), result.end() - 1, bucket_size_compare{});
return result;
}
};
template <size_t M, class Item, size_t N, class Hash, class Key, class PRG>
pmh_buckets<M> constexpr make_pmh_buckets(const carray<Item, N> &items,
Hash const &hash, Key const &key,
PRG &prg) {
using result_t = pmh_buckets<M>;
result_t result{};
bool rejected = false;
// Continue until all items are placed without exceeding bucket_max
while (1) {
for (auto &b : result.buckets) {
b.clear();
}
result.seed = prg();
rejected = false;
for (std::size_t i = 0; i < N; ++i) {
auto &bucket =
result.buckets[hash(key(items[i]), static_cast<size_t>(result.seed)) %
M];
if (bucket.size() >= result_t::bucket_max) {
rejected = true;
break;
}
bucket.push_back(i);
}
if (!rejected) {
return result;
}
}
}
// Check if an item appears in a cvector
template <class T, size_t N>
constexpr bool all_different_from(cvector<T, N> &data, T &a) {
for (std::size_t i = 0; i < data.size(); ++i)
if (data[i] == a)
return false;
return true;
}
// Represents either an index to a data item array, or a seed to be used with
// a hasher. Seed must have high bit of 1, value has high bit of zero.
struct seed_or_index {
using value_type = uint64_t;
private:
static constexpr value_type MINUS_ONE =
(std::numeric_limits<value_type>::max)();
static constexpr value_type HIGH_BIT = ~(MINUS_ONE >> 1);
value_type value_ = 0;
public:
constexpr value_type value() const { return value_; }
constexpr bool is_seed() const { return value_ & HIGH_BIT; }
constexpr seed_or_index(bool is_seed, value_type value)
: value_(is_seed ? (value | HIGH_BIT) : (value & ~HIGH_BIT)) {}
constexpr seed_or_index() = default;
constexpr seed_or_index(const seed_or_index &) = default;
constexpr seed_or_index &operator=(const seed_or_index &) = default;
};
// Represents the perfect hash function created by pmh algorithm
template <std::size_t M, class Hasher>
struct pmh_tables {
uint64_t first_seed_;
carray<seed_or_index, M> first_table_;
carray<std::size_t, M> second_table_;
Hasher hash_;
template <typename KeyType>
constexpr std::size_t lookup(const KeyType &key) const {
return lookup(key, hash_);
}
// Looks up a given key, to find its expected index in carray<Item, N>
// Always returns a valid index, must use KeyEqual test after to confirm.
template <typename KeyType, typename HasherType>
constexpr std::size_t lookup(const KeyType &key,
const HasherType &hasher) const {
auto const d =
first_table_[hasher(key, static_cast<size_t>(first_seed_)) % M];
if (!d.is_seed()) {
return static_cast<std::size_t>(d.value());
} // this is narrowing uint64 -> size_t but should be fine
else {
return second_table_[hasher(key, static_cast<std::size_t>(d.value())) %
M];
}
}
};
// Make pmh tables for given items, hash function, prg, etc.
template <std::size_t M, class Item, std::size_t N, class Hash, class Key,
class PRG>
pmh_tables<M, Hash> constexpr make_pmh_tables(const carray<Item, N> &items,
Hash const &hash, Key const &key,
PRG prg) {
// Step 1: Place all of the keys into buckets
auto step_one = make_pmh_buckets<M>(items, hash, key, prg);
// Step 2: Sort the buckets to process the ones with the most items first.
auto buckets = step_one.get_sorted_buckets();
// G becomes the first hash table in the resulting pmh function
carray<seed_or_index, M> G; // Default constructed to "index 0"
// H becomes the second hash table in the resulting pmh function
constexpr std::size_t UNUSED = (std::numeric_limits<std::size_t>::max)();
carray<std::size_t, M> H;
H.fill(UNUSED);
// Step 3: Map the items in buckets into hash tables.
for (const auto &bucket : buckets) {
auto const bsize = bucket.size();
if (bsize == 1) {
// Store index to the (single) item in G
// assert(bucket.hash == hash(key(items[bucket[0]]), step_one.seed) % M);
G[bucket.hash] = {false, static_cast<uint64_t>(bucket[0])};
}
else if (bsize > 1) {
// Repeatedly try different H of d until we find a hash function
// that places all items in the bucket into free slots
seed_or_index d{true, prg()};
cvector<std::size_t, decltype(step_one)::bucket_max> bucket_slots;
while (bucket_slots.size() < bsize) {
auto slot = hash(key(items[bucket[bucket_slots.size()]]),
static_cast<size_t>(d.value())) %
M;
if (H[slot] != UNUSED || !all_different_from(bucket_slots, slot)) {
bucket_slots.clear();
d = {true, prg()};
continue;
}
bucket_slots.push_back(slot);
}
// Put successful seed in G, and put indices to items in their slots
// assert(bucket.hash == hash(key(items[bucket[0]]), step_one.seed) % M);
G[bucket.hash] = d;
for (std::size_t i = 0; i < bsize; ++i) H[bucket_slots[i]] = bucket[i];
}
}
// Any unused entries in the H table have to get changed to zero.
// This is because hashing should not fail or return an out-of-bounds entry.
// A lookup fails after we apply user-supplied KeyEqual to the query and the
// key found by hashing. Sending such queries to zero cannot hurt.
for (std::size_t i = 0; i < M; ++i)
if (H[i] == UNUSED)
H[i] = 0;
return {step_one.seed, G, H, hash};
}
} // namespace bits
} // namespace frozen
#endif
+30
View File
@@ -0,0 +1,30 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_VERSION_H
#define FROZEN_LETITGO_VERSION_H
#define FROZEN_MAJOR_VERSION 1
#define FROZEN_MINOR_VERSION 1
#define FROZEN_PATCH_VERSION 1
#endif
+378
View File
@@ -0,0 +1,378 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_MAP_H
#define FROZEN_LETITGO_MAP_H
#include <utility>
#include "frozen/bits/algorithms.h"
#include "frozen/bits/basic_types.h"
#include "frozen/bits/constexpr_assert.h"
#include "frozen/bits/exceptions.h"
#include "frozen/bits/version.h"
namespace frozen {
namespace impl {
template <class Comparator>
class CompareKey {
Comparator const comparator_;
public:
constexpr CompareKey(Comparator const &comparator)
: comparator_(comparator) {}
template <class Key1, class Key2, class Value>
constexpr int operator()(std::pair<Key1, Value> const &self,
std::pair<Key2, Value> const &other) const {
return comparator_(std::get<0>(self), std::get<0>(other));
}
template <class Key1, class Key2, class Value>
constexpr int operator()(Key1 const &self_key,
std::pair<Key2, Value> const &other) const {
return comparator_(self_key, std::get<0>(other));
}
template <class Key1, class Key2, class Value>
constexpr int operator()(std::pair<Key1, Value> const &self,
Key2 const &other_key) const {
return comparator_(std::get<0>(self), other_key);
}
template <class Key1, class Key2>
constexpr int operator()(Key1 const &self_key, Key2 const &other_key) const {
return comparator_(self_key, other_key);
}
};
} // namespace impl
template <class Key, class Value, std::size_t N, class Compare = std::less<Key>>
class map {
using container_type = bits::carray<std::pair<Key, Value>, N>;
impl::CompareKey<Compare> less_than_;
container_type items_;
public:
using key_type = Key;
using mapped_type = Value;
using value_type = typename container_type::value_type;
using size_type = typename container_type::size_type;
using difference_type = typename container_type::difference_type;
using key_compare = decltype(less_than_);
using reference = typename container_type::reference;
using const_reference = typename container_type::const_reference;
using pointer = typename container_type::pointer;
using const_pointer = typename container_type::const_pointer;
using iterator = typename container_type::iterator;
using const_iterator = typename container_type::const_iterator;
using reverse_iterator = typename container_type::reverse_iterator;
using const_reverse_iterator =
typename container_type::const_reverse_iterator;
public:
/* constructors */
constexpr map(container_type items, Compare const &compare)
: less_than_{compare}, items_{bits::quicksort(items, less_than_)} {}
explicit constexpr map(container_type items) : map{items, Compare{}} {}
constexpr map(std::initializer_list<value_type> items, Compare const &compare)
: map{container_type{items}, compare} {
constexpr_assert(
items.size() == N,
"Inconsistent initializer_list size and type size argument");
}
constexpr map(std::initializer_list<value_type> items)
: map{items, Compare{}} {}
/* element access */
constexpr Value const &at(Key const &key) const {
return at_impl(*this, key);
}
constexpr Value &at(Key const &key) { return at_impl(*this, key); }
/* iterators */
constexpr iterator begin() { return items_.begin(); }
constexpr const_iterator begin() const { return items_.begin(); }
constexpr const_iterator cbegin() const { return items_.cbegin(); }
constexpr iterator end() { return items_.end(); }
constexpr const_iterator end() const { return items_.end(); }
constexpr const_iterator cend() const { return items_.cend(); }
constexpr reverse_iterator rbegin() { return items_.rbegin(); }
constexpr const_reverse_iterator rbegin() const { return items_.rbegin(); }
constexpr const_reverse_iterator crbegin() const { return items_.crbegin(); }
constexpr reverse_iterator rend() { return items_.rend(); }
constexpr const_reverse_iterator rend() const { return items_.rend(); }
constexpr const_reverse_iterator crend() const { return items_.crend(); }
/* capacity */
constexpr bool empty() const { return !N; }
constexpr size_type size() const { return N; }
constexpr size_type max_size() const { return N; }
/* lookup */
template <class KeyType>
constexpr std::size_t count(KeyType const &key) const {
return bits::binary_search<N>(items_.begin(), key, less_than_);
}
template <class KeyType>
constexpr const_iterator find(KeyType const &key) const {
return map::find_impl(*this, key);
}
template <class KeyType>
constexpr iterator find(KeyType const &key) {
return map::find_impl(*this, key);
}
template <class KeyType>
constexpr std::pair<const_iterator, const_iterator> equal_range(
KeyType const &key) const {
return equal_range_impl(*this, key);
}
template <class KeyType>
constexpr std::pair<iterator, iterator> equal_range(KeyType const &key) {
return equal_range_impl(*this, key);
}
template <class KeyType>
constexpr const_iterator lower_bound(KeyType const &key) const {
return lower_bound_impl(*this, key);
}
template <class KeyType>
constexpr iterator lower_bound(KeyType const &key) {
return lower_bound_impl(*this, key);
}
template <class KeyType>
constexpr const_iterator upper_bound(KeyType const &key) const {
return upper_bound_impl(*this, key);
}
template <class KeyType>
constexpr iterator upper_bound(KeyType const &key) {
return upper_bound_impl(*this, key);
}
/* observers */
constexpr const key_compare &key_comp() const { return less_than_; }
constexpr const key_compare &value_comp() const { return less_than_; }
private:
template <class This, class KeyType>
static inline constexpr auto &at_impl(This &&self, KeyType const &key) {
auto where = self.lower_bound(key);
if (where != self.end())
return where->second;
else
FROZEN_THROW_OR_ABORT(std::out_of_range("unknown key"));
}
template <class This, class KeyType>
static inline constexpr auto find_impl(This &&self, KeyType const &key) {
auto where = self.lower_bound(key);
if ((where != self.end()) && !self.less_than_(key, *where))
return where;
else
return self.end();
}
template <class This, class KeyType>
static inline constexpr auto equal_range_impl(This &&self,
KeyType const &key) {
auto lower = self.lower_bound(key);
using lower_t = decltype(lower);
if (lower == self.end())
return std::pair<lower_t, lower_t>{lower, lower};
else
return std::pair<lower_t, lower_t>{lower, lower + 1};
}
template <class This, class KeyType>
static inline constexpr auto lower_bound_impl(This &&self, KeyType const &key)
-> decltype(self.end()) {
auto where =
bits::lower_bound<N>(self.items_.begin(), key, self.less_than_);
if ((where != self.end()) && !self.less_than_(key, *where))
return where;
else
return self.end();
}
template <class This, class KeyType>
static inline constexpr auto upper_bound_impl(This &&self, KeyType const &key)
-> decltype(self.end()) {
auto where =
bits::lower_bound<N>(self.items_.begin(), key, self.less_than_);
if ((where != self.end()) && !self.less_than_(key, *where))
return where + 1;
else
return self.end();
}
};
template <class Key, class Value, class Compare>
class map<Key, Value, 0, Compare> {
using container_type = bits::carray<std::pair<Key, Value>, 0>;
impl::CompareKey<Compare> less_than_;
public:
using key_type = Key;
using mapped_type = Value;
using value_type = typename container_type::value_type;
using size_type = typename container_type::size_type;
using difference_type = typename container_type::difference_type;
using key_compare = decltype(less_than_);
using reference = typename container_type::reference;
using const_reference = typename container_type::const_reference;
using pointer = typename container_type::pointer;
using const_pointer = typename container_type::const_pointer;
using iterator = pointer;
using const_iterator = const_pointer;
using reverse_iterator = pointer;
using const_reverse_iterator = const_pointer;
public:
/* constructors */
constexpr map(const map &other) = default;
constexpr map(std::initializer_list<value_type>, Compare const &compare)
: less_than_{compare} {}
constexpr map(std::initializer_list<value_type> items)
: map{items, Compare{}} {}
/* element access */
template <class KeyType>
constexpr mapped_type at(KeyType const &) const {
FROZEN_THROW_OR_ABORT(std::out_of_range("invalid key"));
}
template <class KeyType>
constexpr mapped_type at(KeyType const &) {
FROZEN_THROW_OR_ABORT(std::out_of_range("invalid key"));
}
/* iterators */
constexpr iterator begin() { return nullptr; }
constexpr const_iterator begin() const { return nullptr; }
constexpr const_iterator cbegin() const { return nullptr; }
constexpr iterator end() { return nullptr; }
constexpr const_iterator end() const { return nullptr; }
constexpr const_iterator cend() const { return nullptr; }
constexpr reverse_iterator rbegin() { return nullptr; }
constexpr const_reverse_iterator rbegin() const { return nullptr; }
constexpr const_reverse_iterator crbegin() const { return nullptr; }
constexpr reverse_iterator rend() { return nullptr; }
constexpr const_reverse_iterator rend() const { return nullptr; }
constexpr const_reverse_iterator crend() const { return nullptr; }
/* capacity */
constexpr bool empty() const { return true; }
constexpr size_type size() const { return 0; }
constexpr size_type max_size() const { return 0; }
/* lookup */
template <class KeyType>
constexpr std::size_t count(KeyType const &) const {
return 0;
}
template <class KeyType>
constexpr const_iterator find(KeyType const &) const {
return end();
}
template <class KeyType>
constexpr iterator find(KeyType const &) {
return end();
}
template <class KeyType>
constexpr std::pair<const_iterator, const_iterator> equal_range(
KeyType const &) const {
return {end(), end()};
}
template <class KeyType>
constexpr std::pair<iterator, iterator> equal_range(KeyType const &) {
return {end(), end()};
}
template <class KeyType>
constexpr const_iterator lower_bound(KeyType const &) const {
return end();
}
template <class KeyType>
constexpr iterator lower_bound(KeyType const &) {
return end();
}
template <class KeyType>
constexpr const_iterator upper_bound(KeyType const &) const {
return end();
}
template <class KeyType>
constexpr iterator upper_bound(KeyType const &) {
return end();
}
/* observers */
constexpr key_compare const &key_comp() const { return less_than_; }
constexpr key_compare const &value_comp() const { return less_than_; }
};
template <typename T, typename U, typename Compare = std::less<T>>
constexpr auto make_map(
bits::ignored_arg =
{} /* for consistency with the initializer below for N = 0*/) {
return map<T, U, 0, Compare>{};
}
template <typename T, typename U, std::size_t N>
constexpr auto make_map(std::pair<T, U> const (&items)[N]) {
return map<T, U, N>{items};
}
template <typename T, typename U, std::size_t N>
constexpr auto make_map(std::array<std::pair<T, U>, N> const &items) {
return map<T, U, N>{items};
}
template <typename T, typename U, typename Compare, std::size_t N>
constexpr auto make_map(std::pair<T, U> const (&items)[N],
Compare const &compare = Compare{}) {
return map<T, U, N, Compare>{items, compare};
}
template <typename T, typename U, typename Compare, std::size_t N>
constexpr auto make_map(std::array<std::pair<T, U>, N> const &items,
Compare const &compare = Compare{}) {
return map<T, U, N, Compare>{items, compare};
}
} // namespace frozen
#endif
+97
View File
@@ -0,0 +1,97 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_RANDOM_H
#define FROZEN_LETITGO_RANDOM_H
#include <cstdint>
#include <type_traits>
#include "frozen/bits/algorithms.h"
#include "frozen/bits/version.h"
namespace frozen {
template <class UIntType, UIntType a, UIntType c, UIntType m>
class linear_congruential_engine {
static_assert(std::is_unsigned<UIntType>::value,
"UIntType must be an unsigned integral type");
template <class T>
static constexpr UIntType modulo(T val, std::integral_constant<UIntType, 0>) {
return static_cast<UIntType>(val);
}
template <class T, UIntType M>
static constexpr UIntType modulo(T val, std::integral_constant<UIntType, M>) {
// the static cast below may end up doing a truncation
return static_cast<UIntType>(val % M);
}
public:
using result_type = UIntType;
static constexpr result_type multiplier = a;
static constexpr result_type increment = c;
static constexpr result_type modulus = m;
static constexpr result_type default_seed = 1u;
linear_congruential_engine() = default;
constexpr linear_congruential_engine(result_type s) { seed(s); }
void seed(result_type s = default_seed) { state_ = s; }
constexpr result_type operator()() {
using uint_least_t =
bits::select_uint_least_t<bits::log(a) + bits::log(m) + 4>;
uint_least_t tmp =
static_cast<uint_least_t>(multiplier) * state_ + increment;
state_ = modulo(tmp, std::integral_constant<UIntType, modulus>());
return state_;
}
// constexpr void discard(unsigned long long n) {
// while (n--) operator()();
// }
// static constexpr result_type min() { return increment == 0u ? 1u : 0u; }
// static constexpr result_type max() { return modulus - 1u; }
friend constexpr bool operator==(linear_congruential_engine const &self,
linear_congruential_engine const &other) {
return self.state_ == other.state_;
}
friend constexpr bool operator!=(linear_congruential_engine const &self,
linear_congruential_engine const &other) {
return !(self == other);
}
private:
result_type state_ = default_seed;
};
using minstd_rand0 =
linear_congruential_engine<std::uint_fast32_t, 16807, 0, 2147483647>;
using minstd_rand =
linear_congruential_engine<std::uint_fast32_t, 48271, 0, 2147483647>;
// This generator is used by default in unordered frozen containers
using default_prg_t = minstd_rand;
} // namespace frozen
#endif
+283
View File
@@ -0,0 +1,283 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_SET_H
#define FROZEN_SET_H
#include <utility>
#include "frozen/bits/algorithms.h"
#include "frozen/bits/basic_types.h"
#include "frozen/bits/constexpr_assert.h"
#include "frozen/bits/defines.h"
#include "frozen/bits/version.h"
namespace frozen {
template <class Key, std::size_t N, class Compare = std::less<Key>>
class set {
using container_type = bits::carray<Key, N>;
Compare less_than_;
container_type keys_;
public:
/* container typedefs*/
using key_type = Key;
using value_type = Key;
using size_type = typename container_type::size_type;
using difference_type = typename container_type::size_type;
using key_compare = Compare;
using value_compare = Compare;
using reference = typename container_type::const_reference;
using const_reference = reference;
using pointer = typename container_type::const_pointer;
using const_pointer = pointer;
using iterator = typename container_type::const_iterator;
using reverse_iterator = typename container_type::const_reverse_iterator;
using const_iterator = iterator;
using const_reverse_iterator = reverse_iterator;
public:
/* constructors */
constexpr set(const set &other) = default;
constexpr set(container_type keys, Compare const &comp)
: less_than_{comp}, keys_(bits::quicksort(keys, less_than_)) {}
explicit constexpr set(container_type keys) : set{keys, Compare{}} {}
constexpr set(std::initializer_list<Key> keys, Compare const &comp)
: set{container_type{keys}, comp} {
constexpr_assert(
keys.size() == N,
"Inconsistent initializer_list size and type size argument");
}
constexpr set(std::initializer_list<Key> keys) : set{keys, Compare{}} {}
constexpr set &operator=(const set &other) = default;
/* capacity */
constexpr bool empty() const { return !N; }
constexpr size_type size() const { return N; }
constexpr size_type max_size() const { return N; }
/* lookup */
template <class KeyType>
constexpr std::size_t count(KeyType const &key) const {
return bits::binary_search<N>(keys_.begin(), key, less_than_);
}
template <class KeyType>
constexpr const_iterator find(KeyType const &key) const {
const_iterator where = lower_bound(key);
if ((where != end()) && !less_than_(key, *where))
return where;
else
return end();
}
template <class KeyType>
constexpr std::pair<const_iterator, const_iterator> equal_range(
KeyType const &key) const {
auto const lower = lower_bound(key);
if (lower == end())
return {lower, lower};
else
return {lower, lower + 1};
}
template <class KeyType>
constexpr const_iterator lower_bound(KeyType const &key) const {
auto const where = bits::lower_bound<N>(keys_.begin(), key, less_than_);
if ((where != end()) && !less_than_(key, *where))
return where;
else
return end();
}
template <class KeyType>
constexpr const_iterator upper_bound(KeyType const &key) const {
auto const where = bits::lower_bound<N>(keys_.begin(), key, less_than_);
if ((where != end()) && !less_than_(key, *where))
return where + 1;
else
return end();
}
/* observers */
constexpr const key_compare &key_comp() const { return less_than_; }
constexpr const key_compare &value_comp() const { return less_than_; }
/* iterators */
constexpr const_iterator begin() const { return keys_.begin(); }
constexpr const_iterator cbegin() const { return keys_.cbegin(); }
constexpr const_iterator end() const { return keys_.end(); }
constexpr const_iterator cend() const { return keys_.cend(); }
constexpr const_reverse_iterator rbegin() const { return keys_.rbegin(); }
constexpr const_reverse_iterator crbegin() const { return keys_.crbegin(); }
constexpr const_reverse_iterator rend() const { return keys_.rend(); }
constexpr const_reverse_iterator crend() const { return keys_.crend(); }
/* comparison */
constexpr bool operator==(set const &rhs) const {
return bits::equal(begin(), end(), rhs.begin());
}
constexpr bool operator!=(set const &rhs) const { return !(*this == rhs); }
constexpr bool operator<(set const &rhs) const {
return bits::lexicographical_compare(begin(), end(), rhs.begin(),
rhs.end());
}
constexpr bool operator<=(set const &rhs) const {
return (*this < rhs) || (*this == rhs);
}
constexpr bool operator>(set const &rhs) const {
return bits::lexicographical_compare(rhs.begin(), rhs.end(), begin(),
end());
}
constexpr bool operator>=(set const &rhs) const {
return (*this > rhs) || (*this == rhs);
}
};
template <class Key, class Compare>
class set<Key, 0, Compare> {
using container_type = bits::carray<Key, 0>; // just for the type definitions
Compare less_than_;
public:
/* container typedefs*/
using key_type = Key;
using value_type = Key;
using size_type = typename container_type::size_type;
using difference_type = typename container_type::size_type;
using key_compare = Compare;
using value_compare = Compare;
using reference = typename container_type::const_reference;
using const_reference = reference;
using pointer = typename container_type::const_pointer;
using const_pointer = pointer;
using iterator = pointer;
using reverse_iterator = pointer;
using const_iterator = const_pointer;
using const_reverse_iterator = const_pointer;
public:
/* constructors */
constexpr set(const set &other) = default;
constexpr set(bits::carray<Key, 0>, Compare const &) {}
explicit constexpr set(bits::carray<Key, 0>) {}
constexpr set(std::initializer_list<Key>, Compare const &comp)
: less_than_{comp} {}
constexpr set(std::initializer_list<Key> keys) : set{keys, Compare{}} {}
constexpr set &operator=(const set &other) = default;
/* capacity */
constexpr bool empty() const { return true; }
constexpr size_type size() const { return 0; }
constexpr size_type max_size() const { return 0; }
/* lookup */
template <class KeyType>
constexpr std::size_t count(KeyType const &) const {
return 0;
}
template <class KeyType>
constexpr const_iterator find(KeyType const &) const {
return end();
}
template <class KeyType>
constexpr std::pair<const_iterator, const_iterator> equal_range(
KeyType const &) const {
return {end(), end()};
}
template <class KeyType>
constexpr const_iterator lower_bound(KeyType const &) const {
return end();
}
template <class KeyType>
constexpr const_iterator upper_bound(KeyType const &) const {
return end();
}
/* observers */
constexpr key_compare key_comp() const { return less_than_; }
constexpr key_compare value_comp() const { return less_than_; }
/* iterators */
constexpr const_iterator begin() const { return nullptr; }
constexpr const_iterator cbegin() const { return nullptr; }
constexpr const_iterator end() const { return nullptr; }
constexpr const_iterator cend() const { return nullptr; }
constexpr const_reverse_iterator rbegin() const { return nullptr; }
constexpr const_reverse_iterator crbegin() const { return nullptr; }
constexpr const_reverse_iterator rend() const { return nullptr; }
constexpr const_reverse_iterator crend() const { return nullptr; }
};
template <typename T>
constexpr auto make_set(
bits::ignored_arg =
{} /* for consistency with the initializer below for N = 0*/) {
return set<T, 0>{};
}
template <typename T, std::size_t N>
constexpr auto make_set(const T (&args)[N]) {
return set<T, N>(args);
}
template <typename T, std::size_t N>
constexpr auto make_set(std::array<T, N> const &args) {
return set<T, N>(args);
}
template <typename T, typename Compare, std::size_t N>
constexpr auto make_set(const T (&args)[N],
Compare const &compare = Compare{}) {
return set<T, N, Compare>(args, compare);
}
template <typename T, typename Compare, std::size_t N>
constexpr auto make_set(std::array<T, N> const &args,
Compare const &compare = Compare{}) {
return set<T, N, Compare>(args, compare);
}
#ifdef FROZEN_LETITGO_HAS_DEDUCTION_GUIDES
template <class T, class... Args>
set(T, Args...) -> set<T, sizeof...(Args) + 1>;
#endif // FROZEN_LETITGO_HAS_DEDUCTION_GUIDES
} // namespace frozen
#endif
+150
View File
@@ -0,0 +1,150 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_STRING_H
#define FROZEN_LETITGO_STRING_H
#include <cstring>
#include <functional>
#include "frozen/bits/defines.h"
#include "frozen/bits/elsa.h"
#include "frozen/bits/hash_string.h"
#include "frozen/bits/version.h"
#ifdef FROZEN_LETITGO_HAS_STRING_VIEW
#include <string_view>
#endif
namespace frozen {
template <typename _CharT>
class basic_string {
using chr_t = _CharT;
chr_t const *data_;
std::size_t size_;
public:
template <std::size_t N>
constexpr basic_string(chr_t const (&data)[N]) : data_(data), size_(N - 1) {}
constexpr basic_string(chr_t const *data, std::size_t size)
: data_(data), size_(size) {}
#ifdef FROZEN_LETITGO_HAS_STRING_VIEW
constexpr basic_string(std::basic_string_view<chr_t> data)
: data_(data.data()), size_(data.size()) {}
#endif
constexpr basic_string(const basic_string &) noexcept = default;
constexpr basic_string &operator=(const basic_string &) noexcept = default;
constexpr std::size_t size() const { return size_; }
constexpr chr_t operator[](std::size_t i) const { return data_[i]; }
constexpr bool operator==(basic_string other) const {
if (size_ != other.size_)
return false;
for (std::size_t i = 0; i < size_; ++i)
if (data_[i] != other.data_[i])
return false;
return true;
}
constexpr bool operator<(const basic_string &other) const {
unsigned i = 0;
while (i < size() && i < other.size()) {
if ((*this)[i] < other[i]) {
return true;
}
if ((*this)[i] > other[i]) {
return false;
}
++i;
}
return size() < other.size();
}
constexpr const chr_t *data() const { return data_; }
constexpr const chr_t *begin() const { return data(); }
constexpr const chr_t *end() const { return data() + size(); }
};
template <typename _CharT>
struct elsa<basic_string<_CharT>> {
constexpr std::size_t operator()(basic_string<_CharT> value) const {
return hash_string(value);
}
constexpr std::size_t operator()(basic_string<_CharT> value,
std::size_t seed) const {
return hash_string(value, seed);
}
};
using string = basic_string<char>;
using wstring = basic_string<wchar_t>;
using u16string = basic_string<char16_t>;
using u32string = basic_string<char32_t>;
#ifdef FROZEN_LETITGO_HAS_CHAR8T
using u8string = basic_string<char8_t>;
#endif
namespace string_literals {
constexpr string operator"" _s(const char *data, std::size_t size) {
return {data, size};
}
constexpr wstring operator"" _s(const wchar_t *data, std::size_t size) {
return {data, size};
}
constexpr u16string operator"" _s(const char16_t *data, std::size_t size) {
return {data, size};
}
constexpr u32string operator"" _s(const char32_t *data, std::size_t size) {
return {data, size};
}
#ifdef FROZEN_LETITGO_HAS_CHAR8T
constexpr u8string operator"" _s(const char8_t *data, std::size_t size) {
return {data, size};
}
#endif
} // namespace string_literals
} // namespace frozen
namespace std {
template <typename _CharT>
struct hash<frozen::basic_string<_CharT>> {
size_t operator()(frozen::basic_string<_CharT> s) const {
return frozen::elsa<frozen::basic_string<_CharT>>{}(s);
}
};
} // namespace std
#endif
+275
View File
@@ -0,0 +1,275 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_UNORDERED_MAP_H
#define FROZEN_LETITGO_UNORDERED_MAP_H
#include <functional>
#include <tuple>
#include "frozen/bits/basic_types.h"
#include "frozen/bits/constexpr_assert.h"
#include "frozen/bits/elsa.h"
#include "frozen/bits/exceptions.h"
#include "frozen/bits/pmh.h"
#include "frozen/bits/version.h"
#include "frozen/random.h"
namespace frozen {
namespace bits {
struct GetKey {
template <class KV>
constexpr auto const &operator()(KV const &kv) const {
return kv.first;
}
};
} // namespace bits
template <class Key, class Value, std::size_t N, typename Hash = anna<Key>,
class KeyEqual = std::equal_to<Key>>
class unordered_map {
static constexpr std::size_t storage_size =
bits::next_highest_power_of_two(N) *
(N < 32 ? 2 : 1); // size adjustment to prevent high collision rate for
// small sets
using container_type = bits::carray<std::pair<Key, Value>, N>;
using tables_type = bits::pmh_tables<storage_size, Hash>;
KeyEqual const equal_;
container_type items_;
tables_type tables_;
public:
/* typedefs */
using Self = unordered_map<Key, Value, N, Hash, KeyEqual>;
using key_type = Key;
using mapped_type = Value;
using value_type = typename container_type::value_type;
using size_type = typename container_type::size_type;
using difference_type = typename container_type::difference_type;
using hasher = Hash;
using key_equal = KeyEqual;
using reference = typename container_type::reference;
using const_reference = typename container_type::const_reference;
using pointer = typename container_type::pointer;
using const_pointer = typename container_type::const_pointer;
using iterator = typename container_type::iterator;
using const_iterator = typename container_type::const_iterator;
public:
/* constructors */
unordered_map(unordered_map const &) = default;
constexpr unordered_map(container_type items, Hash const &hash,
KeyEqual const &equal)
: equal_{equal},
items_{items},
tables_{bits::make_pmh_tables<storage_size>(
items_, hash, bits::GetKey{}, default_prg_t{})} {}
explicit constexpr unordered_map(container_type items)
: unordered_map{items, Hash{}, KeyEqual{}} {}
constexpr unordered_map(std::initializer_list<value_type> items,
Hash const &hash, KeyEqual const &equal)
: unordered_map{container_type{items}, hash, equal} {
constexpr_assert(
items.size() == N,
"Inconsistent initializer_list size and type size argument");
}
constexpr unordered_map(std::initializer_list<value_type> items)
: unordered_map{items, Hash{}, KeyEqual{}} {}
/* iterators */
constexpr iterator begin() { return items_.begin(); }
constexpr iterator end() { return items_.end(); }
constexpr const_iterator begin() const { return items_.begin(); }
constexpr const_iterator end() const { return items_.end(); }
constexpr const_iterator cbegin() const { return items_.cbegin(); }
constexpr const_iterator cend() const { return items_.cend(); }
/* capacity */
constexpr bool empty() const { return !N; }
constexpr size_type size() const { return N; }
constexpr size_type max_size() const { return N; }
/* lookup */
template <class KeyType, class Hasher, class Equal>
constexpr std::size_t count(KeyType const &key, Hasher const &hash,
Equal const &equal) const {
auto const &kv = lookup(key, hash);
return equal(kv.first, key);
}
template <class KeyType>
constexpr std::size_t count(KeyType const &key) const {
return count(key, hash_function(), key_eq());
}
template <class KeyType, class Hasher, class Equal>
constexpr Value const &at(KeyType const &key, Hasher const &hash,
Equal const &equal) const {
return at_impl(*this, key, hash, equal);
}
template <class KeyType, class Hasher, class Equal>
constexpr Value &at(KeyType const &key, Hasher const &hash,
Equal const &equal) {
return at_impl(*this, key, hash, equal);
}
template <class KeyType>
constexpr Value const &at(KeyType const &key) const {
return at(key, hash_function(), key_eq());
}
template <class KeyType>
constexpr Value &at(KeyType const &key) {
return at(key, hash_function(), key_eq());
}
template <class KeyType, class Hasher, class Equal>
constexpr const_iterator find(KeyType const &key, Hasher const &hash,
Equal const &equal) const {
return find_impl(*this, key, hash, equal);
}
template <class KeyType, class Hasher, class Equal>
constexpr iterator find(KeyType const &key, Hasher const &hash,
Equal const &equal) {
return find_impl(*this, key, hash, equal);
}
template <class KeyType>
constexpr const_iterator find(KeyType const &key) const {
return find(key, hash_function(), key_eq());
}
template <class KeyType>
constexpr iterator find(KeyType const &key) {
return find(key, hash_function(), key_eq());
}
template <class KeyType, class Hasher, class Equal>
constexpr std::pair<const_iterator, const_iterator> equal_range(
KeyType const &key, Hasher const &hash, Equal const &equal) const {
return equal_range_impl(*this, key, hash, equal);
}
template <class KeyType, class Hasher, class Equal>
constexpr std::pair<iterator, iterator> equal_range(KeyType const &key,
Hasher const &hash,
Equal const &equal) {
return equal_range_impl(*this, key, hash, equal);
}
template <class KeyType>
constexpr std::pair<const_iterator, const_iterator> equal_range(
KeyType const &key) const {
return equal_range(key, hash_function(), key_eq());
}
template <class KeyType>
constexpr std::pair<iterator, iterator> equal_range(KeyType const &key) {
return equal_range(key, hash_function(), key_eq());
}
/* bucket interface */
constexpr std::size_t bucket_count() const { return storage_size; }
constexpr std::size_t max_bucket_count() const { return storage_size; }
/* observers*/
constexpr const hasher &hash_function() const { return tables_.hash_; }
constexpr const key_equal &key_eq() const { return equal_; }
private:
template <class This, class KeyType, class Hasher, class Equal>
static inline constexpr auto &at_impl(This &&self, KeyType const &key,
Hasher const &hash,
Equal const &equal) {
auto &kv = self.lookup(key, hash);
if (equal(kv.first, key))
return kv.second;
else
FROZEN_THROW_OR_ABORT(std::out_of_range("unknown key"));
}
template <class This, class KeyType, class Hasher, class Equal>
static inline constexpr auto find_impl(This &&self, KeyType const &key,
Hasher const &hash,
Equal const &equal) {
auto &kv = self.lookup(key, hash);
if (equal(kv.first, key))
return &kv;
else
return self.items_.end();
}
template <class This, class KeyType, class Hasher, class Equal>
static inline constexpr auto equal_range_impl(This &&self, KeyType const &key,
Hasher const &hash,
Equal const &equal) {
auto &kv = self.lookup(key, hash);
using kv_ptr = decltype(&kv);
if (equal(kv.first, key))
return std::pair<kv_ptr, kv_ptr>{&kv, &kv + 1};
else
return std::pair<kv_ptr, kv_ptr>{self.items_.end(), self.items_.end()};
}
template <class This, class KeyType, class Hasher>
static inline constexpr auto &lookup_impl(This &&self, KeyType const &key,
Hasher const &hash) {
return self.items_[self.tables_.lookup(key, hash)];
}
template <class KeyType, class Hasher>
constexpr auto const &lookup(KeyType const &key, Hasher const &hash) const {
return lookup_impl(*this, key, hash);
}
template <class KeyType, class Hasher>
constexpr auto &lookup(KeyType const &key, Hasher const &hash) {
return lookup_impl(*this, key, hash);
}
};
template <typename T, typename U, std::size_t N>
constexpr auto make_unordered_map(std::pair<T, U> const (&items)[N]) {
return unordered_map<T, U, N>{items};
}
template <typename T, typename U, std::size_t N, typename Hasher,
typename Equal>
constexpr auto make_unordered_map(std::pair<T, U> const (&items)[N],
Hasher const &hash = elsa<T>{},
Equal const &equal = std::equal_to<T>{}) {
return unordered_map<T, U, N, Hasher, Equal>{items, hash, equal};
}
template <typename T, typename U, std::size_t N>
constexpr auto make_unordered_map(std::array<std::pair<T, U>, N> const &items) {
return unordered_map<T, U, N>{items};
}
template <typename T, typename U, std::size_t N, typename Hasher,
typename Equal>
constexpr auto make_unordered_map(std::array<std::pair<T, U>, N> const &items,
Hasher const &hash = elsa<T>{},
Equal const &equal = std::equal_to<T>{}) {
return unordered_map<T, U, N, Hasher, Equal>{items, hash, equal};
}
} // namespace frozen
#endif
+198
View File
@@ -0,0 +1,198 @@
/*
* Frozen
* Copyright 2016 QuarksLab
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef FROZEN_LETITGO_UNORDERED_SET_H
#define FROZEN_LETITGO_UNORDERED_SET_H
#include <utility>
#include "frozen/bits/basic_types.h"
#include "frozen/bits/constexpr_assert.h"
#include "frozen/bits/elsa.h"
#include "frozen/bits/pmh.h"
#include "frozen/bits/version.h"
#include "frozen/random.h"
namespace frozen {
namespace bits {
struct Get {
template <class T>
constexpr T const &operator()(T const &key) const {
return key;
}
};
} // namespace bits
template <class Key, std::size_t N, typename Hash = elsa<Key>,
class KeyEqual = std::equal_to<Key>>
class unordered_set {
static constexpr std::size_t storage_size =
bits::next_highest_power_of_two(N) *
(N < 32 ? 2 : 1); // size adjustment to prevent high collision rate for
// small sets
using container_type = bits::carray<Key, N>;
using tables_type = bits::pmh_tables<storage_size, Hash>;
KeyEqual const equal_;
container_type keys_;
tables_type tables_;
public:
/* typedefs */
using key_type = Key;
using value_type = Key;
using size_type = typename container_type::size_type;
using difference_type = typename container_type::difference_type;
using hasher = Hash;
using key_equal = KeyEqual;
using const_reference = typename container_type::const_reference;
using reference = const_reference;
using const_pointer = typename container_type::const_pointer;
using pointer = const_pointer;
using const_iterator = const_pointer;
using iterator = const_iterator;
public:
/* constructors */
unordered_set(unordered_set const &) = default;
constexpr unordered_set(container_type keys, Hash const &hash,
KeyEqual const &equal)
: equal_{equal},
keys_{keys},
tables_{bits::make_pmh_tables<storage_size>(keys_, hash, bits::Get{},
default_prg_t{})} {}
explicit constexpr unordered_set(container_type keys)
: unordered_set{keys, Hash{}, KeyEqual{}} {}
constexpr unordered_set(std::initializer_list<Key> keys)
: unordered_set{keys, Hash{}, KeyEqual{}} {}
constexpr unordered_set(std::initializer_list<Key> keys, Hash const &hash,
KeyEqual const &equal)
: unordered_set{container_type{keys}, hash, equal} {
constexpr_assert(
keys.size() == N,
"Inconsistent initializer_list size and type size argument");
}
/* iterators */
constexpr const_iterator begin() const { return keys_.begin(); }
constexpr const_iterator end() const { return keys_.end(); }
constexpr const_iterator cbegin() const { return keys_.cbegin(); }
constexpr const_iterator cend() const { return keys_.cend(); }
/* capacity */
constexpr bool empty() const { return !N; }
constexpr size_type size() const { return N; }
constexpr size_type max_size() const { return N; }
/* lookup */
template <class KeyType, class Hasher, class Equal>
constexpr std::size_t count(KeyType const &key, Hasher const &hash,
Equal const &equal) const {
auto const k = lookup(key, hash);
return equal(k, key);
}
template <class KeyType>
constexpr std::size_t count(KeyType const &key) const {
return count(key, hash_function(), key_eq());
}
template <class KeyType, class Hasher, class Equal>
constexpr const_iterator find(KeyType const &key, Hasher const &hash,
Equal const &equal) const {
auto const &k = lookup(key, hash);
if (equal(k, key))
return &k;
else
return keys_.end();
}
template <class KeyType>
constexpr const_iterator find(KeyType const &key) const {
return find(key, hash_function(), key_eq());
}
template <class KeyType, class Hasher, class Equal>
constexpr std::pair<const_iterator, const_iterator> equal_range(
KeyType const &key, Hasher const &hash, Equal const &equal) const {
auto const &k = lookup(key, hash);
if (equal(k, key))
return {&k, &k + 1};
else
return {keys_.end(), keys_.end()};
}
template <class KeyType>
constexpr std::pair<const_iterator, const_iterator> equal_range(
KeyType const &key) const {
return equal_range(key, hash_function(), key_eq());
}
/* bucket interface */
constexpr std::size_t bucket_count() const { return storage_size; }
constexpr std::size_t max_bucket_count() const { return storage_size; }
/* observers*/
constexpr const hasher &hash_function() const { return tables_.hash_; }
constexpr const key_equal &key_eq() const { return equal_; }
private:
template <class KeyType, class Hasher>
constexpr auto const &lookup(KeyType const &key, Hasher const &hash) const {
return keys_[tables_.lookup(key, hash)];
}
};
template <typename T, std::size_t N>
constexpr auto make_unordered_set(T const (&keys)[N]) {
return unordered_set<T, N>{keys};
}
template <typename T, std::size_t N, typename Hasher, typename Equal>
constexpr auto make_unordered_set(T const (&keys)[N], Hasher const &hash,
Equal const &equal) {
return unordered_set<T, N, Hasher, Equal>{keys, hash, equal};
}
template <typename T, std::size_t N>
constexpr auto make_unordered_set(std::array<T, N> const &keys) {
return unordered_set<T, N>{keys};
}
template <typename T, std::size_t N, typename Hasher, typename Equal>
constexpr auto make_unordered_set(std::array<T, N> const &keys,
Hasher const &hash, Equal const &equal) {
return unordered_set<T, N, Hasher, Equal>{keys, hash, equal};
}
#ifdef FROZEN_LETITGO_HAS_DEDUCTION_GUIDES
template <class T, class... Args>
unordered_set(T, Args...) -> unordered_set<T, sizeof...(Args) + 1>;
#endif
} // namespace frozen
#endif
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
File diff suppressed because it is too large Load Diff
+117
View File
@@ -0,0 +1,117 @@
#pragma once
#if (defined(_MSC_VER) && _MSC_VER >= 1920 && _MSVC_LANG >= 202002L) || \
(!defined(_MSC_VER) && defined(__cplusplus) && __cplusplus >= 202002L)
#include <bit>
#endif
#include <array>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#if defined __clang__
#define IGUANA_INLINE __attribute__((always_inline)) inline
#define IGUANA__INLINE_LAMBDA __attribute__((always_inline)) constexpr
#elif defined _MSC_VER
#define IGUANA_INLINE __forceinline
#define IGUANA__INLINE_LAMBDA constexpr
#else
#define IGUANA_INLINE __attribute__((always_inline)) inline
#define IGUANA__INLINE_LAMBDA constexpr __attribute__((always_inline))
#endif
#if __has_cpp_attribute(likely) && __has_cpp_attribute(unlikely) && \
__cplusplus >= 202002L
#define IGUANA_LIKELY [[likely]]
#define IGUANA_UNLIKELY [[unlikely]]
#else
#define IGUANA_LIKELY
#define IGUANA_UNLIKELY
#endif
#ifdef _MSC_VER
#if _MSC_VER >= 1920 && _MSVC_LANG >= 202002L
IGUANA_INLINE int countr_zero(unsigned long long x) {
return std::countr_zero(x);
}
template <typename T>
constexpr inline bool contiguous_iterator = std::contiguous_iterator<T>;
#else
IGUANA_INLINE int countr_zero(unsigned long long n) {
// x will never be zero in iguana
#if defined(_MSC_VER) && defined(_M_X64)
unsigned long c;
_BitScanForward64(&c, n);
return static_cast<int>(c);
#elif defined(_MSC_VER) && defined(_M_IX86)
unsigned long c;
if (static_cast<uint32_t>(n) != 0) {
_BitScanForward(&c, static_cast<uint32_t>(n));
return static_cast<int>(c);
}
else {
_BitScanForward(&c, static_cast<uint32_t>(n >> 32));
return static_cast<int>(c) + 32;
}
#endif
}
namespace std {
template <typename T>
using remove_cvref_t =
typename remove_cv<typename remove_reference<T>::type>::type;
}
template <typename T>
constexpr inline bool contiguous_iterator =
std::is_same_v<std::remove_cvref_t<T>,
typename std::vector<char>::iterator> ||
std::is_same_v<std::remove_cvref_t<T>,
typename std::vector<char>::const_iterator> ||
std::is_same_v<std::remove_cvref_t<T>, typename std::string::iterator> ||
std::is_same_v<std::remove_cvref_t<T>,
typename std::string::const_iterator> ||
std::is_same_v<std::remove_cvref_t<T>, char *> ||
std::is_same_v<std::remove_cvref_t<T>,
typename std::string_view::iterator> ||
std::is_same_v<std::remove_cvref_t<T>,
typename std::string_view::const_iterator>;
#endif
#else
#if __cplusplus >= 202002L
IGUANA_INLINE int countr_zero(unsigned long long x) {
return std::countr_zero(x);
}
template <typename T>
constexpr inline bool contiguous_iterator = std::contiguous_iterator<T>;
#else
IGUANA_INLINE int countr_zero(unsigned long long x) {
// x will never be zero in iguana
return __builtin_ctzll(x);
}
namespace std {
template <typename T>
using remove_cvref_t =
typename remove_cv<typename remove_reference<T>::type>::type;
}
template <typename T>
constexpr inline bool contiguous_iterator =
std::is_same_v<std::remove_cvref_t<T>,
typename std::vector<char>::iterator> ||
std::is_same_v<std::remove_cvref_t<T>,
typename std::vector<char>::const_iterator> ||
std::is_same_v<std::remove_cvref_t<T>, typename std::string::iterator> ||
std::is_same_v<std::remove_cvref_t<T>,
typename std::string::const_iterator> ||
std::is_same_v<std::remove_cvref_t<T>, char *> ||
std::is_same_v<std::remove_cvref_t<T>,
typename std::string_view::iterator> ||
std::is_same_v<std::remove_cvref_t<T>,
typename std::string_view::const_iterator>;
#endif
#endif
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include <charconv>
#include "dragonbox_to_chars.h"
#include "fast_float.h"
#include "iguana/define.h"
#include "itoa.hpp"
namespace iguana {
template <typename T>
struct is_char_type
: std::disjunction<std::is_same<T, char>, std::is_same<T, wchar_t>,
std::is_same<T, char16_t>, std::is_same<T, char32_t>> {};
inline void *to_chars_float(...) {
throw std::runtime_error("not allowed to invoke");
return {};
}
template <typename T, typename Ret = decltype(to_chars_float(
std::declval<T>(), std::declval<char *>()))>
using return_of_tochars = std::conditional_t<std::is_same_v<Ret, char *>,
std::true_type, std::false_type>;
// here std::true_type is used as a type , any other type is also ok.
using has_to_chars_float = iguana::return_of_tochars<std::true_type>;
namespace detail {
// check_number==true: check if the string [first, last) is a legal number
template <bool check_number = true, typename U>
std::pair<const char *, std::errc> from_chars(const char *first,
const char *last, U &value) {
using T = std::decay_t<U>;
if constexpr (std::is_floating_point_v<T>) {
auto [p, ec] = fast_float::from_chars(first, last, value);
if constexpr (check_number) {
if (p != last || ec != std::errc{})
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
}
return {p, ec};
}
else {
auto [p, ec] = std::from_chars(first, last, value);
if constexpr (check_number) {
if (p != last || ec != std::errc{})
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
}
return {p, ec};
}
}
// not support uint8 for now
template <typename T>
char *to_chars(char *buffer, T value) noexcept {
using U = std::decay_t<T>;
if constexpr (std::is_floating_point_v<U>) {
if constexpr (has_to_chars_float::value) {
return static_cast<char *>(to_chars_float(value, buffer));
}
else {
return jkj::dragonbox::to_chars(value, buffer);
}
}
else if constexpr (std::is_signed_v<U> && (sizeof(U) >= 8)) {
return xtoa(value, buffer, 10, 1); // int64_t
}
else if constexpr (std::is_unsigned_v<U> && (sizeof(U) >= 8)) {
return xtoa(value, buffer, 10, 0); // uint64_t
}
else if constexpr (std::is_integral_v<U> && (sizeof(U) > 1)) {
return itoa_fwd(value, buffer); // only support more than 2 bytes intergal
}
else if constexpr (!is_char_type<U>::value) {
return itoa_fwd(static_cast<int>(value),
buffer); // only support more than 2 bytes intergal
}
else {
static_assert(!sizeof(U), "only support arithmetic type except char type");
}
}
} // namespace detail
} // namespace iguana
File diff suppressed because it is too large Load Diff
+629
View File
@@ -0,0 +1,629 @@
// Copyright 2020-2022 Junekey Jeon
//
// The contents of this file may be used under the terms of
// the Apache License v2.0 with LLVM Exceptions.
//
// (See accompanying file LICENSE-Apache or copy at
// https://llvm.org/foundation/relicensing/LICENSE.txt)
//
// Alternatively, the contents of this file may be used under the terms of
// the Boost Software License, Version 1.0.
// (See accompanying file LICENSE-Boost or copy at
// https://www.boost.org/LICENSE_1_0.txt)
//
// Unless required by applicable law or agreed to in writing, this software
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.
#ifndef JKJ_HEADER_DRAGONBOX_TO_CHARS
#define JKJ_HEADER_DRAGONBOX_TO_CHARS
#include "dragonbox.h"
#if defined(__GNUC__) || defined(__clang__)
#define JKJ_FORCEINLINE inline __attribute__((always_inline))
#elif defined(_MSC_VER)
#define JKJ_FORCEINLINE __forceinline
#else
#define JKJ_FORCEINLINE inline
#endif
namespace jkj::dragonbox {
namespace to_chars_detail {
template <class Float, class FloatTraits>
extern char *to_chars(typename FloatTraits::carrier_uint significand,
int exponent, char *buffer) noexcept;
// These "//"'s are to prevent clang-format to ruin this nice alignment.
// Thanks to reddit user u/mcmcc:
// https://www.reddit.com/r/cpp/comments/so3wx9/dragonbox_110_is_released_a_fast_floattostring/hw8z26r/?context=3
static constexpr char radix_100_table[] = {
'0', '0', '0', '1', '0', '2', '0', '3', '0', '4', //
'0', '5', '0', '6', '0', '7', '0', '8', '0', '9', //
'1', '0', '1', '1', '1', '2', '1', '3', '1', '4', //
'1', '5', '1', '6', '1', '7', '1', '8', '1', '9', //
'2', '0', '2', '1', '2', '2', '2', '3', '2', '4', //
'2', '5', '2', '6', '2', '7', '2', '8', '2', '9', //
'3', '0', '3', '1', '3', '2', '3', '3', '3', '4', //
'3', '5', '3', '6', '3', '7', '3', '8', '3', '9', //
'4', '0', '4', '1', '4', '2', '4', '3', '4', '4', //
'4', '5', '4', '6', '4', '7', '4', '8', '4', '9', //
'5', '0', '5', '1', '5', '2', '5', '3', '5', '4', //
'5', '5', '5', '6', '5', '7', '5', '8', '5', '9', //
'6', '0', '6', '1', '6', '2', '6', '3', '6', '4', //
'6', '5', '6', '6', '6', '7', '6', '8', '6', '9', //
'7', '0', '7', '1', '7', '2', '7', '3', '7', '4', //
'7', '5', '7', '6', '7', '7', '7', '8', '7', '9', //
'8', '0', '8', '1', '8', '2', '8', '3', '8', '4', //
'8', '5', '8', '6', '8', '7', '8', '8', '8', '9', //
'9', '0', '9', '1', '9', '2', '9', '3', '9', '4', //
'9', '5', '9', '6', '9', '7', '9', '8', '9', '9' //
};
static constexpr char radix_100_head_table[] = {
'0', '.', '1', '.', '2', '.', '3', '.', '4', '.', //
'5', '.', '6', '.', '7', '.', '8', '.', '9', '.', //
'1', '.', '1', '.', '1', '.', '1', '.', '1', '.', //
'1', '.', '1', '.', '1', '.', '1', '.', '1', '.', //
'2', '.', '2', '.', '2', '.', '2', '.', '2', '.', //
'2', '.', '2', '.', '2', '.', '2', '.', '2', '.', //
'3', '.', '3', '.', '3', '.', '3', '.', '3', '.', //
'3', '.', '3', '.', '3', '.', '3', '.', '3', '.', //
'4', '.', '4', '.', '4', '.', '4', '.', '4', '.', //
'4', '.', '4', '.', '4', '.', '4', '.', '4', '.', //
'5', '.', '5', '.', '5', '.', '5', '.', '5', '.', //
'5', '.', '5', '.', '5', '.', '5', '.', '5', '.', //
'6', '.', '6', '.', '6', '.', '6', '.', '6', '.', //
'6', '.', '6', '.', '6', '.', '6', '.', '6', '.', //
'7', '.', '7', '.', '7', '.', '7', '.', '7', '.', //
'7', '.', '7', '.', '7', '.', '7', '.', '7', '.', //
'8', '.', '8', '.', '8', '.', '8', '.', '8', '.', //
'8', '.', '8', '.', '8', '.', '8', '.', '8', '.', //
'9', '.', '9', '.', '9', '.', '9', '.', '9', '.', //
'9', '.', '9', '.', '9', '.', '9', '.', '9', '.' //
};
// These digit generation routines are inspired by James Anhalt's itoa
// algorithm: https://github.com/jeaiii/itoa The main idea is for given n, find
// y such that floor(10^k * y / 2^32) = n holds, where k is an appropriate
// integer depending on the length of n. For example, if n = 1234567, we set k
// = 6. In this case, we have floor(y / 2^32) = 1, floor(10^2 * ((10^0 * y) mod
// 2^32) / 2^32) = 23, floor(10^2 * ((10^2 * y) mod 2^32) / 2^32) = 45, and
// floor(10^2 * ((10^4 * y) mod 2^32) / 2^32) = 67.
// See https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/ for more
// explanation.
JKJ_FORCEINLINE static void print_9_digits(std::uint32_t s32, int &exponent,
char *&buffer) noexcept {
// -- IEEE-754 binary32
// Since we do not cut trailing zeros in advance, s32 must be of 6~9 digits
// unless the original input was subnormal.
// In particular, when it is of 9 digits it shouldn't have any trailing zeros.
// -- IEEE-754 binary64
// In this case, s32 must be of 7~9 digits unless the input is subnormal,
// and it shouldn't have any trailing zeros if it is of 9 digits.
if (s32 >= 1'0000'0000) {
// 9 digits.
// 1441151882 = ceil(2^57 / 1'0000'0000) + 1
auto prod = s32 * std::uint64_t(1441151882);
prod >>= 25;
std::memcpy(buffer, radix_100_head_table + std::uint32_t(prod >> 32) * 2,
2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 2, radix_100_table + std::uint32_t(prod >> 32) * 2, 2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 4, radix_100_table + std::uint32_t(prod >> 32) * 2, 2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 6, radix_100_table + std::uint32_t(prod >> 32) * 2, 2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 8, radix_100_table + std::uint32_t(prod >> 32) * 2, 2);
exponent += 8;
buffer += 10;
}
else if (s32 >= 100'0000) {
// 7 or 8 digits.
// 281474978 = ceil(2^48 / 100'0000) + 1
auto prod = s32 * std::uint64_t(281474978);
prod >>= 16;
auto two_digits = std::uint32_t(prod >> 32);
// If s32 is of 8 digits, increase the exponent by 7.
// Otherwise, increase it by 6.
exponent += (6 + unsigned(two_digits >= 10));
// Write the first digit and the decimal point.
std::memcpy(buffer, radix_100_head_table + two_digits * 2, 2);
// This third character may be overwritten later but we don't care.
buffer[2] = radix_100_table[two_digits * 2 + 1];
// Remaining 6 digits are all zero?
if (std::uint32_t(prod) <=
std::uint32_t((std::uint64_t(1) << 32) / 100'0000)) {
// The number of characters actually written is:
// 1, if only the first digit is nonzero, which means that either s32 is
// of 7 digits or it is of 8 digits but the second digit is zero, or 3,
// otherwise.
// Note that buffer[2] is never zero if s32 is of 7 digits, because the
// input is never zero.
buffer +=
(1 + (unsigned(two_digits >= 10) & unsigned(buffer[2] > '0')) * 2);
}
else {
// At least one of the remaining 6 digits are nonzero.
// After this adjustment, now the first destination becomes buffer + 2.
buffer += unsigned(two_digits >= 10);
// Obtain the next two digits.
prod = std::uint32_t(prod) * std::uint64_t(100);
two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer + 2, radix_100_table + two_digits * 2, 2);
// Remaining 4 digits are all zero?
if (std::uint32_t(prod) <=
std::uint32_t((std::uint64_t(1) << 32) / 1'0000)) {
buffer += (3 + unsigned(buffer[3] > '0'));
}
else {
// At least one of the remaining 4 digits are nonzero.
// Obtain the next two digits.
prod = std::uint32_t(prod) * std::uint64_t(100);
two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer + 4, radix_100_table + two_digits * 2, 2);
// Remaining 2 digits are all zero?
if (std::uint32_t(prod) <=
std::uint32_t((std::uint64_t(1) << 32) / 100)) {
buffer += (5 + unsigned(buffer[5] > '0'));
}
else {
// Obtain the last two digits.
prod = std::uint32_t(prod) * std::uint64_t(100);
two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer + 6, radix_100_table + two_digits * 2, 2);
buffer += (7 + unsigned(buffer[7] > '0'));
}
}
}
}
else if (s32 >= 1'0000) {
// 5 or 6 digits.
// 429497 = ceil(2^32 / 1'0000)
auto prod = s32 * std::uint64_t(429497);
auto two_digits = std::uint32_t(prod >> 32);
// If s32 is of 6 digits, increase the exponent by 5.
// Otherwise, increase it by 4.
exponent += (4 + unsigned(two_digits >= 10));
// Write the first digit and the decimal point.
std::memcpy(buffer, radix_100_head_table + two_digits * 2, 2);
// This third character may be overwritten later but we don't care.
buffer[2] = radix_100_table[two_digits * 2 + 1];
// Remaining 4 digits are all zero?
if (std::uint32_t(prod) <=
std::uint32_t((std::uint64_t(1) << 32) / 1'0000)) {
// The number of characters actually written is 1 or 3, similarly to the
// case of 7 or 8 digits.
buffer +=
(1 + (unsigned(two_digits >= 10) & unsigned(buffer[2] > '0')) * 2);
}
else {
// At least one of the remaining 4 digits are nonzero.
// After this adjustment, now the first destination becomes buffer + 2.
buffer += unsigned(two_digits >= 10);
// Obtain the next two digits.
prod = std::uint32_t(prod) * std::uint64_t(100);
two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer + 2, radix_100_table + two_digits * 2, 2);
// Remaining 2 digits are all zero?
if (std::uint32_t(prod) <=
std::uint32_t((std::uint64_t(1) << 32) / 100)) {
buffer += (3 + unsigned(buffer[3] > '0'));
}
else {
// Obtain the last two digits.
prod = std::uint32_t(prod) * std::uint64_t(100);
two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer + 4, radix_100_table + two_digits * 2, 2);
buffer += (5 + unsigned(buffer[5] > '0'));
}
}
}
else if (s32 >= 100) {
// 3 or 4 digits.
// 42949673 = ceil(2^32 / 100)
auto prod = s32 * std::uint64_t(42949673);
auto two_digits = std::uint32_t(prod >> 32);
// If s32 is of 4 digits, increase the exponent by 3.
// Otherwise, increase it by 2.
exponent += (2 + int(two_digits >= 10));
// Write the first digit and the decimal point.
std::memcpy(buffer, radix_100_head_table + two_digits * 2, 2);
// This third character may be overwritten later but we don't care.
buffer[2] = radix_100_table[two_digits * 2 + 1];
// Remaining 2 digits are all zero?
if (std::uint32_t(prod) <= std::uint32_t((std::uint64_t(1) << 32) / 100)) {
// The number of characters actually written is 1 or 3, similarly to the
// case of 7 or 8 digits.
buffer +=
(1 + (unsigned(two_digits >= 10) & unsigned(buffer[2] > '0')) * 2);
}
else {
// At least one of the remaining 2 digits are nonzero.
// After this adjustment, now the first destination becomes buffer + 2.
buffer += unsigned(two_digits >= 10);
// Obtain the last two digits.
prod = std::uint32_t(prod) * std::uint64_t(100);
two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer + 2, radix_100_table + two_digits * 2, 2);
buffer += (3 + unsigned(buffer[3] > '0'));
}
}
else {
// 1 or 2 digits.
// If s32 is of 2 digits, increase the exponent by 1.
exponent += int(s32 >= 10);
// Write the first digit and the decimal point.
std::memcpy(buffer, radix_100_head_table + s32 * 2, 2);
// This third character may be overwritten later but we don't care.
buffer[2] = radix_100_table[s32 * 2 + 1];
// The number of characters actually written is 1 or 3, similarly to the
// case of 7 or 8 digits.
buffer += (1 + (unsigned(s32 >= 10) & unsigned(buffer[2] > '0')) * 2);
}
}
template <>
inline char *to_chars<float, default_float_traits<float>>(
std::uint32_t s32, int exponent, char *buffer) noexcept {
// Print significand.
print_9_digits(s32, exponent, buffer);
// Print exponent and return
if (exponent < 0) {
std::memcpy(buffer, "E-", 2);
buffer += 2;
exponent = -exponent;
}
else {
buffer[0] = 'E';
buffer += 1;
}
if (exponent >= 10) {
std::memcpy(buffer, &radix_100_table[exponent * 2], 2);
buffer += 2;
}
else {
buffer[0] = char('0' + exponent);
buffer += 1;
}
return buffer;
}
template <>
inline char *to_chars<double, default_float_traits<double>>(
std::uint64_t const significand, int exponent, char *buffer) noexcept {
// Print significand by decomposing it into a 9-digit block and a 8-digit
// block.
std::uint32_t first_block, second_block;
bool no_second_block;
if (significand >= 1'0000'0000) {
first_block = std::uint32_t(significand / 1'0000'0000);
second_block = std::uint32_t(significand) - first_block * 1'0000'0000;
exponent += 8;
no_second_block = (second_block == 0);
}
else {
first_block = std::uint32_t(significand);
no_second_block = true;
}
if (no_second_block) {
print_9_digits(first_block, exponent, buffer);
}
else {
// We proceed similarly to print_9_digits(), but since we do not need to
// remove trailing zeros, the procedure is a bit simpler.
if (first_block >= 1'0000'0000) {
// The input is of 17 digits, thus there should be no trailing zero at
// all. The first block is of 9 digits. 1441151882 = ceil(2^57 /
// 1'0000'0000) + 1
auto prod = first_block * std::uint64_t(1441151882);
prod >>= 25;
std::memcpy(buffer, radix_100_head_table + std::uint32_t(prod >> 32) * 2,
2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 2, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 4, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 6, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 8, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
// The second block is of 8 digits.
// 281474978 = ceil(2^48 / 100'0000) + 1
prod = second_block * std::uint64_t(281474978);
prod >>= 16;
prod += 1;
std::memcpy(buffer + 10, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 12, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 14, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 16, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
exponent += 8;
buffer += 18;
}
else {
if (first_block >= 100'0000) {
// 7 or 8 digits.
// 281474978 = ceil(2^48 / 100'0000) + 1
auto prod = first_block * std::uint64_t(281474978);
prod >>= 16;
auto two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer, radix_100_head_table + two_digits * 2, 2);
buffer[2] = radix_100_table[two_digits * 2 + 1];
exponent += (6 + unsigned(two_digits >= 10));
buffer += unsigned(two_digits >= 10);
// Print remaining 6 digits.
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 2, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 4, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 6, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
buffer += 8;
}
else if (first_block >= 1'0000) {
// 5 or 6 digits.
// 429497 = ceil(2^32 / 1'0000)
auto prod = first_block * std::uint64_t(429497);
auto two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer, radix_100_head_table + two_digits * 2, 2);
buffer[2] = radix_100_table[two_digits * 2 + 1];
exponent += (4 + unsigned(two_digits >= 10));
buffer += unsigned(two_digits >= 10);
// Print remaining 4 digits.
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 2, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 4, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
buffer += 6;
}
else if (first_block >= 100) {
// 3 or 4 digits.
// 42949673 = ceil(2^32 / 100)
auto prod = first_block * std::uint64_t(42949673);
auto two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer, radix_100_head_table + two_digits * 2, 2);
buffer[2] = radix_100_table[two_digits * 2 + 1];
exponent += (2 + unsigned(two_digits >= 10));
buffer += unsigned(two_digits >= 10);
// Print remaining 2 digits.
prod = std::uint32_t(prod) * std::uint64_t(100);
std::memcpy(buffer + 2, radix_100_table + std::uint32_t(prod >> 32) * 2,
2);
buffer += 4;
}
else {
// 1 or 2 digits.
std::memcpy(buffer, radix_100_head_table + first_block * 2, 2);
buffer[2] = radix_100_table[first_block * 2 + 1];
exponent += unsigned(first_block >= 10);
buffer += (2 + unsigned(first_block >= 10));
}
// Next, print the second block.
// The second block is of 8 digits, but we may have trailing zeros.
// 281474978 = ceil(2^48 / 100'0000) + 1
auto prod = second_block * std::uint64_t(281474978);
prod >>= 16;
prod += 1;
auto two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer, radix_100_table + two_digits * 2, 2);
// Remaining 6 digits are all zero?
if (std::uint32_t(prod) <=
std::uint32_t((std::uint64_t(1) << 32) / 100'0000)) {
buffer += (1 + unsigned(buffer[1] > '0'));
}
else {
// Obtain the next two digits.
prod = std::uint32_t(prod) * std::uint64_t(100);
two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer + 2, radix_100_table + two_digits * 2, 2);
// Remaining 4 digits are all zero?
if (std::uint32_t(prod) <=
std::uint32_t((std::uint64_t(1) << 32) / 1'0000)) {
buffer += (3 + unsigned(buffer[3] > '0'));
}
else {
// Obtain the next two digits.
prod = std::uint32_t(prod) * std::uint64_t(100);
two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer + 4, radix_100_table + two_digits * 2, 2);
// Remaining 2 digits are all zero?
if (std::uint32_t(prod) <=
std::uint32_t((std::uint64_t(1) << 32) / 100)) {
buffer += (5 + unsigned(buffer[5] > '0'));
}
else {
// Obtain the last two digits.
prod = std::uint32_t(prod) * std::uint64_t(100);
two_digits = std::uint32_t(prod >> 32);
std::memcpy(buffer + 6, radix_100_table + two_digits * 2, 2);
buffer += (7 + unsigned(buffer[7] > '0'));
}
}
}
}
}
// Print exponent and return
if (exponent < 0) {
std::memcpy(buffer, "E-", 2);
buffer += 2;
exponent = -exponent;
}
else {
buffer[0] = 'E';
buffer += 1;
}
if (exponent >= 100) {
// d1 = exponent / 10; d2 = exponent % 10;
// 6554 = ceil(2^16 / 10)
auto prod = std::uint32_t(exponent) * std::uint32_t(6554);
auto d1 = prod >> 16;
prod = std::uint16_t(prod) * std::uint32_t(5); // * 10
auto d2 = prod >> 15; // >> 16
std::memcpy(buffer, &radix_100_table[d1 * 2], 2);
buffer[2] = char('0' + d2);
buffer += 3;
}
else if (exponent >= 10) {
std::memcpy(buffer, &radix_100_table[exponent * 2], 2);
buffer += 2;
}
else {
buffer[0] = char('0' + exponent);
buffer += 1;
}
return buffer;
}
// Avoid needless ABI overhead incurred by tag dispatch.
template <class PolicyHolder, class Float, class FloatTraits>
char *to_chars_n_impl(float_bits<Float, FloatTraits> br,
char *buffer) noexcept {
auto const exponent_bits = br.extract_exponent_bits();
auto const s = br.remove_exponent_bits(exponent_bits);
if (br.is_finite(exponent_bits)) {
if (s.is_negative()) {
*buffer = '-';
++buffer;
}
if (br.is_nonzero()) {
auto result = to_decimal<Float, FloatTraits>(
s, exponent_bits, policy::sign::ignore, policy::trailing_zero::ignore,
typename PolicyHolder::decimal_to_binary_rounding_policy{},
typename PolicyHolder::binary_to_decimal_rounding_policy{},
typename PolicyHolder::cache_policy{});
return to_chars_detail::to_chars<Float, FloatTraits>(
result.significand, result.exponent, buffer);
}
else {
std::memcpy(buffer, "0E0", 3);
return buffer + 3;
}
}
else {
if (s.has_all_zero_significand_bits()) {
if (s.is_negative()) {
*buffer = '-';
++buffer;
}
std::memcpy(buffer, "Infinity", 8);
return buffer + 8;
}
else {
std::memcpy(buffer, "NaN", 3);
return buffer + 3;
}
}
}
} // namespace to_chars_detail
// Returns the next-to-end position
template <class Float, class FloatTraits = default_float_traits<Float>,
class... Policies>
char *to_chars_n(Float x, char *buffer, Policies... policies) noexcept {
using namespace jkj::dragonbox::detail::policy_impl;
using policy_holder = decltype(make_policy_holder(
base_default_pair_list<
base_default_pair<decimal_to_binary_rounding::base,
decimal_to_binary_rounding::nearest_to_even>,
base_default_pair<binary_to_decimal_rounding::base,
binary_to_decimal_rounding::to_even>,
base_default_pair<cache::base, cache::full>>{},
policies...));
return to_chars_detail::to_chars_n_impl<policy_holder>(
float_bits<Float, FloatTraits>(x), buffer);
}
// Null-terminate and bypass the return value of fp_to_chars_n
template <class Float, class FloatTraits = default_float_traits<Float>,
class... Policies>
char *to_chars(Float x, char *buffer, Policies... policies) noexcept {
auto ptr = to_chars_n<Float, FloatTraits>(x, buffer, policies...);
*ptr = '\0';
return ptr;
}
// Maximum required buffer size (excluding null-terminator)
template <class FloatFormat>
inline constexpr std::size_t max_output_string_length =
std::is_same_v<FloatFormat, ieee754_binary32>
?
// sign(1) + significand(9) + decimal_point(1) + exp_marker(1) +
// exp_sign(1) + exp(2)
(1 + 9 + 1 + 1 + 1 + 2)
:
// format == ieee754_format::binary64
// sign(1) + significand(17) + decimal_point(1) + exp_marker(1) +
// exp_sign(1) + exp(3)
(1 + 17 + 1 + 1 + 1 + 3);
} // namespace jkj::dragonbox
#endif
File diff suppressed because it is too large Load Diff
+321
View File
@@ -0,0 +1,321 @@
//=== itoa.h - Fast integer to ascii conversion --*- C++ -*-//
//
// The MIT License (MIT)
// Copyright (c) 2016 Arturo Martin-de-Nicolas
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//===----------------------------------------------------------------------===//
#ifndef DEC_ITOA_IMPL_H
#define DEC_ITOA_IMPL_H
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring> // memcpy
#include <type_traits>
namespace dec_ {
// Using a lookup table to convert binary numbers from 0 to 99
// into ascii characters as described by Andrei Alexandrescu in
// https://www.facebook.com/notes/facebook-engineering/
// three-optimization-tips-for-c/10151361643253920/
template <typename T, size_t N, typename Gen, size_t... Is>
constexpr auto generate_array(Gen&& item, std::index_sequence<Is...>) {
return std::array<T, N>{{item(Is)...}};
}
const std::array<char, 200> digits = generate_array<char, 200>(
[](size_t i) {
return char('0' + ((i % 2) ? ((i / 2) % 10) : ((i / 2) / 10)));
},
std::make_index_sequence<200>{});
// extern const std::array<char,200> digits;
static inline uint16_t const& dd(uint8_t u) {
return reinterpret_cast<uint16_t const*>(digits.data())[u];
}
template <typename T>
static constexpr T pow10(size_t x) {
return x ? 10 * pow10<T>(x - 1) : 1;
}
// Division by a power of 10 is implemented using a multiplicative inverse.
// This strength reduction is also done by optimizing compilers, but
// presently the fastest results are produced by using the values
// for the multiplication and the shift as given by the algorithm
// described by Agner Fog in "Optimizing Subroutines in Assembly Language"
//
// http://www.agner.org/optimize/optimizing_assembly.pdf
//
// "Integer division by a constant (all processors)
// A floating point number can be divided by a constant by multiplying
// with the reciprocal. If we want to do the same with integers, we have
// to scale the reciprocal by 2n and then shift the product to the right
// by n. There are various algorithms for finding a suitable value of n
// and compensating for rounding errors. The algorithm described below
// was invented by Terje Mathisen, Norway, and not published elsewhere."
// using uint128_t = unsigned __int128;
template <typename UInt, bool A, UInt M, unsigned S>
struct MulInv {
using type = UInt;
static constexpr bool a{A};
static constexpr UInt m{M};
static constexpr unsigned s{S};
};
template <int, int, class...>
struct UT;
template <int N, class T, class... Ts>
struct UT<N, N, T, Ts...> {
using U = T;
};
template <int N, int M, class T, class... Ts>
struct UT<N, M, T, Ts...> {
using U = typename UT<N, 2 * M, Ts...>::U;
};
template <int N>
using MI = typename UT<N, 1, MulInv<uint8_t, 0, 205U, 11>,
MulInv<uint16_t, 1, 41943U, 22>,
MulInv<uint32_t, 0, 3518437209U, 45>,
MulInv<uint64_t, 0, 12379400392853802749U, 90>>::U;
template <int N>
using U = typename MI<N>::type;
// struct QR holds the result of dividing an unsigned N-byte variable
// by 10^N resulting in
template <size_t N>
struct QR {
U<N> q; // quotient with fewer than 2*N decimal digits
U<N / 2> r; // remainder with at most N decimal digits
};
template <size_t N>
QR<N> static inline split(U<N> u) {
constexpr MI<N> mi{};
U<N> q = (mi.m * (U<2 * N>(u) + mi.a)) >> mi.s;
return {q, U<N / 2>(u - q * pow10<U<N / 2>>(N))};
}
enum Direction { Fwd, Rev };
template <Direction D>
struct convert {
//===----------------------------------------------------------===//
// output the digits in either a forward or reverse direction
// use memcpy so compiler handles alignment on target architecture.
// Typically generates one store to memory with an optimizing
// compiler for target architecture that supports unaligned access.
//===----------------------------------------------------------===//
template <typename T>
static inline char* out(char* p, T&& obj) {
if (D == Rev)
p -= sizeof(T);
memcpy(p, reinterpret_cast<const void*>(&obj), sizeof(T));
if (D == Fwd)
p += sizeof(T);
return p;
}
//===----------------------------------------------------------===//
// head: find most significant digit, skip leading zeros
//===----------------------------------------------------------===//
// "x" contains quotient and remainder after division by 10^N
// quotient is less than 10^N
template <size_t N>
static inline char* head(char* p, QR<N> x) {
return (D == Fwd ? (tail(head(p, U<N / 2>(x.q)), x.r))
: (head(tail(p, x.r), U<N / 2>(x.q))));
}
// "u" is less than 10^2*N
template <typename UInt, size_t N = sizeof(UInt)>
static inline char* head(char* p, UInt u) {
return (u < pow10<U<N>>(N) ? (head(p, U<N / 2>(u)))
: (head<N>(p, split<N>(u))));
}
// recursion base case, selected when "u" is one byte
static inline char* head(char* p, U<1> u) {
return (u < 10 ? (out<char>(p, '0' + u)) : (out(p, dd(u))));
}
//===----------------------------------------------------------===//
// tail: produce all digits including leading zeros
//===----------------------------------------------------------===//
// recursive step, "u" is less than 10^2*N
template <typename UInt, size_t N = sizeof(UInt)>
static inline char* tail(char* p, UInt u) {
QR<N> x = split<N>(u);
return (D == Fwd ? (tail(tail(p, U<N / 2>(x.q)), x.r))
: (tail(tail(p, x.r), U<N / 2>(x.q))));
}
// recursion base case, selected when "u" is one byte
static inline char* tail(char* p, U<1> u) { return out(p, dd(u)); }
//===----------------------------------------------------------===//
// large values are >= 10^2*N
// where x contains quotient and remainder after division by 10^N
//===----------------------------------------------------------===//
template <size_t N>
static inline char* large(char* p, QR<N> x) {
QR<N> y = split<N>(x.q);
return (D == Fwd ? (tail(tail(head(p, U<N / 2>(y.q)), y.r), x.r))
: (head(tail(tail(p, x.r), y.r), U<N / 2>(y.q))));
}
//===----------------------------------------------------------===//
// handle values of "u" that might be >= 10^2*N
// where N is the size of "u" in bytes
//===----------------------------------------------------------===//
template <typename UInt, size_t N = sizeof(UInt)>
static inline char* itoa(char* p, UInt u) {
if (u < pow10<U<N>>(N))
return head(p, U<N / 2>(u));
QR<N> x = split<N>(u);
return (u < pow10<U<N>>(2 * N) ? (head<N>(p, x)) : (large<N>(p, x)));
}
// selected when "u" is one byte
static inline char* itoa(char* p, U<1> u) {
if (u < 10)
return out<char>(p, '0' + u);
if (u < 100)
return out(p, dd(u));
return (D == Fwd ? (out(out<char>(p, '0' + u / 100), dd(u % 100)))
: (out<char>(out(p, dd(u % 100)), '0' + u / 100)));
}
//===----------------------------------------------------------===//
// handle unsigned and signed integral operands
//===----------------------------------------------------------===//
// itoa: handle unsigned integral operands (selected by SFINAE)
template <typename U, std::enable_if_t<!std::is_signed<U>::value &&
std::is_integral<U>::value>* = nullptr>
static inline char* itoa(U u, char* p) {
return convert<D>::template itoa<>(p, u);
}
// itoa: handle signed integral operands (selected by SFINAE)
template <typename I, size_t N = sizeof(I),
std::enable_if_t<std::is_signed<I>::value &&
std::is_integral<I>::value>* = nullptr>
static inline char* itoa(I i, char* p) {
// Need "mask" to be filled with a copy of the sign bit.
// If "i" is a negative value, then the result of "operator >>"
// is implementation-defined, though usually it is an arithmetic
// right shift that replicates the sign bit.
// Use a conditional expression to be portable,
// a good optimizing compiler generates an arithmetic right shift
// and avoids the conditional branch.
U<N> mask = i < 0 ? ~U<N>(0) : 0;
// Now get the absolute value of "i" and cast to unsigned type U<N>.
// Cannot use std::abs() because the result is undefined
// in 2's complement systems for the most-negative value.
// Want to avoid conditional branch for performance reasons since
// CPU branch prediction will be ineffective when negative values
// occur randomly.
// Let "u" be "i" cast to unsigned type U<N>.
// Subtract "u" from 2*u if "i" is positive or 0 if "i" is negative.
// This yields the absolute value with the desired type without
// using a conditional branch and without invoking undefined or
// implementation defined behavior:
U<N> u = ((2 * U<N>(i)) & ~mask) - U<N>(i);
// Unconditionally store a minus sign when producing digits
// in a forward direction and increment the pointer only if
// the value is in fact negative.
// This avoids a conditional branch and is safe because we will
// always produce at least one digit and it will overwrite the
// minus sign when the value is not negative.
if (D == Fwd) {
*p = '-';
p += (mask & 1);
}
p = convert<D>::template itoa<>(p, u);
if (D == Rev && mask)
*--p = '-';
return p;
}
};
} // namespace dec_
// Programming interface: itoa_fwd, itoa_rev
template <typename I>
char* itoa_fwd(I i, char* p) {
return dec_::convert<dec_::Fwd>::itoa(i, p);
}
inline char* xtoa(long long sval, char* str, int radix, int signedp) {
unsigned long long uval;
unsigned int uradix = radix;
char* sp = str;
char* sp2;
char* sp3;
/* If signed, store sign at start of buffer for negative base-10 values */
if (signedp && (10 == uradix) && (0 > sval)) {
*sp++ = '-';
uval = -sval;
}
else {
uval = sval;
}
sp2 = sp;
do {
unsigned int rem = uval % uradix;
uval /= uradix;
if (10 > rem) {
*sp++ = '0' + (char)rem;
}
else {
*sp++ = 'A' + (char)rem - 10;
}
} while (0 < uval);
/* Mark end of string */
sp3 = sp;
*sp-- = 0;
/* Reverse string contents (excluding sign) in place */
while (sp2 < sp) {
char tmp = *sp2;
*sp2++ = *sp;
*sp-- = tmp;
}
return sp3;
}
template <typename I>
char* itoa_rev(I i, char* p) {
return dec_::convert<dec_::Rev>::itoa(i, p);
}
#endif // DEC_ITOA_IMPL_H
+143
View File
@@ -0,0 +1,143 @@
#pragma once
#include <cstdint>
#include <type_traits>
namespace iguana {
struct sint32_t {
using value_type = int32_t;
int32_t val;
bool operator==(const sint32_t& other) const { return val == other.val; }
};
inline bool operator==(sint32_t value1, int32_t value2) {
return value1.val == value2;
}
// for key in std::map
inline bool operator<(const sint32_t& lhs, const sint32_t& rhs) {
return lhs.val < rhs.val;
}
struct sint64_t {
using value_type = int64_t;
int64_t val;
bool operator==(const sint64_t& other) const { return val == other.val; }
};
inline bool operator==(sint64_t value1, int64_t value2) {
return value1.val == value2;
}
inline bool operator<(const sint64_t& lhs, const sint64_t& rhs) {
return lhs.val < rhs.val;
}
struct fixed32_t {
using value_type = uint32_t;
uint32_t val;
bool operator==(const fixed32_t& other) const { return val == other.val; }
};
inline bool operator==(fixed32_t value1, uint32_t value2) {
return value1.val == value2;
}
inline bool operator<(const fixed32_t& lhs, const fixed32_t& rhs) {
return lhs.val < rhs.val;
}
struct fixed64_t {
using value_type = uint64_t;
uint64_t val;
bool operator==(const fixed64_t& other) const { return val == other.val; }
};
inline bool operator==(fixed64_t value1, uint64_t value2) {
return value1.val == value2;
}
inline bool operator<(const fixed64_t& lhs, const fixed64_t& rhs) {
return lhs.val < rhs.val;
}
struct sfixed32_t {
using value_type = int32_t;
int32_t val;
bool operator==(const sfixed32_t& other) const { return val == other.val; }
};
inline bool operator==(sfixed32_t value1, int32_t value2) {
return value1.val == value2;
}
inline bool operator<(const sfixed32_t& lhs, const sfixed32_t& rhs) {
return lhs.val < rhs.val;
}
struct sfixed64_t {
using value_type = int64_t;
int64_t val;
bool operator==(const sfixed64_t& other) const { return val == other.val; }
};
inline bool operator==(sfixed64_t value1, int64_t value2) {
return value1.val == value2;
}
inline bool operator<(const sfixed64_t& lhs, const sfixed64_t& rhs) {
return lhs.val < rhs.val;
}
template <typename Type, typename T = std::decay_t<Type>>
constexpr bool is_pb_type_v =
std::is_same_v<sint32_t, T> || std::is_same_v<sint64_t, T> ||
std::is_same_v<fixed32_t, T> || std::is_same_v<fixed64_t, T> ||
std::is_same_v<sfixed32_t, T> || std::is_same_v<sfixed64_t, T>;
} // namespace iguana
// for key in std::unordered_map
namespace std {
template <>
struct hash<iguana::sint32_t> {
size_t operator()(const iguana::sint32_t& x) const noexcept {
return std::hash<int32_t>()(x.val);
}
};
template <>
struct hash<iguana::sint64_t> {
size_t operator()(const iguana::sint64_t& x) const noexcept {
return std::hash<int64_t>()(x.val);
}
};
template <>
struct hash<iguana::fixed32_t> {
size_t operator()(const iguana::fixed32_t& x) const noexcept {
return std::hash<uint32_t>()(x.val);
}
};
template <>
struct hash<iguana::fixed64_t> {
size_t operator()(const iguana::fixed64_t& x) const noexcept {
return std::hash<uint64_t>()(x.val);
}
};
template <>
struct hash<iguana::sfixed32_t> {
size_t operator()(const iguana::sfixed32_t& x) const noexcept {
return std::hash<int32_t>()(x.val);
}
};
template <>
struct hash<iguana::sfixed64_t> {
size_t operator()(const iguana::sfixed64_t& x) const noexcept {
return std::hash<int64_t>()(x.val);
}
};
} // namespace std
+131
View File
@@ -0,0 +1,131 @@
#pragma once
#include <cstddef>
#include <string>
#include <utility>
#include <vector>
namespace iguana::detail {
#if __cpp_lib_string_resize_and_overwrite >= 202110L
template <typename ch>
inline void resize(std::basic_string<ch> &str, std::size_t sz) {
str.resize_and_overwrite(sz, [sz](ch *, std::size_t) {
return sz;
});
}
#elif (defined(_MSC_VER) && _MSC_VER <= 1920)
// old msvc don't support visit private, discard it.
#else
template <typename Function, Function func_ptr>
class string_thief {
public:
friend void string_set_length_hacker(std::string &self, std::size_t sz) {
#if defined(_MSVC_STL_VERSION)
(self.*func_ptr)._Myval2._Mysize = sz;
#else
#if defined(_LIBCPP_VERSION)
(self.*func_ptr)(sz);
#else
#if (_GLIBCXX_USE_CXX11_ABI == 0) && defined(__GLIBCXX__)
(self.*func_ptr)()->_M_set_length_and_sharable(sz);
#else
#if defined(__GLIBCXX__)
(self.*func_ptr)(sz);
#endif
#endif
#endif
#endif
}
};
#if defined(__GLIBCXX__) // libstdc++
#if (_GLIBCXX_USE_CXX11_ABI == 0)
template class string_thief<decltype(&std::string::_M_rep),
&std::string::_M_rep>;
#else
template class string_thief<decltype(&std::string::_M_set_length),
&std::string::_M_set_length>;
#endif
#elif defined(_LIBCPP_VERSION)
template class string_thief<decltype(&std::string::__set_size),
&std::string::__set_size>;
#elif defined(_MSVC_STL_VERSION)
template class string_thief<decltype(&std::string::_Mypair),
&std::string::_Mypair>;
#endif
void string_set_length_hacker(std::string &, std::size_t);
template <typename ch>
inline void resize(std::basic_string<ch> &raw_str, std::size_t sz) {
std::string &str = *reinterpret_cast<std::string *>(&raw_str);
#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) || \
defined(_MSVC_STL_VERSION)
if (sz > str.capacity()) {
str.reserve(sz);
}
string_set_length_hacker(str, sz);
str[sz] = '\0';
#else
raw_str.resize(sz);
#endif
}
#endif
#if (defined(_MSC_VER) && _MSC_VER <= 1920)
#else
void vector_set_length_hacker(std::vector<char> &self, std::size_t sz);
template <typename Function, Function func_ptr>
class vector_thief {
public:
friend void vector_set_length_hacker(std::vector<char> &self,
std::size_t sz) {
#if defined(_MSVC_STL_VERSION)
(self.*func_ptr)._Myval2._Mylast = self.data() + sz;
#else
#if defined(_LIBCPP_VERSION)
#if _LIBCPP_VERSION < 14000
((*(std::__vector_base<char, std::allocator<char> > *)(&self)).*func_ptr) =
self.data() + sz;
#else
(self.*func_ptr) = self.data() + sz;
#endif
#else
#if defined(__GLIBCXX__)
((*(std::_Vector_base<char, std::allocator<char> > *)(&self)).*func_ptr)
._M_finish = self.data() + sz;
#endif
#endif
#endif
}
};
#if defined(__GLIBCXX__) // libstdc++
template class vector_thief<decltype(&std::vector<char>::_M_impl),
&std::vector<char>::_M_impl>;
#elif defined(_LIBCPP_VERSION)
template class vector_thief<decltype(&std::vector<char>::__end_),
&std::vector<char>::__end_>;
#elif defined(_MSVC_STL_VERSION)
template class vector_thief<decltype(&std::vector<char>::_Mypair),
&std::vector<char>::_Mypair>;
#endif
template <typename ch>
inline void resize(std::vector<ch> &raw_vec, std::size_t sz) {
#if defined(__GLIBCXX__) || \
(defined(_LIBCPP_VERSION) && defined(_LIBCPP_HAS_NO_ASAN)) || \
defined(_MSVC_STL_VERSION)
std::vector<char> &vec = *reinterpret_cast<std::vector<char> *>(&raw_vec);
vec.reserve(sz);
vector_set_length_hacker(vec, sz);
#else
raw_vec.resize(sz);
#endif
}
#endif
}; // namespace iguana::detail
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include <string>
#if __has_include(<memory_resource>)
#include <memory_resource>
#endif
namespace iguana {
#ifdef IGUANA_ENABLE_PMR
#if __has_include(<memory_resource>)
inline char iguana_buf[2048];
inline std::pmr::monotonic_buffer_resource iguana_resource(iguana_buf, 2048);
using string_stream = std::pmr::string;
#endif
#else
using string_stream = std::string;
#endif
} // namespace iguana
+229
View File
@@ -0,0 +1,229 @@
//
// Created by QY on 2017-01-05.
//
#ifndef SERIALIZE_TRAITS_HPP
#define SERIALIZE_TRAITS_HPP
#include <deque>
#include <list>
#include <map>
#include <queue>
#include <type_traits>
#include <unordered_map>
#include <variant>
#include <vector>
#if __cplusplus > 201703L
#if __has_include(<span>)
#include <span>
#endif
#endif
#include "iguana/define.h"
namespace iguana {
template <class T>
struct is_signed_intergral_like
: std::integral_constant<bool, (std::is_integral<T>::value) &&
std::is_signed<T>::value> {};
template <class T>
struct is_unsigned_intergral_like
: std::integral_constant<bool, (std::is_integral<T>::value) &&
std::is_unsigned<T>::value> {};
template <template <typename...> class U, typename T>
struct is_template_instant_of : std::false_type {};
template <template <typename...> class U, typename... args>
struct is_template_instant_of<U, U<args...>> : std::true_type {};
template <typename T>
struct is_stdstring : is_template_instant_of<std::basic_string, T> {};
template <typename T>
struct is_tuple : is_template_instant_of<std::tuple, T> {};
#if __cplusplus > 201703L
#if __has_include(<span>)
template <typename>
struct is_span : std::false_type {};
template <typename T, size_t Extent>
struct is_span<std::span<T, Extent>> : std::true_type {};
#endif
#endif
template <class T>
struct is_sequence_container
: std::integral_constant<
bool, is_template_instant_of<std::deque, T>::value ||
is_template_instant_of<std::list, T>::value ||
is_template_instant_of<std::vector, T>::value ||
is_template_instant_of<std::queue, T>::value> {};
template <class T>
struct is_associat_container
: std::integral_constant<
bool, is_template_instant_of<std::map, T>::value ||
is_template_instant_of<std::unordered_map, T>::value> {};
template <class T>
struct is_emplace_back_able
: std::integral_constant<
bool, is_template_instant_of<std::deque, T>::value ||
is_template_instant_of<std::list, T>::value ||
is_template_instant_of<std::vector, T>::value> {};
template <typename T, typename Tuple>
struct has_type;
template <typename T, typename... Us>
struct has_type<T, std::tuple<Us...>>
: std::disjunction<std::is_same<T, Us>...> {};
template <class T>
struct member_tratis {};
template <class T, class Owner>
struct member_tratis<T Owner::*> {
using owner_type = Owner;
using value_type = T;
};
template <typename T>
inline constexpr bool is_int64_v =
std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>;
template <typename T>
struct is_variant : std::false_type {};
template <typename... T>
struct is_variant<std::variant<T...>> : std::true_type {};
template <class T>
struct member_traits {
using value_type = T;
};
template <class T, class Owner>
struct member_traits<T Owner::*> {
using owner_type = Owner;
using value_type = T;
};
template <class T>
using member_value_type_t = typename member_traits<T>::value_type;
template <typename T, std::size_t I, typename = void>
struct variant_type_at {
using type = T;
};
template <typename T, std::size_t I>
struct variant_type_at<T, I, std::enable_if_t<is_variant<T>::value>> {
using type = std::variant_alternative_t<I, T>;
};
template <typename T, std::size_t I>
using variant_type_at_t =
typename variant_type_at<typename member_traits<T>::value_type, I>::type;
/// concepts
template <typename Type>
constexpr bool is_char_v =
std::is_same_v<Type, signed char> || std::is_same_v<Type, char> ||
std::is_same_v<Type, unsigned char> || std::is_same_v<Type, wchar_t> ||
std::is_same_v<Type, char16_t> || std::is_same_v<Type, char32_t>
#ifdef __cpp_lib_char8_t
|| std::is_same_v<Type, char8_t>
#endif
;
#if __cplusplus >= 202002L
template <typename Type>
concept is_container_v = requires(Type container) {
typename std::remove_cvref_t<Type>::value_type;
container.size();
container.begin();
container.end();
};
template <typename Type>
concept is_resizable_char_container_v = requires(Type container) {
typename std::remove_cvref_t<Type>::value_type;
container.size();
container.begin();
container.end();
requires is_char_v<typename std::remove_cvref_t<Type>::value_type>;
container.resize(std::size_t{});
};
template <typename T>
concept char_writer = requires(T t) {
t.write((const char *)nullptr, std::size_t{});
};
template <typename T>
concept char_reader = requires(T t) {
t.read((char *)nullptr, std::size_t{});
};
#else
template <typename T, typename = void>
struct container_impl : std::false_type {};
template <typename T>
struct container_impl<T,
std::void_t<typename std::remove_cvref_t<T>::value_type,
decltype(std::declval<T>().size()),
decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>>
: std::true_type {};
template <typename T>
constexpr bool is_container_v = container_impl<T>::value;
template <typename T, typename = void>
struct resizable_char_container_impl : std::false_type {};
template <typename T>
struct resizable_char_container_impl<
T, std::void_t<typename std::remove_cvref_t<T>::value_type,
decltype(std::declval<T>().size()),
decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end()),
std::enable_if_t<
is_char_v<typename std::remove_cvref_t<T>::value_type>>,
decltype(std::declval<T>().resize(std::size_t{}))>>
: std::true_type {};
template <typename T>
constexpr bool is_resizable_char_container_v =
resizable_char_container_impl<T>::value;
template <typename T, typename = void>
struct char_writer_impl : std::false_type {};
template <typename T>
struct char_writer_impl<T, std::void_t<decltype(std::declval<T>().write(
(const char *)nullptr, std::size_t{}))>>
: std::true_type {};
template <typename T>
constexpr bool char_writer = char_writer_impl<T>::value;
template <typename T, typename = void>
struct char_reader_impl : std::false_type {};
template <typename T>
struct char_reader_impl<T, std::void_t<decltype(std::declval<T>().read(
(char *)nullptr, std::size_t{}))>>
: std::true_type {};
template <typename T>
constexpr bool char_reader = char_reader_impl<T>::value;
#endif
} // namespace iguana
#endif // SERIALIZE_TRAITS_HPP
+154
View File
@@ -0,0 +1,154 @@
#pragma once
#include <cassert>
#include <stdexcept>
#include "iguana/define.h"
namespace iguana {
// https://github.com/Tencent/rapidjson/blob/master/include/rapidjson/reader.h
template <typename Ch = char, typename It>
inline unsigned parse_unicode_hex4(It &&it) {
unsigned codepoint = 0;
for (int i = 0; i < 4; i++) {
Ch c = *it;
codepoint <<= 4;
codepoint += static_cast<unsigned>(c);
if (c >= '0' && c <= '9')
codepoint -= '0';
else if (c >= 'A' && c <= 'F')
codepoint -= 'A' - 10;
else if (c >= 'a' && c <= 'f')
codepoint -= 'a' - 10;
else {
throw std::runtime_error("Invalid Unicode Escape Hex");
}
++it;
}
return codepoint;
}
// https://github.com/Tencent/rapidjson/blob/master/include/rapidjson/encodings.h
template <typename Ch = char, typename OutputStream>
inline void encode_utf8(OutputStream &os, unsigned codepoint) {
if (codepoint > 0x10FFFF)
IGUANA_UNLIKELY {
throw std::runtime_error("Invalid unicode codepoint:" +
std::to_string(codepoint));
}
if (codepoint <= 0x7F)
os.push_back(static_cast<Ch>(codepoint & 0xFF));
else if (codepoint <= 0x7FF) {
os.push_back(static_cast<Ch>(0xC0 | ((codepoint >> 6) & 0xFF)));
os.push_back(static_cast<Ch>(0x80 | ((codepoint & 0x3F))));
}
else if (codepoint <= 0xFFFF) {
os.push_back(static_cast<Ch>(0xE0 | ((codepoint >> 12) & 0xFF)));
os.push_back(static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F)));
os.push_back(static_cast<Ch>(0x80 | (codepoint & 0x3F)));
}
else {
os.push_back(static_cast<Ch>(0xF0 | ((codepoint >> 18) & 0xFF)));
os.push_back(static_cast<Ch>(0x80 | ((codepoint >> 12) & 0x3F)));
os.push_back(static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F)));
os.push_back(static_cast<Ch>(0x80 | (codepoint & 0x3F)));
}
}
// https://github.com/Tencent/rapidjson/blob/master/include/rapidjson/encodings.h
static inline unsigned char GetRange(unsigned char c) {
static const unsigned char type[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0x10, 0x10, 0x10, 0x10,
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3,
11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8,
};
return type[c];
}
// https://github.com/Tencent/rapidjson/blob/master/include/rapidjson/encodings.h
template <typename Ch = char, typename It>
inline bool decode_utf8(It &&it, unsigned &codepoint) {
auto c = *(it++);
bool result = true;
auto copy = [&]() IGUANA__INLINE_LAMBDA {
c = *(it++);
codepoint = (codepoint << 6) | (static_cast<unsigned char>(c) & 0x3Fu);
};
auto trans = [&](unsigned mask) IGUANA__INLINE_LAMBDA {
result &= ((GetRange(static_cast<unsigned char>(c)) & mask) != 0);
};
auto tail = [&]() IGUANA__INLINE_LAMBDA {
copy();
trans(0x70);
};
if (!(c & 0x80)) {
codepoint = static_cast<unsigned char>(c);
return true;
}
unsigned char type = GetRange(static_cast<unsigned char>(c));
if (type >= 32) {
codepoint = 0;
}
else {
codepoint = (0xFFu >> type) & static_cast<unsigned char>(c);
}
switch (type) {
case 2:
tail();
return result;
case 3:
tail();
tail();
return result;
case 4:
copy();
trans(0x50);
tail();
return result;
case 5:
copy();
trans(0x10);
tail();
tail();
return result;
case 6:
tail();
tail();
tail();
return result;
case 10:
copy();
trans(0x20);
tail();
return result;
case 11:
copy();
trans(0x60);
tail();
tail();
return result;
default:
return false;
}
}
} // namespace iguana
+197
View File
@@ -0,0 +1,197 @@
#pragma once
#include "common.hpp"
namespace iguana {
using base = detail::base;
constexpr inline uint8_t ENABLE_JSON = 0x01;
constexpr inline uint8_t ENABLE_YAML = 0x02;
constexpr inline uint8_t ENABLE_XML = 0x04;
constexpr inline uint8_t ENABLE_PB = 0x08;
constexpr inline uint8_t ENABLE_ALL = 0x0F;
template <typename T, uint8_t ENABLE_FLAG = ENABLE_PB>
struct base_impl : public base {
base_impl() { [[maybe_unused]] static bool r = register_type<T>(); }
void to_pb(std::string& str) const override {
if constexpr ((ENABLE_FLAG & ENABLE_PB) != 0) {
to_pb_adl((iguana_adl_t*)nullptr, *(static_cast<T const*>(this)), str);
}
else {
throw std::runtime_error("Protobuf Disabled");
}
}
void from_pb(std::string_view str) override {
if constexpr ((ENABLE_FLAG & ENABLE_PB) != 0) {
from_pb_adl((iguana_adl_t*)nullptr, *(static_cast<T*>(this)), str);
}
else {
throw std::runtime_error("Protobuf Disabled");
}
}
void to_json(std::string& str) const override {
if constexpr ((ENABLE_FLAG & ENABLE_JSON) != 0) {
to_json_adl((iguana_adl_t*)nullptr, *(static_cast<T const*>(this)), str);
}
else {
throw std::runtime_error("Json Disabled");
}
}
void from_json(std::string_view str) override {
if constexpr ((ENABLE_FLAG & ENABLE_JSON) != 0) {
from_json_adl((iguana_adl_t*)nullptr, *(static_cast<T*>(this)), str);
}
else {
throw std::runtime_error("Json Disabled");
}
}
void to_xml(std::string& str) const override {
if constexpr ((ENABLE_FLAG & ENABLE_XML) != 0) {
to_xml_adl((iguana_adl_t*)nullptr, *(static_cast<T const*>(this)), str);
}
else {
throw std::runtime_error("Xml Disabled");
}
}
void from_xml(std::string_view str) override {
if constexpr ((ENABLE_FLAG & ENABLE_XML) != 0) {
from_xml_adl((iguana_adl_t*)nullptr, *(static_cast<T*>(this)), str);
}
else {
throw std::runtime_error("Xml Disabled");
}
}
void to_yaml(std::string& str) const override {
if constexpr ((ENABLE_FLAG & ENABLE_YAML) != 0) {
to_yaml_adl((iguana_adl_t*)nullptr, *(static_cast<T const*>(this)), str);
}
else {
throw std::runtime_error("Yaml Disabled");
}
}
void from_yaml(std::string_view str) override {
if constexpr ((ENABLE_FLAG & ENABLE_YAML) != 0) {
from_yaml_adl((iguana_adl_t*)nullptr, *(static_cast<T*>(this)), str);
}
else {
throw std::runtime_error("Yaml Disabled");
}
}
iguana::detail::field_info get_field_info(
std::string_view name) const override {
static auto map =
detail::get_members(ylt::reflection::internal::wrapper<T>::value);
iguana::detail::field_info info{};
for (auto const& [no, field] : map) {
if (info.offset > 0) {
break;
}
std::visit(
[&](auto const& val) {
if (val.field_name == name) {
info.offset = val.offset;
using value_type =
typename std::remove_reference_t<decltype(val)>::value_type;
#if defined(__clang__) || defined(_MSC_VER) || \
(defined(__GNUC__) && __GNUC__ > 8)
info.type_name = type_string<value_type>();
#endif
}
},
field);
}
return info;
}
std::vector<std::string_view> get_fields_name() const override {
static auto map =
detail::get_members(ylt::reflection::internal::wrapper<T>::value);
std::vector<std::string_view> vec;
for (auto const& [no, val] : map) {
std::visit(
[&](auto const& field) {
std::string_view const current_name{field.field_name.data(),
field.field_name.size()};
if (vec.empty() || (vec.back() != current_name)) {
vec.push_back(current_name);
}
},
val);
}
return vec;
}
std::any get_field_any(std::string_view name) const override {
static auto map =
detail::get_members(ylt::reflection::internal::wrapper<T>::value);
std::any result;
for (auto const& [no, field] : map) {
if (result.has_value()) {
break;
}
std::visit(
[&](auto const& val) {
if (val.field_name == name) {
using value_type =
typename std::remove_reference_t<decltype(val)>::value_type;
auto ptr =
reinterpret_cast<const char*>(static_cast<const T*>(this)) +
val.offset;
result = *((value_type*)ptr);
}
},
field);
}
return result;
}
virtual ~base_impl() {}
mutable size_t cache_size = 0;
protected:
void* object_ptr() override { return static_cast<T*>(this); }
const void* object_ptr() const override {
return static_cast<const T*>(this);
}
private:
virtual void dummy() {
// make sure init T before main, and can register_type before main.
[[maybe_unused]] static auto t =
ylt::reflection::internal::wrapper<T>::value;
}
};
IGUANA_INLINE std::shared_ptr<base> create_instance(std::string_view name) {
auto it = iguana::detail::g_pb_map.find(name);
if (it == iguana::detail::g_pb_map.end()) {
throw std::invalid_argument(std::string(name) +
"not inheried from iguana::base_impl");
}
return it->second();
}
} // namespace iguana
#ifdef YLT_USE_CXX26_REFLECTION
namespace ylt::reflection::reflect26 {
template <>
constexpr inline bool skip_base_v<iguana::detail::base> = true;
template <typename T, uint8_t ENABLE_FLAG>
constexpr inline bool skip_base_v<iguana::base_impl<T, ENABLE_FLAG>> = true;
} // namespace ylt::reflection::reflect26
#endif
+100
View File
@@ -0,0 +1,100 @@
#pragma once
#include <cassert>
#include <map>
#include <system_error>
namespace iguana {
class iguana_category : public std::error_category {
public:
virtual const char *name() const noexcept override {
return "iguana::category";
}
virtual std::string message(int err_val) const override {
for (auto &pair : err_map_) {
if (pair.second == err_val) {
return pair.first;
}
}
return "unrecognized error";
}
int add_message(const std::string &msg) {
if (auto it = err_map_.find(msg); it != err_map_.end()) {
return it->second;
}
else {
err_++;
err_map_.emplace(msg, err_);
return err_;
}
}
int err_ = 0;
std::map<std::string, int> err_map_;
};
inline iguana::iguana_category &category() {
static iguana::iguana_category instance;
return instance;
}
inline std::error_code make_error_code(const std::string data = "") {
assert(!data.empty());
auto err = iguana::category().add_message(data);
return std::error_code(err, iguana::category());
}
// dom parse error code
enum class dom_errc {
ok = 0,
wrong_type,
};
class iguana_dom_category : public std::error_category {
public:
virtual const char *name() const noexcept override {
return "iguana::dom_category";
}
virtual std::string message(int err_val) const override {
switch (static_cast<dom_errc>(err_val)) {
case dom_errc::ok:
return "ok";
case dom_errc::wrong_type: {
auto it = detail_msg_map_.find(dom_errc::wrong_type);
if (it != detail_msg_map_.end()) {
return std::string("wrong type, ")
.append("real type is ")
.append(it->second);
}
else {
return "wrong type";
}
}
default:
return "(unrecognized error)";
}
}
// private:
std::map<iguana::dom_errc, std::string> detail_msg_map_;
};
inline iguana::iguana_dom_category &dom_category() {
static iguana::iguana_dom_category instance;
return instance;
}
inline std::error_code make_error_code(iguana::dom_errc err,
const std::string &error_msg) {
auto &instance = iguana::dom_category();
if (!error_msg.empty()) {
instance.detail_msg_map_.emplace(err, error_msg);
}
return std::error_code((int)err, instance);
}
} // namespace iguana
+192
View File
@@ -0,0 +1,192 @@
#pragma once
#include <array>
#include <string_view>
#include "frozen/string.h"
#include "frozen/unordered_map.h"
namespace iguana {
#if defined(__clang__) || defined(_MSC_VER) || \
(defined(__GNUC__) && __GNUC__ > 8)
template <typename T>
constexpr std::string_view get_raw_name() {
#ifdef _MSC_VER
return __FUNCSIG__;
#else
return __PRETTY_FUNCTION__;
#endif
}
template <auto T>
constexpr std::string_view get_raw_name() {
#ifdef _MSC_VER
return __FUNCSIG__;
#else
return __PRETTY_FUNCTION__;
#endif
}
template <typename T>
inline constexpr std::string_view type_string() {
constexpr std::string_view sample = get_raw_name<int>();
constexpr size_t pos = sample.find("int");
constexpr std::string_view str = get_raw_name<T>();
constexpr auto next1 = str.rfind(sample[pos + 3]);
#if defined(_MSC_VER)
constexpr std::size_t npos = str.find_first_of(" ", pos);
if (npos != std::string_view::npos)
return str.substr(npos + 1, next1 - npos - 1);
else
return str.substr(pos, next1 - pos);
#else
return str.substr(pos, next1 - pos);
#endif
}
template <auto T>
inline constexpr std::string_view enum_string() {
constexpr std::string_view sample = get_raw_name<int>();
constexpr size_t pos = sample.find("int");
constexpr std::string_view str = get_raw_name<T>();
constexpr auto next1 = str.rfind(sample[pos + 3]);
#if defined(__clang__) || defined(_MSC_VER)
constexpr auto s1 = str.substr(pos, next1 - pos);
#else
constexpr auto s1 = str.substr(pos + 5, next1 - pos - 5);
#endif
return s1;
}
template <auto field>
inline constexpr std::string_view field_string() {
constexpr std::string_view raw_name = enum_string<field>();
return raw_name.substr(raw_name.rfind(":") + 1);
}
#if defined(__clang__) && (__clang_major__ >= 17)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wenum-constexpr-conversion"
#endif
template <typename E, E V>
constexpr std::string_view get_raw_name_with_v() {
#ifdef _MSC_VER
return __FUNCSIG__;
#else
return __PRETTY_FUNCTION__;
#endif
}
// True means the V is a valid enum value, and the second of the result is the
// name
template <typename E, E V>
constexpr std::pair<bool, std::string_view> try_get_enum_name() {
constexpr std::string_view sample_raw_name = get_raw_name_with_v<int, 5>();
constexpr size_t pos = sample_raw_name.find("5");
constexpr std::string_view enum_raw_name = get_raw_name_with_v<E, V>();
constexpr auto enum_end = enum_raw_name.rfind(&sample_raw_name[pos + 1]);
#ifdef _MSC_VER
constexpr auto enum_begin = enum_raw_name.rfind(',', enum_end) + 1;
#else
constexpr auto enum_begin = enum_raw_name.rfind(' ', enum_end) + 1;
#endif
constexpr std::string_view res =
enum_raw_name.substr(enum_begin, enum_end - enum_begin);
constexpr size_t pos_brackets = res.find(')');
size_t pos_colon = res.find("::");
return {pos_brackets == std::string_view::npos,
res.substr(pos_colon == std::string_view::npos ? 0 : pos_colon + 2)};
}
// Enumerate the numbers in a integer sequence to see if they are legal enum
// value
template <typename E, std::int64_t... Is>
constexpr inline auto get_enum_arr(
const std::integer_sequence<std::int64_t, Is...> &) {
constexpr std::size_t N = sizeof...(Is);
std::array<std::string_view, N> enum_names = {};
std::array<E, N> enum_values = {};
std::size_t num = 0;
(([&]() {
constexpr auto res = try_get_enum_name<E, static_cast<E>(Is)>();
if constexpr (res.first) {
// the Is is a valid enum value
enum_names[num] = res.second;
enum_values[num] = static_cast<E>(Is);
++num;
}
})(),
...);
return std::make_tuple(num, enum_values, enum_names);
}
template <std::size_t N, const std::array<int, N> &arr, size_t... Is>
constexpr auto array_to_seq(const std::index_sequence<Is...> &) {
return std::integer_sequence<std::int64_t, arr[Is]...>();
}
// convert array to map
template <typename E, size_t N, size_t... Is>
constexpr inline auto get_enum_to_str_map(
const std::array<std::string_view, N> &enum_names,
const std::array<E, N> &enum_values, const std::index_sequence<Is...> &) {
return frozen::unordered_map<E, frozen::string, sizeof...(Is)>{
{enum_values[Is], enum_names[Is]}...};
}
template <typename E, size_t N, size_t... Is>
constexpr inline auto get_str_to_enum_map(
const std::array<std::string_view, N> &enum_names,
const std::array<E, N> &enum_values, const std::index_sequence<Is...> &) {
return frozen::unordered_map<frozen::string, E, sizeof...(Is)>{
{enum_names[Is], enum_values[Is]}...};
}
#endif
// the default generic enum_value
// if the user has not defined a specialization template, this will be called
template <typename T>
struct enum_value {
constexpr static std::array<int, 0> value = {};
};
template <bool str_to_enum, typename E>
constexpr inline auto get_enum_map() {
#if defined(__clang__) || defined(_MSC_VER) || \
(defined(__GNUC__) && __GNUC__ > 8)
constexpr auto &arr = enum_value<E>::value;
constexpr auto arr_size = arr.size();
if constexpr (arr_size > 0) {
// the user has defined a specialization template
constexpr auto arr_seq =
array_to_seq<arr_size, arr>(std::make_index_sequence<arr_size>());
constexpr auto t = get_enum_arr<E>(arr_seq);
if constexpr (str_to_enum) {
return get_str_to_enum_map<E, arr_size>(
std::get<2>(t), std::get<1>(t),
std::make_index_sequence<std::get<0>(t)>{});
}
else {
return get_enum_to_str_map<E, arr_size>(
std::get<2>(t), std::get<1>(t),
std::make_index_sequence<std::get<0>(t)>{});
}
}
else {
return false;
}
#else
return false;
#endif
}
#if defined(__clang__) && (__clang_major__ >= 17)
#pragma clang diagnostic pop
#endif
} // namespace iguana

Some files were not shown because too many files have changed in this diff Show More