#include #include #include #include #include #include #include #include #include #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); // 三维雷达体(数据字典 DD0623 dd_radar_3d)→ 三维数据集(spec §6.1)。 EXPECT_EQ(repo.dimensionOf(rowWith("dd_radar_3d")), 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:回调收到有效 VolumeGrid(nx>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 // volumeInfo:createVolume 后、loadVolume 前 → 返回 true,参数/名称正确,loaded=false、无测点数。 TEST(Api3dRepo, VolumeInfoBeforeLoad) { StubAsyncRepo dsRepo; auto frame = std::make_shared(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"); } // loadMapLine(Api):loadAsync 返回空句柄 → onErr(不崩,给明确错误)。 TEST(Api3dRepo, LoadMapLineNullHandleCallsOnError) { StubAsyncRepo dsRepo; auto frame = std::make_shared(22.0, 114.0); Api3dRepository repo(dsRepo, frame); bool errCalled = false; repo.loadMapLine("traj1", [](MapLine) { FAIL() << "不应成功(空句柄)"; }, [&](const std::string&) { errCalled = true; }); EXPECT_TRUE(errCalled); } // dimensionOf(Api,与 LocalSample3dRepository 同口径):三维雷达体 dd_radar_3d → 三维(spec §6.1)。 TEST(Api3dRepo, DimensionOfMapsDdRadar3dToDim3D) { StubAsyncRepo dsRepo; auto frame = std::make_shared(22.0, 114.0); Api3dRepository repo(dsRepo, frame); EXPECT_EQ(repo.dimensionOf(rowWith("dd_radar_3d")), DsDimension::Dim3D); EXPECT_EQ(repo.dimensionOf(rowWith("dd_voxel")), DsDimension::Dim3D); EXPECT_EQ(repo.dimensionOf(rowWith("dd_unknown_xyz")), DsDimension::Other); } // volumeInfo:未知 dsId(非三维体)→ 返回 false,不弹空对话框。 TEST(Api3dRepo, VolumeInfoUnknownIdReturnsFalse) { StubAsyncRepo dsRepo; auto frame = std::make_shared(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(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(t * 10 + c * 100 + s); f.write(reinterpret_cast(&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(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(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); }