大规模的渐变线条
在maptalks的生态体系内,支持渐变线条的图层有:
- VectorLayer
- LineStringLayer
- ThreeLayer
- 其他等
1.VectorLayer 底层是用 createLinearGradient通过不断的颜色插值 模拟出来的,所以当数据量大后性能非常差
2.LineStringLayer底层是通过构造渐变纹理来解决的,当数据频繁的更新时也会纹理构造的瓶颈
3.ThreeLayer比较自由,随便用户自定义化,我们可以利用webgl里的顶点着色来进行构造这个渐变的线条
总结:这种需求应该使用ThreeLayer来完成,因为极端的需求都需要细致和几乎变态的手动优化
难点分析
- 颜色插值
因为线条是不规则的线条,上面有多个顶点,而一般我们的线条颜色都是按百分比配色的, 这时我们可以求出每个顶点相对于开始点的长度百分比来获取这个顶点的颜色,至于颜色插值库这里用的colorin
js
let ciCache;
function getCi(feature) {
const { startColor, endColor } = feature;
const key = `${startColor}_${endColor}`;
let ci = ciCache.get(key);
if (!ci) {
ci = new colorin.ColorIn([
[0, startColor],
[1, endColor],
]);
ciCache.add(key, ci);
}
return ci;
}
function getLineColors(feature, colors) {
const ci = getCi(feature);
const totalDistance = feature.distance;
const coordinates = feature.geometry.coordinates;
let distance = 0;
let p = 0;
//第一个顶点
let [r, g, b] = ci.getColor(p);
r /= 255;
g /= 255;
b /= 255;
colors.push(r, g, b);
for (let i = 1, len = coordinates.length; i < len; i++) {
const c1 = coordinates[i - 1],
c2 = coordinates[i];
const d = calDistance(c1, c2);
distance += d;
p = distance / totalDistance;
//中间的顶点需要插入两次,注意Line和LineSegments的区别
let [r, g, b] = ci.getColor(p);
r /= 255;
g /= 255;
b /= 255;
colors.push(r, g, b);
if (i === len - 1) {
continue;
}
colors.push(r, g, b);
}
}
- 线条渲染
这里我们使用的three的fatline扩展, maptalks.three插件里也提供了支持
- 支持worker里构造图形
- 支持合并线条(toFatLines)
从而有更好的性能表现
js
line = threeLayer.toFatLines(
features,
{ altitude: 1, asynchronous: true },
material,
);
关于fatline的底层细节的文档和使用请参阅three里的例子fatline demo,关于更新fatline的颜色,这里我直接给代码了
js
function updateLineColors() {
const lines = line.getOptions().lineStrings;
const colorsArray = [];
for (let i = 0, len = lines.length; i < len; i++) {
getLineColors(lines[i], colorsArray);
}
line.getObject3d().geometry.setColors(colorsArray);
}
模拟线条颜色变化
线条颜色的更新一般不会完全不一样的,一般都是值更新了部分数据的,所以我们只需要更新需要的线条插值的顶点 颜色即可,不要简单的粗暴的去全量更新
js
function mockColorUpdate() {
const lines = line.getOptions().lineStrings;
const linesLen = lines.length;
const updateIds = [];
//模拟每秒有50条数据颜色更新
while (updateIds.length < 50) {
const index = Math.min(
linesLen - 1,
Math.ceil(Math.random() * linesLen),
);
updateIds.push(index);
}
for (let i = 0, len = updateIds.length; i < len; i++) {
const index = updateIds[i];
const line = lines[index];
if (!line) {
continue;
}
line.startColor = randomColor();
line.endColor = randomColor();
}
updateLineColors();
}
WARNING
注意关于colorin的插值实例我们使用LRU缓存的,无需每次都去new,根据线条的颜色值生成key来进行缓存
js
function getCi(feature) {
const { startColor, endColor } = feature;
const key = `${startColor}_${endColor}`;
let ci = ciCache.get(key);
if (!ci) {
ci = new colorin.ColorIn([
[0, startColor],
[1, endColor],
]);
ciCache.add(key, ci);
}
return ci;
}
加点业务点
这个比较简单,maptalks里的VectorLayer,PointLayer等都可以完成这个工作,这里我选了VectorLayer, 因为数据量比较大所以开启了渐进渲染
js
const layer = new maptalks.VectorLayer("layer", {
// collision: true,
progressiveRender: true,
progressiveRenderCount: 1000,
forceRenderOnMoving: true,
forceRenderOnZooming: true,
forceRenderOnRotating: true,
}).addTo(map);
function addPoints(features) {
const points = [];
features.forEach((feature) => {
const coordinates = feature.geometry.coordinates;
const c1 = coordinates[0],
c2 = coordinates[coordinates.length - 1];
const point = new maptalks.Marker(c1, {
symbol: {
markerFile: "./../assets/image/camera.png",
markerWidth: 20,
markerHeight: 20,
},
});
points.push(point);
});
layer.addGeometry(points);
}
监听线条的事件
这个比较简单
js
//features is geojso line array
line = threeLayer.toFatLines(
features,
{ altitude: 1, asynchronous: true },
material,
);
threeLayer.addMesh(line);
line.on("workerload", (e) => {
updateLineColors();
addPoints(features);
setInterval(() => {
mockColorUpdate();
}, 1000);
// debug(features);
});
line.setInfoWindow({
content: "hello world",
title: "message",
animationDuration: 0,
autoOpenOn: false,
});
line.on("click", (e) => {
console.log(e);
if (e.selectMesh) {
const properties = e.selectMesh.data.properties || {};
e.target.getInfoWindow().setContent(JSON.stringify(properties));
e.target.getInfoWindow().show(e.coordinate);
}
});