浅析基于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();
            }
        }
    };

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

<think>好的,用户想了解如何用Cesium实现风场效果。首先,我需要回忆Cesium的基本功能和风场可视化的常见方法。Cesium是一个强大的地理可视化库,支持3D地球和2D地图的渲染,通常用于展示地理空间数据。 风场通常用粒子系统或矢量场来表示。粒子系统的话,可能需要用风的方向和速度来驱动粒子的运动。而矢量场可能用箭头或其他符号表示风向和风速,颜色可能用来表示强度。 接下来,我需要考虑Cesium实现这些效果的技术。Cesium的粒子系统(ParticleSystem)可以用来创建动态效果,比如雨、雪,或者这里的风场粒子。另外,使用Custom Shader或者Primitive API可以自定义图形,可能适合绘制箭头等矢量表示。 用户可能需要具体的步骤或代码示例。我应该分两部分介绍:粒子系统和箭头矢量场。对于粒子系统,需要设置发射器、粒子速度、生命周期等参数,可能还需要根据风场数据动态更新粒子位置。对于箭头,可能需要在地图上放置多个实体,每个实体对应一个位置的风向和风速,用箭头指向方向,颜色或长度表示速度。 还需要考虑性能问题,因为风场数据可能很大,粒子数量多会影响性能。可能需要优化粒子数量,或者使用层次细节(LOD)技术。此外,风场数据通常是网格数据,可能需要处理成Cesium可用的格式,比如GeoJSON或自定义的JSON结构。 可能用户对风场数据的来源和处理也有疑问,但问题可能主要集中在可视化部分。不过还是应该简要提及数据格式的处理,比如如何将风速和方向数据传入Cesium。 另外,Cesium的Sandcastle示例可能有相关的demo,比如粒子系统或动态矢量场的例子,可以参考这些示例来构建代码结构。 总结下来,回答的结构应该是先介绍两种主要方法:粒子动画和箭头表示,然后分别给出实现思路和代码片段,最后提到数据格式和性能优化。确保用中文,技术术语正确,代码注释清晰。</think>在 Cesium实现风场可视化,通常通过 **粒子系统** 或 **动态箭头/线符号** 表现风向和风速。以下是两种常见方法的实现思路与示例: --- ### 1. 粒子系统模拟风场 **原理**:用粒子运动轨迹表现风场流动效果,粒子速度和方向由风场数据驱动。 ```javascript // 创建粒子系统 const particleSystem = viewer.scene.primitives.add(new Cesium.ParticleSystem({ // 粒子外观 image: 'path/to/particle.png', // 透明小圆点或风纹贴图 startColor: Cesium.Color.WHITE, endColor: Cesium.Color.TRANSPARENT, startScale: 1.0, endScale: 0.0, // 粒子行为 emissionRate: 100, // 每秒发射粒子数 lifetime: 10, // 粒子存活时间(秒) speed: 5.0, // 基础速度(需结合风场数据动态调整) // 发射器配置(区域风场) emitter: new Cesium.CircleEmitter(100000), // 圆形发射区域半径(米) emitterModelMatrix: computeEmitterMatrix(centerPosition), // 发射区域位置 // 更新函数(关键!动态调整粒子速度) updateCallback: function(p, dt) { const windVector = getWindData(p.position); // 根据位置获取当前风速方向 p.velocity = windVector; // 动态设置粒子速度 } })); ``` --- ### 2. 箭头/线符号表示风场 **原理**:用箭头方向表示风向,长度/颜色表示风速强度。 ```javascript // 生成风场网格点数据(示例) const windData = [ {longitude: 120, latitude: 30, speed: 5, direction: 45}, // 经度、纬度、风速(m/s)、风向(度) // ... 更多数据点 ]; // 遍历数据创建箭头实体 windData.forEach(item => { const position = Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude); const angle = Cesium.Math.toRadians(item.direction); // 风向转弧度 // 创建箭头实体 viewer.entities.add({ position: position, arrow: { length: item.speed * 10000, // 长度映射风速 direction: new Cesium.VelocityOrientationProperty(position, angle), material: Cesium.Color.fromHsl((item.speed / 20), 1, 0.5) // 色相映射风速 } }); }); ``` --- ### 关键技术点 - **数据驱动**:需将风场数据(如JSON、NetCDF)解析为经纬度-风速-方向的结构。 - **动态更新**:若风场随时间变化,需通过`SampledProperty`或回调函数更新粒子/箭头状态。 - **性能优化**: - **粒子系统**:控制粒子数量,使用`particleBillboard`简化渲染。 - **箭头符号**:使用`GroundPrimitive`替代大量Entity,或基于LOD(细节层次)动态加载。 --- ### 扩展工具库 - **CesiumWind**:开源库(如[wind-js](https://github.com/eswind/wind-js))可快速集成风场粒子效果,支持WebGL加速渲染。 若有具体需求(如实时数据源接入),可进一步细化实现步骤!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值