<template>
<div id="CesiumViewer">
<slot></slot>
</div>
</template>
<script lang="ts" setup>
import * as Cesium from "cesium";
import CesiumNavigation from "cesium-navigation-es6";
import { ref } from "vue";
import { onMounted, onBeforeUnmount } from "vue";
const options = defineProps({
options: Object,
navigatorOptions: {
type: Object,
default() {
return {
show: true,
};
},
},
});
let latLonBounds = { "east": 180.0, "north": 90.0, "south": -90.0, "west": -180.0 };
let rectangle = new Cesium.Rectangle(Cesium.Math.toRadians(latLonBounds.west), Cesium.Math.toRadians(latLonBounds.south),
Cesium.Math.toRadians(latLonBounds.east), Cesium.Math.toRadians(latLonBounds.north));
const MapConfig = {
// ION: "",
global: {
enableLighting: false, //光照开关
depthTestAgainstTerrain: true, //高度检测
highDynamicRange: false, //是否使用高动态范围渲染。物体需要阳光照射出阴影开启
maximumScreenSpaceError: 4 / 3 //用于驱动细节层次细化的最大屏幕空间误差。较高的值将提供更好的性能,但会降低视觉质量 默认2
},
MAPOPTIONS: {
imageryProvider: new Cesium.UrlTemplateImageryProvider({
url: 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
minimumLevel: 0,
maximumLevel: 18
}), //设置影像图列表
requestVertexNormals: true,
shouldAnimate: true,
geocoder: false, //右上角查询按钮
shadows: false,
terrainProviderViewModels: [], //设置地形图列表
animation: false, //动画小窗口
baseLayerPicker: false, //图层选择器
fullscreenButton: false, //全屏
vrButton: false, //vr按钮
homeButton: false, //home按钮
infoBox: false,
sceneModePicker: false, //2D,2.5D,3D切换
selectionIndicator: false,
timeline: false, //时间轴
navigationHelpButton: false, //帮助按钮
terrainShadows: Cesium.ShadowMode.DISABLED
},
};
// Cesium.Ion.defaultAccessToken = MapConfig.ION;
window.Cesium = Cesium;
const $emit = defineEmits(["onViewerLoaded"]);
let beActive = ref<Boolean>(false);
let viewer: Cesium.Viewer | null;
let beforeDestroy = new Cesium.Event();
const destroy = (): void => {
beforeDestroy.raiseEvent();
console.warn("cesium destroy!!");
viewer?.destroy();
viewer = null;
};
onMounted(() => {
start()
});
const start = (flag: boolean = false) => {
viewer = new Cesium.Viewer("CesiumViewer", {
...MapConfig.MAPOPTIONS,
...options.options,
});
(viewer.cesiumWidget.creditContainer as HTMLElement).style.display = "none"; //去除版权信息
viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(
Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK
); //移除双击选中
let terrainProvider;
if (options.options?.terrainProvider) {
terrainProvider = options.options.terrainProvider;
viewer.terrainProvider = terrainProvider;
} else {
// terrainProvider = new Cesium.CesiumTerrainProvider({
// url: "/public/Terran"
// });
}
//图像显示效果设置
viewer.scene.highDynamicRange = MapConfig.global.highDynamicRange
viewer.scene.globe.maximumScreenSpaceError = MapConfig.global.maximumScreenSpaceError
viewer.scene.globe.enableLighting = MapConfig.global.enableLighting;
viewer.scene.globe.depthTestAgainstTerrain = MapConfig.global.depthTestAgainstTerrain;
// 获取要改变的的图层
// let layer = viewer.scene.imageryLayers.get(1);
//调整伽马值
// layer.gamma = 0.66;
//改变当前地图的组织结构
// layer.magnificationFilter = Cesium.TextureMagnificationFilter.NEAREST;
(viewer as any).frameUpdate = new Cesium.Event();
let lasTime: null | number;
viewer.scene.preUpdate.addEventListener((time) => {
let dateNow = Date.now();
let deltaTime = lasTime != null ? dateNow - lasTime : 0;
lasTime = dateNow;
(viewer as any).frameUpdate.raiseEvent(deltaTime);
});
const { navigatorOptions } = options;
if (navigatorOptions?.show === true) {
//指南针比例尺
let CesiumNavigationOptions = {
// 用于在使用重置导航重置地图视图时设置默认视图控制。接受的值是Cesium.Cartographic 和Cesium.Rectangle.
defaultResetView:
navigatorOptions.defaultResetView ||
Cesium.Cartographic.fromDegrees(115, 30, 2000000),
// 用于启用或禁用罗盘。true是启用罗盘,false是禁用罗盘。默认值为true。如果将选项设置为false,则罗盘将不会添加到地图中。
enableCompass: true,
// 用于启用或禁用缩放控件。true是启用,false是禁用。默认值为true。如果将选项设置为false,则缩放控件 将不会添加到地图中。
enableZoomControls: true,
// 用于启用或禁用距离图例。true是启用,false是禁用。默认值为true。如果将选项设置为false,距离图例将不会添加到地图中。
enableDistanceLegend: true,
// 用于启用或禁用指南针外环。true是启用,false是禁用。默认值为true。如果将选项设置为false,则该环将可见但无效。
enableCompassOuterRing: true,
};
new CesiumNavigation(viewer, CesiumNavigationOptions);
}
window.Viewer = viewer;
beActive.value = true;
console.warn("cesium 启动!!");
if (!flag) $emit("onViewerLoaded", viewer);
}
onBeforeUnmount(() => {
destroy();
});
defineExpose({ start })
</script>
<style lang="less" scoped>
#CesiumViewer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
<template>
<div class="eye" id="eagleEye"></div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue'
import * as Cesium from "cesium";
import { getCurrentExtent, getZoomLevel, getCenterPosition, zoomToAltitude } from '@/components/Cesium/utils/tool'
onMounted(() => {
initMap()
})
const props = defineProps({
viewer: {
type: Cesium.Viewer
}
})
let eyeViewer: Cesium.Viewer | null
const initMap = () => {
eyeViewer = new Cesium.Viewer("eagleEye", {
animation: false,
baseLayerPicker: false,
fullscreenButton: false,
imageryProvider: new Cesium.UrlTemplateImageryProvider({
url: 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
minimumLevel: 0,
maximumLevel: 18
}),
geocoder: false,
homeButton: false,
infoBox: false,
sceneModePicker: false,
selectionIndicator: false,
timeline: false,
navigationHelpButton: false,
scene3DOnly: false,
navigationInstructionsInitiallyVisible: false,
showRenderLoopErrors: false,
sceneMode: Cesium.SceneMode.SCENE2D,
});
(eyeViewer.cesiumWidget.creditContainer as HTMLElement).style.display = "none"; //去除版权信息
//限制相机高度范围
eyeViewer.scene.screenSpaceCameraController.minimumZoomDistance = 500.0
addListenerEvent()
syncViewer()
}
let rectangleArr: number[] = []
let entity: Cesium.Entity | undefined
const drawRegionalScope = () => { //绘制区域范围轮廓
const extent = getCurrentExtent((props.viewer as Cesium.Viewer)) //相机当前视野范围
const { xmin, ymax, xmax, ymin } = extent
rectangleArr = [xmin, ymin,xmin,ymax, xmax, ymax, xmax, ymin, xmin, ymin]
if (!entity) {
entity = eyeViewer?.entities.add({
polyline: {
positions: new Cesium.CallbackProperty(() => {
return Cesium.Cartesian3.fromDegreesArray(rectangleArr);
}, false),
show: true,
material: Cesium.Color.RED,
width: 3,
clampToGround: true //是否贴地
}
});
}
}
let isMapTrigger: boolean = true
let isEyeMapTrigger: boolean = false
const addListenerEvent = () => {
const viewer: Cesium.Viewer = (props.viewer as Cesium.Viewer);
const scene = viewer.scene;
const sceneEye = eyeViewer?.scene;
const handlerL = new Cesium.ScreenSpaceEventHandler(scene.canvas);
const handlerR = new Cesium.ScreenSpaceEventHandler(sceneEye?.canvas);
handlerL.setInputAction(() => {
isMapTrigger = true;
isEyeMapTrigger = false;
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
handlerR.setInputAction(() => {
isMapTrigger = false;
isEyeMapTrigger = true;
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
viewer.scene.camera.moveEnd.addEventListener(syncViewer); //主地图更新后触发
eyeViewer?.scene.camera.moveEnd.addEventListener(syncViewerR);//小地图更新后触发
}
const syncViewer = () => {
if (isMapTrigger) {
const level = getZoomLevel((props.viewer as Cesium.Viewer).camera) //获取当前层级
const { lon, lat } = getCenterPosition((props.viewer as Cesium.Viewer)) //获取中心点
eyeViewer?.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(lon, lat, zoomToAltitude(level - 1))
});
drawRegionalScope()
}
};
const syncViewerR = () => {
if (isEyeMapTrigger && eyeViewer) {
const level = getZoomLevel(eyeViewer.camera)
const { lon, lat } = getCenterPosition(eyeViewer);
(props.viewer as Cesium.Viewer).camera.setView({
destination: Cesium.Cartesian3.fromDegrees(lon, lat, zoomToAltitude(level + 1)),
orientation: {
heading: (props.viewer as Cesium.Viewer).camera.heading,
pitch: (props.viewer as Cesium.Viewer).camera.pitch,
roll: (props.viewer as Cesium.Viewer).camera.roll,
}
});
drawRegionalScope()
}
};
onUnmounted(() => {
(props.viewer as Cesium.Viewer).scene.camera.moveEnd.removeEventListener(syncViewer);
eyeViewer?.scene.camera.moveEnd.removeEventListener(syncViewerR);
eyeViewer?.entities.removeAll()
eyeViewer = null
entity = undefined
})
</script>
<style lang="less" scoped>
.eye {
position: absolute;
right: 25px;
bottom: 30px;
border-radius: 50%;
width: 200px;
height: 200px;
overflow: hidden;
border: 2px solid orange;
box-shadow: 2px 2px 3px #2b2b2b;
z-index: 10;
}
</style>
tip:重点就是在两个地图容器更新后同步到另一个地图中,因为鹰眼地图容器小很多,所以鹰眼地图的层级总是比地图容器大一级。
/*
@ 相机当前视野范围
*/
export function getCurrentExtent(viewer: Cesium.Viewer) {
//范围对象
let extent = {
xmin: 0,
ymax: 0,
xmax: 0,
ymin: 0,
height: 0,
xminRadian: 0,
ymaxRadian: 0,
xmaxRadian: 0,
yminRadian: 0,
};
// 得到当前三维场景
const scene = viewer.scene;
// 得到当前三维场景的椭球体
const ellipsoid = scene.globe.ellipsoid;
const canvas = scene.canvas;
// canvas左上角
const car3_lt = viewer.camera.pickEllipsoid(new Cesium.Cartesian2(0, 0), ellipsoid);
// canvas右下角
const car3_rb = viewer.camera.pickEllipsoid(new Cesium.Cartesian2(canvas.width, canvas.height), ellipsoid);
// 当canvas左上角和右下角全部在椭球体上
if (car3_lt && car3_rb) {
const carto_lt = ellipsoid.cartesianToCartographic(car3_lt);
const carto_rb = ellipsoid.cartesianToCartographic(car3_rb);
extent.xmin = Cesium.Math.toDegrees(carto_lt.longitude);
extent.ymax = Cesium.Math.toDegrees(carto_lt.latitude);
extent.xmax = Cesium.Math.toDegrees(carto_rb.longitude);
extent.ymin = Cesium.Math.toDegrees(carto_rb.latitude);
extent.xminRadian = Cesium.Math.toDegrees(carto_lt.longitude);
extent.ymaxRadian = Cesium.Math.toDegrees(carto_lt.latitude);
extent.xmaxRadian = Cesium.Math.toDegrees(carto_rb.longitude);
extent.yminRadian = Cesium.Math.toDegrees(carto_rb.latitude);
}
// 当canvas左上角不在但右下角在椭球体上
else if (!car3_lt && car3_rb) {
let car3_lt2 = null;
let yIndex = 0;
do {
// 这里每次10像素递加,一是10像素相差不大,二是为了提高程序运行效率
yIndex <= canvas.height ? yIndex += 10 : canvas.height;
car3_lt2 = viewer.camera.pickEllipsoid(new Cesium.Cartesian2(0,
yIndex), ellipsoid);
} while (!car3_lt2);
let carto_lt2 = ellipsoid.cartesianToCartographic(car3_lt2);
let carto_rb2 = ellipsoid.cartesianToCartographic(car3_rb);
extent.xmin = Cesium.Math.toDegrees(carto_lt2.longitude);
extent.ymax = Cesium.Math.toDegrees(carto_lt2.latitude);
extent.xmax = Cesium.Math.toDegrees(carto_rb2.longitude);
extent.ymin = Cesium.Math.toDegrees(carto_rb2.latitude);
extent.xminRadian = Cesium.Math.toDegrees(carto_lt2.longitude);
extent.ymaxRadian = Cesium.Math.toDegrees(carto_lt2.latitude);
extent.xmaxRadian = Cesium.Math.toDegrees(carto_rb2.longitude);
extent.yminRadian = Cesium.Math.toDegrees(carto_rb2.latitude);
} // 获取高度
extent.height = Math.ceil(viewer.camera.positionCartographic.height);
return extent;
}
export function getCenterPosition(viewer: Cesium.Viewer) {
let centerResult = viewer.camera.pickEllipsoid(
new Cesium.Cartesian2(
viewer.canvas.clientWidth / 2,
viewer.canvas.clientHeight / 2,
),
)
let curLongitude = 0
let curLatitude = 0
let height = 0
if(centerResult) {
let curPosition = Cesium.Ellipsoid.WGS84.cartesianToCartographic(centerResult);
curLongitude = (curPosition.longitude * 180) / Math.PI;
curLatitude = (curPosition.latitude * 180) / Math.PI;
height = Math.ceil(viewer.camera.positionCartographic.height);
}
return {
lon: curLongitude,
lat: curLatitude,
height
}
}
export function getZoomLevel(camera: Cesium.Camera) {
let height = camera.positionCartographic.height;
const A = 40487.57;
const B = 0.00007096758;
const C = 91610.74;
const D = -40467.74;
return Math.round(D+(A-D)/(1+Math.pow(height/C, B)));
}