本文最终效果是实现移动地图位置后,点弹框会跟随点一起移动
测试项目目录结构如下:
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>
效果如下,点击右上角画点按钮,进行点绘制,点击地图图标点,出现弹框