本文最终效果是实现移动地图位置后,点弹框会跟随点一起移动
测试项目目录结构如下:

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代码如下:
- <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>
- <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">xspan>
- 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>
效果如下,点击右上角画点按钮,进行点绘制,点击地图图标点,出现弹框
