百度地图开放平台 https://lbs.baidu.com/index.php?title=%E9%A6%96%E9%A1%B5
javaScript API https://lbs.baidu.com/index.php?title=jspopularGL
百度地图实例 https://lbsyun.baidu.com/index.php?title=open/jsdemo
Vue Baidu Map文档 https://dafrok.github.io/vue-baidu-map/#/zh/index
效果图:
支持放大缩小 调动播放进度 搜索轨迹时段 调整播放速度等。
首先,你需要在百度地图开放平台注册一个开发者账号,并创建一个应用,以获取API密钥。API密钥是用于标识你的应用身份的重要凭证。
在项目的public/index.html文件中引入百度地图API和样式表:
<script type="text/javascript" src="http://api.map.baidu.com/api?v=3.0&ak=换成自己的API秘钥"></script>
<script type="text/javascript" src="https://api.map.baidu.com/library/TextIconOverlay/1.2/src/TextIconOverlay_min.js"></script>
<script type="text/javascript" src="https://api.map.baidu.com/library/MarkerClusterer/1.2/src/MarkerClusterer_min.js"></script>
API | 描述 | 使用示例 |
---|---|---|
BMap.Map(container [, opts]) | 创建地图实例 | new BMap.Map(“map-container”); |
BMap.Point(lng, lat) | 创建地理坐标点实例 | new BMap.Point(114.161291, 22.644619); |
BMap.Marker(point [, opts]) | 创建标记点实例 | new BMap.Marker(point); |
BMap.Icon(url, size [, opts]) | 创建标记点图标实例 | new BMap.Icon(“marker.png”, new BMap.Size(30, 30)); |
BMap.Polyline(points [, opts]) | 创建折线实例 | new BMap.Polyline([point1, point2]); |
BMap.InfoWindow(content [, opts]) | 创建信息窗口实例 | new BMap.InfoWindow(“内容”); |
BMap.NavigationControl(opts) | 创建地图缩放控件实例 | new BMap.NavigationControl(); |
BMapLib.MarkerClusterer(map, opts) | 创建点聚合实例 | new BMapLib.MarkerClusterer(map, { markers: markers }); |
map.centerAndZoom(center, zoom) | 设置地图中心点和缩放级别 | map.centerAndZoom(point, 15); |
map.addOverlay(overlay) | 添加覆盖物到地图 | map.addOverlay(marker); |
map.clearOverlays() | 清除地图上的所有覆盖物 | map.clearOverlays(); |
map.enableScrollWheelZoom() | 启用鼠标滚轮缩放 | map.enableScrollWheelZoom(); |
map.getDistance(point1, point2) | 计算两点间的直线距离 | map.getDistance(point1, point2); |
marker.addEventListener(type, handler) | 为标记点添加事件监听器 | marker.addEventListener(“click”, function() { console.log(“点击标记点”); }); |
polyline.getPath() | 获取折线的路径 | polyline.getPath(); |
InfoWindow.setContent(content) | 设置信息窗口的内容 | infoWindow.setContent(“新内容”); |
注意:
以上是一些基础的API,百度地图提供了丰富的功能,可以根据实际需求查阅百度地图JavaScript API官方文档获取更多详细信息。
模板部分:
<div ref="map" class="map-container"></div>:地图容器。
<a-modal>:断油电操作的模态框,包括确认按钮和输入备注的文本框。
<a-modal>:恢复油电操作的模态框,同样包括确认按钮和输入备注的文本框。
<a-modal>:轨迹回放的模态框,包含了子组件 <run-map> 来展示实际的轨迹。
数据:
map:百度地图实例。
markers:存储标记的数组。
infoWindows:存储信息窗口的数组。
markerCluster:MarkerClusterer 实例,用于标记点聚合。
devices:设备信息数据。
modalBreak和modalRestore:控制断油电和恢复油电的模态框显示。
modaldrivingTrajectory:控制轨迹回放的模态框显示。
url:包含一些后端接口的URL。
deviceNum、optRemark、numberplate:一些操作所需的参数。
方法:
initMap():初始化地图,设置中心点和缩放级别,添加控件,并加载地图标点数据。
loadRandomDevices():加载地图标点数据,清除之前的标记、信息窗口和点聚合,添加新的标记、信息窗口和点聚合。
createPopupContent(point):根据设备信息创建信息窗口内容。
handleBreakOk()和handleRestoreOk():处理断油电和恢复油电的确认事件。
cancellation():关闭轨迹回放的模态框。
<template>
<div class="parent-container">
<div ref="map" class="map-container"></div>
<!-- <button @click="loadRandomDevices">加载随机设备</button> -->
<a-modal v-model="modalBreak" title="断油电" @ok="handleBreakOk">
<div style="font-size: 16px; font-weight: bolder;"><img src="../../assets/jingao.png" width="40px"
height="40px"></img>您确定要下发<span style="font-size: 18px; color: red;"> 断油电</span> 指令吗?</div>
<!-- <a-radio-group v-model="triggerType" style="margin-top: 30px;"> -->
<!-- <a-radio :value="合同未交款断油电">合同未交款断油电</a-radio>
<a-radio :value="违竟断油电">违竟断油电</a-radio>
<a-radio :value="其他">其他:</a-radio> -->
<!-- </a-radio-group> -->
<a-input v-model="optRemark" placeholder="请输入备注" style="margin-top: 30px;">
</a-input>
</a-modal>
<a-modal v-model="modalRestore" title="恢复油电" @ok="handleRestoreOk">
<div style="font-size: 16px; font-weight: bolder;"><img src="../../assets/jingao.png" width="40px"
height="40px"></img>您确定要执行<span style="font-size: 18px; color: red;"> 恢复油电</span> 指令吗?</div>
<a-input v-model="optRemark" placeholder="请输入备注" style="margin-top: 30px;">
</a-input>
</a-modal>
<a-modal width="100%" height="100%" v-model="modaldrivingTrajectory" title="轨迹回放" >
<run-map :deviceNum="deviceNum"></run-map>
<template slot="footer">
<a-button @click="cancellation">关闭</a-button>
</template>
</a-modal>
</div>
</template>
<script>
import Vue from 'vue'
import {
getAction,
httpAction,
postAction
} from '../../api/manage'
import RunMap from './GjMap.vue'
export default {
props: {
gpsInfo: {
type: Object,
}
},
components: {
RunMap
},
data() {
return {
map: null,
markers: [], // 存储标记
infoWindows: [], // 存储信息窗口
markerCluster: null, // MarkerClusterer 实例
devices: [],
modalBreak: false, // 模态框可见状态
modaldrivingTrajectory:false,//轨迹回放
modalRestore: false,
url: {
//断油电 恢复油电
executeCmd: '/jeecg-customers/car/carCheXiaoGpsController/executeCmd',
//获取轨迹url
trajectory: '/jeecg-customers/ car/carCheXiaoGpsController/trajectory',
},
//设备编号
deviceNum: null,
optRemark: null,
numberplate: null,
};
},
mounted() {
this.initMap();
},
methods: {
initMap() {
// 创建地图实例
this.map = new BMap.Map(this.$refs.map);
// 设置地图中心点和缩放级别
const point = new BMap.Point(114.16129136801659, 22.64461948509109);
this.map.centerAndZoom(point, 10);
// 添加地图缩放控件
const navigationControl = new BMap.NavigationControl();
this.map.addControl(navigationControl);
// 启用鼠标滚轮缩放
this.map.enableScrollWheelZoom();
//加载地图点数据
this.loadRandomDevices();
},
createPopupContent(point) {
// 创建信息窗口内容,包括设备属性
return `
设备信息
设备号: ${point.deviceNum}
车牌号码: ${point.licenseName}
最近定位位置: ${point.address}
经度: ${point.lng}
纬度: ${point.lat}
设备状态: ${point.deviceState}
断油电
恢复油电
行驶轨迹
`;
},
/**
* 加载地图标点数据
*/
loadRandomDevices() {
// 清除之前的标记、信息窗口和点聚合
this.map.clearOverlays();
this.markers = [];
this.infoWindows = [];
if (this.markerCluster) {
this.markerCluster.clearMarkers();
}
// 定义自定义图标的样式
// const myIcon = new BMap.Icon(require("@/assets/car.png"), new BMap.Size(32, 32), {
// anchor: new BMap.Size(16, 32), // 图标的定位点相对于图标左上角的偏移
// imageSize: new BMap.Size(32, 32) // 图标的大小
// });
// 根据 gpsInfo 的经纬度设置地图中心点
const gpsInfo = this.gpsInfo;
const centerPoint = new BMap.Point(gpsInfo.lng, gpsInfo.lat);
this.map.setCenter(centerPoint);
// 设置缩放级别
this.map.setZoom(18);
const point = new BMap.Point(gpsInfo.lng, gpsInfo.lat);
// const marker = new BMap.Marker(point, {
// icon: myIcon
// });
const marker = new BMap.Marker(point);
//创建信息窗口
const infoWindow = new BMap.InfoWindow(this.createPopupContent(gpsInfo));
//添加单击事件侦听器以打开信息窗口
marker.addEventListener("click", () => {
this.map.openInfoWindow(infoWindow, point);
const deviceIdElement = document.getElementById("deviceNum");
const deviceNum = deviceIdElement.textContent.trim();
const licenseNameElement = document.getElementById("licenseName");
const licenseName = licenseNameElement.textContent.trim();
this.deviceNum = deviceNum;
this.numberplate = licenseName
// 获取按钮元素并添加点击事件
const handleBreak = document.getElementById("handleBreak");
handleBreak.addEventListener("click", () => {
this.modalBreak = true; // 打开模态框
// console.log("执行断油电操作");
console.log("设备编号:", deviceNum);
});
const handleRestore = document.getElementById("handleRestore");
handleRestore.addEventListener("click", () => {
console.log("执行恢复油电操作");
this.modalRestore=true;
})
const drivingTrajectory = document.getElementById("drivingTrajectory");
drivingTrajectory.addEventListener("click", () => {
console.log("行驶轨迹执行事件");
this.modaldrivingTrajectory=true
})
});
// 将标记和信息窗口添加到各自的数组中
this.markers.push(marker);
this.infoWindows.push(infoWindow);
// 添加标记到地图
this.map.addOverlay(marker);
// });
// 点聚合
this.markerCluster = new BMapLib.MarkerClusterer(this.map, {
markers: this.markers,
gridSize: 80, // 根据需要进行调整
maxZoom: 18,
});
},
cancellation(){
this.modaldrivingTrajectory=false
},
/**
* 确定断油电事件
*/
handleBreakOk() {
if (this.optRemark == null) {
this.$message.info("备注不能为空")
return
}
httpAction(this.url.executeCmd, {
"deviceNum": this.deviceNum,
"switchKey": "14",
"remark": this.optRemark,
"numberplate": this.numberplate
}, 'post').then((res) => {
if (res) {
console.log(res)
this.$message.info(res.result)
}
})
this.modalBreak = false; // 关闭模态框
},
/**
* 确定恢复油电事件
*/
handleRestoreOk() {
if (this.optRemark == null) {
this.$message.info("备注不能为空")
return
}
httpAction(this.url.executeCmd, {
"deviceNum": this.deviceNum,
"switchKey": "15",
"remark": this.optRemark,
"numberplate": this.numberplate,
}, 'post').then((res) => {
if (res) {
console.log(res)
this.$message.info(res.result)
}
})
this.modalRestore = false; // 关闭模态框
},
},
};
</script>
<style scoped>
.parent-container {
width: 100%;
height: 800px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.map-container {
width: 100%;
height: 100%;
}
button {
margin-top: 10px;
}
</style>
模板部分:
<div ref="mapContainer" style="width: 100%; height: 100vh;"></div>:地图容器。
控制面板:包含轨迹时段选择、搜索、播放、暂停、进度调节等功能。
数据:
dateData:轨迹时段的日期数据。
startDate和endDate:选择的开始和结束日期时间。
map:百度地图实例。
pathData:轨迹点的数据。
marker:地图上的标记点。
currentIndex:当前轨迹点索引。
speed:轨迹播放速度。
isPlaying:是否正在播放。
progressPercent:轨迹播放进度百分比。
url:包含后端接口的URL。
totalPoints:轨迹点总数。
方法:
handleDateFilterChange():处理日期范围变化。
searchData():执行搜索轨迹数据的操作。
decreaseProgress()和increaseProgress():调整轨迹播放进度。
updateMarkerPosition():更新标记的位置。
pauseAnimation()和startAnimation():暂停和播放轨迹动画。
generatePathData():生成轨迹点数据。
initMap():初始化地图,添加标记、标点、路径等,并开始动画。
animateMarker():播放轨迹动画的主要逻辑。
calculateZoomLevel():根据距离计算新的缩放级别。
clearMap()和clearMapAndSearch():清除地图上的标记和地图实例。
<template>
<div>
<div ref="mapContainer" style="width: 100%; height: 100vh;"></div>
<!-- 包含轨迹时段和控制按钮的浮动层 -->
<div class="control-panel">
轨迹时段: <a-range-picker :value="dateData" @change="handleDateFilterChange" :show-time="true"
:disabledDate="disabledDate">
</a-range-picker>
<!-- 搜索按钮 -->
<a-button @click="clearMapAndSearch">搜索</a-button>
<a-button @click="startAnimation">播放</a-button>
<a-button @click="pauseAnimation">暂停</a-button>
<a-button @click="decreaseProgress" style="margin-left: 20px;">-</a-button>
<a-button @click="increaseProgress">+</a-button>
<a-row>
<a-col :span="12">
播放速度:
<a-radio-group v-model="speed">
<a-radio :value="100">慢速</a-radio>
<a-radio :value="500">正常</a-radio>
<a-radio :value="1000">快速</a-radio>
<a-radio :value="1500">最快速</a-radio>
</a-radio-group>
</a-col>
</a-row>
<a-slider v-model="progressPercent" :min="0" :max="100" />
</div>
</div>
</template>
<script>
import Vue from 'vue';
import {
getAction,
httpAction,
postAction
} from '../../api/manage';
import moment from 'dayjs'
export default {
props: {
deviceNum: {
type: String, // 参数的数据类型
required: true // 参数是否必需
},
},
watch: {
progressPercent(newProgress) {
// 在进度变化时更新标记的位置
this.updateMarkerPosition();
},
},
data() {
return {
dateData: [],
startDate: null, // 开始日期时间
endDate: null, // 结束日期时间
map: null,
pathData: [],
marker: null,
currentIndex: 0,
speed: 100,
//是否播放
isPlaying: false,
progressPercent: 0,
url: {
trajectory: '/jeecg-customers/car/carCheXiaoGpsController/trajectory',
},
totalPoints: 0,
};
},
mounted() {
const startDate = moment().subtract(5, 'hours').format('YYYY-MM-DD HH:mm:ss');
console.log(startDate, '开始时间');
const endDate = moment().format('YYYY-MM-DD HH:mm:ss');
console.log(endDate, '结束时间');
this.dateData = [startDate, endDate]
this.startDate = startDate
this.endDate = endDate
this.searchData();
},
methods: {
disabledDate(current) {
// 禁用日期的函数
if (!this.startDate || !this.endDate) {
// 如果没有选择日期范围,不禁用任何日期
return false;
}
const maxDate = moment(this.startDate).add(3, 'days'); // 允许选择日期范围的最大结束日期
return current && (current < this.startDate || current > maxDate);
},
handleDateFilterChange(dates, dateString) {
this.dateData = dates;
this.startDate = dateString[0]
this.endDate = dateString[1]
console.log(this.startDate, this.endDate)
},
searchData() {
// 获取开始日期时间和结束日期时间
if (!this.startDate || !this.endDate) {
// 显示错误消息或进行其他处理
this.$message.warning('请选择时间');
}
this.generatePathData().then((res) => {
if (res) {
this.initMap();
}
});
},
decreaseProgress() {
if (this.progressPercent > 0) {
this.progressPercent -= 1;
this.updateMarkerPosition();
}
},
increaseProgress() {
if (this.progressPercent < 100) {
this.progressPercent += 1;
this.updateMarkerPosition();
}
},
updateMarkerPosition() {
const newIndex = Math.round((this.progressPercent / 100) * (this.totalPoints - 1));
if (newIndex !== this.currentIndex) {
this.currentIndex = newIndex;
const point = this.pathData[newIndex];
const position = new BMap.Point(point.lng, point.lat);
this.marker.setPosition(position);
this.map.panTo(position);
}
},
pauseAnimation() {
this.isPlaying = false;
},
startAnimation() {
this.isPlaying = true;
this.animateMarker();
},
generatePathData() {
console.log(this.startDate)
console.log(this.endDate)
return new Promise((resolve) => {
getAction(this.url.trajectory, {
// deviceNum: "10213134933",
deviceNum: this.deviceNum,
baseStateLBS: "1",
startTime: this.startDate,
endTime: this.endDate,
stopPoint: "0",
stopPointTime: "10",
}).then((res) => {
resolve(res);
this.pathData = res.result;
this.totalPoints = this.pathData.length; // Set totalPoints
});
});
},
initMap() {
this.map = new BMap.Map(this.$refs.mapContainer);
const firstPoint = this.pathData[0];
// 添加缩放控件
this.map.addControl(new BMap.NavigationControl());
this.map.centerAndZoom(new BMap.Point(firstPoint.lng, firstPoint.lat), 15);
// 启用鼠标滚轮缩放
this.map.enableScrollWheelZoom();
// 遍历pathData添加图标覆盖物
for (let i = 0; i < this.pathData.length; i++) {
const point = this.pathData[i];
const myIcon = new BMap.Icon(require("@/assets/stop.png"), new BMap.Size(20, 20), {
// anchor: new BMap.Size(25, 25), // 图标的定位点相对于图标左上角的偏移
imageSize: new BMap.Size(20, 20) // 图标的大小
});
const marker = new BMap.Marker(new BMap.Point(point.lng, point.lat), {
icon: myIcon,
});
// 根据sp的值判断是否添加覆盖物
if (point.sp === "0.0") {
this.map.addOverlay(marker);
}
}
const myIcon = new BMap.Icon(require("@/assets/carrun.png"), new BMap.Size(50, 50), {
anchor: new BMap.Size(25, 25), // 图标的定位点相对于图标左上角的偏移
imageSize: new BMap.Size(50, 50) // 图标的大小
});
// const myIcon = new BMap.Icon("https://webapi.amap.com/images/car.png", new BMap.Size(60, 30), {
// // anchor: new BMap.Size(25, 25), // 图标的定位点相对于图标左上角的偏移
// imageSize: new BMap.Size(60, 30) // 图标的大小
// });
this.marker = new BMap.Marker(new BMap.Point(firstPoint.lng, firstPoint.lat), {
icon: myIcon
});
this.map.addOverlay(this.marker);
const polyline = new BMap.Polyline(
this.pathData.map((point) => new BMap.Point(point.lng, point.lat)), {
enableEditing: false,
enableClicking: true,
strokeWeight: '8',
strokeOpacity: 0.8,
strokeColor: "#18a45b",
}
);
this.map.addOverlay(polyline);
// 开始动画
this.animateMarker();
},
animateMarker() {
if (this.currentIndex < this.totalPoints - 1 && this.isPlaying) {
const currentPoint = this.pathData[this.currentIndex];
const nextPoint = this.pathData[this.currentIndex + 1];
const startPosition = new BMap.Point(currentPoint.lng, currentPoint.lat);
const endPosition = new BMap.Point(nextPoint.lng, nextPoint.lat);
const distance = this.map.getDistance(startPosition, endPosition);
// 基于速度和两个点之间的距离来计算时长
const duration = (distance / this.speed) * 1000;
const angle = this.calculateAngle(startPosition, endPosition);
this.marker.setRotation(angle);
// 调整地图的缩放级别以使标点可见
// const newZoomLevel = this.calculateZoomLevel(distance); // 自定义函数来计算缩放级别
// this.map.centerAndZoom(endPosition, newZoomLevel);
this.progressPercent = Math.round((this.currentIndex / (this.totalPoints - 1)) * 100);
if (this.isPlaying) {
setTimeout(() => {
this.marker.setPosition(endPosition);
this.currentIndex++;
requestAnimationFrame(this.animateMarker);
}, duration);
}
}
},
calculateZoomLevel(distance) {
// 根据距离或其他条件来计算新的缩放级别
if (distance < 1000) {
return 19;
} else {
return 14;
}
},
clearMap() {
if (this.map) {
this.map.clearOverlays(); // 清除地图上的所有标记点
this.map = null; // 销毁地图实例
}
},
clearMapAndSearch() {
// 清除地图上的标点和地图实例
this.clearMap();
// 执行搜索并加载标点的逻辑
this.searchData();
},
/**根据点位旋转角度
* @param {Object} point1
* @param {Object} point2
*/
calculateAngle(point1, point2) {
// 计算两点之间的方向(角度)
const lat1 = point1.lat * (Math.PI / 180);
const lng1 = point1.lng * (Math.PI / 180);
const lat2 = point2.lat * (Math.PI / 180);
const lng2 = point2.lng * (Math.PI / 180);
const y = Math.sin(lng2 - lng1) * Math.cos(lat2);
const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1);
let angle = Math.atan2(y, x);
angle = (angle * 180) / Math.PI;
// 调整角度以匹配图标朝向
angle -= 90;
return angle;
}
},
};
</script>
<style>
.control-panel {
position: absolute;
top: 10px;
/* 调整 bottom 属性来控制浮动层距离底部的距离 */
left: 10px;
/* 调整 left 属性来控制浮动层距离左侧的距离 */
background-color: white;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
</style>