feat(data): 样本 JSON 解析器(grid/scatter/colorscale/anomaly)

This commit is contained in:
gaozheng 2026-06-07 20:27:58 +08:00
parent a35ababdd4
commit fe5936a3a6
6 changed files with 199 additions and 0 deletions

View File

@ -9,4 +9,5 @@
# add_subdirectory(controller) # # add_subdirectory(controller) #
# #
add_subdirectory(core) add_subdirectory(core)
add_subdirectory(data)
add_subdirectory(app) add_subdirectory(app)

6
src/data/CMakeLists.txt Normal file
View File

@ -0,0 +1,6 @@
find_package(nlohmann_json CONFIG REQUIRED)
add_library(geopro_data STATIC parse/SampleParsers.cpp)
target_include_directories(geopro_data PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(geopro_data PUBLIC geopro_core PRIVATE nlohmann_json::nlohmann_json)
target_compile_features(geopro_data PUBLIC cxx_std_17)
set_target_properties(geopro_data PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF)

View File

@ -0,0 +1,150 @@
#include "parse/SampleParsers.hpp"
#include <nlohmann/json.hpp>
namespace geopro::data {
using nlohmann::json;
using namespace geopro::core;
namespace {
// 读取 number 数组到 vector<double>,缺字段返回空。
std::vector<double> readDoubleArray(const json& obj, const char* key) {
std::vector<double> out;
auto it = obj.find(key);
if (it == obj.end() || !it->is_array()) return out;
out.reserve(it->size());
for (const auto& e : *it) out.push_back(e.get<double>());
return out;
}
} // namespace
Grid parseGrid(const std::string& jsonText) {
const json root = json::parse(jsonText);
const json& d = root.at("data");
const std::vector<double> xs = readDoubleArray(d, "x");
const std::vector<double> ys = readDoubleArray(d, "y");
const int nx = static_cast<int>(xs.size());
const int ny = static_cast<int>(ys.size());
Grid g(nx, ny);
g.x = xs;
g.y = ys;
// v: 外层 j=y、内层 i=x ⇒ v[j][i],写入 valueAt(i,j)i 最快,与 values 一致)。
auto vIt = d.find("v");
if (vIt != d.end() && vIt->is_array()) {
const json& v = *vIt;
for (int j = 0; j < ny && j < static_cast<int>(v.size()); ++j) {
const json& row = v[j];
if (!row.is_array()) continue;
for (int i = 0; i < nx && i < static_cast<int>(row.size()); ++i) {
g.valueAt(i, j) = row[i].get<double>();
}
}
}
// z: [ny][nx] 扁平成 g.z顺序 j*nx+i与 values 一致i 最快)。
auto zIt = d.find("z");
if (zIt != d.end() && zIt->is_array()) {
const json& z = *zIt;
g.z.assign(static_cast<size_t>(nx) * ny, 0.0);
for (int j = 0; j < ny && j < static_cast<int>(z.size()); ++j) {
const json& row = z[j];
if (!row.is_array()) continue;
for (int i = 0; i < nx && i < static_cast<int>(row.size()); ++i) {
g.z[static_cast<size_t>(j) * nx + i] = row[i].get<double>();
}
}
}
g.elevation = readDoubleArray(d, "elevation");
g.lat = readDoubleArray(d, "lat");
g.lon = readDoubleArray(d, "lon");
g.vmin = d.value("vmin", 0.0);
g.vmax = d.value("vmax", 0.0);
return g;
}
ScatterField parseScatter(const std::string& jsonText) {
const json root = json::parse(jsonText);
const json& d = root.at("data");
ScatterField s;
s.x = readDoubleArray(d, "xlist");
s.y = readDoubleArray(d, "ylist");
s.z = readDoubleArray(d, "hlist");
s.v = readDoubleArray(d, "vlist");
s.projX = readDoubleArray(d, "projectXList");
s.projY = readDoubleArray(d, "projectYList");
return s;
}
ColorScale parseColorScale(const std::string& jsonText) {
const json root = json::parse(jsonText);
const json& d = root.at("data");
ColorScale cs;
auto propsIt = d.find("properties");
if (propsIt == d.end()) return cs;
auto barIt = propsIt->find("colorBar");
if (barIt == propsIt->end() || !barIt->is_array()) return cs;
for (const auto& entry : *barIt) {
if (!entry.is_array() || entry.size() < 2) continue;
const std::string valStr = entry[0].get<std::string>();
const std::string colStr = entry[1].get<std::string>();
cs.addStop(std::stod(valStr), parseColor(colStr, AlphaScale::Bit255));
}
return cs;
}
std::vector<Anomaly> parseAnomalies(const std::string& jsonText) {
const json root = json::parse(jsonText);
const json& d = root.at("data");
std::vector<Anomaly> out;
if (!d.is_array()) return out;
out.reserve(d.size());
for (const auto& item : d) {
Anomaly a;
a.name = item.value("exceptionName", std::string{});
a.typeName = item.value("exceptionTypeName", std::string{});
a.markType = static_cast<AnomalyMarkType>(
item.value("exceptionMarkType", static_cast<int>(AnomalyMarkType::Polyline)));
// location.coordinate -> localPts
auto locIt = item.find("location");
if (locIt != item.end()) {
auto coordIt = locIt->find("coordinate");
if (coordIt != locIt->end() && coordIt->is_array()) {
for (const auto& pt : *coordIt) {
Vec2 v{pt.value("x", 0.0), pt.value("y", 0.0)};
a.localPts.push_back(v);
}
}
}
// legend 字段(存在性检查,缺则保留默认)
auto legIt = item.find("legend");
if (legIt != item.end()) {
const json& leg = *legIt;
a.lineColor = leg.value("polylineColor", a.lineColor);
a.lineWidth = leg.value("polylineWidth", a.lineWidth);
auto shapeIt = leg.find("polylineShape");
if (shapeIt != leg.end() && shapeIt->is_string()) {
a.dashed = (shapeIt->get<std::string>() == "dash");
}
}
out.push_back(std::move(a));
}
return out;
}
} // namespace geopro::data

View File

@ -0,0 +1,16 @@
#pragma once
#include <string>
#include <vector>
#include "model/Field.hpp"
#include "model/ColorScale.hpp"
#include "model/Anomaly.hpp"
namespace geopro::data {
// 纯函数样本 JSON 解析器(无 Qt/VTK。输入为完整 JSON 文本,
// 内部读取顶层 "data" 子对象。字段缺失时做容错处理。
geopro::core::Grid parseGrid(const std::string& jsonText);
geopro::core::ScatterField parseScatter(const std::string& jsonText);
geopro::core::ColorScale parseColorScale(const std::string& jsonText);
std::vector<geopro::core::Anomaly> parseAnomalies(const std::string& jsonText);
} // namespace geopro::data

View File

@ -29,4 +29,7 @@ target_sources(geopro_tests PRIVATE core/test_crs_transform.cpp)
target_sources(geopro_tests PRIVATE core/test_model_data.cpp) target_sources(geopro_tests PRIVATE core/test_model_data.cpp)
target_link_libraries(geopro_tests PRIVATE geopro_core) target_link_libraries(geopro_tests PRIVATE geopro_core)
target_sources(geopro_tests PRIVATE data/test_parsers.cpp)
target_link_libraries(geopro_tests PRIVATE geopro_data)
add_subdirectory(spike) # spike S3: banded contour add_subdirectory(spike) # spike S3: banded contour

View File

@ -0,0 +1,23 @@
#include <gtest/gtest.h>
#include <fstream>
#include <sstream>
#include "parse/SampleParsers.hpp"
using namespace geopro::data;
using namespace geopro::core;
static std::string slurp(const char* p){std::ifstream f(p);std::stringstream s;s<<f.rdbuf();return s.str();}
TEST(Parsers, Grid) {
Grid g = parseGrid(slurp("D:/dev/spike_data/grid.json"));
EXPECT_EQ(g.nx(), 100); EXPECT_EQ(g.ny(), 22);
EXPECT_EQ(g.values().size(), 100u*22u);
EXPECT_NEAR(g.vmin, -57.09, 0.1); EXPECT_NEAR(g.vmax, 828.08, 0.1);
EXPECT_EQ(g.lat.size(), 100u); EXPECT_EQ(g.lon.size(), 100u);
}
TEST(Parsers, ColorScale) {
ColorScale cs = parseColorScale(slurp("D:/dev/spike_data/colorbar.json"));
EXPECT_FALSE(cs.empty());
auto c = cs.colorAt(-57.0); // 首段 -57.09 → rgba(0,0,170,255)
EXPECT_EQ(c.b, 170);
}