这个需求算是比较简单的,不涉及到着色器,但鉴于我到的文章都是直接简单的给一个和主视角同步的俯视角小窗 ,而不是给小窗一个大的全局范围,使用entity在这个范围中同步渲染主视角视口位置,所以索性就写一个相关功能的教程,供大家参考
第一步 创建第二个地图
这一段应该没啥好说的,我是用了自己的底图,你可以加载天地图
php
async init() {
this.HawkeyeViewer = new Cesium.Viewer(containerId, {
animation: false, // 左下角的动画仪表盘
baseLayerPicker: false, // 右下角的图层选择按钮
geocoder: false, // 搜索框,
homeButton: false, // home按钮
sceneModePicker: false, // 模式切换按钮
timeline: false, // 底部的时间轴
navigationHelpButton: false, // 右上角的帮助按钮,
fullscreenButton: false, // 右下角的全屏按钮
shouldAnimate: true,
selectionIndicator: false,
infoBox: false
})
this.HawkeyeViewer.imageryLayers.removeAll() // 清除所有图层
const env = import.meta.env
let url = 'abcd1234'
const layer = await new Cesium.WebMapTileServiceImageryProvider({
url: url,
layer: 'nbrmap_nbrmapcgcs2000',
style: 'default',
tileMatrixSetID: 'default',
format: 'image/png',
tilingScheme: new Cesium.GeographicTilingScheme(),
maximumLevel: 21,
tileMatrixLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21']
})
this.setimageryLayers(layer) // 重新加载自定义的图层(底图)
const topLayer = viewer.imageryLayers.get(0)
topLayer.show = true
document.querySelector('#'+containerId).querySelector('.cesium-viewer-bottom').remove()
}
第二步 初始化设置
这一步完成鹰眼视角的固定视角和相机前往初始位置,向init
函数中添加以下代码
ini
// 前往中心
this.HawkeyeViewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(121.408887, 29.8313129, 75000.0),
})
// 阻止鹰眼视角被拖动
let control = this.HawkeyeViewer.scene.screenSpaceCameraController;
control.enableRotate = false;
control.enableTranslate = false;
control.enableZoom = false;
control.enableTilt = false;
control.enableLook = false;
第三步 创建展示主视角位置的实体
当鹰眼图底图创建完成之后,在init
方法里调用下面方法
csharp
async createRectangleEntity() {
if(this.rectangleEntity) {
this.HawkeyeViewer.entities.remove(this.rectangleEntity)
this.rectangleEntity = null
}
// 先不去设置实体的位置
this.rectangleEntity = new Cesium.Entity({
id: 'rectangleEntity',
polygon: new Cesium.PolygonGraphics({
hierarchy: new Cesium.PolygonHierarchy([]),
fill: true,
material: Cesium.Color.ORANGE.withAlpha(0.3),
}),
polyline: new Cesium.PolylineGraphics({
material: Cesium.Color.ORANGE,
width: 2
})
})
this.HawkeyeViewer.entities.add(this.rectangleEntity)
}
createRectangleEntity()
第四步 获取主视口的范围数据
通过屏幕坐标转笛卡尔3坐标,我们能得到左上和右下的经纬度,拆开之后就可以获取到左下,右上的经纬度,将四个位置转为笛卡尔3并放入数组即是实体位置
kotlin
getRange() {
const leftTop = CusMap.windowPositionToCartesian3({x:0,y:0})
if(!leftTop) return
const {clientWidth, clientHeight} = this.mainViewer.canvas
const rightBottom = CusMap.windowPositionToCartesian3({x: clientWidth, y: clientHeight})
const [l, t] = CusMap.cartesian3ToDegrees(leftTop)
const [r, b] = CusMap.cartesian3ToDegrees(rightBottom)
const leftBottom = Cesium.Cartesian3.fromDegrees(l, b)
const rightTop = Cesium.Cartesian3.fromDegrees(r, t)
this.rectangleEntity._p = [leftTop, rightTop, rightBottom, leftBottom, leftTop]
}
这里列一下坐标转换方法
- 屏幕坐标转笛卡尔3
javascript
static windowPositionToCartesian3(windowPosition, viewer = window.viewer) {
// 球面
if (viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider) { // 还是默认的,没有加载地表高程信息
// 如果选择了椭球或地图,则返回椭球或地图在世界坐标中的点。如果没有选择了椭球或地图,返回undefined。
return viewer.scene.camera.pickEllipsoid(windowPosition); // windowPosition Cartesian2 一个像素的x和y坐标。-> Cartesian3
}
// 地形
else {
const ray = viewer.camera.getPickRay(windowPosition); // 在世界坐标中,通过像素位置windowPosition,从相机位置创建一条射线
return viewer.scene.globe.pick(ray, viewer.scene); // 拾取射线与地球表面的交点。射线必须以世界坐标表示。→ Cartesian3|undefined
}
}
- 笛卡尔3转经纬度
ini
static cartesian3ToDegrees(cartesian3) {
const cartographic = Cesium.Cartographic.fromCartesian(cartesian3);
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
const height = cartographic.height;
return [longitude, latitude, height];
}
第五步 同步实体在鹰眼视角中的位置
在 createRectangleEntity
方法最后添加以下代码
kotlin
// 完成实体创建后,监听主视角移动事件,获取实体位置
this.mainViewer.scene.preRender.addEventListener(this.getRange, this)
this.updateRectangleEntityPosition() // 调用检测到位置变换就去更新的方法
updateRectangleEntityPosition
使用CallbackProperty实现自动更新
kotlin
updateRectangleEntityPosition() {
this.rectangleEntity.polyline.positions = new Cesium.CallbackProperty(() => {
if(this.rectangleEntity._p?.length) {
return this.rectangleEntity._p
}
})
this.rectangleEntity.polygon.hierarchy = new Cesium.CallbackProperty(() => {
if(this.rectangleEntity._p?.length) {
return new Cesium.PolygonHierarchy(this.rectangleEntity._p)
}
})
}
最后贴一下完整代码
kotlin
import { CusMap } from './map.js'
export default class HawkeyeView {
constructor(mainViewer, containerId) {
this.mainViewer = mainViewer
this.HawkeyeViewer = null
this.rectangleEntity = null
this.init(containerId)
}
async init(containerId) {
this.HawkeyeViewer = new Cesium.Viewer(containerId, {
animation: false, // 左下角的动画仪表盘
baseLayerPicker: false, // 右下角的图层选择按钮
geocoder: false, // 搜索框,
homeButton: false, // home按钮
sceneModePicker: false, // 模式切换按钮
timeline: false, // 底部的时间轴
navigationHelpButton: false, // 右上角的帮助按钮,
fullscreenButton: false, // 右下角的全屏按钮
shouldAnimate: true,
selectionIndicator: false,
infoBox: false
})
this.HawkeyeViewer.imageryLayers.removeAll()
const env = import.meta.env
let url = env.VITE_APP_SERVER_URL + 'map/wmts?tilename=yx&imgtype=png&level={TileMatrix}&row={TileRow}&col={TileCol}'
const layer = await new Cesium.WebMapTileServiceImageryProvider({
url: url,
layer: 'nbrmap_nbrmapcgcs2000',
style: 'default',
tileMatrixSetID: 'default',
format: 'image/png',
tilingScheme: new Cesium.GeographicTilingScheme(),
maximumLevel: 21,
tileMatrixLabels: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21']
})
this.setimageryLayers(layer)
const topLayer = viewer.imageryLayers.get(0)
topLayer.show = true
this.HawkeyeViewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(121.408887, 29.8313129, 75000.0),
})
let control = this.HawkeyeViewer.scene.screenSpaceCameraController;
control.enableRotate = false;
control.enableTranslate = false;
control.enableZoom = false;
control.enableTilt = false;
control.enableLook = false;
this.createRectangleEntity()
document.querySelector('#'+containerId).querySelector('.cesium-viewer-bottom').remove()
}
setimageryLayers (options) {
const layer = this.HawkeyeViewer.scene.imageryLayers
if(Array.isArray(options) | options instanceof Cesium.ImageryLayer || options instanceof Cesium.ImageryProvider || options instanceof Cesium.WebMapTileServiceImageryProvider) {
const addImageryLayer = (imagery) =>{
if (imagery instanceof Cesium.ImageryLayer){
layer.add(imagery)
} else if (imagery instanceof Cesium.ImageryProvider){
layer.addImageryProvider(imagery)
if (Array.isArray(options)) {
options.forEach(item => {
addImageryLayer(item)
})
} else {
addImageryLayer(options)
}
} else if (imagery instanceof Cesium.WebMapTileServiceImageryProvider){
layer.addImageryProvider(imagery)
}
}
addImageryLayer(options)
}
}
getRange() {
const leftTop = CusMap.windowPositionToCartesian3({x:0,y:0})
if(!leftTop) return
const {clientWidth, clientHeight} = this.mainViewer.canvas
const rightBottom = CusMap.windowPositionToCartesian3({x: clientWidth, y: clientHeight})
const [l, t] = CusMap.cartesian3ToDegrees(leftTop)
const [r, b] = CusMap.cartesian3ToDegrees(rightBottom)
const leftBottom = Cesium.Cartesian3.fromDegrees(l, b)
const rightTop = Cesium.Cartesian3.fromDegrees(r, t)
this.rectangleEntity._p = [leftTop, rightTop, rightBottom, leftBottom, leftTop]
}
async createRectangleEntity() {
if(this.rectangleEntity) {
this.HawkeyeViewer.entities.remove(this.rectangleEntity)
this.rectangleEntity = null
}
this.rectangleEntity = new Cesium.Entity({
id: 'rectangleEntity',
polygon: new Cesium.PolygonGraphics({
hierarchy: new Cesium.PolygonHierarchy([]),
fill: true,
material: Cesium.Color.ORANGE.withAlpha(0.3),
}),
polyline: new Cesium.PolylineGraphics({
material: Cesium.Color.ORANGE,
width: 2
})
})
this.HawkeyeViewer.entities.add(this.rectangleEntity)
this.mainViewer.scene.preRender.addEventListener(this.getRange, this)
this.updateRectangleEntityPosition()
}
updateRectangleEntityPosition() {
this.rectangleEntity.polyline.positions = new Cesium.CallbackProperty(() => {
if(this.rectangleEntity._p?.length) return this.rectangleEntity._p
})
this.rectangleEntity.polygon.hierarchy = new Cesium.CallbackProperty(() => {
if(this.rectangleEntity._p?.length) return new Cesium.PolygonHierarchy(this.rectangleEntity._p)
})
}
destroy() {
this.mainViewer.scene.preRender.removeEventListener(this.getRange)
this.HawkeyeViewer.entities.remove(this.rectangleEntity)
this.rectangleEntity = null
this.HawkeyeViewer.destroy()
this.HawkeyeViewer = null
}
}