ECharts 3D地球(铁路线、飞线、标点、图标、文字标注等)
旋转3D地球效果如下:
整体思路:
- 加载世界地图坐标信息json数据
- 准备生成散点、铁路线、箭头标注、文字标注等一些echarts需要的配置对象
- 使用加载的json数据生成地图基础纹理,基本纹理中series包含第二步骤生成的那些东西
- 使用globe配置对象绘制3d地球,在globe里面配置生成的纹理
- 可开启一个定时器,让地球旋转起来
1.加载世界地图坐标信息json数据
// request.js//没有基地址 访问根目录下文件export const GETNOBASE = async (url, params) => { try { const data = await axios.get(url, { params: params, }); return data; } catch (error) { return error; }}// earth3d.vue// 加载地理信息async loadGeoJson() { const res = await GETNOBASE(\"./map-geojson/worldZh.json\"); return res.data;} // worldZh.json这个文件自己去这个网站下 [数据可视化平台](https://datav.aliyun.com/portal/school/atlas/area_selector)// worldZh.json这个文件我放在public/map-geojson文件下的
- 准备生成散点、铁路线、箭头标注、文字标注等一些echarts需要的配置对象
// 准备绘制数据// earth3d.vue prepareData() { this.prepareEffectScatterData(); this.prepareTrainLines(); this.prepareArrows(); this.prepareTrainNames();} // 生成散点数据 prepareEffectScatterData() { cityData.forEach((point) => { this.effectScatter.push({ name: point.name, value: point.value, symbol: point.hasIcon ? `image://${point.icon}` : \"circle\", // 使用自定义图片作为symbol symbolSize: point.name === \"重庆\" ? 12 : 8, z: point.name === \"重庆\" ? 5 : 2, label: { position: point.position, fontSize: point.name === \"重庆\" ? 16 : 14, // fontWeight: \'bold\', color: point.name === \"重庆\" ? \"#e51c1c\" : \"#fff\", }, }); }); }// 生成铁路线数据 prepareTrainLines() { this.trainLines = [ drawTrainLine(hasTielu, 4, \"solid\", \"rgba(255, 255, 255, 1)\", 3), drawTrainLine(hasTielu, 4, [10, 10], \"#000\", 2), ]; } // 生成箭头标注 prepareArrows() { this.arrows = [ createArrow({ name: \"向上的箭头\", value: [102.3043, 40.2248], x: 200, y: 250, type: \"up\", }), createArrow({ name: \"向左的箭头\", value: [71.511721, 42.302711], x: 300, y: 150, type: \"left\", }), createArrow({ name: \"向下的箭头\", value: [99.36, 17.58], x: 150, y: 300, type: \"down\", }), ]; } // 生成文字标注 prepareTrainNames() { this.trainNames = [ ...trainName.map((item) => createNamePoint(item)), // 印在地球上不带框框的文字 createNamePointImg({ name: \"勒是铁路线\", value: [102.11293, 42.14693] }), // 带框框的提示文字 ]; } // map.js// 散点数据export const cityData = [ { name: \'科伦坡\', value: [79.52, 6.55], position: \'left\', // 这个是在点的四周位置,top,right,left,bottom,根据实际情况来定 hasIcon: false, }, { name: \"钦州\", value: [107.27, 21.25], // [107.27, 21.35] position: \'bottom\', hasIcon: false, }, { name: \"重庆\", value: [106.55116, 29.61203], position: \'bottom\', hasIcon: true, icon: cqIcon, // 这个是引入的图片资源 }, { name: \"武汉\", value: [113.41, 29.58], position: \'top\', hasIcon: false, }, { name: \"上海\", value: [121.451830, 31.175201], position: \'top\', hasIcon: false, }, { name: \"成都\", value: [104.083736, 30.65318], position: \'top\', hasIcon: false, }, { name: \"二连浩特\", value: [111.982513, 43.655688], position: \'right\', hasIcon: false, }, { name: \"马拉\", value: [19.40624, 52.12210], position: \'bottom\', hasIcon: false, }, { name: \"明斯克\", value: [27.30, 53.51], position: \'bottom\', hasIcon: false, }, { name: \"吉大港\", value: [91.48, 22.18], position: \'top\', hasIcon: false, }, { name: \"台湾\", value: [120.1, 21.45], position: \'right\', hasIcon: false, }, { name: \"满洲里\", value: [117.3837, 49.6035], position: \'right\', hasIcon: false, }, { name: \"霍尔果斯\", value: [80.4852, 44.163962], position: \'top\', hasIcon: false, }, { name: \"莫克兰\", value: [62.280, 25.2200], position: \'right\', hasIcon: false, }, { name: \"塔什干\", value: [69.2444, 41.3396], position: \'bottom\', hasIcon: false, }, { name: \"吉洪诺沃\", value: [40.2936, 56.3469], position: \'top\', hasIcon: false, },]// 铁路线export const hasTielu = [ { name: \"重庆-成都\", coords: [ [106.55116, 29.61203], [104.083736, 30.65318], ], lineStyle: { curveness: -0.1 }, }, { name: \"成都-太原\", coords: [ [104.083736, 30.65318], [112.510097, 37.936464], ], lineStyle: { curveness: -0.1 }, }, { name: \"太原-二连浩特\", coords: [ [112.510097, 37.936464], [111.982513, 43.655688], ], lineStyle: { curveness: -0.1 }, }, { name: \"二连浩特-a\", coords: [ [111.982513, 43.655688], [102.3043, 51.2248], ], lineStyle: { curveness: -0.2 }, }, { name: \"a-新西伯利亚州\", coords: [ [102.3043, 51.2248], [79.733157, 55.566475], ], lineStyle: { curveness: -0.1 }, }, { name: \"新西伯利亚州-吉洪诺沃\", coords: [ [79.733157, 55.566475], [40.2936, 56.3469], ], lineStyle: { curveness: -0.1 }, }, { name: \"吉洪诺沃-明斯克\", coords: [ [40.2936, 56.3469], [27.30, 53.51], ], lineStyle: { curveness: 0 }, }, { name: \"重庆-荆州\", coords: [ [106.55116, 29.61203], [111.15, 29.26], ], lineStyle: { curveness: -0.1 }, }, { name: \"荆州-武汉\", coords: [ [111.15, 29.26], [113.41, 29.58], ], lineStyle: { curveness: 0 }, }, { name: \"武汉-上海\", coords: [ [113.41, 29.58], [121.451830, 31.175201], ], lineStyle: { curveness: 0.1 }, }, { name: \"成都-b\", coords: [ [104.083736, 30.65318], [95.51293, 37.44693], ], lineStyle: { curveness: -0.1 }, }, { name: \"b-霍尔果斯\", coords: [ [95.51293, 37.44693], [80.4852, 44.163962], ], lineStyle: { curveness: -0.1 }, }, { name: \"霍尔果斯-塔什干\", coords: [ [80.4852, 44.163962], [69.2444, 41.3396], ], lineStyle: { curveness: 0.1 }, }, { name: \"塔什干-阿克套\", coords: [ [69.2444, 41.3396], [51.1644, 43.6595], ], lineStyle: { curveness: 0.1 }, }, { name: \"明斯克-马拉\", coords: [ [27.30, 53.51], [19.40624, 52.12210], ], lineStyle: { curveness: 0 }, },]// 地球上无框文字export const trainName = [ { name: \'白色无框文字\', value: [55.1644, 44.6595], position: \'top\', color: \'#fff\', rotate: 8, }, { name: \'深蓝无框文字\', value: [90.774369, 50.14839], position: \'top\', color: \'#214D97\', rotate: -10, }, { name: \'深蓝无框文字\', value: [71.5598, 29.228952], position: \'top\', color: \'#214D97\', rotate: 50, }, { name: \'深蓝无框文字\', value: [81.538926, 18.785731], position: \'top\', color: \'#214D97\', rotate: 45, },]// 飞线--线段export const airLine = [ { value: (Math.random() * 3000).toFixed(2), coords: [ [106.55116, 29.61203], // 重庆 [91.48, 22.18], // 吉大港 ] }, { value: (Math.random() * 3000).toFixed(2), coords: [ [91.48, 22.18], // 吉大港 [79.52, 6.55], // 科伦坡 ] },]// mapFun.js// 画铁路线export const drawTrainLine = (arr, zIndex, lineType, lineColor, lineW) => { return { name: \"铁路线\", type: \"lines\", coordinateSystem: \"geo\", zlevel: zIndex, effect: { show: true, period: 6, // 动画周期 trailLength: 0, color: \"#7FFBFD\", // 轨迹颜色 symbol: `image://${trainIcon}`, // 使用自定义图片作为symbol symbolSize: 10, }, lineStyle: { normal: { type: lineType, // solid,dotted,dashed color: lineColor || \"#fff\", width: lineW || 3, opacity: 1, curveness: 0.3, }, }, data: arr.map((line) => ({ coords: line.coords, // 起点和终点 lineStyle: { normal: { curveness: line.lineStyle.curveness }, }, })), } } // 渲染大箭头标注const imgMap = { // 全是自定义图片 up: arrow1, left: arrow2, down: arrow3,}export const createArrow = (obj) => { return { name: obj.name, type: \'scatter\', coordinateSystem: \'geo\', symbol: `image://${imgMap[obj.type]}`, // 使用自定义图片作为symbol symbolSize: [obj.x, obj.y], // 设置图标大小 label: { show: false, position: \'inside\', // 文字显示在图标内部 formatter: \'{b}\', // 显示标注点的名字 color: \'#fff\', // 文字颜色 }, data: [ { name: obj.name, // 图标上显示的文字 value: obj.value, // 标注的地理坐标(经纬度) }, ], zlevel: 2, }}// 文字标注名称,利用散点图画出来export const createNamePoint = (point) => { return { name: point.name, type: \'scatter\', coordinateSystem: \"geo\", data: [{ name: point.name, value: point.value }], symbol: \"circle\", symbolSize: 0, // 不显示实际的点,只显示文字 zlevel: 5, label: { show: true, formatter: \'{b}\', // {b} 会显示数据名称 position: point.position, fontSize: 10, // getFontSize() fontWeight: \'bold\', color: point.color || \"#fff\", rotate: point.rotate, } }}// 带框的文字标注点,有自定义的图片export const createNamePointImg = (obj) => { return { name: obj.name, type: \'scatter\', coordinateSystem: \'geo\', symbol: `image://${imageURL}`, // 使用自定义图片作为symbol symbolSize: [120, 50], // 设置图标大小 label: { show: true, position: \'inside\', // 文字显示在图标内部 formatter: \'{b}\', // 显示标注点的名字 color: \'#fff\', // 文字颜色 fontSize: 10, // 文字大小 offset: [5, -10], }, data: [ { name: obj.name, // 图标上显示的文字 value: obj.value, // 标注的地理坐标(经纬度) }, ], zlevel: 7, }}
3.使用加载的json数据生成地图基础纹理,基本纹理中series包含第二步骤生成的那些东西
// earth3d.vue// 生成基础纹理generateBaseTexture(geoJson) {// 注册geo数据 echarts.registerMap(this.code, geoJson); // 创建画布 生成纹理 const canvas = document.createElement(\"canvas\"); this.baseTexture = echarts.init(canvas, null, { width: 1920, height: 1080 }); this.baseTexture.setOption({ backgroundColor: \"rgba(26, 40, 71, 0.85)\", //相当于海洋颜色 geo: { type: \"map\", map: this.code, left: 0, top: 0, right: 0, bottom: 0, boundingCoords: [ [-180, 90], [180, -90], ], roam: false, selectedMode: \"single\", select: { itemStyle: { areaColor: \"#3ADAF4\", }, label: { show: true, color: \"#000\", fontSize: 10, }, }, emphasis: { disabled: true, itemStyle: { areaColor: \"#3ADAF4\", }, label: { show: true, color: \"#000\", fontSize: 10, }, }, itemStyle: { areaColor: \"#2C89F5\", // #1EA3C8 rgba(44, 153, 245, 1) borderColor: \"#314E85\", }, }, series: [ // 散点 { type: \"effectScatter\", coordinateSystem: \"geo\", zlevel: 6, rippleEffect: { number: 0, brushType: \"stroke\" }, label: { show: true, formatter: \"{b}\", distance: 5 }, itemStyle: { normal: { color: \"#fdf80c\", borderColor: \"#fff\" } }, data: this.effectScatter, }, // 飞线 { type: \"lines\", zlevel: 3, effect: { show: true, period: 4, trailLength: 0, symbol: \"arrow\", symbolSize: 12, }, lineStyle: { normal: { color: \"#fdf80c\", width: 2, type: \"solid\", opacity: 1, curveness: -0.1, }, }, data: airLine, }, ...this.trainLines, ...this.trainNames, ...this.arrows, ], }); // 监听鼠标悬浮和点击事件 this.baseTexture.on(\"click\", (params) => { if (params.name === \"重庆\") { // 点击了重庆,要干点什么 } });}
4.使用globe配置对象绘制3d地球,在globe里面配置生成的纹理
// earth3d.vue // 绘制地球 drawEarth() { const option = { globe: { baseTexture: this.baseTexture, // 基础纹理 // globeRadius: getEarthRadius(), shading: \"color\", // color lambert // \'lambert\' 通过经典的 lambert 着色表现光照带来的明暗 light: { ambient: { intensity: 0.9 }, main: { alpha: -45, beta: 45, intensity: 0.6 }, // 主光源 }, atmosphere: { show: true, color: \"rgba(33, 97, 179, 0.6)\", glowPower: 4, }, viewControl: { projection: \'perspective\', alpha: this.currentAlpha, beta: this.currentBeta, autoRotateSpeed: 0.6, autoRotate: true, // 开启自动旋转 true autoRotateAfterStill: 5, //鼠标停止操纵后,恢复自转时间 // distance: 200, //默认视角距离主体距离 distance: this.currentDistance, // 使用记录的距离 minDistance: 40, // 最小视角距离 maxDistance: 400, // 最大视角距离 damping: 0, //鼠标旋转或缩放操作时的迟滞因子 rotateSensitivity: 0.8, //旋转操作的灵敏度 zoomSensitivity: 8, //缩放操作的灵敏度 maxBeta: this.maxBeta, }, layers: [ { show: true, type: \"blend\", blendTo: \"emission\", texture: this.createLatitudeLongitudeGrid(), // distance: getEarthRadius() + 5, // 网格稍微在地球表面上方 distance: this.currentDistance + 5, }, ], top: \"6%\", }, series: [], }; this.myChart.clear(); this.myChart.setOption(option); }// 创建经纬网格 createLatitudeLongitudeGrid() { const canvas = document.createElement(\"canvas\"); const ctx = canvas.getContext(\"2d\"); const size = 1024; canvas.width = size; canvas.height = size; ctx.strokeStyle = \"#114A72\"; ctx.lineWidth = 1; ctx.globalAlpha = 0.8; // 绘制经线 for (let i = 0; i <= size; i += size / 18) { ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, size); ctx.stroke(); } // 绘制纬线 for (let j = 0; j <= size; j += size / 9) { ctx.beginPath(); ctx.moveTo(0, j); ctx.lineTo(size, j); ctx.stroke(); } console.log(canvas); return canvas; }
5.可开启一个定时器,让地球旋转起来
setTimer() { this.rotateTimer = setInterval(() => { this.initBeta++; if (this.initBeta > this.maxBeta) { clearInterval(this.rotateTimer); this.initBeta = 180; this.currentBeta = this.initBeta this.drawEarth(); this.setTimer(); } }, 1000); }
完整的代码:
// earth3d.vue<template> <div class=\"earth-3d\" ref=\"earth3dRef\" id=\"earth3dRef\"></div></template><script>import * as echarts from \"echarts\";import \"echarts-gl\"; // 引入 ECharts 3D 的模块 必须引入 ECharts GL 才能使用 WebGL 功能import { GETNOBASE } from \"api/request\";import { airLine, cityData, hasTielu, trainName } from \"./js/map\";import { drawTrainLine, createNamePoint, createNamePointImg, createArrow,} from \"./js/echartsFun\";export default { data() { return { myChart: null, baseTexture: null, code: \"world\", effectScatter: [], trainLines: [], // 铁路线集合 arrows: [], // 箭头集合 trainNames: [], // 文字标注集合 maxBeta: 220, // 设置旋转的最大角度 rotateTimer: null, initBeta: 180, currentDistance: 230, // 添加当前视角距离属性 currentAlpha: 30, // 添加当前视角角度属性 currentBeta: 180, // 添加当前视角角度属性 }; }, mounted() { if (!this.webglSupport()) { this.message(\"您的浏览器不支持 WebGL,请切换或升级浏览器\"); return; } this.$nextTick(() => { this.myChart = echarts.init(this.$refs.earth3dRef); this.initializeMap(); }); }, beforeDestroy() { if (this.myChart) { this.myChart.dispose(); } if (this.rotateTimer) { clearInterval(this.rotateTimer); this.initBeta = 180; } }, methods: { // 这段代码会在组件挂载时检查用户的浏览器是否支持 WebGL。如果不支持,会给出提示信息,而不会继续执行图表初始化 webglSupport() { try { const canvas = document.createElement(\"canvas\"); return !!( window.WebGLRenderingContext && (canvas.getContext(\"webgl\") || canvas.getContext(\"experimental-webgl\")) ); } catch (e) { return false; } }, // 初始化地图 initializeMap() { this.loadGeoJson().then((geoJson) => { this.prepareData(); this.generateBaseTexture(geoJson); this.drawEarth(); this.setTimer(); // 获取地图容器元素 const mapContainer = this.$refs.earth3dRef; // 添加滚轮事件监听 mapContainer.addEventListener(\'wheel\', () => { // 获取当前视图的配置 const viewControl = this.myChart.getOption().globe[0].viewControl; this.currentDistance = viewControl.distance; }); }); }, setTimer() { this.rotateTimer = setInterval(() => { this.initBeta++; if (this.initBeta > this.maxBeta) { clearInterval(this.rotateTimer); this.initBeta = 180; this.currentBeta = this.initBeta this.drawEarth(); this.setTimer(); } }, 1000); }, // 加载地理信息 async loadGeoJson() { const res = await GETNOBASE(\"./map-geojson/worldZh.json\"); return res.data; }, // 准备绘制数据 prepareData() { this.prepareEffectScatterData(); this.prepareTrainLines(); this.prepareArrows(); this.prepareTrainNames(); }, // 生成散点数据 prepareEffectScatterData() { cityData.forEach((point) => { this.effectScatter.push({ name: point.name, value: point.value, symbol: point.hasIcon ? `image://${point.icon}` : \"circle\", // 使用自定义图片作为symbol symbolSize: point.name === \"重庆\" ? 12 : 8, z: point.name === \"重庆\" ? 5 : 2, label: { position: point.position, fontSize: point.name === \"重庆\" ? 16 : 14, // fontWeight: \'bold\', color: point.name === \"重庆\" ? \"#e51c1c\" : \"#fff\", }, }); }); }, // 生成铁路线数据 prepareTrainLines() { this.trainLines = [ drawTrainLine(hasTielu, 4, \"solid\", \"rgba(255, 255, 255, 1)\", 3), drawTrainLine(hasTielu, 4, [10, 10], \"#000\", 2), ]; }, // 生成箭头标注 prepareArrows() { this.arrows = [ createArrow({ name: \"向上的箭头\", value: [102.3043, 40.2248], x: 20, y: 25, type: \"up\", }), createArrow({ name: \"向左的箭头\", value: [71.511721, 42.302711], x: 30, y: 15, type: \"left\", }), createArrow({ name: \"向下的箭头\", value: [99.36, 17.58], x: 15, y: 30, type: \"down\", }), ]; }, // 生成文字标注 prepareTrainNames() { this.trainNames = [ ...trainName.map((item) => createNamePoint(item)), // 印在地球上不带框框的文字 createNamePointImg({ name: \"勒是铁路线\", value: [102.11293, 42.14693] }), // 带框框的提示文字 ]; }, // 生成基础纹理 generateBaseTexture(geoJson) { // 注册geo数据 echarts.registerMap(this.code, geoJson); // 创建画布 生成纹理 const canvas = document.createElement(\"canvas\"); this.baseTexture = echarts.init(canvas, null, { width: 1920, height: 1080 }); this.baseTexture.setOption({ backgroundColor: \"rgba(26, 40, 71, 0.85)\", //相当于海洋颜色 geo: { type: \"map\", map: this.code, left: 0, top: 0, right: 0, bottom: 0, boundingCoords: [ [-180, 90], [180, -90], ], roam: false, selectedMode: \"single\", select: { itemStyle: { areaColor: \"#3ADAF4\", }, label: { show: true, color: \"#000\", fontSize: 10, }, }, emphasis: { disabled: true, itemStyle: { areaColor: \"#3ADAF4\", }, label: { show: true, color: \"#000\", fontSize: 10, }, }, itemStyle: { areaColor: \"#2C89F5\", // #1EA3C8 rgba(44, 153, 245, 1) borderColor: \"#314E85\", }, }, series: [ // 散点 { type: \"effectScatter\", coordinateSystem: \"geo\", zlevel: 6, rippleEffect: { number: 0, brushType: \"stroke\" }, label: { show: true, formatter: \"{b}\", distance: 5 }, itemStyle: { normal: { color: \"#fdf80c\", borderColor: \"#fff\" } }, data: this.effectScatter, }, // 飞线 { type: \"lines\", zlevel: 3, effect: { show: true, period: 4, trailLength: 0, symbol: \"arrow\", symbolSize: 12, }, lineStyle: { normal: { color: \"#fdf80c\", width: 2, type: \"solid\", opacity: 1, curveness: -0.1, }, }, data: airLine, }, ...this.trainLines, ...this.trainNames, ...this.arrows, ], }); // 监听鼠标悬浮和点击事件 this.baseTexture.on(\"click\", (params) => { if (params.name === \"重庆\") { // 点击了重庆,要干点什么 } }); }, // 绘制地球 drawEarth() { const option = { globe: { baseTexture: this.baseTexture, // 基础纹理 shading: \"color\", // color lambert // \'lambert\' 通过经典的 lambert 着色表现光照带来的明暗 light: { ambient: { intensity: 0.9 }, main: { alpha: -45, beta: 45, intensity: 0.6 }, // 主光源 }, atmosphere: {// 利用大气层实现发光效果 show: true, color: \"rgba(33, 97, 179, 0.6)\", glowPower: 4, }, viewControl: { projection: \'perspective\', alpha: this.currentAlpha, beta: this.currentBeta, autoRotateSpeed: 0.6, autoRotate: true, // 开启自动旋转 true autoRotateAfterStill: 5, //鼠标停止操纵后,恢复自转时间 // distance: 200, //默认视角距离主体距离 distance: this.currentDistance, // 使用记录的距离 minDistance: 40, // 最小视角距离 maxDistance: 400, // 最大视角距离 damping: 0, //鼠标旋转或缩放操作时的迟滞因子 rotateSensitivity: 0.8, //旋转操作的灵敏度 zoomSensitivity: 8, //缩放操作的灵敏度 maxBeta: this.maxBeta, }, layers: [ { show: true, type: \"blend\", blendTo: \"emission\", texture: this.createLatitudeLongitudeGrid(), // distance: getEarthRadius() + 5, // 网格稍微在地球表面上方 distance: this.currentDistance + 5, }, ], top: \"6%\", }, series: [], }; this.myChart.clear(); this.myChart.setOption(option); }, // 创建经纬网格 createLatitudeLongitudeGrid() { const canvas = document.createElement(\"canvas\"); const ctx = canvas.getContext(\"2d\"); const size = 1024; canvas.width = size; canvas.height = size; ctx.strokeStyle = \"#114A72\"; ctx.lineWidth = 1; ctx.globalAlpha = 0.8; // 绘制经线 for (let i = 0; i <= size; i += size / 18) { ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, size); ctx.stroke(); } // 绘制纬线 for (let j = 0; j <= size; j += size / 9) { ctx.beginPath(); ctx.moveTo(0, j); ctx.lineTo(size, j); ctx.stroke(); } return canvas; }, // 消息提示 message(text) { this.$Message({ text: text, type: \"warning\", }); }, },};</script><style lang=\"scss\" scoped>.earth-3d { width: 100%; height: 100%;}</style>
现在你已经拥有 了一个可以旋转的3d地球了