feat/vtk-merged-dataset-column #10
|
|
@ -16,6 +16,7 @@
|
||||||
#include "data/store/ChunkedVolumeStore.hpp"
|
#include "data/store/ChunkedVolumeStore.hpp"
|
||||||
#include "io/gpr/GprSurveyAssembler.hpp"
|
#include "io/gpr/GprSurveyAssembler.hpp"
|
||||||
#include "io/gpr/IprHeader.hpp"
|
#include "io/gpr/IprHeader.hpp"
|
||||||
|
#include "io/gpr/LocalPath.hpp"
|
||||||
|
|
||||||
namespace geopro::data {
|
namespace geopro::data {
|
||||||
|
|
||||||
|
|
@ -49,7 +50,7 @@ std::string toHeaderPath(const std::string& iprbPath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string readFileText(const std::string& path) {
|
std::string readFileText(const std::string& path) {
|
||||||
std::ifstream f(path, std::ios::binary);
|
std::ifstream f(geopro::io::gpr::localPath(path), std::ios::binary);
|
||||||
if (!f) throw std::runtime_error("StreamingVolumeBuilder: 无法打开 " + path);
|
if (!f) throw std::runtime_error("StreamingVolumeBuilder: 无法打开 " + path);
|
||||||
std::ostringstream ss;
|
std::ostringstream ss;
|
||||||
ss << f.rdbuf();
|
ss << f.rdbuf();
|
||||||
|
|
@ -68,7 +69,7 @@ std::int64_t totalTraces(const std::vector<std::string>& iprb, double& surveyDx)
|
||||||
throw std::runtime_error("StreamingVolumeBuilder: samples<=0");
|
throw std::runtime_error("StreamingVolumeBuilder: samples<=0");
|
||||||
if (c == 0) surveyDx = h.distanceInterval;
|
if (c == 0) surveyDx = h.distanceInterval;
|
||||||
const std::int64_t bytes =
|
const std::int64_t bytes =
|
||||||
static_cast<std::int64_t>(fs::file_size(fs::path(iprb[c])));
|
static_cast<std::int64_t>(fs::file_size(geopro::io::gpr::localPath(iprb[c])));
|
||||||
const std::int64_t per = static_cast<std::int64_t>(h.samples) * 2;
|
const std::int64_t per = static_cast<std::int64_t>(h.samples) * 2;
|
||||||
if (per <= 0 || bytes % per != 0)
|
if (per <= 0 || bytes % per != 0)
|
||||||
throw std::runtime_error("StreamingVolumeBuilder: .iprb 字节非整道");
|
throw std::runtime_error("StreamingVolumeBuilder: .iprb 字节非整道");
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
add_library(geopro_io_gpr STATIC
|
add_library(geopro_io_gpr STATIC
|
||||||
IprHeader.cpp IprbReader.cpp GprGeometry.cpp GprSurveyAssembler.cpp
|
IprHeader.cpp IprbReader.cpp GprGeometry.cpp GprSurveyAssembler.cpp
|
||||||
GpsTrack.cpp NormalizedRadarReader.cpp
|
GpsTrack.cpp NormalizedRadarReader.cpp LocalPath.cpp
|
||||||
RadarVolumeAssembler.cpp NormalizedRadarVolumeBridge.cpp)
|
RadarVolumeAssembler.cpp NormalizedRadarVolumeBridge.cpp)
|
||||||
target_include_directories(geopro_io_gpr PUBLIC ${CMAKE_SOURCE_DIR}/src)
|
target_include_directories(geopro_io_gpr PUBLIC ${CMAKE_SOURCE_DIR}/src)
|
||||||
target_compile_features(geopro_io_gpr PUBLIC cxx_std_17)
|
target_compile_features(geopro_io_gpr PUBLIC cxx_std_17)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include "io/gpr/GprGeometry.hpp"
|
#include "io/gpr/GprGeometry.hpp"
|
||||||
#include "io/gpr/IprHeader.hpp"
|
#include "io/gpr/IprHeader.hpp"
|
||||||
#include "io/gpr/IprbReader.hpp"
|
#include "io/gpr/IprbReader.hpp"
|
||||||
|
#include "io/gpr/LocalPath.hpp"
|
||||||
|
|
||||||
namespace geopro::io::gpr {
|
namespace geopro::io::gpr {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
@ -27,7 +28,7 @@ std::string toHeaderPath(const std::string& iprbPath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string readFileText(const std::string& path) {
|
std::string readFileText(const std::string& path) {
|
||||||
std::ifstream f(path, std::ios::binary);
|
std::ifstream f(localPath(path), std::ios::binary);
|
||||||
if (!f) {
|
if (!f) {
|
||||||
throw std::runtime_error("无法打开文件: " + path);
|
throw std::runtime_error("无法打开文件: " + path);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "io/gpr/LocalPath.hpp"
|
||||||
|
|
||||||
namespace geopro::io::gpr {
|
namespace geopro::io::gpr {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
@ -26,7 +28,7 @@ bool parseDouble(const std::string& s, double& out) {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
GpsTrack parseGps(const std::string& path) {
|
GpsTrack parseGps(const std::string& path) {
|
||||||
std::ifstream f(path);
|
std::ifstream f(localPath(path));
|
||||||
if (!f) throw std::runtime_error("parseGps: 打不开 " + path);
|
if (!f) throw std::runtime_error("parseGps: 打不开 " + path);
|
||||||
|
|
||||||
GpsTrack track;
|
GpsTrack track;
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "io/gpr/LocalPath.hpp"
|
||||||
|
|
||||||
namespace geopro::io::gpr {
|
namespace geopro::io::gpr {
|
||||||
|
|
||||||
BScan readIprb(const std::string& path, const IprHeader& h) {
|
BScan readIprb(const std::string& path, const IprHeader& h) {
|
||||||
std::ifstream f(path, std::ios::binary | std::ios::ate);
|
std::ifstream f(localPath(path), std::ios::binary | std::ios::ate);
|
||||||
if (!f) {
|
if (!f) {
|
||||||
throw std::runtime_error("readIprb: 无法打开文件: " + path);
|
throw std::runtime_error("readIprb: 无法打开文件: " + path);
|
||||||
}
|
}
|
||||||
|
|
@ -43,7 +45,7 @@ BScan readIprb(const std::string& path, const IprHeader& h) {
|
||||||
|
|
||||||
BScan readIprbRange(const std::string& path, const IprHeader& h,
|
BScan readIprbRange(const std::string& path, const IprHeader& h,
|
||||||
std::int64_t t0, std::int64_t t1) {
|
std::int64_t t0, std::int64_t t1) {
|
||||||
std::ifstream f(path, std::ios::binary | std::ios::ate);
|
std::ifstream f(localPath(path), std::ios::binary | std::ios::ate);
|
||||||
if (!f) {
|
if (!f) {
|
||||||
throw std::runtime_error("readIprbRange: 无法打开文件: " + path);
|
throw std::runtime_error("readIprbRange: 无法打开文件: " + path);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
#include "io/gpr/LocalPath.hpp"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace geopro::io::gpr {
|
||||||
|
|
||||||
|
std::filesystem::path localPath(const std::string& p) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (p.empty()) return std::filesystem::path{};
|
||||||
|
const int wlen = ::MultiByteToWideChar(
|
||||||
|
CP_ACP, 0, p.data(), static_cast<int>(p.size()), nullptr, 0);
|
||||||
|
if (wlen <= 0) return std::filesystem::path(p); // 退化:原样(ASCII 安全)
|
||||||
|
std::wstring w(static_cast<std::size_t>(wlen), L'\0');
|
||||||
|
::MultiByteToWideChar(CP_ACP, 0, p.data(), static_cast<int>(p.size()),
|
||||||
|
w.data(), wlen);
|
||||||
|
return std::filesystem::path(w);
|
||||||
|
#else
|
||||||
|
return std::filesystem::path(p); // POSIX:窄字节即 UTF-8 原生路径
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace geopro::io::gpr
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace geopro::io::gpr {
|
||||||
|
|
||||||
|
// 把"本地 8 位编码"的窄字节路径转成 std::filesystem::path。
|
||||||
|
// Windows:源串按当前 ANSI 代码页(简中=GBK/936,即 QString::toLocal8Bit 产物)
|
||||||
|
// 解码为宽字符 path,使 std::ifstream/ofstream 走宽字符打开 —— 否则
|
||||||
|
// 窄字符 ifstream 在默认 "C" locale 下无法解析含中文的路径,open 失败。
|
||||||
|
// 其它平台:窄字节即原生 UTF-8 路径,直接构造。
|
||||||
|
// 退化保护:转换失败时原样返回(ASCII 路径不受影响)。
|
||||||
|
std::filesystem::path localPath(const std::string& p);
|
||||||
|
|
||||||
|
} // namespace geopro::io::gpr
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include "io/gpr/NormalizedRadarReader.hpp"
|
#include "io/gpr/NormalizedRadarReader.hpp"
|
||||||
|
#include "io/gpr/LocalPath.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
@ -76,11 +77,11 @@ std::vector<std::int16_t> readRadarDataCube(const std::string& dataPath,
|
||||||
const std::size_t n = static_cast<std::size_t>(h.lastTrace) * h.samples;
|
const std::size_t n = static_cast<std::size_t>(h.lastTrace) * h.samples;
|
||||||
const std::uintmax_t expect = static_cast<std::uintmax_t>(n) * 2;
|
const std::uintmax_t expect = static_cast<std::uintmax_t>(n) * 2;
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
const auto fsize = std::filesystem::file_size(dataPath, ec);
|
const auto fsize = std::filesystem::file_size(localPath(dataPath), ec);
|
||||||
if (ec || fsize != expect)
|
if (ec || fsize != expect)
|
||||||
throw std::runtime_error("规范化 .data 大小不符: " + dataPath);
|
throw std::runtime_error("规范化 .data 大小不符: " + dataPath);
|
||||||
std::vector<std::int16_t> cube(n);
|
std::vector<std::int16_t> cube(n);
|
||||||
std::ifstream f(dataPath, std::ios::binary);
|
std::ifstream f(localPath(dataPath), std::ios::binary);
|
||||||
if (!f) throw std::runtime_error("打开 .data 失败: " + dataPath);
|
if (!f) throw std::runtime_error("打开 .data 失败: " + dataPath);
|
||||||
f.read(reinterpret_cast<char*>(cube.data()), static_cast<std::streamsize>(expect));
|
f.read(reinterpret_cast<char*>(cube.data()), static_cast<std::streamsize>(expect));
|
||||||
if (!f) throw std::runtime_error("读 .data 失败: " + dataPath);
|
if (!f) throw std::runtime_error("读 .data 失败: " + dataPath);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "io/gpr/LocalPath.hpp"
|
||||||
#include "io/gpr/NormalizedRadarReader.hpp"
|
#include "io/gpr/NormalizedRadarReader.hpp"
|
||||||
#include "io/gpr/RadarVolumeAssembler.hpp"
|
#include "io/gpr/RadarVolumeAssembler.hpp"
|
||||||
|
|
||||||
|
|
@ -20,7 +21,7 @@ geopro::core::BuiltI16 buildLineVolumeFromNormalized(const std::string& lineDir,
|
||||||
|
|
||||||
std::string headText;
|
std::string headText;
|
||||||
{
|
{
|
||||||
std::ifstream f(head);
|
std::ifstream f(localPath(head));
|
||||||
if (!f) throw std::runtime_error("打开 .head 失败: " + head);
|
if (!f) throw std::runtime_error("打开 .head 失败: " + head);
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << f.rdbuf();
|
ss << f.rdbuf();
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,29 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
#include <clocale>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
#include "core/algo/GprVolumeBuilder.hpp"
|
#include "core/algo/GprVolumeBuilder.hpp"
|
||||||
#include "io/gpr/NormalizedRadarVolumeBridge.hpp"
|
#include "io/gpr/NormalizedRadarVolumeBridge.hpp"
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// 在指定目录写出一组最小可解析的规范化 .head/.data(K=4 道 M=2 通道 N=3 采样)。
|
||||||
|
void writeMinimalLine(const fs::path& head, const fs::path& data) {
|
||||||
|
{ std::ofstream f(head);
|
||||||
|
f << "SAMPLES:3\nNUMBER_OF_CH:2\nLAST_TRACE:8\nBITS:16\nENDIAN_TYPE:1\n"
|
||||||
|
"DISTANCE_INTERVAL:0.1\nTIMEWINDOW:30\nDIELECTRIC:9\n"; }
|
||||||
|
{ std::ofstream f(data, std::ios::binary);
|
||||||
|
for (int t = 0; t < 4; ++t) for (int c = 0; c < 2; ++c) for (int s = 0; s < 3; ++s) {
|
||||||
|
std::int16_t v = static_cast<std::int16_t>(t * 10 + c * 100 + s);
|
||||||
|
f.write(reinterpret_cast<const char*>(&v), 2); } }
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
TEST(NormalizedRadarBridge, BuildsVolumeWithExpectedAxes) {
|
TEST(NormalizedRadarBridge, BuildsVolumeWithExpectedAxes) {
|
||||||
// K=4 道, M=2 通道, N=3 采样, 无通道偏移(不插值), coarse=1。
|
// K=4 道, M=2 通道, N=3 采样, 无通道偏移(不插值), coarse=1。
|
||||||
fs::path dir = fs::temp_directory_path() / "radar_bridge_test";
|
fs::path dir = fs::temp_directory_path() / "radar_bridge_test";
|
||||||
|
|
@ -26,3 +44,55 @@ TEST(NormalizedRadarBridge, BuildsVolumeWithExpectedAxes) {
|
||||||
EXPECT_GT(b.spacing[2], 0.0); // dz 由 timewindow/dielectric 求得 >0
|
EXPECT_GT(b.spacing[2], 0.0); // dz 由 timewindow/dielectric 求得 >0
|
||||||
EXPECT_NEAR(b.quant.toPhys(b.vol.at(3, 1, 2)), 132.0, b.quant.scale); // t3c1s2=30+100+2
|
EXPECT_NEAR(b.quant.toPhys(b.vol.at(3, 1, 2)), 132.0, b.quant.scale); // t3c1s2=30+100+2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 回归:中文目录路径必须能打开渲染。app 传入的是 QString::toLocal8Bit(),即当前
|
||||||
|
// ANSI 代码页(简中=GBK)窄字节。关键复现条件——GUI app 链接 QWebEngine(Chromium)/VTK,
|
||||||
|
// 它们在启动时 setlocale(LC_ALL,"") 把 LC_CTYPE 提升为系统 UTF-8 locale;此后窄字符
|
||||||
|
// ifstream 会把 GBK 路径字节当 UTF-8 解析 → open 失败(即"打开 .head 失败")。
|
||||||
|
// 故本测试显式置 UTF-8 locale 复现该失败面,走宽字符打开(见 io/gpr/LocalPath)守护回归。
|
||||||
|
// (纯 "C" locale 下 UCRT 用 CP_ACP=GBK 解窄路径,反而不失败,无法复现——须置 UTF-8。)
|
||||||
|
TEST(NormalizedRadarBridge, OpensCjkDirectoryPathUnderUtf8Locale) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
const std::wstring wname = L"radar_cjk_南同大道";
|
||||||
|
const fs::path dir = fs::temp_directory_path() / wname;
|
||||||
|
fs::remove_all(dir);
|
||||||
|
fs::create_directories(dir);
|
||||||
|
writeMinimalLine(dir / L"南同大道_000.head", dir / L"南同大道_000.data");
|
||||||
|
|
||||||
|
// 模拟 app:宽字符 → 当前 ANSI 代码页(GBK)窄字节,等价 QString::toLocal8Bit()。
|
||||||
|
auto toAcp = [](const std::wstring& w) {
|
||||||
|
const int n = ::WideCharToMultiByte(CP_ACP, 0, w.data(),
|
||||||
|
static_cast<int>(w.size()), nullptr, 0,
|
||||||
|
nullptr, nullptr);
|
||||||
|
std::string s(static_cast<std::size_t>(n), '\0');
|
||||||
|
::WideCharToMultiByte(CP_ACP, 0, w.data(), static_cast<int>(w.size()),
|
||||||
|
s.data(), n, nullptr, nullptr);
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
const std::string dirAcp = toAcp(dir.wstring());
|
||||||
|
const std::string prefixAcp = toAcp(L"南同大道_000");
|
||||||
|
|
||||||
|
// 复现 app 运行期的 UTF-8 C locale(QWebEngine/VTK 所置)——不修复则 narrow open 失败。
|
||||||
|
const char* prevC = std::setlocale(LC_ALL, nullptr);
|
||||||
|
const std::string savedC = prevC ? prevC : "C";
|
||||||
|
std::setlocale(LC_ALL, ".UTF-8");
|
||||||
|
|
||||||
|
geopro::core::BuiltI16 b;
|
||||||
|
try {
|
||||||
|
b = geopro::io::gpr::buildLineVolumeFromNormalized(
|
||||||
|
dirAcp, prefixAcp, /*coarse=*/1, /*targetDy=*/0.0);
|
||||||
|
} catch (...) {
|
||||||
|
std::setlocale(LC_ALL, savedC.c_str());
|
||||||
|
fs::remove_all(dir);
|
||||||
|
throw; // 未修复时会在此抛"打开 .head 失败"→ 测试红,正是回归守护
|
||||||
|
}
|
||||||
|
std::setlocale(LC_ALL, savedC.c_str());
|
||||||
|
|
||||||
|
EXPECT_EQ(b.vol.nx(), 4);
|
||||||
|
EXPECT_EQ(b.vol.ny(), 2);
|
||||||
|
EXPECT_EQ(b.vol.nz(), 3);
|
||||||
|
fs::remove_all(dir);
|
||||||
|
#else
|
||||||
|
GTEST_SKIP() << "中文窄路径打开问题仅 Windows 相关";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue