import _ from 'lodash'
|
import { boxTransformScale } from '@/utils/turfFunc'
|
import { MAP_LEVEL } from '@/const/drc'
|
import * as Cesium from 'cesium'
|
import { PolyGradientMaterial } from '@/utils/cesium/Material'
|
import { useStore } from 'vuex'
|
import { getDeviceRegion, getDeviceRegionCount } from '@/api/home/aggregation'
|
import userStore from '@/store/modules/user'
|
import { areaCodeToArr, getContourByCode, getDockPolyLine, getLnglatAltitude } from '@/utils/cesium/mapUtil'
|
import getBaseConfig from '@/buildConfig/config'
|
import { getDroneStatusImage } from '@/utils/stateToImageMap/drone'
|
import { GroundCirclePrimitiveManager } from '@/utils/mapUtils'
|
|
import lowBattery from '@/assets/images/aiNowFly/low-battery.svg'
|
import mediumBattery from '@/assets/images/aiNowFly/medium-battery.svg'
|
import fullBattery from '@/assets/images/aiNowFly/full-charge.svg'
|
|
const { VITE_APP_BASE, VITE_APP_ENV, VITE_APP_REGION_URL } = import.meta.env
|
const { singleDockSystem } = getBaseConfig()
|
|
/**
|
* 使用通用的边界
|
* @param viewer
|
* @param options
|
*/
|
export const useBoundary = (viewer, options = {}) => {
|
const {
|
multiple = 1.4,
|
dockOptions = {},
|
boundaryChange,
|
boundaryColor,
|
dockRangeType,
|
useDockHeight = false,
|
} = options
|
const { scrollShowDock = false, showDock = false, showDockNameAndBattery = false } = dockOptions
|
|
const manager = new GroundCirclePrimitiveManager()
|
manager.init(viewer)
|
|
// 缩放判断list(包含省市县的轮廓边界散点数据)
|
let scalingJudgment = _.cloneDeep(MAP_LEVEL).map(i => ({ ...i, gJson: null, show: false, outline: {} }))
|
let active = null
|
const defaultDir = `${VITE_APP_REGION_URL}/100000/`
|
const userAreaCode = userStore.state.userInfo.detail.areaCode
|
const selectedAreaCode = userStore.state.selectedAreaCode
|
const dockSource = new Cesium.CustomDataSource('dockSource')
|
const outlineSource = new Cesium.CustomDataSource('outlineSource')
|
const minOutlineSource = new Cesium.CustomDataSource('minOutlineSource')
|
|
viewer.dataSources.add(dockSource)
|
viewer.dataSources.add(outlineSource)
|
viewer.dataSources.add(minOutlineSource)
|
let initDockList = []
|
let showDockSnList = null
|
|
if (scrollShowDock || showDock) {
|
getDeviceRegionFun().then(list => {
|
initDockList = list
|
if (showDock && !scrollShowDock) droneSplashed()
|
})
|
}
|
|
// 获取设备列表
|
async function getDeviceRegionFun() {
|
const res = await getDeviceRegion({ areaCode: selectedAreaCode })
|
return res?.data?.data || []
|
}
|
|
// 确定缩放比例
|
const determineScaling = () => {
|
if (!viewer) return
|
let height = viewer.camera.positionCartographic.height
|
// 根据高度展示对应的 gJson
|
for (let [index, item] of scalingJudgment.entries()) {
|
if (!item.show) return
|
if (height > item.heightRange[0] && height <= item.heightRange[1]) {
|
if (active === item.name) return
|
boundaryChange?.(item?.gJson?.features)
|
active = item.name
|
removeBoundary()
|
renderOutline(item)
|
scrollShowDock && removeDockCover()
|
item.gJson || (scrollShowDock && droneSplashed(item))
|
break
|
}
|
}
|
}
|
|
const removeBoundary = () => {
|
minOutlineSource?.entities.removeAll()
|
outlineSource?.entities?.removeAll()
|
}
|
const removeAllEntities = () => {
|
removeBoundary()
|
removeDockCover()
|
}
|
|
const removeDockCover = () => {
|
dockSource?.entities?.removeAll()
|
manager?.removeAll()
|
}
|
|
const getFiler = async url => {
|
const res = await fetch(url)
|
return await res.json()
|
}
|
|
function getBase64Image(imgUrl) {
|
const img = new Image()
|
const canvas = document.createElement('canvas')
|
const ctx = canvas.getContext('2d')
|
|
return new Promise(resolve => {
|
img.onload = function () {
|
canvas.width = img.width
|
canvas.height = img.height
|
ctx.drawImage(img, 0, 0)
|
const base64 = canvas.toDataURL('image/png')
|
resolve(base64)
|
}
|
img.src = imgUrl
|
})
|
}
|
|
function batteryLevelSvg(batteryNum) {
|
if (batteryNum <= 30) {
|
return lowBattery
|
} else if (batteryNum > 30 && batteryNum <= 60) {
|
return mediumBattery
|
} else {
|
return fullBattery
|
}
|
}
|
|
async function nestSVG(nickname, batteryImgUrl, batteryNum) {
|
let txt = nickname.length < 4 ? 130 : nickname.length * 38
|
let tranx = nickname.length < 4 ? 86 : nickname.length * 26 + 3
|
const batteryBase64Image = await getBase64Image(batteryImgUrl)
|
const svg = `
|
<svg width="${txt}" height="100" xmlns="http://www.w3.org/2000/svg">
|
<g transform="translate(${tranx}, 10)">
|
<!-- 电池图标 -->
|
<image href="${batteryBase64Image}" x="0" y="2" width="12" height="13"/>
|
<!-- 电量值 -->
|
<text x="12" y="10" font-size="14" fill="${
|
batteryNum <= 30 ? '#FF604B' : batteryNum >= 60 ? '#40FF5C' : '#00FFF2'
|
}" text-anchor="start" dominant-baseline="middle" font-weight="bolder" font-family="Source Han Sans CN" text-rendering="geometricPrecision">
|
${batteryNum}%
|
</text>
|
</g>
|
</svg>
|
`
|
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
|
}
|
|
// 无人机散点渲染
|
const droneSplashed = () => {
|
manager.removeAll()
|
let showDockList =
|
showDockSnList === null
|
? _.cloneDeep(initDockList)
|
: initDockList.filter(item => showDockSnList.includes(item.device_sn))
|
|
showDockList.forEach(async (item, index) => {
|
if (item.status === 'OFFLINE') return
|
let polyline = {}
|
|
if (dockRangeType === 2) {
|
manager.addCircleOutline({
|
data: {
|
lng: item.longitude,
|
lat: item.latitude,
|
},
|
frameColor: Cesium.Color.fromCssColorString('#7FFFD4'),
|
})
|
} else {
|
manager.addCircle({
|
data: {
|
lng: item.longitude,
|
lat: item.latitude,
|
},
|
materialColor: item.color ? item.color : Cesium.Color.CORNFLOWERBLUE.withAlpha(0.3),
|
})
|
}
|
|
if (useDockHeight) {
|
polyline = getDockPolyLine(item, viewer)
|
}
|
const batteryImgUrl = batteryLevelSvg(item.capacity_percent)
|
const combinedImage = await nestSVG(item.nickname, batteryImgUrl, item.capacity_percent)
|
let pointObj = {}
|
try {
|
pointObj = await getLnglatAltitude(item.longitude, item.latitude, viewer)
|
} catch (e) {}
|
const position = Cesium.Cartesian3.fromDegrees(
|
+item.longitude,
|
+item.latitude,
|
useDockHeight ? +item.height : pointObj?.height || 0
|
)
|
if (showDockNameAndBattery) {
|
// 带电量
|
dockSource.entities.add({
|
position,
|
billboard: {
|
// new Cesium.ConstantProperty(pointData.status === 'OFFLINE' ? endingImg : unSelectedOnline),
|
image: combinedImage,
|
},
|
})
|
}
|
dockSource.entities.add({
|
label: {
|
text: item.nickname,
|
font: 'bold 16px Source Han Sans CN',
|
fillColor: Cesium.Color.WHITE,
|
outlineColor: Cesium.Color.BLACK,
|
outlineWidth: 2,
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
pixelOffset: new Cesium.Cartesian2(0, -24),
|
},
|
position,
|
polyline,
|
billboard: {
|
image: getDroneStatusImage(item.status),
|
width: 60,
|
height: 60,
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
eyeOffset: new Cesium.Cartesian3(0, 0, -5),
|
},
|
})
|
})
|
}
|
|
// 渲染各个独立的轮廓
|
const renderOutline = item => {
|
// 加载大边界
|
item.outline &&
|
Cesium.GeoJsonDataSource.load(item.outline).then(dataSource => {
|
const entities = dataSource.entities.values
|
entities.forEach((entity, index) => {
|
// 创建独立折线作为轮廓
|
const positions = entity.polygon.hierarchy.getValue().positions
|
const randomInt = Math.floor(Math.random() * 5) + 1
|
let polygon = {}
|
if (item.name === '县' && !scalingJudgment[0].outline) {
|
let material = new PolyGradientMaterial({
|
color: Cesium.Color.fromCssColorString(arrColor[randomInt]),
|
opacity: 0.7,
|
alphaPower: 1.3,
|
})
|
entity.polygon.extrudedHeight = (entity.properties.childrenNum._value || 1) * 500
|
entity.polygon.material = material
|
polygon = entity.polygon
|
entity.polygon.outline = false // 显示边框
|
}
|
outlineSource.entities.add({
|
polyline: {
|
positions: positions,
|
width: 5, // 直接设置宽度
|
clampToGround: true,
|
material: Cesium.Color.fromCssColorString(boundaryColor),
|
},
|
polygon,
|
})
|
})
|
})
|
|
if (!item.gJson) return
|
// 加载小边界
|
Cesium.GeoJsonDataSource.load(item.gJson).then(dataSource => {
|
// 获取数据源中的实体
|
const entities = dataSource.entities.values
|
entities.forEach(entity => {
|
const positions = entity.polygon.hierarchy.getValue().positions
|
minOutlineSource.entities.add({
|
polyline: {
|
positions: positions,
|
width: 1, // 直接设置宽度
|
clampToGround: true,
|
material: Cesium.Color.fromCssColorString(boundaryColor),
|
},
|
})
|
})
|
})
|
}
|
|
const findFun = (featItem, numItem) => Number(featItem.region_code.slice(0, 6)) === numItem.properties.adcode
|
|
// 打开边界
|
const openContour = async () => {
|
const areaCode = selectedAreaCode || userAreaCode
|
viewer.scene.postRender.removeEventListener(determineScaling)
|
if (!areaCode) return
|
const hierarchy = areaCodeToArr(areaCode.slice(0, 6))
|
const jsonPath = hierarchy.join('/')
|
scalingJudgment = scalingJudgment.map(item => ({ ...item, show: true }))
|
scalingJudgment[0].gJson = null
|
active = null
|
const res = await getDeviceRegionCount({ areaCode })
|
const list = res?.data?.data || []
|
try {
|
// 省市县三级
|
if (hierarchy.length === 1) {
|
const gJson1 = await getFiler(`${defaultDir}${jsonPath}/indexDistrict.json`)
|
const gJson2 = await getFiler(`${defaultDir}${jsonPath}/index.json`)
|
scalingJudgment[1].gJson = {
|
...gJson1,
|
features: gJson1.features.map(item => {
|
const findData = list.flatMap(item => item.childrens || []).find(item1 => findFun(item1, item))
|
return { ...item, data: { ...findData } }
|
}),
|
}
|
scalingJudgment[2].gJson = gJson2
|
}
|
// 市县两级
|
if (hierarchy.length === 2) {
|
scalingJudgment[2].gJson = null
|
scalingJudgment[2].show = false
|
scalingJudgment[1].gJson = await getFiler(`${defaultDir}${jsonPath}/index.json`)
|
}
|
// 区县一级
|
if (hierarchy.length === 3) {
|
scalingJudgment[1].gJson = null
|
scalingJudgment[1].show = false
|
scalingJudgment[2].gJson = null
|
scalingJudgment[2].show = false
|
}
|
// 轮廓
|
const outlineGJson = await getContourByCode(areaCode.slice(0, 6))
|
scalingJudgment.forEach(item => item.show && (item.outline = outlineGJson))
|
renderOutline(scalingJudgment[(hierarchy.length - 3) * -1])
|
} catch (e) {}
|
try {
|
viewer?.scene?.postRender?.addEventListener(determineScaling)
|
}catch (e) {
|
|
}
|
}
|
|
// 关闭边界
|
function closeContour() {
|
removeAllEntities()
|
viewer.scene.postRender.removeEventListener(determineScaling)
|
}
|
|
// 飞向边界
|
async function flyToBoundary() {
|
// 单机巢系统
|
if (singleDockSystem) {
|
if (!initDockList.length) {
|
initDockList = await getDeviceRegionFun()
|
}
|
viewer?.camera.flyTo({
|
destination: Cesium.Cartesian3.fromDegrees(initDockList[0].longitude, initDockList[0].latitude, 22000),
|
duration: 0,
|
})
|
return
|
}
|
const areaCode = selectedAreaCode || userAreaCode
|
const gJson = await getContourByCode(areaCode.slice(0, 6))
|
const dataSource = await Cesium.GeoJsonDataSource.load(gJson)
|
if (!dataSource) return
|
viewer.dataSources.add(dataSource)
|
// 获取多边形边界所有点
|
let positionList = []
|
dataSource.entities.values.forEach(function (entity) {
|
if (entity.polygon) {
|
// 获取多边形的边界球
|
// const boundingSphere = entity.polygon.hierarchy.getValue(viewer.clock.currentTime).boundingSphere
|
let curPolygonPosition = entity.polygon.hierarchy._value.positions.map(item => {
|
let cartographic = Cesium.Cartographic.fromCartesian(item)
|
let lng = Cesium.Math.toDegrees(cartographic.longitude) // 经度
|
let lat = Cesium.Math.toDegrees(cartographic.latitude) // 纬度
|
return [_.round(lng, 6), _.round(lat, 6)]
|
})
|
positionList = positionList.concat(curPolygonPosition)
|
}
|
})
|
const newBox = boxTransformScale(positionList, multiple)
|
viewer.camera.flyTo({
|
destination: Cesium.Rectangle.fromDegrees(...newBox),
|
offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90), 0),
|
duration: 0.5,
|
})
|
|
dataSource.entities.values.forEach(entity => {
|
entity.polygon.material = new Cesium.ColorMaterialProperty(Cesium.Color.YELLOW.withAlpha(0))
|
entity.polygon.outline = new Cesium.ConstantProperty(false) // 显示边框
|
entity.polygon.outlineColor = new Cesium.ConstantProperty(Cesium.Color.AQUAMARINE.withAlpha(0))
|
})
|
}
|
|
/**
|
* 设置要显示的机巢列表
|
* @param dockSns - 要显示的机巢设备序列号数组。如果为 undefined,则显示所有已初始化的机巢;如果为空数组,则不显示任何机巢。
|
*/
|
function setShowDock(dockSns) {
|
if (dockSns === undefined) {
|
showDockSnList = null
|
} else if (dockSns.length === 0) {
|
showDockSnList = []
|
} else {
|
showDockSnList = dockSns
|
}
|
removeDockCover()
|
if (!showDock) return
|
if (scrollShowDock ? active === '县' : true) {
|
droneSplashed()
|
}
|
}
|
function setDockCoverColor(arr) {
|
initDockList.forEach(item => {
|
arr.forEach(dock => {
|
item.color = dock.device_sn === item.device_sn ? dock.color : null
|
})
|
})
|
if (scrollShowDock ? active === '县' : true) {
|
droneSplashed()
|
}
|
}
|
onBeforeUnmount(() => {
|
manager.destroy()
|
})
|
|
return {
|
openContour,
|
closeContour,
|
flyToBoundary,
|
setShowDock,
|
setDockCoverColor,
|
}
|
}
|