根据地形更新图形的海拔值
有时业务用到了地形了,但是业务里的数据其没有海拔值,能否做到根据地形自动将图形抬高呢?
要求:
- 根据当前图形处的地形海拔值自动更新对应的图形海拔高度
- 性能和体验要好
WARNING
注意例子里用到了mapbox的地形服务,因为怕token泄漏的导致的不必要的损失,所以在代码里我删了token的值,所以代码是不能直接运行的, 需要你填写自己的token值,当然我也就不提供在线的例子了
现状分析
目前maptalks生态图层里只有VectorTileLayer
适配了地形,图形管理图层(PointLayer,VectorLayer等)并没有 适配地形,那么当我们使用图形管理图层时需要自己的手动的更新图形(Geometry)的海拔
因为地形的加载是异步和只加载当前视野范围内的数据,所以:
我们是无法主动去查询地形的海拔值的,当然有的同学利用地图的事件,定时器,动画函数等去不断轮询这个是不好的, 容易导致性能浪费
因为地形只加载当前视野的瓦片,所有我们是无法主动去更新视野外的数据的, 也不需要去更新,因为这些图形不在视野内
解决方法
地形在加载的过程会抛个tileload
事件,我们可以利用这个事件来被动的更新图形的海拔
- 判断哪些图形落在这个瓦片内
- 这时去查询这些图形的对应的海拔值
- 更新这些图形的海拔值
编码
示例里为了更好的体现的海拔的值,用了threelayer里的bar来表示垂直的线, 并给柱子了个高度,所以图形(Marker)的 海拔高度= 地形的海拔+ 柱子的高度
// the ThreeLayer to draw buildings
var threeLayer = new maptalks.ThreeLayer("t", {
identifyCountOnEvent: 1,
// forceRenderOnMoving: true,
// forceRenderOnRotating: true
});
threeLayer.prepareToDraw = function (gl, scene, camera) {
var light = new THREE.DirectionalLight(0xffffff);
light.position.set(0, -10, 10).normalize();
scene.add(light);
scene.add(new THREE.AmbientLight("#fff", 0.3));
addBars();
threeLayer.inited = true;
};
const barHeight = 1000;
function addBars() {
const material = new THREE.MeshLambertMaterial({ color: "red" });
const bars = data.map((d) => {
const bar = threeLayer.toBar(
d.coordinates,
{ height: barHeight, radius: 100, topColor: "#fff" },
material,
);
bar.setId(d.id);
return bar;
});
threeLayer.addMesh(bars);
}
function findBar(id) {
const bars = threeLayer.getBaseObjects();
for (let i = 0, len = bars.length; i < len; i++) {
const bar = bars[i];
if (bar.getId() === id) {
return bar;
}
}
}
TIP
这一切仅仅是为了一种效果而已,方面测试,业务根据自己的需要是否要携带柱子,如果你不需要柱子, 那么Marker的海拔值就是地形的海拔值
你可以利用垂直的线来代替柱子
准备测试数据
自己随便找了个点测试的,业务里查询自己的接口数据即可
const coordinates = [
[119.26945017383241, 30.376871910976178],
[119.41899273004572, 30.377746042120833],
[119.51608471650138, 30.37726503747328],
[119.46354801648795, 30.31694687527127],
[119.60562937352347, 30.37820924055947],
[119.59045825643739, 30.430436461050107],
[119.584406756833, 30.487931288984754],
[119.51453485707066, 30.49039516064832],
[119.45440953161744, 30.48646614394201],
[119.69247157405653, 30.486815730590678],
[119.80953084691987, 30.472457196921],
[119.89535951684763, 30.47496435491533],
[119.84708685095575, 30.36586547405281],
[119.67054883716764, 30.303609976340255],
[119.65234867735501, 30.363784873871822],
[119.54423459602981, 30.480024325865287],
[119.47395594040563, 30.56338903320065],
[119.39707374359091, 30.53546677150925],
[119.57396685725621, 30.574315977713553],
[119.6401686202828, 30.569305689523247],
];
const data = coordinates.map((c) => {
return {
id: maptalks.Util.GUID(),
coordinates: c,
};
});
监听地形的tileload并查询哪些图形在这个瓦片内
group.getTerrainLayer().on("tileload", (e) => {
const zoom = map.getZoom();
if (zoom < 10) {
return;
}
//计算当前的瓦片内的点,加入到更新队列
const { res, extent2d } = e.tile;
const points = pointLayer.getGeometries();
points.forEach((point) => {
const coordinate = point.getCoordinates();
const glpoint = map.coordinateToPointAtRes(coordinate, res);
if (extent2d.contains(glpoint)) {
const height = group.queryTerrain(coordinate);
if (height && Array.isArray(height)) {
needUpdateGeos.push({
geometry: point,
height: height[0],
});
}
}
});
});
更新这些图形的海拔值
注意图形海拔的更新任务我们放到了一个任务队列里,利用动画函数异步更新,防止tileload触发太频繁导致单位时间内大规模的图形海拔更新, 图形海拔的更新利用削峰填谷的方法
WARNING
注意队列的遍历是从后向前的,因为:
- 同一个图形可能被多次更新,所以我们使用最新的海拔值来更新图形,老的直接舍弃
- 不管队列多长,更新的数据量只和图形的数量有关,因为队列里可能有一个图形的多个任务,而我们只需要处理最新的任务,老的舍弃
- 每次更新完直接清空任务队列,当没有任务时直接闲置,真正的按需消费,不浪费性能资源
const cache = new Map();
function animtion() {
// layer animation support Skipping frames
threeLayer._needsUpdate = !threeLayer._needsUpdate;
if (threeLayer._needsUpdate) {
threeLayer.redraw();
}
if (needUpdateGeos.length && threeLayer.inited) {
for (let len = needUpdateGeos.length, i = len - 1; i >= 0; i--) {
const data = needUpdateGeos[i];
const { geometry, height } = data;
if (cache.has(geometry)) {
continue;
}
const altitude = height;
geometry.setAltitude(altitude + barHeight);
const id = geometry.getId();
const bar = findBar(id);
if (bar) {
bar.setAltitude(altitude);
}
cache.set(geometry, 1);
}
cache.clear();
needUpdateGeos = [];
}
requestAnimationFrame(animtion);
}
animtion();
完整代码
思维拓展
我们也可以利用同样的思想来动态identify vt和3dtile图层的高度值的