当前位置: 首页 > news >正文

地图可视化实践录:使用Turf.js简化路线

本文如何使用Turf.js的simplify接口简化路线,并给出nodejs和golang的处理函数。

知识点

路线效果图分析

GeoJSON学习那篇文章的示例11,定制了3条路线,效果图如下:

在这里插入图片描述

这些路线实际是使用高德地图平台导航再找出经纬度坐标的,部分路段有重叠,所以只看到了一条线,下面是使用高德地图导航放大立交桥部分的图。

在这里插入图片描述

我们自己绘制的路线细节如下:

在这里插入图片描述

从图可以看到,立交转弯部分线段的效果非常好,与实际地图吻合。经分析,这部分需要用到的经纬度坐标点也是很多的。因此,可以得到结论:绘制线段越细致,坐标点越多,数据量体积越大。如涉及传输,则消耗的流量带宽也多。

故,可以根据使用一定的简化手段,在减少数据量情况下兼顾绘制效果。

simplify简化

simplify接口根据给定的GeoJSON对象和参数,处理后返回简化后的 GeoJSON。其内部使用 simplify-js 实现,简化算法为Ramer–Douglas–Peucker

simplify有2个参数:geojson对象和options选项。具体如下:

  • geojson GeoJSON 待简化的GeoJSON对象
  • options? Object 可选参数(默认为{})
    • options.tolerance? number 简化容差(默认为1)
    • options.highQuality? boolean 是否采用更耗时的高质量简化算法(默认为false)
    • options.mutate? boolean 是否允许直接修改输入数据(设置为true可显著提升性能)(默认为false)

实践

代码

在综合示例基础上添加简化路线功能。核心代码:

function showSimplifyTurf(inputStr) {console.log('简化路线:', inputStr);const geojsonData = JSON.parse(inputStr);// tolerancevar options = { tolerance: 0.1, highQuality: false };var simplified = turf.simplify(geojsonData, options);var object = drawOneLayer(simplified);addToLayers(object);mymap.fitBounds(object.getBounds());var ouput = JSON.stringify(simplified, null, 2)return `简化成功\r\n${ouput}`
}

从上述3条路线中抽出较有代表性的路线:小鸡村到大鸡村路线。

整理的GeoJSON如下,可以算出经纬度坐标数组有106个元素,数量较多。

小鸡村到大鸡村路线.json
{"type": "FeatureCollection","features": [{"type": "Feature","properties": {"color": "#FF0000","name": "小鸡村到大鸡村"},"geometry": {"type": "LineString","coordinates": [[108.355996,22.856245],[108.355997,22.856245],[108.356491,22.856224],[108.356877,22.856202],[108.357494,22.856175],[108.358438,22.856122],[108.359371,22.85609],[108.35949,22.85609],[108.359774,22.85609],[108.360224,22.856084],[108.360305,22.856084],[108.360573,22.85609],[108.360847,22.8561],[108.360975,22.8561],[108.362107,22.856127],[108.36273,22.856213],[108.363025,22.856261],[108.363625,22.856433],[108.364033,22.856572],[108.364382,22.856674],[108.364865,22.856862],[108.364999,22.85691],[108.365133,22.856958],[108.366039,22.857388],[108.366286,22.857533],[108.366646,22.857758],[108.36759,22.858434],[108.367692,22.858461],[108.367987,22.858557],[108.368089,22.858563],[108.368148,22.858557],[108.368191,22.858546],[108.368282,22.858514],[108.368351,22.858466],[108.36841,22.858412],[108.368453,22.858348],[108.36848,22.858278],[108.368496,22.858192],[108.368496,22.858128],[108.36847,22.85801],[108.368426,22.857935],[108.368394,22.857886],[108.36833,22.857833],[108.368271,22.857801],[108.36818,22.857768],[108.368062,22.857758],[108.367965,22.857768],[108.36789,22.85779],[108.367842,22.857811],[108.367756,22.85787],[108.367708,22.857924],[108.367606,22.858048],[108.367515,22.858112],[108.36701,22.858949],[108.366672,22.859415],[108.366308,22.859871],[108.366125,22.860065],[108.366007,22.860199],[108.365878,22.860327],[108.365744,22.860461],[108.36509,22.861057],[108.364859,22.861234],[108.364591,22.861422],[108.364511,22.861459],[108.364344,22.861513],[108.363486,22.86206],[108.363153,22.862232],[108.362703,22.862425],[108.362381,22.862548],[108.361769,22.862763],[108.361211,22.862935],[108.360943,22.863128],[108.360831,22.863149],[108.360471,22.86323],[108.359811,22.863331],[108.359334,22.863385],[108.358937,22.863406],[108.358395,22.863417],[108.358272,22.863417],[108.358111,22.863417],[108.357891,22.863417],[108.357773,22.863417],[108.357038,22.863412],[108.356662,22.863412],[108.356442,22.863417],[108.356453,22.863557],[108.356464,22.863643],[108.35655,22.864597],[108.356603,22.865064],[108.35663,22.865263],[108.356636,22.865365],[108.356759,22.866502],[108.356915,22.867929],[108.356941,22.868331],[108.357,22.869195],[108.356963,22.869329],[108.356915,22.870203],[108.356904,22.870407],[108.356893,22.870622],[108.35685,22.872016],[108.356845,22.872548],[108.356383,22.872601],[108.354361,22.872585],[108.353015,22.872596],[108.35192,22.872606],[108.351785,22.872607]]}},{"type": "Feature","properties": {"name": "小鸡村"},"geometry": {"type": "Point","coordinates": [108.355996,22.856245]}},{"type": "Feature","properties": {"name": "大鸡村"},"geometry": {"type": "Point","coordinates": [108.351785,22.872607]}}]
}

调整简化容差tolerance参数,逐个测试,从下图可以看到,容差值为0.1时,简化结果只有头尾2个坐标点了。该值越小,效果越好。

在这里插入图片描述

下面对立交部分进行测试对比。

在这里插入图片描述

经过计算,上图2个等级简化后的坐标点数量分别为18和56,与原来对比,起码减少一半的数量。同时可以看到,当容差值为0.00001时,简化后效果与原路线基本没有什么差别了。

在nodejs中简化

本节在nodejs中编写简化函数,先安装turf

npm install @turf/turf

完整代码如下:

const turf = require('@turf/turf');
const fs = require('fs');// 获取命令行参数
const args = process.argv;main()function main() {const inputFile = args[2];const outputFile = args[3];if (!inputFile || !outputFile) {console.error('请提供输入文件和输出文件路径');console.log('用法: node xxx.js <输入文件> <输出文件>');process.exit(1);}if (!fs.existsSync(inputFile)) {console.error(`错误: 输入文件 "${inputFile}" 不存在`);return;}const originalGeoJSON = JSON.parse(fs.readFileSync(inputFile, 'utf8'));// 执行优化const optimizedGeoJSON = optimizeGeoJSON(originalGeoJSON);// 保存优化后的文件fs.writeFileSync(outputFile, JSON.stringify(optimizedGeoJSON, null, 2));// 统计信息const originalSize = JSON.stringify(originalGeoJSON).length;const optimizedSize = JSON.stringify(optimizedGeoJSON).length;console.log('\n=== 优化完成 ===');console.log(`原始文件大小: ${(originalSize / 1024).toFixed(2)} KB`);console.log(`优化后文件大小: ${(optimizedSize / 1024).toFixed(2)} KB`);console.log(`体积减少: ${((1 - optimizedSize / originalSize) * 100).toFixed(2)}%`);//formatJsonFile(inputFile, outputFile); return;
}// 将json格式化再保存
function formatJsonFile(inputFile, outputFile) {const orgJsonObj = JSON.parse(fs.readFileSync(inputFile, 'utf8'));const jsonstr = JSON.stringify(orgJsonObj, null, 2);fs.writeFile(outputFile, jsonstr, 'utf8', (error) => {if (error) {console.error('保存文件时出错:', error);} else {console.log('文件保存成功:', outputFile);}});
}/*** 优化GeoJSON的完整流程* @param {Object} geojson 原始GeoJSON数据* @returns {Object} 优化后的GeoJSON数据*/
function optimizeGeoJSON(geojson) {console.log('开始优化GeoJSON数据...');const optimizedFeatures = geojson.features.map((feature, index) => {if (feature.geometry.type === 'LineString') {console.log(`处理线路 ${index + 1}: ${feature.properties.name || feature.properties.id}`);const originalPoints = feature.geometry.coordinates.length;// 第一步:清理重复顶点let cleaned = turf.cleanCoords(feature);console.log(`  - 清理重复顶点: ${originalPoints} -> ${cleaned.geometry.coordinates.length}`);// 第二步:几何简化const simplified = turf.simplify(cleaned, {tolerance: 0.0002, //0.0001,    // 容差值,可调整highQuality: true     // 高质量模式});console.log(`  - 几何简化: ${cleaned.geometry.coordinates.length} -> ${simplified.geometry.coordinates.length}`);// 第三步:再次清理,确保没有因简化产生的重复点const final = turf.cleanCoords(simplified);console.log(`  - 最终优化: ${simplified.geometry.coordinates.length} -> ${final.geometry.coordinates.length}`);console.log(`  - 总减少: ${originalPoints} -> ${final.geometry.coordinates.length} (${((1 - final.geometry.coordinates.length / originalPoints) * 100).toFixed(1)}%)`);return {...final,properties: feature.properties};}return feature;});return {type: 'FeatureCollection',features: optimizedFeatures};
}

执行:

node turf_simple.js  turf_file/my_input.json turf_file/output.json

执行输出结果示例:

开始优化GeoJSON数据...
处理线路 1: 小鸡村到大鸡村- 清理重复顶点: 106 -> 101- 几何简化: 101 -> 15- 最终优化: 15 -> 15- 总减少: 106 -> 15 (85.8%)
处理线路 2: 某立交到狮山公园- 清理重复顶点: 94 -> 89- 几何简化: 89 -> 14- 最终优化: 14 -> 14- 总减少: 94 -> 14 (85.1%)
处理线路 3: 某立交到某检测站- 清理重复顶点: 95 -> 90- 几何简化: 90 -> 14- 最终优化: 14 -> 14- 总减少: 95 -> 14 (85.3%)=== 优化完成 ===
原始文件大小: 7.16 KB
优化后文件大小: 1.55 KB
体积减少: 78.30%

将简化后的GeoJson在页面展示,多路线简化效果如下。

在这里插入图片描述

可以看到,即使原始路段的位置有重叠,但简化后,有些点的位置并不相同。

在golang中简化

在golang中,可以使用github.com/paulmach/orb库简化路线,该库实际也是使用RDP算法(Ramer–Douglas–Peucker)的。

实现函数如下:

import ("github.com/paulmach/orb""github.com/paulmach/orb/geojson""github.com/paulmach/orb/simplify"
)
// simplyOneLine 使用 RDP算法(Ramer–Douglas–Peucker)简化线段
// lineCoord: 输入线段坐标,格式为 [][2]float64,每个元素是 [lng, lat]
// tolerance: 容差值,单位与坐标相同(度)。值越大简化越厉害,通常用 0.0001 开始测试
// 返回: 简化后的坐标数组
func simplyOneLine(lineCoord [][2]float64) [][2]float64 {tolerance := 0.0001 // 中等,再大效果就差很多了// 如果点数少于3个,直接返回(RDP算法需要至少3个点才有意义)if len(lineCoord) < 3 {return lineCoord}// 将 [][2]float64 转换为 orb.LineStringlineString := make(orb.LineString, len(lineCoord))for i, coord := range lineCoord {lineString[i] = orb.Point{coord[0], coord[1]} // [lng, lat]}// 使用 Ramer–Douglas–Peucker 算法进行简化simplified := simplify.DouglasPeucker(tolerance).Simplify(lineString.Clone())// 类型断言转换回 orb.LineStringsimplifiedLine, ok := simplified.(orb.LineString)if !ok {// 如果简化失败,返回原始数据return lineCoord}// 将 orb.LineString 转换回 [][2]float64result := make([][2]float64, len(simplifiedLine))for i, point := range simplifiedLine {result[i] = [2]float64{point[0], point[1]} // [lng, lat]}// klog.Printf("debug 简化画线 %v -> %v\n", len(lineCoord), len(result))return result
}

小结

本文通过一些实例介绍了使用Turf.js库简化路线的实现和效果。在前后端分离的项目中,建议对较复杂的路线进行简化,以减少传输数据体积。

代码

文中列出了主要的代码片段,另有相关的工程demo,已上传到github仓库。因不定时更新,代码不一定与文中严格对应,以代码仓库为准。如使用,请自行根据实际情况修改。

仓库地址:https://github.com/latelee/mapdemo 。

本文涉及文件:100.综合示例.html,函数为showSimplifyTurf

  • turf官方API说明(英文版):http://turfjs.org/docs/api/simplify
http://www.dtcms.com/a/609834.html

相关文章:

  • 从零开始搭建Linux Web服务器
  • 南通网站建设制作html网页设计表格代码范文
  • Chrome 插件框架 Plasmo 基本使用示例
  • 一小时学做网站杭州高端网站设计
  • LinuxC语言文件i/o笔记(第十八天)
  • 上海网站设计哪家强常州做上市公司律所
  • Word进阶
  • MySQL: 基准测试全流程指南:原理、工具(mysqlslap/sysbench)与实战演示
  • 青岛建站公司流程建筑行业公司
  • 贵州省网站备案虚拟主机管理系统
  • Hexo 个人博客从搭建到上线全流程(含踩坑指南)
  • CNN详解:卷积神经网络是如何识别图像的?
  • [高可用/负载均衡] Ribbon LoadBalancer: 开源的客户端式负载均衡框架
  • 深入理解 Python 的 `with` 语句及其与迭代器的交互
  • R脚本-环境数据处理:利用R批量对环境数据眼膜提取转ASC
  • 可做影视网站的服务器手机浏览wordpress
  • 网站做跳转微信打开源码之家
  • 集美区网站建设校园网站建设管理工作制度
  • MySQL 常用 SQL 语句大全
  • 海康视频 h5player 配置 proxy 代理websocket播放视频问题(websocket在业务系统https方式访问http的播放视频)
  • 近期发生一个因为渲染导致的bug
  • 关于在嵌入式中打印float类型遇到的bug
  • Docker、Compose、Portainer与K8s详解
  • 益和热力性能优化实践:从 SQL Server 到 TDengine,写入快 20 秒、查询提速 5 倍
  • 自定义导航网站 源码网站按钮样式
  • docker启动失败
  • 卡索(CASO)汽车调查:数据智能时代,车企如何打赢一场“认知战”?
  • 数据结构之二叉树-链式结构(上)
  • 无极网站广告制作合同模板免费
  • 安全模式怎么进?【图文详解】win10/11安全模式?如何进入安全模式?