Skip to content
目录

根据地形更新图形的海拔值

有时业务用到了地形了,但是业务里的数据其没有海拔值,能否做到根据地形自动将图形抬高呢?

要求:

  • 根据当前图形处的地形海拔值自动更新对应的图形海拔高度
  • 性能和体验要好

WARNING

注意例子里用到了mapbox的地形服务,因为怕token泄漏的导致的不必要的损失,所以在代码里我删了token的值,所以代码是不能直接运行的, 需要你填写自己的token值,当然我也就不提供在线的例子了

现状分析

目前maptalks生态图层里只有VectorTileLayer适配了地形,图形管理图层(PointLayer,VectorLayer等)并没有 适配地形,那么当我们使用图形管理图层时需要自己的手动的更新图形(Geometry)的海拔

因为地形的加载是异步和只加载当前视野范围内的数据,所以:

  • 我们是无法主动去查询地形的海拔值的,当然有的同学利用地图的事件,定时器,动画函数等去不断轮询这个是不好的, 容易导致性能浪费

  • 因为地形只加载当前视野的瓦片,所有我们是无法主动去更新视野外的数据的, 也不需要去更新,因为这些图形不在视野内

解决方法

地形在加载的过程会抛个tileload事件,我们可以利用这个事件来被动的更新图形的海拔

  • 判断哪些图形落在这个瓦片内
  • 这时去查询这些图形的对应的海拔值
  • 更新这些图形的海拔值

编码

示例里为了更好的体现的海拔的值,用了threelayer里的bar来表示垂直的线, 并给柱子了个高度,所以图形(Marker)的 海拔高度= 地形的海拔+ 柱子的高度

js
// 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的海拔值就是地形的海拔值

你可以利用垂直的线来代替柱子

准备测试数据

自己随便找了个点测试的,业务里查询自己的接口数据即可

js
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并查询哪些图形在这个瓦片内

js
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

注意队列的遍历是从后向前的,因为:

  • 同一个图形可能被多次更新,所以我们使用最新的海拔值来更新图形,老的直接舍弃
  • 不管队列多长,更新的数据量只和图形的数量有关,因为队列里可能有一个图形的多个任务,而我们只需要处理最新的任务,老的舍弃
  • 每次更新完直接清空任务队列,当没有任务时直接闲置,真正的按需消费,不浪费性能资源
js
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();

完整代码

updatealtitude-terrain.html

思维拓展

我们也可以利用同样的思想来动态identify vt和3dtile图层的高度值的

This document is generated by mdpress