在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线效果,这些效果不仅增强了数据的可视化表现力,也提升了用户体验。

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

相关推荐
weixin_382395236 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__6 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.6 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~7 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华8 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子10 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅11 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni11 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学12 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端