各位掘友,大家好,我是 安大桃子。在当下数字化地图与地理信息蓬勃发展的浪潮里,WebGIS 技术成为关键力量。而 Vue 和 Cesium 作为 WebGIS 开发中的 “黄金搭档”,一个凭借组件化优势让开发高效有序,一个专注地理空间渲染,呈现逼真地图。接下来,我将结合实际项目案例,为大家深度解析如何巧用 Vue、Cesium 以及相关 WebGIS 技术,攻克复杂地理数据可视化与交互难题,开启 WebGIS 开发 进阶之路。
1. 开篇介绍
在当今的 Web 应用开发领域,数据可视化与 3D 地球展示的需求日益增长。Cesium 作为强大的开源 JavaScript 库,能轻松构建 3D 地球和地图场景;ECharts 则是广受欢迎的数据可视化工具,支持多种图表类型。本文将详细介绍如何把 ECharts 图表嵌入 Cesium 中,实现二者的无缝融合,呈现出令人惊艳的数据可视化效果。
2. 主要功能
2.1 图表嵌入
在 Cesium 构建的 3D 地球场景里,成功嵌入 ECharts 图表,让各类数据图表能直观地展示在地球表面,极大地丰富了数据的呈现形式。
2.2 精准坐标转换
通过特定的坐标系转换机制,把地理坐标精确地转化为屏幕坐标。这一过程确保了 ECharts 图表在 Cesium 场景中的位置精准无误,使图表与 3D 地球场景完美贴合。
2.3 交互协同
针对 Cesium 场景中的平移、缩放等操作,精心设计交互处理逻辑。使得 ECharts 图表能够实时响应这些变化,与 Cesium 场景保持高度同步,为用户带来流畅的交互体验。
2.4 资源管理
提供一套完善的资源管理方案,涵盖初始化、更新和销毁等方法。有效管理和释放资源,保障应用程序在运行过程中的稳定性和性能。
3. 代码实现
3.1 RegisterCoordinateSystem类
RegisterCoordinateSystem类承担着坐标系转换的关键任务,它确保 ECharts 图表能在 Cesium 场景中正确显示。
class RegisterCoordinateSystem {
constructor(glMap) {
this._GLMap = glMap;
this._mapOffset = [0, 0];
this.dimensions = ['lng', 'lat'];
}
setMapOffset(mapOffset) {
this._mapOffset = mapOffset;
}
getMap() {
return this._GLMap;
}
fixLat(lat) {
return lat >= 90 ? 89.99999999999999 : lat <= -90 ? -89.99999999999999 : lat;
}
dataToPoint(coords) {
coords[1] = this.fixLat(coords[1]);
let position = Cesium.Cartesian3.fromDegrees(coords[0], coords[1]);
if (!position) return [];
let coordinates = this._GLMap.cartesianToCanvasCoordinates(position);
if (!coordinates) return [];
if (this._GLMap.mode === Cesium.SceneMode.SCENE3D) {
const pointA = position;
const pointB = this._GLMap.camera.position;
const transform = Cesium.Transforms.eastNorthUpToFixedFrame(pointA);
const positionvector = Cesium.Cartesian3.subtract(pointB, pointA, new Cesium.Cartesian3());
const vector = Cesium.Matrix4.multiplyByPointAsVector(Cesium.Matrix4.inverse(transform, new Cesium.Matrix4()), positionvector, new Cesium.Cartesian3());
const direction = Cesium.Cartesian3.normalize(vector, new Cesium.Cartesian3());
if (direction.z < 0) return [];
}
return [coordinates.x - this._mapOffset[0], coordinates.y - this._mapOffset[1]];
}
pointToData(pixel) {
let mapOffset = this._mapOffset;
// 这里的_bmap.project方法可能未定义,假设在实际代码中有正确定义和实现
let coords = this._bmap.project([pixel[0] + pixel[0], pixel[1] + pixel[1]]);
return [coords.lng, coords.lat];
}
getViewRect() {
let api = this._api;
return new echarts.graphic.BoundingRect(0, 0, api.getWidth(), api.getHeight());
}
getRoamTransform() {
return echarts.matrix.create();
}
create(echartModel, api) {
this._api = api;
let registerCoordinateSystem;
echartModel.eachComponent("GLMap", function (seriesModel) {
let painter = api.getZr().painter;
if (painter) {
try {
let glMap = echarts.glMap;
registerCoordinateSystem = new RegisterCoordinateSystem(glMap, api);
registerCoordinateSystem.setMapOffset(seriesModel.__mapOffset || [0, 0]);
seriesModel.coordinateSystem = registerCoordinateSystem;
} catch (error) {
console.log(error);
}
}
});
echartModel.eachSeries(function (series) {
"GLMap" === series.get("coordinateSystem") && (series.coordinateSystem = registerCoordinateSystem);
});
}
}
3.2 EchartsLayer类
EchartsLayer类负责在 Cesium 场景中创建和管理 ECharts 图表。
export default class EchartsLayer {
constructor(viewer, option) {
this._viewer = viewer;
this._isRegistered = false;
this._chartLayer = this._createLayerContainer();
this.option = option;
this._chartLayer.setOption(option);
this.resizeFuc = null;
this.resize();
}
_createLayerContainer() {
let scene = this._viewer.scene;
let container = document.createElement('div');
container.style.position = 'absolute';
container.style.top = '60px';
container.style.left = '0px';
container.style.right = '0px';
container.style.bottom = '0px';
container.style.width = "100vw";
container.style.height = scene.canvas.height + "px";
container.style.pointerEvents = "none";
this._viewer.container.appendChild(container);
this._echartsContainer = container;
if (!echarts.glMap) {
echarts.glMap = scene;
this._register();
}
return echarts.init(container);
}
_register() {
if (this._isRegistered) return;
echarts.registerCoordinateSystem("GLMap", new RegisterCoordinateSystem(echarts.glMap));
echarts.registerAction({
type: "GLMapRoam",
event: "GLMapRoam",
update: "updateLayout"
// eslint-disable-next-line @typescript-eslint/no-empty-function
}, function (e, t) {});
echarts.extendComponentModel({
type: "GLMap",
getBMap: function () {
return this.__GLMap;
},
defaultOption: {
roam: false
}
});
echarts.extendComponentView({
type: "GLMap",
init: function (echartModel, api) {
this.api = api;
echarts.glMap.postRender.addEventListener(this.moveHandler, this);
},
moveHandler: function (e, t) {
this.api.dispatchAction({
type: "GLMapRoam"
});
},
// eslint-disable-next-line @typescript-eslint/no-empty-function
render: function (e, t, i) {},
dispose: function () {
echarts.glMap.postRender.removeEventListener(this.moveHandler, this);
}
});
this._isRegistered = true;
}
dispose() {
this._echartsContainer && (this._viewer.container.removeChild(this._echartsContainer), this._echartsContainer = null);
this._chartLayer && (this._chartLayer.dispose(), this._chartLayer = null);
this._isRegistered = false;
}
destroy() {
window.removeEventListener('resize', this.resizeFuc);
this.dispose();
}
updateEchartsLayer(option) {
this._chartLayer && this._chartLayer.setOption(option);
}
getMap() {
return this._viewer;
}
getEchartsLayer() {
return this._chartLayer;
}
show() {
this._echartsContainer && (this._echartsContainer.style.visibility = "visible");
}
hide() {
this._echartsContainer && (this._echartsContainer.style.visibility = "hidden");
}
resize() {
const me = this;
window.addEventListener('resize', this.resizeFuc = () => {
const scene = me._viewer.scene;
me._echartsContainer.style.width = scene.canvas.style.width;
me._echartsContainer.style.height = scene.canvas.style.height;
me._chartLayer.resize();
});
}
}
3.3 addEcharts函数
addEcharts函数用于在 Cesium 场景中添加 ECharts 图表。
const addEcharts = (viewer) => {
if (!layer) {
layer = new EchartsLayer(viewer, geoOption);
console.log("addEcharts", layer);
}
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(117.16, 32.71, 10000000.0)
});
}
4. 原理讲解
4.1 坐标系转换
RegisterCoordinateSystem类的dataToPoint和pointToData方法,实现地理坐标与屏幕坐标的相互转换。其中,fixLat方法用于修正纬度值,防止其超出有效范围,确保图表在 3D 场景中的精准定位。
4.2 交互支持
注册自定义的GLMapRoam动作,专门处理地图的平移和缩放事件。当用户在 Cesium 场景中执行平移或缩放操作时,会触发GLMapRoam事件,进而更新 ECharts 图表的位置和大小,保证图表与 Cesium 场景的同步变化。在GLMap组件视图的init方法中,注册postRender事件的处理函数moveHandler。每次 Cesium 场景渲染完成后,moveHandler会被调用,触发GLMapRoam事件,更新 ECharts 图表的布局。
4.3 动态更新
updateEchartsLayer方法允许动态修改 ECharts 图表的配置选项。通过调用setOption方法,可以实时更新图表的数据和样式,及时反映数据的变化情况。
4.4 资源管理
dispose方法负责清理 ECharts 容器和实例,移除相关事件监听器,有效避免内存泄漏。当不再需要 ECharts 图表时,调用该方法释放资源。destroy方法不仅移除窗口大小调整事件监听器,还会调用dispose方法,确保在组件销毁时,所有相关资源都能被彻底清理。
4.5 窗口大小调整
resize方法监听窗口大小变化事件,动态调整 ECharts 容器的尺寸。当窗口大小改变时,该方法会更新容器的宽度和高度,并调用 ECharts 图表的resize方法,使图表始终与 Cesium 场景保持同步显示。
5. 总结
借助EchartsLayer类,成功将 ECharts 图表嵌入 Cesium 场景,实现数据的动态可视化展示。RegisterCoordinateSystem类负责坐标转换和地图偏移处理,保证图表在 Cesium 场景中的精准定位。通过注册自定义动作和扩展 ECharts 组件,实现了地图的平移、缩放等交互操作,使图表与 Cesium 场景协同工作。此外,完善的资源管理和窗口大小调整功能,进一步提升了应用的稳定性和性能。
| 我是 安大桃子,专注前端代码世界的‘筑梦师’。这一趟前端开发的代码之旅暂时告一段落啦,希望文章中的代码思路和技巧能成为你构建前端项目的得力工具。前端技术日新月异,咱们一起在这充满挑战与机遇的领域持续探索,下次代码冒险,咱们不见不散! |
源码仓库:完整源码和使用案例请查看此处,里面还有其他相关案例,如果觉得有用,欢迎给个star!