| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706 |
- <script setup lang='ts'>
- import {
- Viewer, ScreenSpaceEventType, Cartesian3,
- Math as CMath, Color, EventHelper, UrlTemplateImageryProvider,
- DistanceDisplayCondition, ScreenSpaceEventHandler, WebMercatorTilingScheme,
- VerticalOrigin, HeightReference, Cartesian2, Cartographic, GeoJsonDataSource, SceneMode, WebMapTileServiceImageryProvider, GeographicTilingScheme
- } from 'cesium'
- import {
- computed, onMounted, onUnmounted, ref, watch
- } from 'vue'
- // import useStore from '@/pages/store/index'
- import lnglatIcon from '@/assets/icons/lnglat.svg'
- import camera from '@/assets/icons/camera.svg'
- import broadcast from '@/assets/icons/broadcast.svg'
- import preBranke from '@/assets/icons/preBranke.svg'
- import carBranke from '@/assets/icons/carBranke.svg'
- import { NInput, useNotification, useDialog } from 'naive-ui'
- import EquipmentService, { TreeList, Device } from '@/services/equipmentTree.service'
- const notification = useNotification()
- const dialog = useDialog()
- const equipmentService = new EquipmentService()
- // const store = useStore()
- const props = defineProps({
- type: { default: true, type: Boolean },
- checkList: { default: [], type: Array<TreeList> },
- treeList: { default: [], type: Array<TreeList> }
- })
- const isAdmin = computed(() => props.type)
- let viewer = null as Any
- let Helper = new EventHelper() as Any
- let handler = null as Any
- const currenIcon = ref({ icon: '', x: 0, y: 0 })
- const icontypes = [
- {
- name: '摄像机', type: 'camera', icon: camera, deviceType: 0
- },
- {
- name: '广播', type: 'broadcast', icon: broadcast, deviceType: 1
- },
- {
- name: '人行闸机', type: 'preBranke', icon: preBranke, deviceType: 2
- },
- {
- name: '车行闸机', type: 'carBranke', icon: carBranke, deviceType: 3
- }
- ]
- const modelShow = ref(false)
- const model = ref({
- deviceId: '',
- itemId: -1,
- longitude: '',
- latitude: '',
- describeData: '',
- selfId: '',
- deviceType: 0
- })
- const rules = {
- deviceId: [ { required: true, message: '请输入设备ID' } ],
- itemId: [ { required: true, message: '请选择项目' } ],
- longitude: [ { required: true, message: '请输入经度' } ],
- latitude: [ { required: true, message: '请输入维度' } ],
- describeData: [ { required: true, message: '请输入安装位置描述' } ]
- }
- const options = ref([] as Array<TreeList>)
- const formRef = ref()
- const isAdd = ref(false)
- /**
- * 添加路径
- * @param lngLat 经纬度数组
- * @param displayMin 最小显示范围
- * @param displayMax 最大显示范围
- */
- function addLine(lngLat: Array<number>, displayMin?: number, displayMax?: number) {
- if (!viewer) return
- viewer.entities.add({
- position: Cartesian3.fromDegrees(lngLat[0], lngLat[1]),
- polyline: {
- positions: Cartesian3.fromDegreesArray(lngLat),
- width: 5,
- material: Color.fromCssColorString('#55F8F8'),
- clampToGround: true,
- distanceDisplayCondition: new DistanceDisplayCondition(displayMin, displayMax)
- }
- })
- }
- /**
- * 添加label
- * @param lng 经度
- * @param lat 纬度
- * @param icon 图标
- * @param text 文本内容
- * @param displayMin 最小显示范围
- * @param displayMax 最大显示范围
- */
- function addLable(lng: number, lat: number, icon: string, text: string, displayMin?: number, displayMax?: number, oldData?: Device) {
- if (!viewer) return
- viewer.entities.add({
- position: Cartesian3.fromDegrees(lng, lat),
- billboard: {
- image: icon || lnglatIcon,
- width: 25,
- height: 25,
- verticalOrigin: VerticalOrigin.BOTTOM,
- disableDepthTestDistance: 21618529,
- heightReference: HeightReference.CLAMP_TO_GROUND,
- distanceDisplayCondition: new DistanceDisplayCondition(displayMin, displayMax)
- },
- label: {
- text,
- font: '12px MicrosoftYaHei',
- backgroundColor: Color.fromCssColorString('#0F2830'),
- showBackground: true,
- fillColor: Color.WHITE,
- pixelOffset: new Cartesian2(0, -40),
- disableDepthTestDistance: 21618529,
- heightReference: HeightReference.CLAMP_TO_GROUND,
- distanceDisplayCondition: new DistanceDisplayCondition(displayMin, displayMax)
- },
- oldData
- })
- }
- /**
- * 加载geojson
- * @param path 文件路径
- * @param callback
- * @param displayMin
- * @param displayMax
- */
- function loadGeoJson(path:string, displayMin?: number, displayMax?: number) {
- GeoJsonDataSource.load(path, {
- stroke: Color.fromCssColorString('#55F8F8'),
- strokeWidth: 3,
- clampToGround: false,
- fill: Color.fromCssColorString('rgba(0,0,0,0)')
- }).then((res) => {
- const entities = res.entities.values
- // console.log(55555, entities)
- // for (let k = 0; k < entities.length; k++) {
- // const el = entities[k]
- // const polygon = el.polygon as Any
- // const polyline = el.polyline as Any
- // const billboard = el.billboard as Any
- // if (polygon || polyline || el.billboard) {
- // if (polygon) polygon.distanceDisplayCondition = new DistanceDisplayCondition(displayMin, displayMax)
- // if (polyline) polyline.distanceDisplayCondition = new DistanceDisplayCondition(displayMin, displayMax)
- // if (billboard) billboard.distanceDisplayCondition = new DistanceDisplayCondition(displayMin, displayMax)
- // }
- // }
- if (viewer) {
- viewer.dataSources.add(res)
- viewer.scene.requestRender()
- }
- })
- }
- /**
- * 移动
- * @param lng
- * @param lat
- * @param profundity 高度
- */
- function flyTo(lng: number, lat: number, profundity = 19633) {
- if (!viewer) return
- viewer.camera.flyTo({
- destination: Cartesian3.fromDegrees(lng, lat * 0.997, profundity),
- orientation: {
- heading: CMath.toRadians(0.0),
- pitch: CMath.toRadians(-60.0),
- roll: CMath.toRadians(0.0)
- }
- })
- }
- /**
- * 屏幕坐标转经纬度
- * @param position xy
- */
- function XYToLngLat(position: { x: number, y: number }) {
- // 二维屏幕坐标转为三维笛卡尔空间直角坐标(世界坐标)
- const cartesian3 = viewer.scene.globe.pick(viewer.camera.getPickRay(position), viewer.scene)
- // 第一步:笛卡尔空间直角坐标系转为地理坐标(弧度制)
- const cartographic = Cartographic.fromCartesian(cartesian3)
- // 第二步: 地理坐标(弧度制) 转为经纬度坐标
- const lat = CMath.toDegrees(cartographic.latitude)
- const lng = CMath.toDegrees(cartographic.longitude)
- return { lng, lat }
- }
- // 初始化移动添加标点
- function initMove() {
- // 添加标点
- const dom = document.querySelector('.maps') as HTMLElement
- dom.onmousedown = (e: Any) => {
- const item = e.target.parentElement.dataset.type
- if (item) {
- const isNoPro = props.checkList.find((el) => el.lastData === 0 || !el.lastData)
- if (isNoPro || !props.checkList.length || modelShow.value || props.checkList.length > 1) {
- let txt = ''
- if (isNoPro) txt = '请选择项目'
- if (!props.checkList.length) txt = '请先选择项目'
- if (modelShow.value) txt = '请完成你的操作再来!'
- if (props.checkList.length > 1) txt = '请不要勾选多个项目'
- notification.warning({
- content: txt,
- duration: 3000
- })
- return
- }
- let ismove = false
- modelShow.value = false
- dom.onmousemove = (em) => {
- // 移动样式
- if (!ismove) ismove = true
- if (currenIcon.value.icon !== item) currenIcon.value.icon = item
- currenIcon.value.x = em.x - 15 + window.scrollX
- currenIcon.value.y = em.y - 100 + window.scrollY
- }
- dom.onmouseup = (eu) => {
- currenIcon.value = { icon: '', x: 0, y: 0 }
- const { lng, lat } = XYToLngLat({ x: eu.x + window.scrollX, y: eu.y - 74 + window.scrollY })
- const items = icontypes.find((el) => el.type === item)
- if (ismove) {
- addLable(lng, lat, items?.icon!, items?.name!, 1, 16219740)
- model.value = {
- deviceId: '',
- itemId: +props.checkList[0].id,
- longitude: lng.toString(),
- latitude: lat.toString(),
- describeData: '',
- selfId: '',
- deviceType: icontypes.filter((el) => el.type === item)[0].deviceType
- }
- modelShow.value = true
- isAdd.value = true
- }
- // flyTo(lng, lat)
- viewer.scene.requestRender()
- console.log('经纬度', lng, lat, props.checkList, model.value)
- dom.onmousemove = null
- dom.onmouseup = null
- ismove = false
- }
- }
- }
- }
- // 初始化
- async function initMap(callback:()=>void) {
- // store.setLoading(true)
- // init
- viewer = new Viewer('cesiumContainer', {
- baseLayerPicker: false,
- geocoder: false,
- infoBox: false,
- homeButton: false,
- sceneModePicker: false,
- navigationHelpButton: false,
- animation: false,
- creditContainer: 'cesiumContainer',
- timeline: false,
- fullscreenButton: false,
- vrButton: false,
- requestRenderMode: true,
- scene3DOnly: true,
- selectionIndicator: false,
- navigationInstructionsInitiallyVisible: false
- // sceneMode: SceneMode.COLUMBUS_VIEW
- })
- // 增加地形属性
- viewer.scene.globe.depthTestAgainstTerrain = true
- // 缩放范围
- viewer.scene.screenSpaceCameraController.minimumZoomDistance = 100
- viewer.scene.screenSpaceCameraController.maximumZoomDistance = 7546388
- // viewer.imageryLayers.removeAll(true)
- // viewer.imageryLayers.get(0).show = false// 不显示底图
- // viewer.scene.globe.baseColor = Color.fromCssColorString('#07101a') // 设置地球颜色07101a
- viewer.imageryLayers.addImageryProvider(new UrlTemplateImageryProvider({
- url: 'https://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8'
- }))
- // loadGeoJson('are.json', 1, 7546388)
- // loadGeoJson('road.json', 1, 7546388)
- // 加载图层
- // viewer.imageryLayers.addImageryProvider(new UrlTemplateImageryProvider({
- // url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
- // subdomains: [ '0', '1', '2', '3' ],
- // tilingScheme: new WebMercatorTilingScheme(),
- // maximumLevel: 14
- // }))
- // 加载地形
- // viewer.terrainProvider = await (CesiumTerrainProvider as any).fromUrl('https://www.supermapol.com/realspace/services/3D-stk_terrain/rest/realspace/datas/info/data/path', {
- // requestMetadata: true,
- // requestVertexNormals: true,
- // requestWaterMask: true
- // })
- viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(ScreenSpaceEventType.LEFT_DOUBLE_CLICK) // 取消原双击事件
- viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK) // 取消原单击事件
- // viewer.camera.changed.addEventListener(() => {
- // // 打印中心点坐标、高度
- // const { height } = viewer.scene.globe.ellipsoid.cartesianToCartographic(viewer.camera.position)
- // console.log(999999, height)
- // })
- // 重新注册点击事件
- handler = new ScreenSpaceEventHandler(viewer.scene.canvas)
- // 右键还原
- handler.setInputAction(() => {
- viewer.camera.flyTo({
- destination: Cartesian3.fromDegrees(101.84599, 30.04260, 7546388),
- orientation: {
- heading: CMath.toRadians(0.0),
- pitch: CMath.toRadians(-85.0),
- roll: CMath.toRadians(0.0)
- }
- })
- }, ScreenSpaceEventType.RIGHT_CLICK)
- // 点击左键
- handler.setInputAction((movement: Any) => {
- const pick = viewer.scene.pick(movement.position)
- if (!isAdmin.value || !pick) return
- if (isAdd.value) {
- notification.warning({
- content: '请完成你的操作再来!',
- duration: 3000
- })
- return
- }
- console.log('点击详情', pick.id)
- isAdd.value = false
- const { oldData } = pick.id
- model.value = { ...oldData, selfId: pick.id.id }
- modelShow.value = true
- }, ScreenSpaceEventType.LEFT_CLICK)
- // 监听加载结束
- Helper.add(viewer.scene.globe.tileLoadProgressEvent, (e: any) => {
- if (e === 0) {
- viewer.camera.flyTo({
- destination: Cartesian3.fromDegrees(101.84599, 30.04260, 7546388),
- orientation: {
- heading: CMath.toRadians(0.0),
- pitch: CMath.toRadians(-85.0),
- roll: CMath.toRadians(0.0)
- }
- })
- // store.setLoading(false)
- Helper.removeAll()
- // 初始化添加标点
- callback()
- }
- })
- }
- function close() {
- if (isAdd.value) {
- const { values } = viewer.entities
- if (values.length) viewer.entities.removeById(values[values.length - 1].id)
- viewer.scene.requestRender()
- isAdd.value = false
- }
- modelShow.value = false
- }
- async function submit() {
- await formRef.value.validate()
- // 提交数据
- const msg = isAdd.value ? await equipmentService.addDevice({ ...model.value }) : await equipmentService.upDevice({ ...model.value })
- notification.success({
- content: msg,
- duration: 3000
- })
- // 属性赋值
- const { values } = viewer.entities
- values[values.length - 1].oldData = { ...model.value, selfId: values[values.length - 1].id }
- modelShow.value = false
- isAdd.value = false
- }
- async function del() {
- dialog.info({
- title: '你确定要删除当前设备?',
- positiveText: '确定',
- onPositiveClick: async () => {
- const msg = await equipmentService.delDevice({ ...model.value })
- notification.success({
- content: msg,
- duration: 3000
- })
- viewer.entities.removeById(model.value.selfId)
- viewer.scene.requestRender()
- modelShow.value = false
- }
- })
- }
- /** 递归 */
- function recursion(arr: Array<TreeList>, newData: Array<TreeList>) {
- for (let k = 0; k < arr.length; k++) {
- const el = arr[k]
- if (el.lastData === 1 || el.lastData === 0) {
- newData.push({
- name: el.name,
- id: el.id,
- lastData: el.lastData,
- itemLongitude: el.itemLongitude,
- itemLatitude: el.itemLatitude,
- deviceDataList: el.deviceDataList
- })
- }
- if (el.children && el.children.length) recursion(el.children, newData)
- }
- }
- /** input 正则 */
- function onlyAllowNumber(value:string, type:boolean) {
- if (type) {
- return +(value) >= 0 && +(value) <= 180
- }
- return +(value) >= 0 && +(value) <= 90
- }
- // 选中时
- watch(() => props.checkList, (v) => {
- if (!isAdmin.value) return
- if (!v.length) {
- viewer.entities.removeAll()
- } else {
- equipmentService.getDevice(v[0]?.id as number).then((res) => {
- viewer.entities.removeAll()
- for (let k = 0; k < res.length; k++) {
- const el = res[k]
- if (el.del === 1) {
- const items = icontypes.find((es) => es.deviceType === el.deviceType)
- addLable(+(el.longitude!), +(el.latitude!), items!.icon, items!.name, 1, 16219740, el)
- }
- }
- viewer.scene.requestRender()
- })
- }
- viewer.scene.requestRender()
- close()
- })
- // 树列表
- watch(() => props.treeList, (v) => {
- options.value = v
- viewer.entities.removeAll()
- if (isAdmin.value || !v.length) return
- const newData = [] as TreeList[]
- recursion(v, newData)
- for (let k = 0; k < newData.length; k++) {
- const el = newData[k]
- if (el.lastData === 1) {
- // 项目
- if (el.itemLongitude) {
- const lngLat = el.itemLongitude?.split(',')
- addLable(+(lngLat![0]), +(lngLat![1]), lnglatIcon, el.nameReferred || el.name, 1038633, 4938633)
- }
- if (el.deviceDataList && el.deviceDataList.length) {
- // 设备
- for (let j = 0; j < el.deviceDataList.length; j++) {
- const es = el.deviceDataList[j]
- if (es.del === 1) {
- const items = icontypes.find((ed) => ed.deviceType === es.deviceType)
- addLable(+(es.longitude!), +(es.latitude!), items!.icon, items!.name, 1, 938633)
- }
- }
- }
- } else {
- // 公司
- }
- }
- viewer.scene.requestRender()
- })
- // 修改经纬度=>同步图标
- watch(() => [ model.value.longitude, model.value.latitude ], (v) => {
- if (isAdd.value) return
- const ens = viewer.entities.getById(model.value.selfId)
- ens.position = Cartesian3.fromDegrees(+v[0], +v[1])
- setTimeout(() => {
- viewer.scene.requestRender()
- }, 1500)
- })
- onMounted(() => {
- initMap(() => {
- if (isAdmin.value) initMove()
- // loadGeoJson('geo.json')
- // loadGeoJson('1.geojson')
- // loadGeoJson('2.geojson')
- // loadGeoJson('3.geojson')
- // loadGeoJson('4.geojson')
- // loadGeoJson('5.geojson')
- // loadGeoJson('6.geojson')
- // loadGeoJson('gm.geojson')
- // loadGeoJson('tmy.geojson')
- // loadGeoJson('dd.geojson')
- })
- })
- onUnmounted(() => {
- // store.setLoading(false)
- if (viewer) {
- if (viewer.entities) viewer.entities.removeAll()
- viewer.destroy()
- }
- viewer = null
- Helper = null
- handler = null
- const dom = (document.querySelector('.maps') as HTMLElement)
- if (dom && dom.onmousedown) dom.onmousedown = null
- })
- </script>
- <template>
- <div class="maps">
- <div id="cesiumContainer" />
- <template v-if="isAdmin">
- <div class="topbar">
- <template
- v-for="(item, index) in icontypes"
- :key="index"
- >
- <div>
- <Icon
- :name="item.type"
- :size="50"
- :data-type="item.type"
- />
- <p>{{ item.name }}</p>
- </div>
- </template>
- </div>
- <Icon
- :name="currenIcon.icon"
- :size="25"
- class="moveIcon"
- :style="`left:${currenIcon.x};top:${currenIcon.y}`"
- />
- <div
- v-if="modelShow"
- class="details"
- >
- <n-card
- style="width: 400px"
- :bordered="false"
- size="huge"
- role="dialog"
- aria-modal="true"
- class="n-modal"
- >
- <n-form
- ref="formRef"
- :model="model"
- :rules="rules"
- >
- <n-form-item
- path="deviceId"
- label="设备ID"
- >
- <n-input
- v-model:value="model.deviceId"
- :readonly="!isAdd"
- />
- </n-form-item>
- <n-form-item
- path="itemId"
- label="所属项目"
- >
- <n-tree-select
- v-model:value="model.itemId"
- :options="options"
- :render-label="(info:any)=>info.option.nameReferred || info.option.name"
- label-field="name"
- key-field="id"
- check-strategy="child"
- clearable
- filterable
- />
- </n-form-item>
- <n-form-item
- path="longitude"
- label="经度"
- >
- <n-input
- v-model:value="model.longitude"
- :allow-input="(e: string) => onlyAllowNumber(e, true)"
- />
- </n-form-item>
- <n-form-item
- path="latitude"
- label="纬度"
- >
- <n-input
- v-model:value="model.latitude"
- :allow-input="(e: string) => onlyAllowNumber(e, false)"
- />
- </n-form-item>
- <n-form-item
- path="describeData"
- label="安装位置描述"
- >
- <n-input
- v-model:value="model.describeData"
- type="textarea"
- size="small"
- :autosize="{
- minRows: 3,
- maxRows: 5
- }"
- :maxlength="150"
- />
- </n-form-item>
- </n-form>
- <template #footer>
- <n-button @click="close">
- 取消
- </n-button>
- <n-button
- v-if="!isAdd"
- type="error"
- @click="del"
- >
- 删除
- </n-button>
- <n-button
- type="primary"
- @click="submit"
- >
- 确定
- </n-button>
- </template>
- </n-card>
- </div>
- </template>
- </div>
- </template>
- <style scoped lang="scss">
- .maps {
- width: 100%;
- height: 100%;
- position: absolute;
- left: 0;
- top: 0;
- #cesiumContainer {
- width: 100%;
- height: 100%;
- }
- .topbar {
- position: absolute;
- top: 10px;
- left: 50%;
- width: 1080px;
- height: 104px;
- transform: translate(-50%, 0);
- background: linear-gradient(229deg, rgba(26, 65, 78, 0.85) 0%, rgba(3, 20, 26, 0.95) 100%);
- border: 1px solid rgba(105, 206, 206, 0.8);
- display: flex;
- align-items: center;
- div {
- display: flex;
- flex-flow: column;
- align-items: center;
- width: 65px;
- user-select: none;
- cursor: pointer;
- font-size: 14px;
- &:first-child {
- margin-left: 10px;
- }
- p {
- margin-top: 5px;
- text-align: center;
- }
- }
- }
- .moveIcon {
- position: absolute;
- top: 0;
- left: 0;
- }
- .details {
- position: absolute;
- right: 10px;
- top: 10px;
- min-height: 400px;
- }
- }
- </style>
|