🧩 效果预览
👇 飞机从多个城市起飞并向其他城市飞行,动画流畅,地图可缩放拖拽:

📦 一、项目技术栈
技术 | 用途 |
---|---|
Vue 3 | 现代前端框架 |
OpenLayers | 地图底图渲染 |
ECharts + ol-echarts |
飞机飞行动画渲染 |
ol-echarts | 将 ECharts 图层嵌入到 OpenLayers |
⚙️ 二、环境准备
1. 创建项目(如果你已有 Vue3 项目可跳过)
javascript
npm init vue@latest vue-openlayers-echarts
cd vue-openlayers-echarts
npm install
选用 Vue 3 + TypeScript
或 Vue 3 + JavaScript
皆可。
2. 安装必要依赖
javascript
npm install ol echarts ol-echarts
如果你使用的是 Vite 构建工具,也可以添加 ECharts 按需引入优化:
🌍 三、准备地图 JSON 数据
我们需要一个 GeoJSON 格式的中国地图数据 作为 ECharts 的底图。你有两种方式下载:
✅ 方法一:使用阿里官方提供的地图数据
-
打开:https://geo.datav.aliyun.com/areas/bound/100000_full.json
-
将其放入你的项目
public/map/china.json
✅ 方法二:也可以使用世界地图 world.json
(视觉更国际化)
-
下载链接:https://cdn.jsdelivr.net/npm/echarts@5/map/json/world.json
-
保存路径同样为:
public/map/world.json
🧱 四、完整代码实现(Composition API)
创建组件 OpenlayersPlane.vue
,核心代码如下:
javascript
<!--
* @Author: 彭麒
* @Date: 2025/7/8
* @Email: 1062470959@qq.com
* @Description: 此源码版权归吉檀迦俐所有,可供学习和借鉴或商用。
-->
<template>
<div class="container">
<div class="w-full flex justify-center flex-wrap">
<div class="font-bold text-[24px]">
在 Vue3 中使用 OpenLayers 地图上Echarts模拟飞机循环飞行
</div>
</div>
<div id="vue-openlayers"></div>
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import 'ol/ol.css'
import { Map, View } from 'ol'
import TileLayer from 'ol/layer/Tile'
import OSM from 'ol/source/OSM'
import EChartsLayer from 'ol-echarts'
import * as echarts from 'echarts/core'
// 引入世界地图数据
import { registerMap } from 'echarts/core'
let map = null
onMounted(async () => {
// 注册世界地图数据
try {
// 方法1:如果你已下载文件到项目中
// import worldJson from '@/assets/geo/world.json'
// 方法2:从公共目录获取
const worldJson = await fetch('/map/china.json').then(res => res.json())
//
// // 注册世界地图数据
registerMap('world', worldJson)
initMap()
} catch (error) {
console.error('加载世界地图数据失败:', error)
}
})
function initMap() {
const osmLayer = new TileLayer({
source: new OSM()
})
map = new Map({
target: 'vue-openlayers',
layers: [osmLayer],
view: new View({
projection: 'EPSG:4326',
center: [116.53, 39.44],
zoom: 7
})
})
// 正确初始化 EChartsLayer
const option = getOption()
const echartslayer = new EChartsLayer(option, {
hideOnMoving: false,
hideOnZooming: false,
forcedRerender: true, // 强制重新渲染
coordinate: map.getView().getProjection().getCode()
})
echartslayer.appendTo(map)
}
function getOption() {
const geoCoordMap = {
'北京': [116.4551, 40.2539],
'上海': [121.4648, 31.2891],
'广州': [113.5107, 23.2196],
'大连': [122.2229, 39.4409],
'南宁': [108.479, 23.1152],
'南昌': [116.0046, 28.6633],
'拉萨': [91.1865, 30.1465],
'长春': [125.8154, 44.2584],
'包头': [110.3467, 41.4899],
'重庆': [107.7539, 30.1904],
'常州': [119.4543, 31.5582],
'昆明': [102.9199, 25.4663],
'郑州': [113.4668, 34.6234],
'长沙': [113.0823, 28.2568],
'丹东': [124.541, 40.4242]
}
const BJData = [
[{ name: '北京' }, { name: '上海', value: 95 }],
[{ name: '北京' }, { name: '广州', value: 90 }],
[{ name: '北京' }, { name: '大连', value: 80 }],
[{ name: '北京' }, { name: '南宁', value: 70 }],
[{ name: '北京' }, { name: '南昌', value: 60 }],
[{ name: '北京' }, { name: '拉萨', value: 50 }],
[{ name: '北京' }, { name: '长春', value: 40 }],
[{ name: '北京' }, { name: '包头', value: 30 }],
[{ name: '北京' }, { name: '重庆', value: 20 }],
[{ name: '北京' }, { name: '常州', value: 10 }]
]
const SHData = [
[{ name: '上海' }, { name: '包头', value: 95 }],
[{ name: '上海' }, { name: '昆明', value: 90 }],
[{ name: '上海' }, { name: '广州', value: 80 }],
[{ name: '上海' }, { name: '郑州', value: 70 }],
[{ name: '上海' }, { name: '长春', value: 60 }],
[{ name: '上海' }, { name: '重庆', value: 50 }],
[{ name: '上海' }, { name: '长沙', value: 40 }],
[{ name: '上海' }, { name: '北京', value: 30 }],
[{ name: '上海' }, { name: '丹东', value: 20 }],
[{ name: '上海' }, { name: '大连', value: 10 }]
]
const planePath =
'path://M1705.06,1318.313v-89.254l-319.9-221.799l0.073-208.063c0.521-84.662-26.629-121.796-63.961-121.491c-37.332-0.305-64.482,36.829-63.961,121.491l0.073,208.063l-319.9,221.799v89.254l330.343-157.288l12.238,241.308l-134.449,92.931l0.531,42.034l175.125-42.917l175.125,42.917l0.531-42.034l-134.449-92.931l12.238-241.308L1705.06,1318.313z';
function convertData(data) {
const res = []
for (let i = 0; i < data.length; i++) {
const fromCoord = geoCoordMap[data[i][0].name]
const toCoord = geoCoordMap[data[i][1].name]
if (fromCoord && toCoord) {
res.push({
fromName: data[i][0].name,
toName: data[i][1].name,
coords: [fromCoord, toCoord]
})
}
}
return res
}
const color = ['#f00', '#0000ff']
const series = []
// 修复数组格式和括号对齐问题
const dataList = [
['北京', BJData],
['上海', SHData]
]
dataList.forEach((item, i) => {
series.push(
{
name: item[0] + ' Top10',
type: 'lines',
coordinateSystem: 'geo', // 添加坐标系统
zlevel: 1,
effect: {
show: true,
period: 6,
trailLength: 0.7,
color: '#fff',
symbolSize: 3
},
lineStyle: {
normal: {
color: color[i],
width: 0,
curveness: 0.2
}
},
data: convertData(item[1])
},
{
name: item[0] + ' Top10',
type: 'lines',
coordinateSystem: 'geo', // 添加坐标系统
zlevel: 2,
effect: {
show: true,
period: 6,
trailLength: 0,
symbol: planePath,
symbolSize: 15
},
lineStyle: {
normal: {
color: color[i],
width: 1,
opacity: 0.4,
curveness: 0.2
}
},
data: convertData(item[1])
},
{
name: item[0] + ' Top10',
type: 'effectScatter',
coordinateSystem: 'geo',
zlevel: 2,
rippleEffect: {
brushType: 'stroke'
},
label: {
normal: {
show: true,
position: 'right',
formatter: '{b}'
}
},
symbolSize(val) {
return val[2] / 8
},
itemStyle: {
normal: {
color: color[i]
}
},
data: item[1].map(dataItem => ({
name: dataItem[1].name,
value: geoCoordMap[dataItem[1].name].concat([dataItem[1].value])
}))
}
)
})
return {
tooltip: {
trigger: 'item'
},
geo: {
map: 'world',
roam: true,
silent: true,
itemStyle: {
normal: {
borderColor: 'rgba(0, 0, 0, 0.2)'
}
}
},
series
}
}
</script>
<style scoped>
.container {
width: 840px;
height: 570px;
margin: 50px auto;
border: 1px solid #42B983;
position: relative;
}
#vue-openlayers {
width: 800px;
height: 450px;
margin: 0 auto;
border: 1px solid #42B983;
position: relative;
}
</style>
🔍 五、关键说明
✅ 为什么用 ol-echarts
?
-
官方维护,轻量集成 ECharts 图层到 OpenLayers
-
支持地图缩放拖拽不丢失动画
-
自动将 ECharts 转换为地图坐标系
✅ 为什么要注册地图数据?
ECharts 默认不包含地图底图,注册 world
或 china
是必须的。
✅ 常见问题:
-
❌ 地图加载失败 :检查
/map/china.json
路径是否正确 -
❌ 飞机不动 :确认
symbol
使用了合法的 SVG path -
✅ 加载慢:地图数据尽量缓存到本地,避免 CDN 延迟
🔚 六、结语与拓展建议
这只是地理可视化的一个小案例,你还可以尝试:
-
🌟 动态航线实时更新(结合 WebSocket)
-
🧭 飞机移动+转向效果(结合 Cesium)
-
📡 与后端数据库联动(显示实时航班位置)
-
🎯 鼠标交互事件,如点击城市展示信息面板
📁 七、项目源码 & 参考
-
OpenLayers 官网:https://openlayers.org/
-
ECharts 官网:https://echarts.apache.org/
-
ol-echarts 项目地址:https://github.com/sakitam-gis/ol-echarts
如果你觉得这篇文章对你有帮助,欢迎点赞 👍、收藏 ⭐、关注我获取更多前端 + 地图可视化实战教程!