MATLAB山地地形仿真与最高点到最低点最短路径

MATLAB山地地形仿真与最高点到最低点最短路径(考虑地形高程)

注意:这里不是平面欧氏最短距离,是三维地形表面的最短路径(测地线),山地有高低起伏,路径要沿着山体表面走,不能直接穿山洞。

  • 地形:用随机分形 / 高斯叠加模拟起伏不平山地曲面 z=f(x,y)z = f(x,y)z=f(x,y)

  • 算法选用:Dijkstra 算法,把地形离散成网格图,每个网格点为图节点,相邻节点代价为地表实际行走距离,求解从最高海拔点到最低海拔点的最小代价路径。

完整 MATLAB 可直接运行代码

matlab 复制代码
%% 1.生成起伏山地地形
clear; clc; close all;
n = 50;   % 网格分辨率 n*n地形网格
[x,y] = meshgrid(linspace(0,20,n), linspace(0,20,n));

% 多个高斯山峰叠加模拟山地,制造高低起伏
z = zeros(size(x));
peak_num = 8;
rng(1); %固定随机种子,复现地形
for i = 1:peak_num
    x0 = 20*rand();
    y0 = 20*rand();
    sigma = 1.5 + 3*rand();
    amp = 2 + 6*rand();
    z = z + amp*exp( -((x-x0).^2+(y-y0).^2)/(2*sigma^2) );
end
% 叠加小噪声模拟粗糙山地
z = z + 0.4*randn(size(z));

%% 找最高点、最低点的网格下标
[z_max,idx_max] = max(z(:));
[ix_start,iy_start] = ind2sub(size(z),idx_max);

[z_min,idx_min] = min(z(:));
[ix_end,iy_end] = ind2sub(size(z),idx_min);

fprintf("最高点(%d,%d),高程=%.2f \n",ix_start,iy_start,z_max);
fprintf("最低点(%d,%d),高程=%.2f \n",ix_end,iy_end,z_min);

%% 2.Dijkstra求解地形表面最短路径
% 将二维网格(i,j)映射成一维节点编号 node = (j-1)*n + i
idx2node = @(i,j) (j-1)*n + i;
node2idx = @(node) ind2sub([n,n],node);

N = n*n;   %总节点数目
INF = 1e10;
dist = ones(N,1)*INF;
start_node = idx2node(ix_start, iy_start);
end_node   = idx2node(ix_end, iy_end);
dist(start_node) = 0;
visited = false(N,1);
prev = zeros(N,1); %记录路径前驱,用于回溯路径

% 四邻域:上下左右;也可以改成8邻域允许斜向行走
neigh_d = [ -1,0; 1,0; 0,-1;0,1 ];
% neigh_d = [ -1,-1;-1,0;-1,1;0,-1;0,1;1,-1;1,0;1,1 ]; %8邻域

for iter = 1:N
    % 选取未访问距离最小的节点u(Dijkstra核心)
    minD = INF;
    u = -1;
    for v = 1:N
        if(~visited(v) && dist(v) < minD)
            minD = dist(v);
            u = v;
        end
    end
    if(u == -1) break;
    if(u == end_node) break; %到达终点,提前退出
    visited(u) = true;

    [iu,ju] = node2idx(u);
    for k = 1:size(neigh_d,1)
        di = neigh_d(k,1);
        dj = neigh_d(k,2);
        iv = iu + di;
        jv = ju + dj;
        if(iv<1||iv>n||jv<1||jv>n) continue; %越界跳过
        v = idx2node(iv,jv);
        if(visited(v)), continue; end

        % =========关键:计算山地地表实际行走距离=========
        % 两点三维距离:dx,dy平面步长;dz高程差
        dx = x(iu,ju)-x(iv,jv);
        dy = y(iu,ju)-y(iv,jv);
        dz = z(iu,ju)-z(iv,jv);
        cost_uv = sqrt(dx^2 + dy^2 + dz^2); %地表欧式距离

        if( dist(v) > dist(u)+cost_uv )
            dist(v) = dist(u)+cost_uv;
            prev(v) = u;
        end
    end
end

%% 回溯得到路径坐标
path_nodes = [];
cur = end_node;
while(cur ~= 0)
    path_nodes = [cur; path_nodes];
    cur = prev(cur);
end
%转成x,y,z坐标
px = []; py = []; pz = [];
for nd = path_nodes.'
    [ii,jj] = node2idx(nd);
    px = [px, x(ii,jj)];
    py = [py, y(ii,jj)];
    pz = [pz, z(ii,jj)];
end
fprintf("地形表面最短路径总长度:%.3f \n", dist(end_node));

%% 绘图可视化
figure('Color','w');
surf(x,y,z,'EdgeAlpha',0.1); hold on;
shading interp;
colormap jet;
%绘制路径
plot3(px,py,pz+0.15,'r-','LineWidth',2.5);
%标记起点最高点、终点最低点
plot3(x(ix_start,iy_start),y(ix_start,iy_start),z_max+0.2,'go','MarkerSize',8,'MarkerFaceColor','g');
plot3(x(ix_end,iy_end),y(ix_end,iy_end),z_min+0.2,'mo','MarkerSize',8,'MarkerFaceColor','m');
xlabel('X');ylabel('Y');zlabel('Z 高程');
title('山地地形 + 最高点→最低点地表最短路径(Dijkstra)');
view(3);
grid on;

运行说明

  1. rng (1) 固定随机种子,每次生成同一片山地;注释掉可以生成不同起伏山地。

  2. 邻域:代码默认4 邻域(只能上下左右走);注释切换为 8 邻域可以斜向穿越。

  3. cost_uv = Δx2+Δy2+Δz2\sqrt{\Delta x^2+\Delta y^2+\Delta z^2}Δx2+Δy2+Δz2 ,这是沿着山地表面行走的实际距离,不是平面距离,上坡下坡都会增加路径代价。

  4. 绿色圆点 = 地形最高点;品红色圆点 = 地形最低点;红色线条为算法算出来的地表最短路径。


算法原理详细说明

问题建模:山地→加权图

山地是连续曲面 z=f(x,y)z=f(x,y)z=f(x,y),计算机不能直接处理连续曲面,做离散网格化

  1. 将 XY 平面切分成 n×nn\times nn×n 网格,每个网格交点 (i,j)(i,j)(i,j) 对应三维坐标 (xi,j,yi,j,zi,j)(x_{i,j},y_{i,j},z_{i,j})(xi,j,yi,j,zi,j),作为图的节点

  2. 每个节点只和周围相邻节点连通(4 邻域 / 8 邻域)。

  3. 边的权重 cost:两个相邻节点之间沿着山体表面的三维欧氏距离:

关键点:高程差Δz\Delta zΔz会增加行走代价。爬上陡坡,两点平面很近,但地表路径代价会很大;下坡同样也要计入行走距离。

  1. 起点 S:网格上高程最大的点;终点 T:网格高程最小的点。

目标:求从 S 到 T,总边权之和最小的路径,也就是山地表面最短路径

Dijkstra 算法原理(求解单源最短路径)

Dijkstra 适用于所有边权 ≥0 的图,本问题行走距离全部大于 0,满足条件。

核心变量

  • dist[]dist(v)表示起点 S 到达节点 v 的当前已知最短距离;初始全部无穷大,仅起点 dist (S)=0。

  • visited[]:标记节点是否已经确定最短距离。

  • prev[]:记录路径前驱节点,算法结束后回溯,还原完整路径。

算法迭代步骤

  1. 在所有未被访问 的节点中,选出dist值最小的节点 u。这个节点 u 的最短路径已经确定。

  2. 将 u 标记为已访问。

  3. 遍历 u 全部相邻节点 v:

    尝试松弛:if dist[v] > dist[u] + w(u→v)

    • 如果成立:说明经过 u 走到 v,距离更近。更新dist[v]=dist[u]+w(u→v],记录prev[v]=u
  4. 重复 1‑3,直到终点 T 被选为最小节点,提前终止;或者全部节点遍历完毕。

  5. 路径回溯 :从终点 T 不断向前读取prev[],反向得到完整路径点。

Dijkstra 数学保证

因为所有边权重非负,一旦节点 u 被选出(visited 标记),不可能后续再找到一条距离更小的路径到达 u,所以可以直接确定 u 的最短距离。

本模型局限

  1. 是网格离散近似,网格 n 越大结果越准,但计算速度变慢;n=50 兼顾效果与速度。

  2. 路径只能沿着网格点走,不是理论上连续曲面的精确测地线,属于工程数值近似。

  3. 本版本代价只考虑几何行走距离;如果你想模拟爬山体力消耗,可以修改 cost,上坡额外乘惩罚系数,模拟 "爬山比平路费力"。

拓展:A * 算法(加速版本)

Dijkstra 会盲目搜索整张图;A*加入启发函数加速,适合网格很大的场景。

A*估价函数:

  • g(n)g(n)g(n):起点到 n 的实际行走距离(等价 Dijkstra 的 dist)

  • h(n)h(n)h(n):启发估计,n 到终点的三维直线距离(不能大于真实最短距离,保证结果正确)

补充:如果想要真实连续曲面测地线

Dijkstra 网格是离散近似;MATLAB 自带 geodesic 可以计算三角网格曲面的严格测地线,需要把山地转成三角面片triangulation对象,计算量更大。Dijkstra 网格方法更容易理解,教学演示首选。

拓展修改示例:增加爬山体力惩罚

比如上坡增加代价:

matlab 复制代码
dz = z(iu,ju)-z(iv,jv);
geo_dist = sqrt(dx^2+dy^2+dz^2);
if(dz < 0)
    % dz<0:从u走到v高度上升(上坡)增加惩罚系数
    cost_uv = geo_dist * 1.8;
else
    cost_uv = geo_dist;
end

此时不再求几何最短,而是求体力消耗最小路径,路径会主动绕开陡峭山坡。

相关推荐
雨田言炎4 小时前
四、关于Qt项目需要知道的
开发语言·笔记·qt
程序喵大人5 小时前
【C++进阶】STL算法与函数对象 - 02 sort为什么需要随机访问迭代器
开发语言·c++·算法
SomeB1oody5 小时前
【RustyML入门】2.6. 线性判别分析
开发语言·后端·机器学习·rust·教程
用户昵称1006 小时前
C/C++编程-工程实践-本地存储log的工程意义
c语言·开发语言
吹什么轩7 小时前
c++复习:map和set的使用
开发语言·c++
必须得开心呀7 小时前
qt生成dump文件并定位异常
开发语言·qt
fpcc7 小时前
跟我学C++中级篇—内存流
开发语言·c++
Cicada1288 小时前
ccvt:一个用 Rust 写的中国地图坐标系互转命令行工具
开发语言·后端·rust
1001101_QIA8 小时前
工控机网络配置
开发语言·数据库·php