在Cesium中加载OD线

前言

在地理信息系统(GIS)和3D地图应用中,"OD线"(Origin-Destination Line)是一种重要的视觉工具,用于表示两点之间的各种关系,例如航班线路、人口迁徙、交通流量和经济往来。

随着大数据和可视化技术的发展,OD线的应用越来越广泛,对可视化手段也提出了新的要求,包括二维和三维、静态和动态、以及不同数据量的处理。

实现

1. 北京公交动态OD线

为了实现动态OD线的渲染,我们需要自定义MaterialProperty,因为Cesium提供的内置MaterialProperty类型无法满足需求。

以下是北京公交动态OD线的自定义MaterialProperty实现:

javascript 复制代码
// 导入线材质图片
import lineImage from './images/shortFlowColor.png';

/**
 * SpriteLineMaterialProperty构造函数
 * @param {Object} options - 构造对象
 */
export default class SpriteLineMaterialProperty {
    constructor(options) {
        options = Cesium.defaultValue(options, Cesium.defaultValue.EMPTY_OBJECT);

        // 定义变化事件
        this._definitionChanged = new Cesium.Event();
        // 初始化颜色属性
        this._color = undefined;
        // 颜色属性订阅
        this._colorSubscription = undefined;
        // 设置颜色
        this.color = options.color;
        // 设置持续时间,默认1000毫秒
        this.duration = Cesium.defaultValue(options.duration, 1000);
        // 设置当前时间
        this._time = (new Date()).getTime();
    }
}

// 定义属性
Object.defineProperties(SpriteLineMaterialProperty.prototype, {
    // 是否常量
    isConstant: {
        get: function () {
            return false;
        }
    },
    // 定义变化事件
    definitionChanged: {
        get: function () {
            return this._definitionChanged;
        }
    },
    // 颜色属性
    color: Cesium.createPropertyDescriptor('color')
});

/**
 * 获取材质类型
 * @param {Number} time - 时间
 * @returns {String} 材质类型
 */
SpriteLineMaterialProperty.prototype.getType = function (time) {
    return 'SpriteLine';
};

/**
 * 获取材质值
 * @param {Number} time - 时间
 * @param {Object} result - 结果对象
 * @returns {Object} 材质值
 */
SpriteLineMaterialProperty.prototype.getValue = function (time, result) {
    if (!Cesium.defined(result)) {
        result = {};
    }
    // 获取颜色值
    result.color = Cesium.Property.getValueOrClonedDefault(this._color, time, Cesium.Color.WHITE, result.color);
    // 设置线材质图片
    result.image = Cesium.Material.SpriteLineImage;
    // 计算时间比例
    result.time = (((new Date()).getTime() - this._time) % this.duration) / this.duration;
    return result;
};

/**
 * 判断是否相等
 * @param {SpriteLineMaterialProperty} other - 另一个对象
 * @returns {Boolean} 是否相等
 */
SpriteLineMaterialProperty.prototype.equals = function (other) {
    return this === other ||
        (other instanceof SpriteLineMaterialProperty && Cesium.Property.equals(this._color, other._color))
};

// 注册材质属性和材质类型
Cesium.SpriteLineMaterialProperty = SpriteLineMaterialProperty;
Cesium.Material.SpriteLineType = 'SpriteLine';
Cesium.Material.SpriteLineImage = lineImage;
Cesium.Material.SpriteLineSource = `czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_material material = czm_getDefaultMaterial(materialInput);
    vec2 st = materialInput.st;
    vec4 colorImage = texture(image, vec2(fract(st.s - time), st.t));
    material.alpha = colorImage.a * color.a;
    material.diffuse = color.rgb * 1.5;
    return material;
}
`;
Cesium.Material._materialCache.addMaterial(Cesium.Material.SpriteLineType, {
    fabric: {
        type: Cesium.Material.SpriteLineType,
        uniforms: {
            color: new Cesium.Color(1.0, 0.0, 0.0, 0.5),
            image: Cesium.Material.SpriteLineImage,
            time: 0,
        },
        source: Cesium.Material.SpriteLineSource
    },
    translucent: function (material) {
        return true;
    }
});

2. 三维OD线

三维OD线用于在三维场景下展示OD数据,例如网络攻击、全球资本流动和全球航班轨迹等。以下是实现三维OD线的步骤:

根据起终点获取弧线

javascript 复制代码
/**
 * 根据起终点获取弧线
 * @param {Object} startPoint - 起点坐标
 * @param {Object} endPoint - 终点坐标
 * @param {Number} ratio - 弧度比率,默认0.5
 * @returns {Array} 弧线坐标数组
 */
function getParabolaLine(startPoint, endPoint, ratio = 0.5) {
    // 方程 y=-(4h/L^2)*x^2+h h:顶点高度 L:横纵间距较大者
    const h = Cesium.Cartesian3.distance(
        Cesium.Cartesian3.fromDegrees(startPoint.lng, startPoint.lat),
        Cesium.Cartesian3.fromDegrees(endPoint.lng, endPoint.lat),
    ) * ratio;
    const L = Math.abs(startPoint.lng - endPoint.lng) > Math.abs(startPoint.lat - endPoint.lat)
        ? Math.abs(startPoint.lng - endPoint.lng)
        : Math.abs(startPoint.lat - endPoint.lat);

    const num = 99;
    const result = [];
    let dlt = L / num;
    if (Math.abs(startPoint.lng - endPoint.lng) > Math.abs(startPoint.lat - endPoint.lat)) {
        //以 lng 为基准
        const delLat = (endPoint.lat - startPoint.lat) / num;
        if (startPoint.lng - endPoint.lng > 0) dlt = -dlt;
        for (let i = 0; i < num; i++) {
            const tempH = h - (Math.pow(-0.5 * L + Math.abs(dlt) * i, 2) * 4 * h) / Math.pow(L, 2);
            const lng = startPoint.lng + dlt * i;
            const lat = startPoint.lat + delLat * i;
            result.push({ lng, lat, height: tempH });
        }
    } else {
        //以 lat 为基准
        let delLng = (endPoint.lng - startPoint.lng) / num;
        if (startPoint.lat - endPoint.lat > 0) dlt = -dlt;
        for (let i = 0; i < num; i++) {
            const tempH = h - (Math.pow(-0.5 * L + Math.abs(dlt) * i, 2) * 4 * h) / Math.pow(L, 2);
            const lng = startPoint.lng + delLng * i;
            const lat = startPoint.lat + dlt * i;
            result.push({ lng, lat, height: tempH });
        }
    }
    // 落地
    result.push({lng: endPoint.lng, lat: endPoint.lat, height: endPoint.height || 0});

    return result;
}

自定义三维OD线材质

javascript 复制代码
/**
 * ODLineMaterialProperty构造函数
 * @param {Object} options - 构造对象
 */
export default class ODLineMaterialProperty {
    constructor(options) {
        options = Cesium.defaultValue(options, Cesium.defaultValue.EMPTY_OBJECT);

        this._definitionChanged = new Cesium.Event();
        this._color = undefined;
        this._speed = undefined;
        this._percent = undefined;
        this._gradient = undefined;
        this._gradientColor = undefined;
        this.color = options.color;
        this.speed = options.speed;
        this.percent = options.percent;
        this.gradient = options.gradient;
        this.gradientColor = options.gradientColor;
    }
}
Object.defineProperties(ODLineMaterialProperty.prototype, {
    // 是否常量
    isConstant: {
        get: function () {
            return false;
        }
    },
    // 定义变化事件
    definitionChanged: {
        get: function () {
            return this._definitionChanged;
        }
    },
    // 颜色属性
    color: Cesium.createPropertyDescriptor('color'),
    speed: Cesium.createPropertyDescriptor('speed'),
    percent: Cesium.createPropertyDescriptor('percent'),
    gradient: Cesium.createPropertyDescriptor('gradient'),
    gradientColor: Cesium.createPropertyDescriptor('gradientColor')
});

/**
 * 获取材质类型
 * @param {Number} time - 时间
 * @returns {String} 材质类型
 */
ODLineMaterialProperty.prototype.getType = function (time) {
    return 'ODLine';
};

/**
 * 获取材质值
 * @param {Number} time - 时间
 * @param {Object} result - 结果对象
 * @returns {Object} 材质值
 */
ODLineMaterialProperty.prototype.getValue = function (time, result) {
    if (!Cesium.defined(result)) {
        result = {};
    }
    result.color = Cesium.Property.getValueOrDefault(this._color, time, Cesium.Color.RED, result.color);
    result.speed = Cesium.Property.getValueOrDefault(this._speed, time, 5.0, result.speed);

总结

本文介绍了在 Cesium 中实现 OD 线(Origin-Destination Line)的两种方法:动态OD线和三维OD线。

通过自定义 MaterialProperty,我们能够创建出满足特定需求的OD线效果,这些效果不仅增强了数据的可视化表现力,也提升了用户体验。

-- 欢迎点赞、关注、转发、收藏【我码玄黄】,各大平台同名。

相关推荐
百万蹄蹄向前冲1 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5812 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路2 小时前
GeoTools 读取影像元数据
前端
ssshooter2 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友2 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry3 小时前
Jetpack Compose 中的状态
前端
dae bal4 小时前
关于RSA和AES加密
前端·vue.js
柳杉4 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog4 小时前
低端设备加载webp ANR
前端·算法
LKAI.5 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi