开发背景
入职这家公司之前,我是没有做过可视化项目的,最近可视化项目做的有点多,每一个可视化项目都不可避免地有一个地图,最初接手的可视化是Vue2
+ 高德地图Api
的,后来使用第三方可视化平台,而这个可视化平台使用的jsx语法
封装组件,况且地图还要收费,对于我这种白嫖党绝对不能忍受!所以使用jsx
又封装了一个地图组件,最近又有一个可视化项目,领导找我讨论继续用平台还是自己写,我义不容辞的选择了还是自己搞吧!然后技术选择了Vue3
+ TypeScript
+ 高德地图Api
,特来此分享一下整个实现流程。
NodeJS 版本使用的
16.18.0
项目构建
这里使用
vite
+vue3
+typescript
开发
创建项目
首先进入你的一个空目录,打开终端,执行npm create vite@latest
来创建项目。这里这个项目名就叫vue3-vite-gaode
。
这里项目名叫做vue3-vite-gaode
,framework选择vue
,语法选择TypeScript
。回车之后项目就创建好了。然后使用vscode
打开这个项目,你也可以选择别的编辑器。
定义组件
使用vscode
打开项目之后,执行下面命令安装依赖,然后跑起项目。
# 安装依赖,如果你安装依赖比较慢的话,可以选择淘宝镜像,或者用yarn
npm i
# 跑起项目
npm run dev
跑起来之后,我们就可以使用这个链接访问
接下来我们删除掉模板里不需要的东西
src/components/HelloWorld.vue
App.vue
里面的代码src/style.css
里面代码删除,加上body,html{ margin:0 }
去除body
和html
的默认margin
然后在src/components
里面定义我们的Map
组件,src/components/Map.vue
。
// src/components/Map.vue
<script setup lang="ts"></script>
<template></template>
<style scoped></style>
最后在App.vue
引入这个Map
。
// App.vue
<script setup lang="ts">
import Map from "@/components/Map.vue";
</script>
<template>
<Map />
</template>
<style scoped></style>
发现这里好像报错了!
这是TypeScript
给我们报的错,因为他不知道@
指向哪里,我们需要在tsconfig.json
里面加上如下配置,告诉他基准路径
和@指向哪里
就好了。
{
"compilerOptions":{
//...
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
}
}
}
可是vscode
的终端
仍然报错,同样vite
不知道我们@
指向哪里。
最后在vite.config.js
里面添加如下配置,但你发现path
又报错,原因是path
这个模块是使用js
编写的,而我们使用的是ts
,包必须有ts
的声明文件,这里需要再次执行npm install @types/node
来安装node
的ts
声明文件,为什么需要安装node
的ts
声明文件,因为path
是node
内置的。
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
// 配置路径别名
alias: {
'@': path.resolve(__dirname, './src'),
}
},
})
到这里我们的Map组件
已经定义好了,并在App.vue
引入了,只是Map组件
什么内容都没有。下一步我们将开始开发地图组件。
组件开发
第一阶段(显示地图)
首先肯定需要把地图能正常显示出来
- 安装依赖
因为这里使用的是高德地图Api
,所以先来安装依赖npm i @amap/amap-jsapi-loader
。
- 初始化地图
要想显示地图,肯定需要一个容器,这里就使用一个id=map
的div
。
<div id="map"></div>
并设置这个div
满屏
#map{
width:100vw;
height:100vh;
}
接下来在js
里面引入高德地图的AMapLoader
,引入Vue
的生命周期onMounted
和ref
。
<script setup lang="ts">
// 引入 onMounted 是因为地图一定是在页面挂载之后渲染,ref 为了定义响应式变量
import { onMounted, ref } from "vue";
import AMapLoader from "@amap/amap-jsapi-loader";
</script>
定义一个加载地图的方法,并在onMounted
生命周期中调用
<script setup lang="ts">
// ...
const loadAMap = () => {
AMapLoader.load({
key: "你的高德地图Key",
version: "2.0",
plugins: [],// 你所使用到的插件
}).then((AMap) => {
});
};
onMounted(() => {
loadAMap();
})
</script>
定义一个地图实例mapInstance
,并在地图加载完成之后
初始化地图
<script setup lang="ts">
// ...
const mapInstance = ref(null);
// 初始化地图
const initMapInstance = (AMap) => {
// 生成地图的参数,具体可详见高德api文档
const option:{[key:string]:any} = {};
mapInstance.value = new AMap.Map("map", option);
};
// 在loadMap方法的then里面调用初始化地图
const loadAMap = () => {
AMapLoader.load({
key: "你的高德地图Key",
version: "你的高德地图版本",
plugins: [],// 你所使用到的插件
}).then((AMap) => {
initMapInstance(AMap);
});
};
</script>
这时地图就已经正常显示了。
第二阶段(显示指定区域的地图)
需求是实现暗色系,西安市的地图
主要的操作都是在new AMap.Map
的options
参数中处理
处理地图样式
这里我们定义一个props,用来扩展后面的参数
const props = defineProps({
// 地图样式,需要实现暗色系,默认值就给成暗色系
mapStyle: { type: String, default: "amap://styles/darkblue" },
})
// ...
// 在options中读取这个mapStyle
options = {
mapStyle:props.mapStyle
}
暗色系地图就实现了,至于为什么是这个值,可以参考官方文档高德地图官方API之地图主题这里面也提供了自定义地图主题方法。
仅显示西安市地图
这里需要给props
扩展属性,将我们的加载地图时的ke
y和versio
n等写到props
里面,定义为apiConfig
,
同时扩展plugins
,定义一个属性名areaName
,值为需要显示的区域名,这里默认为西安市
,增加实例化AMap.DistrictSearch
对象是需要的属性(level
、extensions
、subdistrict
)
// ...
const props = defineProps({
// 地图样式,需要实现暗色系,默认值就给成暗色系
mapStyle: { type: String, default: "amap://styles/darkblue" },
// 地图配置
apiConfig:{ type: Object, default:() => ({
version:"2.0",
key:"你所申请的高德key",
plugins:["AMap.DistrictSearch"]
}) },
// 区域名
areaName:{ type:String, default:"西安市" },
// 显示下级行政区级数,行政区级别包括:国家、省/直辖市、市、区/县4个级别
subdistrict:{ type:Number, default:0 },
// 是否返回行政区边界坐标点 all / base
extensions:{ type:String,default:"all" },
// 搜索范围[对应文档https://lbs.amap.com/api/javascript-api/reference/search#m_AMap.DistrictSearch]
level:{ type:String,default:"city" }
})
将加载地图时写死的key
和version
改为读取props
属性
// ...
AMapLoader.load({
key:props.apiConfig.key,
version:props.apiConfig.version,
plugins:props.apiConfig.plugins
}).then(AMap => {
initMapInstance(AMap);
})
改写初始化地图方法,需要先获取区域的边界坐标点,然后再初始化地图。
const initMapInstance = (AMap:any) => {
const options:{
[key:string]:any
} = {
mapStyle:props.mapStyle,
};
// 初始化district对象
const district = new AMap.DistrictSearch({
level:props.level,
extensions:props.extensions,
subdistrict:props.subdistrict,
});
// 搜索区域
district.search(props.areaName, function (status, result) {
const bounds = result.districtList[0]["boundaries"];
// 获取区域各坐标
const mask = [];
for (let i = 0; i < bounds.length; i += 1) {
mask.push([bounds[i]]);
}
// options中设置mask,超出mask的区域就不显示
options.mask = mask;
mapInstance.value = new AMap.Map("map", options);
})
};
这时地图就已经只显示西安市的了
但是有个问题我相信大家已经发现了,这个网格背景怎么去掉?
只需要在css中加入这么一行代码就去掉了背景网格。
.amap-container{
background-image: unset;
}
这个时候就成功只显示了西安市的地图
但是总感觉有点奇怪,现在我们来给这个地图加个描边。
在props
里面添加一个边界配置属性polylineConfig
。
// ...
const props = defineProps({
// ...
polylineConfig: {
type: Object,
default: () => ({
// 是否显示边界线
show: true,
// 是否显示边界以外的区域
showOuter: false,
// 边界线条颜色
strokeColor: "#99ffff",
// 边界线条粗细
strokeWeight: 4,
}),
},
})
同样再改写一下初始化地图方法
// 新增一个渲染边界的方法
const renderPolyLine = (bounds = []) => {
const { polylineConfig } = props;
if (polylineConfig.show) {
for (let i = 0; i < bounds.length; i++) {
new AMap.Polyline({
path: bounds[i],
strokeColor: polylineConfig.strokeColor,
strokeWeight: polylineConfig.strokeWeight,
map: mapInstance.value,
});
}
}
};
// 初始化地图
const initMapInstance = (AMap:any) => {
const options:{
[key:string]:any
} = {
mapStyle:props.mapStyle,
};
// 初始化district对象
const district = new AMap.DistrictSearch({
level:props.level,
extensions:props.extensions,
subdistrict:props.subdistrict,
});
// 搜索区域
district.search(props.areaName, function (status, result) {
const bounds = result.districtList[0]["boundaries"];
// 获取区域各坐标
const mask = [];
for (let i = 0; i < bounds.length; i += 1) {
mask.push([bounds[i]]);
}
// 不显示区域外位置
if (!props.polylineConfig.showOuter) {
options.mask = mask;
}
mapInstance.value = new AMap.Map("map", options);
// 渲染边界
renderPolyLine(bounds);
})
};
这个时候我们地图就好看多了
到这里为止,我们地图只显示指定区域就结束了,后面我们将逐步扩展,使得地图更加丰富。
第三阶段(新增需求)
需求一
- 支持鼠标双击放大缩小
- 支持设置中心点
- 支持设置缩放范围和初始缩放等级
别看这么多需求,每个都只有一行代码。
先在props
中将这些属性都纷纷配置。
const props = defineProps({
// ...
// 地图是否支持双击鼠标放大
doubleClickZoom: { type: Boolean, default: true },
// 中心点坐标
center: { type: Array, default: () => [108.939677,34.3432] },
// 初始地图缩放等级
zoom: { type: Number, default: 10 },
// 地图显示的缩放级别范围
zooms: { type: Array, default: () => [3, 18] },
})
然后在初始化地图时加入到options
中
const initMapInstance = (AMap:any) => {
const options:{
[key:string]:any
} = {
mapStyle:props.mapStyle,
doubleClickZoom:props.doubleClickZoom,
center:props.center,
zoom:props.zoom,
zooms:props.zooms
};
// ...
};
就这!上述需求都已实现!
需求二
- 展示卫星地图
- 展示卫星路网
同样我们先在props
里面配置是否展示卫星地图isShowSatellite
和是否展示卫星路网isShowRoadNet
。
const props = defineProps({
// ...
// 是否展示卫星地图
isShowSatellite: { type: Boolean, default: true },
// 是否展示卫星路网
isShowRoadNet: { type: Boolean, default: true },
})
修改初始化地图方法
const initMapInstance = (AMap:any) => {
const options:{
[key:string]:any
} = {
mapStyle:props.mapStyle,
doubleClickZoom:props.doubleClickZoom,
center:props.center,
zoom:props.zoom,
zooms:props.zooms,
// 图层,卫星地图,卫星路网都属于图层,push到这个layers就可以了
layers:[]
};
// 展示卫星图层
if (props.isShowSatellite) {
option.layers.push(new AMap.TileLayer.Satellite());
}
// 展示路网图层
if (props.isShowRoadNet) {
option.layers.push(new AMap.TileLayer.RoadNet());
}
// ...
};
卫星路网,卫星地图也都正常显示了
但是这种地图我觉得不好看,经过一番辩论之后,还是在props
中将他设为false
,换回正常的地图。
需求三
- 要实现3D效果
- 并且可以调整俯视角度
- 还要有地图方位控制器
继续在props
中添加属性
const props = defineProps({
// 这里用到了地图方位控制器插件,所以需要在apiConfig的plugins中引入
apiConfig:{ type: Object, default:() => ({
version:"2.0",
key:"你所申请的高德key",
plugins:["AMap.DistrictSearch","AMap.ControlBar"]
}) },
// ...
// 是否3D显示
isShow3D: { type: Boolean, default: true },
// 俯视角度
pitch: { type: Number, default:40 },
// 地图方位控制器配置
controllBarConfig: {
type: Object,
default: () => ({
// 是否显示方位控制器
show: true,
// 是否显示缩放按钮
showZoomBar: true,
// 是否显示倾斜、旋转按钮
showControlButton: true,
// 距离顶部的距离
positionTop: 10,
// 距离右侧的距离
positionRight: 10,
}),
},
// 3d墙体配置
object3dWallConfig: {
type: Object,
default: () => ({
// 是否显示3d墙体
show: true,
// 层级
zIndex: 1,
// 墙高
wallHeight: -4000,
// 墙体颜色
color: "#0088ffcc",
// 是否使用了透明颜色,并进行颜色混合
transparent: true,
// 控制显示正反面,both,front,back
backOrFront: "both",
}),
},
})
老样子,继续在地图初始化函数中处理,但是3D墙体
只有在1.4.15
的低版本API
中才有,不得已又把props.apiConfig.version
换回了1.4.15
。
// 定义一个渲染3d墙体的方法
const render3dWall = (bounds = []) => {
const { object3dWallConfig, apiConfig } = props;
// 1.4.15版本的api通过Object3DLayer创建墙体
if (apiConfig.version == "1.4.15") {
if (object3dWallConfig.show) {
// 定义一个3D图层
const object3Dlayer = new AMap.Object3DLayer({
zIndex: object3dWallConfig.zIndex,
});
// 创建墙体
const wall = new AMap.Object3D.Wall({
path: bounds,
height: object3dWallConfig.wallHeight,
color: object3dWallConfig.color,
});
wall.transparent = object3dWallConfig.transparent;
wall.backOrFront = object3dWallConfig.backOrFront;
object3Dlayer.add(wall);
mapInstance.value.add(object3Dlayer);
}
} else if (apiConfig.version == "2.0") {
// 2.0版本的api通过描边添加墙体
for (let i = 0; i < bounds.length; i += 1) {
new AMap.Polyline({
path: bounds[i],
strokeColor: object3dWallConfig.color,
strokeWeight: object3dWallConfig.wallHeight,
map: mapInstance.value,
});
}
}
};
// 定义一个渲染地图方位控制器
const renderControlBar = () => {
const { controllBarConfig } = props;
if (controllBarConfig.show) {
mapInstance.value.addControl(
new AMap.ControlBar({
showZoomBar: controllBarConfig.showZoomBar,
showControlButton: controllBarConfig.showControlButton,
position: {
right: `${controllBarConfig.positionRight}px`,
top: `${controllBarConfig.positionTop}px`,
},
}),
);
}
};
// 初始化地图
const initMapInstance = (AMap:any) => {
const options:{
[key:string]:any
} = {
mapStyle:props.mapStyle,
doubleClickZoom:props.doubleClickZoom,
center:props.center,
zoom:props.zoom,
zooms:props.zooms,
// 图层,卫星地图,卫星路网都属于图层,push到这个layers就可以了
layers:[],
// 俯仰角度,默认0,[0,83],2D地图下无效
pitch: props.pitch,
};
// 3D显示,地图风格,3D还是2D
if (props.isShow3D) {
options.viewMode = "3D";
}
// ...
// 添加3D墙体 高德地图api1.4.15生效
render3dWall(bounds);
// 添加地图方位控制器
renderControlBar();
};
又加了这一坨屎山,终于完成了这个需求。
需求四
- 绘制线条
要想实现绘制线条,我们首先得有线条数据,而每一条线条数据都是由一堆点位集合组成,我这里事先准备西安市的一些点位。
// 第二维代表线 第三维代表点
[
[
[108.771259,34.210635],
[108.827026,34.190093],
[108.880493,34.199648]
],
[
[108.895441,34.241677],
[108.951208,34.230217]
]
]
然后我们定义一个渲染线条的方法,将线条数据循环添加到地图上。
// 渲染线条
const renderLine = () => {
const polyLineData = [
[
[108.771259,34.210635],
[108.827026,34.190093],
[108.880493,34.199648]
],
[
[108.895441,34.241677],
[108.951208,34.230217]
]
];
const polyLines = [];
for (let i = 0; i < polyLineData.length; i++) {
const polyline = new AMap.Polyline({
// 线条坐标
path: polyLineData[i],
});
polyLines.push(polyline);
}
mapInstance.value.add(polyLines);
};
然后继续在initMapInstance
方法中去调用渲染线条的方法。
// 初始化地图
const initMapInstance = (AMap:any) => {
// ...
renderLine()
};
此时我们的线条就已经有了。
但是这也太不明显了吧,我们还要实现线条的样式控制。
这时我们就要考虑将他加入到props
里了,首先接收一个布尔类型isDrawPolyLine
用来控制是否绘制线条,然后接收一个数组polyLineData
,数组的每一项都是一条线,数组里面的path
指的是这条线由哪些点连成,点的顺序不同线条也就不同,其他属性都是线条的样式控制。
const props = defineProps({
// ...
// 是否绘制线条
isDrawPolyLine:{ type:Boolean,default:true },
// 线条数据
polyLineData: {
type: Array,
default: () => [
{
path: [
[108.771259,34.210635],
[108.827026,34.190093],
[108.880493,34.199648]
],
// 是否显示描边
isOutline: true,
// 线条描边颜色
outlineColor: "#ffeeff",
// 描边的宽度
borderWeight: 3,
// 线条颜色
strokeColor: "#3366FF",
// 线条透明度,取值范围[0,1],默认0.9
strokeOpacity: 1,
// 线条宽度
strokeWeight: 6,
// 线条样式,实线:solid,虚线:dashed
strokeStyle: "solid",
// 勾勒形状轮廓的虚线和间隙的样式
strokeDasharray: [10, 5],
// 折线拐点的绘制样式,默认值为'miter'尖角,其他可选值:'round'圆角、'bevel'斜角
lineJoin: "round",
// 折线两端线帽的绘制样式,默认值为'butt'无头,其他可选值:'round'圆头、'square'方头
lineCap: "round",
// 折线覆盖物的叠加顺序
zIndex: 50,
},
{
path:[
[108.895441,34.241677],
[108.951208,34.230217]
],
// 是否显示描边
isOutline: true,
// 线条描边颜色
outlineColor: "#ffeeff",
// 描边的宽度
borderWeight: 3,
// 线条颜色
strokeColor: "#3366FF",
// 线条透明度,取值范围[0,1],默认0.9
strokeOpacity: 1,
// 线条宽度
strokeWeight: 6,
// 线条样式,实线:solid,虚线:dashed
strokeStyle: "solid",
// 勾勒形状轮廓的虚线和间隙的样式
strokeDasharray: [10, 5],
// 折线拐点的绘制样式,默认值为'miter'尖角,其他可选值:'round'圆角、'bevel'斜角
lineJoin: "round",
// 折线两端线帽的绘制样式,默认值为'butt'无头,其他可选值:'round'圆头、'square'方头
lineCap: "round",
// 折线覆盖物的叠加顺序
zIndex: 50,
},
],
}
})
接下来我们修改一下渲染线条的方法。
const renderLine = () => {
const { isDrawPolyLine, polyLineData }:any = props;
if (isDrawPolyLine) {
const polyLines = [];
for (let i = 0; i < polyLineData.length; i++) {
const polyline = new AMap.Polyline({
// 线条坐标
path: polyLineData[i].path,
// 是否显示外线
isOutline: polyLineData[i].isOutline,
// 外线颜色
outlineColor: polyLineData[i].outlineColor,
// 外线宽度
borderWeight: polyLineData[i].borderWeight,
// 线条颜色
strokeColor: polyLineData[i].strokeColor,
// 线条透明度
strokeOpacity: polyLineData[i].strokeOpacity,
// 线条宽度
strokeWeight: polyLineData[i].strokeWeight,
// 线条样式,实线:solid,虚线:dashed
strokeStyle: polyLineData[i].strokeStyle,
// 勾勒形状轮廓的虚线和间隙的样式
strokeDasharray: polyLineData[i].strokeDasharray,
// 折线拐点的绘制样式
lineJoin: polyLineData[i].lineJoin,
// 折线两端线帽的绘制样式
lineCap: polyLineData[i].lineCap,
// 折线覆盖物的叠加顺序
zIndex: polyLineData[i].zIndex,
});
polyLines.push(polyline);
}
mapInstance.value.add(polyLines);
}
};
这时地图上的线条就这样了,emmm…好像有点丑,但我的审美好像就到这了,读者可以自行优化。
需求五
- 增加点位
有了线条,怎么少得了点位呢?
首先,在props
里面加入是否渲染点位的布尔值和点位数据。
const props = defineProps({
// 是否绘制点位
isDrawPoint: { type: Boolean, default: true },
// 点位数据
pointData: {
type: Array,
default: () => [
{
// 唯一值
iden: "点位1",
// 坐标
lngLat: [108.979378,34.221143],
// marker点位基于坐标的偏移量
offset: [-13, -30],
// 自定义图标(Object可设置精灵图定位,String为图标地址)
icon: {
// 图标大小
size: [25, 34],
// 图标地址
image:
"//a.amap.com/jsapi_demos/static/demo-center/icons/dir-marker.png",
// 图标所用的图片大小
imageSize: [135, 40],
// 图标取图偏移量(背景图定位)
imageOffset: [-9, -3],
},
},
{
// 唯一值
iden: "点位2",
// 坐标
lngLat: [108.896591,34.255523],
// marker点位基于坐标的偏移量
offset: [-13, -30],
// 自定义图标(Object可设置精灵图定位,String为图标地址)
icon: "//a.amap.com/jsapi_demos/static/demo-center/icons/dir-via-marker.png",
},
{
// 唯一值
iden: "点位3",
// 坐标
lngLat: [109.065616,34.328056],
// marker点位基于坐标的偏移量
offset: [-13, -30],
// 自定义图标(Object可设置精灵图定位,String为图标地址)
icon: {
// 图标大小
size: [25, 34],
// 图标地址
image:
"//a.amap.com/jsapi_demos/static/demo-center/icons/dir-marker.png",
// 图标所用的图片大小
imageSize: [135, 40],
// 图标取图偏移量(背景图定位)
imageOffset: [-95, -3],
},
},
],
}
})
然后继续定义一个渲染点位的方法,每一个点位其实就是高德里面的一个Maker
对象,通常,点位是可以被点击的,也就给所有的Maker
添加点击事件,这里就不细讲了,可以拿到当前Maker
的数据,通过Vue
的emit
传给父组件,点击事件在父组件去处理。
// 渲染点位
const renderPoint = () => {
const { isDrawPoint, pointData }:any = props;
if (isDrawPoint) {
const makers = [];
for (let i = 0; i < pointData.length; i++) {
// 定义图标
let icon = pointData[i].icon;
if (typeof pointData[i].icon !== "string") {
icon = new AMap.Icon({
// 图标尺寸
size: new AMap.Size(...pointData[i].icon.size),
// 图标的取图地址
image: pointData[i].icon.image,
// 图标所用图片大小
imageSize: new AMap.Size(...pointData[i].icon.imageSize),
// 图标取图偏移量
imageOffset: new AMap.Pixel(...pointData[i].icon.imageOffset),
});
}
// 定义maker
const maker = new AMap.Marker({
position: new AMap.LngLat(...pointData[i].lngLat),
offset: new AMap.Pixel(...pointData[i].offset),
icon,
});
// 点位添加点击事件
maker.on("click", function () {
alert(pointData[i])
});
// 添加maker
makers.push(maker);
}
// 添加点位到地图
mapInstance.value.add(makers);
}
};
最后在初始化地图之后再次调用。
const initMapInstance = (AMap:any) => {
//...
// 渲染点位
renderPoint();
}
当然,这个点位的图标你是可以自行配置的,可以让你们的UI
设计一些图标供你使用,或者在网上找一些图标都是可以的,我这里用的是高德官方的图标。
需求六
- 增加脉冲线
先把之前的点位和线条关掉。
然后再次在props
里面添加属性,脉冲线的配置和数据。脉冲线我们需要用到高德地图的LOCAL数据可视化API
,所以需要加上LOCAL
的配置,其次还有脉冲线的数据及样式配置。
const props = defineProps({
// 地图Loca配置
locaConfig: {
type: Object,
default: () => ({
// 是否展示
show: true,
// 资源类型 url(geoJson地址) data(geoJson数据)
sourceType: "data",
// 缓冲线脚本版本号,目前是基于2.0开发的
version: "2.0",
}),
},
// 脉冲线数据
locaData: {
type: Object,
default: () => ({
// 当脉冲线sourceType为url时必传
geoJsonUrl: "",
// 当脉冲线sourceType为data时必传
geoJsonData: {
type: "FeatureCollection",
features: [
{
type: "Feature",
// id保持唯一,如果用其他平台获取geoJson数据的话自带有id
id: 3657,
// properties里面的属性可以自由扩展
properties: { _draw_type: "line" },
geometry: {
type: "LineString",
// 脉冲线由哪些点位坐标组成
coordinates: [
[108.735039,34.16429],
[108.929361,34.120309],
[109.085738,34.179582],
[108.927636,34.217322],
],
},
// 脉冲头和脉冲尾的坐标
bbox: [108.735039,34.16429, 108.927636,34.217322],
},
{
type: "Feature",
id: 4901,
properties: { _draw_type: "line" },
geometry: {
type: "LineString",
coordinates: [
[108.769534,34.286071],
[108.979953,34.298001],
[109.121958,34.187705],
[108.894866,34.20968],
],
},
bbox: [108.769534,34.286071, 108.894866,34.20968],
},
],
},
// 脉冲线图层样式
globalStyle: {
// 图层显示层级
zIndex: 10,
// 图层整体透明度
opacity: 1,
// 图层是否可见
visible: true,
// 图层缩放等级[0-20]
zooms: [2, 22],
},
// 脉冲线样式
layerStyle: {
// 线整体海拔高度,Number
altitude: 0,
// 脉冲线的宽度
lineWidth: 10,
// 脉冲头颜色
headColor: "rgba(227,43,43,0.4)",
// 脉冲尾颜色
trailColor: "rgba(0,0,0, 0)",
// 脉冲长度,0.25 表示一段脉冲占整条路的 1/4
interval: 0.75,
// 脉冲线的速度,几秒钟跑完整段路
duration: 2000,
},
}),
},
})
然后我们需要定义一个变量,用来初始化LOCAL
。
const locaInstance = ref(null);
在加载地图时也要加载Loca
。
const loadAMap = () => {
AMapLoader.load({
key:props.apiConfig.key,
version:props.apiConfig.version,
plugins:props.apiConfig.plugins,
// 加载Loca脉冲线才有效果
Loca: {
version: props.locaConfig.version,
},
}).then(AMap => {
initMapInstance(AMap);
})
}
定义一个渲染脉冲线的方法。
// 渲染脉冲线
const renderLoca = () => {
const { locaConfig, locaData } = props;
// 未开启脉冲线
if (!locaConfig.show) return;
// 初始化脉冲线容器
locaInstance.value = new Loca.Container({
map: mapInstance.value,
});
// 获取geoJson数据
const sourceParams = {};
// sourceType与data.locaData的key的映射关系
const sourceTypeToDataKey = {
url: "geoJsonUrl",
data: "geoJsonData",
};
sourceParams[locaConfig.sourceType] =
locaData[sourceTypeToDataKey[locaConfig.sourceType]];
// 读取指定资源
const geo = new Loca.GeoJSONSource(sourceParams);
// 添加脉冲线图层
const layer = new Loca.PulseLineLayer({
loca: locaInstance.value,
// 图层显示层级
zIndex: locaData.globalStyle.zIndex,
// 图层整体透明度
opacity: locaData.globalStyle.opacity,
// 图层是否可见
visible: locaData.globalStyle.visible,
// 图层缩放等级[0-20]
zooms: locaData.globalStyle.zooms,
});
// 将geoJson数据加载给脉冲线图层
layer.setSource(geo);
// 设置脉冲线样式
layer.setStyle({
// 线整体海拔高度,Number
altitude: locaData.layerStyle.altitude,
// 脉冲线的宽度
lineWidth: locaData.layerStyle.lineWidth,
// 脉冲头颜色locaData.layerStyle.headColor
headColor: "#ff0000",
// 脉冲尾颜色
trailColor:"#0099ff",
// 脉冲头和脉冲尾的值可以是个回调函数,回调函数里面的第二个参数就能拿到你的geoJson数据项,可以根据里面的唯一值来取对应的颜色
// trailColor: (_, feature) =>
feature.properties.type
? DeviceTypeToLocaLayerTrailColor[feature.properties.type]
: locaData.layerStyle.trailColor,
// 脉冲长度,0.25 表示一段脉冲占整条路的 1/4
interval: locaData.layerStyle.interval,
// 脉冲线的速度,几秒钟跑完整段路
duration: locaData.layerStyle.duration,
});
// 添加脉冲线图层到脉冲线容器
locaInstance.value.add(layer);
// 开始动画
locaInstance.value.animate.start();
};
最后将这个方法在地图初始化initMapInstance
完成之后调用。
const initMapInstance = (AMap:any) => {
//...
// 渲染脉冲线
renderLoca();
}
最后效果就是这样子,线条的点位顺序决定了脉冲线动画走向。这里都是随机点的几个点位。
需求七
- 增加预警点位
继续在props
里面加入是否展示预警点位isShowPointWarning
和预警点位集合warningList
。
const props = defineProps({
// 是否显示预警点位动画
isShowPointWarning: { type: Boolean, default: true },
// 预警点位集合
warningList: {
type: Array,
default: () => [[108.777008,34.216845]],
},
})
然后定义一个渲染预警点位的方法
// 引入红色预警的图片
import breathRedPng from "./assets/breath_red.png";
//处理动画点位所需的json
const aniPointJsonData = () => {
const features = props.warningList.map((coordinates) => ({
type: "Feature",
geometry: { type: "Point", coordinates },
}));
return {
data: { type: "FeatureCollection", features },
};
};
// 渲染动态动画点位
const renderAniPoint = () => {
const { isShowPointWarning } = props;
// 红色呼吸点
if (isShowPointWarning) {
const geoLevelF = new Loca.GeoJSONSource(aniPointJsonData());
const breathRed = new Loca.ScatterLayer({
loca: locaInstance.value,
// 图层显示层级
zIndex: 113,
// 图层整体透明度
opacity: 1,
// 图层是否可见
visible: true,
// 图层缩放等级范围
zooms: [2, 22],
});
breathRed.setSource(geoLevelF);
breathRed.setStyle({
// size 和 borderWidth 的单位,可以是 'px' 和 'meter',meter 是实际地理的米,px 是屏幕像素。
unit: "meter",
// 图标长宽,单位取决于 unit 字段。
size: [4000, 4000],
// 图标纹理资源
texture: breathRedPng,
// 一轮动画的时长,单位毫秒(ms)
duration: 500,
// 是否有动画
animate: true,
});
// 启动渲染动画
locaInstance.value.animate.start();
}
};
最后将这个方法在地图初始化initMapInstance
完成之后调用,需要注意的是,同样依赖于LOCAL
,如果你前面没有做脉冲线的话,需要先实例化LOCAL
再使用预警点位的api。
const initMapInstance = (AMap:any) => {
//...
// 渲染预警点位
renderAniPoint();
}
最后效果是这样
结语
历时两周(周末),完成这篇文章,整个Demo也是自己独立完成,希望这篇文章能让你学会使用高德API去做一些好玩的东西,如果能够帮助到你,我自然也很开心,最后希望大家一起进步。如果不嫌弃可以稍微点个赞!!!
稍后我会将这个地图组件的源码开源到Gitee
,你只需要换上你自己的高德Key
,并传入你需要的参数即可使用,示例中的参数值数据什么的都是放在props
的default
中,你可以自行删除!!!
原文链接:https://juejin.cn/post/7211744771948740668 作者:webxue