From 0e5b1764e3eb59e5f4aac4d380c6fda3f0c1e2fe Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Thu, 05 Feb 2026 17:23:42 +0800
Subject: [PATCH] feat:多边形绘制及编辑增加交叉判断

---
 applications/drone-command/src/utils/cesium/shapeTools/draw/DrawPolygonTool.js |  126 +++++++++++++++++++++---
 applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue       |   64 ++++++++++++
 applications/drone-command/src/utils/cesium/shapeTools/edit/EditPolygonTool.js |   98 ++++++++++++++++++-
 3 files changed, 263 insertions(+), 25 deletions(-)

diff --git a/applications/drone-command/src/utils/cesium/shapeTools/draw/DrawPolygonTool.js b/applications/drone-command/src/utils/cesium/shapeTools/draw/DrawPolygonTool.js
index c932d8b..88674c7 100644
--- a/applications/drone-command/src/utils/cesium/shapeTools/draw/DrawPolygonTool.js
+++ b/applications/drone-command/src/utils/cesium/shapeTools/draw/DrawPolygonTool.js
@@ -11,6 +11,69 @@
 	fill: style?.fill || DEFAULT_STYLE.fill,
 	outline: style?.outline || DEFAULT_STYLE.outline,
 })
+const resolveColorWithAlpha = (baseColor, alphaSource) => {
+	if (!baseColor) return alphaSource
+	const alpha = typeof alphaSource?.alpha === 'number' ? alphaSource.alpha : 1
+	return baseColor.withAlpha(alpha)
+}
+
+const normalizeRing = points => {
+	if (!Array.isArray(points)) return []
+	const ring = points.filter(Boolean)
+	if (ring.length < 2) return ring
+	const first = ring[0]
+	const last = ring[ring.length - 1]
+	if (Cesium.Cartesian3.equals(first, last)) {
+		return ring.slice(0, -1)
+	}
+	return ring
+}
+
+const isSelfIntersecting = positions => {
+	const ring = normalizeRing(positions)
+	if (ring.length < 4) return false
+	const epsilon = 1e-12
+	const toPoint = cartesian => {
+		const carto = Cesium.Cartographic.fromCartesian(cartesian)
+		return { x: carto.longitude, y: carto.latitude }
+	}
+	const pts = ring.map(toPoint)
+	const orientation = (a, b, c) => {
+		const value = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y)
+		if (Math.abs(value) < epsilon) return 0
+		return value > 0 ? 1 : 2
+	}
+	const onSegment = (a, b, c) =>
+		b.x <= Math.max(a.x, c.x) + epsilon &&
+		b.x + epsilon >= Math.min(a.x, c.x) &&
+		b.y <= Math.max(a.y, c.y) + epsilon &&
+		b.y + epsilon >= Math.min(a.y, c.y)
+	const segmentsIntersect = (p1, q1, p2, q2) => {
+		const o1 = orientation(p1, q1, p2)
+		const o2 = orientation(p1, q1, q2)
+		const o3 = orientation(p2, q2, p1)
+		const o4 = orientation(p2, q2, q1)
+		if (o1 !== o2 && o3 !== o4) return true
+		if (o1 === 0 && onSegment(p1, p2, q1)) return true
+		if (o2 === 0 && onSegment(p1, q2, q1)) return true
+		if (o3 === 0 && onSegment(p2, p1, q2)) return true
+		if (o4 === 0 && onSegment(p2, q1, q2)) return true
+		return false
+	}
+	const count = pts.length
+	for (let i = 0; i < count; i += 1) {
+		const p1 = pts[i]
+		const q1 = pts[(i + 1) % count]
+		for (let j = i + 1; j < count; j += 1) {
+			const isAdjacent = j === i || j === i + 1 || (i === 0 && j === count - 1)
+			if (isAdjacent) continue
+			const p2 = pts[j]
+			const q2 = pts[(j + 1) % count]
+			if (segmentsIntersect(p1, q1, p2, q2)) return true
+		}
+	}
+	return false
+}
 
 export class DrawPolygonTool extends ToolBase {
 	constructor(viewer, options = {}) {
@@ -23,6 +86,7 @@
 		this.floatPosition = null
 		this.isDrawing = false
 		this.lastMousePosition = null
+		this.isIntersecting = false
 		this.style = resolveStyle(options?.style)
 	}
 
@@ -50,6 +114,13 @@
 		const position = this.getPositionFromScreen(click.position)
 		if (!position) return
 
+		const preview = [...this.positions, position]
+		if (isSelfIntersecting(preview)) {
+			this.isIntersecting = true
+			this.tooltip.show(this.getTipText(), click.position)
+			return
+		}
+
 		this.addPoint(position)
 		this.tooltip.show(this.getTipText(), click.position)
 	}
@@ -66,15 +137,20 @@
 
 	handleMouseMove(movement) {
 		if (!this.isDrawing) return
-		this.tooltip.show(this.getTipText(), movement.endPosition)
 		if (this.positions.length === 0) return
 		const position = this.getPositionFromScreen(movement.endPosition)
 		if (!position) return
 		this.lastMousePosition = position
 		this.floatPosition = position
+		this.tooltip.show(this.getTipText(), movement.endPosition)
 	}
 
 	getTipText() {
+		const preview = this.getPreviewPositions()
+		this.isIntersecting = isSelfIntersecting(preview)
+		if (this.isIntersecting) {
+			return '区域存在交叉,请调整位置'
+		}
 		const count = this.positions.length
 		if (count === 0) {
 			return '单击增加点'
@@ -116,27 +192,41 @@
 		}
 	}
 
+	getPreviewPositions() {
+		if (this.positions.length === 0) return []
+		if (!this.isDrawing || !this.floatPosition) return this.positions
+		return [...this.positions, this.floatPosition]
+	}
+
 	createEntities() {
-		const getPreviewPositions = () => {
-			if (this.positions.length === 0) return []
-			if (!this.isDrawing || !this.floatPosition) return this.positions
-			return [...this.positions, this.floatPosition]
+		const getIntersecting = () => {
+			const preview = this.getPreviewPositions()
+			this.isIntersecting = isSelfIntersecting(preview)
+			return this.isIntersecting
 		}
 
 		this.polygonEntity = this.dataSource.entities.add({
 			polygon: {
-				hierarchy: new Cesium.CallbackProperty(() => new Cesium.PolygonHierarchy(getPreviewPositions()), false),
-				material: this.style.fill,
+				hierarchy: new Cesium.CallbackProperty(
+					() => new Cesium.PolygonHierarchy(this.getPreviewPositions()),
+					false
+				),
+				material: new Cesium.ColorMaterialProperty(
+					new Cesium.CallbackProperty(() => {
+						if (getIntersecting()) return resolveColorWithAlpha(Cesium.Color.RED, this.style.fill)
+						return this.style.fill
+					}, false)
+				),
 				outline: false,
 				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
-				show: new Cesium.CallbackProperty(() => getPreviewPositions().length >= 3, false),
+				show: new Cesium.CallbackProperty(() => this.getPreviewPositions().length >= 3, false),
 			},
 		})
 
 		this.polylineEntity = this.dataSource.entities.add({
 			polyline: {
 				positions: new Cesium.CallbackProperty(() => {
-					const positions = getPreviewPositions()
+					const positions = this.getPreviewPositions()
 					if (positions.length < 2) return positions
 					if (positions.length >= 3) {
 						return [...positions, positions[0]]
@@ -145,8 +235,13 @@
 				}, false),
 				clampToGround: true,
 				width: 2,
-				material: this.style.outline,
-				show: new Cesium.CallbackProperty(() => getPreviewPositions().length >= 2, false),
+				material: new Cesium.ColorMaterialProperty(
+					new Cesium.CallbackProperty(() => {
+						if (getIntersecting()) return resolveColorWithAlpha(Cesium.Color.RED, this.style.outline)
+						return this.style.outline
+					}, false)
+				),
+				show: new Cesium.CallbackProperty(() => this.getPreviewPositions().length >= 2, false),
 			},
 		})
 	}
@@ -180,6 +275,7 @@
 		if (this.positions.length < 3) return
 		this.isDrawing = false
 		this.floatPosition = null
+		this.isIntersecting = false
 		this.notify('getPolygonPositions', this.positions)
 		this.tooltip.hide()
 		this.clearPreviewEntities()
@@ -187,12 +283,6 @@
 
 	setStyle(style) {
 		this.style = resolveStyle(style)
-		if (this.polygonEntity?.polygon) {
-			this.polygonEntity.polygon.material = this.style.fill
-		}
-		if (this.polylineEntity?.polyline) {
-			this.polylineEntity.polyline.material = this.style.outline
-		}
 		if (this.dataSource) {
 			this.dataSource.entities.values
 				.filter(entity => entity?.name === POINT_ENTITY_NAME)
@@ -209,6 +299,7 @@
 		this.dataSource.entities.removeAll()
 		this.polygonEntity = null
 		this.polylineEntity = null
+		this.isIntersecting = false
 	}
 
 	getPositionFromScreen(screenPosition) {
@@ -237,5 +328,6 @@
 		this.floatPosition = null
 		this.isDrawing = false
 		this.lastMousePosition = null
+		this.isIntersecting = false
 	}
 }
diff --git a/applications/drone-command/src/utils/cesium/shapeTools/edit/EditPolygonTool.js b/applications/drone-command/src/utils/cesium/shapeTools/edit/EditPolygonTool.js
index 4e43634..7d0bf60 100644
--- a/applications/drone-command/src/utils/cesium/shapeTools/edit/EditPolygonTool.js
+++ b/applications/drone-command/src/utils/cesium/shapeTools/edit/EditPolygonTool.js
@@ -12,6 +12,69 @@
 	fill: style?.fill || DEFAULT_STYLE.fill,
 	outline: style?.outline || DEFAULT_STYLE.outline,
 })
+const resolveColorWithAlpha = (baseColor, alphaSource) => {
+	if (!baseColor) return alphaSource
+	const alpha = typeof alphaSource?.alpha === 'number' ? alphaSource.alpha : 1
+	return baseColor.withAlpha(alpha)
+}
+
+const normalizeRing = points => {
+	if (!Array.isArray(points)) return []
+	const ring = points.filter(Boolean)
+	if (ring.length < 2) return ring
+	const first = ring[0]
+	const last = ring[ring.length - 1]
+	if (Cesium.Cartesian3.equals(first, last)) {
+		return ring.slice(0, -1)
+	}
+	return ring
+}
+
+const isSelfIntersecting = positions => {
+	const ring = normalizeRing(positions)
+	if (ring.length < 4) return false
+	const epsilon = 1e-12
+	const toPoint = cartesian => {
+		const carto = Cesium.Cartographic.fromCartesian(cartesian)
+		return { x: carto.longitude, y: carto.latitude }
+	}
+	const pts = ring.map(toPoint)
+	const orientation = (a, b, c) => {
+		const value = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y)
+		if (Math.abs(value) < epsilon) return 0
+		return value > 0 ? 1 : 2
+	}
+	const onSegment = (a, b, c) =>
+		b.x <= Math.max(a.x, c.x) + epsilon &&
+		b.x + epsilon >= Math.min(a.x, c.x) &&
+		b.y <= Math.max(a.y, c.y) + epsilon &&
+		b.y + epsilon >= Math.min(a.y, c.y)
+	const segmentsIntersect = (p1, q1, p2, q2) => {
+		const o1 = orientation(p1, q1, p2)
+		const o2 = orientation(p1, q1, q2)
+		const o3 = orientation(p2, q2, p1)
+		const o4 = orientation(p2, q2, q1)
+		if (o1 !== o2 && o3 !== o4) return true
+		if (o1 === 0 && onSegment(p1, p2, q1)) return true
+		if (o2 === 0 && onSegment(p1, q2, q1)) return true
+		if (o3 === 0 && onSegment(p2, p1, q2)) return true
+		if (o4 === 0 && onSegment(p2, q1, q2)) return true
+		return false
+	}
+	const count = pts.length
+	for (let i = 0; i < count; i += 1) {
+		const p1 = pts[i]
+		const q1 = pts[(i + 1) % count]
+		for (let j = i + 1; j < count; j += 1) {
+			const isAdjacent = j === i || j === i + 1 || (i === 0 && j === count - 1)
+			if (isAdjacent) continue
+			const p2 = pts[j]
+			const q2 = pts[(j + 1) % count]
+			if (segmentsIntersect(p1, q1, p2, q2)) return true
+		}
+	}
+	return false
+}
 
 const normalizePoints = points =>
 	(points || []).map(point => ({
@@ -30,6 +93,8 @@
 		this.positions = []
 		this.isDragging = false
 		this.draggedIndex = -1
+		this.dragSnapshot = null
+		this.isIntersecting = false
 		this.style = resolveStyle(options?.style)
 	}
 
@@ -68,6 +133,7 @@
 			this.rebuildPointEntities()
 			this.isDragging = true
 			this.draggedIndex = rightIndex
+			this.dragSnapshot = this.positions.map(position => Cesium.Cartesian3.clone(position))
 			this.disableMapControl()
 			return
 		}
@@ -76,6 +142,7 @@
 		if (typeof index !== 'number') return
 		this.isDragging = true
 		this.draggedIndex = index
+		this.dragSnapshot = this.positions.map(position => Cesium.Cartesian3.clone(position))
 		this.disableMapControl()
 	}
 
@@ -84,6 +151,14 @@
 		this.isDragging = false
 		this.draggedIndex = -1
 		this.enableMapControl()
+		if (this.isIntersecting && this.dragSnapshot?.length) {
+			this.positions = this.dragSnapshot.map(position => Cesium.Cartesian3.clone(position))
+			this.isIntersecting = false
+			this.rebuildPointEntities()
+			this.dragSnapshot = null
+			return
+		}
+		this.dragSnapshot = null
 		this.notify('getPolygonPositions', this.positions)
 	}
 
@@ -99,6 +174,7 @@
 		const position = this.getPositionFromScreen(movement.endPosition)
 		if (!position) return
 		this.positions[this.draggedIndex] = position
+		this.isIntersecting = isSelfIntersecting(this.positions)
 		const pointEntity = this.dataSource.entities.values.find(
 			entity => entity?.name === POINT_ENTITY_NAME && entity.customData?.index === this.draggedIndex
 		)
@@ -122,7 +198,12 @@
 		this.polygonEntity = this.dataSource.entities.add({
 			polygon: {
 				hierarchy: new Cesium.CallbackProperty(() => new Cesium.PolygonHierarchy(this.positions), false),
-				material: this.style.fill,
+				material: new Cesium.ColorMaterialProperty(
+					new Cesium.CallbackProperty(() => {
+						if (this.isIntersecting) return resolveColorWithAlpha(Cesium.Color.RED, this.style.fill)
+						return this.style.fill
+					}, false)
+				),
 				outline: false,
 				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
 				show: new Cesium.CallbackProperty(() => this.positions.length >= 3, false),
@@ -137,7 +218,12 @@
 				}, false),
 				clampToGround: true,
 				width: 2,
-				material: this.style.outline,
+				material: new Cesium.ColorMaterialProperty(
+					new Cesium.CallbackProperty(() => {
+						if (this.isIntersecting) return resolveColorWithAlpha(Cesium.Color.RED, this.style.outline)
+						return this.style.outline
+					}, false)
+				),
 				show: new Cesium.CallbackProperty(() => this.positions.length >= 2, false),
 			},
 		})
@@ -212,12 +298,6 @@
 
 	setStyle(style) {
 		this.style = resolveStyle(style)
-		if (this.polygonEntity?.polygon) {
-			this.polygonEntity.polygon.material = this.style.fill
-		}
-		if (this.polylineEntity?.polyline) {
-			this.polylineEntity.polyline.material = this.style.outline
-		}
 		if (this.dataSource) {
 			this.dataSource.entities.values
 				.filter(entity => entity?.name === POINT_ENTITY_NAME)
@@ -262,5 +342,7 @@
 		this.positions = []
 		this.isDragging = false
 		this.draggedIndex = -1
+		this.dragSnapshot = null
+		this.isIntersecting = false
 	}
 }
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
index 342b43c..0d7cb25 100644
--- a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -536,6 +536,60 @@
 	return Math.abs(left.lat - right.lat) < 0.000001 && Math.abs(left.lng - right.lng) < 0.000001
 }
 
+function normalizePolygonPoints (points) {
+	if (!Array.isArray(points)) return []
+	const ring = points.filter(point => point && Number.isFinite(point.lng) && Number.isFinite(point.lat))
+	if (ring.length < 2) return ring
+	const first = ring[0]
+	const last = ring[ring.length - 1]
+	if (isSameWktPoint(first, last)) {
+		return ring.slice(0, -1)
+	}
+	return ring
+}
+
+function isSelfIntersectingPolygon (points) {
+	const ring = normalizePolygonPoints(points)
+	if (ring.length < 4) return false
+	const epsilon = 1e-12
+	const orientation = (a, b, c) => {
+		const value = (b.lat - a.lat) * (c.lng - b.lng) - (b.lng - a.lng) * (c.lat - b.lat)
+		if (Math.abs(value) < epsilon) return 0
+		return value > 0 ? 1 : 2
+	}
+	const onSegment = (a, b, c) =>
+		b.lng <= Math.max(a.lng, c.lng) + epsilon &&
+		b.lng + epsilon >= Math.min(a.lng, c.lng) &&
+		b.lat <= Math.max(a.lat, c.lat) + epsilon &&
+		b.lat + epsilon >= Math.min(a.lat, c.lat)
+	const segmentsIntersect = (p1, q1, p2, q2) => {
+		const o1 = orientation(p1, q1, p2)
+		const o2 = orientation(p1, q1, q2)
+		const o3 = orientation(p2, q2, p1)
+		const o4 = orientation(p2, q2, q1)
+		if (o1 !== o2 && o3 !== o4) return true
+		if (o1 === 0 && onSegment(p1, p2, q1)) return true
+		if (o2 === 0 && onSegment(p1, q2, q1)) return true
+		if (o3 === 0 && onSegment(p2, p1, q2)) return true
+		if (o4 === 0 && onSegment(p2, q1, q2)) return true
+		return false
+	}
+
+	const count = ring.length
+	for (let i = 0; i < count; i += 1) {
+		const p1 = ring[i]
+		const q1 = ring[(i + 1) % count]
+		for (let j = i + 1; j < count; j += 1) {
+			const p2 = ring[j]
+			const q2 = ring[(j + 1) % count]
+			const isAdjacent = j === i || j === i + 1 || (i === 0 && j === count - 1)
+			if (isAdjacent) continue
+			if (segmentsIntersect(p1, q1, p2, q2)) return true
+		}
+	}
+	return false
+}
+
 function buildPolygonWktFromPoints (points) {
 	if (!Array.isArray(points) || points.length < 3) return null
 	const ring = points
@@ -928,6 +982,16 @@
 		const val = cartesian3Convert(item, viewer)
 		return { ...val, lng: val.longitude, lat: val.latitude }
 	})
+	if (currentShapeType.value === 'polygon' && isSelfIntersectingPolygon(pointList)) {
+		ElMessage.warning('多边形存在自相交,请调整后再保存')
+		if (activeToolMode.value === 'draw') {
+			pointList = []
+			activeShapeId.value = null
+			clearActiveTool()
+			startDraw('polygon')
+		}
+		return
+	}
 	const controlPoints = resolveControlPoints(meta, currentShapeType.value)
 	if (!activeAreaType.value) {
 		const detectAreaType = getDetectAreaTypeKey()

--
Gitblit v1.9.3