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 } } 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 } } 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()