使用echarts绘制3d地图

914 阅读5分钟

前言

  • 需求:在平台中能够展示用户所在地区的行政区域图,点击每一个下级区域后显示其相关信息。
  • 技术:vue3、vite、echarts

准备

  1. 安装echarts
    npm install echarts
  2. 安装echarts-gl
    npm install echarts-gl
  3. 获取行政区域下所有下级地区的边框经纬度数据。JSON数据格式参考:datav.aliyun.com/portal/scho…

实现

首先,我们要做的是先去根据数据区绘制3d区域地图,给地图设置交互效果。

交互效果

F市下的行政区域有A区、B区、C区、D区、E区,设定最多可以选中1个区域。用户开始选中A区后再选择B区,这时候选中区域数量已经超过最大数量,所以会先取消上次选中的区域(A区),在选中B区。

绘制地图
◇ 配置地图options

在utils下创建setOptions.js文件。文件内容如下:

export async function set3dMapOptions_backstage(opts) {
	let { mapData, regions, color, formatterCallback } = Object.assign(
		{
			color: "#0fd2aa",//地图区域颜色
			regions: [],//自定义某区域颜色
			formatterCallback: null,//划入区域后的回调
		},
		opts || {})
	//地图根据地区进行个性化配置
	const mapStatic = await getMapDate();
	let bottoms = mapStatic?.bottom;
	let lefts = mapStatic?.left;
	let distances = mapStatic?.zoomDistance;
	let alphas = mapStatic?.rotationAngle;
	let options = {
		series: [
			{
				type: "map3D",
				map: "zhejiang",
				name: "3D地图",
				data: regions.length != 0 ? regions : mapData.features.map((region, index) => ({
					name: region.properties.name,
					itemStyle: {
						color: colors[index % 5],
					}
				})),
				label: {
					show: true, //是否显示高亮
					distance: 0,
					textStyle: {
						color: '#000',
						fontSize: 14,
					}
				},
				light: {
					// 配置光照
					main: {
						intensity: 1, // 光照强度
						shadow: true, // 是否开启阴影
						alpha: 50, // 俯视角度
						beta: 50 // 水平角度
					}
				},
				bottom: bottoms,  //地图距离底部
				left: lefts, //地图距离左侧
				itemStyle: {
					color: color,//regions优先级高于color
					opacity: 1,
					borderWidth: 1,
					borderColor: '#a88c98'
				},
				viewControl: {
					rotateSensitivity: 1,
					zoomSensitivity: 1,
					panSensitivity: 1,
					distance: distances, // 缩放距离
					alpha: alphas // 旋转角度
				},
				emphasis: {
					disabled: true,
					label: {
						show: true,
						distance: 0,
						textStyle: {
							color: '#fff',
						},
						formatter: (params) => {
							if (formatterCallback) formatterCallback(params.name);
							return params.name;
						},
					},
					itemStyle: {
						color: "#40a1fd", // 高亮时地图板块颜色改变
					},
				},
			},
		],
	};
	return options;
}
◇ 绘制行政区域图

创建echarts3DAreaMap.vue文件,文件内容如下:
为地图准备一个定义了宽高的容器。

<div class="echarts3DAreaMap">
    <div class="map-chart" id="mapEchart" style="height: 100%; width: 100%"></div>
</div>

初始化echarts实例,根据options生成一个3d地图。

<script setup>
import { ref, onMounted, watch } from "vue";
import * as echarts from "echarts";
import "echarts-gl"; 

const props = defineProps({
    //地图配置项
	options: Object,
    //地图数据
	mapData: Object,
    //最大选择区域数量
	maxClick: {
		type: Number,
		default: 1,
	},
    //地图是否可点击
	clickable: {
		type: Boolean,
		default: true,
	},
});

const emit = defineEmits(["areaCancelSelect", "areaSelect"]);

let myChart = null; //chart节点对象

/**初始化3d地图*/
function chartMapInit() {
	myChart = echarts.init(document.getElementById("mapEchart"));
	echarts.registerMap("zhejiang", props.mapData);
	myChart.hideLoading();
	myChart.setOption(props.options);
    //绑定点击事件
	myChart.on('click', (e) => {
		myChartClick(e.data.name, e)
	})
}

/**选中的区域名称列表[string]*/
let selectAreaList = [];

/**
 * 处理图表点击事件的函数
 * @param {string} name - 被点击区域的名称
 * @param {Object} e - 点击事件对象
 */
function myChartClick(name, e) {
	if (props.clickable) {
		let index = selectAreaList.findIndex((item) => item == name);
		if (index == -1) {
			areaSelect(name, e);
		} else {
			areaCancelSelect(index, name);
		}
	} else {
		emit("areaSelect");
	}
}

/**
 * 区域选择函数 
 * @param {string} name - 要选择的区域的名称
 * @param {Event} e - 触发的事件对象
 */
function areaSelect(name, e) {
	if (selectAreaList.length < props.maxClick || props.maxClick == 0) {
		selectAreaList.push(name);
		setStyle_active(name, e)
	} else if (selectAreaList.length >= props.maxClickAreaNum) {
		areaCancelSelect(0, selectAreaList[0])
		selectAreaList.push(name);
		setStyle_active(name, e)
	}
}

let seriesData = [...props.options.series[0].data]

let oldStyle = {
	name: null,
	itemStyle: {
		color: "#40a1fd",
	},
	height: 3,
};

let newObj = {
	name: null,
	label: {
		textStyle: {
			color: '#fff',
		},
	},
	itemStyle: {
		color: "#40a1fd",
	},
	height: 5,
};

/**
 * 设置区域选中颜色函数 
 * @param {string} name - 要选择的区域的名称
 * @param {Event} e - 触发的事件对象
 */
 function setStyle_active(name, e) {
	newObj.name = name
	let obj = JSON.parse(JSON.stringify(newObj))
	let i = seriesData.findIndex(
		(item) => item.name == name,
	);
	if (i != -1) {
		oldStyle = { ...props.options.series[0].data[i] }
		seriesData[i] = obj
	} else {
		oldStyle = { name: name, itemStyle: {}, height: 3 }
		seriesData.push(obj)
	}
	props.options.series[0].data = JSON.parse(JSON.stringify(seriesData))
	myChart.setOption(props.options);
	emit("areaSelect", e, selectAreaList);
}

/**
 * 区域取消选中
 * 当用户取消选择某个区域时调用此函数
 * @param {number} index - 被取消选中的区域在选中区域列表中的索引
 * @param {string} areaName - 被取消选中的区域的名称
 */
 function areaCancelSelect(index, areaName) {
	selectAreaList.splice(index, 1);
	cancelSelectAfter(areaName)
}

/**
 * 设置区域取消选中颜色函数 
 * @param {string} areaName - 区域的名称
 */
function setStyle_active(name, e) {
	let i = seriesData.findIndex(
		(item) => item.name == areaName,
	);
	let { name, itemStyle, height } = seriesData[i];
	oldStyle.name = areaName
	let obj = JSON.parse(JSON.stringify(oldStyle))
	seriesData[i] = obj
	props.options.series[0].data = JSON.parse(JSON.stringify(seriesData))
	myChart.setOption(props.options);
	emit("areaCancelSelect", name, props.options);
}
/**窗口变化时修改地图大小*/
window.addEventListener("resize", () => {
	myChart.resize();
});

onMounted(() => {
	chartMapInit();
});

defineExpose({
	myChartClick,
});
</script>

引入地图字组件。

获取完地图边框数据后,需要将边框数据以及鼠标移入区域后的回调作为参数调用set3dMapOptions_backstage方法,拿到返回的地图配置项并展示地图。

<template>
    <div class="regionalComparison-map">
              <Echarts3DAreaMap ref="echarts3DAreaMapRef"  v-if="isShowMap" :mapData="mapData" :options="mapOptions" :maxClickAreaNum="2" @areaCancelSelect="areaCancelSelect" @areaSelect="areaSelect"></Echarts3DAreaMap>
    </div>
</template>

<script setup>
import { ref, onMounted } from "vue";
import {
	set3dMapOptions_backstage,
} from "@/utils/setOptions";

/**是否显示地图子组件*/
const isShowMap = ref(false);

/**地图子组件ref*/
const echarts3DAreaMapRef = ref(null);

/**地图边框数据*/
let mapData = ref(null);
/**获取完地图边框数据后,需要将边框数据以及鼠标移入区域后的回调作为参数调用set3dMapOptions_backstage方法,拿到返回的地图配置项并展示地图*/

/**区域选中*/
function areaSelect(data){
}

/**区域取消选中*/
function areaCancelSelect(name){
}

/**鼠标移入区域后的回到函数*/
function labelformatter(name) {
	
}
</script>

通过上面的代码,已经实现了地图的绘制和交互,下面就是怎么实现区域选中后,在用户点击的位置上显示区域信息。这里将通过css样式来实现这一功能。

◇ 区域信息

首先,创建一个展示区域信息的子组件areaInfo.vue,内容如下:

<template>
	<div class="detail-model" :style="styles">内容</div>
</template>
<script setup>
import { ref, onMounted } from "vue";

const props = defineProps({
	styles: {
		type: Object,
		default: {},
	},
	contentStyles: {
		type: Object,
		default: {},
	},
});
</script>
<style lang="scss" scoped>
.detail-model {
	width: 550px;
	height: 600px;
	position: absolute;
	display: flex;
	z-index: 999;

	.content {
		width: 80%;
		background: url("@/assets/screen/mapDialogBg.png") no-repeat;
		background-size: 100% 100%;
	}

	.line-box {
		width: 110px;
		height: 100%;

		.children {
			position: absolute;
			bottom: 0;
		}

		.line1 {
			height: 1px;
			width: 155px;
			background-color: #fb7293;
			transform-origin: 0% 0%;
			transform: rotate(-45deg);
		}
	}
}
</style>

父组件中引入区域信息子组件,并在根据offsetX、offsetY这两个数值来设置区域信息子组件得样式,内容如下:

<template>
		<AreaFarmerDetail v-if="isShowDialog" :styles="styles" :contentStyles="contentStyles" @closeModel="closeDetailModel"></AreaFarmerDetail>
</template>
<script setup>
import { ref } from "vue";

function areaSelect(e, data) {
    isShowDialog.value=true
	styles.value.left = e.event.offsetX + 30 + "px";
	styles.value.top = e.event.offsetY - 490 + "px";
    //解决区域信息因遮盖导致显示不全问题
	if (e.event.offsetY < 430) {
		contentStyles.value.top = 600 - e.event.offsetY + 'px';
	} else {
		contentStyles.value.top = 0 + 'px';
	}
}
</style>

到此就结束了,下面是功能的效果图:

image.png

image.png

image.png