吉安感知网项目-前端
shuishen
2026-02-05 0e5b1764e3eb59e5f4aac4d380c6fda3f0c1e2fe
feat:多边形绘制及编辑增加交叉判断
3 files modified
288 ■■■■■ changed files
applications/drone-command/src/utils/cesium/shapeTools/draw/DrawPolygonTool.js 126 ●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/shapeTools/edit/EditPolygonTool.js 98 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue 64 ●●●●● patch | view | raw | blame | history
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()