技术需求如下:
1、封装高德 vue3 hooks脚本
2、实现3D城市地图
① 点击具体位置可以获取对应的坐标信息及位置信息
② 轨迹动画效果,以3D地图为底,实现从起点到终点
3、实现点聚合效果
4、覆盖物、几何计算的了解
① 实现5公里,10公里范围内的圆形覆盖,并计算其面积和距离
5、高德地图和echarts的结合
技术实现如下:
一 封装高德 vue3 hooks脚本
首先注册高德地图,安装依赖,使用key,安装使用插件,加载使用AMapUI等然后绘制地图
useMap.js
import AMapLoader from "@amap/amap-jsapi-loader";
import { ref, onMounted, onUnmounted } from "vue";
/**
* 返回地图相关内容
* @param HTMLID 地图ID
* @param options 地图初始值
* @param callback 地图绘制完成之后的方法
*/
export default function useMap(HTMLID,options,callback) {
const AMapItem = ref();
const container = ref();
// 注册 高德地图
const init = () => {
let mapItem = new Promise((resolve, reject) => {
window._AMapSecurityConfig = {
securityJsCode: "a780c3a6a5547aaca7b94205c563fadc",
};
AMapLoader.load({
key: "522a23c47a02eb83559470d9326eeb9b", //备用测试key e1dedc6bdd765d46693986ff7ff969f4
version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
// 需要使用的的插件列表,如比例尺'AMap.Scale'等
plugin: [
"AMap.IndexCluster",
"AMap.Autocomplete", // 输入提示插件
"AMap.PlaceSearch", // POI搜索插件
"AMap.Scale", // 右下角缩略图插件 比例尺
"AMap.OverView", // 地图鹰眼插件
"AMap.ToolBar", // 地图工具条
"AMap.Geolocation", // 定位控件,用来获取和展示用户主机所在的经纬度位置
"AMap.Geocoder",
"AMap.MapType",
"AMap.InfoWindow",
],
AMapUI: {
// 是否加载 AMapUI,缺省不加载
version: "1.1", // AMapUI 版本
plugins: ["overlay/SimpleMarker", "misc/PathSimplifier"], // 需要加载的 AMapUI ui插件
},
})
.then((AMap) => {
resolve(AMap);
})
.catch((e) => {
reject(e);
console.log(e);
});
});
return mapItem;
};
onMounted(() => {
init().then((AMap) => {
AMapItem.value = AMap;
// 创建地图
container.value = new AMap.Map(HTMLID, options);
callback(AMap)
});
});
return {
AMapItem,
container,
};
}
二 实现点击具体位置可以获取对应的坐标信息及位置信息
<template>
<div id="container"></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import useMap from "@/hooks/useMap";
const infoWindow = ref(); //选择的弹出框信息
const { AMapItem, container } = useMap(
"container",
{
resizeEnable: true, //地图是否可旋转
terrain: true, //开启地形图
viewMode: "3D", //地图模式
rotateEnable: true, //是否开启地图旋转交互 鼠标右键 + 鼠标画圈移动 或 键盘Ctrl + 鼠标左键画圈移动
pitchEnable: true, //是否开启地图倾斜交互 鼠标右键 + 鼠标上下移动或键盘Ctrl + 鼠标左键上下移动
zoom: 17, //初始化地图层级
// rotation: -15, //初始地图顺时针旋转的角度
zooms: [2, 30], //地图显示的缩放级别范围
center: [120.469626, 36.094172], //初始地图中心经纬度
pitch: 70, //地图俯仰角度,有效范围 0 度- 83 度
isHotspot: true, //是否开启地图热点和标注的 hover 效果
},
(AMapItem) => {
AMapItem.plugin(
["AMap.InfoWindow", "AMap.PlaceSearch"],
function () {
getPositionInformation(AMapItem)
}
);
}
);
function getPositionInformation(AMap) {
// 鼠标放上去搜索加弹框
var placeSearch = new AMap.PlaceSearch(); //构造地点查询类
infoWindow.value = new AMap.InfoWindow({});
// 鼠标滑过热点时触发
container.value.on("hotspotover", function (result) {
placeSearch.getDetails(result.id, function (status, result) {
// 根据PGUID 查询POI 详细信息
console.log('result',result)
var poiArr = result.poiList.pois;
if (status === "complete" && result.info === "OK"&&poiArr.length!=0) {
var location = poiArr[0].location;
infoWindow.value.setContent(createContent(poiArr[0]));
infoWindow.value.open(container.value, location);
}
});
});
}
function createContent(poi) {
//信息窗体内容
var s = [];
s.push(
'<div class="info-title">' +
poi.name +
'</div><div class="info-content">' +
"地址:" +
poi.address
);
s.push("电话:" + poi.tel);
s.push("经纬度:" + poi.location.pos + "");
s.push("<div>");
return s.join("<br>");
}
onUnmounted(() => {
container.value?.destroy();
});
</script>
<style scoped>
#container {
padding: 0px;
margin: 0px;
width: 100%;
position: fixed;
top: 0;
bottom: 0;
}
#loadingTip {
position: absolute;
z-index: 9999;
top: 0;
left: 0;
padding: 3px 10px;
background: red;
color: #fff;
font-size: 14px;
}
</style>
鼠标放上后效果如下
三 轨迹动画效果,实现从起点到终点
引入UI组件,绘制路线的经纬度
<template>
<div>
<div id="container"></div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import useMap from "@/hooks/useMap";
const { AMapItem, container } = useMap(
"container",
{
resizeEnable: true, //地图是否可旋转
terrain: true, //开启地形图
rotateEnable: true, //是否开启地图旋转交互 鼠标右键 + 鼠标画圈移动 或 键盘Ctrl + 鼠标左键画圈移动
pitchEnable: true, //是否开启地图倾斜交互 鼠标右键 + 鼠标上下移动或键盘Ctrl + 鼠标左键上下移动
zoom: 17, //初始化地图层级
zooms: [2, 30], //地图显示的缩放级别范围
center: [120.469626, 36.094172], //初始地图中心经纬度
isHotspot: true, //是否开启地图热点和标注的 hover 效果
},
(AMap) => {
console.log('AMapItem',AMap)
AMapUI.load(["ui/misc/PathSimplifier"], function (PathSimplifier) {
if (!PathSimplifier.supportCanvas) {
alert("当前环境不支持 Canvas!");
return;
}
initOrbitPage(PathSimplifier);
});
}
);
// 动画轨迹
function initOrbitPage(PathSimplifier) {
console.log('PathSimplifier',PathSimplifier)
//创建组件实例
var pathSimplifierIns = new PathSimplifier({
zIndex: 100,
map: container.value, //所属的地图实例
getPath: function (pathData, pathIndex) {
//返回轨迹数据中的节点坐标信息,[AMap.LngLat, AMap.LngLat...] 或者 [[lng|number,lat|number],...]
return pathData.path;
},
getHoverTitle: function (pathData, pathIndex, pointIndex) {
//返回鼠标悬停时显示的信息
if (pointIndex >= 0) {
//鼠标悬停在某个轨迹节点上
return (
pathData.name + ",点:" + pointIndex + "/" + pathData.path.length
);
}
//鼠标悬停在节点之间的连线上
return pathData.name + ",点数量" + pathData.path.length;
},
renderOptions: {
pathTolerance: 5,
//轨迹线的样式
pathLineStyle: {
strokeStyle: "red",
lineWidth: 5,
dirArrowStyle: true,
},
},
});
window.pathSimplifierIns = pathSimplifierIns;
//这里构建两条简单的轨迹,仅作示例
pathSimplifierIns.setData([
{
name: "青岛路线",
path: [
[120.454872, 36.081169],
[120.461817, 36.089068],
[120.467918, 36.091224],
[120.465185, 36.093182],
[120.454872, 36.107663],
[120.415399, 36.105078],
[120.433611, 36.088984],
[120.379194, 36.089942],
],
},
]);
//创建一个巡航器
var navg0 = pathSimplifierIns.createPathNavigator(
0, //关联第1条轨迹
{
loop: true, //循环播放
speed: 10000,
}
);
navg0.start();
}
onUnmounted(() => {
container.value?.destroy();
});
</script>
效果如下
四 实现点聚合效果, 覆盖物、几何计算的了解
以北京为例 滑动缩小到小区后点击一个地名 绘制他的半径为500m或者1000m圆
实现5公里,10公里范围内的圆形覆盖,并计算其面积和距离
<template>
<div>
<div id="container"></div>
<div class="input-card" style="width: 120px" v-if="circleInfo">
<button
class="btn"
@click="AMapItemGet(500)"
:class="AMapItemValue == 500 ? 'btnActive' : ''"
style="margin-bottom: 5px"
>
500m
</button>
<button
class="btn"
@click="AMapItemGet(1000)"
:class="AMapItemValue == 1000 ? 'btnActive' : ''"
>
1000m
</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import useMap from "@/hooks/useMap";
import { district } from "@/views/map/district.js";
import { points } from "@/views/map/community.js";
const AMapItemValue = ref(500);
const selectLatInfo =ref() //选择的经纬度
const circleInfo=ref()
const { AMapItem, container } = useMap(
"container",
{
resizeEnable: true, //地图是否可旋转
terrain: true, //开启地形图
viewMode: "3D", //地图模式
rotateEnable: true, //是否开启地图旋转交互 鼠标右键 + 鼠标画圈移动 或 键盘Ctrl + 鼠标左键画圈移动
pitchEnable: true, //是否开启地图倾斜交互 鼠标右键 + 鼠标上下移动或键盘Ctrl + 鼠标左键上下移动
zoom: 8, //初始化地图层级
// rotation: -15, //初始地图顺时针旋转的角度
zooms: [2, 30], //地图显示的缩放级别范围
center: [116.405285,39.904989], //初始地图中心经纬度
pitch: 70, //地图俯仰角度,有效范围 0 度- 83 度
isHotspot: true, //是否开启地图热点和标注的 hover 效果
},
(AMapItem) => {
AMapItem.plugin(
["AMap.IndexCluster"],
function () {
IndexClusterFun(AMapItem);
}
);
}
);
// 点聚合
function IndexClusterFun(AMap) {
var clusterIndexSet = {
city: {
minZoom: 2,
maxZoom: 10,
},
district: {
minZoom: 10,
maxZoom: 12,
},
area: {
minZoom: 12,
maxZoom: 15,
},
community: {
minZoom: 15,
maxZoom: 22,
},
};
// 聚合规则:通过定义的配置对数据内属性值相同的数据聚合到一起,比如:当地图级别为2到10的时候,将使用 city 属性聚合,数据中 city 值相同的将被聚合在一起。
var cluster = new AMap.IndexCluster(container.value, points, {
renderClusterMarker: _renderClusterMarker, //自定义聚合点样式
clusterIndexSet: clusterIndexSet, //聚合规则
});
console.log("cluster", cluster);
}
// 自定义聚合点样式
function _renderClusterMarker(context) {
console.log("_renderClusterMarker-context1111", context);
// 自定义点标记样式
var styleObj = getStyle(context);
var div = document.createElement("div");
div.className = "amap-cluster";
div.style.backgroundColor = styleObj.bgColor;
div.style.width = styleObj.size + "px";
if (styleObj.index <= 2) {
div.style.height = styleObj.size + "px";
// 自定义点击事件
context.marker.on("click", function (e) {
var curZoom = container.value.getZoom(); //获取当前地图缩放级别
if (curZoom < 20) {
curZoom += 3;
}
container.value.setZoomAndCenter(curZoom, e.lnglat);
});
} else if (context.index.mainKey == "community") {
context.marker.on("click", function (e) {
let lat = context.clusterData[0].lnglat.lat;
let lng = context.clusterData[0].lnglat.lng;
let data ={lat,lng}
selectLatInfo.value=data
clickCircle(data);
});
}
div.style.border = "solid 1px " + styleObj.borderColor;
div.style.borderRadius = styleObj.size + "px";
div.innerHTML = styleObj.text;
div.style.color = styleObj.color;
div.style.textAlign = styleObj.textAlign;
div.style.boxShadow = styleObj.boxShadow;
div.style.fontSize = styleObj.index==3?'15px': Number(styleObj.size/3) + "px";
context.marker.setContent(div); //设置点标记显示的自定义内容
// 自定义聚合点标记显示位置
var position = getPosition(context);
if (position) {
context.marker.setPosition(position);
}
// 设置覆盖物锚点设置点标记锚点。
context.marker.setAnchor("center");
}
function clickCircle(e) {
console.log("·································", e);
if (!e) return;
console.log(e.lat, e.lng);
//设置圆形位置
var center = new AMapItem.value.LngLat(e.lng, e.lat); //
//设置圆的半径大小
var radius = AMapItemValue.value
if(circleInfo.value){
container.value.remove(circleInfo.value)
}
//创建圆形 Circle 实例
var circle = new AMapItem.value.Circle({
center: center, //圆心
radius: radius, //半径
borderWeight: 3, //描边的宽度
strokeColor: "#FF33FF", //轮廓线颜色
strokeOpacity: 1, //轮廓线透明度
strokeWeight: 1, //轮廓线宽度
fillOpacity: 0.2, //圆形填充透明度
strokeStyle: "dashed", //轮廓线样式
strokeDasharray: [10, 10],
fillColor: "#1791fc", //圆形填充颜色
zIndex: 50, //圆形的叠加顺序
});
circleInfo.value=circle
//圆形 Circle 对象添加到 Map
console.log('circleInfo.value---circle',circle)
container.value.add(circle);
//根据覆盖物范围调整视野
// container.value.setFitView([ circle ])
}
function getStyle(context) {
var clusterData = context.clusterData; // 聚合中包含数据
var index = context.index; // 聚合的条件
var count = context.count; // 聚合中点的总数
var color = ["8,60,156", "66,130,198", "107,174,214", "78,200,211"];
var indexs = ["city", "district", "area", "community"];
var i = indexs.indexOf(index["mainKey"]);
var text = clusterData[0][index["mainKey"]];
var size = Math.round(30 + Math.pow(count / points.length, 1 / 5) * 70);
if (i==0){
text = '<span class="showNameCity">' + text + "</span>";
size = 60
} else if (i <= 2) {
text = '<span class="showName">' + text + "</span>";
size = 50
} else{
size =100
}
var style = {
bgColor: "rgba(" + color[i] + ",.8)",
borderColor: "rgba(" + color[i] + ",1)",
text: text,
size: size,
index: i,
color: "#ffffff",
textAlign: "center",
boxShadow: "0px 0px 5px rgba(0,0,0,0.8)",
};
return style;
}
function getPosition(context) {
var key = context.index.mainKey;
var dataItem = context.clusterData && context.clusterData[0];
var districtName = dataItem[key];
if (!district[districtName]) {
return null;
}
var center = district[districtName].center.split(",");
var centerLnglat = new AMapItem.value.LngLat(center[0], center[1]); //点标记的经纬度信息
console.log('center',center,centerLnglat)
return centerLnglat;
}
function AMapItemGet(e) {
AMapItemValue.value = e;
let opts = {
center: selectLatInfo.value, //圆心
radius: AMapItemValue.value, //半径
borderWeight: 3, //描边的宽度
strokeColor: "#FF33FF", //轮廓线颜色
strokeOpacity: 1, //轮廓线透明度
strokeWeight: 1, //轮廓线宽度
fillOpacity: 0.2, //圆形填充透明度
strokeStyle: "dashed", //轮廓线样式
strokeDasharray: [10, 10],
fillColor: "#1791fc", //圆形填充颜色
zIndex: 50, //圆形的叠加顺序
};
container.value.remove(circleInfo.value);
//创建圆形 Circle 实例
var circle = new AMapItem.value.Circle(opts);
circleInfo.value._opts.radius = AMapItemValue.value;
container.value.add(circleInfo.value);
container.value.setFitView([circle]);
}
onUnmounted(() => {
container.value?.destroy();
});
</script>
<style scoped>
#container {
padding: 0px;
margin: 0px;
width: 100%;
position: fixed;
top: 0;
bottom: 0;
}
#loadingTip {
position: absolute;
z-index: 9999;
top: 0;
left: 0;
padding: 3px 10px;
background: red;
color: #fff;
font-size: 14px;
}
.amap-cluster {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
font-size: 6px;
}
.showName {
font-size: 8px;
}
.showCount,
.showName {
display: block;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: 80%;
line-height: 40px;
}
.showNameCity{
width: 50px;
height: 50px;
line-height: 60px;
background-color: #25a5f7;
}
.input-card {
display: flex;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border-radius: 0.25rem;
width: 22rem;
border-width: 0;
border-radius: 0.4rem;
box-shadow: 0 2px 6px 0 rgba(114, 124, 245, 0.5);
position: fixed;
bottom: 1rem;
right: 1rem;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
padding: 0.75rem 1.25rem;
position: fixed;
z-index: 99;
}
.btn {
display: inline-block;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border: 1px solid transparent;
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out,
border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
background-color: transparent;
background-image: none;
color: #25a5f7;
border-color: #25a5f7;
padding: 0.25rem 0.5rem;
line-height: 1.5;
border-radius: 1rem;
-webkit-appearance: button;
cursor: pointer;
}
.btnActive {
background-color: #25a5f7;
color: #fff;
}
</style>
效果如下
使用数据如下
district.js
export const district = {
'北京': {
"adcode" : "110000",
"center" : "116.405285,39.904989",
// "center" : "117.939152,40.976204",
},
'亦庄开发区': {
"center": "116.506647,39.795337",
},
'密云区': {
"adcode" : "110118",
"center" : "116.843352,40.377362",
},
'怀柔区': {
"adcode" : "110116",
"center" : "116.637122,40.324272",
},
'门头沟区': {
"adcode" : "110109",
"center" : "116.105381,39.937183",
},
'顺义区': {
"adcode" : "110113",
"center" : "116.653525,40.128936",
},
'朝阳区': {
"adcode" : "110105",
"center" : "116.486409,39.921489",
},
'通州区': {
"adcode" : "110112",
"center" : "116.658603,39.902486",
},
'大兴区': {
"adcode" : "110115",
"center" : "116.338033,39.728908",
},
'昌平区': {
"adcode" : "110114",
"center" : "116.235906,40.218085",
},
'西城区': {
"adcode" : "110102",
"center" : "116.366794,39.915309",
},
'东城区': {
"adcode" : "110101",
"center" : "116.418757,39.917544",
},
'房山区': {
"adcode" : "110111",
"center" : "116.139157,39.735535",
},
'石景山区': {
"adcode" : "110107",
"center" : "116.195445,39.914601",
},
'海淀区': {
"adcode" : "110108",
"center" : "116.310316,39.956074",
},
'丰台区': {
"center": "116.286968,39.863642",
},
};