CesiumJS+SuperMap3D.js混用实现可视域分析 S3M图层加载 裁剪区域绘制
版本简介:
cesium:1.99;Supermap3D:SuperMap iClient JavaScript 11i(2023);
官方下载文档链家:SuperMap技术资源中心|为您提供全面的在线技术服务
示例参考:support.supermap.com.cn:8090/webgl/Cesium/examples/webgl/examples.html#analysis
support.supermap.com.cn:8090/webgl/examples/webgl/examples.html
Cesium:场景初始化、渲染、Bing地图、S3M图层加载。
SuperMap3D:可视域分析、S3M图层加载、裁剪区域绘制、Knockout绑定等功能。
两者结合:Cesium 提供基础渲染和事件处理,SuperMap3D 提供高级的功能实现。
1. Cesium 部分
场景初始化与配置
Cesium.Ion.defaultAccessToken = '...';
var viewer = new Cesium.Viewer('Container', {
selectionIndicator: false,
infoBox: false,
terrainProvider: Cesium.createWorldTerrain()
});
viewer.resolutionScale = window.devicePixelRatio;
- 这段代码是使用 Cesium 进行场景渲染的部分。
Cesium.Ion.defaultAccessToken
用于访问 Cesium Ion 服务,viewer
是 Cesium Viewer 的实例,它用于创建一个可视化容器,其中指定了Container
元素来渲染场景。createWorldTerrain()
设置了全球地形服务,resolutionScale
提高了分辨率,以适应高DPI屏幕。
添加Bing地图图层
viewer.imageryLayers.addImageryProvider(new Cesium.BingMapsImageryProvider({
url: 'https://dev.virtualearth.net',
mapStyle: Cesium.BingMapsStyle.AERIAL,
key: URL_CONFIG.BING_MAP_KEY
}));
- 这里是Cesium的图层管理部分,使用
BingMapsImageryProvider
添加了 Bing 地图的航拍图层。Cesium 的图层管理方式主要通过imageryLayers.addImageryProvider()
实现。
事件处理与视口操作
var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
handler.setInputAction(function (e) {
//...
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
- 这一部分代码处理的是 Cesium 中的鼠标事件,如屏幕空间事件(
ScreenSpaceEventHandler
) 和鼠标移动事件 (MOUSE_MOVE
)。这是 Cesium 的交互控制,通过捕捉鼠标操作来对场景进行更新。
2. SuperMap3D 部分
可视域分析与裁剪
var viewshed3D = new SuperMap3D.ViewShed3D(scene);
- 这里是 SuperMap3D 提供的可视域分析功能的初始化。
SuperMap3D.ViewShed3D
是用于在 3D 场景中执行可视域分析的类,用于计算某个点是否可见。
加载S3M图层
var promise = scene.open('http://www.supermapol.com/realspace/services/3D-CBD-2/rest/realspace');
SuperMap3D.when(promise, function (layers) {
// 设置相机位置等操作
}, function (e) {
// 错误处理
});
- 这里通过
scene.open()
加载了 SuperMap3D 的 S3M 图层,这个图层是 SuperMap 提供的特定格式,通常用于大规模3D场景的渲染和展示。
裁剪区域操作
var handlerPolygon = new SuperMap3D.DrawHandler(viewer, SuperMap3D.DrawMode.Polygon, 0);
handlerPolygon.movingEvt.addEventListener(function (windowPosition) {
if (handlerPolygon.isDrawing) {
tooltip.showAt(windowPosition, '<p>绘制相交区域(右键结束绘制)</p>'); // 绘制提示
}
});
handlerPolygon.drawEvt.addEventListener(function (result) {
var array = [].concat(result.object.positions);
var positions = [];
for (var i = 0, len = array.length; i < len; i++) {
var cartographic = SuperMap3D.Cartographic.fromCartesian(array[i]);
var longitude = SuperMap3D.Math.toDegrees(cartographic.longitude);
var latitude = SuperMap3D.Math.toDegrees(cartographic.latitude);
var h = cartographic.height;
positions.push(longitude, latitude, h);
}
viewshed3D.addClipRegion({name: 'test', position: positions}); // 添加裁剪区域
});
- 这是 SuperMap3D 的裁剪操作部分。通过
SuperMap3D.DrawHandler
绘制多边形区域,viewshed3D.addClipRegion()
函数则用于将绘制的区域应用到可视域分析对象中,进行裁剪。 DrawHandler
用于激活绘制多边形裁剪面的功能。movingEvt
事件在绘制过程中显示提示信息。drawEvt
事件在绘制完成时获取多边形的坐标,并将其设置为可视域的裁剪区域。
Knockout 绑定
SuperMap3D.knockout.track(viewModel);
SuperMap3D.knockout.applyBindings(viewModel, toolbar);
- 这段代码是使用 SuperMap3D 提供的 Knockout 绑定功能,目的是将数据模型
viewModel
与 UI 绑定。这个功能允许动态更新可视域分析的参数。
3. Cesium 和 SuperMap3D 的结合
Cesium 在整个代码中主要负责场景渲染、基础交互和图层的管理,如初始化 Viewer、处理鼠标事件、添加图层等。而 SuperMap3D 负责具体的功能实现,比如可视域分析、S3M 图层加载、裁剪操作等。
两者通过 viewer.scene
来共享场景,SuperMap3D 的功能在 Cesium 的场景之上实现。例如:
var viewshed3D = new SuperMap3D.ViewShed3D(scene);
—— 这里的scene
是 Cesium 场景,而viewshed3D
是 SuperMap3D 的可视域对象,它依赖于 Cesium 的场景。- 加载S3M图层和添加裁剪区域也是在 Cesium 场景中进行操作,二者配合使用
4.完整代码展示
<!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>
<link href="../../public/SuperMap3D/Widgets/widgets.css" rel="stylesheet">
<link rel="stylesheet" href="./css/font-awesome.min.css">
<link href="../css/pretty.css" rel="stylesheet">
<link href="../css/style.css" rel="stylesheet">
<link href="../css/viewshed3D.css" rel="stylesheet">
<script type="text/javascript" src="../js/jquery.min.js"></script>
<script src="../js/slider.js"></script>
<script src="../js/config.js"></script>
<script src="../js/tooltip.js"></script>
<script src="../js/spectrum.js"></script>
<script type="text/javascript" src="../../public/SuperMap3D/SuperMap3D.js"></script>
<script src="../../../Cesium-1.99/Build/Cesium/Cesium.js"></script>
<link href="../../../Cesium-1.99/Build/Cesium/Widgets/widgets.css">
</head>
<body>
<div id="Container"></div>
<div id='loadingbar' class="spinner">
<div class="spinner-container container1">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container2">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
<div class="spinner-container container3">
<div class="circle1"></div>
<div class="circle2"></div>
<div class="circle3"></div>
<div class="circle4"></div>
</div>
</div>
<div id="toolbar" class="param-container tool-bar">
<button type="button" id="chooseView" class="button black">绘制可视域</button>
<button type="button" id="cilpRegion" class="button black">绘制裁剪面</button>
<button type="button" id="clear" class="button black">清除</button>
<div class="param-item">
<b>裁剪模式:</b>
<select id="clip-mode" class="supermap3d-button">
<option value="keep-inside">保留区域内</option>
<option value="keep-outside">保留区域外</option>
</select>
</div>
</div>
<div id="wrapper" style="display:none">
<div id="login" class="animate form">
<span class="close" aria-hidden="true">×</span>
<form>
<h1>属性编辑</h1>
<p>
<div>
<label>方向(度)</label>
<input type="range" id="direction" min="0" max="360" step="1.0" title="方向"
data-bind="value: direction, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: direction">
</div>
<div>
<label>翻转(度)</label>
<input type="range" id="pitch" min="-90" max="90" step="1.0" value="1" title="翻转"
data-bind="value: pitch, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: pitch">
</div>
<div>
<label>距离(米)</label>
<input type="range" id="distance" min="1" max="500" step="1.0" value="1" title="距离"
data-bind="value: distance, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: distance">
</div>
<div>
<label>水平视场角(度)</label>
<input type="range" id="horizonalFov" min="1" max="120" step="1" value="1" title="水平视场角"
data-bind="value: horizontalFov, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: horizontalFov">
</div>
<div>
<label>垂直视场角(度)</label>
<input type="range" id="verticalFov" min="1" max="90" step="1.0" value="1" title="垂直视场角"
data-bind="value: verticalFov, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: verticalFov">
</div>
</p>
<p>
<div class="square square-left">
<label>可见区域颜色</label><input class="colorPicker" data-bind="value: visibleAreaColor,valueUpdate: 'input'"
id="colorPicker1"/>
</div>
<div class="square square-right">
<label>不可见区域颜色</label><input class="colorPicker"
data-bind="value: invisibleAreaColor,valueUpdate: 'input'"
id="colorPicker2"/>
</div>
</p><br/><br/>
<p><label>本例中观察者附加高度:1.8 米</label></p>
</form>
</div>
</div>
<script type="text/javascript">
function onload(Cesium) {
Cesium.Ion.defaultAccessToken = 'your token'
var viewer = new Cesium.Viewer('Container', {
selectionIndicator: false,
infoBox: false,
terrainProvider: Cesium.createWorldTerrain()
});
viewer.resolutionScale = window.devicePixelRatio;
viewer.scenePromise.then(function(scene){
init(Cesium, scene, viewer);
});
}
function init(Cesium, scene, viewer) {
var labelImagery = new Cesium.TiandituImageryProvider({
mapStyle: Cesium.TiandituMapsStyle.CIA_C,//天地图全球中文注记服务
token: 'your token' //由天地图官网申请的密钥
});
var scene = viewer.scene;
scene.lightSource.ambientLightColor = new Cesium.Color(0.65, 0.65, 0.65, 1);
var viewPosition;
if (!scene.pickPositionSupported) {
alert('不支持深度纹理,可视域分析功能无法使用(无法添加观测)!');
}
// 先将此标记置为true,不激活鼠标移动事件中对可视域分析对象的操作
scene.viewFlag = true;
var pointHandler = new Cesium.DrawHandler(viewer, Cesium.DrawMode.Point);
// 创建可视域分析对象
var viewshed3D = new SuperMap3D.ViewShed3D(scene);
var colorStr1 = viewshed3D.visibleAreaColor.toCssColorString();
var colorStr2 = viewshed3D.hiddenAreaColor.toCssColorString();
var widget = viewer.Widget;
try {
//添加S3M图层
var promise = scene.open('http://www.supermapol.com/realspace/services/3D-CBD-2/rest/realspace');
SuperMap3D.when(promise, function (layers) {
// 图层加载完成,设置相机位置
scene.camera.setView({
destination: SuperMap3D.Cartesian3.fromDegrees(116.44366835831197, 39.907137217792666, 48.237028126511696),
orientation: {
heading: 1.6310555040487564,
pitch: 0.0017367269669030794,
roll: 3.007372129104624e-12
}
});
for (var i = 0; i < layers.length; i++) {
layers[i].selectEnabled = false;
}
}, function (e) {
if (widget._showRenderLoopErrors) {
var title = '加载SCP失败,请检查网络连接状态或者url地址是否正确?';
widget.showErrorPanel(title, undefined, e);
}
});
} catch (e) {
if (widget._showRenderLoopErrors) {
var title = '渲染时发生错误,已停止渲染。';
widget.showErrorPanel(title, undefined, e);
}
}
var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
// 鼠标移动时间回调
handler.setInputAction(function (e) {
// 若此标记为false,则激活对可视域分析对象的操作
if (!scene.viewFlag) {
//获取鼠标屏幕坐标,并将其转化成笛卡尔坐标
var windowPosition = e.endPosition;
scene.pickPositionAsync(windowPosition).then((last)=>{
//计算该点与视口位置点坐标的距离
var distance = SuperMap3D.Cartesian3.distance(viewPosition, last);
if (distance > 0) {
// 将鼠标当前点坐标转化成经纬度
var cartographic = Cesium.Cartographic.fromCartesian(last);
var longitude = Cesium.Math.toDegrees(cartographic.longitude);
var latitude = Cesium.Math.toDegrees(cartographic.latitude);
var height = cartographic.height;
// 通过该点设置可视域分析对象的距离及方向
viewshed3D.setDistDirByPoint([longitude, latitude, height]);
}
})
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
handler.setInputAction(function (e) {
//鼠标右键事件回调,不再执行鼠标移动事件中对可视域的操作
scene.viewFlag = true;
$("#wrapper").show();
viewModel.direction = viewshed3D.direction;
viewModel.pitch = viewshed3D.pitch;
viewModel.distance = viewshed3D.distance;
viewModel.horizontalFov = viewshed3D.horizontalFov;
viewModel.verticalFov = viewshed3D.verticalFov;
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
var tooltip = createTooltip(document.body);
//绘制裁剪面
var handlerPolygon = new SuperMap3D.DrawHandler(viewer, SuperMap3D.DrawMode.Polygon, 0);
handlerPolygon.activeEvt.addEventListener(function (isActive) {
if (isActive == true) {
viewer.enableCursorStyle = false;
viewer._element.style.cursor = '';
$('body').removeClass('drawCur').addClass('drawCur');
} else {
viewer.enableCursorStyle = true;
$('body').removeClass('drawCur');
}
});
handlerPolygon.movingEvt.addEventListener(function (windowPosition) {
if (handlerPolygon.isDrawing) {
tooltip.showAt(windowPosition, '<p>绘制相交区域(右键结束绘制)</p>');
}
});
handlerPolygon.drawEvt.addEventListener(function (result) {
tooltip.setVisible(false);
var array = [].concat(result.object.positions);
var positions = [];
for (var i = 0, len = array.length; i < len; i++) {
var cartographic = SuperMap3D.Cartographic.fromCartesian(array[i]);
var longitude = SuperMap3D.Math.toDegrees(cartographic.longitude);
var latitude = SuperMap3D.Math.toDegrees(cartographic.latitude);
var h = cartographic.height;
if (positions.indexOf(longitude) == -1 && positions.indexOf(latitude) == -1) {
positions.push(longitude);
positions.push(latitude);
positions.push(h);
}
}
handlerPolygon.polygon.show = false;
handlerPolygon.polyline.show = false;
viewshed3D.addClipRegion({name: 'test', position: positions});
handlerPolygon.deactivate();
});
pointHandler.drawEvt.addEventListener(function (result) {
// var point = result.object;
var position = result.object.position;
viewPosition = position;
// 将获取的点的位置转化成经纬度
var cartographic = Cesium.Cartographic.fromCartesian(position);
var longitude = Cesium.Math.toDegrees(cartographic.longitude);
var latitude = Cesium.Math.toDegrees(cartographic.latitude);
var height = cartographic.height + 1.8;
// point.position = SuperMap3D.Cartesian3.fromDegrees(longitude, latitude, height);
if (scene.viewFlag) {
// 设置视口位置
viewshed3D.viewPosition = [longitude, latitude, height];
viewshed3D.build();
// 将标记置为false以激活鼠标移动回调里面的设置可视域操作
scene.viewFlag = false;
}
});
var viewModel = {
direction: 1.0,
pitch: 1.0,
distance: 1.0,
verticalFov: 1.0,
horizontalFov: 1.0,
visibleAreaColor: '#ffffffff',
invisibleAreaColor: '#ffffffff'
};
SuperMap3D.knockout.track(viewModel);
var toolbar = document.getElementById('wrapper');
SuperMap3D.knockout.applyBindings(viewModel, toolbar);
SuperMap3D.knockout.getObservable(viewModel, 'direction').subscribe(
function (newValue) {
if(viewshed3D.direction !== parseFloat(newValue)){
viewshed3D.direction = parseFloat(newValue);
viewshed3D.removeClipRegion('test');
}
}
);
SuperMap3D.knockout.getObservable(viewModel, 'pitch').subscribe(
function (newValue) {
if(viewshed3D.pitch !== parseFloat(newValue)){
viewshed3D.pitch = parseFloat(newValue);
viewshed3D.removeClipRegion('test');
}
}
);
SuperMap3D.knockout.getObservable(viewModel, 'distance').subscribe(
function (newValue) {
if(viewshed3D.distance !== parseFloat(newValue)){
viewshed3D.distance = parseFloat(newValue);
viewshed3D.removeClipRegion('test');
}
}
);
SuperMap3D.knockout.getObservable(viewModel, 'verticalFov').subscribe(
function (newValue) {
if(viewshed3D.verticalFov !== parseFloat(newValue)){
viewshed3D.verticalFov = parseFloat(newValue);
viewshed3D.removeClipRegion('test');
}
}
);
SuperMap3D.knockout.getObservable(viewModel, 'horizontalFov').subscribe(
function (newValue) {
if(viewshed3D.horizontalFov !== parseFloat(newValue)){
viewshed3D.horizontalFov = parseFloat(newValue);
viewshed3D.removeClipRegion('test');
}
}
);
SuperMap3D.knockout.getObservable(viewModel, 'visibleAreaColor').subscribe(
function (newValue) {
var color = SuperMap3D.Color.fromCssColorString(newValue);
viewshed3D.visibleAreaColor = color;
}
);
SuperMap3D.knockout.getObservable(viewModel, 'invisibleAreaColor').subscribe(
function (newValue) {
var color = SuperMap3D.Color.fromCssColorString(newValue);
viewshed3D.hiddenAreaColor = color;
}
);
$("#colorPicker1").spectrum({
color: colorStr1,
showPalette: true,
showAlpha: true,
localStorageKey: "spectrum.demo",
preferredFormat:'rgb'
});
$('#colorPicker2').spectrum({
color: colorStr2,
showPalette: true,
showAlpha: true,
localStorageKey: "spectrum.demo",
preferredFormat:'rgb'
});
$(".close").click(function () {
$("#wrapper").hide();
});
$("#chooseView").click(function (e) {
if (pointHandler.active) {
return;
}
//先清除之前的可视域分析
// viewer.entities.removeAll();
viewshed3D.distance = 0.1;
scene.viewFlag = true;
//激活绘制点类
pointHandler.activate();
});
$("#clip-mode").on("input propertychange", function () {
clipMode = $(this).val() === 'keep-inside' ? SuperMap3D.ClippingType.KeepInside : SuperMap3D.ClippingType.KeepOutside;
viewshed3D.setClipMode(clipMode);
});
$("#cilpRegion").click(function (e) {
handlerPolygon.deactivate();
handlerPolygon.activate();
});
$("#clear").on("click", function () {
viewshed3D.removeAllClipRegion();
// 清除观察点
pointHandler.clear()
$("#wrapper").hide();
viewshed3D.distance = 0.1;
scene.viewFlag = true;
})
$('#loadingbar').remove();
$("#toolbar").show();
}
if (typeof SuperMap3D !== 'undefined') {
window.startupCalled = true;
onload(SuperMap3D);
}
</script>
</body>
</html>
注意替换Cesium.Ion.defaultAccessToken,以及天地图官网申请的密钥