Cesium是一个跨平台、跨浏览器的展示三维地球和地图的javascript库。
使用WebGL来进行硬件加速图形,使用时不需要任何插件支持,但是浏览器必须支持WebGL。
基于Apache2.0许可的开源程序,它可以免费的用于商业和非商业用途
通过Cesium提供的Javascript API,可以实现以下功能:
- 支持 3d 地球、 2d 地图、 2.5d 哥伦布模式的地理(地图)数据展示。
- 支持鼠标和触摸操作的三维空间 渲染、缩放、惯性平移、飞行、任意视角漫游。
- 支持各种几何体:点、 线、面、走廊、管径、墙体、立方体、圆柱、球体等。
- 支持标准的矢量格式 KML 、GeoJSON、TopoJSON, 以及矢量的贴地效果。
- 支持多种资源的图像层,包括 WMS,TMS, WMTS以及时序图像。支持透明度叠加, 亮度等参数的调整。
- 使用gtlf和3dtiles格式加载各种不同的 3d 数据,包含 倾斜摄影、人工模型、 BIM,点云数据等。
涉及三个知识领域 : Web前端、计算机图形学、地理信息系统(GIS)。所以想要学好Cesium,需要对Web前端、计算机图形学、GIS相关的基础知识有所掌握。
Cesium中常用的坐标:
1、屏幕坐标
即二维笛卡尔平面坐标,我们通过鼠标点击直接获取的坐标就是屏幕坐标了,单位是像素值
2.笛卡尔空间直角坐标(常用)
笛卡尔空间直角坐标又称为世界坐标,主要是用来做空间位置的变化如平移、旋转和缩放等等
1、开发环境搭建
以vue-cli举例子,包括拷贝配置、cesium静态资源,按需引入,模块打包等配置
const webpack = require('webpack')
const CopyWebpackPlugin = require('copy-webpack-plugin')
// The path to the cesium source code
const cesiumSource = 'node_modules/cesium/Source'
const cesiumWorkers = '../Build/Cesium/Workers'
// Configure your module bundler to copy the following four directories and serve them as static files
const cesiumBaseUrl = 'static/cesium/'
module.exports = {
configureWebpack: {
plugins: [
// Copy Cesium Assets, Widgets, and Workers to a static directory
new CopyWebpackPlugin([{ from: path.join(cesiumSource, cesiumWorkers), to: cesiumBaseUrl + 'Workers' }]),
new CopyWebpackPlugin([{ from: path.join(cesiumSource, 'Assets'), to: cesiumBaseUrl + 'Assets' }]),
new CopyWebpackPlugin([{ from: path.join(cesiumSource, 'Widgets'), to: cesiumBaseUrl + 'Widgets' }]),
// Define relative base path in cesium for loading assets
new webpack.DefinePlugin({
// example http://localhost:8080/static/cesium/Assets/, then you would set the base URL as follows:
CESIUM_BASE_URL: JSON.stringify(cesiumBaseUrl)
})
],
module: {
// 解决:Critical dependency: require function is used in a way in which dependencies cannot be statically extracted
unknownContextCritical: false,
rules: [
{
// Strip cesium pragmas
test: /\.js$/,
enforce: 'pre',
include: path.resolve(__dirname, cesiumSource),
sideEffects: false,
use: [{
loader: 'strip-pragma-loader',
options: {
pragmas: {
debug: false
}
}
}]
}
]
}
},
chainWebpack(config) {
config
.when(process.env.NODE_ENV !== 'development',
config => {
// cesium单独打一个包
config
.optimization.splitChunks({
chunks: 'all',
cacheGroups: {
cesium: {
name: 'chunk-cesium',
priority: 20,
test: /[\\/]node_modules[\\/]_?cesium(.*)/ // in order to adapt to cnpm
}
}
})
}
)
}
}
vite可以直接使用vite-plugin-cesium
插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import cesium from 'vite-plugin-cesium'
const path = require('path')
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), cesium()],
resolve: {
// 配置路径别名
alias: {
'@': path.resolve(__dirname, './src')
}
}
})
2、初始化
任何Cesium应用程序的基础都是Viewer,Viewer是一个带有多种功能的可交互的三位数字地球的容器。
初始化界面也默认自带了一些组件,在初始化的时候进行隐藏,也可以通过CSS隐藏。
import { Viewer, Ion, Color, createWorldTerrain } from 'cesium'
import 'cesium/Build/Cesium/Widgets/widgets.css'
const ION_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI4MDRjNjY3Mi0wOWFlLTQ0ZWMtYTBmMC1jOWM2NjQ3MzA3NWQiLCJpZCI6MTE2NDEsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1NTk1MzI0MzJ9.-KqKMl5H6TLPNwZJUZzwkz7_lwm0QN3UtLZ8Ko3z7dY'
Ion.defaultAccessToken = ION_TOKEN
export default class CesiumCluster {
/**
* @param {Object} ops Cesium配置项
*/
constructor(ops) {
super()
const options = { ...defaultOptions, ...ops }
this.viewer = null
this.init(options)
}
/**
* 初始化Viewer
* @param {String} element Dom节点
* @param {Number} sceneMode 默认视图类型
* @param {Object} imageryLayer 默认底图
* @param {Object} center 初始位置
* @see https://cesium.com/learn/cesiumjs/ref-doc/Viewer.html
*/
init({ element, sceneMode, imageryLayer, center, callback }) {
this.viewer = new Viewer(element, {
sceneMode,
orderIndependentTranslucency: false,
contextOptions: {
webgl: {
alpha: true
}
},
selectionIndicator: false, // 用于在选定对象上显示指示器的小部件
animation: false, // 动画小组件
baseLayerPicker: false, // 底图组件,选择三维数字地球的底图(imagery and terrain)
fullscreenButton: false, // 全屏组件
geocoder: false, // 地理编码(搜索)组件
homeButton: false, // 首页,点击之后将视图跳转到默认视角
infoBox: false, // 信息框
sceneModePicker: false, // 场景模式,切换2D、3D 和 Columbus View (CV) 模式
timeline: false, // 时间轴
navigationHelpButton: false, // 帮助提示,如何操作数字地球
terrainProvider: createWorldTerrain() // 加载地形数据
})
// 隐藏天空盒子、太阳、月亮
this.viewer.scene.skyBox.show = false
this.viewer.scene.sun.show = false
this.viewer.scene.moon.show = false
this.viewer.scene.backgroundColor = new Color(0.0, 0.0, 0.0, 0.0)
// 隐藏logo
this.viewer._cesiumWidget._creditContainer.style.display = 'none'
// 渲染效果和性能调优
// this.viewer.extend(viewerCesiumInspectorMixin)
// 开启地形深度测试
// this.viewer.scene.globe.depthTestAgainstTerrain = true
}
// 销毁Viewer
destroy() {
this.viewer.destroy()
this.viewer = null
}
}
2.1、添加影像数据
栅格瓦片就是我们浏览三维能感知的"皮肤"了,通常我们叠加的是各种卫星影像或瓦片数据。
添加底图影像或电子地图,以天地图卫星影像为例
// 添加影像底图
addImageryProvider() {
this.viewer.scene.globe.maximumScreenSpaceError = 1.8
const imageryProvider = this.setImageryProvider('img_w', 'img', 'tdtBasicLayer')
const imageryAnnotation = language === 'en' ? this.setImageryProvider('eia_w', 'eia') : this.setImageryProvider('cia_w', 'cia')
this.viewer.imageryLayers.addImageryProvider(imageryProvider)
this.viewer.imageryLayers.addImageryProvider(imageryAnnotation)
}
// 天地图 Provider
setImageryProvider(lyr, layer, layername = 'tdtLayer') {
const tk = 'xxxxxxxx'
return new WebMapTileServiceImageryProvider({
url: `http://t{s}.tianditu.com/${lyr}/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=${layer}&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=${tk}`,
layer: layername,
style: 'default',
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
format: 'tiles',
tileMatrixSetID: 'c',
maximumLevel: 18
})
}
3、添加空间可视化数据
三维场景中,地形和栅格来组成了三维的基础,但更多的业务还是需要点线面等矢量数据来充实, 这就是我们的矢量数据图层,数据是GIS的开始。
矢量数据是用经度、纬度、高度坐标来表示地图图形或地理实体位置的数据,常见的矢量数据有:点、线、面、体等格式。
Cesium在空间数据可视化方面提供了两种类型的API,一种是面向图形开发人员的低级(原始)API,通过Primitive类实现,另一种是用于数据驱动的高级(实体)API,通过Entity类实现。
import {
Cartesian2,
Cartesian3,
Color,
LabelStyle,
HorizontalOrigin,
VerticalOrigin,
HeightReference,
HeadingPitchRoll,
Transforms
} from 'cesium'
const defaultColor = '#fff'
/**
* 添加实体
* @summary 包括广告牌、标签、点、线等
* @see https://zhuanlan.zhihu.com/p/348807058
* @see https://cesium.com/learn/cesiumjs/ref-doc/Entity.html
*/
export default class Entities {
/**
* 添加广告牌
* @param {String} name 实体名称
* @param {Array} degrees 经纬度
* @param {*} image 广告牌图片
*/
addBillboard(options = {}) {
const { id, name = 'billboard', degrees, show = true, image, width, height, scale = 1, color, pixelOffset = [0, 0], clampToGround = true } = options
const params = {
id,
name,
position: this.fromDegrees(degrees),
billboard: {
show,
image: image || 'static/cesium/Assets/Images/cesium_credit.png',
pixelOffset: new Cartesian2(...pixelOffset), // 广告牌偏移
color: Color.fromCssColorString(color || defaultColor),
horizontalOrigin: HorizontalOrigin.CENTER,
verticalOrigin: VerticalOrigin.CENTER,
heightReference: clampToGround ? HeightReference.CLAMP_TO_GROUND : HeightReference.NONE,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
width,
height,
scale
}
}
this.viewer.entities.add(params)
}
/**
* 添加标签
* @param {Array} degrees 经纬度
* @param {String} text 标签内容
*/
addLabel(options = {}) {
const {
id, name = 'label', degrees, text, show = true, font = '12px', color = '#fff',
outlineColor = '#000', outlineWidth = 2, pixelOffset = [0, 18]
} = options
const params = {
id,
name,
position: this.fromDegrees(degrees),
label: {
show,
text, // 标签内容
font,
style: LabelStyle.FILL_AND_OUTLINE,
fillColor: Color.fromCssColorString(color), // 文本填充色
outlineColor: Color.fromCssColorString(outlineColor), // 文本轮廓线颜色
outlineWidth: outlineWidth, // 文本轮廓线宽
pixelOffset: new Cartesian2(...pixelOffset), // 偏移
horizontalOrigin: HorizontalOrigin.CENTER,
verticalOrigin: VerticalOrigin.CENTER,
eyeOffset: Cartesian3.ZERO
}
}
this.viewer.entities.add(params)
}
/**
* 添加点
* @param {Array} degrees 经纬度
*/
addPoint(options) {
const {
id, name = 'point', degrees, pixelSize = 10, show = true, color,
outlineColor = '#000', outlineWidth = 0, pixelOffset = [0, 0]
} = options
const params = {
id,
name,
position: this.fromDegrees(degrees),
point: {
show,
pixelSize, // 像素大小
// heightReference: Cesium.HeightReference.NONE,
color: Color.fromCssColorString(color || defaultColor),
outlineColor: Color.fromCssColorString(outlineColor),
outlineWidth: outlineWidth,
pixelOffset: new Cartesian2(...pixelOffset)
}
}
this.viewer.entities.add(params)
}
/**
* 添加多线段
* @param {Array} degrees 经纬度
* @example { degrees: [-75, 35, -125, 35] }
*/
addPolyline(options = {}) {
const {
id, name = 'polyline', degrees, width = 6, show = true, color, clampToGround = true, zIndex
} = options
const params = {
id,
name,
polyline: {
show,
positions: this.fromDegreesArray(degrees), // 定义线条的 Cartesian3 位置的数组
clampToGround: clampToGround, // 是否贴地
width: width,
material: Color.fromCssColorString(color || defaultColor), // 线低于地形时用于绘制折线的材质
zIndex // 指定用于订购地面几何形状的z索引
}
}
return this.viewer.entities.add(params)
}
/**
* 添加多边形
* @param {Array} degrees 经纬度
* @example { degrees: [-115, 37, -115, 32, -107, 33, -102, 31, -102, 35] }
*/
addPolygon(options = {}) {
const {
id, name = 'polygon', degrees, height, show = true, color, outline, outlineColor, outlineWidth = 1, clampToGround = true, zIndex
} = options
const params = {
id,
name,
polygon: {
show,
hierarchy: this.fromDegreesArray(degrees), // 指定PolygonHierarchy
height, // 多边形相对于椭球面的高度
heightReference: clampToGround ? HeightReference.CLAMP_TO_GROUND : HeightReference.RELATIVE_TO_GROUND, // 是否贴地
material: Color.fromCssColorString(color || defaultColor),
outline,
outlineColor: Color.fromCssColorString(outlineColor || defaultColor),
outlineWidth,
zIndex // 指定用于订购地面几何形状的z索引
}
}
return this.viewer.entities.add(params)
}
/**
* 添加模型
* @param {Array} lngLatHeight 经纬度高程
* @param {String} url 模型地址
*/
addModel(options = {}) {
const { id, name = 'model', lngLatHeight, url, color, scale = 1, heading = 0, pitch = 0, roll = 0, clampToGround } = options
const position = this.fromDegrees(lngLatHeight)
const hpr = new HeadingPitchRoll(this.toRadians(heading), this.toRadians(pitch), this.toRadians(roll))
const orientation = Transforms.headingPitchRollQuaternion(
position,
hpr
)
const params = {
id,
name,
position,
orientation,
model: {
show: true,
uri: url,
scale: scale,
// minimumPixelSize: 128, // 模型的最小最小像素大小,而不考虑缩放
// maximumScale: 20000, // 模型的最大比例尺大小。 minimumPixelSize的上限
incrementallyLoadTextures: true, // 确定在加载模型后纹理是否可以继续流入
runAnimations: true, // 是否应启动模型中指定的glTF动画
clampAnimations: true, // glTF动画是否应在没有关键帧的持续时间内保持最后一个姿势
heightReference: clampToGround ? HeightReference.CLAMP_TO_GROUND : HeightReference.RELATIVE_TO_GROUND,
// silhouetteColor: Color.RED, // 轮廓的颜色
// silhouetteSize: 0, // 轮廓的宽度
color: Color.fromCssColorString(color || defaultColor) // 模型的颜色
}
}
return this.viewer.entities.add(params)
}
}
4、工具集
主要是坐标转换等公共方法。
屏幕坐标(像素)即二维笛卡尔平面坐标,我们通过鼠标点击直接获取的坐标就是屏幕坐标了,单位是像素值。
笛卡尔空间直角坐标又称为世界坐标,主要是用来做空间位置的变化如平移、旋转和缩放等等,它的坐标原点在椭球的中心。
经纬度坐标,即测绘中的地理经纬度坐标,默认是WGS84坐标系,坐标原点在椭球的质心。
import {
Cartesian3,
Math as CesiumMath,
Cartographic,
SceneTransforms
} from 'cesium'
/**
* @summary 包括坐标转换...
* @see https://zhuanlan.zhihu.com/p/334540571
*/
export default class Utils {
/**
* 经纬度转弧度
* @param {*} degrees 经、纬度
*/
toRadians(degrees) {
return CesiumMath.toRadians(degrees)
}
/**
* 弧度转经纬度
* @param {*} radians 弧度
*/
toDegrees(radians) {
return CesiumMath.toDegrees(radians)
}
/**
* 单个经纬度(带高程)转笛卡尔坐标
* @param {Array} lnglat 经纬高
*/
fromDegrees(lnglat) {
return Cartesian3.fromDegrees(...lnglat)
}
/**
* 多个经纬度(不带高程)转笛卡尔坐标
* @param {Array} lnglat 经纬度
* @example lnglat = [113.33, 23.33, 113.45, 24.34]
*/
fromDegreesArray(lnglat) {
return Cartesian3.fromDegreesArray(lnglat)
}
/**
* 笛卡尔坐标转为经纬度
* @param {Object} cartesian3 笛卡尔坐标
*/
cartesianTodegrees(cartesian3) {
const cartographic = Cartographic.fromCartesian(cartesian3)
if (!cartographic) return []
const lat = this.toDegrees(cartographic.latitude)
const lng = this.toDegrees(cartographic.longitude)
const height = cartographic.height
return [lng, lat, height]
}
/**
* 屏幕坐标转笛卡尔坐标
* @param {Object} position 屏幕坐标
*/
windowCoordinatesToCartesian(position) {
let cartesian3 = this.getGlobeClickCartesian(position)
const pick = this.viewer.scene.pickPosition(position)
const pickModel = this.viewer.scene.pick(position)
if (pickModel && pick) {
cartesian3 = this.getModelClickCartesian(pick)
}
return cartesian3
}
// 获取倾斜摄影或模型点击处的笛卡尔坐标
getModelClickCartesian(pick) {
const { longitude, latitude, height } = Cartographic.fromCartesian(pick)
const lat = CesiumMath.toDegrees(latitude)
const lng = CesiumMath.toDegrees(longitude)
const cartesian3 = Cartesian3.fromDegrees(lng, lat, height)
return cartesian3
}
/**
* 加载地形后, 获取点击处对应的笛卡尔坐标
* @param {Array} position 屏幕坐标
*/
getGlobeClickCartesian(position) {
const pickRay = this.viewer.camera.getPickRay(position)
if (!pickRay) return
const cartesian3 = this.viewer.scene.globe.pick(pickRay, this.viewer.scene)
return cartesian3
}
/**
* 笛卡尔坐标转屏幕坐标
* @param {*} cartesian3 三维笛卡尔空间直角坐标
* @summary 结果是Cartesian2对象,取出X, Y即为屏幕坐标
* @returns {Object} {x, y}
*/
wgs84ToWindowCoordinates(cartesian3) {
return SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, cartesian3)
}
/**
* 计算区域中心点
* @param geoCoordinateList {Array<Array<Object>>} [[{lat, lon}]]
* @return { Object } {lat lon}
*/
getBoundsCenter(geoCoordinateList, height = 5000000) {
const geoCoordinateListFlat = geoCoordinateList.reduce((s, v) => {
return (s = s.concat(v))
}, [])
const total = geoCoordinateListFlat.length
let X = 0
let Y = 0
let Z = 0
for (const g of geoCoordinateListFlat) {
const lat = g.lat * Math.PI / 180
const lon = g.lon * Math.PI / 180
const x = Math.cos(lat) * Math.cos(lon)
const y = Math.cos(lat) * Math.sin(lon)
const z = Math.sin(lat)
X += x
Y += y
Z += z
}
X = X / total
Y = Y / total
Z = Z / total
const Lon = Math.atan2(Y, X)
const Hyp = Math.sqrt(X * X + Y * Y)
const Lat = Math.atan2(Z, Hyp)
return { lon: Lon * 180 / Math.PI, lat: Lat * 180 / Math.PI, height }
}
}
5、飞行、定位
Camera相机控制了三维场景的视图。旋转(rotate)、缩放(zoom)和飞到目的地(flyTo)
heading:偏航角(弧度),绕负Z轴旋转,顺时针为正,默认为正北方向0,可简单理解成左右方向的改变。
pitch:俯仰角(弧度),绕负Y轴旋转,顺时针为正,默认为俯视-90,可简单理解成前空翻、后空翻。
roll:翻滚角(弧度),绕正x轴旋转,顺时针为正,默认为0,可简单理解成侧空翻。
import {
HeadingPitchRoll
} from 'cesium'
/**
* 相机操作
* @summary 包括飞行、定位
* @see https://zhuanlan.zhihu.com/p/351731187
* @see https://cesium.com/learn/cesiumjs/ref-doc/Camera.html
*/
export default class Camera {
/**
* 飞行定位到Entity、DataSource、ImageryLayer、Cesium3DTileset等
* @param {Object} target 目标 如Entity
*/
flyTo(options = {}) {
const { target, duration, heading = 0, pitch = -90, roll = 0 } = options
this.viewer.flyTo(target, {
duration,
offset: new HeadingPitchRoll(heading, pitch, roll)
})
}
/**
* 快速定位
* @param {Number} lng 经度
* @param {Number} lat 纬度
* @param {Number} height 高程
*/
setView({ lng = 113.281023, lat = 23.129487, height = 300000 }) {
this.viewer.camera.setView({
destination: this.fromDegrees([lng, lat, height])
})
}
/**
* 飞行定位到指定位置
* @param {Number} lng 经度
* @param {Number} lat 纬度
* @param {Number} height
*/
flyToPosition(position = {}, options) {
const { lng = 113.281023, lat = 23.129487, height = 300000, heading = 0, pitch = -90, roll = 0 } = position
const _lat = pitch === -50 ? lat - 2 : lat
this.viewer.camera.flyTo({
destination: this.fromDegrees([lng, _lat, height]),
orientation: {
heading: this.toRadians(heading),
pitch: this.toRadians(pitch),
roll: this.toRadians(roll)
},
...options
})
}
}
6、3D Tiles加载
切换二维视图、哥伦布视图、三维视图
加载3D Tiles(Cesium 加载海量三维模型数据的一种数据格式)
import {
Cesium3DTileset,
Cartesian3,
Cartographic,
Matrix4
} from 'cesium'
/**
* 场景操作
* @see https://cesium.com/learn/cesiumjs/ref-doc/Scene.html
*/
export default class Scene {
get sceneMode() {
return this.viewer.scene.mode
}
/**
* 切换场景视图
* @param {Number} mode 视图类型 2:二维视图,2.5:哥伦布视图,3:三维视图
* @param {Number} duration 过渡时间
*/
changeSceneMode(mode, duration) {
switch (mode) {
case 2:
return this.viewer.scene.morphTo2D(duration)
case 2.5:
return this.viewer.scene.morphToColumbusView(duration)
default:
return this.viewer.scene.morphTo3D(duration)
}
}
/**
* 加载三维模型
* @param {String} url 三维模型地址
* @param {Number} height 三维模型高度
*/
add3DTiles({ url, height = 40 }) {
this.tileset = this.viewer.scene.primitives.add(
new Cesium3DTileset({
url
// maximumScreenSpaceError: 2, //最大的屏幕空间误差
// maximumNumberOfLoadedTiles: 1000, //最大加载瓦片个数
})
)
this.tileset.readyPromise.then(tileset => {
this.set3DTilesHeight(tileset, height)
})
return this.tileset
}
remove3DTiles() {
if (this.tileset) {
this.tileset.destroy()
this.viewer.scene.primitives.remove(this.tileset)
}
}
/**
* 设置3DTiles高度
* @param {Object} tileset 3DTiles
* @param {Number} height 高度
*/
set3DTilesHeight(tileset, height) {
const boundingSphere = tileset.boundingSphere
console.log('boundingSphere', boundingSphere)
const cartographic = Cartographic.fromCartesian(boundingSphere.center)
// 调整模型高度
const surface = Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0)
const offset = Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, height)
const translation = Cartesian3.subtract(offset, surface, new Cartesian3())
tileset.modelMatrix = Matrix4.fromTranslation(translation)
}
}
7、Event 事件
无论是前端系统,还是二维/三维GIS应用系统,都离不开各种事件的应用,尤其是鼠标的单击、双击事件。
鼠标事件:点击地图上的某一个矢量数据,并获取其属性信息。
场景渲染事件:
scene.preUpdate: 更新或呈现场景之前将引发的事件
scene.postUpdate: 场景更新后以及渲染场景之前立即引发的事件
scene.preRender: 场景更新后以及渲染场景之前将引发的事件,一般用来获取相机当前的状态,如相机高度、偏航角、俯仰角、翻滚角等
scene.postRender: 渲染场景后立即引发的事件
import {
ScreenSpaceEventHandler,
ScreenSpaceEventType
} from 'cesium'
export default class Event {
constructor() {
this.popupPosition = null // 弹框位置
this.clickHandler = null // 鼠标点击事件
this.scenePostRender = null // 监听cesium渲染
this.overlayers = []
}
/**
* 添加气泡框
* @param {String} element 元素id
* @param {String} positioning 容器基于鼠标点的定位
* @param {Array} offset 容器的偏移
*/
addPopup(name, positioning, element = 'Overlay', offset = [0, 0], callback) {
const $el = document.getElementById(element)
// 开启监听
if (!this.scenePostRender) {
this.listenPostRender(element, positioning, offset)
}
// 清空鼠标事件
if (this.clickHandler) {
this.clickHandler.destroy()
this.clickHandler = null
}
this.viewer._container.style.cursor = 'default'
// 添加鼠标事件
this.clickHandler = new ScreenSpaceEventHandler(this.viewer.scene.canvas)
// 鼠标左键点击
this.clickHandler.setInputAction(clickEvent => {
const pick = this.viewer.scene.pick(clickEvent.position)
// 判断点击的是否为当前图层的Billboard
if (pick && pick.collection && pick.collection.name === name) {
// 转屏幕坐标
const position = this.wgs84ToWindowCoordinates(pick.primitive.position)
// 保存当前鼠标点击的位置
this.popupPosition = pick.primitive.position
// 计算弹框的位置
this.updatePopup($el, position, positioning, offset)
typeof callback === 'function' && callback(pick.id)
} else {
this.popupPosition = null
$el.style.cssText = 'display:none;'
}
}, ScreenSpaceEventType.LEFT_CLICK)
// 鼠标移动
this.clickHandler.setInputAction((movement) => {
const pick = this.viewer.scene.pick(movement.endPosition)
if (pick && pick.collection && pick.collection.name === name) {
this.viewer._container.style.cursor = 'pointer'
} else {
this.viewer._container.style.cursor = 'default'
}
}, ScreenSpaceEventType.MOUSE_MOVE)
}
/**
* 隐藏气泡框
* @param {String} element 元素id
*/
hidePopup(element) {
const $el = document.getElementById(element)
if ($el) {
this.popupPosition = null
$el.style.cssText = 'display:none;'
}
}
/**
* 更新弹窗位置
* @param {Element} $el 弹窗dom
* @param {Object} position 屏幕坐标
* @param {String} positioning 容器基于鼠标点的定位
* @param {Array} offset 容器的偏移
*/
updatePopup($el, { x, y }, positioning, [offsetX, offsetY]) {
// console.log(x, y)
switch (positioning) {
case 'left':
$el.style.cssText += `display:block; top:${y + $el.offsetHeight / 2 + offsetY}px; left:${x - $el.offsetWidth + offsetX}px`
break
case 'right':
$el.style.cssText += `display:block; top:${y - $el.offsetHeight / 2 + offsetY}px; left:${x + offsetX}px`
break
case 'bottom':
$el.style.cssText += `display:block; top:${y + offsetY}px; left:${x - $el.offsetWidth / 2 + offsetX}px`
break
default:
$el.style.cssText += `display:block; top:${y - $el.offsetHeight + offsetY}px; left:${x - $el.offsetWidth / 2 + offsetX}px`
}
}
listenPostRender(element, positioning, offset) {
// 刷新弹框位置
this.scenePostRender = this.viewer.scene.postRender.addEventListener(() => {
if (this.popupPosition) {
const position = this.wgs84ToWindowCoordinates(this.popupPosition)
if (!position) return
const $el = document.getElementById(element)
// 计算弹框的位置
this.updatePopup($el, position, positioning, offset)
}
})
}
}
还没有评论,快来抢第一吧