/* * @Author: your name * @Date: 2022-01-11 13:45:12 * @LastEditTime: 2023-07-19 18:02:31 * @LastEditors: 纪泽龙 jizelong@qq.com * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @FilePath: /test/hello-world/src/utils/mapClass.js */ import { pipeColor, svgUrl, svgAlarm } from "@/utils/mapClass/config.js"; // 编辑类 // 在地图上新增的设备可以直接编辑, // 已经保存完成的设备需要点编辑才可以编辑 export class EditorMap { // 地图的对象实例 map = null; // 外部传进来的中心店 center = null; // 父vue的实例 vue = null; // 操作 新建,编辑,删除,编辑跟删除只对已经在图上的设备有效 默认值:0, 新建:1,编辑:2, 删除: 3。 // 新建的时候会把未保存的线条清空 control = 0; // 鼠标事件对象,用来将点跟线上图 mousetool = null; // 当前正在手动绘制的对象 nowMouseTarget = null; // 当线mousetool线被按下的时候的flag 当线被按下的时候为true,就不询问是否删除了 mosueToolPolineDownFlag = false; // 绘制marer的时候的配置,在绘制完挂载事件的时候需要使用 mouseToolMarkerOptions = null; // 绘制poline的时候的配置,在绘制完挂载事件的时候需要使用 mouseToolPolineOptions = null; // 存放所有的设备的数组集合,这是一个对象,对象里面是各种设备的数组 allDevice = {}; // 存放所有的管道 pipeArr = {}; // 报警设备的对象 alarmObj = {}; // 当前的infowindow的组件 infowindowComponent = null; // infowindow本身 infowindow = null; constructor(contaienr, config = {}, vue) { this.map = new AMap.Map(contaienr, { viewMode: "3D", center: [118.168541, 39.838353], layers: [AMap.createDefaultLayer()], // layers 字段为空或者不赋值将会自动创建默认底图。 zoom: 14, zooms: [3, 20], ...config, }); const { center } = config; this.center = center; this.vue = vue; this.init(config); } init(config) { // 地图事件 this.mapEvent(); // 手动点线上图准备,编辑模式 this.mouseAddDevice(); // this.mouseAddMarker(); // this.mouseAddPipeline(); // 地图边界 this.map.getCity((res) => { const city = res.district; this.mapBound(city, config.maskFLag); }, this.center); } mapBound(city, maskFLag) { var district = new AMap.DistrictSearch({ extensions: "all", // 返回行政区边界坐标等具体信息 level: "district", // 设置查询行政区级别为 区 }); district.search(city, (status, result) => { // 获取朝阳区的边界信息 var bounds = result.districtList[0].boundaries; var polygons = []; console.log("boundsboundsboundsboundsbounds", bounds); if (bounds) { for (var i = 0, l = bounds.length; i < l; i++) { //生成行政区划polygon // new AMap.Polygon({ // map: this.map, // strokeWeight: 2, // path: bounds[i], // fillOpacity: 1, // fillColor: "transparent", // strokeColor: "#09f", // }); new AMap.Polyline({ map: this.map, strokeWeight: 4, strokeColor: "#09f", path: bounds[i], }); // polygons.push(polygon); } if (maskFLag) { let mask = new AMap.Polygon({ path: bounds[0], // 边界坐标点集合 // fillColor: "#000000", // 填充色 // fillOpacity: 0.8, // 填充透明度 strokeWeight: 0, // 边框宽度,设为 0 表示没有边框 zIndex: 100, // 显示优先级,越大越靠上层 }); // 添加遮罩层到地图 this.map.add(mask); // this.map.setMapStyle('amap://styles/dark'); // this.map.setMask([mask]); } } }); } // map的事件监听 mapEvent() { this.map.on("click", () => { // mousetool对象画出的对象的操作 // 如果有手动绘制对象,要手动清楚一下,因为画线的时候不好清除旧线,这其实是用来清楚旧线的 // 当画出来的线被mousedown,不删除,但是mouseToolPipeLineFlag要归位,在移出线的时候统一归位 // 如果对象是marker,直接删除 if (this.nowMouseTarget?.type == "AMap.Marker") { this.mouseToolDrawClear(); } else { // 当地图上已经画了线,并且没有点在线上,询问是否删除 if (this.nowMouseTarget && !this.mosueToolPolineDownFlag) { this.confirm("是否重新绘制管道", { type: "warning" }) .then(() => { // 删除原来的线 this.mouseToolDrawClear(); // 鼠标事件开启,并且赋值原来的属性,this.mouseToolMarkerOptions是开启绘制的时候记录的 this.mousetool.polyline(this.mouseToolPolineOptions); }) .catch(() => {}); } } }); this.map.on("moveend", () => { console.log("地图停止移动"); if (this.flag) { console.log("弹框"); this.flag = false; } }); // this.map.on('zoomchange', () => { // //获取当前最新的地图层级 // let Zoom = this.map.getZoom() // /** // * 地图层级发生改变后操作 // * */ // console.log(Zoom); // }) window.panTo = () => { this.flag = true; this.map.panTo([116.428285, 39.886129]); }; } // 弹框工具 confirm(message, obj) { return this.vue.$confirm(message, obj); } // 改变操作状态 changeControl(num) { this.control = num; } // 点线编辑上图准备 mouseAddDevice() { this.map.plugin(["AMap.MouseTool"], () => { this.mousetool = new AMap.MouseTool(this.map); }); setTimeout(() => { this.mouseDrawEvent(); }, 2000); // 挂载绘制结束的事件 } // 点或者线上图结束后触发的事件 mouseDrawEvent() { this.mousetool.on("draw", (e) => { const target = e.obj; // console.log([target._position.lng, target._position.lat]); const { type: targetType } = target; // 当这个点是marker的时候 if (targetType == "AMap.Marker") { this.mouseToolMarkerEvent(target); } else { // 如果是线,挂上编辑 this.lineEditor(target); this.mousetool.close(); this.mouseToolPolineEvent(target); console.log(targetType, "当前对象是管道"); } this.nowMouseTarget = target; }); } // 绘制marker结束后,在marker身上添加的事件 mouseToolMarkerEvent(target) { // 由于画出来的marker点击会换位置,所以当移入的时候删除绘制事件,移出去在增加绘制事件 target.on("mouseover", (e) => { // 鼠标事件关闭 this.mousetool.close(false); }); target.on("mouseout", (e) => { // 这里不方便获取原来的属性,因为position不好解决,还是设置一个值吧 // 鼠标事件开启,并且赋值原来的属性,this.mouseToolMarkerOptions是开启绘制的时候记录的 this.mousetool.marker(this.mouseToolMarkerOptions); }); // 点 target.on("click", (e) => { // 弹框 }); } // 挂上线以及线的事件 lineEditor(line) { // line.editor && line.editor.close(); // 当前点击次数,1次为编辑,2次为弹框 line.editorNum = 0; line.editor = new AMap.PolyEditor(this.map, line); } // 绘制管道的时候,挂载的事件 mouseToolPolineEvent(target) { // 线按下的时候会变成编辑,mousetool事件会清空 移出线的时候 在把polyline事件加上 target.on("mouseover", (e) => { // 鼠标事件关闭 // this.mousetool.close(false); }); target.on("mouseout", (e) => { // 有时候按在线上移动地图,map点击事件中mosueToolPolineDownFlag无法归位,在这里归位 this.mosueToolPolineDownFlag = false; // 鼠标事件开启,并且赋值原来的属性,this.mouseToolMarkerOptions是开启绘制的时候记录的 // this.mousetool.polyline(this.mouseToolPolineOptions); }); // 线 target.on("mousedown", (e) => { const line = e.target; // mosueTool按下的flag,按在线上,不询问是否删除 this.mosueToolPolineDownFlag = true; // 按下的时候要关闭事件 this.mousetool.close(false); // 如果当前状态不是编辑,则进入编辑状态 if (line.editorNum < 1) { // 打开并且++ line.editor.open(); line.editorNum++; } else { // 这里就要弹框了 console.log(line.getPath()); } }); } // 设备点击上图开启 mouseAddMarker(markerObj = {}) { // 清空已经绘制完的对象 this.mousetoolClose(true); // 记录一下配置项,在挂载点击的时候,需要使用 this.mouseToolMarkerOptions = { draggable: true, ...markerObj, }; this.mousetool.marker(this.mouseToolMarkerOptions); } // 管线点击上图开启 mouseAddPipeline(pipeObj = {}) { this.mousetoolClose(true); // 开始画线 this.mosuetoolPolineFlag = true; this.mouseToolPolineOptions = { strokeWeight: 5, ...pipeObj, }; this.mousetool.polyline(this.mouseToolPolineOptions); } // 手动清除map上绘制的对象 mouseToolDrawClear() { if (this.nowMouseTarget) { this.map.remove(this.nowMouseTarget); // 如果有editor,则关闭 this.nowMouseTarget.editor && this.nowMouseTarget.editor.close(); this.nowMouseTarget = null; } } // 关闭点击上图事件 true清除之前绘制的图像,false 仅关闭上图事件 mousetoolClose(boolean) { // 清空地图上的绘制对象的同时,也要清楚这个nowMouseTarget控制对象 if (this.nowMouseTarget) { // 如果有editor,则关闭 this.nowMouseTarget.editor && this.nowMouseTarget.editor.close(); this.nowMouseTarget = null; } this.mousetool.close(boolean); } /** * * * * * * * 地图上add设备 * * * @description: * @param {*} deviceData marker的数据 * @param {*} compontent marker点击弹出的infowindow的组件 * @return {*} */ addDevice(deviceData, compontent, showBool = true) { const { longitude: lng, latitude: lat, iconType } = deviceData; const icon = svgUrl[iconType]; console.log("icon", icon); let device = this.createMarker({ map: this.map, anchor: "bottom-center", icon, position: [lng, lat], extData: deviceData, // label:{ // content:123, // direction:"top", // } }); if (!showBool) { device.hide(); } // 如果没有有这个公司的数组,就创建,有就直接push if (!Array.isArray(this.allDevice[iconType])) { this.allDevice[iconType] = []; } this.allDevice[iconType].push(device); // 设备的事件函数 this.deviceEvent(device, compontent); } deviceEvent(device, compontent) { device.on("click", (e) => { const target = e.target; // 如果control==0就是默认值,没有使用123功能,就显示infowindow if (this.control == 0) { this.markerClick(target, compontent); } else if (this.control == 2) { // 2是已经上图的设备拥有的编辑功能 } else if (this.control == 3) { // 3是删除操作 } }); device.on("mouseover", (e) => { const target = e.target; const data = target.getExtData(); console.log(data); const name = data.nickName || data.deviceName || data.videoName || data.stationName || data.time; target.setLabel({ content: name, direction: "top" }); }); device.on("mouseout", (e) => { const target = e.target; const data = target.getExtData(); target.setLabel({ content: "" }); }); } /** 点击marker出现infowindow * @description: * @param {*} target 点击的对象 * @param {*} compontent marker点击弹出的infowindow的组件 * @return {*} */ markerClick(target, compontent) { const deviceExtData = target.getExtData(); const { longitude: lng, latitude: lat } = deviceExtData; // 创建一个可以控制的组件,将其dom插入infowindow this.infowindowComponent = this.createInfowindowDom( this.vue, this, deviceExtData, compontent ); // 没恩么用,控制台测试的时候用着玩的 window.func = () => { const { longitude: lng, latitude: lat } = target.getExtData(); const cd = { id: 1, name: "9", lng, lat, }; this.infowindowComponentChange(cd); target.setExtData(cd); }; this.infowindow = new AMap.InfoWindow({ isCustom: true, content: this.infowindowComponent.$el, position: [lng, lat], // anchor: "top-left", // offset: [20, -45], anchor: "middle-left", offset: [20, -10], }); this.infowindow.open(this.map); } // 创建要加入到infowindow里的 createInfowindowDom(vueRoot, mapClass, deviceData, compontent) { const Component = this.vue.$Vue.extend(compontent, {}); return new Component({ data() { return { // 当前vue实例 vueRoot, // 自己写的map类 mapClass, // 数据 deviceData, }; }, }).$mount(); } // 当前显示的infowindow内部的数据发生变化,一般在socket传回数据的时候使用 infowindowComponentChange(data) { this.infowindowComponent.deviceData = data; } // 创建marker createMarker(MarkerOptions) { return new AMap.Marker(MarkerOptions); } // 地图上add管道 addPipeLine(objData, component) { const { path, pipePressure, iconType } = objData; // 根据压力获取颜色 const color = pipeColor[pipePressure + 1]; const pipe = this.createPipeLine({ path: eval(path), strokeWeight: objData.strokeWeight || 4, strokeColor: color, extData: objData, cursor: "pointer", }); this.map.add(pipe); // pipe.hide(); if (!Array.isArray(this.pipeArr[iconType])) { this.pipeArr[iconType] = []; } this.pipeArr[iconType].push(pipe); // console.log(this.pipeArr); // this.map.panTo([path[0][0], path[0][1]]); this.pipeEvent(pipe, component); } createPipeLine(pipeLineOptions) { return new AMap.Polyline(pipeLineOptions); } pipeEvent(pipe, compontent) { pipe.on("mouseover", (e) => { const target = e.target; // 获取当前颜色 const options = target.getOptions(); options.strokeColor = "blue"; // options.strokeWeight =4; target.setOptions(options); }); pipe.on("mouseout", (e) => { const target = e.target; // 根据管道压力获取颜色 const { pipePressure } = target.getExtData(); const color = pipeColor[pipePressure + 1]; // 获取当前颜色 const options = target.getOptions(); options.strokeColor = color; // options.strokeWeight =2; target.setOptions(options); }); pipe.on("click", (e) => { const target = e.target; target.lnglat = e.lnglat; // 如果control==0就是默认值,没有使用123功能,就显示infowindow if (this.control == 0) { this.pipeClick(target, compontent); } else if (this.control == 2) { // 2是已经上图的设备拥有的编辑功能 } else if (this.control == 3) { // 3是删除操作 } }); } pipeClick(target, compontent) { const deviceExtData = target.getExtData(); const { lng, lat } = target.lnglat; // 创建一个可以控制的组件,将其dom插入infowindow this.infowindowComponent = this.createInfowindowDom( this.vue, this, deviceExtData, compontent ); // 没恩么用,控制台测试的时候用着玩的 // window.func = () => { // const { lng, lat } = target.getExtData(); // const cd = { // id: 1, // name: "9", // lng, // lat, // }; // this.infowindowComponentChange(cd); // target.setExtData(cd); // }; this.infowindow = new AMap.InfoWindow({ isCustom: true, content: this.infowindowComponent.$el, position: [lng, lat], // anchor: "top-left", // offset: [20, -15], anchor: "middle-left", offset: [20, 0], }); this.infowindow.open(this.map); } infowindowClose() { if (!this.infowindow) return; this.infowindow.close(); } // 设备以及公司过滤 allfilter(companyArr, typeArr) { for (let pipeItem in this.pipeArr) { this.pipeArr[pipeItem].forEach((pipe) => { const data = pipe.getExtData(); if (companyArr.indexOf(data.companyType + "") >= 0) { pipe.show(); } else { pipe.hide(); } }); } for (let deviceItem in this.allDevice) { this.allDevice[deviceItem].forEach((device) => { const data = device.getExtData(); // 设备过滤受到bigwindow页面的的两种制约,companyArr, typeArr 两个数组制约显示隐藏 // 燃气没有公司,所以没有device.companyType不收到公司的控制 const companyHas = companyArr.indexOf(data.companyType + "") >= 0; // 设备存在 受到设备按钮限制 const deviceHas = typeArr.indexOf(+data.iconType) >= 0; // 如果是燃气公司的话,不受按钮限制 const enterprise = data.iconType == 7 || data.iconType == 8; // 必须设备存在数组里,才会显示设备 !data.companyType代表用户不受公司制约 if (enterprise || (deviceHas && (companyHas || !data.companyType))) { device.show(); } else { device.hide(); } }); } } // 普通调用方法 // 设备报警 deviceAlarm(obj) { // 设备的类型 const { iconType } = obj; const publicId = obj.userId || obj.deviceId; // 找到这个设备所属的空间 const device = this.allDevice[iconType].filter( (item) => (item.getExtData().userId || item.getExtData().deviceId) == publicId )[0]; // 更改的icon const icon = svgAlarm[iconType]; device.setIcon(icon); device.setzIndex(13); // 将旧的值缓存一下 // device.oldData = device.getExtData(); // device.setExtData(obj); // 可能出现infoWindow数据变化 this.alarmInfowindowChange(obj); if (!this.alarmObj[iconType]) { this.alarmObj[iconType] = []; } const ind = this.alarmObj[iconType].indexOf(publicId); if (ind < 0) { this.alarmObj[iconType].push(publicId); } } // 报警时候,可能发生infowindowComponent存在,也要变化 alarmInfowindowChange(obj) { if (!this.infowindowComponent) return; // 如果infowindow是打开的,就改变里面的数据 必须是 调压箱2 阀门3 用户6 才会发生变化电话 const infowindowDeviceType = [2, 3, 6].includes( this.infowindowComponent.deviceData.iconType ); // 如果存在userId就用userId,如果存在deviceId就用deviceId const infowindowDeviceId = this.infowindowComponent.deviceData.userId || this.infowindowComponent.deviceData.deviceId; // 如果存在userId就用userId,如果存在deviceId就用deviceId const objId = obj.userId || obj.deviceId; // 如果两个值匹配,才可以改变infowindow身上的组件 const infowindowComponentHas = infowindowDeviceId == objId; if ( this.infowindowComponent && infowindowDeviceType && infowindowComponentHas ) { // this.infowindowComponentChange(obj); if (this.infowindowComponent.http) { this.infowindowComponent.myHttp(); } } } //如果传过来的报警设备中,有不在deviceAlarm中,就是恢复的设备恢复 relieveAlarm(alarmObj) { // 把报警数组改变结构,如果有userId就是用户,如果有deviceId就是其他设备 const httpObj = {}; alarmObj.forEach((item) => { const publicId = item.userId || item.deviceId; if (!Array.isArray(httpObj[item.iconType])) { httpObj[item.iconType] = []; } httpObj[item.iconType].push(publicId); }); // 循环现有报警设备 for (let iconType in this.alarmObj) { const arr = this.alarmObj[iconType]; // 循环现有报警设备 for (let i = 0; i < arr.length; i++) { // 看看现有报警设备中有没有不在 报警数组中的值,有就恢复可能是userId,也可能是deviceId const publicId = arr[i]; // 看看接口传来的数据中还有没有这些Id,如果没有了,就是修好了 const hasId = httpObj[iconType]?.includes(publicId); // 如果已经不在报警数组中,就要去大数组中找他,利用userId // 如果id不存在了,说明不报警了 if (!hasId) { // 过滤出来这个要恢复的设备 const device = this.allDevice[iconType].filter( (item) => (item.getExtData().userId || item.getExtData().deviceId) == publicId )[0]; // 恢复 const icon = svgUrl[iconType]; device.setIcon(icon); // 赋值 // const deviceData = { ...device.oldData }; // device.setExtData(deviceData); // device.oldData = null; // 如果infowindow是打开的 this.alarmInfowindowChange(device.getExtData()); // 在arr中删掉 arr.splice(i, 1); // 由于删掉了当前,所以要--恢复位置 i--; } } } } // ws协议方法 wsAlarm(obj) { const { iconType, userId, userStatus } = obj; const decice = this.allDevice[iconType].filter( (item) => item.getExtData().userId == userId )[0]; let icon = null; // 2报警,1恢复 if (userStatus == 2) { icon = svgAlarm[iconType]; device.oldData = device.getExtData(); device.setExtData(obj); } else if (userStatus == 1) { icon = svgUrl[iconType]; const oldData = device.oldData; device.setExtData(oldData); device.oldData = null; } decice.setIcon(icon); } // 卫星图切换 satellite = null; changeMap(bool) { // 卫星, if (!bool) { if (this.satellite) return; this.satellite = new AMap.TileLayer.Satellite(); this.map.addLayer(this.satellite); } else { if (this.satellite) { this.map.removeLayer(this.satellite); this.satellite = null; } } } // 轨迹回放 /** * @description: * @param {*} vehicleId 设备id * @param {*} path 轨迹回访率丼 * @return {*} */ backTrack(vehicleId, path, times, component) { this.infowindowClose(); AMap.plugin("AMap.MoveAnimation", () => { let marker = this.allDevice[9].filter( (item) => item.getExtData().vehicleId == vehicleId )[0]; // 绘制轨迹 marker.polyline = new AMap.Polyline({ map: this.map, path, showDir: true, strokeColor: "#28F", //线颜色 // strokeOpacity: 1, //线透明度 strokeWeight: 6, //线宽 // strokeStyle: "solid" //线样式 }); marker.passedPolyline = new AMap.Polyline({ map: this.map, strokeColor: "#AF5", //线颜色 strokeWeight: 6, //线宽 }); marker.on("moving", (e) => { marker.passedPolyline.setPath(e.passedPath); // this.map.setCenter(e.target.getPosition(), true); // console.log(getPosition()); }); // 每个path的点 // marker.pointArr = []; // carTarget //点击的时候,先传进来一个点 const carPathData = { ...marker.getExtData(), time: times[0] }; carPathData.iconType = 14; this.addDevice(carPathData, component); // marker.pointArr.push(point); marker.on("moveend", (e) => { // this.addDevice(carPathData,carBackComponent); // 如果不是最后一个点,就创建一个新的worderpoint,如果是就不创建,并且把自身删除 let z = { longitude: e.pos[0], latitude: e.pos[1], iconType: 14, time: times[e.index], }; // if (e.index == path.length - 1) { // point = this.addDevice(z, null); // } else { this.addDevice(z, component); // workPoint.infoWindow.open(map,e.passedPos); // } console.log("定点", e); }); marker.moveAlong(path, { // 每一段的时长 duration: 8000, //可根据实际采集时间间隔设置 // JSAPI2.0 是否延道路自动设置角度在 moveAlong 里设置 autoRotation: true, }); }); } setCenter(path) { this.map.setCenter(path, true); } clearbackTrack(vehicleId) { // let marker = this.allDevice[9].filter( // (item) => item.getExtData().vehicleId == vehicleId // )[0]; this.allDevice["9"]?.forEach((item) => { if (item) { // 停止运动 item.stopMove(); // 删除每个点 console.log(item.pointArr); if (item.polyline) { this.map.remove(item.polyline); } if (item.passedPolyline) { this.map.remove(item.passedPolyline); } this.map.remove(item); } }); // 最后把数组清空 this.allDevice["9"] = []; // 把car的路径点也清空 this.allDevice["14"]?.forEach((iten) => { this.map.remove(iten); }); this.allDevice["14"] = []; this.infowindowClose(); } }