文章目录
0.引言
现有的gis开发方向较流行的是webgis开发,其中Cesium是一款开源的WebGIS库,主要用于实时地球和空间数据的可视化和分析。它提供了丰富的地图显示和数据可视化功能,并能实现三维可视化开发。本文将使用Cesium完成一些工具应用级别的功能开发,并介绍如何与常见的第三方库(如Echarts、Turf.js、CesiumHeatmap.js等)进行集成来完成一些需求的开发。
1.场景截图
场景截图是指将当前Cesium容器canvas中的场景打印成图片并下载到本地,主要思路为:首先创建DOM元素标签,然后通过canvas.toDataURL方法获取图片的链接,最后使用标签将图片保存到本地。
(1)实现代码
7_01_场景截图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_场景截图</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium//Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.tool {
top: 20px;
left: 40px;
position: absolute;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<div class="tool">
<button onclick="printScreenScene()">打印场景</button>
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
});
var tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: './倾斜摄影/大雁塔3DTiles/tileset.json'
})
);
viewer.zoomTo(tileset);
//打印图件
function savePng(data, pngName) {
var saveLink = document.createElement('a');//创建下载链接标签<a>
saveLink.href = data; //设置下载链接
saveLink.download = pngName; //设置下载图片名称
document.body.appendChild(saveLink); //将 <a>标签 添加到 <body> 中
saveLink.click(); //点击<a>标签
};
function printScreenScene() {
var image = new Image();//创建img对象
viewer.render();//重新渲染界面
image = viewer.scene.canvas.toDataURL("image/png");//获取下载链接
savePng(image, '当前场景');//调用打印图件函数
}
</script>
</body>
</html>
(2)结果显示
2.卷帘对比
Cesium卷帘对比功能是指同时加载两个不同的影像底图,并将上层影像卷起来,使其只在左侧或者右侧显示,而下层影像在另一侧显示。通常在对比不同时间段影像或者不同影像底图时会用到该功能。本节将使用Cesium提供的splitDirection、imagerySplitPosition属性来实现影像图与矢量图、注记图的卷帘对比。
实现卷帘对比的整体思路如下:首先加载Bing地图、天地图矢量图层、天地图注记图层3个底图数据;然后给天地图矢量图层及天地图注记图层设置图层分区显示,均设置在右侧显示;接着使用图像分割器在屏幕中的位置进行分区,默认从屏幕中间进行分区;最后根据添加的卷帘工具位置来实时调整图像分割器的位置以实现卷帘效果。下面介绍整个卷帘对比的实现。
(1)实现代码
7_02_卷帘对比.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_卷帘对比</title>
<script type="text/javascript" src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
</head>
<body>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#slider {
position: absolute;
left: 50%;
top: 0px;
background-color: #d3d3d3;
width: 5px;
height: 100%;
z-index: 9999;
}
#slider:hover {
cursor: ew-resize;
}
</style>
<div id="cesiumContainer">
<div id="slider"></div>
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
});
/* var tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: './倾斜摄影/大雁塔3DTiles/tileset.json',//文件的路径
})
);
//定位过去
viewer.zoomTo(tileset);
//卷帘Cesium3DTileset 1.92版本发布
tileset.splitDirection = Cesium.SplitDirection.LEFT; */
var rightImageryVec = viewer.imageryLayers.addImageryProvider(
new Cesium.WebMapTileServiceImageryProvider({
//天地图矢量图层
url:"http://t{s}.tianditu.com/vec_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=vec&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=你的tk",
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'], //服务负载子域
format: "image/jpeg",
tileMatrixSetID: "GoogleMapsCompatible",//使用谷歌的瓦片切片方式
})
);
var rightImageryCva = viewer.imageryLayers.addImageryProvider(
new Cesium.WebMapTileServiceImageryProvider({
//天地图矢量注记
url:"http://t{s}.tianditu.com/cva_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cva&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default.jpg&tk=你的tk",
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
format: "image/jpeg",
tileMatrixSetID: "GoogleMapsCompatible",
})
);
//设置在图像拆分前右侧显示该影像,在右侧显示
rightImageryVec.splitDirection = Cesium.ImagerySplitDirection.RIGHT;
rightImageryCva.splitDirection = Cesium.ImagerySplitDirection.RIGHT;
//获取或设置图像拆分器在视口中的初始位置。有效值在0.0到1.0之间。
viewer.scene.imagerySplitPosition = 0.5;
//卷帘DOM
var slider = document.getElementById("slider");
//卷帘激活状态 true为开启 false为关闭
var moveActive = false;
//移动卷帘
function move(movement) {
if (!moveActive) {
return;
}
//拿到鼠标在卷帘工具中移动结束的屏幕坐标
var relativeOffset = movement.endPosition.x;
//计算 图像拆分器在视口中新的位置
var splitPosition =
(slider.offsetLeft + relativeOffset) /
slider.parentElement.offsetWidth;
//卷帘移动,更新卷帘的位置
slider.style.left = `${100.0 * splitPosition}%`;
//更新设置 图像拆分器在视口中新的位置
viewer.scene.imagerySplitPosition = splitPosition;
}
//为卷帘工具实例化ScreenSpaceEventHandler对象
var handler = new Cesium.ScreenSpaceEventHandler(slider);
//左键按下开启卷帘
handler.setInputAction(function () {
moveActive = true;
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);
//左键弹起关闭卷帘
handler.setInputAction(function () {
moveActive = false;
}, Cesium.ScreenSpaceEventType.LEFT_UP);
//鼠标移动更新图像拆分器的位置以及卷帘的位置
handler.setInputAction(move, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
</script>
</body>
</html>
(2)结果显示
3.反选遮罩
反选遮罩通常在需要特别突出某个目标区域时使用,通过将目标区域以外的地图掩盖以达到突出目标区域的效果,基本原理是先绘制一个多边形区域,然后将需要突出的目标区域挖空,并对其余部分设置一定的透明度进行掩盖。下面介绍如何使用Cesium中的polygon绘制带洞的多边形来突出目标区域以实现反选遮罩效果。
(1)实现代码
7_03_反选遮罩层.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_反选遮罩层</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
/* // 是否支持图像渲染像素化处理
if (Cesium.FeatureDetection.supportsImageRenderingPixelated()) {
viewer.resolutionScale = window.devicePixelRatio
}
// 开启抗锯齿
viewer.scene.postProcessStages.fxaa.enabled = true */
//大区域坐标,够用即可,不必太大,要形成闭环
var pointArr1 = [114.3944, 30.5237, 114.3943, 30.5192, 114.4029, 30.5192, 114.4029, 30.5237, 114.3944, 30.5237];
var positions = Cesium.Cartesian3.fromDegreesArray(pointArr1);
//洞的坐标,要形成闭环
var pointArr2 = [114.3972, 30.5224, 114.3972, 30.5218, 114.3988, 30.5218, 114.3988, 30.5224, 114.3972, 30.5224];
var hole = Cesium.Cartesian3.fromDegreesArray(pointArr2);
//绘制遮罩函数
function drawMask(positions, hole) {
// 带洞区域
var mask = viewer.entities.add({
polygon: {
// 获取指定属性(positions,holes(图形内需要挖空的区域))
hierarchy: {
positions: positions,
holes: [{ positions: hole }],
},
// 填充的颜色,withAlpha透明度
material: Cesium.Color.BLACK.withAlpha(0.7),
// 是否被提供的材质填充
fill: true,
},
});
var hightlightLine = viewer.entities.add({
polyline: {
positions: hole,
width: 3,
material: Cesium.Color.AQUA.withAlpha(1),
clampToGround: true,
},
});
}
//绘制反选遮罩层
drawMask(positions, hole);
//设置相机视角
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(114.3981, 30.5221, 300)
});
</script>
</body>
</html>
(2)结果显示
4.鹰眼视图
鹰眼视图是GIS中的一个基本功能,其数据范围与主数据视图的数据范围保持一致,可以用来展示同一范围内的不同数据。例如,可以在Cesium中添加一个鹰眼视图,设置底图为矢量图,当操作主视图时,鹰眼视图会跟随主视图的视角变化,和主视图中的影像底图进行对比。
在Cesium中制作鹰眼视图有多种方法,可以选择和其他的二维WebGIS框架Leaflet、OpenLayers等集成制作,也可以通过创建多个Viewer来实现。本节将使用Cesium创建另一个Viewer来实现鹰眼视图功能,实现原理为当主视图相机视角发生变化时,实时同步鹰眼视图的相机视角,使其与主视图保持一致。
(1)实现代码
7_04_鹰眼视图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_鹰眼视图</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#eagleEye {
position: absolute;
width: 25%;
height: 30%;
bottom: 0;
right: 0;
z-index: 999;
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<!-- 鹰眼 -->
<div id="eagleEye"></div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
var viewerEye = new Cesium.Viewer("eagleEye", {
geocoder: false, //是否显示地名查找工具
homeButton: false, //是否显示首页位置工具
sceneModePicker: false, //是否显示视角模式切换工具
baseLayerPicker: false, //是否显示默认图层选择工具
navigationHelpButton: false, //是否显示导航帮助工具
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain(),
//加载天地图矢量图
imageryProvider: new Cesium.WebMapTileServiceImageryProvider({
url:"http://t{s}.tianditu.com/vec_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=vec&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=你的tk",
subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'], //服务负载子域
layer: "tdtImgLayer",
style: "default",
format: "image/jpeg",
tileMatrixSetID: "GoogleMapsCompatible",//使用谷歌的瓦片切片方式
show: true
})
});
viewerEye.imageryLayers.addImageryProvider(new Cesium.WebMapTileServiceImageryProvider({
//天地矢量注记
url:"http://t{s}.tianditu.com/cva_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cva&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default.jpg&tk=你的tk",
subdomains: ['0','1','2','3','4','5','6','7'],
layer: "tdtCiaLayer",
style: "default",
format: "image/jpeg",
tileMatrixSetID: "GoogleMapsCompatible",
show: true
}));
viewerEye._cesiumWidget._creditContainer.style.display = 'none';//去除cesium版权信息
//禁用鹰眼视图相机操作
// 旋转
viewerEye.scene.screenSpaceCameraController.enableRotate = false;
// 平移
viewerEye.scene.screenSpaceCameraController.enableTranslate = false;
// 放大
viewerEye.scene.screenSpaceCameraController.enableZoom = false;
// 倾斜
viewerEye.scene.screenSpaceCameraController.enableTilt = false;
// 看向
viewerEye.scene.screenSpaceCameraController.enableLook = false;
//控制鹰眼视图相机视角
function reViewer() {
viewerEye.camera.flyTo({
destination: viewer.camera.position,
orientation: {
heading: viewer.camera.heading,
pitch: viewer.camera.pitch,
roll: viewer.camera.roll
},
duration: 0.0
});
}
//设置主视图引起监听事件触发的相机变化幅度,越小越灵敏
viewer.camera.percentageChanged = 0.01;
//当主视图相机变化时,鹰眼视图跟着变化
viewer.camera.changed.addEventListener(reViewer);
</script>
</body>
</html>
(2)结果显示
5.指南针与比例尺
Cesium本身并没有提供指南针、比例尺及罗盘等控件,因此不能像开启其他Cesium默认控件一样直接使用指南针与比例尺。本节介绍GitHub上的一个名为solocao的工程师维护的插件,该插件的使用方式非常简单,只需引入viewerCesiumNavigationMixin.min.js文件,然后使用几行代码即可直接在场景中添加指南针、比例尺、罗盘及缩放按钮等控件。
(1)实现代码
7_05_指南针与比例尺.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_指南针与比例尺</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<!-- 指南针、比例尺等 -->
<script src="./Build/js/viewerCesiumNavigationMixin.min.js"></script>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
/*比例尺定位*/
.distance-legend {
position: absolute;
left: 0px;
bottom: 30px;
z-index: 10;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
});
/* var options = {};
// 用于在使用重置导航重置地图视图时设置默认视图控制。接受的值是Cesium.Cartographic 和Cesium.Rectangle.
options.defaultResetView = Cesium.Cartographic.fromDegrees(110, 30, 2000000);
// 用于启用或禁用罗盘。true是启用罗盘,false是禁用罗盘。默认值为true。如果将选项设置为false,则罗盘将不会添加到地图中。
options.enableCompass = true;
// 用于启用或禁用缩放控件。true是启用,false是禁用。默认值为true。如果将选项设置为false,则缩放控件 将不会添加到地图中。
options.enableZoomControls = true;
// 用于启用或禁用距离图例。true是启用,false是禁用。默认值为true。如果将选项设置为false,距离图例将不会添加到地图中。
options.enableDistanceLegend = true;
// 用于启用或禁用指南针外环。true是启用,false是禁用。默认值为true。如果将选项设置为false,则该环将可见但无效。
options.enableCompassOuterRing = true; */
//添加指南针、罗盘、比例尺等
viewer.extend(Cesium.viewerCesiumNavigationMixin, {
// 用于在使用重置导航重置地图视图时设置默认视图控制。接受的值是Cesium.Cartographic 和Cesium.Rectangle.
defaultResetView: Cesium.Cartographic.fromDegrees(110, 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,
});
</script>
</body>
</html>
(2)结果显示
6.坐标测量
坐标测量是指在三维场景中,获取单击点位置的相关经纬度、高度信息并标注出来。测量功能是GIS系统必备的基本功能,下面介绍在三维场景中,如何对加载的三维模型、地形等实现坐标测量。
(1)实现代码
7_06_坐标测量.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_坐标测量</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
//viewer.scene.globe.depthTestAgainstTerrain = false;//开启深度检测后 会有高程遮挡效果
var tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: './倾斜摄影/大雁塔3DTiles/tileset.json'
})
);
viewer.zoomTo(tileset);
//实例化屏幕空间事件ScreenSpaceEventHandler对象
var handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
//实例化标签集合并添加至场景中
var annotations = viewer.scene.primitives.add(
new Cesium.LabelCollection()
);
//添加点
function createPoint(worldPosition) {
var point = viewer.entities.add({
position: worldPosition,
point: {
color: Cesium.Color.CRIMSON,
pixelSize: 9,
outlineColor: Cesium.Color.ALICEBLUE,
outlineWidth: 2,
disableDepthTestDistance: 1000 //当距离在1000以下不被高程遮挡
}
});
return point;
}
//添加点和标注框
function annotate(cartesian, lng, lat, height) {
//创建点
createPoint(cartesian);
//添加标注框
annotations.add({
position: cartesian,
text:
'Lon: ' + lng.toFixed(5) + '\u00B0' +
'\nLat: ' + lat.toFixed(5) + '\u00B0' +
"\nheight: " + height.toFixed(2) + "m",
showBackground: true,
font: '22px monospace',
horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
disableDepthTestDistance: Number.POSITIVE_INFINITY
});
}
//鼠标左键点击事件
handler.setInputAction(function (evt) {
var pickedObject = viewer.scene.pick(evt.position);//判断是否拾取到模型
console.log('pickedObject',pickedObject);
//如果拾取到模型
if (viewer.scene.pickPositionSupported && Cesium.defined(pickedObject)) {
var cartesian = viewer.scene.pickPosition(evt.position);
if (Cesium.defined(cartesian)) {
var cartographic = Cesium.Cartographic.fromCartesian(cartesian); //根据笛卡尔坐标获取到弧度
var lng = Cesium.Math.toDegrees(cartographic.longitude); //根据弧度获取到经度
var lat = Cesium.Math.toDegrees(cartographic.latitude); //根据弧度获取到纬度
var height = cartographic.height;//模型高度
annotate(cartesian, lng, lat, height);
}
}
/* //如果未拾取到模型,拾取到地形
else {
//在世界坐标系中从屏幕坐标向场景中创建射线
var ray = viewer.camera.getPickRay(evt.position);
//找到射线与渲染的地球表面之间的交点 值为Cartesian3类型
var cartesian = viewer.scene.globe.pick(ray, viewer.scene);
if (Cesium.defined(cartesian)) {
//根据交点得到经纬度、高度信息并添加点和标签
var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
var lng = Cesium.Math.toDegrees(cartographic.longitude); //根据弧度获取到经度
var lat = Cesium.Math.toDegrees(cartographic.latitude); //根据弧度获取到纬度
var height = cartographic.height;//高度
annotate(cartesian, lng, lat, height);
}
} */
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
//鼠标右键点击,删除点和标注框
handler.setInputAction(function () {
//清除点
viewer.entities.removeAll()
//清除标注框
annotations.removeAll();
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
</script>
</body>
</html>
(2)结果显示
7.距离测量
距离测量是指空间距离测量,即考虑点与点之间的位置坐标及高度差来计算两点之间的距离,不仅可以计算平面距离,还可以计算两个高度差较大的点(如屋顶与地面)之间的距离。下面将介绍如何实现空间距离测量。
(1)实现代码
7_07_空间距离测量.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_空间距离测量</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
//开启深度检测后 会有高程遮挡效果
viewer.scene.globe.depthTestAgainstTerrain = true;
var tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: './倾斜摄影/大雁塔3DTiles/tileset.json'
})
);
viewer.zoomTo(tileset);
//空间两点距离计算函数
function getSpaceDistance(positions) {
var distance = 0;
for (var i = 0; i < positions.length - 1; i++) {
//直接计算距离
distance += Cesium.Cartesian3.distance(positions[i],positions[i+1])
/**根据经纬度计算出距离**/
/* var point1cartographic = Cesium.Cartographic.fromCartesian(positions[i]);
var point2cartographic = Cesium.Cartographic.fromCartesian(positions[i + 1]);
var geodesic = new Cesium.EllipsoidGeodesic();
geodesic.setEndPoints(point1cartographic, point2cartographic);
var s = geodesic.surfaceDistance;
//console.log(Math.sqrt(Math.pow(distance, 2) + Math.pow(endheight, 2)));
//返回两点之间的距离
s = Math.sqrt(Math.pow(s, 2) + Math.pow(point2cartographic.height - point1cartographic.height, 2));
distance = distance + s; */
}
return distance.toFixed(2);
}
//绘制实体
var positions = []; //用来存储要计算距离的点
var activeShapePoints = []; //动态点数组
var activeShape; //动态图形
var floatingPoint; //第一个点 判断是否开始获取鼠标移动结束位置并添加至activeShapePoints
//绘制点与标签
function drawPoint(position, textDisance) {
var pointGeometry = viewer.entities.add({
name: "点几何对象",
position: position,
point: {
color: Cesium.Color.SKYBLUE,
pixelSize: 6,
outlineColor: Cesium.Color.YELLOW,
outlineWidth: 2,
disableDepthTestDistance: 1000 //当距离在1000以下不被高程遮挡
},
label: {
text: textDisance + "米",
font: '18px sans-serif',
fillColor: Cesium.Color.GOLD,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(20, -20),
heightReference: Cesium.HeightReference.NONE
}
});
return pointGeometry;
};
//绘制图形
function drawShape(positionData) {
var shape;
shape = viewer.entities.add({
polyline: {
positions: positionData,
width: 5.0,
material: new Cesium.PolylineGlowMaterialProperty({
color: Cesium.Color.GOLD,
}),
//clampToGround: true
}
});
return shape;
}
var handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
//鼠标左键
handler.setInputAction(function (event) {
var earthPosition = viewer.scene.pickPosition(event.position);
//计算距离
positions.push(earthPosition);
var disance = getSpaceDistance(positions);
// `如果鼠标不在地球上 则earthPosition是未定义
if (Cesium.defined(earthPosition)) {
//第一次点击时,通过CallbackProperty绘制动态图
if (activeShapePoints.length === 0) {
//floatingPoint = drawPoint(earthPosition,null);
activeShapePoints.push(earthPosition);
//动态点通过CallbackProperty实时更新渲染
var dynamicPositions = new Cesium.CallbackProperty(function () {
return activeShapePoints;
}, false);
activeShape = drawShape(dynamicPositions);//绘制动态图
}
//添加当前点进activeShapePoints,实时渲染动态图
activeShapePoints.push(earthPosition);
floatingPoint = drawPoint(earthPosition, disance);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
//鼠标移动
handler.setInputAction(function (event) {
if (Cesium.defined(floatingPoint)) {
var newPosition = viewer.scene.pickPosition(event.endPosition); //获取鼠标移动最终位置
if (Cesium.defined(newPosition)) {
//动态去除数组中最后一个点,再添加最新一个点,保证只保留鼠标位置点
activeShapePoints.pop();
activeShapePoints.push(newPosition);
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
//鼠标右键
handler.setInputAction(function (event) {
activeShapePoints.pop();//去除最后一个动态点
if (activeShapePoints.length) {
drawShape(activeShapePoints);//绘制最终图
}
//viewer.entities.remove(floatingPoint);//移除第一个点(重复了)
viewer.entities.remove(activeShape);//去除动态图形
floatingPoint = undefined;
activeShape = undefined;
activeShapePoints = [];
positions = []; //清空计算距离的点数组
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
</script>
</body>
</html>
(2)结果显示
8.面积测量
面积测量是指空间面积测量,即考虑点与点之间的位置坐标及高度差来计算每相邻两点之间的角度、距离,并通过拆分三角形来计算整个封闭多边形的空间面积。面积测量在交互绘制时,如果绘制点超过两个,就开始计算相邻点之间的角度、距离,并在绘制结束后根据点与点之间的角度、距离来拆分三角形,从而计算整个多边形的面积。下面介绍如何实现空间面积测量。
(1)实现代码
7_08_空间面积测量.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_空间面积测量</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium//Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
viewer.scene.globe.depthTestAgainstTerrain = true;//开启深度检测后 会有高程遮挡效果
var tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: './倾斜摄影/大雁塔3DTiles/tileset.json'
})
);
viewer.zoomTo(tileset);
var radiansPerDegree = Math.PI / 180.0;//角度转化为弧度(rad)
var degreesPerRadian = 180.0 / Math.PI;//弧度转化为角度
/*方向*/
function getBearing(from, to) {
var from = Cesium.Cartographic.fromCartesian(from);
var to = Cesium.Cartographic.fromCartesian(to);
//度转弧度表示
var lat1 = from.latitude * radiansPerDegree;
var lon1 = from.longitude * radiansPerDegree;
var lat2 = to.latitude * radiansPerDegree;
var lon2 = to.longitude * radiansPerDegree;
//返回从原点(0,0)到(x,y)点的线段与x轴正方向之间的平面角度(弧度值)
var angle = -Math.atan2(
Math.sin(lon1 - lon2) * Math.cos(lat2),
Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon1 - lon2)
);
if (angle < 0) {
angle += Math.PI * 2.0;
}
//弧度转角度表示
angle = angle * degreesPerRadian;
return angle;
}
/*角度*/
function getAngle(p1, p2, p3) {
var bearing21 = getBearing(p2, p1);
var bearing23 = getBearing(p2, p3);
var angle = bearing21 - bearing23;
if (angle < 0) {
angle += 360;
}
return angle;
}
/* 距离 */
function getDistance(point1, point2) {
//console.log('point1',point1);
/* var point1cartographic = Cesium.Cartographic.fromCartesian(point1);
var point2cartographic = Cesium.Cartographic.fromCartesian(point2); */
/* console.log('point1', point1);
console.log('point2', point2); */
/**根据经纬度计算出距离**/
var geodesic = new Cesium.EllipsoidGeodesic();
//设置测地线的起点和终点
geodesic.setEndPoints(point1, point2);
//获取起点和终点之间的表面距离
var s = geodesic.surfaceDistance;
//返回两点之间的距离 c等于(a方+b方)再开平方
s = Math.sqrt(Math.pow(s, 2) + Math.pow(point2.height - point1.height, 2));
return s;
}
//计算多边形面积
function getArea(points) {
var res = 0;
//拆分三角曲面
for (var i = 0; i < points.length - 2; i++) {
var j = (i + 1) % points.length;
var k = (i + 2) % points.length;
var totalAngle = getAngle(points[i], points[j], points[k]);
var totalAngle = totalAngle.toFixed();
var dis_temp1 = getDistance(pCartographic[i], pCartographic[j]);
var dis_temp2 = getDistance(pCartographic[j], pCartographic[k]);
console.log('dis_temp1', dis_temp1);
console.log('dis_temp2', dis_temp2);
console.log('totalAngle', totalAngle);
console.log('Math.abs(Math.sin(totalAngle))', Math.sin(totalAngle));
console.log('Math.abs(Math.sin(totalAngle))', Math.round(Math.sin((totalAngle * Math.PI / 180)) * 1000000) / 1000000);
//Math.abs为绝对值
//不知道为什么Math.sin(totalAngle)值有时准确有时不准确
/* res += dis_temp1 * dis_temp2 * Math.abs(Math.sin(totalAngle)); */
//查了下 解决办法如下
res += dis_temp1 * dis_temp2 * Math.abs(Math.round(Math.sin((totalAngle * Math.PI / 180)) * 1000000) / 1000000);
console.log(res);
}
//单位是平方米
return res.toFixed(2);
}
//添加label
function addLabel(pCartographic, text) {
//将label添加到最后一个点上方
var position = Cesium.Cartesian3.fromRadians(
pCartographic[pCartographic.length - 1].longitude,
pCartographic[pCartographic.length - 1].latitude,
pCartographic[pCartographic.length - 1].height)
var label = viewer.entities.add({
position: position,
label: {
text: text + "平方米",
font: '18px sans-serif',
fillColor: Cesium.Color.GOLD,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(20, -20),
heightReference: Cesium.HeightReference.NONE
}
});
}
//绘制实体
var p = [];
var pCartographic = [];
var activeShapePoints = []; //动态点数组
var activeShape; //动态图形
var floatingPoint; //第一个点 判断是否开始获取鼠标移动结束位置并添加至activeShapePoints
//绘制点
function drawPoint(position) {
var pointGeometry = viewer.entities.add({
name: "点几何对象",
position: position,
point: {
color: Cesium.Color.SKYBLUE,
pixelSize: 6,
outlineColor: Cesium.Color.YELLOW,
outlineWidth: 2,
disableDepthTestDistance: 1000 //当距离在1000以下不被高程遮挡
}
});
return pointGeometry;
};
//绘制多边形
function drawShape(positionData) {
var shape;
shape = viewer.entities.add({
polygon: {
hierarchy: positionData,
material: new Cesium.ColorMaterialProperty(Cesium.Color.SKYBLUE.withAlpha(0.7))
}
});
return shape;
}
var handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
//鼠标左键
handler.setInputAction(function (event) {
var earthPosition = viewer.scene.pickPosition(event.position);
console.log('earthPosition', Cesium.Cartographic.fromCartesian(earthPosition));
pCartographic.push(Cesium.Cartographic.fromCartesian(earthPosition));
p.push(earthPosition);
// `如果鼠标不在地球上 则earthPosition是未定义
if (Cesium.defined(earthPosition)) {
//第一次点击时,通过CallbackProperty绘制动态图
if (activeShapePoints.length === 0) {
floatingPoint = drawPoint(earthPosition);
activeShapePoints.push(earthPosition);
//动态点通过CallbackProperty实时更新渲染
var dynamicPositions = new Cesium.CallbackProperty(function () {
//绘制模式是polygon 回调返回的值就要是PolygonHierarchy类型
return new Cesium.PolygonHierarchy(activeShapePoints);
}, false);
activeShape = drawShape(dynamicPositions);//绘制动态图
}
//添加当前点进activeShapePoints,实时渲染动态图
activeShapePoints.push(earthPosition);
drawPoint(earthPosition);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
//鼠标移动
handler.setInputAction(function (event) {
if (Cesium.defined(floatingPoint)) {
var newPosition = viewer.scene.pickPosition(event.endPosition); //获取鼠标移动最终位置
if (Cesium.defined(newPosition)) {
//动态去除数组中最后一个点,再添加最新一个点,保证只保留鼠标位置点
activeShapePoints.pop();
activeShapePoints.push(newPosition);
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
//鼠标右键
handler.setInputAction(function (event) {
activeShapePoints.pop();//去除最后一个动态点
if (activeShapePoints.length) {
drawShape(activeShapePoints);//绘制最终图
}
console.log('points', p);
console.log('pCartographic', pCartographic);
//计算面积
var text = getArea(p);
//添加标注
addLabel(pCartographic, text);
viewer.entities.remove(floatingPoint);//移除第一个点(重复了)
viewer.entities.remove(activeShape);//去除动态图形
floatingPoint = undefined;
activeShape = undefined;
activeShapePoints = [];
//清空存储测量面积的点
p = [];
pCartographic = [];
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
</script>
</body>
</html>
(2)结果显示
9.热力图
热力图是目前GIS常用的功能之一,它是通过颜色分布,描述人群分布、密度和变化趋势等的一种地图表现手法。热力图能够清晰地反映出样本数据的分布格局,例如,某选项的数值越大,其颜色越深。
Cesium不同于二维的OpenLayers、Leaflet等,它本身并没有提供热力图相关接口,如果需要该功能,就需要自己开发。但是,对于初学者来说,要想独立实现热力图分析功能还是比较困难的,因此在此介绍一个插件CesiumHeatmap.js。该插件对于初学者十分友好,只需引入该插件即可方便地在Cesium中创建热力图,并支持热点半径、模糊尺寸等参数的指定。下面具体介绍该插件的使用。
(1)实现代码
7_09_热力图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_热力图</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<!-- 引入JQuery -->
<script src="./Build/js/jquery.min.js"></script>
<!-- 引入CesiumHeatmap.js -->
<script src="./Build/js/CesiumHeatmap.js"></script>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
//定义热力图边界
var bounds = {
west: 114,
east: 115,
south: 30,
north: 31
};
//定义最大最小热力值
var valueMin = 0;
var valueMax = 100;
//定义热力图参数
var params = {
radius: 150, //热点半径
maxOpacity: 0.5,//最大不透明度
minOpacity: 0,//最小不透明度
blur: 0.75 //模糊尺寸
}
//创建热力图对象
var heatMap = CesiumHeatmap.create(
viewer,
bounds,
params
);
/* //拿数据
window.onload = function () {
var url = "矢量文件/json/heatmap.json" //json文件url,本地的就写本地的位置,如果是服务器的就写服务器的路径
var request = new XMLHttpRequest();
request.open("get", url); //设置请求方法与路径
request.send();//发送请求但不发送数据到服务器
request.onload = function () {//XHR对象获取到返回信息后执行
if (request.status == 200) {//返回状态为200,即为数据获取成功
var json = JSON.parse(request.responseText);
for (var i = 0; i < json.length; i++) {
var point = {
x: json[i].x,
y: json[i].y,
value: json[i].value
}
points.push(point);
}
console.log('points', points);
heatMap.setWGS84Data(valueMin, valueMax, points);
console.log('成功');
}
else {
console.log('读取失败');
}
}
} */
//发送ajax请求
$.ajax({
url: "矢量文件/json/heatmap.json", //json数据地址
type: "GET",
dataType: "json",
success: function (data) {
console.log('data', data);
heatMap.setWGS84Data(valueMin, valueMax, data);
}
})
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(114.1977, 30.6533, 200000),
})
</script>
</body>
</html>
(2)结果显示
10.视频投影
视频投影或者说视频投射,就是在三维场景中,将视频投影在场景中的地形或者模型上进行播放。实现原理比较简单,就是将Video视频作为材质赋予实体,如墙、多边形等,和材质特效篇中视频材质的原理差不多。也可以通过ShadowMap方式来实现,不过会比较麻烦,但是效果更好。下面介绍两种视频投影方式:第一种是创建实体墙来投影视频;第二种是通过多边形将视频投影到模型上。
视频资源网址: https://cesium.com/public/SandcastleSampleData/big-buck-bunny_trailer.mp4
(1)实现代码
7_10_视频投影.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>工具应用篇_视频投影</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
</head>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.toolbar {
position: absolute;
top: 10px;
left: 20px;
background-color: rgb(0, 0, 0, 0);
}
</style>
<body>
<div id="cesiumContainer">
</div>
<div class="toolbar">
<select id="dropdown" onchange="change()">
<option value="option1">视频墙</option>
<option value="option2">视频贴地</option>
</select>
</div>
<video id="myVideo" muted="true" autoplay="true" loop="true" style="display: none;">
<source src="./vedio/big-buck-bunny_trailer.mp4" type="video/mp4">
</video>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer('cesiumContainer', {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
/* var tileSet = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: "./3D格式数据/Tileset/tileset.json"
})
);
//定位过去
viewer.zoomTo(tileSet); */
//添加OSM建筑白膜数据
var osmBuildingsTileset = Cesium.createOsmBuildings();
viewer.scene.primitives.add(osmBuildingsTileset);
function videoWall() {
//获取Video标签
const videoElement = document.getElementById("myVideo");
//将视频元素与模拟时钟同步
let synchronizer = new Cesium.VideoSynchronizer({
clock: viewer.clock,
element: videoElement
});
//自动播放
viewer.clock.shouldAnimate = true;
//创建墙
var greenWall = viewer.entities.add({
name: "视频墙",
wall: {
positions: Cesium.Cartesian3.fromDegreesArrayHeights([
114.391418, 30.524281, 120.0, 114.391918, 30.524281, 120.0,
]),
minimumHeights: [90, 90],
material: videoElement,
outline: true,
shadows: Cesium.ShadowMode.ENABLED
},
})
viewer.zoomTo(greenWall);
}
function videoFusion() {
//获取Video标签
const videoElement = document.getElementById("myVideo");
//将视频元素与模拟时钟同步
let synchronizer = new Cesium.VideoSynchronizer({
clock: viewer.clock,
element: videoElement
});
//自动播放
viewer.clock.shouldAnimate = true;
var polygon = viewer.entities.add({
polygon: {
hierarchy: new Cesium.PolygonHierarchy(Cesium.Cartesian3.fromDegreesArray(
[114.39344518569266, 30.525768035404223,
114.3961071839177, 30.52566180691624,
114.3960458511302, 30.524014906984178,
114.39344432117545, 30.52402876336925,
])
),
classificationType: Cesium.ClassificationType.BOTH,
material: videoElement,
stRotation: -45,
},
})
}
videoWall();
var dropdown = document.getElementById('dropdown');
function change() {
switch (dropdown.value) {
case 'option1':
viewer.entities.removeAll();
videoWall();
break;
case 'option2':
viewer.entities.removeAll();
videoFusion();
break;
default:
break;
}
}
</script>
</body>
</html>
(2)结果显示
11.日照分析
日照分析是指在指定日期模拟建筑或建筑群受光照影响的变换。Cesium中提供了日照阴影效果,我们可以基于Cesium日照阴影,修改时钟并提高时间变换速率,在短时间内模拟指定日期的某段时间内的日照变化情况。下面以在线OSM建筑白膜数据为例,模拟日照变化情况。
(1)实现代码
7_11_日照分析.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_日照分析</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.toolbar {
position: absolute;
top: 10px;
left: 20px;
background-color: rgb(0, 0, 0, 0);
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<div class="toolbar">
<input type="text" id="Date" value="2021-5-10" placeholder="请输入分析日期" maxlength="10"><br>
<input type="text" id="Ktime" value="8" placeholder="请输入开始时间" maxlength="6"><br>
<input type="text" id="Ttime" value="18" placeholder="请输入停止时间" maxlength="6"><br>
<button class="kjfxbutton" onclick="setvisible()">开始</button>
<button class="kjfxbutton" onclick="guanbi()">清除</button>
</tr>
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
//设置每个阴影贴图的宽度和高度(以像素为单位)
viewer.shadowMap.size = 8192;
viewer.shadowMap.darkness = 0.1;
//添加OSM建筑白膜数据
var osmBuildingsTileset = Cesium.createOsmBuildings();
viewer.scene.primitives.add(osmBuildingsTileset);
//调整相机视角
viewer.scene.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(114.29964, 30.61214, 2000),
//destination:Cesium.Cartesian3.fromDegrees(-74.0012351579127,40.715093849131,1000),
});
function stratPlay() {
viewer.shadows = true;//开启阴影
viewer.clock.shouldAnimate = true
//定义变量
var text1 = document.getElementById("Date");
var text2 = document.getElementById("Ktime");
var text3 = document.getElementById("Ttime");
var e = text1.value,
t = new Date(e),
i = text2.value,
a = text3.value,
r = new Date(new Date(t).setHours(Number(i))),
o = new Date(new Date(t).setHours(Number(a)));
//设置参数
viewer.scene.globe.enableLighting = true,
viewer.shadows = true,
viewer.clock.startTime = Cesium.JulianDate.fromDate(r),
viewer.clock.currentTime = Cesium.JulianDate.fromDate(r),
viewer.clock.stopTime = Cesium.JulianDate.fromDate(o),
//到达stopTime后时钟跳转到startTime
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP,
viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER,
viewer.clock.multiplier = 1600
}
function setvisible() {
if (document.getElementById("Date").value == "" ||
document.getElementById("Ktime").value == "" ||
document.getElementById("Ttime").value == "")
{
alert("请输入有效参数!");
}
else {
//开始模拟
stratPlay();
}
}
function guanbi() {
viewer.scene.globe.enableLighting = false; //关闭光照
viewer.shadows = false;//关闭阴影
}
</script>
</body>
</html>
(2)结果显示
12.淹没分析
淹没分析是指根据指定的最大高程、最小高程及淹没速度,动态模拟某区域水位由最小高程涨到最大高程的淹没过程,常使用地形、模型数据。Cesium可以通过绘制实体面,并不断抬高实体面的高度来模拟水位上涨的过程。下面介绍如何在Cesium中实现淹没分析。
(1)实现代码
7_12_淹没分析.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_淹没分析</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.toolbar {
top: 20px;
left: 40px;
position: absolute;
line-height: 25px;
}
.toolbar input {
width: 140px;
height: 23px;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<div class="toolbar">
<input type="text" id="startHeight" placeholder="请输入起始高度"><br>
<input type="text" id="stopHeight" placeholder="请输入终止高度"><br>
<input type="text" id="speed" placeholder="请输入增长速度"><br>
<button onclick="draw()">绘制淹没区域</button>
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
//开启深度检测
viewer.scene.globe.depthTestAgainstTerrain = true;
var height; //水位高度
var maxHeight; //最大高度
var speed; //增长速度
var positions = []; //绘制多边形顶点
var handler;
var addRegion //多边形
//调整相机视角
viewer.scene.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(114.38564, 30.52914, 2000),
});
//水位高度更新函数
function updataHeight() {
if (height < maxHeight)
height += speed;
return height;
}
//绘制分析区域
function addPolygon(hierarchy) {
addRegion = {
id: 'polygon',
name: '多边形',
show: true,
polygon: {
hierarchy: hierarchy,
material: new Cesium.ImageMaterialProperty({
image: "./RasterImage/图片/河流纹理.png",
repeat: Cesium.Cartesian2(1.0, 1.0),
transparent: true,
color: Cesium.Color.WHITE.withAlpha(0.2),
}),
height: new Cesium.CallbackProperty(updataHeight, false),
}
}
viewer.entities.add(addRegion);
handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)//移除事件
handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK)//移除事件
}
function draw() {
height = parseFloat(document.getElementById("startHeight").value);
maxHeight = parseFloat(document.getElementById("stopHeight").value);
speed = parseFloat(document.getElementById("speed").value);
viewer.entities.remove(addRegion);
handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
//鼠标左键
handler.setInputAction(function (event) {
// 用 `viewer.scene.pickPosition` 代替 `viewer.camera.pickEllipsoid`
// 当鼠标在地形上移动时可以得到正确的点.
var earthPosition = viewer.scene.pickPosition(event.position);
positions.push(earthPosition);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
//鼠标右键
handler.setInputAction(function (event) {
addPolygon(positions);
positions = [];
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
}
</script>
</body>
</html>
(2)结果显示
左键点击绘制多边形,右键进行淹没分析。
13.通视分析
通视分析是指在三维场景中绘制两点,将其中一点作为观察点,另一点作为目标点,若从观察点能够直接看到目标点,则这两点之间就是可通视的。Cesium中实现通视分析的原理大致相同,只不过判断条件略有不同,在Cesium三维场景中,先选择一个观察点,再选择一个目标点,若两点之间的连线和场景没有交点,则证明从观察点可以直接看到目标点,即两点之间可通视。我们可以以此为切入点,若两点之间的连线和场景有交点,则从观察点到交点部分绘制一条绿色的线代表可视,从交点到目标点部分绘制一条红色线代表不可视。下面介绍具体的实现。
(1)实现代码
7_13_通视分析.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_通视分析</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
viewer.scene.globe.depthTestAgainstTerrain = true;//开启深度检测
var tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: './倾斜摄影/大雁塔3DTiles/tileset.json'
})
);
viewer.zoomTo(tileset);
//绘制点
function drawPoint (position) {
var pointGeometry = viewer.entities.add({
name: "点几何对象",
position: position,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
point: {
color: Cesium.Color.SKYBLUE,
pixelSize: 6,
outlineColor: Cesium.Color.YELLOW,
outlineWidth: 2,
}
});
return pointGeometry;
};
//绘制线
function drawLine (startPosition, endPosition, color) {
viewer.entities.add({
polyline: {
positions: [startPosition, endPosition],
width: 2,
material: color,
depthFailMaterial: color //用于指定折线低于地形时用于绘制折线的材料
}
});
}
function startAnaly (positions) {
//计算两点分量差异
var subtract = Cesium.Cartesian3.subtract(
positions[1], //目标点
positions[0], //观察点
new Cesium.Cartesian3()
)
//标准化计算射线方向
var direction = Cesium.Cartesian3.normalize(
subtract,
new Cesium.Cartesian3()
);
//创建射线
var ray = new Cesium.Ray(positions[0], direction);
//计算交点
var result = viewer.scene.pickFromRay(ray, []); //返回第一个交点
//若有交点
if (result !== undefined && result !== null) {
drawLine(result.position, positions[0], Cesium.Color.GREEN); // 可视
drawLine(result.position, positions[1], Cesium.Color.RED); // 不可视
}
//没交点
else {
drawLine(positions[0], positions[1], Cesium.Color.GREEN);
}
}
//存储观察点和目标点
var positions = [];
var handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
//鼠标左键点击事件
handler.setInputAction(function (evt) {
var pickedObject = viewer.scene.pick(evt.position); //判断是否拾取到模型
//var pickedObject = viewer.scene.pickPosition(evt.position);//判断是否拾取到地形
if (viewer.scene.pickPositionSupported && Cesium.defined(pickedObject)) {
var cartesian = viewer.scene.pickPosition(evt.position);
if (Cesium.defined(cartesian)) {
console.log('cartesian', cartesian);
//保证每次只有一个观察点和一个目标点
if (positions.length < 2) {
drawPoint(cartesian);
positions.push(cartesian);
}
else {
alert("观察点和目标点是唯一的!");
}
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
//鼠标右键点击事件
handler.setInputAction(function (evt) {
startAnaly(positions);
positions = [];//每次绘制完线清空坐标点数组
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
</script>
</body>
</html>
(2)结果显示
14.可视域分析
可视域分析是指在三维场景中选取模型表面某一点,并以该点为观察点,基于一定的水平夹角、垂直夹角及范围半径分析得到范围内所有通视点的集合。
在Cesium中,可视域分析的实现方法不止一种,但较为常见的一种实现方法是通过ShadowMap实现可视域分析。ShadowMap的官方解释是获取源自太阳的场景贴图,通过ShadowMap实现可视域分析的原理大致如下:先创建一个相机并设置阴影贴图,然后创建场景后处理对象,对阴影贴图进行修改,分为红色和绿色,最后绘制视锥线和视网等。目前,通过ShadowMap实现可视域分析已经有了很多插件案例,实现方法都差不多,因此我们可以直接利用前人造好的“轮子”。在此我们介绍一个可视域分析插件cesium-viewshed。引入该插件即可方便地进行可视域分析。下面具体介绍该插件的使用。
(1)实现代码
7_14_可视域分析.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_可视域分析</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<!-- 引入cesium-vieweshed -->
<script src="./Build/js/cesium-viewshed.js"></script>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.toolbar {
top: 20px;
left: 40px;
position: absolute;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<div class="toolbar">
<button onclick="add()">添加可视域</button>
<button onclick="remove()">删除可视域</button>
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
/* viewer.scene.globe.enableLighting = true;
viewer.shadows = true; */
//开启深度检测后 会有高程遮挡效果
viewer.scene.globe.depthTestAgainstTerrain = true;
var tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: './倾斜摄影/大雁塔3DTiles/tileset.json'
})
);
viewer.zoomTo(tileset);
// 分析参数
var viewModel = { verticalAngle: 90, horizontalAngle: 120};
//存储分析结果
var arrViewField = [];
// 添加可视域
function add() {
var viewshed = new Cesium.ViewShed3D(viewer, {
horizontalAngle: Number(viewModel.horizontalAngle),
verticalAngle: Number(viewModel.verticalAngle),
/*distance: Number(viewModel.distance),
calback: function () {
viewModel.distance = viewshed.distance;
console.log('viewModel.distance',viewModel.distance);
} */
});
arrViewField.push(viewshed)
}
function remove() {
for (var i = 0; i < arrViewField.length; i++) {
arrViewField[i].destroy()
}
arrViewField = [];
}
</script>
</body>
</html>
(2)结果显示
15.缓冲区分析
缓冲区分析是指以点、线、面实体为基础,自动建立一定宽度范围内的缓冲区多边形图层,然后建立该图层与目标图层的叠加部分,并进行分析而得到所需结果。它是用来解决邻近度问题的空间分析工具之一。
Cesium本身并没有提供相关的接口,需要借助一个轻量级的用于空间分析的前端库Turf.js来实现缓冲区分析。Turf.js主要用于计算空间对象的关系,可以方便地计算空间对象的相交、包含等关系,只需通过CDN引入或者通过NPM下载,即可开始使用。Turf.js中文网的地址为https://turfjs.fenxianglu.cn。下面介绍如何使用Turf.js在Cesium中实现缓冲区分析。
(1)实现代码
7_15_缓冲区分析.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_缓冲区分析</title>
<script src="./Build/CesiumUnminified/Cesium.js"></script>
<link rel="stylesheet" href="./Build/CesiumUnminified/Widgets/widgets.css">
<!-- 引入turf.js -->
<script src="https://unpkg.com/@turf/turf/turf.min.js"></script>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
terrainProvider: Cesium.createWorldTerrain(),
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
});
//格式转换 将点数据拆成一个大数组
function pointsFormatConv(points) {
let degreesArray = [];
//拆分合并
points.map(item => {
degreesArray.push(item[0]);
degreesArray.push(item[1]);
});
return degreesArray;
}
// 添加缓冲区
function addBuffer(positions) {
viewer.entities.add({
polygon: {
hierarchy: new Cesium.PolygonHierarchy(positions),
material: Cesium.Color.RED.withAlpha(0.7),
},
});
}
// 添加点
function addPoint(point) {
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(point[0], point[1]),
point: {
pixelSize: 9,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
color: Cesium.Color.BLUE,
},
});
}
// 初始化点缓冲
function initPointBuffer() {
let point = [114.40086, 30.51888];
addPoint(point);
//创建点要素
let pointF = turf.point(point);
let buffered = turf.buffer(pointF, 60, { units: "meters" });
let coordinates = buffered.geometry.coordinates;
let points = coordinates[0];
/* console.log('points',points); */
let degreesArray = pointsFormatConv(points);
/* console.log('degreesArray',degreesArray); */
addBuffer(Cesium.Cartesian3.fromDegreesArray(degreesArray));
}
//添加线
function addPolyline(positions) {
viewer.entities.add({
polyline: {
positions: Cesium.Cartesian3.fromDegreesArray(positions),
width: 2,
material: Cesium.Color.BLUE,
clampToGround: true
}
})
}
//初始化线缓冲
function initPolylineBuffer() {
let points = [
[114.3950, 30.5200],
[114.3990, 30.5200],
[114.4020, 30.5230],
];
let degreesArray = pointsFormatConv(points);
addPolyline(degreesArray);
//创建线要素
let polylineF = turf.lineString(points);
//创建缓冲区
let buffered = turf.buffer(polylineF, 30, { units: 'meters' });
let coordinates = buffered.geometry.coordinates;
points = coordinates[0];
degreesArray = pointsFormatConv(points);
addBuffer(Cesium.Cartesian3.fromDegreesArray(degreesArray));
}
//添加面
function addPolygon(positions) {
viewer.entities.add({
polygon: {
hierarchy: new Cesium.PolygonHierarchy(positions),
material: Cesium.Color.BLUE.withAlpha(0.6),
}
});
}
//初始化面缓冲
function initPolygonBuffer() {
let points = [
[114.3940, 30.5220],
[114.3970, 30.5220],
[114.3980, 30.5240],
[114.3960, 30.5250],
[114.3940, 30.5220],
];
let degreesArray = pointsFormatConv(points);
addPolygon(Cesium.Cartesian3.fromDegreesArray(degreesArray));
//创建面要素
let polygonF = turf.polygon([points]);
let buffered = turf.buffer(polygonF, 60, { units: 'meters' });
let coordinates = buffered.geometry.coordinates;
points = coordinates[0];
degreesArray = pointsFormatConv(points);
addBuffer(Cesium.Cartesian3.fromDegreesArray(degreesArray));
}
//初始化缓冲区
initPointBuffer();
initPolylineBuffer();
initPolygonBuffer();
//调整相机视角
viewer.scene.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(114.40086, 30.51888, 2000),
//destination: Cesium.Cartesian3.fromDegrees(-74.0012351579127, 40.715093849131, 1000),
});
</script>
</body>
</html>
(2)结果显示
16.地形开挖
地形开挖是指在地形表面根据多边形范围挖空地形,常用于地下管网、排污管道等地下场景。Cesium提供了ClippingPlane、ClippingPlaneCollection等接口,可以实现地形开挖的效果。下面介绍如何实现地形开挖的效果。
(1)实现代码
7_16_地形开挖.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_地形开挖</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<script>
//#region 初始化
Cesium.Ion.defaultAccessToken = '你的token';
var terrainModels = Cesium.createWorldTerrain();
var viewer = new Cesium.Viewer("cesiumContainer", {
terrainProvider: terrainModels,
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
});
//#endregion
//开启深度检测
viewer.scene.globe.depthTestAgainstTerrain = true;
//调整相机视角
viewer.scene.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(114.39564, 30.52214, 2000),
});
var points = []; //填挖点
var controls = []; //插值控制点
var hierarchy = []; //绘制面的坐标点
//实例化ScreenSpaceEventHandler对象
var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
//封装挖地形函数
function DrawClippingPlane(points) {
var pointsLength = points.length;
var clippingPlanes = []; // 存储ClippingPlane集合
//计算裁剪面
for (var i = 0; i < pointsLength; ++i) {
var nextIndex = (i + 1) % pointsLength;
//计算两个笛卡尔的按分量求和
var midpoint = Cesium.Cartesian3.add(points[i], points[nextIndex], new Cesium.Cartesian3());
//缩放笛卡尔坐标
midpoint = Cesium.Cartesian3.multiplyByScalar(midpoint, 0.5, midpoint);
//计算提供的笛卡尔坐标系的标准化形式
var up = Cesium.Cartesian3.normalize(midpoint, new Cesium.Cartesian3());
//计算两个笛卡尔的分量差异
var right = Cesium.Cartesian3.subtract(points[nextIndex], midpoint, new Cesium.Cartesian3());
//计算提供的笛卡尔坐标系的标准化形式
right = Cesium.Cartesian3.normalize(right, right);
//计算两个笛卡尔的叉(外)乘积
var normal = Cesium.Cartesian3.cross(right, up, new Cesium.Cartesian3());
//计算提供的笛卡尔坐标系的标准化形式
normal = Cesium.Cartesian3.normalize(normal, normal);
//原始中心平面
var originCenteredPlane = new Cesium.Plane(normal, 0.0);
//计算点到平面的有符号最短距离
var distance = Cesium.Plane.getPointDistance(originCenteredPlane, midpoint);
clippingPlanes.push(new Cesium.ClippingPlane(normal, distance));
}
//创建ClippingPlaneCollection对象
var ClippingPlaneCollectionObj = new Cesium.ClippingPlaneCollection({
planes: clippingPlanes,
edgeWidth: 1.0,
edgeColor: Cesium.Color.YELLOW
});
//赋值给globe的clippingPlanes
viewer.scene.globe.clippingPlanes = ClippingPlaneCollectionObj;
console.log("创建完毕!");
}
//封装绘制面、贴纹理函数
function addPolygon(hierarchy) {
viewer.entities.add({
polygon: {
hierarchy: Cesium.Cartesian3.fromDegreesArrayHeights(hierarchy),
material: new Cesium.ImageMaterialProperty({
image: "./RasterImage/图片/挖地贴图.png"
}),
closeTop: false, // 这个要设置为false
extrudedHeight: 0,
perPositionHeight: true, // 这个要设置true
}
});
}
//获取指定位置高度
function getHeight(position) {
height = viewer.scene.globe.getHeight(position);
return height;
}
//样条插值
function interpolation(point1, point2) {
var spline = new Cesium.LinearSpline({
times: [0.0, 1],
points: [point1, point2]
});
for (var i = 0; i <= 100; i++) {
var cartesian3 = spline.evaluate(i / 100);
var cartographic = Cesium.Cartographic.fromCartesian(cartesian3);
var lat = Cesium.Math.toDegrees(cartographic.latitude);
var lng = Cesium.Math.toDegrees(cartographic.longitude);
var height = getHeight(cartographic);
hierarchy.push(lng);
hierarchy.push(lat);
hierarchy.push(height);
}
}
//鼠标左键点击选点
handler.setInputAction(function (event) {
var earthPosition = viewer.scene.pickPosition(event.position);
var cartographic = Cesium.Cartographic.fromCartesian(earthPosition);
var lat = Cesium.Math.toDegrees(cartographic.latitude);
var lng = Cesium.Math.toDegrees(cartographic.longitude);
//var height = cartographic.height;
controls.push(Cesium.Cartesian3.fromDegrees(lng, lat));//添加控制点
points.push(Cesium.Cartesian3.fromDegrees(lng, lat));//添加填挖点
//当控制点大于等于两个的时候进行插值,每次插值最新添加的两个点
if (controls.length > 1) {
interpolation(controls[controls.length - 2], controls[controls.length - 1])
}
/* hierarchy.push(lng);
hierarchy.push(lat);
hierarchy.push(height); */
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
//点击右键挖地形、绘制面以及贴纹理
handler.setInputAction(function (event) {
console.log('选择完毕!');
//全部选择完后对控制点最后一个点和第一个点直接进行插值
interpolation(controls[controls.length - 1], controls[0])
//移除贴图时添加的polygon
viewer.entities.removeAll();
//挖地形
DrawClippingPlane(points);
//绘制面、贴纹理
addPolygon(hierarchy);
//将数组清空
points = [];
hierarchy = [];
controls = [];
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
/* handler.setInputAction(function (event) {
var earthPosition = viewer.scene.pickPosition(event.position);
var cartographic = Cesium.Cartographic.fromCartesian(earthPosition);
var lat = Cesium.Math.toDegrees(cartographic.latitude);
var lng = Cesium.Math.toDegrees(cartographic.longitude);
var height = cartographic.height;
controls.push(Cesium.Cartesian3.fromDegrees(lng, lat));//添加控制点
if (controls.length > 1) {
interpolation(controls[controls.length - 1], controls[controls.length - 2])
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
function interpolation(point1, point2) {
var spline = new Cesium.LinearSpline({
times: [0.0, 1],
points: [point1, point2],
});
var positions = [];
for (var i = 0; i <= 50; i++) {
var cartesian3 = spline.evaluate(i / 50);
positions.push(cartesian3);
viewer.entities.add({
position: cartesian3,
point: {
color: Cesium.Color.YELLOW,
pixelSize: 6,
heightReference:Cesium.HeightReference.CLAMP_TO_GROUND
},
});
}
viewer.entities.add({
name: "LinearSpline",
polyline: {
positions: positions,
width: 3,
material: Cesium.Color.GREEN,
},
});
} */
/* var controls = []
controls.push(Cesium.Cartesian3.fromDegrees(120.11551237582385, 35.97934910503657))
controls.push(Cesium.Cartesian3.fromDegrees(121.1367529, 35.9629172))
controls.push(Cesium.Cartesian3.fromDegrees(122.1367529, 36.9629172))
controls.push(Cesium.Cartesian3.fromDegrees(123.8632471, 36.9629172))
controls.push(Cesium.Cartesian3.fromDegrees(124.1367529, 35.9629172))
for (var i = 0; i < controls.length; i++) {
viewer.entities.add({
position: controls[i],
point: {
color: Cesium.Color.RED,
pixelSize: 10,
},
});
}
viewer.zoomTo(viewer.entities);
var spline = new Cesium.LinearSpline({
times: [0.0, 0.25, 0.5, 0.75, 1],
points: controls,
});
var positions = [];
for (var i = 0; i <= 50; i++) {
var cartesian3 = spline.evaluate(i / 50);
positions.push(cartesian3);
viewer.entities.add({
position: cartesian3,
point: {
color: Cesium.Color.YELLOW,
pixelSize: 6,
},
});
}
viewer.entities.add({
name: "LinearSpline",
polyline: {
positions: positions,
width: 3,
material: Cesium.Color.GREEN,
},
}); */
</script>
</body>
</html>
(2)结果显示
左键绘制多边形,右键开挖。
17.要素聚合
要素聚合是指在一个小区域中加载大量要素时,根据要素之间的距离将一定范围内的要素聚合在一起显示,从而解决要素过于密集而导致的相互遮盖问题。Cesium官方提供了EntityCluster接口,用于定义屏幕空间对象,如广告牌、点、标签之间的聚合方式。聚合图标可以自定义,也可以使用Cesium中的PinBuilder类提供的聚合图标。下面介绍如何使用PinBuilder类提供的聚合图标来实现大量数据聚合效果。
(1)实现代码
7_17_点聚合.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_点聚合</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
});
//定义聚和标签样式
var pinBuilder = new Cesium.PinBuilder();
//fromText中三个参数分别为 label内容、颜色、大小
var pin100 = pinBuilder
.fromText("100+", Cesium.Color.RED, 70)
.toDataURL();
var pin70 = pinBuilder
.fromText("70+", Cesium.Color.GOLD, 65)
.toDataURL();
var pin50 = pinBuilder
.fromText("50+", Cesium.Color.BLUE, 60)
.toDataURL();
var pin40 = pinBuilder
.fromText("40+", Cesium.Color.GREEN, 55)
.toDataURL();
var pin30 = pinBuilder
.fromText("30+", Cesium.Color.YELLOW, 50)
.toDataURL();
var pin20 = pinBuilder
.fromText("20+", Cesium.Color.CYAN, 45)
.toDataURL();
var pin10 = pinBuilder
.fromText("10+", Cesium.Color.AQUA, 40)
.toDataURL();
var singleDigitPins = new Array(9);
for (var i = 0; i < singleDigitPins.length; ++i) {
singleDigitPins[i] = pinBuilder
.fromText("" + (i + 2), Cesium.Color.VIOLET, 40)
.toDataURL();
}
//加载点数据
var kmlDataSourcePromise = viewer.dataSources.add(
Cesium.KmlDataSource.load(
"./矢量文件/kml/facilities/facilities.kml",
)
);
kmlDataSourcePromise.then(function (dataSource) {
dataSource.clustering.enabled = true;
dataSource.clustering.minimumClusterSize = 2;
//添加事件监听
dataSource.clustering.clusterEvent.addEventListener(
function (clusteredEntities, cluster) {
cluster.label.show = false;
cluster.billboard.show = true;
cluster.billboard.id = cluster.label.id;
cluster.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
if (clusteredEntities.length >= 100) {
cluster.billboard.image = pin100;
}else if (clusteredEntities.length >= 70) {
cluster.billboard.image = pin70;
}else if (clusteredEntities.length >= 50) {
cluster.billboard.image = pin50;
} else if (clusteredEntities.length >= 40) {
cluster.billboard.image = pin40;
} else if (clusteredEntities.length >= 30) {
cluster.billboard.image = pin30;
} else if (clusteredEntities.length >= 20) {
cluster.billboard.image = pin20;
} else if (clusteredEntities.length >= 10) {
cluster.billboard.image = pin10;
} else {
cluster.billboard.image =
singleDigitPins[clusteredEntities.length - 2];
}
}
);
/* afterAddDataSource(dataSource); */
});
/* var removeListener
function afterAddDataSource(dataSource) {
var pixelRange = 59;
var minimumClusterSize = 2;
var enabled = true;
dataSource.clustering.enabled = enabled;
dataSource.clustering.pixelRange = pixelRange;
dataSource.clustering.minimumClusterSize = minimumClusterSize;
var pinBuilder = new Cesium.PinBuilder();
var pin100 = pinBuilder
.fromText("100+", Cesium.Color.RED, 48)
.toDataURL();
var pin50 = pinBuilder
.fromText("50+", Cesium.Color.RED, 48)
.toDataURL();
var pin40 = pinBuilder
.fromText("40+", Cesium.Color.ORANGE, 48)
.toDataURL();
var pin30 = pinBuilder
.fromText("30+", Cesium.Color.YELLOW, 48)
.toDataURL();
var pin20 = pinBuilder
.fromText("20+", Cesium.Color.GREEN, 48)
.toDataURL();
var pin10 = pinBuilder
.fromText("10+", Cesium.Color.BLUE, 48)
.toDataURL();
var singleDigitPins = new Array(8);
for (var i = 0; i < singleDigitPins.length; ++i) {
singleDigitPins[i] = pinBuilder
.fromText("" + (i + 2), Cesium.Color.VIOLET, 48)
.toDataURL();
}
customStyle();
function customStyle() {
if (Cesium.defined(removeListener)) {
removeListener();
removeListener = undefined;
} else {
removeListener = dataSource.clustering.clusterEvent.addEventListener(
function (clusteredEntities, cluster) {
cluster.label.show = false;
cluster.billboard.show = true;
cluster.billboard.id = cluster.label.id;
cluster.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
if (clusteredEntities.length >= 100) {
cluster.billboard.image = pin100;
} else if (clusteredEntities.length >= 50) {
cluster.billboard.image = pin50;
} else if (clusteredEntities.length >= 40) {
cluster.billboard.image = pin40;
} else if (clusteredEntities.length >= 30) {
cluster.billboard.image = pin30;
} else if (clusteredEntities.length >= 20) {
cluster.billboard.image = pin20;
} else if (clusteredEntities.length >= 10) {
cluster.billboard.image = pin10;
} else {
cluster.billboard.image =
singleDigitPins[clusteredEntities.length - 2];
}
}
);
}
// force a re-cluster with the new styling
var pixelRange = dataSource.clustering.pixelRange;
dataSource.clustering.pixelRange = 0;
}
}
*/
</script>
</body>
</html>
(2)结果显示
18.开启地下模式
Cesium最初是不支持地下场景的,而是在之后的1.70版本中推出了地下模式。这项改进允许用户无缝衔接地从地面模式过渡到地下模式,对于地下管网、排污管道及矿产电缆等地下三维结构的数据可视化更为友好。只需禁止相机对地形的碰撞检测,即可通过鼠标进入地下模式来浏览地下三维场景及建筑内部场景。下面加载一栋地下建筑来演示地下场景及室内场景。
(1)实现代码
7_18_开启地下模式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>工具应用篇_开启地下模式</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
</head>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
<body>
<div id="cesiumContainer"></div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer('cesiumContainer', {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
/* viewer.scene.globe.depthTestAgainstTerrain = true;//开启深度监测 */
var modelEntity = viewer.entities.add({
name: "教室",
position: new Cesium.Cartesian3.fromDegrees(120.14046454, 30.27415039,0),
model: {
uri: './3D格式数据/glTF/别墅1.gltf',
/* uri: './3D格式数据/教室/scene.gltf', */
scale:1
},
});
viewer.zoomTo(modelEntity);
//设置鼠标进去地下
viewer.scene.screenSpaceCameraController.enableCollisionDetection = false;
//设置地球透明
viewer.scene.globe.translucency.enabled = true;
viewer.scene.globe.translucency.frontFaceAlphaByDistance = new Cesium.NearFarScalar(400.0, 0.5, 8000, 0.9);
</script>
</body>
</html>
(2)结果显示
查看地下场景所加载的三维模型效果。
通过鼠标缩放可以调整相机并进入地下模式浏览。当相机一直朝着建筑放大时,即可进入建筑内部浏览。
19.开启等高线
等高线是指地形图上高度相等的相邻各点所连成的闭合曲线。将地面上海拔高度相同的点连成闭合曲线,垂直投影到一个水平面上,并按比例缩绘在图纸上,可以得到等高线。等高线也可以被看作不同海拔高度的水平面与实际地面的交线,所以等高线是闭合曲线。
Cesium中提供了一个Material类,该类内置了多种材质,包括之前我们使用过的条纹、网格等。下面介绍Material类中的等高线材质ElevationContour,只需设置等高距及线宽度、颜色并设置地球的材质外观即可。
(1)实现代码
7_19_等高线分析.html
|
<div id="cesiumContainer"></div>
<div class="toolbar">
<label style="color: white;">等高距</label> <br />
<input type="range" min="0" max="300" step="1" oninput="change()" id="HeightSpa" value="150">
<input type="text" style="width:70px; " id="HeightSpaValue" value="150" onchange="heightSpaValue()"> <br>
<label style="color: white;">线宽</label> <br />
<input type="range" min="0" max="20" step="1" oninput="change()" id="LineWid" value="2">
<input type="text" style="width:70px; " id="LineWidValue" value="2" onchange="lineWidValue()"> <br>
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer('cesiumContainer', {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
//滑动条
function change() {
//拿到等高距滑动条当前值
var HS = Number(HeightSpa.value);
//等高距文本框显示当前值
HeightSpaValue.value = HS;
//拿到线宽滑动条当前值
var LW = Number(LineWid.value);
//线宽文本框显示当前值
LineWidValue.value = LW;
// 使用等高线材质
var material = Cesium.Material.fromType("ElevationContour");
var contourUniforms = {};
// 等高距
contourUniforms.spacing = HS;
// 线宽
contourUniforms.width = LW;
// 颜色
contourUniforms.color = Cesium.Color.RED;
material.uniforms =contourUniforms;
// 设置材质
viewer.scene.globe.material = material;
}
change();
//等高距文本框
function heightSpaValue() {
var HS = Number(HeightSpaValue.value);
HeightSpa.value = HS;
change();
}
//线宽文本框
function lineWidValue() {
var LW = Number(LineWidValue.value);
LineWid.value = LW;
change();
}
//移动相机视角到珠穆朗玛峰
viewer.camera.setView({
destination: new Cesium.Cartesian3(282157.6960889096, 5638892.465594703, 2978736.186473513),
orientation: {
heading: 4.747266966349747,
pitch: -0.2206998858596192,
roll: 6.280340554587955
}
});
</script>
``` **(2)结果显示** ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/71953ddfc0f64c3bad661bc58d11fa56.png =800x)
20.坡度坡向
坡度(slope)表示地表单元陡缓的程度。我们通常把坡面的垂直高度和水平方向的距离的比叫作坡度。坡向的定义为坡面法线在水平面上的投影的方向(也可以通俗地理解为由高及低的方向)。
坡度坡向的渲染同样用到了Material类,其中,SlopeRamp类型为坡度材质,AspectRamp类型为坡向材质。两种类型的材质均需要使用颜色梯度进行渲染,例如,坡度从0到1(即坡度从水平到垂直)变换时,会渲染不同的颜色,最终根据颜色梯度和坡度值生成坡度图。下面介绍具体实现过程。
(1)实现代码
7_20_坡度坡向分析.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>工具应用篇_坡度坡向分析</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
</head>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.toolbar {
position: absolute;
top: 10px;
left: 20px;
}
</style>
<body>
<div id="cesiumContainer"></div>
<div class="toolbar">
<button onclick="analysis('坡度')">坡度分析</button>
<button onclick="analysis('坡向')">坡向分析</button>
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer('cesiumContainer', {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain({
requestVertexNormals: true //可视化坡度,必须开启
})
});
/* const slopeRamp = [0.0, 0.29, 0.5, Math.sqrt(2) / 2, 0.87, 0.91, 1.0]; */
//开启光照
viewer.scene.globe.enableLighting = true;
//定义颜色梯度的数组
const slopeRamp = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6];
const aspectRamp = [0.0, 0.2, 0.4, 0.6, 0.8, 0.9, 1.0];
/* const aspectRamp = [0.0, 0.25, 0.5, 0.75, 1.0]; */
//创建颜色梯度
function getColorRamp(selectedShading) {
const ramp = document.createElement("canvas");
ramp.width = 100;
ramp.height = 1;
const ctx = ramp.getContext("2d");
let values;
if (selectedShading === "slope") {
values = slopeRamp;
} else if (selectedShading === "aspect") {
values = aspectRamp;
}
//创建线性渐变
const grd = ctx.createLinearGradient(0, 0, 100, 0);
//规定渐变对象中的颜色和停止位置
/* grd.addColorStop(values[0], "#000000"); //black
grd.addColorStop(values[1], "#2747E0"); //blue
grd.addColorStop(values[2], "#D33B7D"); //pink
grd.addColorStop(values[3], "#D33038"); //red
grd.addColorStop(values[4], "#FF9742"); //orange
grd.addColorStop(values[5], "#ffd700"); //yellow
grd.addColorStop(values[6], "#ffffff"); //white */
grd.addColorStop(values[0], "#b6d7a8"); //black
grd.addColorStop(values[1], "#a2c4c9"); //blue
grd.addColorStop(values[2], "#a4c2f4"); //pink
grd.addColorStop(values[3], "#6d9eeb"); //red
grd.addColorStop(values[4], "#3c78d8"); //orange
grd.addColorStop(values[5], "#1c4587"); //yellow
grd.addColorStop(values[6], "#20124d"); //blue
/* grd.addColorStop(values[0], "#93afeb"); //black
grd.addColorStop(values[1], "#4a81f7"); //blue
grd.addColorStop(values[2], "#2f69e6"); //pink
grd.addColorStop(values[3], "#1c59dc"); //red
grd.addColorStop(values[4], "#2747E0"); */
//设置填充样式
ctx.fillStyle = grd;
//绘制矩形
ctx.fillRect(0, 0, 100, 1);
return ramp;
}
var shadingUniforms = {};
//渲染地球地形
function analysis(type) {
if (type == "坡度") {
material = Cesium.Material.fromType("SlopeRamp");
shadingUniforms.image = getColorRamp('slope');
material.uniforms = shadingUniforms;
} else {
material = Cesium.Material.fromType("AspectRamp");
shadingUniforms.image = getColorRamp('aspect');
material.uniforms = shadingUniforms;
}
viewer.scene.globe.material = material;
}
//移动相机视角到珠穆朗玛峰
viewer.camera.setView({
destination: new Cesium.Cartesian3(282157.6960889096, 5638892.465594703, 2978736.186473513),
orientation: {
heading: 4.747266966349747,
pitch: -0.2206998858596192,
roll: 6.280340554587955
}
});
</script>
</body>
</html>
(2)结果显示
坡度分析:
坡向分析:
21.填挖方量计算
填挖方量是指在路基表面高于原地面或低于原地面时,从原地面填筑或开挖至路基表面部分的体积,通常应用于道路修建等工程场景。例如,在某个区域需要修建一条路,但是该区域的地势崎岖不平,就需要先确定一个道路基准面,然后根据道路基准面高度将低于道路基准面的部分填埋,将高于道路基准面的部分挖平,最后进行道路修建。
在Cesium中,实现填挖方量计算的方法实际上就是微积分,也就是先将一个大区域剖分成多个小区域,然后分别计算小区域的填挖方量,最后的总和就是整个大区域的填挖方量。这种计算方法得到的值只能无限接近实际值,在一些情况下可以近似代替实际值。下面介绍具体的实现。
(1)实现代码
7_21_填挖方量计算.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<title>工具应用篇_填挖方分析</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #000000;
}
.toolbar {
position: absolute;
width: 260px;
height: 120px;
top: 10px;
left: 20px;
background-color: rgba(0, 0, 0, 0.6);
color: white;
font-size: 16px;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<div class="toolbar">
<label>填挖方计算</label> <br />
<label>挖方:</label>
<label id="excavation"></label> <br />
<label>填埋:</label>
<label id="bury"></label> <br />
<label>开挖高度</label>
<input type="text" style="width:70px; " id="excavateHeight" value="30"><br />
<label>填埋高度</label>
<input type="text" style="width:70px; " id="buryHeight" value="40">
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var terrainProvider = Cesium.createWorldTerrain();
var viewer = new Cesium.Viewer('cesiumContainer', {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: terrainProvider,
});
//开启深度检测
viewer.scene.globe.depthTestAgainstTerrain = true;
/* var excavateHeight = 30; // 开挖高度
var buryHeight = 40; // 填埋高度 */
//填挖方量计算
function calculation(lon_star, lon_stop, lat_star, lat_stop) {
/* 计算 开挖/填埋 的 开挖量/填方量 的 核心思想就是微积分 */
// 设置剖分最小步长0.0001
var mimStepSize = 0.0001;
// 存储所有的剖分矩形
var subRectangles = [];
// 将整个区域剖分成多个小区域
for (var i = lon_star; i <= lon_stop; i = i + mimStepSize) {
for (var j = lat_star; j <= lat_stop; j = j + mimStepSize) {
var subRectangle = new Cesium.Rectangle(
Cesium.Math.toRadians(i),
Cesium.Math.toRadians(j),
Cesium.Math.toRadians(i + mimStepSize),
Cesium.Math.toRadians(j + mimStepSize)
);
subRectangles.push(subRectangle);
}
}
// 计算每个矩形的中心点作为计算该矩形高度的位置
var subRectanglesCenterPoints = [];
subRectangles.forEach(subRectangle => {
var centerPoint = Cesium.Cartographic.fromRadians((subRectangle.west + subRectangle.east) / 2, (subRectangle
.north +
subRectangle.south) / 2);
subRectanglesCenterPoints.push(centerPoint);
});
// 计算每个中心点的地形高度
var promise = Cesium.sampleTerrainMostDetailed(terrainProvider, subRectanglesCenterPoints);
Cesium.when(promise, function (updatedPositions) {
// 所有高度
var heights = [];
updatedPositions.forEach(point => {
heights.push(point.height);
});
// 开始计算土方
var excavateVolumes = 0; // 挖方
var buryVolumes = 0; // 填埋
//填挖方基准高度
var excavateHeight = document.getElementById("excavateHeight").value;
var buryHeight = document.getElementById("buryHeight").value;
// 1.计算每个矩形的长、宽
for (var i = 0; i < subRectangles.length; i++) {
var subRectangle = subRectangles[i];
var leftBottom = Cesium.Cartesian3.fromRadians(subRectangle.west, subRectangle.south);
var leftTop = Cesium.Cartesian3.fromRadians(subRectangle.west, subRectangle.north);
var rightBottom = Cesium.Cartesian3.fromRadians(subRectangle.east, subRectangle.south);
var height = Cesium.Cartesian3.distance(leftBottom, leftTop); // 宽
var width = Cesium.Cartesian3.distance(leftBottom, rightBottom); // 长
// 挖方
if (heights[i] > excavateHeight) { // 如果地形高度大于开挖高度才需要开挖
var excavateVolume = width * height * (heights[i] - excavateHeight);
excavateVolumes += excavateVolume;
}
// 填埋
if (heights[i] < buryHeight) { // 如果地形高度小于填埋高度才需要填埋
var buryVolume = width * height * (buryHeight - heights[i]);
buryVolumes += buryVolume;
}
}
//显示结果
document.getElementById('excavation').innerHTML = excavateVolumes.toFixed(4) + "立方米(m³)";
document.getElementById('bury').innerHTML = buryVolumes.toFixed(4) + "立方米(m³)";
});
}
//绘制填挖体
function drawResult(minHeight, maxHeight, minLon, maxLon, minLat, maxLat) {
var entity = viewer.entities.add({
name: '填挖体',
show: true,
rectangle: {
coordinates: Cesium.Rectangle.fromDegrees(
minLon, minLat,
maxLon, maxLat
),
material: Cesium.Color.BLUE.withAlpha(0.5),
height: minHeight,
extrudedHeight: maxHeight,
}
})
}
//绘制实体
var points = []; //存储填挖方区域顶点
var activeShapePoints = []; //动态点数组
var activeShape; //动态图形
var floatingPoint; //第一个点 判断是否开始获取鼠标移动结束位置并添加至activeShapePoints
//绘制点
function drawPoint(position) {
var pointGeometry = viewer.entities.add({
name: "点几何对象",
position: position,
point: {
color: Cesium.Color.SKYBLUE,
pixelSize: 6,
outlineColor: Cesium.Color.YELLOW,
outlineWidth: 2,
//disableDepthTestDistance: Number.POSITIVE_INFINITY
}
});
return pointGeometry;
};
//绘制图形
function drawShape(positionData) {
var shape;
//当positionData为数组时绘制最终图,如果为function则绘制动态图
var arr = typeof positionData.getValue === 'function' ? positionData.getValue(0) : positionData;
shape = viewer.entities.add({
name: '矩形',
rectangle: {
coordinates: new Cesium.CallbackProperty(function () {
var obj = Cesium.Rectangle.fromCartesianArray(arr);
return obj;
}, false),
material: Cesium.Color.RED.withAlpha(0.5)
}
});
return shape;
}
var handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
//鼠标左键
handler.setInputAction(function (event) {
// 用 `viewer.scene.pickPosition` 代替 `viewer.camera.pickEllipsoid`
// 当鼠标在地形上移动时可以得到正确的点.
var earthPosition = viewer.scene.pickPosition(event.position);
var cartographic = Cesium.Cartographic.fromCartesian(earthPosition);
var lon = Cesium.Math.toDegrees(cartographic.longitude);
var lat = Cesium.Math.toDegrees(cartographic.latitude);
var pointObj = {
经度: lon,
纬度: lat
}
points.push(pointObj);
// `如果鼠标不在地球上 则earthPosition是未定义
//第一次点击时,通过CallbackProperty绘制动态图
if (activeShapePoints.length === 0) {
floatingPoint = drawPoint(earthPosition);
activeShapePoints.push(earthPosition);
//动态点通过CallbackProperty实时更新渲染
var dynamicPositions = new Cesium.CallbackProperty(function () {
return activeShapePoints;
}, false);
activeShape = drawShape(dynamicPositions);//绘制动态图
}
//添加当前点进activeShapePoints,实时渲染动态图
activeShapePoints.push(earthPosition);
drawPoint(earthPosition);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
//鼠标移动
handler.setInputAction(function (event) {
if (Cesium.defined(floatingPoint)) {
var newPosition = viewer.scene.pickPosition(event.endPosition); //获取鼠标移动最终位置
if (Cesium.defined(newPosition)) {
//动态去除数组中最后一个点,再添加最新一个点,保证只保留鼠标位置点
activeShapePoints.pop();
activeShapePoints.push(newPosition);
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
//鼠标右键
handler.setInputAction(function (event) {
activeShapePoints.pop();//去除最后一个动态点
if (activeShapePoints.length) {
drawShape(activeShapePoints);//绘制最终图
}
viewer.entities.remove(floatingPoint);//移除第一个点(重复了)
viewer.entities.remove(activeShape);//去除动态图形
floatingPoint = undefined;
activeShape = undefined;
activeShapePoints = [];
//比较大小
var minLon = Math.min(...[points[0].经度, points[1].经度]),
maxLon = Math.max(...[points[0].经度, points[1].经度]),
minLat = Math.min(...[points[0].纬度, points[1].纬度]),
maxLat = Math.max(...[points[0].纬度, points[1].纬度]);
calculation(minLon, maxLon, minLat, maxLat);
viewer.entities.removeAll();
var excavateHeight = document.getElementById("excavateHeight").value;
var buryHeight = document.getElementById("buryHeight").value;
drawResult(excavateHeight, buryHeight, minLon, maxLon, minLat, maxLat);
//清空填挖点
points = [];
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(114.40372, 30.52252, 2000)
});
</script>
</body>
</html>
(2)结果显示
左键绘制开挖矩形区域,右键进行填挖方计算。
22.动态缓冲区分析
在Cesium中做缓冲区分析其实与在二维地图中做缓存区分析是一样的道理,缓冲后需要将二维坐标转到三维坐标,然后在场景中显示结果。
(1)实现代码
7_22_动态缓冲区分析.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_动态缓冲区分析</title>
<script src="./Build/CesiumUnminified/Cesium.js"></script>
<link rel="stylesheet" href="./Build/CesiumUnminified/Widgets/widgets.css">
<script src="https://unpkg.com/@turf/turf/turf.min.js"></script>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
terrainProvider: Cesium.createWorldTerrain(),
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
});
viewer.scene.globe.depthTestAgainstTerrain = false;
// 添加点函数,需要传入点坐标
function addPoint(point) {
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(point[0], point[1]),
point: {
pixelSize: 10,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
color: Cesium.Color.YELLOW,
outlineWidth: 3,
outlineColor: Cesium.Color.YELLOW.withAlpha(0.4),
},
});
}
//格式转换 将点数据拆成一个大数组
function pointsToDegreesArray(points) {
let degreesArray = [];
points.map(item => {
degreesArray.push(item[0]);
degreesArray.push(item[1]);
});
return degreesArray;
}
// 初始化点缓冲
function initPointBuffer(radius) {
pointF = turf.point(point);
let buffered = turf.buffer(pointF, radius, { units: "meters" });
let coordinates = buffered.geometry.coordinates;
let points = coordinates[0];
//因为turf.buffer计算得到的数组中元素是坐标点对 所以需要拆分组合为一个大数组
var degreesArray = pointsToDegreesArray(points);
return degreesArray;
}
//中心点
var point = [114.40086, 30.51888];
//定义缓冲区半径
var radius = 10;
//绘制缓冲区中心点
addPoint(point);
//绘制缓冲区面
addBufferPolyogn();
// 添加缓冲面
function addBufferPolyogn() {
viewer.entities.add({
polygon: {
/* hierarchy: new Cesium.PolygonHierarchy(positions), */
//缓冲区面的边界坐标使用CallbackProperty动态回调
hierarchy : new Cesium.CallbackProperty(updataRadius, false),
//材质
material: Cesium.Color.RED.withAlpha(0.6),
//贴地
classificationType: Cesium.ClassificationType.BOTH,
},
});
}
//CallbackProperty回调函数
function updataRadius(){
//当半径小于60
if(radius <60)
{
//半径每次+1
radius += 1;
//使用初始化点缓冲函数计算缓冲区边界点坐标 结果是数组
var pointArr = initPointBuffer(radius);
//将缓冲区边界点坐标转换成Cartesian3类型
var pointCar3 = Cesium.Cartesian3.fromDegreesArray(pointArr)
//将Cartesian3类型的缓冲区边界坐标转换成 polygon的hierarchy属性所要求的PolygonHierarchy类型
var points = new Cesium.PolygonHierarchy(pointCar3)
//计算结果返回 作为缓冲区面的边界坐标
return points;
}
else
{
radius = 10;
var pointArr = initPointBuffer(radius);
//将缓冲区边界点坐标转换成Cartesian3类型
var pointCar3 = Cesium.Cartesian3.fromDegreesArray(pointArr)
//将Cartesian3类型的缓冲区边界坐标转换成 polygon的hierarchy属性所要求的PolygonHierarchy类型
var points = new Cesium.PolygonHierarchy(pointCar3)
//计算结果返回 作为缓冲区面的边界坐标
return points;
}
}
//调整相机视角
viewer.scene.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(114.40086, 30.51888, 2000),
//destination: Cesium.Cartesian3.fromDegrees(-74.0012351579127, 40.715093849131, 1000),
});
</script>
</body>
</html>
(2)结果显示
23.加载室内场景
(1)实现代码
7_23_加载室内场景.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>工具应用篇_加载室内场景</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
</head>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
<body>
<div id="cesiumContainer"></div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer('cesiumContainer', {
geocoder: true, //是否显示地名查找工具
homeButton: true, //是否显示首页位置工具
sceneModePicker: true, //是否显示视角模式切换工具
baseLayerPicker: true, //是否显示默认图层选择工具
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
});
const modelEntity = viewer.entities.add({
name: "glb模型",
position: new Cesium.Cartesian3.fromDegrees(120.14046454, 30.27415039),
model: {
uri: './3D格式数据/教室/scene.gltf',
/* minimumPixelSize: 256,
maxumunScale: 20000, */
},
});
viewer.zoomTo(modelEntity);
/* // 聚焦模型
viewer.trackedEntity = modelEntity; */
let lastWhellCameraPosition = undefined;
let lastWhellCameraPositionTimes = 0;
let currentCameraPosition = viewer.camera.position;
let ellipsoid = viewer.scene.globe.ellipsoid;
viewer.screenSpaceEventHandler.setInputAction(function onMouseWheel(e) {
if (e > 0 && lastWhellCameraPosition
&& Math.abs(currentCameraPosition.x - lastWhellCameraPosition.x) < 0.001
&& Math.abs(currentCameraPosition.y - lastWhellCameraPosition.y) < 0.001
&& Math.abs(currentCameraPosition.z - lastWhellCameraPosition.z) < 0.001) {
if (lastWhellCameraPositionTimes > 1) {
console.log(e);
let cameraHeight = ellipsoid.cartesianToCartographic(currentCameraPosition).height;
viewer.camera.moveForward(cameraHeight / 50.0);
} else {
lastWhellCameraPositionTimes++;
}
} else {
lastWhellCameraPositionTimes = 0;
}
lastWhellCameraPosition = currentCameraPosition.clone();
}, Cesium.ScreenSpaceEventType.WHEEL);
//设置鼠标进去地下
//viewer.scene.screenSpaceCameraController.enableCollisionDetection = false;
</script>
</body>
</html>
(2)结果显示
24.剖面分析
(1)实现代码
7_24_剖面分析.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工具应用篇_剖面分析</title>
<script src="./Build/Cesium/Cesium.js"></script>
<link rel="stylesheet" href="./Build/Cesium/Widgets/widgets.css">
<!-- 引入jQuery -->
<script src="./Build/js/jquery.min.js"></script>
<!-- 引入echarts -->
<script src="./Build/js/echarts.min.js"></script>
<style>
html,
body,
#cesiumContainer {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
.toolbar {
position: absolute;
top: 10px;
left: 10px;
width: 60%;
height: 30%;
background-color: rgb(0, 0, 0, 0);
}
</style>
</head>
<body>
<div id="cesiumContainer">
</div>
<div id="proAnl" class="toolbar" style="display: none;">
</div>
<script>
Cesium.Ion.defaultAccessToken = '你的token';
var viewer = new Cesium.Viewer("cesiumContainer", {
animation: false, //是否显示动画工具
timeline: false, //是否显示时间轴工具
fullscreenButton: false, //是否显示全屏按钮工具
terrainProvider: Cesium.createWorldTerrain()
});
//开启深度检测后 会有高程遮挡效果
/* viewer.scene.globe.depthTestAgainstTerrain = true;
var tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: './倾斜摄影/大雁塔3DTiles/tileset.json'
})
);
viewer.zoomTo(tileset); */
//绘制实体
var star, end;
var p = []; //存储经纬度
var positions = []; //用来存储要要进行插值的点
var activeShapePoints = []; //动态点数组
var activeShape; //动态图形
var floatingPoint; //第一个点 判断是否开始获取鼠标移动结束位置并添加至activeShapePoints
//绘制点与标签
function drawPoint(position,) {
var pointGeometry = viewer.entities.add({
name: "点几何对象",
position: position,
point: {
color: Cesium.Color.SKYBLUE,
pixelSize: 6,
outlineColor: Cesium.Color.YELLOW,
outlineWidth: 2,
//disableDepthTestDistance: 1000 //当距离在1000以下不被高程遮挡
},
});
return pointGeometry;
};
//绘制图形
function drawShape(positionData) {
var shape;
shape = viewer.entities.add({
polyline: {
positions: positionData,
width: 5.0,
material: new Cesium.PolylineGlowMaterialProperty({
color: Cesium.Color.GOLD,
}),
clampToGround: true
}
});
return shape;
}
var handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
//鼠标左键
handler.setInputAction(function (event) {
var earthPosition = viewer.scene.pickPosition(event.position);
var cartographic = Cesium.Cartographic.fromCartesian(earthPosition);
var lng = Cesium.Math.toDegrees(cartographic.longitude);
var lat = Cesium.Math.toDegrees(cartographic.latitude);
positions.push(Cesium.Cartesian3.fromDegrees(lng, lat));//记录要进行插值的点
// `如果鼠标不在地球上 则earthPosition是未定义
if (Cesium.defined(earthPosition)) {
//第一次点击时,通过CallbackProperty绘制动态图
if (activeShapePoints.length === 0) {
//floatingPoint = drawPoint(earthPosition,null);
activeShapePoints.push(earthPosition);
//动态点通过CallbackProperty实时更新渲染
var dynamicPositions = new Cesium.CallbackProperty(function () {
return activeShapePoints;
}, false);
activeShape = drawShape(dynamicPositions);//绘制动态图
}
//添加当前点进activeShapePoints,实时渲染动态图
activeShapePoints.push(earthPosition);
floatingPoint = drawPoint(earthPosition);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
//鼠标移动
handler.setInputAction(function (event) {
if (Cesium.defined(floatingPoint)) {
var newPosition = viewer.scene.pickPosition(event.endPosition); //获取鼠标移动最终位置
if (Cesium.defined(newPosition)) {
//动态去除数组中最后一个点,再添加最新一个点,保证只保留鼠标位置点
activeShapePoints.pop();
activeShapePoints.push(newPosition);
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
//鼠标右键
handler.setInputAction(function (event) {
star = positions[0];
end = positions[positions.length - 1];
interpolation(star, end);
setEchartsData(p);
activeShapePoints.pop();//去除最后一个动态点
if (activeShapePoints.length) {
drawShape(activeShapePoints);//绘制最终图
}
//viewer.entities.remove(floatingPoint);//移除第一个点(重复了)
viewer.entities.remove(activeShape);//去除动态图形
floatingPoint = undefined;
activeShape = undefined;
activeShapePoints = [];
//清空原来的点数组
p = [];
positions = [];
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
//获取指定位置高度
function getHeight(position) {
height = viewer.scene.globe.getHeight(position);
return height;
}
//样条插值
function interpolation(point1, point2) {
var spline = new Cesium.LinearSpline({
times: [0.0, 1],
points: [point1, point2]
});
for (var i = 0; i <= 100; i++) {
var cartesian3 = spline.evaluate(i / 100);
var cartographic = Cesium.Cartographic.fromCartesian(cartesian3);
var lon = Cesium.Math.toDegrees(cartographic.longitude);
var lat = Cesium.Math.toDegrees(cartographic.latitude);
var height = getHeight(cartographic);
var position = {
经度: lon,
纬度: lat,
高度: height
}
p.push(position);
}
}
//设置Echart数据
function setEchartsData(p) {
$('#proAnl').show();
var myChart = echarts.init(document.getElementById("proAnl"), 'dark');
var heightData = [];
var position = [];
for (var i = 0; i < p.length; i++) {
heightData.push(p[i].高度.toFixed(4));
var pos = p[i].经度.toFixed(4) + "," + p[i].纬度.toFixed(4);
position.push(pos);
}
var option = {
grid: {
top:10,
left: 0,
right: 0,
bottom: 5,
containLabel: !0,
},
tooltip: {
trigger: 'axis',
/* axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
} */
formatter: function (e) {
var htmldiv = '';
return (
htmldiv +=
'经纬度:' + e[0].axisValue + '<br/>' +
'高程值:' + e[0].value + '米' + '<br/>'
)
},
},
xAxis:
{
name: '距离',
type: 'category',
boundaryGap: !1,
axisLabel: {
show: !1,
},
data: position,
},
yAxis:
{
type: 'value',
axisLabel: {
rotate: 0,
formatter: '{value} 米',
},
},
series: [
{
name: '高程值',
type: 'line',
smooth: !0,
symbol: 'none',
sampling: 'average',
itemStyle: {
normal: {
color: 'rgb(255, 70, 131)',
},
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(255, 158, 68)',
},
{
offset: 1,
color: 'rgb(255, 70, 131)',
},
]),
},
},
data: heightData,
},
],
}
option && myChart.setOption(option);
}
</script>
</body>
</html>
(2)结果显示
左键画线,右键进行剖面分析。
参考资料:
[1] 郭明强. 《WebGIS之Cesium三维软件开发》; 2023-04-01 [accessed 2024-01-26].
[2] xt3d. Cesium缓冲区分析; 2020-11-21 [accessed 2024-01-27].
[3] evomap. cesium初探之加载三维模型; 2016-12-27 [accessed 2024-01-27].
[4] hpugisers. Cesium(五)剖面分析(仿火星科技); 2019-06-13 [accessed 2024-01-27].