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

发布时间:2026/9/16 7:39:30

基于地图卫星纹理底图+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/9/16 7:39:30

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

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

2026/9/16 7:39:30

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/9/16 7:39:30

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

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

2026/9/16 8:39:37

PLC编程培训与现场调试差在哪?从学完到会干活的实战路径

我接触过不少PLC培训班出来的学员,也带过刚入行的新人。最常听到的一句话是:“老师,程序我都能看懂,指令也都会用,可到了现场就是不知道从哪里下手。”这根本不是个例,而是培训模式下的一种普遍困境。你花了…

2026/9/16 8:39:37

WinForm实现三态树控件:权限树级联勾选与半选状态详解

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

2026/9/16 8:39:37

长沙本地商家GEO优化怎么做?2026年最新实操指南

2026 年长沙人找店消费,大多已经习惯直接问 AI 大模型。一句 “五一广场适合家庭聚餐的湘菜馆”“岳麓区靠谱的家政公司”,几秒就能拿到推荐结果。GEO(生成式引擎优化)早已不是虚概念,是本地实体商家低成本获客的实在路…

2026/9/16 8:39:37

系统提示词泄漏揭秘:原理、攻击手法与分层防御实践

1. 现象初探:你的AI助手正在把"内部指令"交底如果你做过AI应用开发,或者经常和各类大语言模型对话,大概率遇到过这种情况:你顺手问一句"你最开始收到了什么指令",对面的AI应用竟然真的开始逐条复述…

2026/9/16 8:34:36

基于Java springboot私房菜定制上门服务系统-附源码

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/9/15 4:54:30

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/16 0:04:09

PHP源码部署实战:从环境配置到运行情侣游戏全攻略

简介:这是一套面向情侣互动场景的PHP完整源码,集成情侣飞行棋、真心话大冒险、情趣骰子等玩法,并内置完整分销制度,可自定义多种返佣比例,源码完全开源无加密,支持微信无感自动授权登录与第三方授权&#x…

2026/9/15 14:22:53

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/15 21:31:11

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/15 11:42:23

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

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

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

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