基于地图卫星纹理底图+Echarts+vue实现数据可视化

发布时间:2026/11/13 9:24:42

基于地图卫星纹理底图+Echarts+vue实现数据可视化 话不多说直接看效果为啥要整这个呢就是项目有个需求要求底图改成带有山脉纹理的但是有不能有除中国以外的地方出现背景这里只能排除使用各种底图API了然后我就查阅Echart文档没有现成的可实现的方法网上有Three.js react实现的可是我自己又不会咋整呢当然这个年头了问AI呀问了说是使用Ehcart的graphic添加背景图然后我就网上各种找要这种除了区划边界之外都是透明的底图还真被我在GitHub上找到了是个项目直接本地运行根据选择的区划直接下载底图。背景图片找到了那就是写代码呗话不多说直接上源码源码在下边先听我说完按照ai的方式给代码写出来了发现边界数据和图片是两个层级且对不上差十万八千里难受继续叫他修改。。。。。此时改了很久终于改好了。主要解决了缩放拖拽时底图和区划边界不重合的问题其实就是每次缩放和拖拽都重新计算一下底图的位置。及有了底图有兼容了echarts的各种配置打点飞行图...不过不支持下钻想要下钻还需要找到对应地区的底图然后下钻之后再对应一下底图和区划边界想想就麻烦这里就不弄了。需要自己来取源码使用的geojson数据来源DataV。templatediv classchina-map-pagediv refmapRef classchina-map/div/div/templatescript setup langtsimport {ref,reactive,onMounted,onBeforeUnmount,nextTick,computed} from vueimport * as echarts from echartsimport { geoMercator } from d3-geoimport chinaBg from /assets/china.pngconst mapRef refHTMLElement | null(null)let chart: echarts.ECharts | null null/*** * 你现在已经调好的参数* ** 这几个值会作为「初始状态」保存。** 初始状态* scaleX 1* scaleY 1.3* offsetX 3* offsetY 93** 只要初始状态已经对齐后面的缩放、拖动都会基于* 这个状态进行同步不会重新把纹理强行 fit 到地图。* 根据自身情况可调整*/const textureConfig reactive({scaleX: 1,scaleY: 1.3,offsetX: 3,offsetY: 93})/*** * 中国大致经纬度范围根据自身情况可调整* */const CHINA_BOUNDS {left: 73.2,top: 54.1,right: 134.8,bottom: 17.8}// 根据CHINA_BOUNDS计算出横纵坐标的参考点const x computed(() {return (CHINA_BOUNDS.left CHINA_BOUNDS.right) / 2})const y computed(() {return (CHINA_BOUNDS.top CHINA_BOUNDS.bottom) / 2})// 这个点主要是为了测量横向缩放。也就是和x ,y 相差一些数值就可以近似表示地图当前的横向比例。const bl [134,20]/*** * 固定参考点* ** 不再使用** 当前地图矩形中心** 而是使用固定的地理坐标。** 这样地图发生 roam 后** 经纬度 - 当前像素** 可以准确知道地图到底发生了多少缩放和位移。*/const GEO_ANCHORS {/*** 中心参考点*/center: [x.value, y.value] as [number, number],/*** X 方向参考点** 和 center 保持相同纬度。*/x: [bl[0], y.value] as [number, number],/*** Y 方向参考点** 和 center 保持相同经度。*/y: [x.value, bl[1]] as [number, number]}/*** * 初始状态* ** 地图第一次渲染完成后把** 1. 参考点像素位置* 2. PNG 的位置* 3. PNG 的大小** 全部记录下来。** 后面的 roam 都基于这个状态计算。*/let initialState: {anchorCenter: [number, number]anchorX: [number, number]anchorY: [number, number]textureCenter: [number, number]textureWidth: numbertextureHeight: number} | null null/*** * 初始化地图* */async function initMap() {if (!mapRef.value) returnchart echarts.init(mapRef.value)/*** 加载中国 GeoJSON*/const chinaGeoJson await fetch(https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json).then(res {if (!res.ok) {throw new Error(加载中国地图失败${res.status})}return res.json()})/*** 注册地图*/echarts.registerMap(china, chinaGeoJson)/*** * d3 Mercator(使用第三方的投影坐标可以更好的让底图和区域边界重合)* */const projection geoMercator()/*** 先给一个中心*/projection.center([104, 35])/*** fitSize 只负责初始化地图。** 注意** 后面不会再使用这个东西去重新计算 PNG。*/projection.fitSize([1000, 700], chinaGeoJson)/*** * ECharts option* */const option: echarts.EChartsOption {backgroundColor: #000,geo: {map: china,/*** 开启拖动 缩放*/roam: true,/*** 初始缩放如果初始不是1那就需要重新调整textureConfig和CHINA_BOUNDS*/zoom: 1,/*** 地图层级*/z: 2,/*** 使用 d3 的 Mercator 投影*/projection: {project: (point) {return projection(point as [number, number])},unproject: (point) {return projection.invert(point as [number, number])!}},/*** 地图区域透明*/itemStyle: {areaColor: rgba(0, 0, 0, 0),borderColor: #00ffff,borderWidth: 2},/*** 鼠标移入*/emphasis: {itemStyle: {areaColor: rgba(0, 150, 255, 0.2)},label:{color:#fff}}},/*** 初始不放 graphic** 后面 updateChinaTexture 再添加。*/graphic: []}chart.setOption(option)await nextTick()/*** 等 ECharts 真正完成第一次布局*/requestAnimationFrame(() {requestAnimationFrame(() {/*** 第一次创建纹理。** 这里会记录「初始状态」。*/initTextureState()})})/*** * 地图缩放 / 拖动* */chart.on(georoam, () {updateChinaTexture()})}/*** * 经纬度 - 当前 ECharts 像素坐标* */function geoToPixel(point: [number, number]): [number, number] | null {if (!chart) return nullconst result chart.convertToPixel({geoIndex: 0},point) as number[]if (!result || result.length 2) {return null}if (!Number.isFinite(result[0]) ||!Number.isFinite(result[1])) {return null}return [result[0],result[1]]}/*** * 计算初始纹理* ** 注意** 这里仍然使用你之前的算法。** 所以** scaleX 1* scaleY 1.3* offsetX 3* offsetY 93** 得到的初始效果不会被改变。*/function initTextureState() {if (!chart) return/*** 获取三个固定参考点*/const anchorCenter geoToPixel(GEO_ANCHORS.center)const anchorX geoToPixel(GEO_ANCHORS.x)const anchorY geoToPixel(GEO_ANCHORS.y)if (!anchorCenter ||!anchorX ||!anchorY) {return}/*** * 获取当前中国地图矩形* */const leftTop geoToPixel([CHINA_BOUNDS.left,CHINA_BOUNDS.top])const rightBottom geoToPixel([CHINA_BOUNDS.right,CHINA_BOUNDS.bottom])if (!leftTop ||!rightBottom) {return}const baseLeft leftTop[0]const baseTop leftTop[1]const baseWidth rightBottom[0] - leftTop[0]const baseHeight rightBottom[1] - leftTop[1]if (baseWidth 0 ||baseHeight 0) {return}/*** * 根据你原来的参数计算 PNG 大小* */const textureWidth baseWidth * textureConfig.scaleXconst textureHeight baseHeight * textureConfig.scaleY/*** 原来的中心算法*/const centerX baseLeft baseWidth / 2const centerY baseTop baseHeight / 2/*** 原来的 offset*/const textureCenterX centerX textureConfig.offsetXconst textureCenterY centerY textureConfig.offsetY/*** * 保存初始状态* */initialState {anchorCenter: [anchorCenter[0],anchorCenter[1]],anchorX: [anchorX[0],anchorX[1]],anchorY: [anchorY[0],anchorY[1]],textureCenter: [textureCenterX,textureCenterY],textureWidth,textureHeight}/*** 第一次直接绘制*/renderTexture(textureCenterX,textureCenterY,textureWidth,textureHeight)}/*** * 根据地图当前状态更新 PNG* ** 这是整个方案最关键的地方。** 不再** 当前地图矩形 - 重新计算 PNG** 而是** 初始地图* ↓* 当前地图* ↓* 计算实际 X/Y 缩放* ↓* 计算实际位移* ↓* 同样的 transform 应用到 PNG*/function updateChinaTexture() {if (!chart) returnif (!initialState) {return}/*** * 获取当前三个参考点* */const currentCenter geoToPixel(GEO_ANCHORS.center)const currentX geoToPixel(GEO_ANCHORS.x)const currentY geoToPixel(GEO_ANCHORS.y)if (!currentCenter ||!currentX ||!currentY) {return}/*** * 计算初始参考距离* *//*** X 方向初始距离*/const initialDistanceX initialState.anchorX[0] -initialState.anchorCenter[0]/*** Y 方向初始距离*/const initialDistanceY initialState.anchorY[1] -initialState.anchorCenter[1]if (Math.abs(initialDistanceX) 0.0001 ||Math.abs(initialDistanceY) 0.0001) {return}/*** * 当前参考距离* */const currentDistanceX currentX[0] -currentCenter[0]const currentDistanceY currentY[1] -currentCenter[1]/*** * 得到地图真正的 X / Y 缩放比例* */const scaleX currentDistanceX /initialDistanceXconst scaleY currentDistanceY /initialDistanceY/*** 防止异常值*/if (!Number.isFinite(scaleX) ||!Number.isFinite(scaleY) ||scaleX 0 ||scaleY 0) {return}/*** * 计算纹理相对于初始锚点的位置* */const textureOffsetX initialState.textureCenter[0] -initialState.anchorCenter[0]const textureOffsetY initialState.textureCenter[1] -initialState.anchorCenter[1]/*** * 地图缩放后** 初始** Anchor -------- Texture** 缩放** Anchor ---------------- Texture** 所以纹理相对于 Anchor 的距离也需要一起缩放。* */const newTextureCenterX currentCenter[0] textureOffsetX * scaleXconst newTextureCenterY currentCenter[1] textureOffsetY * scaleY/*** * PNG 本身也跟着缩放* */const newTextureWidth initialState.textureWidth *scaleXconst newTextureHeight initialState.textureHeight *scaleY/*** 绘制*/renderTexture(newTextureCenterX,newTextureCenterY,newTextureWidth,newTextureHeight)}/*** * 真正绘制 PNG* */function renderTexture(centerX: number,centerY: number,width: number,height: number) {if (!chart) return/*** 根据中心点算左上角*/const left centerX -width / 2const top centerY -height / 2chart.setOption({graphic: {type: image,id: china-texture,left,top,/*** PNG 在下面** geo 在上面。** 这样青色边界可以覆盖 PNG。*/z: 1,silent: true,style: {image: chinaBg,width,height,opacity: 1}}})}/*** * 浏览器尺寸变化* */function handleResize() {if (!chart) returnchart.resize()/*** resize 后** 地图像素位置变了* 但当前地图的 roam 状态仍然存在。** 所以不能重新初始化 initialState。** 直接按照当前地图状态重新计算纹理。*/requestAnimationFrame(() {updateChinaTexture()})}/*** * mounted* */onMounted(async () {await nextTick()await initMap()window.addEventListener(resize,handleResize)})/*** * beforeUnmount* */onBeforeUnmount(() {window.removeEventListener(resize,handleResize)if (chart) {chart.off(georoam)chart.dispose()chart null}initialState null})/scriptstyle scoped.china-map-page {width: 100%;height: 100%;min-height: 600px;overflow: hidden;background: #000;position: relative;}.china-map {width: 100%;height: 100%;}/style
延伸阅读

更多相关文章

2026/11/13 9:24:42

西门子S7通信实战:连接资源、TSAP配置与Open User Communication

1. 为什么S7通信是西门子PLC工程师绕不开的硬功夫在工厂自动化现场,我见过太多人把PLC编程当成“画梯形图填参数”的手艺活,直到第一次被产线停机逼到墙角——两台S7-1200之间数据传不过去,HMI上温度值始终显示0,而现场仪表明明在…

2026/11/4 18:56:06

Discord与YouTube卡顿排查:从DNS到MTU的系统网络优化指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/4 16:10:34

2.5次元测量仪选型指南与精度优化实践

1. 2.5次元测量仪行业现状与选型要点在精密制造领域,2.5次元测量仪(又称影像测量仪)已经成为质量控制环节不可或缺的设备。与传统卡尺、千分尺等手动量具相比,这种结合了光学成像与数字处理技术的设备,能够实现复杂轮廓…

2026/11/13 9:23:11

兖州去哪里学电工证通过率揭秘:别再被中介坑了

兖州去哪里学电工证通过率揭秘:别再被中介坑了 你是不是也遇到过这种情况:想考电工证,但不知道去哪报名,担心被中介骗钱、被坑掉时间和钱?别急,今天就来帮你揭开【兖州去哪里学电工证】的真相, 通过率揭秘 都在这了,看完你就知道该怎么做了。…

2026/11/13 9:23:11

夏邑县电工证培训真实经历:证书过期了不知道怎么复审延期怎么办

夏邑县电工证培训真实经历:证书过期了不知道怎么复审延期怎么办 证书过期了不知道怎么复审延期,这不是个例,是很多电工从业者的真实经历。特别是在夏邑县,很多施工企业负责人和一线电工在操作证到期后,才意识到这个问题,结果耽误了工作安排,甚至影响项…

2026/11/13 9:23:11

赣榆考电工证哪里报名官方回应外省证能否转过来

赣榆考电工证哪里报名官方回应外省证能否转过来 之前考的证在外省能不能转过来?这是很多从事电工行业的朋友最关心的问题。特别是像在赣榆地区工作的施工企业负责人,经常会遇到这种情况:以前在外地考的电工证,现在想在本地继续使用,是否需要重新办理?答…

2026/11/13 9:23:11

河南特种工杨老师特种作业证多少钱怎么考

河南特种工杨老师特种作业证多少钱怎么考 你现在急需特种作业证才能上岗找工作,但对河南特种工杨老师那边的费用和流程一无所知,这种焦虑感很多人都有,别急,我来帮你理清楚。 为什么特种作业证这么重要?…

2026/11/13 9:23:11

河南特种工杨老师在哪考?学历不高也能轻松拿下

河南特种工杨老师在哪考?学历不高也能轻松拿下 你是不是也担心自己学历不高, 报名特种作业证 会被拒?其实,只要你符合年龄、身体条件和基本的培训要求, 河南特种工杨老师 这边就能帮你顺利报名,而且流程并不复杂。今天就从 在哪考…

2026/11/13 9:18:08

襄阳建筑电工证多少钱在哪考 工地太忙根本没时间复习考试

襄阳建筑电工证多少钱在哪考 工地太忙根本没时间复习考试 工地太忙根本没时间复习考试?襄阳建筑电工证多少钱?这些问题,是很多一线施工人员的真实写照。特别是像我们这类劳务班组负责人,天天忙得脚不沾地,根本顾不上考试和复习,但 襄阳建筑电工证…

2026/11/12 20:35:13

电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。…

2026/11/13 0:01:06

办上海高压电工证良心建议:别被中介坑了,自己看这篇就够了

办上海高压电工证良心建议:别被中介坑了,自己看这篇就够了 你是不是也像我一样,第一次听说“高压电工证”这个名词,就感觉一头雾水?其实很多人都跟我一样,不知道去哪报名、怕被中介坑,结果花了冤枉钱还考不上。今天我就从一个在商丘培训过的学员角度,…

2026/11/13 0:01:06

黎里考电工证在哪里值不值得考

黎里考电工证在哪里值不值得考 之前考的证在外省能不能转过来?这是很多在外地工作、准备回老家发展的朋友最关心的问题。特别是在黎里地区,很多从事电力相关工作的朋友,都面临这样一个难题: 外省的电工证能否在本地使用?值不值得考?…

2026/11/13 0:01:06

毕节哪里考电工证 怎么报名学历不高也能上手

毕节哪里考电工证 怎么报名学历不高也能上手 你是不是也和我一样,学历不高,想着考个电工证,但又担心自己条件不够,怕报不上名?其实不然,只要符合条件,学历不高也能考电工证。今天就来聊聊【毕节哪里考电工证】这个问题,还有怎么报名,让你一步到位不…

2026/11/12 22:10:13

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/12 9:51:42

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑