Leaflet.js与Turf.js平滑曲线生成展示实战教程
在GIS路线相关的开发场景中,轨迹可视化是一个高频需求。通常我们会在地图上采集若干个关键经纬度点,再将其串联成一条路线。但一个常见痛点随之而来:如果仅采集少数关键点,最终呈现的折线会非常生硬,尤其在拐点部位显得格外突兀。直观效果就像下面这张图所示:
那么,有没有什么手段能让这些拐点连成的曲线看起来更加自然平滑?有开发者可能会说:“多采集一些点不就行了?”理论上确实如此,点越密曲线越平滑。但现实是,野外数据采集工作强度大,点位越多耗费的人力时间成本越高。那么,能否在不增加外业工作量的前提下,直接基于现有数据进行曲线平滑处理呢?
这里分享一套基于Turf.js的WebGIS解决方案。它能在不增加采集负担的情况下,动态生成平滑曲线并渲染到地图上。对于希望在WebGIS中实现类似功能的开发者来说,这是一个相当实用的技术路径。
一、问题的由来
线由点构成。将相邻点两两连接,一条折线便形成了。为了便于演示,我们模拟了一条虚拟的自驾路线。然后借助Turf.js生成不同平滑程度的曲线,直观对比效果。
先从最基础的实现开始:以Leaflet作为地图渲染引擎,配合Turf.js进行曲线计算。
1、创建网页框架
首先创建一个HTML页面。页面中需要引入Leaflet的CSS和JS文件,以及Turf.js的库。核心代码如下:
基于Leaflet和Turf生成平滑曲线实践
<script src="/2d/leaflet/leaflet.js?v=1.0.0"></script>
<script src="https://unpkg.com/@turf/turf/turf.min.js"></script>
<script></script>
2、创建map对象
基础框架搭建完成后,实例化地图对象,将容器绑定到DOM上:
var mymap = L.map('map').setView([29.052934, 104.0625], 6);
var tileLayer = L.tileLayer('http://localhost:8086/data/xxgc/q0403/{z}/{x}/{y}.png', {
maxZoom: 7,
minZoom:0
});
tileLayer.addTo(mymap);
3、构建点位,生成路线
为了演示,我们准备一组虚拟坐标数据。模拟一条自驾路线:从云南保山出发,依次经过攀枝花、昆明、成都、重庆、贵阳、长沙、赣州,最终抵达福建福州。各城市经纬度如下:
var cityPoint = new Array();
cityPoint.push([99.116482, 25.078402]);//保山
cityPoint.push([101.708392, 26.50289]);//攀枝花
cityPoint.push([102.780718, 24.915559]);//昆明
cityPoint.push([104.098757, 30.594412]);//成都
cityPoint.push([106.559098, 29.452047]);//重庆
cityPoint.push([106.515163, 26.461228]);//贵阳
cityPoint.push([112.929622, 28.141659]);//长沙
cityPoint.push([114.956049, 25.713705]);//赣州
cityPoint.push([119.344081, 26.027307]);//福州
然后将这些点位连成线,显示在地图上:
var linestring = turf.lineString(cityPoint, {name: 'line 1'});
L.geoJSON(linestring,{
style:{color:"blue",wight:2,fill:false}
}).addTo(mymap);
查看原始效果。注意各个拐点——转折确实不够平滑。
二、Turf.js平滑曲线改造
现在进入核心环节。先简要了解Turf.js中用于曲线平滑的API。
1、官网方法介绍
Turf.js提供了bezierSpline()方法,用于对多线段进行平滑处理。其原理基于贝塞尔样条算法,将原始折线转换为弯曲路径。关于贝塞尔曲线的数学原理,感兴趣的话可以自行查阅相关资料,里面涉及不少数值计算细节。
方法参数如下:
| 参数 | 类型 | 描述 |
|---|---|---|
| line | Feature |
input LineString |
| options | Object | 可选参数:见下文 |
options选项:
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| resolution | number | 10000 | 点之间的时间(毫秒) |
| sharpness | number | 0.85 | 衡量样条路径应该有多弯曲的一个度量 |
返回值是Feature
2、0.4弯曲度曲线
在实际测试中,我们将sharpness设置为0.4,观察效果:
var curved = turf.bezierSpline(linestring,{sharpness:0.4,resolution:10000});
L.geoJSON(curved,{
style:{color:"red",wight:2,fill:false}
}).addTo(mymap);
将生成的红色曲线与原始蓝色折线叠加显示。可以明显看到,整条路线的过渡变得平滑许多。
3、0.85弯曲度曲线
接着将弯曲度调高至0.85:
L.geoJSON(turf.bezierSpline(linestring,{sharpness:0.85,resolution:10000}),{
style:{color:"green",wight:2,fill:false}
}).addTo(mymap);
从效果来看,0.85的曲线弯曲程度显著增大,与实际路线的形态偏差也更为明显。
4、0.1弯曲度曲线
再尝试将弯曲度降至0.1:
L.geoJSON(turf.bezierSpline(linestring,{sharpness:0.1,resolution:10000}),{
style:{color:"yellow",wight:2,fill:false}
}).addTo(mymap);
5、综合对比
将三条不同弯曲度的曲线叠加在同一张地图上,对比效果更加直观:
从对比中可以看出:弯曲度系数越大,曲线越“弯”。黄色对应0.1弯曲度,红色对应0.4,绿色对应0.85。
| 序号 | 弯曲度 | 实际效果 |
|---|---|---|
| 1 | 0.1 | 与原始拟合 |
| 2 | 0.4 | 有一定弯曲 |
| 3 | 0.85 | 弯曲较大 |
最后附上完整的示例代码。运行时请将图源地址替换为自己服务器上的瓦片服务地址。
基于Leaflet和Turf生成平滑曲线实践
<script src="/2d/leaflet/leaflet.js?v=1.0.0"></script>
<script src="https://unpkg.com/@turf/turf/turf.min.js"></script>
<script>
var mymap = L.map('map').setView([29.052934, 104.0625], 6);
var tileLayer = L.tileLayer('http://localhost:8086/data/xxgc/q0403/{z}/{x}/{y}.png', {
maxZoom: 7,
minZoom:0
});
tileLayer.addTo(mymap);
var cityPoint = new Array();
cityPoint.push([99.116482, 25.078402]);
cityPoint.push([101.708392, 26.50289]);
cityPoint.push([102.780718, 24.915559]);
cityPoint.push([104.098757, 30.594412]);
cityPoint.push([106.559098, 29.452047]);
cityPoint.push([106.515163, 26.461228]);
cityPoint.push([112.929622, 28.141659]);
cityPoint.push([114.956049, 25.713705]);
cityPoint.push([119.344081, 26.027307]);
var linestring = turf.lineString(cityPoint, {name: 'line 1'});
L.geoJSON(linestring,{
style:{color:"blue",wight:2,fill:false}
}).addTo(mymap);
var curved = turf.bezierSpline(linestring,{sharpness:0.4,resolution:10000});
L.geoJSON(curved,{
style:{color:"red",wight:2,fill:false}
}).addTo(mymap);
L.geoJSON(turf.bezierSpline(linestring,{sharpness:0.85,resolution:10000}),{
style:{color:"green",wight:2,fill:false}
}).addTo(mymap);
L.geoJSON(turf.bezierSpline(linestring,{sharpness:0.1,resolution:10000}),{
style:{color:"yellow",wight:2,fill:false}
}).addTo(mymap);
</script>
总结
总体而言,利用Turf.js的bezierSpline方法配合sharpness参数,能够高效地在已有数据基础上对手绘路线做平滑优化。既不需要增加野外采集的点数,也不会扭曲原始路径的走向意图。在拐点修饰这类GIS可视化场景中,这是一条相当优雅的技术路线。





