feat(render): GeoLocalFrame(经纬→局部米) + CurtainActor(测线竖直帘面)
This commit is contained in:
parent
f4e61cb947
commit
c9d0d90433
|
|
@ -3,6 +3,7 @@ find_package(PROJ CONFIG REQUIRED)
|
|||
|
||||
add_library(geopro_core STATIC
|
||||
geo/LocalFrame.cpp
|
||||
geo/GeoLocalFrame.cpp
|
||||
geo/CrsTransform.cpp
|
||||
model/ColorScale.cpp
|
||||
algo/IdwInterpolator.cpp
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
#include "geo/GeoLocalFrame.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace geopro::core {
|
||||
|
||||
namespace {
|
||||
constexpr double kPi = 3.14159265358979323846;
|
||||
// 纬度每度约 110540 m(近似常数);经度每度随纬度收缩 111320*cos(lat)。
|
||||
constexpr double kMetersPerDegLat = 110540.0;
|
||||
constexpr double kMetersPerDegLonEquator = 111320.0;
|
||||
} // namespace
|
||||
|
||||
GeoLocalFrame::GeoLocalFrame(double lat0, double lon0)
|
||||
: lat0_(lat0),
|
||||
lon0_(lon0),
|
||||
mPerDegLon_(kMetersPerDegLonEquator * std::cos(lat0 * kPi / 180.0)),
|
||||
mPerDegLat_(kMetersPerDegLat) {}
|
||||
|
||||
LocalXY GeoLocalFrame::toLocal(double lat, double lon) const {
|
||||
return LocalXY{(lon - lon0_) * mPerDegLon_, (lat - lat0_) * mPerDegLat_};
|
||||
}
|
||||
|
||||
} // namespace geopro::core
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
namespace geopro::core {
|
||||
|
||||
struct LocalXY { double x, y; };
|
||||
|
||||
// 等距圆柱(equirectangular)近似:把经纬度投到以(lat0,lon0)为原点的局部米平面。
|
||||
// 小范围测区足够;x=East、y=North(米)。
|
||||
class GeoLocalFrame {
|
||||
public:
|
||||
GeoLocalFrame(double lat0, double lon0);
|
||||
LocalXY toLocal(double lat, double lon) const; // -> (x East m, y North m)
|
||||
private:
|
||||
double lat0_, lon0_, mPerDegLon_, mPerDegLat_;
|
||||
};
|
||||
|
||||
} // namespace geopro::core
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel FiltersGeometry FiltersModeling RenderingOpenGL2 RenderingVolumeOpenGL2 InteractionStyle InteractionWidgets)
|
||||
find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel FiltersGeometry FiltersModeling RenderingCore RenderingOpenGL2 RenderingVolumeOpenGL2 InteractionStyle InteractionWidgets)
|
||||
add_library(geopro_render STATIC
|
||||
Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp actors/GridContourActor.cpp actors/VoxelActor.cpp)
|
||||
Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp actors/GridContourActor.cpp actors/VoxelActor.cpp actors/CurtainActor.cpp)
|
||||
target_include_directories(geopro_render PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(geopro_render PUBLIC geopro_core ${VTK_LIBRARIES})
|
||||
target_compile_features(geopro_render PUBLIC cxx_std_17)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
#include "actors/CurtainActor.hpp"
|
||||
|
||||
#include <vtkDataSetMapper.h>
|
||||
#include <vtkDoubleArray.h>
|
||||
#include <vtkNew.h>
|
||||
#include <vtkPointData.h>
|
||||
#include <vtkPoints.h>
|
||||
#include <vtkStructuredGrid.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "ColorLutBuilder.hpp"
|
||||
|
||||
namespace geopro::render {
|
||||
|
||||
namespace {
|
||||
// LUT 级数。
|
||||
constexpr int kLutLevels = 256;
|
||||
} // namespace
|
||||
|
||||
vtkSmartPointer<vtkActor> buildCurtain(const geopro::core::Grid& g,
|
||||
const geopro::core::ColorScale& cs,
|
||||
const geopro::core::GeoLocalFrame& frame)
|
||||
{
|
||||
const int nx = g.nx(), ny = g.ny();
|
||||
|
||||
// 退化网格:返回空 actor(调用方仍可安全 addActor,mapper 无输入则不绘制)。
|
||||
if (nx < 1 || ny < 1 || g.y.size() < static_cast<size_t>(ny)) {
|
||||
return vtkSmartPointer<vtkActor>::New();
|
||||
}
|
||||
|
||||
const bool hasLatLon =
|
||||
g.lat.size() >= static_cast<size_t>(nx) && g.lon.size() >= static_cast<size_t>(nx);
|
||||
|
||||
// 结构化网格:维度 (nx, ny, 1),点序 i 最快、j 次之(id = j*nx + i)。
|
||||
vtkNew<vtkStructuredGrid> sgrid;
|
||||
sgrid->SetDimensions(nx, ny, 1);
|
||||
|
||||
vtkNew<vtkPoints> points;
|
||||
points->SetNumberOfPoints(static_cast<vtkIdType>(nx) * ny);
|
||||
|
||||
vtkNew<vtkDoubleArray> sc;
|
||||
sc->SetName("v");
|
||||
sc->SetNumberOfTuples(static_cast<vtkIdType>(nx) * ny);
|
||||
|
||||
for (int j = 0; j < ny; ++j) {
|
||||
for (int i = 0; i < nx; ++i) {
|
||||
double px, py;
|
||||
if (hasLatLon) {
|
||||
auto p = frame.toLocal(g.lat[i], g.lon[i]);
|
||||
px = p.x;
|
||||
py = p.y;
|
||||
} else {
|
||||
// 退化:用 g.x[i] 作 x、0 作 y。
|
||||
px = (g.x.size() > static_cast<size_t>(i)) ? g.x[i] : static_cast<double>(i);
|
||||
py = 0.0;
|
||||
}
|
||||
const vtkIdType id = static_cast<vtkIdType>(j) * nx + i;
|
||||
points->SetPoint(id, px, py, g.y[j]); // z=深度/高程,VTK Z 向上
|
||||
sc->SetValue(id, g.valueAt(i, j));
|
||||
}
|
||||
}
|
||||
|
||||
sgrid->SetPoints(points);
|
||||
sgrid->GetPointData()->SetScalars(sc);
|
||||
|
||||
// vmin/vmax 来自 Grid;若退化(==)则扫数据兜底。
|
||||
double vmin = g.vmin, vmax = g.vmax;
|
||||
if (vmin >= vmax) {
|
||||
const auto& vals = g.values();
|
||||
vmin = vals.empty() ? 0.0 : vals.front();
|
||||
vmax = vmin;
|
||||
for (double v : vals) {
|
||||
if (v < vmin) vmin = v;
|
||||
if (v > vmax) vmax = v;
|
||||
}
|
||||
if (vmin >= vmax) vmax = vmin + 1.0;
|
||||
}
|
||||
|
||||
auto lut = buildLut(cs, vmin, vmax, kLutLevels);
|
||||
|
||||
vtkNew<vtkDataSetMapper> mapper;
|
||||
mapper->SetInputData(sgrid);
|
||||
mapper->SetScalarModeToUsePointData();
|
||||
mapper->SetLookupTable(lut);
|
||||
mapper->SetScalarRange(vmin, vmax);
|
||||
|
||||
auto actor = vtkSmartPointer<vtkActor>::New();
|
||||
actor->SetMapper(mapper);
|
||||
return actor;
|
||||
}
|
||||
|
||||
} // namespace geopro::render
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
#include <vtkSmartPointer.h>
|
||||
#include <vtkActor.h>
|
||||
#include "model/Field.hpp"
|
||||
#include "model/ColorScale.hpp"
|
||||
#include "geo/GeoLocalFrame.hpp"
|
||||
namespace geopro::render {
|
||||
|
||||
// 测线竖直帘面:沿 lat/lon 迹线立在世界(x,y),纵向为深度 g.y,面按 colorBar 着色。
|
||||
vtkSmartPointer<vtkActor> buildCurtain(const geopro::core::Grid& g,
|
||||
const geopro::core::ColorScale& cs,
|
||||
const geopro::core::GeoLocalFrame& frame);
|
||||
|
||||
} // namespace geopro::render
|
||||
|
|
@ -31,6 +31,7 @@ target_sources(geopro_tests PRIVATE core/test_color_scale.cpp)
|
|||
target_sources(geopro_tests PRIVATE core/test_idw.cpp)
|
||||
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_geo_frame.cpp)
|
||||
target_link_libraries(geopro_tests PRIVATE geopro_core)
|
||||
|
||||
target_sources(geopro_tests PRIVATE data/test_parsers.cpp)
|
||||
|
|
@ -57,10 +58,12 @@ endif()
|
|||
|
||||
# render 层:ColorLutBuilder(core ColorScale -> vtkLookupTable)。
|
||||
# 需 vtkLookupTable(VTK::CommonCore);geopro_render 已 PUBLIC 传递其余 VTK 组件。
|
||||
find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel)
|
||||
find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel RenderingCore)
|
||||
target_sources(geopro_tests PRIVATE render/test_color_lut.cpp)
|
||||
# dd_voxel:buildVoxel(ScalarVolume->vtkImageData->GPU 体绘制) 构建不崩 + dims 正确。
|
||||
target_sources(geopro_tests PRIVATE render/test_voxel_build.cpp)
|
||||
# Curtain:buildCurtain(Grid+GeoLocalFrame->vtkStructuredGrid 帘面) 非空 actor + 点数=nx*ny。
|
||||
target_sources(geopro_tests PRIVATE render/test_curtain.cpp)
|
||||
target_link_libraries(geopro_tests PRIVATE geopro_render ${VTK_LIBRARIES})
|
||||
vtk_module_autoinit(TARGETS geopro_tests MODULES ${VTK_LIBRARIES})
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "geo/GeoLocalFrame.hpp"
|
||||
|
||||
using geopro::core::GeoLocalFrame;
|
||||
|
||||
namespace {
|
||||
constexpr double kPi = 3.14159265358979323846;
|
||||
}
|
||||
|
||||
// 原点投影到 (0,0)。
|
||||
TEST(GeoFrame, OriginMapsToZero) {
|
||||
GeoLocalFrame f(22.5, 114.16);
|
||||
auto p = f.toLocal(22.5, 114.16);
|
||||
EXPECT_NEAR(p.x, 0.0, 1e-9);
|
||||
EXPECT_NEAR(p.y, 0.0, 1e-9);
|
||||
}
|
||||
|
||||
// 东向 0.01 度经度:x>0,约 0.01*111320*cos(22.5°)≈1028m(5% 容差)。
|
||||
TEST(GeoFrame, EastwardLongitudeGivesPositiveX) {
|
||||
GeoLocalFrame f(22.5, 114.16);
|
||||
auto p = f.toLocal(22.5, 114.17);
|
||||
const double expected = 0.01 * 111320.0 * std::cos(22.5 * kPi / 180.0);
|
||||
EXPECT_GT(p.x, 0.0);
|
||||
EXPECT_NEAR(p.x, expected, expected * 0.05);
|
||||
EXPECT_NEAR(p.y, 0.0, 1e-9);
|
||||
}
|
||||
|
||||
// 北向 0.01 度纬度:y≈0.01*110540≈1105m(5% 容差)。
|
||||
TEST(GeoFrame, NorthwardLatitudeGivesPositiveY) {
|
||||
GeoLocalFrame f(22.5, 114.16);
|
||||
auto p = f.toLocal(22.51, 114.16);
|
||||
const double expected = 0.01 * 110540.0;
|
||||
EXPECT_GT(p.y, 0.0);
|
||||
EXPECT_NEAR(p.y, expected, expected * 0.05);
|
||||
EXPECT_NEAR(p.x, 0.0, 1e-9);
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include <vtkDataSet.h>
|
||||
#include <vtkDataSetMapper.h>
|
||||
#include <vtkMapper.h>
|
||||
|
||||
#include "actors/CurtainActor.hpp"
|
||||
#include "geo/GeoLocalFrame.hpp"
|
||||
#include "model/ColorScale.hpp"
|
||||
#include "model/Field.hpp"
|
||||
|
||||
using namespace geopro::core;
|
||||
|
||||
// buildCurtain: 小 Grid(3,2) + lat/lon/y/values -> 非空 actor,mapper 输入点数=6。
|
||||
TEST(Curtain, BuildsStructuredGridFromSmallGrid) {
|
||||
Grid g(3, 2); // nx=3 (lat/lon 列), ny=2 (深度行)
|
||||
g.lat = {22.50, 22.50, 22.50};
|
||||
g.lon = {114.16, 114.17, 114.18};
|
||||
g.y = {0.0, -10.0}; // 两层深度
|
||||
double n = 0.0;
|
||||
for (int j = 0; j < 2; ++j)
|
||||
for (int i = 0; i < 3; ++i) g.valueAt(i, j) = n++;
|
||||
g.vmin = 0.0;
|
||||
g.vmax = 5.0;
|
||||
|
||||
ColorScale cs;
|
||||
cs.addStop(0.0, Rgba{0, 0, 255, 255});
|
||||
cs.addStop(5.0, Rgba{255, 0, 0, 255});
|
||||
|
||||
GeoLocalFrame frame(22.50, 114.16);
|
||||
|
||||
auto actor = geopro::render::buildCurtain(g, cs, frame);
|
||||
ASSERT_NE(actor.GetPointer(), nullptr);
|
||||
|
||||
auto* mapper = vtkDataSetMapper::SafeDownCast(actor->GetMapper());
|
||||
ASSERT_NE(mapper, nullptr);
|
||||
auto* input = mapper->GetInput();
|
||||
ASSERT_NE(input, nullptr);
|
||||
EXPECT_EQ(input->GetNumberOfPoints(), 6);
|
||||
}
|
||||
Loading…
Reference in New Issue