vue3使用cesium实现跟随弹框

 本文最终效果是实现移动地图位置后,点弹框会跟随点一起移动

测试项目目录结构如下:

 

 vite.config.ts代码如下:

import { fileURLToPath, URL } from 'node:url'
import cesium from 'vite-plugin-cesium'; // 引入插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), cesium()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

 初始化地图 initCesium.js 代码如下

import { onMounted } from 'vue';
import { Viewer } from 'cesium';
import * as Cesium from "cesium";
export let viewer;
export let handler;
export function initCesium() {
    onMounted(() => {
        viewer = new Viewer('cesiumContainer', {
            timeline: false,
            fullscreenButton: false,
            shouldAnimate: true,
            geocoder: false,
            sceneModePicker: false,
            baseLayerPicker: false,
            homeButton: false,
            navigationHelpButton: false,
            selectionIndicator: false,
            //skyBox: false,
            infoBox: false,
            // 实现canvas缓存获得canvas图像内容
            contextOptions: {
                webgl: { preserveDrawingBuffer: true },
            },
        });
        viewer.camera.flyTo({
            destination: Cesium.Cartesian3.fromDegrees(108.09876, 37.200787, 1400000),
            duration: 1,
        });
        handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
        addLayer();
    })
    const addLayer = () => {
        const tdt_tk = "天地图的tk"; //一天只能请求一万次啊
        let TDTImgProvider = new Cesium.WebMapTileServiceImageryProvider({
            url: `http://t0.tianditu.gov.cn/vec_w/wmts?tk=${tdt_tk}`,
            layer: "vec",
            style: "default",
            format: "tiles",
            tileMatrixSetID: "w",
            maximumLevel: 18,
        });

        let TDTZJProvider = new Cesium.WebMapTileServiceImageryProvider({
            url: `http://t0.tianditu.gov.cn/cva_w/wmts?tk=${tdt_tk}`,
            layer: "cva",
            style: "default",
            format: "tiles",
            tileMatrixSetID: "w",
            maximumLevel: 18,
        });

        viewer.imageryLayers.addImageryProvider(TDTImgProvider);
        viewer.imageryLayers.addImageryProvider(TDTZJProvider);
    }
}

 地图相关操作 useCesium.js 代码如下:

 监听事件建议必须清除,否则系统会出现卡顿问题

import { viewer, handler } from '@/hooks/initCesium';
import * as Cesium from "cesium";
import { ref, nextTick } from 'vue';
export function useCesium() {
    const popFlag = ref(false); // 弹框显示
    const popData = ref();  // 点击数据
    const mapPopup = ref();  // 弹框dom
    const setPoint = (points) => {
        points.forEach((e) => {
            addpoint(e)
        })
        setEvent();
    }
    // 添加点
    const addpoint = (
        e
    ) => {
        const imgUrl = new URL('../assets/yl.png', import.meta.url).href;
        viewer.entities.add({
            id: e.id,
            data: e,
            position: Cesium.Cartesian3.fromDegrees(e.position[0], e.position[1]),
            billboard: {
                image: imgUrl,
                width: 36,
                height: 48,
            }
        });
    };
    // 添加绑定事件
    const setEvent = () => {
        // 左键点击事件
        let leftclick = Cesium.ScreenSpaceEventType.LEFT_CLICK;
        viewer.screenSpaceEventHandler.removeInputAction(leftclick);
        handler.setInputAction((movement) => {
            // 返回笛卡尔2坐标系 - 为点击点位置
            // 获取点击的实体
            const pick = viewer.scene.pick(movement.position);
            if (!pick || !pick.id) {
                return false;
            }
            const pick_obj = Cesium.defaultValue(pick.id, pick.primitive.id);
            // 判断是否是Cesium实体
            if (pick_obj instanceof Cesium.Entity) {
                // 经纬度转笛卡尔3
                const cartesian3 = Cesium.Cartesian3.fromDegrees(
                    Number(pick_obj.data.position[0]),
                    Number(pick_obj.data.position[1]),
                    0.1
                );
                // 获取实体笛卡尔2坐标系
                const screenposition = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
                    viewer.scene,
                    cartesian3
                );
                // 这里使用实体的坐标而不是用点击点的坐标,为了防止弹框位置相对于点位置不固定
                createPopwinOnMap(pick_obj.data, screenposition);
            }
        }, leftclick)
    }
    // 存放监听事件
    const closePopEvent = [];
    // 弹窗相关
    const createPopwinOnMap = async (
        value,
        clickPostion
    ) => {
        // popFlag.value = false;
        popData.value = value;
        popFlag.value = true;
        await nextTick();
        if (mapPopup.value) {
            // 获取根节点
            const domref = mapPopup.value.$el;
            if (!domref) {
                return;
            }
            const position = viewer.scene.camera.pickEllipsoid(
                clickPostion,
                viewer.scene.globe.ellipsoid
            );
            let c = new Cesium.Cartesian2(clickPostion.x, clickPostion.y);
            //球面转动后 UI跟随物体处理
            let temEvent = viewer.scene.postRender.addEventListener(() => {
                if (position && c) {
                    const changedC = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
                        viewer.scene,
                        position
                    );
                    domref.style.bottom = viewer.canvas.clientHeight - c.y + "px";
                    domref.style.left = c.x - 100 + "px";
                    if (changedC) {
                        c = changedC;
                    }
                }
            });
            closePopEvent.push(temEvent);
        }
    };
    // 清楚弹框监听事件
    const removeEvent = () => {
        if (closePopEvent.length > 0) {
            closePopEvent.forEach((item) => {
                // 执行监听事件本身,可删除监听事件
                item();
            });
        }
        closePopEvent.length = 0;
    };
    const closeMapPopup = () => {
        popFlag.value = false;
        removeEvent();
    };
    return { addpoint, setPoint, popFlag, popData, mapPopup, closeMapPopup }
}

 index.vue代码如下:

<template>
    <div class="index">
        <div class="buttonBox">
            <button @click="addPagePoint">画点</button>
        </div>
        <iconPop v-if="popFlag" ref="mapPopup" :popupData="popData" @closePop="closeMapPopup"></iconPop>
        <div id="cesiumContainer"></div>
    </div>
</template>
<script setup lang='ts'>
import { initCesium } from '@/hooks/initCesium';
import { useCesium } from '@/hooks/useCesium';
import iconPop from '@/components/iconPop.vue'
initCesium()
const { setPoint, popFlag, popData, mapPopup, closeMapPopup } = useCesium();
const addPagePoint = () => {
    const points = [
        {
            id:'0000',
            name: '我是0',
            position: [108.09876, 37.200787]
        },
        {
            id:'1111',
            name: '我是1',
            position: [106.398901, 33.648651]
        },
        {
            id:'2222',
            name: '我是2',
            position: [113.715685, 37.845557]
        },
        {
            id:'333',
            name: '我是3',
            position: [113.09876, 33.200787]
        },
    ]
    setPoint(points)
 
}


</script>
<style lang='scss' scoped>
.index {
    width: 100%;
    height: 100%;
    position: fixed;

    #cesiumContainer {
        position: fixed;
        top: 0;
        bottom: 0;
        right: 0;
        left: 0;
    }

    .buttonBox {
        position: fixed;
        z-index: 9;
        right: 0;

    }
}
</style>

弹框组件 iconPop.vue,内容如下: 

<!-- 弹框-->
<template>
    <div class="mapPop" v-if="popupData">
        <div class="mapHeader">
            <span>{{ popupData ? popupData.name : '测试' }}</span>
            <span @click="close">x</span>
        </div>
        <div class="mapContent">
        </div>
    </div>
</template>
<script setup>
import { watch } from 'vue';
const props = defineProps({
    popupData: {}
})
watch(() => props.popupData, (val) => {
    console.log(val);
})
const emit = defineEmits(['closePop'])
// 关闭
const close = () => {
    emit('closePop')
}
</script>
  
<style lang="scss" scoped>
.mapPop {
    z-index: 2;
    transform: translate(-20px, -40px);
    position: absolute;
    width: 240px;
    height: 165px;
    background: rgba(255, 255, 255);
    box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.16);
    display: flex;
    flex-direction: column;
    z-index: 999;

    &::before {
        content: "";
        position: absolute;
        display: inline-block;
        top: 100%;
        border: solid transparent;
        content: " ";
        height: 0;
        width: 0;
        position: absolute;
        pointer-events: none;
        border-top-color: white;
        border-width: 10px;
        left: 120px;
        margin-left: -10px;

    }

    .mapHeader {
        width: 100%;
        height: 40px;
        background-color: #67C23A;
        display: flex;
        align-items: center;
        padding: 0 16px;
        box-sizing: border-box;
        justify-content: space-between;

        &.unComplate {
            background-color: #F4921D;
        }

        span:first-child {
            font-size: 16px;
            color: #FFFFFF;
        }

        .el-icon-close {
            font-size: 20px;
            color: #FFFFFF;
            cursor: pointer;
        }
    }

    .mapContent {
        padding: 20px 0 0 16px;
        box-sizing: border-box;
        width: 100%;
        height: 1px;
        flex: 1;

        p {
            font-size: 14px;
            line-height: 20px;
            color: #595959;

            &:not(:last-child) {
                margin-bottom: 10px;
            }
        }
    }
}
</style>

 效果如下,点击右上角画点按钮,进行点绘制,点击地图图标点,出现弹框

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值