浅析基于Cesium实现的风场开源库cesium-wind

cesium-wind开源库 github地址:https://github.com/QJvic/cesium-wind

大家可以去 官网上去看此开源库的介绍,以及用法。此文主要分析一下核心实现步骤。

1. 构建网格

    Field.prototype.buildGrid = function () {
        var grid = [];
        var p = 0;
        var _a = this, rows = _a.rows, cols = _a.cols, us = _a.us, vs = _a.vs;
        for (var j = 0; j < rows; j++) {
            var row = [];
            for (var i = 0; i < cols; i++, p++) {
                var u = us[p];
                var v = vs[p];
                var valid = this.isValid(u) && this.isValid(v);
                row[i] = valid ? new Vector(u, v) : null;
            }
            if (this.isContinuous) {
                row.push(row[0]);
            }
            grid[j] = row;
        }
        return grid;
    };

代码如上,根据用户传入的风场数据,获取横向和纵向数据个数rows(纬度),cols(经度)。us, vs分别是风场数据u 方向和 v方向数据,判断数据是否合法(null, undefined)如不合法则把此经纬度坐标点数据设置为null,否则设置成由uv 组成的数据

2. 生成粒子位置

   

    Field.prototype.randomize = function (o, width, height, unproject) {
        if (o === void 0) { o = {}; }
        var i = (Math.random() * (width || this.cols)) | 0;
        var j = (Math.random() * (height || this.rows)) | 0;
        var coords = unproject([i, j]);
        if (coords !== null) {
            o.x = coords[0];
            o.y = coords[1];
        }
        else {
            o.x = this.longitudeAtX(i);
            o.y = this.latitudeAtY(j);
        }
        return o;
    };


   unproject(pixel) {
    const viewer = this.viewer;
    const pick = new Cartesian2(pixel[0], pixel[1]);
    const cartesian = viewer.scene.globe.pick(
      viewer.camera.getPickRay(pick),
      viewer.scene,
    );

    if (!cartesian) {
      return null;
    }

    const ellipsoid = viewer.scene.globe.ellipsoid;
    const cartographic = ellipsoid.cartesianToCartographic(cartesian);
    const lat = Math$1.toDegrees(cartographic.latitude);
    const lng = Math$1.toDegrees(cartographic.longitude);
    return [lng, lat];
  }

粒子位置是随机的,在canvas画布上随机取屏幕坐标点,然后利用Cesium接口转换为经纬度坐标点,具体转换过程在unproject函数中。相当于用鼠标在屏幕上点击,然后获取经纬度坐标点一样。

3. 移动粒子

BaseLayer.prototype.moveParticles = function () {
        var _a = this.ctx.canvas, width = _a.width, height = _a.height;
        var particles = this.particles;
        // 清空组
        var maxAge = this.options.maxAge;
        var optVelocityScale = isFunction(this.options.velocityScale)
            // @ts-ignore
            ? this.options.velocityScale()
            : this.options.velocityScale;
        var velocityScale = optVelocityScale;
        var i = 0;
        var len = particles.length;
        for (; i < len; i++) {
            var particle = particles[i];
            if (particle.age > maxAge) {
                particle.age = 0;
                // restart, on a random x,y
                this.field.randomize(particle, width, height, this.unproject);
            }
            var x = particle.x;
            var y = particle.y;
            var vector = this.field.interpolatedValueAt(x, y);
            if (vector === null) {
                particle.age = maxAge;
            }
            else {
                var xt = x + vector.u * velocityScale;
                var yt = y + vector.v * velocityScale;
                if (this.field.hasValueAt(xt, yt)) {
                    // Path from (x,y) to (xt,yt) is visible, so add this particle to the appropriate draw bucket.
                    particle.xt = xt;
                    particle.yt = yt;
                    particle.m = vector.m;
                }
                else {
                    // Particle isn't visible, but it still moves through the field.
                    particle.x = xt;
                    particle.y = yt;
                    particle.age = maxAge;
                }
            }
            particle.age++;
        }
    };

var vector = this.field.interpolatedValueAt(x, y); 代码中这一行通过随机生成的经纬度根据双线性插值获取到离这个经纬度坐标点最近的网格点数据,如果找到则把这个粒子移动到下一个经纬度坐标点(经纬度坐标点+uv方向),如果没有找到,说明此粒子经纬度坐标点落在了无效数据的网格点中,此时则把此粒子的生命周期设置为最大,也就是让这个粒子消失再重新生成。

这里用到了双线性插值,其实用最近距离法也可以,看不出差别。修改代码如下所示:

    Field.prototype.interpolatedValueAt = function (lon, lat) {
        if (!this.contains(lon, lat))
            { return null; }
        var _a = this.getDecimalIndexes(lon, lat), i = _a[0], j = _a[1];
        //return this.interpolatePoint(i, j); //双线性插值
        return this.grid[Math.floor(j)][Math.floor(i)]  //最近距离插值
    };

4. 绘制粒子

BaseLayer.prototype.drawCoordsParticle = function (particle, min, max) {
        // TODO 需要判断粒子是否超出视野
        // this.ctx.strokeStyle = color;
        var source = [particle.x, particle.y];
        // when xt isn't exit
        var target = [particle.xt, particle.yt];
        if (target && source && isValide(target[0]) &&
            isValide(target[1]) && isValide(source[0]) &&
            isValide(source[1]) &&
            this.intersectsCoordinate(target)
            && particle.age <= this.options.maxAge) {
            var pointPrev = this.project(source);
            var pointNext = this.project(target);
            if (pointPrev && pointNext) {
                this.ctx.beginPath();
                this.ctx.moveTo(pointPrev[0], pointPrev[1]);
                this.ctx.lineTo(pointNext[0], pointNext[1]);
                particle.x = particle.xt;
                particle.y = particle.yt;
                if (isFunction(this.options.colorScale)) {
                    // @ts-ignore
                    this.ctx.strokeStyle = this.options.colorScale(particle.m);
                }
                else if (Array.isArray(this.options.colorScale)) {
                    var colorIdx = indexFor(particle.m, min, max, this.options.colorScale);
                    this.ctx.strokeStyle = this.options.colorScale[colorIdx];
                }
                if (isFunction(this.options.lineWidth)) {
                    // @ts-ignore
                    this.ctx.lineWidth = this.options.lineWidth(particle.m);
                }
                this.ctx.stroke();
            }
        }
    };

根据此粒子是否在生命周期之内,以及是否可以移动到下个坐标点,如果满足条件则绘制粒子轨迹

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值