前言
在地理信息系统(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线效果,这些效果不仅增强了数据的可视化表现力,也提升了用户体验。
-- 欢迎点赞、关注、转发、收藏【我码玄黄】,各大平台同名。