基于Maplibre的管线流动效果
由于最近开始了新的项目,技术上要求将模型中的管道节点使用二维地图渲染,管线带有流动动画效果, 并对地图性能,动画效果有比较高的要求,因此选择了maplibre作为基础地图框架,并使用antv L7图层绘制进行开发。
Maplibre是一款在Mapbox开源代码上进行了二次封装开发的框架,属于完全开源。
Maplibre国内使用较少,相关文档较少,官网案例中并未提供动态管线流动的案例,并且使用webgl绘制管线流动动画对于纯web前端来说学习成本太高,因此考虑使用了超图团队开发的Supermap for maplibre框架,感兴趣的小伙伴可以去官网查看具体案例 iclient.supermap.io/examples/ma…
话不多说,下面开始介绍具体使用方式,首先是引入Supermap for maplibre框架,官网提供了两种方式。
第一种:直接npm
npm install @supermapgis/iclient-maplibregl
第二种:通过js包引入,iclient.supermap.io/web/downloa…在官网地址中下载maplibregl对应的js包
项目中使用了第二种引入方式,为的是将来方便项目私有化部署。
一、script标签引人,include属性表示maplibregl中需要包含的其他技术,如antv的L7。
<script type="text/javascript" include="L7" src="../src/dist/maplibregl/include-maplibregl.js"></script>
二、引入maplibregl包之后开始进行地图开发,首先初始化地图实例(更多地图配置可以直接参考maplibre官方文档maplibre.org/maplibre-gl…)
// 初始化
let map = new maplibregl.Map({
container: 'map', // container id
style: '../src/assets/VectorWithLabel_en.json', //地图矢量切片
center: [114.177, 22.4819], // starting position
pitchWithRotate: false, // 旋转时倾斜角改变
zoom: 12,
attributionControl: true
});
三、接着在地图加载之后开始绘制管道图层,使用supermap封装的方法新建L7图层实例,图层配置的type为L7官网各类型图层名称。
let layer = new maplibregl.supermap.L7Layer({ type: 'LineLayer' }); // 管线流动图层
let layer2 = new maplibregl.supermap.L7Layer({ type: 'LineLayer', options: {enablePropagation: true, pickingBuffer: 6} }); // 基础管道图层
详情可参考L7官网文档l7.antv.antgroup.com/api/line_la…
四、配置管道图层数据,样式。
通过source来配置管线图层的数据,数据类型可以为JSON,Geojson,csv等,详情可参考l7.antv.antgroup.com/api/line_la…
let l7Layer2 = layer2.getL7Layer();
let l7Layer = layer.getL7Layer();
let lineData = []
let pipeDataReverse = JSON.parse(JSON.stringify(pipeData))
// pipeData 数据格式为[[[x1, y1],[x2,y2]]] 三维数组
pipeDataReverse.forEach((item, index) => {
item.forEach(its =>{
its.reverse()
})
lineData.push({color:[
255,
255,
102
], path: item, id:index, name: '管道' + index })
})
l7Layer
.source(lineData, {
parser: {
type: 'json',
coordinates: 'path'
}
})
通过size(控制管线直径),shape(管线样式,如曲线、直线等),color(管线颜色),select(选中管线颜色)控制绘制管线的具体展示。管道的动画效果则是通过animate来配置,配置参数如下:
- duration 动画时间 单位(s)秒
- interval 轨迹间隔, 取值区间 0 - 1
- trailLength 轨迹长度 取值区间 0 - 1
其他参数的配置的具体参数可以参考官方文档l7.antv.antgroup.com/api/line_la…
l7Layer
.source(lineData, {
parser: {
type: 'json',
coordinates: 'path'
}
})
.size(5)
.shape('line')
.color('color', (v) => {
return `rgba(18,255,255, 0.8)`;
})
.animate({
interval: 0.7,
trailLength: 0.5,
duration: 2
});
五、管线图层绑定maplibre,图层事件监听等
完成图层的配置工作以后需要将管线图层与地图进行绑定.
map.addLayer(layer2);
l7Layer2.on('click', (e) => {
const popup = new L7.Popup({
offsets: [0, 0],
closeButton: true
})
.setLnglat(e.lngLat)
.setHTML(`<span> 管道: ${e.feature.name}</span>`);
scene.addPopup(popup);
});
以上部分完成之后就可以得到一个流畅运行的地图管线动画效果了。以下是完整代码
地图绘制管道完整代码
<html>
<head>
<meta charset='utf-8'/>
<title>管道流量专题图</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no'/>
<script type="text/javascript" include="L7" src="../src/dist/maplibregl/include-maplibregl.js"></script>
<script type="text/javascript" src="../src/components/pipeData.js"></script>
<script type="text/javascript" src="../src/components/flowData.js"></script>
<style>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<div id='map'></div>
<div style="display: flex;
position: fixed;
bottom: 20px;
left: 20px;
background-color: white;
padding: 4px 10px;
align-items: center;
gap: 10px;
font-size: 14px;">
<div>流量(m³/s)</div>
<div style="display: flex;">
<div style="background-color: rgb(0, 62, 170); width: 24px; height: 18px;"></div>
<div style="background-color: rgb(2,167,240); width: 24px; height: 18px;"></div>
<div style="background-color: rgb(129,211,248); width: 24px; height: 18px;"></div>
<div style="background-color: rgb(225,225,225); width: 24px; height: 18px;"></div>
</div>
</div>
<script type="text/javascript">
var tileURL = 'https://iclient.supermap.io/iserver/services/map-china400/rest/maps/ChinaDark/zxyTileImage.png?z={z}&x={x}&y={y}';
var map = new maplibregl.Map({
container: 'map', // container id
style: {
version: 8,
sources: {
'raster-tiles': {
type: 'raster',
tiles: [tileURL],
tileSize: 256
}
},
layers: [
{
id: 'simple-tiles',
type: 'raster',
source: 'raster-tiles',
minzoom: 0,
maxzoom: 22
}
]
},
center: [114.177, 22.4819], // starting position
zoom: 12,
attributionControl: true
});
map.addControl(new maplibregl.NavigationControl(), 'top-left');
// map.on('load', function () {
// debugger
// map.addStyle(geoData)
// })
function divideArrayIntoQuartiles(arr) {
let absData = arr.map(item => Math.abs(item))
// 第一步:对数组进行排序
const sortedArray = absData.slice().sort((a, b) => a - b);
// 第二步:计算四分位数
function getQuartile(sortedArr, quartile) {
const index = (sortedArr.length - 1) * quartile;
const lowerIndex = Math.floor(index);
const upperIndex = Math.ceil(index);
if (lowerIndex === upperIndex) {
return sortedArr[lowerIndex];
}
return sortedArr[lowerIndex] + (sortedArr[upperIndex] - sortedArr[lowerIndex]) * (index - lowerIndex);
}
const Q1 = getQuartile(sortedArray, 0.75);
const Q2 = getQuartile(sortedArray, 0.8); // 中位数
const Q3 = getQuartile(sortedArray, 0.9);
console.log(Q1, Q2, Q3, sortedArray);
// 返回结果
return { Q1, Q2, Q3 };
}
// 示例使用
const quartiles = divideArrayIntoQuartiles(flowData);
map.on('load', function () {
map.getL7Scene().then((scene) => {
var layer = new maplibregl.supermap.L7Layer({ type: 'LineLayer', options:{
minZoom: 15
} });
var layer2 = new maplibregl.supermap.L7Layer({ type: 'LineLayer', options: {enablePropagation: true, pickingBuffer: 6} });
var l7Layer2 = layer2.getL7Layer();
var l7Layer = layer.getL7Layer();
let lineData = []
pipeData.forEach((item, index) => {
item.forEach(its =>{
its.reverse()
})
if(flowData[index] < 0){
item.reverse()
}
lineData.push({color:[
255,
255,
102
], path: item, id:index, name: '管道' + index, flow: Math.abs(flowData[index]) })
})
l7Layer
.source(lineData, {
parser: {
type: 'json',
coordinates: 'path'
}
})
.size(5)
.shape('line')
.color('color', (v) => {
return `rgba(18,255,255, 0.9)`;
})
.animate({
interval: 0.7,
trailLength: 0.5,
duration: 2
});
l7Layer2
.source(lineData, {
parser: {
type: 'json',
coordinates: 'path'
}
})
.size(2)
.select({
color: 'red',
mix: 0,
})
.shape('line').color('flow', (value) => {
if(value<quartiles.Q1){
return 'rgb(225,225,225)';
}else if(value>=quartiles.Q1 && value<quartiles.Q2){
return 'rgb(129,211,248)';
}else if(value>=quartiles.Q2 && value<quartiles.Q3){
return 'rgb(2,167,240)';
}else{
return 'rgb(0, 62, 170)';
}
});
map.addLayer(layer);
map.addLayer(layer2);
l7Layer2.on('click', (e) => {
console.log(e);
const popup = new L7.Popup({
offsets: [0, 0],
closeButton: true
})
.setLnglat(e.lngLat)
.setHTML(`<div> 管道: ${e.feature.name}</div>
<div> 流量: ${e.feature.flow}</div>
`);
scene.addPopup(popup);
});
})
});
</script>
</body>
</html>