腾讯位置服务多边形绘制、编辑、删除

效果图:

1、子组件

java 复制代码
<template>
	<div class="tengxunMapPolygon" :style="{ height: `${height}px` }">
		<div :id="`container-${mid}`" class="xn-wh"></div>
		<div id="toolControl" v-if="showTools">
			<div
				class="toolItem"
				:class="{ active: activeType === 'polygon' }"
				@click="setActiveType('polygon')"
				title="添加"
			>
				添加
			</div>
			<div
				class="toolItem"
				:class="{ active: activeType === 'polygonEditor' }"
				@click="setActiveType('polygonEditor')"
				title="编辑"
			>
				编辑
			</div>
		</div>
		<div id="toolControl" style="top: 0px; left: 200px; width: 60px" v-if="onShow">
			<div class="toolItem" :class="{ active: activeType === 'delete' }" @click="setActiveType('delete')" title="删除">
				删除
			</div>
		</div>
	</div>
</template>
<script setup name="tengxunMapPolygon">
	import { onMounted, onUnmounted, reactive, ref } from 'vue'

	const props = defineProps({
		mid: {
			type: Number,
			default: new Date().getTime()
		},
		height: {
			type: Number,
			default: 800
		},
		apiKey: {
			type: String,
			required: true
		},
		center: {
			type: Array
		},
		markers: {
			type: Array
		},
		zoom: {
			type: Number,
			default: 12
		},
		showTools: {
			type: Boolean,
			default: false
		}
	})

	const emits = defineEmits(['polygonMapClick', 'drawComplete'])

	const onShow = ref(false)
	let map
	let editor = reactive()
	const markers = ref([])
	const polygons = ref([])
	const infoWindows = ref({})
	let activeType = ref('polygon')
	let geometryLayers = reactive({
		delete: null,
		polygon: null,
		polygonEditor: null
	})

	const setActiveType = (type) => {
		activeType.value = type
		if (editor) {
			editor.setActiveOverlay('polygon')
			onShow.value = false
			if (type === 'polygon') {
				editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.DRAW)
			} else if (type === 'polygonEditor') {
				onShow.value = true
				editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.INTERACT)
			} else if (type === 'delete') {
				const selectedList = editor.getSelectedList()
				console.log('selectedList', selectedList)
				if (selectedList && selectedList.length > 0) {
					const activeOverlay = editor.getActiveOverlay()
					console.log(activeOverlay)
					if (activeOverlay && activeOverlay.overlay) {
						const currentGeometries = activeOverlay.overlay.geometries || []
						const newGeometries = currentGeometries.filter(
							(geo) => !selectedList.some((selected) => selected.id === geo.id)
						)
						activeOverlay.overlay.setGeometries(newGeometries)


						let allPolygons = [];
						let polygonIds = [];
						currentGeometries.forEach((geo) => {
							if(!polygonIds.includes(geo.id)){
								polygonIds.push(geo.id)
								let points = [];
                                //绘制和编辑时数据不一致
								let geoPaths = geo.paths
								if(Array.isArray(geoPaths[0])){
									geoPaths = geoPaths[0]
								}
								// console.log(geo.paths, geoPaths)
								geoPaths.forEach((point) => {
									points.push({
										lng: point.lng,
										lat: point.lat
									})
								})
								allPolygons.push(points)
							}
						})

						emits('drawComplete', {
							type: 'polygon',
							action: 'delete',
							geometry: {
								paths: allPolygons
							}
						})

						//清空被选中图形
						editor.delete(selectedList[0].id)

					}
				}
			}
		}
	}

	// 初始化地图
	const initMap = () => {
		if (!window.TMap) {
			const script = document.createElement('script')
			script.type = 'text/javascript'
			script.src = `https://map.qq.com/api/gljs?v=1.exp&key=${props.apiKey}&libraries=tools,service`
			script.onload = () => {
				createMap()
			}
			document.head.appendChild(script)
		} else {
			createMap()
		}
	}

	// 创建地图实例
	const createMap = () => {
		try {
			map = new TMap.Map(document.getElementById(`container-${props.mid}`), {
				zoom: props.zoom,
				center: new TMap.LatLng(props.center.lat, props.center.lng),
				mapStyleId: props.mapStyle
			})

			if (window.TMap.tools && window.TMap.tools.GeometryEditor) {
				editor = new TMap.tools.GeometryEditor({
					map: map,
					overlayList: [
						{
							overlay: new TMap.MultiPolygon({
								map,
								styles: {
									default: new TMap.PolygonStyle({
										// color: '#d1d7e433',
										showBorder: true,
										// borderColor: '#417cfa',
										borderWidth: 2
									}),
									highlight: new TMap.PolygonStyle({
										color: 'rgba(255, 255, 0, 0.4)'
									})
								},
								geometries: []
							}),
							id: 'polygon',
							selectedStyleId: 'highlight'
						}
					],
					actionMode: TMap.tools.constants.EDITOR_ACTION.DRAW,
					activeOverlayId: 'polygon',
					snappable: true,
					selectable: true
				})

				// 监听绘制完成事件
				editor.on('draw_complete', (geometry) => {
					const activeOverlay = editor.getActiveOverlay()
					if (!activeOverlay || !activeOverlay.overlay) return
					// console.log('activeOverlay', activeOverlay)
					// 获取现有的几何图形列表
					const currentGeometries = activeOverlay.overlay.geometries || []
					let allPolygons = [];
					let polygonIds = [];
					currentGeometries.forEach((geo) => {
						if(!polygonIds.includes(geo.id)){
							polygonIds.push(geo.id)
							let points = [];
							let geoPaths = geo.paths
							if(Array.isArray(geoPaths[0])){
								geoPaths = geoPaths[0]
							}
							// console.log(geo.paths, geoPaths)
							geoPaths.forEach((point) => {
								points.push({
									lng: point.lng,
									lat: point.lat
								})
							})
							allPolygons.push(points)
						}
					})
					emits('drawComplete', {
						type: 'polygon',
						geometry: {
							paths: allPolygons
						}
					})
				})

				// 修改编辑完成事件监听
				editor.on('adjust_complete', (geometry) => {
					try {
						const activeOverlay = editor.getActiveOverlay()
						if (!activeOverlay || !activeOverlay.overlay) return

						// 获取现有的几何图形列表
						const currentGeometries = activeOverlay.overlay.geometries || []
						currentGeometries.push(geometry)
						let allPolygons = [];
						let polygonIds = [];
						currentGeometries.forEach((geo) => {
							if(!polygonIds.includes(geo.id)){
								polygonIds.push(geo.id)
								let points = [];
								let geoPaths = geo.paths
								if(Array.isArray(geoPaths[0])){
									geoPaths = geoPaths[0]
								}
								geoPaths.forEach((point) => {
									points.push({
										lng: point.lng,
										lat: point.lat
									})
								})
								allPolygons.push(points)
							}
						})
						// 发送更新后的数据
						emits('drawComplete', {
							type: 'polygon',
							geometry: {
								paths: allPolygons
							}
						})
					} catch (error) {
						console.error('处理编辑完成事件失败:', error)
					}
				})
			}

			// 添加地图点击事件监听
			map.on('click', (evt) => {
				emits('polygonMapClick', {
					lnglat: {
						lng: evt.latLng.lng,
						lat: evt.latLng.lat
					},
					type: evt.type
				})
			})
		} catch (error) {
			console.error('创建地图失败:', error)
		}
	}

	// 添加标记点
	const renderMarkerTx = (dataArr) => {
		try {
			// console.log('添加标记点,数据:', dataArr)
			clearOverlayTx() // 先清除已有标记

			if (!map) {
				console.error('地图未初始化')
				return
			}

			const markerLayer = new TMap.MultiMarker({
				map: map,
				styles: {
					marker: new TMap.MarkerStyle({
						width: 25,
						height: 35,
						anchor: { x: 16, y: 32 }
					})
				},
				geometries: dataArr.map((item) => ({
					id: item.id,
					styleId: 'marker',
					position: new TMap.LatLng(item.position[1], item.position[0]), // 注意经纬度顺序
					properties: {
						title: item.title
					}
				}))
			})

			markers.value.push(markerLayer)
			// console.log('标记点添加成功')
		} catch (error) {
			console.error('添加标记点失败:', error)
		}
	}

	// 修改多边形绘制方法
	const renderPolygon = (dataArr, option = {}) => {
		try {

			// 等待编辑器初始化完成
			if (!editor) {
				setTimeout(() => renderPolygon(dataArr, option), 100)
				return
			}
			//
			// 获取编辑器中的多边形图层
			const activeOverlay = editor.getActiveOverlay()
			if (!activeOverlay || !activeOverlay.overlay) {
				return
			}

			// 处理多个多边形
			let geometries = dataArr.map((polygonPoints, index) => {
				// 确保每个点都是有效的经纬度对象
				const validPoints = polygonPoints.filter(
					(point) => point && typeof point.lng === 'number' && typeof point.lat === 'number'
				)
				let paths = validPoints.map((point) => new TMap.LatLng(point.lat, point.lng))
				console.log(paths);
				return {
					paths: [paths]
				}
			})

			// 验证几何图形数据的有效性
			const validGeometries = geometries.filter((geo) => geo.paths && geo.paths[0] && geo.paths[0].length >= 3)

			if (validGeometries.length === 0) {
				// console.error('没有有效的多边形数据')
				return
			}

			// 更新编辑器中的多边形
			activeOverlay.overlay.setGeometries(validGeometries)
		} catch (error) {
			console.error('渲染多边形失败:', error)
			console.error('错误详情:', error.stack)
			console.error('输入数据:', JSON.stringify(dataArr))
		}
	}

	// 添加信息窗体
	const renderInfoWindow = (dataArr) => {
		dataArr.forEach((item) => {
			const position = new TMap.LatLng(item.position[0], item.position[1])
			const info = new TMap.InfoWindow({
				map: map,
				position,
				content: item.content.join('<br>'),
				offset: { x: 0, y: -32 }
			})
			infoWindows.value[item.position.join(',')] = info
			info.close() // 默认关闭
		})
	}

	// 打开信息窗体
	const openInfoWindow = (position) => {
		const key = position.join(',')
		if (infoWindows.value[key]) {
			infoWindows.value[key].open()
		}
	}

	// 清除覆盖物
	const clearOverlayTx = () => {
		// console.log('清除。。。。')
		markers.value.forEach((marker) => {
			marker.setMap(null)
		})
		markers.value = []

		Object.values(infoWindows.value).forEach((info) => {
			info.setMap(null)
		})
		infoWindows.value = {}
	}

	// 暴露获取地址的方法
	const getAddress = async (lnglat) => {
		return new Promise((resolve) => {
			geocoder.getAddress(lnglat, (status, result) => {
				if (status === 'complete' && result.info === 'OK') {
					resolve(result.regeocode.formattedAddress)
				} else {
					resolve(null)
				}
			})
		})
	}

	onMounted(() => {
		setTimeout(() => {
			initMap()
		}, 100)
	})

	onUnmounted(() => {
		if (map) {
			map.destroy()
		}
	})

	defineExpose({
		renderMarkerTx,
		renderInfoWindow,
		openInfoWindow,
		clearOverlayTx,
		renderPolygon,
		getAddress,
		setActiveType,
		editor: editor
	})
</script>

<style lang="less">
	.tengxunMapPolygon {
		position: relative;
		overflow: hidden;

		.xn-wh {
			width: 100%;
			height: 100%;
			min-height: 300px;
		}
	}

	#toolControl {
		position: absolute;
		top: -0px;
		left: 0px;
		right: 0px;
		margin: auto;
		width: 110px;
		z-index: 1001;
		background: #fff;
		padding: 5px;
		border-radius: 4px;
		box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
		display: flex;
	}

	.toolItem {
		padding: 4px 8px;
		border-radius: 3px;
		cursor: pointer;
		border: 1px solid #ffffff;
		font-size: 14px;
		color: #666;
		transition: all 0.3s;

		&:hover {
			border-color: #789cff;
			color: #789cff;
		}

		&.active {
			border-color: #d5dff2;
			background-color: #d5dff2;
			color: #1890ff;
		}
	}
</style>

2、父组件

java 复制代码
<template>
	<xn-form-container
		:title="formData.id ? '编辑车场管理' : '增加车场管理'"
		:width="700"
		v-model:open="open"
		:destroy-on-close="true"
		@close="onClose"
	>
		<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
			<a-form-item>
				<a-alert message="点击地图开始绘制范围,双击结束绘制" type="warning" closable />
				<TengxunMap
					:mid="2"
					:zoom="17"
					:height="300"
					:apiKey="props.apiKey"
					@polygonMapClick="onPolygonMapClick"
					@drawComplete="onDrawComplete"
					:center="mapCenter"
					:polygons="polygonPoints"
					:pitch="30"
					:showTools="true"
					ref="polygonMapRef"
				/>
			</a-form-item>
			<a-form-item label="车场范围:" name="areaPoints">
				<a-textarea v-model:value="formData.areaPoints" placeholder="请输入车场范围" allow-clear :disabled=true />
			</a-form-item>
		</a-form>
		<template #footer>
			<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
			<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
		</template>
	</xn-form-container>
</template>

<script setup name="parkinglotForm">
	import campusApi from '@/api/emap/campusApi'
import TengxunMap from '@/components/Map/tengxunMap/polygon.vue'
import { required, rules } from '@/utils/formRules'
import { cloneDeep } from 'lodash-es'
import { computed, nextTick, ref } from 'vue'

	// 抽屉状态
	const open = ref(false)
	const emit = defineEmits({ successful: null })
	const formRef = ref()
	// 表单数据
	const formData = ref({})
	const submitLoading = ref(false)
	const typeOptions = ref([])
	const orgOptions = ref([])
	const disabled = ref(false)
	const parkingIconId = ref()
	const tengxunMapRef = ref()
	const polygonMapRef = ref()
	const latitude = ref(32.27686)
	const longitude = ref(118.312082)
	const mapCenter = computed(() => ({
		lat: Number(latitude.value),
		lng: Number(longitude.value)
	}))

	const props = defineProps({
		apiKey: {
			type: String,
			required: true
		}
	})

	// 新增绘制相关的状态
	const isDrawing = ref(false)
	const polygonPoints = ref([])

	// 图标
	const getIconFile = (e) => {
		parkingIconId.value = e.id
		formData.value.parkingIconId = e.id
	}

	// 打开抽屉
	const onOpen = (record) => {
		disabled.value = false
		open.value = true
		formData.value = {
			status: false
		}

		if (record) {
			let recordData = cloneDeep(record)
			// 先设置表单数据
			formData.value = Object.assign({}, recordData)

			// 设置地图中心点和标记
			if (formData.value.lat && formData.value.lng) {
				// 更新中心点坐标
				latitude.value = Number(formData.value.lat)
				longitude.value = Number(formData.value.lng)
			}
			// 处理多边形数据
			if (formData.value.areaPoints) {
				nextTick(() => {
					onPolygonMapComplete()
				})
			}
		}
	
	}
	// 关闭抽屉
	const onClose = () => {
		formRef.value.resetFields()
		formData.value = {}
		open.value = false
	}

	campusApi.list().then((data) => {
		orgOptions.value = data
	})


	/**
	 * 第二张地图
	 * @param e
	 */
	// 处理第二个地图的点击事件(绘制多边形)
	const onPolygonMapClick = (e) => {
		// console.log('父组件开始绘制。。', e)
		if (!isDrawing.value) {
			// 开始绘制
			isDrawing.value = true
			polygonMapRef.value.setActiveType('polygon')
		}
	}

	// 添加绘制完成事件处理
	const onDrawComplete = (data) => {
		if (data.type === 'polygon') {
			const points = data.geometry.paths.map((point) => ({
				lng: point.lng,
				lat: point.lat
			}))
			formData.value.areaPoints = JSON.stringify(data.geometry.paths)
			console.log('areaPoints。',formData.value.areaPoints)
			isDrawing.value = false
		}
	}

	// 处理第二个地图的加载完成事件
	const onPolygonMapComplete = () => {
		if (formData.value.areaPoints) {
			try {
				const points = JSON.parse(formData.value.areaPoints)
				polygonPoints.value = points


				// 只绘制多边形,不绘制顶点标记
				polygonMapRef.value.renderPolygon(points)

			} catch (e) {
				console.error('解析或渲染多边形数据失败:', e)
				console.error('原始数据:', formData.value.areaPoints)
			}
		}
	}

	// 默认要校验的
	const formRules = {
		areaPoints: [required('请输入车场范围')]
	}
	// 验证并提交数据
	const onSubmit = () => {
		formRef.value
			.validate()
			.then(() => {
				submitLoading.value = true
				const formDataParam = cloneDeep(formData.value)
				console.log('formDataParam',formDataParam)
				dataApi
					.SubmitForm(formDataParam, formDataParam.id)
					.then(() => {
						onClose()
						emit('successful')
					})
					.finally(() => {
						submitLoading.value = false
					})
			})
			.catch(() => {})
	}
	// 抛出函数
	defineExpose({
		onOpen
	})
</script>

思路:

复制代码
editor.getActiveOverlay()获取图层上的所有多边形
复制代码
editor.getSelectedList()获取当前选中的多边形
复制代码
//清空被选中图形
editor.delete(selectedList[0].id)

这里遇到的问题是,绘制出来的多边形坐标和编辑时读取的左边层级不一致,需要出来一下,代码里有标注

相关推荐
护国神蛙2 分钟前
auto-i18n-translation-plugins 全自动国际化插件——常见问题汇总
前端·javascript·开源
高hongyuan10 分钟前
Nginx 代理访问一个 Web 界面时缺少内容
运维·前端·nginx
小old弟16 分钟前
讲一下,ts中interface和type的继承怎么写的
前端
无名之逆17 分钟前
使用 Hyperlane 框架的 WebSocket 功能
服务器·前端·网络·websocket·网络协议·http·rust
柯小慕19 分钟前
React自定义Hooks入门指南:让函数组件更强大
前端·react.js
莓事哒19 分钟前
Java Web应用程序实现用户登录、学生信息管理和验证码验证以及页面跳转等基本功能(IDEA)含(Ajax、JSTL)
java·前端·intellij-idea
半克唆麻21 分钟前
用 Lottie 做动画:一个前端菜鸡的真香日记
前端
narukeu23 分钟前
自用基于 TypeScript 的 WebSocket 客户端封装
javascript·websocket·typescript
再吃一根胡萝卜24 分钟前
persist 和 immer 中间件介绍及解决痛点
前端
前端小崔34 分钟前
从零开始学习three.js(10):后处理深度解析,从基础到高级应用
前端·three.js