之前,公司需要我来开发项目中的围栏管理功能。业务需求也不是太难,就是实现在百度地图上使用百度地图工具来绘制一块区域,给该区域添加项目相关的区域信息,然后保存到数据库,另外实现对保存到数据库的围栏的增删改查功能。
因为公司的项目是一个基于vue的前后端分离的项目。所以开发这一功能时,我一开始想到尝试网上有没有关于百度地图的插件可以使用的。我找到了
vue-baidu-map 这一插件。由于没有太多时间来了解这一插件,便pass了。但是开发者们可以去了解一下。
使用百度地图插件的案例 : https://www.cnblogs.com/WorkingBoy/p/15424905.html 可以点击看看这位博主的案例。
为了学习百度地图引入vue项目,我选择了最原始的方法,通过手动js引入。
下面是js代码 : 可以命名为 bmpjl.js。
const ak = '你的密钥' // 百度的地图密钥
/**
* 异步加载百度地图
* @returns {Promise}
* @constructor
*/
function loadBaiDuMap() {
return new Promise(function (resolve, reject) {
try {
console.log('BMap is defined:', BMapGL === undefined || BMapGL)
resolve(BMapGL)
} catch (err) {
window.init = function () {
resolve(BMapGL)
}
let script = document.createElement('script')
script.type = 'text/javascript'
script.src = `http://api.map.baidu.com/api?v=1.0&type=webgl&ak=${ak}&callback=init`
script.onerror = reject
document.body.appendChild(script)
}
})
}
export { loadBaiDuMap }
/**
* 异步加载百度地图,以及绘制工具
* @returns {Promise}
* @constructor
*/
function loadBaiDuDrawMap() {
return loadBaiDuMap().then(BMapGL => {
let loaded = false
try {
loaded = (BMapGLLib && BMapGLLib.DrawingManager)
} catch (err) {
loaded = false
}
if (!loaded) {
console.log('BMapLib.DrawingManager loading!')
let script = document.createElement('script')
script.type = 'text/javascript'
script.src = 'http://mapopen.cdn.bcebos.com/github/BMapGLLib/DrawingManager/src/DrawingManager.min.js'
document.body.appendChild(script)
let link = document.createElement('link')
link.rel = 'stylesheet'
link.href = 'http://mapopen.cdn.bcebos.com/github/BMapGLLib/DrawingManager/src/DrawingManager.min.css'
document.body.appendChild(link)
} else {
console.log('BMapLib.DrawingManager is loaded!')
}
return BMapGL
})
}
export { loadBaiDuDrawMap }
引入百度地图前,你需要去网上搜索百度地图官方文档 ,即 百度地图开发者平台 链接地址是 https://www.baidu.com/link?url=hY2nl1O5PMifStkUFQsDmEbycTTly1JfAarPlyzFc7ICq-Mn4oWR8JWsQp0Wy3dL&wd=&eqid=f47ede5a0003ee7600000006629185d0
进入官网后,选择控制台菜单 -> 应用管理 -> 我的应用 .
点击创建应用 输入应用名称 选择安排ap[服务等,提交即可获得一个自己的密钥。
开发者们也可以通过官方开发文档一步步学习,来创建自己的地图。文档里面除了 js方向,还有小程序,android方向等开发文档。
下面是在页面中引入之前的js文件,展示地图以及围栏管理功能的源码:这里我命名为weilan.vue。
<template>
<div class="home" style="width: 100%; height: 100%">
<!-- BMapGLLib 控件,这里自己去优化一下吧,写成循环吧 ,,懒得写了 -->
<!-- <ul class="drawing-panel">
<li class="bmap-btn bmap-marker" @click="draw('marker')" :style="{ 'background-position-y': actNav === 'marker' ? '-52px' : '0px'}"></li>
<li class="bmap-btn bmap-polyline" @click="draw('polyline')" :style="{ 'background-position-y': actNav === 'polyline' ? '-52px' : '0px' }"></li>
<li class="bmap-btn bmap-rectangle" @click="draw('rectangle')" :style="{ 'background-position-y': actNav === 'rectangle' ? '-52px' : '0px' }"></li>
<li class="bmap-btn bmap-polygon" @click="draw('polygon')" :style="{'background-position-y': actNav === 'polygon' ? '-52px' : '0px'}"></li>
<li class="bmap-btn bmap-circle" @click="draw('circle')" :style="{'background-position-y': actNav === 'circle' ? '-52px' : '0px'}"></li>
</ul> -->
<div class="search">
<a-input
v-model="queryParams.unitname"
maxlength="250"
:allowClear="true"
style="width: 60%"
placeholder="请输入单位名称"
></a-input>
<a-button
style="width: 30px; margin-left: 20px"
type="primary"
shape="circle"
icon="search"
@click="search()"
/>
</div>
<ul class="drawing-panel">
<li :key="item.id" v-for="item in mapToolbar">
<a href="javascript:;">
<my-icon
:type="item.icon"
class="tool-icon"
@click="item.action === 'polygon' ? draw(item.action) : ''"
></my-icon>
</a>
</li>
<!-- <li>
<a href="javascript:;"><my-icon type="icon-sousuo" class="tool-icon" @click="search"></my-icon></a>
</li> -->
</ul>
<!-- 地图容器 -->
<div id="container" class="mymap" @ready="handler"></div>
<div id="r-result" class="minput">
<label for="">请输入:</label>
<a-input
id="suggestCity"
v-model="address"
maxlength="250"
:allowClear="true"
style="width: 60%; margin-left: 20px"
placeholder="请输入你想搜索的地点"
/>
</div>
<div
id="searchResultPanel"
style="
border: 1px solid #c0c0c0;
width: 150px;
height: auto;
display: none;
"
></div>
<!-- 新增覆盖物 -->
<add-overlay
ref="addoverlay"
@addoverlay="addLocalOverlay"
@canceloverlay="cancelOverlay"
></add-overlay>
</div>
</template>
<script>
import { i, log } from "mathjs";
// 引入异步引入地图的方法
import { loadBaiDuDrawMap } from "./bmpgl";
import AddOverlay from "./Modals/AddOverlay.vue";
//围栏管理后台接口
import { sysAreaApi as Sapi } from "@/api";
//地图工具栏
const mapToolbar = [
{
id: 0,
title: "正方形",
icon: "icon-checkbox",
action: "rectangle",
},
{
id: 1,
title: "多边形",
icon: "icon-duobianxing",
action: "polygon",
},
{
id: 2,
title: "圆圈",
icon: "icon-big-circle",
action: "circle",
},
];
export default {
name: "mapCom",
components: {
AddOverlay,
},
data() {
return {
map: null,
actNav: "",
myValue: "",
mapToolbar,
//绘制对象
_drawingManager: null,
//页面存储覆盖物的数组
overlays: [],
//查询地点
address: "",
//查询参数
queryParams: {},
//列表中获取到的点数组
localOverlays: [],
loadData: (parameter) => {
var _this = this;
let searchArr = [],
queryRules = {
unitname: "eq",
},
defaultOperator = "eq";
for (let [key, value] of Object.entries(this.queryParams)) {
if (value || value == 0) {
searchArr.push({
column: key,
value: value,
type: queryRules[key] || defaultOperator,
});
}
}
searchArr.length && (parameter.search = JSON.stringify(searchArr));
return this.$api
.get(Sapi.list, parameter)
.then((res) => {
if (res.success) {
_this.$nextTick(() => {
_this.localOverlays = res.data.records;
_this.$forceUpdate();
if (_this.localOverlays.length > 2) {
return;
} else {
_this.drawSearchResult();
}
});
// return res.data;
} else {
return this.getListError(res.msg);
}
})
.catch((err) => {
return this.getListError(err);
});
},
};
},
mounted() {
this.initMap();
},
methods: {
handler({ BMapGL, map }) {
this.map = map;
},
getListError(msg) {
this.$message.warning(msg);
this.localOverlays = [];
},
initMap() {
// 初始化地图
loadBaiDuDrawMap().then((BMapGL) => {
// 创建地图实例
var _this = this;
let map = new BMapGL.Map("container", { minZoom: 4, maxZoom: 22 });
//添加地图平移缩放控件
// map.addControl(new BMapGL.NavigationControl());
// 添加比例尺控件
map.addControl(
new BMapGL.ScaleControl({
anchor: BMAP_ANCHOR_BOTTOM_LEFT,
offset: new BMapGL.Size(10, 10),
})
);
// 添加缩放控件
map.addControl(
new BMapGL.ZoomControl({
anchor: BMAP_ANCHOR_BOTTOM_RIGHT,
offset: new BMapGL.Size(10, 10),
})
);
// 创建点坐标 axios => res 获取的初始化定位坐标
const point = new BMapGL.Point(114.031761, 22.542826);
// 初始化地图,设置中心点坐标和地图级别
map.centerAndZoom(point, 19);
//开启鼠标滚轮缩放
map.enableScrollWheelZoom(true);
//设置地图3d视角
// map.setHeading(64.5);
// map.setTilt(73);
this.$nextTick(() => {
// 保存地图
this.map = map;
});
this.getLocation();
this.accuarySearch();
this.$nextTick(() => {
const overlays = this.map.getOverlays();
this.refresh();
});
});
},
//获取当前地理位置 将地图中心点移动到定位位置
getLocation() {
var _this = this;
var geolocation = new BMapGL.Geolocation();
//创建定位对象
geolocation.getCurrentPosition(
function (r) {
//通过定位对象调用定位函数,回调函数形参r表示定位结果
if (this.getStatus() == BMAP_STATUS_SUCCESS) {
//如果定位成功
var mk = new BMapGL.Marker(r.point); //创建标记,r是定位结果,r.point就是当前定位的地点
_this.$nextTick(() => {
_this.map.addOverlay(mk); //将标记对象添加到地图上
_this.map.panTo(r.point); //将地图中心店移动到定位地点
});
} else {
alert("定位失败!");
}
},
{ enableHighAccuracy: true }
);
},
draw(type) {
this.actNav = type;
//设置开始绘图时的线条配置
const styleOptions = {
strokeColor: "#5E87DB", // 边线颜色
fillColor: "#5E87DB", // 填充颜色。当参数为空时,圆形没有填充颜色
strokeWeight: 2, // 边线宽度,以像素为单位
strokeOpacity: 1, // 边线透明度,取值范围0-1
fillOpacity: 0.2, // 填充透明度,取值范围0-1
};
// 实例化鼠标绘制工具
const drawingManager = new BMapGLLib.DrawingManager(this.map, {
// isOpen: true, // 是否开启绘制模式
enableCalculate: false, // 绘制是否进行测距测面
enableSorption: true, // 是否开启边界吸附功能
sorptiondistance: 20, // 边界吸附距离
rectangleOptions: styleOptions, // 矩形的样式
});
this._drawingManager = drawingManager;
if (drawingManager._isOpen && drawingManager.getDrawingMode() === type) {
drawingManager.close();
} else {
drawingManager.setDrawingMode(type);
drawingManager.open();
}
//设置覆盖物绘制完成监听事件
this._drawingManager.addEventListener(
"overlaycomplete",
this.overlayComplete.bind(this._drawingManager)
);
},
//绘制完成事件处理
overlayComplete(e) {
console.log(this._drawingManager);
//添加一个新的覆盖物到覆盖物数组中
this.overlays.push(e.overlay);
// //获取绘制类型
// let drawingMode = e.drawingMode;
//边界点
let path = e.overlay.getPath();
//打开模态框
this.$refs.addoverlay.openModal(e.overlay, path);
// if (drawingModeOperate[drawingMode]) drawingModeOperate[drawingMode](e)
// this._drawingManager.close(); //关闭绘图
//给所有的覆盖物添加删除覆盖物的监听事件
for (let over = 0; over < this.overlays.length; over++) {
//添加点击事件
this.overlays[over].addEventListener(
"click",
this.removeOverlay.bind(this.overlays[over])
);
//添加鼠标经过事件
this.overlays[over].addEventListener(
"mouseover",
this.overOverlay.bind(this.overlays[over])
);
//添加鼠标离开事件
this.overlays[over].addEventListener(
"mouseout",
this.outOverlay.bind(this.overlays[over])
);
}
},
//删除围栏
removeOverlay(e) {
var _this = this;
this.$confirm({
title: "提示",
content: "确定删除这个围栏吗?",
onOk() {
_this.$api.delete(Sapi.del + "/" + e.target.unitid).then((res) => {
if (res.success) {
_this.map.removeOverlay(e.target);
_this.map.clearOverlays()
_this.$message.success(res.msg);
} else {
_this.$message.error(res.msg);
}
});
},
onCancel() {},
});
},
drawSearchResult() {
var _this = this;
if (_this.localOverlays.length == 1) {
var overlays = this.map.getOverlays();
if (overlays.length > 0) {
_this.map.clearOverlays();
}
//把围栏名称添加到覆盖物上
var unitid = _this.localOverlays[0].unitid;
var unitname = _this.localOverlays[0].unitname;
const localOverlay = _this.localOverlays[0].area.split(";");
var points = [];
for (var i = 0; i < localOverlay.length; i++) {
var point = new BMapGL.Point(
Number(localOverlay[i].split(",")[0]),
Number(localOverlay[i].split(",")[1])
);
points.push(point);
}
const styleOptions = {
strokeColor: "#5E87DB", // 边线颜色
fillColor: "#5E87DB", // 填充颜色。当参数为空时,圆形没有填充颜色
strokeWeight: 2, // 边线宽度,以像素为单位
strokeOpacity: 1, // 边线透明度,取值范围0-1
fillOpacity: 0.2, // 填充透明度,取值范围0-1
};
var polygon = new BMapGL.Polygon(points, styleOptions);
// //给查询到的围栏做个标识,以便之后的删除
polygon.unitid = unitid;
polygon.unitname = unitname;
_this.overlays.push(polygon);
//给所有的覆盖物添加删除覆盖物的监听事件
for (let over = 0; over < this.overlays.length; over++) {
//添加点击事件
this.overlays[over].addEventListener(
"click",
this.removeOverlay.bind(this.overlays[over])
);
//添加鼠标经过事件
this.overlays[over].addEventListener(
"mouseover",
this.overOverlay.bind(this.overlays[over])
);
//添加鼠标离开事件
this.overlays[over].addEventListener(
"mouseout",
this.outOverlay.bind(this.overlays[over])
);
}
//添加围栏到地图上
_this.map.addOverlay(polygon);
//添加标签至覆盖物上
_this.addLabel(polygon);
var centerPoint = polygon.getBounds();
centerPoint = centerPoint.getCenter();
//地理值转换为像素值
// var centerPixel = _this.map.PointToPixel(centerPoint)
_this.map.panTo(centerPoint);
// _this.map.panBy(centerPixel)
_this.queryParams = {};
} else if (_this.localOverlays.length == 0) {
this.$message.warning("当前单位目前暂无围栏!");
this.queryParams = {};
return;
}
},
//新增围栏
addLocalOverlay(overlay) {
var overlays = this.map.getOverlays();
if (overlays.length > 0) {
this.map.clearOverlays();
}
//添加围栏到地图上
this.map.addOverlay(overlay);
this.addLabel(overlay);
this.refresh();
},
//取消围栏编辑
cancelOverlay(record) {
this.map.removeOverlay(record);
},
//刷新页面
refresh() {
const parameter = {
pageNo: 1,
pageSize: 25,
};
this.loadData(parameter);
},
//搜索围栏
search() {
this.refresh();
},
//给围栏添加标签
addLabel(overlay) {
var centerPoint = overlay.getBounds();
centerPoint = centerPoint.getCenter();
let label = new BMapGL.Label(overlay.unitname, { position: centerPoint });
// label.setContent(overlay.unitname)
label.setStyle({
color: "black",
fontSize: "14px",
fontWeight: 700,
});
// label.setPosition(centerPoint)
this.map.addOverlay(label);
},
//鼠标经过覆盖物
overOverlay(e) {
e.target.setFillColor("#6f6cd8");
},
//鼠标离开覆盖物
outOverlay(e) {
e.target.setFillColor("#fff");
},
//定点查询
accuarySearch() {
var _this = this;
//建立一个自动完成的实例对象
let autocomplete = new BMapGL.Autocomplete({
location: this.map, //设定返回结果的所属范围。例如“北京市”
// types:'city', //设置值为"city",将返回省市区县乡镇街道地址类型数据。
input: "suggestCity", //文本输入框元素或其id
});
//处理函数
function handleAutoComplete(e) {
var str = "";
var _value = e.fromitem.value;
var value = "";
if (e.fromitem.index > -1) {
value =
_value.province +
_value.city +
_value.district +
_value.street +
_value.business;
}
str =
"FromItem<br />index = " +
e.fromitem.index +
"<br />value = " +
value;
value = "";
if (e.toitem.index > -1) {
_value = e.toitem.value;
value =
_value.province +
_value.city +
_value.district +
_value.street +
_value.business;
}
str +=
"<br />ToItem<br />index = " +
e.toitem.index +
"<br />value = " +
value;
document.getElementById("searchResultPanel").innerHTML = str;
document.getElementById("tangram-suggestion--TANGRAM__1").style.zIndex = 2000;
}
//鼠标放在下拉列表上的事件
autocomplete.addEventListener("highlight", handleAutoComplete);
//处理函数
function handleOnConfirm(e) {
var _value = e.item.value;
_this.myValue =
_value.province +
_value.city +
_value.district +
_value.street +
_value.business;
document.getElementById("searchResultPanel").innerHTML =
"onconfirm<br />index = " +
e.item.index +
"<br />myValue = " +
_this.myValue;
document.getElementById("tangram-suggestion--TANGRAM__1").style.zIndex = 2000;
_this.setPlace();
}
//鼠标点击下拉列表后的事件
autocomplete.addEventListener("confirm", handleOnConfirm);
},
//跳转到指定地点
setPlace() {
var _this = this;
this.map.clearOverlays(); //清除地图上所有覆盖物
//智能搜索
function myFun() {
//获取第一个智能搜索的结果
var pp = local.getResults().getPoi(0).point;
_this.map.centerAndZoom(pp, 18);
//添加标注
_this.map.addOverlay(new BMapGL.Marker(pp));
}
var local = new BMapGL.LocalSearch(_this.map, {
onSearchComplete: myFun,
});
local.search(this.myValue);
this.myValue = "";
},
},
};
</script>
<style >
/* 隐藏版权信息 */
.BMap_cpyCtrl {
display: none;
}
/* 隐藏版权图标 */
.anchorBL img {
display: none;
}
</style>
<style scoped>
.mymap {
width: 100%;
height: 95vh;
position: relative;
z-index: 1;
}
.change-map-type {
position: absolute;
right: 50px;
bottom: 10px;
z-index: 2;
}
ul li {
list-style: none;
margin: 0;
padding: 0;
}
.info {
z-index: 999;
width: auto;
min-width: 22rem;
padding: 0.75rem 1.25rem;
margin-left: 1.25rem;
position: fixed;
top: 1rem;
background-color: #fff;
border-radius: 0.25rem;
font-size: 14px;
color: #666;
box-shadow: 0 2px 6px 0 rgba(27, 142, 236, 0.5);
}
.drawing-panel {
display: flex;
z-index: 999;
position: fixed;
top: 108px;
left: 250px;
border-radius: 0.25rem;
height: 47px;
box-shadow: 0 2px 6px 0 rgba(27, 142, 236, 0.5);
background-color: #fff;
padding-inline-start: 0;
}
.drawing-panel li {
width: 47px;
height: 47px;
/* float: left; */
flex: 1;
border-right: 1px solid #d2d2d2;
text-align: center;
line-height: 47px;
cursor: pointer;
}
::v-deep .drawing-panel li:hover,
.drawing-panel li:active {
background-color: rgb(98, 169, 214);
}
.tool-icon {
font-size: 30px;
color: #aaa;
text-align: center;
margin-top: 12px;
}
.tool-icon:hover {
transform: translate(1.1);
text-align: center;
color: #fff;
}
.search {
position: fixed;
z-index: 2;
top: 60px;
left: 240px;
width: 288px;
height: 30px;
line-height: 30px;
}
.minput {
position: fixed;
width: 400px;
top: 60px;
right: 20px;
z-index: 99;
}
::v-deep.tangram-suggestion-main {
z-index: 100 !important;
}
::v-deep table tbody {
z-index: 100 !important;
}
/* .bmap-btn {
border-right: 1px solid #d2d2d2;
float: left;
width: 64px;
height: 100%;
background-image: url(http://api.map.baidu.com/library/DrawingManager/1.4/src/bg_drawing_tool.png);
cursor: pointer;
} */
/* .drawing-panel .bmap-marker {
background-position: -65px 0;
}
.drawing-panel .bmap-polyline {
background-position: -195px 0;
}
.drawing-panel .bmap-rectangle {
background-position: -325px 0;
}
.drawing-panel .bmap-polygon {
background-position: -260px 0;
}
.drawing-panel .bmap-circle {
background-position: -130px 0;
} */
</style>
下面是新增围栏的模态框源码:这里我命名为 addOverlay.vue。
<template>
<div style="width:100%">
<drag-modal
:title="modalTitle"
width="400px"
v-if="visible"
:visible="visible"
:confirmLoading="confirmLoading"
@ok="handleSubmit"
@cancel="handleCancel"
>
<template slot="modalBody">
<a-form :form="form" autocomplete="off">
<a-form-item
label="单位"
:labelCol="{span:4}"
:wrapperCol="{span:16}"
>
<a-tree-select
:allowClear="true"
showSearch
treeNodeFilterProp="title"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
:treeData="deptTreeData"
v-decorator="[
'unitid',
{
rules: [{ required: true, message: '请选择所属单位' }],
initialValue: formdata.unitid || ''
}
]"
>
</a-tree-select>
</a-form-item>
</a-form>
</template>
</drag-modal>
</div>
</template>
<script>
//获取单位树
import { userApi as api } from '@/api'
//围栏管理接口
import {sysAreaApi as Sapi} from '@/api'
export default {
data(){
return {
visible:false,
form:this.$form.createForm(this),
modalTitle:'新增围栏编辑',
formdata: {},
treeExpandedKeys: [],
deptTreeData: [],
//当前编辑的围栏
overlay:{},
//当前围栏的点数组字符串
points:'',
confirmLoading:false
}
},
created: function () {
//获取单位树数据
this.$api.get(api.deptTree).then((res) => {
this.deptTreeData = res.data.map((item) => {
item.selectable = false
return item
})
})
},
methods:{
openModal(overlay,points){
this.visible = true
this.overlay = overlay
points.forEach(item =>{
this.points = this.points + item.lng + ',' + item.lat + ';'
})
this.points = this.points.substring(0,this.points.length - 1)
console.log("overlay:",overlay,this.points);
},
handleSubmit(){
// this.$emit('addoverlay',obj)
this.form.validateFields((err, values) => {
if (err) {
this.confirmLoading = false
return
}
//-----------字符串去首尾空格--------
for (let key in values) {
if (typeof values[key] == 'string') {
values[key] = values[key].trim()
}
}
//-----------字段关联值处理----------
let _this = this
;(function () {
for (let i = 0, pDept; (pDept = _this.deptTreeData[i++]); ) {
for (let j = 0, dept; (dept = pDept.children[j++]); ) {
if (dept.value == values.unitid) {
// values.paraentid = pDept.value
// values.paraentname = pDept.title
values.unitname = dept.title
return
}
}
}
})()
let params = {}
params.id = 0
params.inputtime = ""
params.area = this.points
params.unitid = values.unitid
params.unitname = values.unitname
//保存
console.log("params:",params);
this.$api.post(Sapi.save,params).then((res) =>{
if (res.success) {
this.visible = false
this.$message.success(res.msg)
this.overlay.unitid = params.unitid
this.overlay.unitname = params.unitname
this.$emit('addoverlay',this.overlay)
this.points = ''
this.overlay = {}
}
this.confirmLoading = false
}).catch((err) => {
//保存请求出错,给出提示
this.confirmLoading = false
this.$message.warning(err)
})
})
},
handleCancel(){
this.visible = false
this.$emit('canceloverlay',this.overlay)
}
}
}
</script>
<style scoped>
</style>
代码中已经有了详细的注释,开发者们可以根据注释修改修改代码,就可以实现自己的百度地图引入以及围栏管理模块了。
功能展示:
地图展示:
新增围栏:(以多边形为例)
区域绘制
保存围栏
查询围栏:
修改围栏:通过新增围栏 数据库记录覆盖以达到修改目的。
删除围栏:
地点搜索:
跳至搜索地点:
编码不易,点个小红心呗。