编辑
2024-12-31
Mapbox
00

目录

基于Mapbox高程数据打造无人机仿地飞行可视化图表
引言
技术栈概览
核心功能实现
1. 地形数据获取
2. 动态图表容器
核心绘图函数详解
1. 图表初始化与清理
2. 数据处理与比例尺设置
3. SVG容器创建与渐变定义
4. 坐标轴绘制
5. 地形轮廓与路径生成器
6. 绘制地形和路径
7. 航点标记绘制
视觉设计亮点
1. 整体布局设计
2. 地形表示
3. 飞行路径
4. 航点标记
5. 坐标轴
性能优化
实际应用价值
总结与展望
代码解释

技术应用mapbox-glAPI、mapbox-dem高程数据、D3动态计算生成 关键技术

  • [√] mapbox-dem预加载
  • [√] D3动态计算SVG

仿地飞行

基于Mapbox高程数据打造无人机仿地飞行可视化图表

引言

在无人机航线规划领域,一个常见但棘手的问题是如何确保无人机能够安全地进行仿地飞行(Terrain Following Flight)。今天,我要分享一个结合Mapbox地形数据和D3.js可视化的解决方案,它不仅能帮助我们直观地规划航线,还能实时展示地形起伏与飞行路径的关系。

技术栈概览

  • Mapbox GL JS: 提供地图渲染和地形数据
  • D3.js: 负责飞行路径的可视化
  • React: 构建用户界面
  • TypeScript: 确保代码类型安全

核心功能实现

1. 地形数据获取

首先,我们需要从Mapbox获取数字高程模型(DEM)数据。Mapbox提供了queryTerrainElevationAPI,让我们能够获取任意坐标点的海拔高度:

typescript
const { altitudeCalculation } = usePointAltitudeCalculation(); const waypoints = await altitudeCalculation(taskData, map);

2. 动态图表容器

为了确保图表能够响应式适应不同屏幕尺寸,我们使用了ResizeObserver

typescript
useEffect(() => { if (!containerRef.current) return; const resizeObserver = new ResizeObserver(entries => { const entry = entries[0]; if (entry) { const width = entry.contentRect.width; const height = width * aspectRatio; setDimensions({ width, height }); } }); resizeObserver.observe(containerRef.current); return () => resizeObserver.disconnect(); }, [aspectRatio]);

核心绘图函数详解

drawLineChart函数是整个可视化系统的核心,让我们深入解析其实现细节:

1. 图表初始化与清理

typescript
// 清除现有内容,确保图表重绘时不会产生叠加效果 d3.select(svgRef.current).selectAll('*').remove(); // 设置图表边距,预留轴线和标签空间 const margin = { top: 20, right: 30, bottom: 30, left: 40 }; const innerWidth = dimensions.width - margin.left - margin.right; const innerHeight = dimensions.height - margin.top - margin.bottom;

2. 数据处理与比例尺设置

typescript
// 数据分类:区分地形数据和航点数据 const contrastData: WaypointsFeaturesMeta[] = _data; const dronesData: WaypointsFeaturesMeta[] = _data.filter(d => d.id.indexOf("waypoint-") > -1); // X轴比例尺:将距离映射到画布宽度 const xScale = d3 .scaleLinear() .domain([0, d3.max(contrastData, (d: WaypointsFeaturesMeta) => d.distance) || 0]) .range([0, innerWidth]); // Y轴比例尺:将高度映射到画布高度 const yScale = d3 .scaleLinear() .domain([ // 最小值:最低海拔减去10米余量 (_data.reduce((min, obj) => obj.altitude < min ? obj.altitude : min, _data[0]?.altitude)) - 10, // 最大值:最高海拔加上高度再加10米余量 (_data.reduce((max, obj) => (obj.altitude + obj.height) > max ? (obj.altitude + obj.height) : max, _data[0]?.altitude + _data[0]?.height)) + 10 ]) .range([innerHeight, 0]);

3. SVG容器创建与渐变定义

typescript
// 创建主SVG容器并设置位置 const svg = d3 .select(svgRef.current) .attr('width', dimensions.width) .attr('height', dimensions.height) .append('g') .attr('transform', `translate(${margin.left},${margin.top})`); // 定义地形区域的渐变效果 const contrastGradient = svg .append('defs') .append('linearGradient') .attr('id', 'contrastGradient') .attr('x1', '0%') .attr('x2', '0%') .attr('y1', '0%') .attr('y2', '100%'); // 设置渐变的起始和结束颜色 contrastGradient .append('stop') .attr('offset', '0%') .attr('stop-color', 'rgba(247, 187, 98, 0.3)') // 顶部颜色:淡黄色 .attr('stop-opacity', 1); contrastGradient .append('stop') .attr('offset', '100%') .attr('stop-color', 'rgba(247, 178, 98, 0)') // 底部颜色:透明 .attr('stop-opacity', 0.1);

4. 坐标轴绘制

typescript
// X轴:距离轴 svg.append('g') .attr('transform', `translate(0,${innerHeight})`) .call(d3.axisBottom(xScale)) .attr('color', 'rgba(255, 255, 255, 0.45)') // 设置轴线和刻度颜色 .call(g => g.select('.domain').attr('stroke', 'rgba(255, 255, 255, 0.45)')) // 主轴线颜色 .call(g => g.selectAll('.tick line').attr('stroke', 'rgba(255, 255, 255, 0.45)')) // 刻度线颜色 .append('text') .attr('x', innerWidth - 20) .attr('y', 25) .attr('fill', 'rgba(255, 255, 255, 0.65)') .text('航点相对距离(m)'); // X轴标签 // Y轴:高度轴 svg.append('g') .call(d3.axisLeft(yScale)) .attr('color', 'rgba(255, 255, 255, 0.45)') .call(g => g.select('.domain').attr('stroke', 'rgba(255, 255, 255, 0.45)')) .call(g => g.selectAll('.tick line').attr('stroke', 'rgba(255, 255, 255, 0.45)')) .append('text') .attr('y', -5) .attr('x', 50) .attr('fill', 'rgba(255, 255, 255, 0.65)') .text('地形海拔高度(m)'); // Y轴标签

5. 地形轮廓与路径生成器

typescript
// 地形区域生成器:创建填充区域 const areaGenerator = d3.area<DataPoint>() .x((d: WaypointsFeaturesMeta) => xScale(d.distance)) // X坐标映射 .y0(innerHeight) // 底部基线 .y1((d: WaypointsFeaturesMeta) => yScale(d.altitude)); // 顶部轮廓线 // 地形轮廓线生成器 const lineGenerator = d3.line<DataPoint>() .x((d: WaypointsFeaturesMeta) => xScale(d.distance)) .y((d: WaypointsFeaturesMeta) => yScale(d.altitude)); // 飞行路径线生成器:考虑飞行高度 const _lineGenerator = d3.line<DataPoint>() .x((d: WaypointsFeaturesMeta) => xScale(d.distance)) .y((d: WaypointsFeaturesMeta) => yScale(d.altitude + d.height));

6. 绘制地形和路径

typescript
// 绘制地形填充区域 svg.append('path') .datum(contrastData) .attr('class', 'area') .attr('d', areaGenerator) .attr('fill', 'url(#contrastGradient)'); // 使用之前定义的渐变 // 绘制地形轮廓线 svg.append('path') .datum(contrastData) .attr('class', 'line') .attr('d', lineGenerator) .attr('fill', 'none') .attr('stroke', 'rgba(247, 187, 98, 0.65)') // 黄色轮廓线 .attr('stroke-width', 2); // 绘制飞行路径线 svg.append('path') .datum(dronesData) .attr('d', _lineGenerator) .attr('fill', 'none') .attr('stroke', '#01BE10') // 绿色飞行路径 .attr('stroke-width', 2);

7. 航点标记绘制

typescript
// 为每个航点创建标记 dronesData.forEach((d: WaypointsFeaturesMeta, i: number) => { // 创建航点组 const group = svg.append('g') .attr('transform', `translate(${xScale(d.distance)},${yScale((d.altitude + d.height))})`) .attr("id", d.id) .style('cursor', 'pointer') .on('click', () => setClickId(d.id)); // 点击事件处理 // 绘制外圆:状态指示 group.append('circle') .attr('r', 10) .attr('fill', d.isCorrect ? '#01BE10' : "#FA4444") // 根据状态设置颜色 .attr('stroke', d.isCorrect ? '#01BE10' : "#FA4444") .attr('stroke-width', 2); // 绘制内圆:选中状态 group.append('circle') .attr('r', 9) .attr('fill', clickId == d.id ? d.isCorrect ? '#10881A' : "#FA4444" : '#fff'); // 添加序号文本 group.append('text') .attr('dy', 4) .attr('fill', clickId == d.id ? '#fff' : d.isCorrect ? '#10881A' : "#FA4444") .attr('text-anchor', 'middle') .style('font-size', '12px') .text(i === 0 ? "T" : i + 1); // 首个点显示"T",其他显示序号 });

视觉设计亮点

1. 整体布局设计

  • 渐变填充:地形区域使用渐变填充,增强了视觉层次感
  • 状态反馈:航点使用不同颜色表示不同状态,绿色表示正常,红色表示需要调整
  • 动态伸缩:图表支持展开收起动画,提升用户体验

2. 地形表示

  • 使用渐变填充区域表示地形高度
  • 黄色轮廓线清晰描绘地形起伏
  • 半透明效果提升层次感

3. 飞行路径

  • 绿色实线表示规划的飞行路径
  • 位于地形之上,清晰展示高度关系

4. 航点标记

  • 双层圆环设计,突出显示当前状态
  • 绿色表示正常,红色表示需要调整
  • 序号清晰可见,首个航点特殊标记为"T"
  • 支持点击交互,选中状态视觉反馈明确

5. 坐标轴

  • 白色半透明设计,保持专业性的同时不喧宾夺主
  • 清晰的单位标注,便于数据解读

性能优化

  1. 防抖处理:使用useEffect的依赖项合理控制重绘时机
  2. 数据缓存:使用useRef缓存航点数据,避免不必要的重复计算
  3. 按需更新:仅在必要时更新图表,避免频繁重绘

实际应用价值

这个可视化系统在无人机航线规划中具有几个关键价值:

  1. 安全性提升:直观展示无人机与地形的高度关系,避免碰撞风险
  2. 效率优化:帮助操作员快速识别并调整不合理的航点设置
  3. 实时反馈:通过颜色编码和交互式操作,提供即时的航线质量反馈

总结与展望

这个基于Mapbox和D3.js的无人机航线可视化方案,成功地将复杂的地形数据转化为直观的视觉呈现。它不仅解决了仿地飞行规划的技术难题,还提供了流畅的用户体验。

未来的优化方向包括:

  • 支持3D视角切换
  • 添加更多飞行参数的可视化
  • 优化大数据量下的渲染性能
  • 提供更多的航线优化建议

通过这个项目,我们看到了现代Web技术在专业领域应用的无限可能。期待看到更多基于这个框架的创新应用!

以上内容来源于Claude AI, 我懒得写了, 以上代码给出的是如何绘制出与地图操作对应的图表

以下内容来源于手动补充

如何获取并使用高程数据

1、mapbox-gl初始化时,需要设置styles, 可使用自定义styles也可使用别的
2、初始化之后再load中进行高程瓦片加载

关键代码

react
// map.current 为new mapboxgl.Map()初始化 map.current.addSource('mapbox-dem', { type: 'raster-dem', url: 'mapbox://mapbox.mapbox-terrain-dem-v1', tileSize: 512, maxzoom: 20 }); map.current.setTerrain({ 'source': 'mapbox-dem', 'exaggeration': 1 });

问题
在官方案例中在获取高程数据时需要进行以下代码
await map.once('idle');
但是我在使用过程中发现此段代码会导致获取高程数据卡顿卡死,且删除也能获取到高程数据

代码解释

函数altitudeCalculation为自定义函数,主要是解决绘制的航点根据间隔每2m来进行高程数据获取并与真正航点进行对比,进而绘制出黄色模块的地形图图表

代码中clickId是记录地图上或者D3图上点击了某个航点进行标记,实现图表上以及地图上的颜色标识切换

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:还是夸张一点

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

还是夸张一点技术专栏 © 2019 - 2023 | 滇ICP备2022001556号
世间情动不过盛夏白瓷梅子汤,碎冰碰壁当啷响。