feat(vtk): 底图瓦片坐标数学 TileMath(EPSG:3857 经纬↔z/x/y+瓦片边界)+单测(P5基石)
This commit is contained in:
parent
f407c0adbc
commit
8684e52939
|
|
@ -2,7 +2,8 @@ find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel FiltersGeometry
|
||||||
find_package(GDAL CONFIG REQUIRED)
|
find_package(GDAL CONFIG REQUIRED)
|
||||||
add_library(geopro_render STATIC
|
add_library(geopro_render STATIC
|
||||||
Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp VoxelFromScatters.cpp ContourBands.cpp actors/GridContourActor.cpp actors/VoxelActor.cpp actors/CurtainActor.cpp actors/MapLineActor.cpp actors/ScatterActor.cpp actors/AnomalyActor.cpp actors/ElectrodeActor.cpp actors/TerrainActor.cpp actors/AxesActor.cpp
|
Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp VoxelFromScatters.cpp ContourBands.cpp actors/GridContourActor.cpp actors/VoxelActor.cpp actors/CurtainActor.cpp actors/MapLineActor.cpp actors/ScatterActor.cpp actors/AnomalyActor.cpp actors/ElectrodeActor.cpp actors/TerrainActor.cpp actors/AxesActor.cpp
|
||||||
interact/SlicePlaneMath.cpp interact/SliceTool.cpp interact/PickInteractorStyle.cpp interact/InteractionManager.cpp)
|
interact/SlicePlaneMath.cpp interact/SliceTool.cpp interact/PickInteractorStyle.cpp interact/InteractionManager.cpp
|
||||||
|
ground/TileMath.cpp)
|
||||||
target_include_directories(geopro_render PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(geopro_render PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
target_link_libraries(geopro_render PUBLIC geopro_core ${VTK_LIBRARIES} GDAL::GDAL)
|
target_link_libraries(geopro_render PUBLIC geopro_core ${VTK_LIBRARIES} GDAL::GDAL)
|
||||||
target_compile_features(geopro_render PUBLIC cxx_std_17)
|
target_compile_features(geopro_render PUBLIC cxx_std_17)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
#include "ground/TileMath.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace geopro::render {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr double kPi = 3.14159265358979323846;
|
||||||
|
int clampInt(int v, int lo, int hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TileXY lonLatToTile(double lonDeg, double latDeg, int z) {
|
||||||
|
if (z < 0) z = 0;
|
||||||
|
const double n = std::pow(2.0, z);
|
||||||
|
const double latR = latDeg * kPi / 180.0;
|
||||||
|
int x = static_cast<int>(std::floor((lonDeg + 180.0) / 360.0 * n));
|
||||||
|
int y = static_cast<int>(std::floor((1.0 - std::asinh(std::tan(latR)) / kPi) / 2.0 * n));
|
||||||
|
const int hi = static_cast<int>(n) - 1;
|
||||||
|
return TileXY{z, clampInt(x, 0, hi), clampInt(y, 0, hi)};
|
||||||
|
}
|
||||||
|
|
||||||
|
LonLatBox tileBounds(int z, int x, int y) {
|
||||||
|
if (z < 0) z = 0;
|
||||||
|
const double n = std::pow(2.0, z);
|
||||||
|
const double west = x / n * 360.0 - 180.0;
|
||||||
|
const double east = (x + 1) / n * 360.0 - 180.0;
|
||||||
|
auto latAt = [&](double yy) {
|
||||||
|
return std::atan(std::sinh(kPi * (1.0 - 2.0 * yy / n))) * 180.0 / kPi;
|
||||||
|
};
|
||||||
|
const double north = latAt(static_cast<double>(y));
|
||||||
|
const double south = latAt(static_cast<double>(y + 1));
|
||||||
|
return LonLatBox{west, south, east, north};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace geopro::render
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Web Mercator(EPSG:3857) 瓦片坐标数学:天地图/XYZ 底图瓦片定位用(纯函数,无 VTK/Qt 依赖)。
|
||||||
|
// 标准 slippy-map 公式:n=2^z;x=(lon+180)/360*n;y 用墨卡托纬度映射。
|
||||||
|
namespace geopro::render {
|
||||||
|
|
||||||
|
struct TileXY {
|
||||||
|
int z = 0, x = 0, y = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 瓦片地理边界(度):west/east 经度,south/north 纬度。
|
||||||
|
struct LonLatBox {
|
||||||
|
double west = 0, south = 0, east = 0, north = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 经纬度(度) → 指定 zoom 的瓦片行列(x/y 夹紧到 [0, 2^z-1])。
|
||||||
|
TileXY lonLatToTile(double lonDeg, double latDeg, int z);
|
||||||
|
|
||||||
|
// 瓦片 (z,x,y) → 其覆盖的地理边界(度)。
|
||||||
|
LonLatBox tileBounds(int z, int x, int y);
|
||||||
|
|
||||||
|
} // namespace geopro::render
|
||||||
|
|
@ -100,6 +100,8 @@ target_sources(geopro_tests PRIVATE render/test_terrain.cpp)
|
||||||
target_sources(geopro_tests PRIVATE render/test_camera_preset.cpp)
|
target_sources(geopro_tests PRIVATE render/test_camera_preset.cpp)
|
||||||
# AxesActor(P2):buildAxes(bounds+unit/mode→vtkCubeAxesActor) 单位换算(英尺/经纬度)/不显示返回空。
|
# AxesActor(P2):buildAxes(bounds+unit/mode→vtkCubeAxesActor) 单位换算(英尺/经纬度)/不显示返回空。
|
||||||
target_sources(geopro_tests PRIVATE render/test_axes.cpp)
|
target_sources(geopro_tests PRIVATE render/test_axes.cpp)
|
||||||
|
# TileMath(P5):天地图底图 Web Mercator 瓦片坐标数学(经纬↔z/x/y、瓦片地理边界)——纯函数。
|
||||||
|
target_sources(geopro_tests PRIVATE render/test_tile_math.cpp)
|
||||||
# SlicePlaneMath(P3):切面法向/滚轮平移+夹限/双击正视相机(含竖直兜底)/滚轮步长/最近切片——纯几何。
|
# SlicePlaneMath(P3):切面法向/滚轮平移+夹限/双击正视相机(含竖直兜底)/滚轮步长/最近切片——纯几何。
|
||||||
target_sources(geopro_tests PRIVATE render/test_slice_plane_math.cpp)
|
target_sources(geopro_tests PRIVATE render/test_slice_plane_math.cpp)
|
||||||
target_link_libraries(geopro_tests PRIVATE geopro_render ${VTK_LIBRARIES})
|
target_link_libraries(geopro_tests PRIVATE geopro_render ${VTK_LIBRARIES})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "ground/TileMath.hpp"
|
||||||
|
|
||||||
|
using geopro::render::lonLatToTile;
|
||||||
|
using geopro::render::tileBounds;
|
||||||
|
|
||||||
|
// z=1 把世界分 2x2:原点(0°,0°)在东/南象限交界 → 标准 slippy 取 (1,1)。
|
||||||
|
TEST(TileMath, OriginZoom1) {
|
||||||
|
auto t = lonLatToTile(0.0, 0.0, 1);
|
||||||
|
EXPECT_EQ(t.z, 1);
|
||||||
|
EXPECT_EQ(t.x, 1);
|
||||||
|
EXPECT_EQ(t.y, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// z=1 西北瓦片 (0,0) 覆盖西半球北部:west=-180, east=0, north≈85.0511(墨卡托上限), south=0。
|
||||||
|
TEST(TileMath, BoundsZoom1NW) {
|
||||||
|
auto b = tileBounds(1, 0, 0);
|
||||||
|
EXPECT_NEAR(b.west, -180.0, 1e-6);
|
||||||
|
EXPECT_NEAR(b.east, 0.0, 1e-6);
|
||||||
|
EXPECT_NEAR(b.north, 85.0511287798, 1e-4);
|
||||||
|
EXPECT_NEAR(b.south, 0.0, 1e-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 往返一致:任一经纬点所属瓦片的边界必须包含该点(经度严格、纬度含墨卡托方向)。
|
||||||
|
TEST(TileMath, RoundTripContains) {
|
||||||
|
const double lon = 116.391, lat = 39.907; // 北京附近
|
||||||
|
const int z = 12;
|
||||||
|
auto t = lonLatToTile(lon, lat, z);
|
||||||
|
EXPECT_EQ(t.z, z);
|
||||||
|
auto b = tileBounds(t.z, t.x, t.y);
|
||||||
|
EXPECT_GE(lon, b.west);
|
||||||
|
EXPECT_LE(lon, b.east);
|
||||||
|
EXPECT_LE(lat, b.north); // north 是瓦片上边界(纬度大)
|
||||||
|
EXPECT_GE(lat, b.south);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 夹紧:超界经纬不应产生越界瓦片索引。
|
||||||
|
TEST(TileMath, ClampInRange) {
|
||||||
|
auto t = lonLatToTile(500.0, 95.0, 3); // 非法输入
|
||||||
|
const int hi = (1 << 3) - 1;
|
||||||
|
EXPECT_GE(t.x, 0);
|
||||||
|
EXPECT_LE(t.x, hi);
|
||||||
|
EXPECT_GE(t.y, 0);
|
||||||
|
EXPECT_LE(t.y, hi);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue