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

效果图:

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)

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

相关推荐
白羊@10 分钟前
鸿蒙案例---生肖抽卡
前端·javascript·华为·harmonyos
橙某人16 分钟前
🍊🍊🍊在网格中进行拖动布局-Javascript、Vue
前端·javascript·vue.js
若川18 分钟前
Taro 4 已发布:11. Taro 是如何解析入口配置 app.config.ts 和页面配置的?
前端·javascript·微信小程序
八了个戒22 分钟前
「数据可视化 D3系列」入门第一章:Hello D3.js
开发语言·前端·javascript·数据可视化·canvas
·薯条大王23 分钟前
Node.js 操作 MySQL 数据库
javascript·数据库·mysql
二川bro32 分钟前
深度解析 Vue 项目 Webpack 分包与合包 一文读懂
前端·vue.js·webpack
getapi33 分钟前
flutter底部导航代码解释
前端·javascript·flutter
nui11138 分钟前
汽配快车道解决chrome backgroud.js(Service Worker) XMLHttpRequest is not defined问题
前端·javascript·chrome
团酱1 小时前
ESLint常见错误
开发语言·javascript·ecmascript
CodeCraft Studio1 小时前
PDF处理控件Aspose.PDF指南:使用 C# 从 PDF 文档中删除页面
前端·pdf·c#