feat(io): IprbReader 新增 readIprbRange 道区间读取

只 seek 并读取 [t0,t1) 道,不载全文件,供流式 slab 装配内存有界。
偏移与读取字节数全 64 位防大文件溢出;越界与文件打不开抛 std::runtime_error。
不改动 readIprb 现有行为。
This commit is contained in:
gaozheng 2026-06-23 21:29:13 +08:00
parent 0537e938b4
commit c2ec1d34b4
3 changed files with 76 additions and 0 deletions

View File

@ -41,4 +41,52 @@ BScan readIprb(const std::string& path, const IprHeader& h) {
return b; return b;
} }
BScan readIprbRange(const std::string& path, const IprHeader& h,
std::int64_t t0, std::int64_t t1) {
std::ifstream f(path, std::ios::binary | std::ios::ate);
if (!f) {
throw std::runtime_error("readIprbRange: 无法打开文件: " + path);
}
const std::int64_t fileBytes = static_cast<std::int64_t>(f.tellg());
const std::int64_t samples = static_cast<std::int64_t>(h.samples);
if (samples <= 0) {
throw std::runtime_error("readIprbRange: samples 非法(<=0): " + path);
}
const std::int64_t bytesPerTrace = samples * 2; // int16
if (fileBytes % bytesPerTrace != 0) {
throw std::runtime_error(
"readIprbRange: 文件大小不是 samples*2 的整数倍: " + path);
}
const std::int64_t totalTraces = fileBytes / bytesPerTrace;
// 区间校验0<=t0<=t1<=总道数。
if (t0 < 0 || t0 > t1 || t1 > totalTraces) {
throw std::runtime_error("readIprbRange: 道区间越界 [t0,t1)");
}
const std::int64_t rangeTraces = t1 - t0;
const std::int64_t offsetBytes = t0 * bytesPerTrace; // 64 位防溢出
const std::int64_t readBytes = rangeTraces * bytesPerTrace; // 64 位防溢出
BScan b;
b.samples = h.samples;
b.traces = rangeTraces;
b.data.resize(static_cast<std::size_t>(samples * rangeTraces));
f.seekg(static_cast<std::streamoff>(offsetBytes), std::ios::beg);
if (!f) {
throw std::runtime_error("readIprbRange: seek 失败: " + path);
}
if (readBytes > 0) {
f.read(reinterpret_cast<char*>(b.data.data()),
static_cast<std::streamsize>(readBytes));
if (!f) {
throw std::runtime_error("readIprbRange: 读取数据失败: " + path);
}
}
return b;
}
} // namespace geopro::io::gpr } // namespace geopro::io::gpr

View File

@ -21,6 +21,14 @@ struct BScan {
// 注意h.lastTrace 仅作 header 提示,不决定道数(真实数据规律为道数==lastTrace非 +1 // 注意h.lastTrace 仅作 header 提示,不决定道数(真实数据规律为道数==lastTrace非 +1
BScan readIprb(const std::string& path, const IprHeader& h); BScan readIprb(const std::string& path, const IprHeader& h);
// 只读 [t0, t1) 道(0<=t0<=t1<=总道数)。seek 到 t0*samples*2读 (t1-t0)*samples*2 字节,
// 不载全文件,供流式 slab 装配做到内存有界。
// 返回 BScan.samples=h.samples, traces=(t1-t0), data 大小=(t1-t0)*samples布局同 readIprb。
// 总道数 = fileBytes/(samples*2);越界(t1>总道数 或 t0>t1 或 t0<0)抛 std::runtime_error
// 文件打不开抛 std::runtime_error。偏移用 64 位防大文件溢出。
BScan readIprbRange(const std::string& path, const IprHeader& h,
std::int64_t t0, std::int64_t t1);
} // namespace geopro::io::gpr } // namespace geopro::io::gpr
#endif // GEOPRO_IO_GPR_IPRBREADER_HPP #endif // GEOPRO_IO_GPR_IPRBREADER_HPP

View File

@ -47,3 +47,23 @@ TEST(IprbReader, ThrowsOnMissingFile) {
IprHeader h{}; h.samples = 3; h.lastTrace = 3; IprHeader h{}; h.samples = 3; h.lastTrace = 3;
EXPECT_THROW(readIprb("____no_such_file____.iprb", h), std::runtime_error); EXPECT_THROW(readIprb("____no_such_file____.iprb", h), std::runtime_error);
} }
TEST(IprbReader, RangeReadMatchesFullSlice) {
// 造 samples=3, traces=5 的文件(15 个 int16,值=trace*10+s)
std::vector<int16_t> raw; for(int t=0;t<5;t++)for(int s=0;s<3;s++) raw.push_back((int16_t)(t*10+s));
auto path = writeTmp(raw);
IprHeader h{}; h.samples=3; h.lastTrace=4; // 总道数=5(由文件大小推)
auto full = readIprb(path, h);
auto seg = readIprbRange(path, h, 1, 4); // 读道 [1,4)=3 道
EXPECT_EQ(seg.traces, 3); EXPECT_EQ(seg.samples, 3);
ASSERT_EQ(seg.data.size(), 9u);
for (int t=0;t<3;t++) for(int s=0;s<3;s++)
EXPECT_EQ(seg.data[t*3+s], full.data[(t+1)*3+s]); // 段==全读对应段
std::remove(path.c_str());
}
TEST(IprbReader, RangeOutOfBoundsThrows) {
std::vector<int16_t> raw(15,0); auto path=writeTmp(raw);
IprHeader h{}; h.samples=3; h.lastTrace=4;
EXPECT_THROW(readIprbRange(path,h,3,99), std::runtime_error); // t1>5
EXPECT_THROW(readIprbRange(path,h,4,2), std::runtime_error); // t0>t1
std::remove(path.c_str());
}