geopro/tests/data/test_3d_repo.cpp

279 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <gtest/gtest.h>
#include <QCoreApplication>
#include <QEventLoop>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <memory>
#include <string>
#include <system_error>
#include "api/Api3dRepository.hpp"
#include "geo/GeoLocalFrame.hpp"
#include "repo/I3dSceneRepository.hpp"
#include "repo/IAsyncDatasetRepository.hpp"
#include "repo/LocalSample3dRepository.hpp"
#include "repo/LocalSampleRepository.hpp"
#include "repo/VolumeBuildParams.hpp"
using namespace geopro::data;
static const std::string kDir =
"D:/Git/lanbingtech/geopro/docs/剖面网格数据的色阶数据2等文件/";
static const std::string kCrs = "EPSG:4547";
namespace {
DsRow rowWith(const std::string& ddCode) {
DsRow r;
r.ddCode = ddCode;
return r;
}
} // namespace
// dimensionOf各 ddCode → 维度映射同步纯函数spec §6.1)。
TEST(LocalSample3dRepo, DimensionOfMapsDdCode) {
LocalSampleRepository base(kDir);
LocalSample3dRepository repo(base, kCrs, 22.0, 114.0);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_voxel")), DsDimension::Dim3D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_Structual3D")), DsDimension::Dim3D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_Property3D")), DsDimension::Dim3D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_section")), DsDimension::Dim3D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_inversion_data")), DsDimension::Dim3D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_slice")), DsDimension::Analysis3D);
// 足迹型 → 二维:数据字典 DD0623 只 dd_trajectory_data 为统一通用轨迹「保留」;
// 瞬变电磁/雷达通道/RTK 等轨迹型字典均「删除」→ 不再归 2D落 Other
EXPECT_EQ(repo.dimensionOf(rowWith("dd_trajectory_data")), DsDimension::Dim2D);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_transient_electromagnetic_trajectory_data")),
DsDimension::Other);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_radar_channel_trajectory")), DsDimension::Other);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_radar_rtk_trajectory")), DsDimension::Other);
EXPECT_EQ(repo.dimensionOf(rowWith("dd_unknown_xyz")), DsDimension::Other);
}
// loadMapLine本地样本取 grid1 经纬作足迹折线lat/lon 等长、>=2 点、valid
TEST(LocalSample3dRepo, LoadMapLineCallsBackWithValidLine) {
LocalSampleRepository base(kDir);
LocalSample3dRepository repo(base, kCrs, 22.0, 114.0);
bool ok = false;
std::string err;
MapLine got;
repo.loadMapLine("traj1", [&](MapLine l) { ok = true; got = std::move(l); },
[&](const std::string& m) { err = m; });
ASSERT_TRUE(ok) << "loadMapLine onErr: " << err;
EXPECT_EQ(got.lat.size(), got.lon.size());
EXPECT_GE(got.lat.size(), 2u);
EXPECT_TRUE(got.valid());
}
// loadVolume回调收到有效 VolumeGridnx>0 且 vmax>vmin需 PROJ_DATA。
TEST(LocalSample3dRepo, LoadVolumeCallsBackWithValidGrid) {
LocalSampleRepository base(kDir);
LocalSample3dRepository repo(base, kCrs, 22.0, 114.0);
bool ok = false;
std::string err;
VolumeGrid got;
repo.loadVolume("voxel1",
[&](VolumeGrid g, geopro::core::ColorScale) { ok = true; got = std::move(g); },
[&](const std::string& m) { err = m; });
ASSERT_TRUE(ok) << "loadVolume onErr: " << err;
EXPECT_GT(got.vol.nx(), 0);
EXPECT_GT(got.vol.ny(), 0);
EXPECT_GT(got.vol.nz(), 0);
EXPECT_GT(got.vmax, got.vmin);
EXPECT_TRUE(got.valid());
}
// loadTerrainPaths回调收到 dem/image 绝对路径(非空)。
TEST(LocalSample3dRepo, LoadTerrainPathsCallsBack) {
LocalSampleRepository base(kDir);
LocalSample3dRepository repo(base, kCrs, 22.0, 114.0);
bool ok = false;
TerrainPaths got;
repo.loadTerrainPaths([&](TerrainPaths p) { ok = true; got = std::move(p); },
[&](const std::string&) {});
ASSERT_TRUE(ok);
EXPECT_FALSE(got.demPath.empty());
EXPECT_FALSE(got.imagePath.empty());
}
namespace {
// 极简桩volumeInfo/createVolume 不触碰 dsRepo_loadAsync 直接回空。
struct StubAsyncRepo : IAsyncDatasetRepository {
DetailLoad* loadAsync(const std::string&, const std::string&, int, int) override {
return nullptr;
}
};
} // namespace
// volumeInfocreateVolume 后、loadVolume 前 → 返回 true参数/名称正确loaded=false、无测点数。
TEST(Api3dRepo, VolumeInfoBeforeLoad) {
StubAsyncRepo dsRepo;
auto frame = std::make_shared<geopro::core::GeoLocalFrame>(22.0, 114.0);
Api3dRepository repo(dsRepo, frame);
VolumeBuildParams p;
p.sourceDatasetIds = {"src-a", "src-b"};
p.interpModel = VolumeBuildParams::Model::Idw;
p.cellXY = 2.0;
p.cellZ = 0.5;
p.power = 3.0;
p.maxDist = 5.0;
p.colorScaleId = "src-a";
const std::string id = repo.createVolume(p, "体A");
Api3dRepository::VolumeInfo info;
ASSERT_TRUE(repo.volumeInfo(id, info));
EXPECT_EQ(info.name, "体A");
EXPECT_FALSE(info.loaded);
EXPECT_EQ(info.pointCount, 0u);
ASSERT_EQ(info.params.sourceDatasetIds.size(), 2u);
EXPECT_EQ(info.params.sourceDatasetIds[0], "src-a");
EXPECT_DOUBLE_EQ(info.params.cellXY, 2.0);
EXPECT_DOUBLE_EQ(info.params.power, 3.0);
EXPECT_DOUBLE_EQ(info.params.maxDist, 5.0);
EXPECT_EQ(info.params.colorScaleId, "src-a");
}
// loadMapLineApiloadAsync 返回空句柄 → onErr不崩给明确错误
TEST(Api3dRepo, LoadMapLineNullHandleCallsOnError) {
StubAsyncRepo dsRepo;
auto frame = std::make_shared<geopro::core::GeoLocalFrame>(22.0, 114.0);
Api3dRepository repo(dsRepo, frame);
bool errCalled = false;
repo.loadMapLine("traj1", [](MapLine) { FAIL() << "不应成功(空句柄)"; },
[&](const std::string&) { errCalled = true; });
EXPECT_TRUE(errCalled);
}
// volumeInfo未知 dsId非三维体→ 返回 false不弹空对话框。
TEST(Api3dRepo, VolumeInfoUnknownIdReturnsFalse) {
StubAsyncRepo dsRepo;
auto frame = std::make_shared<geopro::core::GeoLocalFrame>(22.0, 114.0);
Api3dRepository repo(dsRepo, frame);
Api3dRepository::VolumeInfo info;
EXPECT_FALSE(repo.volumeInfo("not-a-volume", info));
}
// anomalyRows异常按 remarkSourceId 存;行 parentId=归属实体 id挂体/挂切片各回得来)。
TEST(Api3dRepo, AnomalyRowsCarryMountAsParent) {
StubAsyncRepo dsRepo;
auto frame = std::make_shared<geopro::core::GeoLocalFrame>(22.0, 114.0);
Api3dRepository repo(dsRepo, frame);
geopro::core::Anomaly onVol;
onVol.name = "异常A"; onVol.typeName = "断层"; onVol.remarkSourceId = "vol-1";
geopro::core::Anomaly onSlice;
onSlice.name = "异常B"; onSlice.remarkSourceId = "slice-9"; // 已保存切片
std::string idV, idS;
repo.saveAnomaly(onVol, "", [&](std::string id) { idV = id; }, [](const std::string&) {});
repo.saveAnomaly(onSlice, "", [&](std::string id) { idS = id; }, [](const std::string&) {});
const auto rows = repo.anomalyRows();
ASSERT_EQ(rows.size(), 2u);
auto find = [&](const std::string& id) -> const geopro::data::DsRow* {
for (const auto& r : rows)
if (r.id == id) return &r;
return nullptr;
};
const auto* rv = find(idV);
ASSERT_NE(rv, nullptr);
EXPECT_EQ(rv->ddCode, "dd_anomaly");
EXPECT_EQ(rv->parentId, "vol-1"); // 挂体
EXPECT_EQ(rv->dsName, "异常A");
EXPECT_EQ(rv->typeName, "断层");
const auto* rs = find(idS);
ASSERT_NE(rs, nullptr);
EXPECT_EQ(rs->parentId, "slice-9"); // 挂切片
EXPECT_EQ(rs->typeName, "异常"); // typeName 空 → 回退"异常"
}
namespace {
// 写一条规范化测线合成数据(.head + .data同 Task 6 createRadarVolumeGrid 用例口径:
// SAMPLES=3 / NUMBER_OF_CH=2 / LAST_TRACE=8 → X=道(4 段)、Y=通道(2)、Z=采样(3)。
void writeSyntheticRadarLine(const std::filesystem::path& dir) {
namespace fs = std::filesystem;
fs::create_directories(dir);
{
std::ofstream f(dir / "L.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(dir / "L.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
// registerRadarDataset登记为 dd_radar_3d 体 DS只存元数据、不建体→ 仍被认作三维体,
// volumeRows 输出 ddCode="dd_radar_3d" + structParentId。
TEST(Api3dRepo, RegisterRadarDatasetRoutesAsDdRadar3d) {
const auto dir = std::filesystem::temp_directory_path() / "api3d_radar_register";
std::error_code ec;
std::filesystem::remove_all(dir, ec);
writeSyntheticRadarLine(dir);
StubAsyncRepo dsRepo;
auto frame = std::make_shared<geopro::core::GeoLocalFrame>(22.0, 114.0);
Api3dRepository repo(dsRepo, frame);
const std::string id = repo.registerRadarDataset(dir.string(), "L", "测线L",
/*structParentId=*/"tm-1", /*coarse=*/1);
EXPECT_FALSE(id.empty());
EXPECT_TRUE(repo.isVolumeDataset(id)); // 运行期按 volumes_ 成员判体 → 真(即便未建体)
const auto rows = repo.volumeRows();
ASSERT_FALSE(rows.empty());
EXPECT_EQ(rows.back().ddCode, "dd_radar_3d"); // 不是 dd_voxel
EXPECT_EQ(rows.back().structParentId, "tm-1");
std::filesystem::remove_all(dir, ec);
}
// loadVolume首次勾选时懒建雷达体无 QCoreApplication → 同步交付;全量测试中若其它用例已建
// QCoreApplication 单例则走异步processEvents 排空队列交付)。回调收到有效 VolumeGrid。
TEST(Api3dRepo, LoadVolumeBuildsRadarLazily) {
const auto dir = std::filesystem::temp_directory_path() / "api3d_radar_lazy";
std::error_code ec;
std::filesystem::remove_all(dir, ec);
writeSyntheticRadarLine(dir);
StubAsyncRepo dsRepo;
auto frame = std::make_shared<geopro::core::GeoLocalFrame>(22.0, 114.0);
Api3dRepository repo(dsRepo, frame);
const std::string id = repo.registerRadarDataset(dir.string(), "L", "测线L", "", 1);
bool got = false;
repo.loadVolume(
id,
[&](VolumeGrid g, geopro::core::ColorScale) {
got = true;
EXPECT_GT(g.vol.nx(), 0);
EXPECT_GT(g.vol.ny(), 0);
EXPECT_GT(g.vol.nz(), 0);
},
[&](const std::string& e) { FAIL() << e; });
// 全量测试单进程中其它用例(test_async_repo_dispatch/test_auth)会建持久 QCoreApplication 单例 →
// radar 分支走异步(std::thread + queued 交付),需驱动事件循环排空;无 app 时同步交付 got 已真。
for (int i = 0; i < 200 && !got && QCoreApplication::instance(); ++i)
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
EXPECT_TRUE(got);
std::filesystem::remove_all(dir, ec);
}