uniapp中使用高德地图实现电子围栏(适配APP H5 不支持小程序)

效果图

想法:点击地图生成一个marker,至少三个marker形成电子围栏,点击第四个以上改变电子围栏的形状, 点击已生成的marker,从电子围栏去掉。

这里使用的是renderjs,我原本的开发模式是vue3+ts,由于uniapp官网的map组件和ucharts无法实现定制需求。 为了高德地图和echarts的开发,不得不变成vue3+ts 与 vue2renderjs的混用,庆幸uniapp兼容这种混用的写法。 

renderjs不支持小程序,所以有app+小程序适配环境的朋友们,可以写俩套文件逻辑,分别去适配app和小程序。如果有更好的方案,欢迎大佬们留言。

renderjs | uni-app官网

实现功能:电子围栏的增删改查

注意事项:除了官网的以外,本人踩坑。

     1. 不支持vue-i18n, pinia等vue3插件, 所以在语言化方面和存储方面 尽可能使用uniapp内置的。

     2. 逻辑层和视图层的变量 不要一样的,容易混淆并且 变量数据会融合。

 话不多说 看代码,代码有注释

<!-- 地图 -->
<view id="fenceMap" style="width: 100%;height:100%;" ref="mapRef" :info="mapConfig"
		:change:info="fencedetailAmap.updateData">
</view>

普通script 逻辑层代码:

data() {
			return {
				keyCode: amap_web_key_passwordTest,
				key: amap_web_keyTest,
				lange: '',
				deviceId: '', // 设备id
				state: '', //  add edit look
				fenceNumber: '', // 围栏编号
				addressList: [], // 设备经纬度 lng lat
				mapConfig: {},
				fencePointsList: [], // 存储电子围栏的顶点坐标
			}
		},
		onLoad(options) {
			if (options && options.info) {
				const info = JSON.parse(options.info)

				this.state = info.state
				this.deviceId = info.deviceId
				this.lange = info.locale
				this.addressList = info.address

				if (info.iteminfo) {
					this.fenceNumber = info.iteminfo.number
					this.fencePointsList = info.iteminfo.coordinate_set
				}
			}
		},
		mounted() {
			this.changeMapConfig()
		},
		methods: {
			changeMapConfig() {
				const WG = gcoord.transform(
					[Number(this.addressList.lng), Number(this.addressList.lat)], // 经纬度坐标
					gcoord.WGS84, // 当前坐标系
					gcoord.GCJ02 // 目标坐标系
				);
				
				this.mapConfig = {
					key: this.key,
					keyCode: this.keyCode,
					lange: this.lange,
					state: this.state,
					addressList: this.addressList,
					center: [Number(WG[0].toFixed(6)), Number(WG[1].toFixed(6))],
					fencePointsList: [...this.fencePointsList]
				}
			},
			// renderjs传来的数据,
			updeRenderData(data) {
				console.log('renderjs传过来的', data);
				this.fencePointsList = data
			},

			btnClick() {
				if (!this.fenceNumber) {
					uni.showToast({
						icon: 'none',
						title: this.lange === 'en' ? 'Please enter the fence number' : '请输入围栏编号'
					})
					return;
				}
				// 新增
				if (this.state === 'add') {
					this.addFence()
				} else if (this.state === 'edit') {
					this.addFence()
				} else if (this.state === 'look') {
					this.state = 'edit'
					this.mapConfig.state = 'edit'
				}
			},
			// 新增
			async addFence() {
				if (this.fencePointsList.length < 3) {
					uni.showToast({
						icon: 'none',
						title: this.lange === 'en' ? 'At least three coordinates' : '坐标点至少三个'
					})
					return;
				}
				await addElefenceApi({}, true, {
					number: this.fenceNumber,
					device_id: this.deviceId,
					coordinate_set: JSON.stringify(this.fencePointsList.map(item => {
						return {
							lng: item[0],
							lat: item[1]
						}
					}))
				})
				uni.$emit('updataList')
				uni.navigateBack()
			},
			// 提示
			explainOpen() {
				this.$refs.notify.show();
			},
			// 返回
			leftClick() {
				uni.navigateBack()
			}
		}

  renderjs 视图层代码: 

<script module="fencedetailAmap" lang="renderjs">


data() {
			return {
				map: null, // 用于存储地图对象
				marker: null, // 存储marker对象  设备所在地

				fenceMarkers: [], // 存储所有生成的 markers
				polygon: null, // 存储电子围栏(多边形)
				fencePoints: [], // 存储电子围栏的顶点坐标
				amapAllInfo: {},
			}
		},
		methods: {
			updateData(newValue, oldValue, ownerInstance, instance) {
				this.$nextTick(() => {
					if (newValue && newValue.state) {
						this.amapAllInfo = newValue
						console.log('renderjs更新触发', this.amapAllInfo);
						// this.$ownerInstance.callMethod('getMessage', this.amapAllInfo)
						this.initAMap();
					}
				});
			},
			isApp() {
				// 判断是否在 APP 环境中(根据具体的环境进行判断)
				return !!(uni.getSystemInfoSync && uni.getSystemInfoSync().platform === 'app');
			},
			// 初始化高德地图 (在app中使用script加载方式速度更快,通过AMapLoader加载兼容性更好,首页需要加载快优先使用script方式)
			initAMap() {
				// if (this.isApp()) {
				// 在 APP 中使用 script 标签方式加载
				if (!window.AMap) {
					const script = document.createElement('script');
					script.type = 'text/javascript';
					script.src =
						`https://webapi.amap.com/maps?v=2.0&key=${this.amapAllInfo.key}&plugins=AMap.Scale,AMap.Polygon,AMap.Marker`;
					script.async = true;
					script.onload = () => {
						this.loadAMap(); // 地图脚本加载完成后执行
					};
					script.onerror = () => {
						console.error('加载高德地图失败');
					};
					document.head.appendChild(script);
				} else {
					this.loadAMap(); // 如果 AMap 已经加载,直接调用
				}
				// } 
				// else {
				// 	// 在 H5 和小程序中使用 AMapLoader
				// 	AMapLoader.load({
				// 		key: this.amapAllInfo.key, // 高德地图 API 密钥
				// 		version: '2.0', // 高德地图 API 的版本
				// 		plugins: ['AMap.Scale'], // 需要加载的插件
				// 	}).then(AMap => {
				// 		this.loadAMap(AMap); // 地图脚本加载完成后执行
				// 	}).catch((error) => {
				// 		console.error('加载高德地图失败', error);
				// 	});
				// }
			},
			// loadAMap(AMap) {
			loadAMap() {
				try {
					// 初始化地图
					this.map = new AMap.Map('fenceMap', {
						zoom: 14,
						center: this.amapAllInfo.center
					});

					const icon = new AMap.Icon({
						size: new AMap.Size(36, 36), //图标尺寸
						image: "static/icon_map_mark_red.png", //Icon 的图像
						imageOffset: new AMap.Pixel(0, 0), //图像相对展示区域的偏移量,适于雪碧图等
						imageSize: new AMap.Size(36, 36), //根据所设置的大小拉伸或压缩图片
					});

					this.marker = new AMap.Marker({
						position: new AMap.LngLat(this.amapAllInfo.center[0], this.amapAllInfo.center[1]), //点标记的位置
						offset: new AMap.Pixel(-10, -30), //偏移量
						icon: icon, //添加 Icon 实例 
					});

					// 回显
					if (['look', 'edit'].includes(this.amapAllInfo.state)) {
						console.log('回显', this.amapAllInfo.fencePointsList);
                      // 由于传给后端格式是[{lng:'xx',lat:'xx'},{lng:'xx',lat:'xx'}],
                      // 这种格式在回显 marker和电子围栏时会报错, 所以需要转换下格式。
						this.amapAllInfo.fencePointsList.forEach((position) => {
							this.addMarker(new AMap.LngLat(parseFloat(position.lng), parseFloat(position.lat)));
						});
						// 创建电子围栏
						if (this.fencePoints.length >= 3) {
							this.createFence();
						}
					}

					// 点击地图
					this.map.on('click', (MapsEvent) => {
						console.log('地图事件');
						this.onMapClick(MapsEvent)
					})

					this.map.add(this.marker)

				} catch (error) {
					console.error('初始化地图失败', error);
				}
			},
			// 点击地图
			onMapClick(e) {
				if (this.amapAllInfo.state === 'look') return;
				const position = e.lnglat; // 获取点击的位置
				console.log('点击地图position', position);
				this.addMarker(position); // 添加marker
				if (this.fencePoints.length >= 3) { // 如果已经有 3 个点,开始创建电子围栏
					this.createFence();
				}
				if (this.fencePoints.length > 3) { // 如果超过 3 个点,允许改变围栏形状
					this.updateFenceShape();
				}
			},
			// 添加marker标记
			addMarker(position) {
				console.log('添加数组的position', position);
				const icon = new AMap.Icon({
					size: new AMap.Size(36, 36),
					image: "static/icon_location.png",
					imageOffset: new AMap.Pixel(0, 0),
					imageSize: new AMap.Size(36, 36),
				});
				const marker = new AMap.Marker({
					position, // 点标记在地图上显示的位置
					offset: new AMap.Pixel(-10, -30), //偏移量
					icon: icon, //添加 Icon 实例 
					map: this.map, // marker所在的地图对象
				});
				// 如果数组中已经存在,就不push进去
				this.fenceMarkers.push(marker); // 将新 marker 添加到 markers 数组中
				this.fencePoints.push(position); // 更新围栏顶点
                // 地图使用onclick事件和marker的onlick事件会起冲突,
                // 点击marker事件会被冒泡到地图事件上,使用 e.stopPropagation() 和 e.preventDefault() 均不生效, 所以删除marker改成了双击
				marker.on('dblclick', () => { // 如果点击的是已存在的 marker,移除该 marker
					console.log('marker事件');

					if (this.amapAllInfo.state === 'look') return;
					this.removeMarker(marker);
				});
			},
			// 创建电子围栏(多边形)
			createFence() {
				// 只有在有 3 个以上的点时才创建围栏
				if (this.fencePoints.length >= 3) {
					// 清除之前的围栏
					if (this.polygon) {
						this.map.remove(this.polygon);
						this.polygon = null;
					}
				}
				// 使用围栏顶点创建一个多边形
				this.polygon = new AMap.Polygon({
					path: this.fencePoints, // 多边形轮廓线的节点坐标数组
					strokeColor: '#0000FF', // 线条颜色,
					strokeWeight: 3, // 轮廓线宽度
					strokeOpacity: 0.5, // 轮廓线透明度,取值范围 [0,1] ,0表示完全透明,1表示不透明。默认为0.5
					fillColor: '#0000FF', // 多边形填充颜色,
					fillOpacity: 0.3, // 多边形填充透明度,取值范围 [0,1] ,0表示完全透明,1表示不透明。默认为0.5
					map: this.map,
				});
				this.updeRender()
			},
			// 更新围栏形状(当点数大于 3 时)
			updateFenceShape() {
				if (this.fencePoints.length > 3) {
					// 例如:可以修改为圆形或其他形状
					// 或者直接将围栏更新为包含所有点的多边形
					this.polygon.setPath(this.fencePoints); // 更新围栏形状
				}
				this.updeRender()
			},
			// 从围栏中移除一个 marker
			removeMarker(marker) {
				// 从 fenceMarkers 数组中移除该 marker
				const index = this.fenceMarkers.indexOf(marker);
				if (index !== -1) {
					this.fenceMarkers.splice(index, 1);
				}

				// 从 fencePoints 中移除该点,转换 marker.getPosition() 为经纬度数组进行比较
				const position = marker.getPosition();
				console.log('position', position);
				console.log('this.fencePoints', this.fencePoints);
				const pointIndex = this.fencePoints.findIndex(point => {
					return point.lng === position.lng && point.lat === position.lat;
				});
				console.log('pointIndex', pointIndex);
				if (pointIndex !== -1) {
					this.fencePoints.splice(pointIndex, 1);
				}

				// 更新围栏
				if (this.fencePoints.length >= 3) {
					this.createFence(); // 重新创建围栏
				} else {
					// 如果少于 3 个点,移除围栏
					if (this.polygon) {
						this.map.remove(this.polygon);
						this.polygon = null;
					}
				}
				// 从地图上移除 marker
				this.map.remove(marker);
				this.updeRender()
			},
			updeRender() {
				console.log('向着逻辑层发送', this.fencePoints);
				this.$ownerInstance.callMethod('updeRenderData', this.fencePoints)
			}

		}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值