简易建模组件
功能介绍
通过远程调用接口获取白膜信息,实现简易建模的协同共享。
注:该功能使用了 elementUI 库,使用前需安装该库,具体方法见 elementUI 安装 。
不妨通过代码示例在 Vue 中尝试一下:
组件代码示例
默认路径为 components/createModelSet/index.vue
vue
<template>
<div>
<!-- 简易建模 -->
<el-card class="box-card">
<div class="title">简易建模</div>
<hr />
<div class="button subButton" @click="drawExtent()">绘制</div>
<div class="button subButton" @click="induationAnalysis()">开始建模</div>
<div class="button mainButton" @click="initCreate()">保存最新建模</div>
<div class="button" @click="generationModel()">测试批量建模</div>
<div class="button" @click="clearAllEntities()">清除</div>
<el-table
v-loading="loading"
:data="testData"
:empty-text="emptyText"
style="width: 100%; min-width: 270px"
max-height="300px"
highlight-current-row
@row-dblclick="onClickRow"
>
<el-table-column
label="显隐"
width="50"
header-align="center"
align="center"
>
<template slot-scope="scope">
<el-switch
v-model="scope.row.isShow"
:active-value="1"
:nactive-value="0"
active-color="#13ce66"
inactive-color="#ff4949"
@change="handleChange(scope.$index, scope.row)"
>
</el-switch>
</template>
</el-table-column>
<el-table-column
label="绘制名称"
width="120"
header-align="center"
align="center"
>
<template slot-scope="scope">
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.name }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="操作" header-align="center" align="center">
<template slot-scope="scope">
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script type="text/javascript">
import * as Cesium from 'cesium'
import axios from 'axios'
import * as turf from '@turf/turf';
import { GET_BUILDING_LIST, INSERT_BUILDING, UPDATE_BUILDING_ISSHOW, DELETE_BUILDING } from '@/apis/building/building'
export default {
components: {
},
data () {
return {
modelId: 1,
loading: false,
emptyText: "暂无数据",
mouseAxisList: [],
testData: [],
tempData: []
}
},
methods: {
/**
* 生成区域绘制
*/
initCreate () {
let viewer = uniCore.viewer;
// 暂时关闭定时器
clearInterval(window.onlineInterval);
window.onlineInterval = undefined;
// 获取building
// TODO: 这里暂时扩大到500,500后的数据如何管理后面处理
axios.get(GET_BUILDING_LIST, {
params: { modelId: this.modelId, size: 500 }
})
.then((res) => {
// 找出在testData中但不在云端中的数据,进行隐藏
let badData = this.tempData.filter(item => !res.data.data.records.includes(item))
badData.forEach((ele) => {
if (uniCore.model.getEntitiesByName(ele.id).length !== 0) {
uniCore.model.getEntitiesByName(ele.id).forEach((e) => {
e.show = false;
})
}
})
this.tempData = res.data.data.records;
this.testData = res.data.data.records;
if (this.testData.length === 0) this.emptyText = "暂无数据";
this.loading = false;
// 添加建筑
this.testData.forEach(item => {
let res = uniCore.model.getEntitiesByName(item.id);
if (res.length < item.axisList.length) {
if (item.isShow) {
// 处理字符串转数字,这里不处理不会报错,因此注释这段代码
// let axisList = item.axisList.map(e => { return { x: parseFloat(e.x), y: parseFloat(e.y), z: parseFloat(e.z) } })
// 避免贴图材质颜色重叠问题
for (let i = 0; i < item.layerNum; i++) {
viewer.entities.add({
name: item.id,
description: item.name,
polygon: {
hierarchy: item.axisList,
// material: Cesium.Color.fromBytes(64, 157, 253, 20), //rgba
material: Cesium.Color.WHITESMOKE.withAlpha(1),
// material: new Cesium.ImageMaterialProperty({
// //贴图图片的地址
// image: './static/img/texture/building.jpg',
// //可以给贴图设置颜色,设置颜色后图片就不是彩色的了
// //color: Cesium.Color.BLUE,
// //设置贴图的横向和纵向的图片数量,这里横向为1,纵向为1,就等于是不平铺
// repeat: new Cesium.Cartesian2(1, 1),
// //transparent: true
// }),
height: Number(item.modelHeight * (i + 0)),
extrudedHeight: Number(item.modelHeight * (i + 1)),
outline: true,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 4,
// perPositionHeight: true
}
});
}
}
} else {
// 这里控制显隐
res.forEach((e) => {
e.show = item.isShow;
})
}
})
}).then(() => {
// 重启定时器
this.$parent.createOnlineInterval();
})
.catch(error => {
console.log(error);
this.emptyText = "网络错误";
this.$notify.error({
title: '错误',
message: '网络错误,无法更新数据',
});
this.loading = false;
})
// // TODO: 缓存当前数据,接后端后可考虑删除或修改此处逻辑
// localStorage.setItem('paintData', JSON.stringify(this.testData));
},
onClickRow (val) {
let axis = uniCore.position.cartesian3_2axis(val.axisList[Math.round(val.axisList.length / 2)]);
uniCore.position.buildingPosition(uniCore.viewer, [parseFloat(axis[0]), parseFloat(axis[1]) - 0.00005, 2260], -20, -90, 1)
},
handleChange (index, row) {
// 这里需要将修改提交到后端再进行更新标签状态,以保证多端协同
this.loading = true;
row.isShow = !row.isShow ? 0 : 1;
axios.post(UPDATE_BUILDING_ISSHOW, {
id: row.id, isShow: row.isShow
})
.then((res) => {
// console.log(res);
// 更新状态
this.initCreate();
this.loading = true;
})
.catch(error => {
console.log(error);
this.emptyText = "网络错误";
this.$notify.error({
title: '错误',
message: '网络错误,无法更新数据',
});
this.loading = false;
})
},
handleDelete (index, row) {
this.loading = true;
// 删除label
axios.post(DELETE_BUILDING, {
"id": row.id
})
.then((res) => {
// console.log(res);
// 隐藏建筑
uniCore.tip.hidePointById(row.id, true)
// 实时删除功能
this.testData = this.testData.filter(e => e.id !== row.id)
this.loading = false;
})
.catch(error => {
console.log(error);
this.$notify.error({
title: '错误',
message: '网络错误,无法更新数据',
});
this.loading = false;
})
},
init () {
this.activeShapePoints = []
this.floatingPoint = undefined
this.activeShape = undefined
this.handler = undefined
this.isDraw = false
this.buildingHeight = 0
this.tempEntities = []
},
drawExtent () {
this.init();
let viewer = uniCore.viewer;
viewer.scene.globe.depthTestAgainstTerrain = true; // 开启深度检测
this.handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
this.handler.setInputAction((event) => {
const earthPosition = viewer.scene.pickPosition(event.position);
if (Cesium.defined(earthPosition)) {
if (this.activeShapePoints.length === 0) {
this.floatingPoint = this.createPoint(earthPosition);
this.activeShapePoints.push(earthPosition);
const dynamicPositions = new Cesium.CallbackProperty(() => {
return new Cesium.PolygonHierarchy(this.activeShapePoints);
}, false);
this.activeShape = this.drawShape(dynamicPositions, Cesium.Color.fromBytes(255, 255, 255, 50));
}
this.activeShapePoints.push(earthPosition);
this.tempEntities.push(this.createPoint(earthPosition));
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
this.handler.setInputAction((event) => {
if (Cesium.defined(this.floatingPoint)) {
const newPosition = viewer.scene.pickPosition(event.endPosition);
if (Cesium.defined(newPosition)) {
this.floatingPoint.position.setValue(newPosition);
this.activeShapePoints.pop();
this.activeShapePoints.push(newPosition);
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
this.handler.setInputAction(() => {
this.activeShapePoints.pop();
if (this.activeShapePoints.length < 3) return;
this.tempEntities.push(this.drawPolyline(this.activeShapePoints));
let ploy = this.drawShape(this.activeShapePoints, Cesium.Color.fromBytes(255, 255, 255, 50));
this.tempEntities.push(ploy);
this.getAreaHeight(this.activeShapePoints);
viewer.entities.remove(this.floatingPoint);
viewer.entities.remove(this.activeShape);
this.floatingPoint = undefined;
this.activeShape = undefined;
this.handler.destroy();// 关闭事件句柄
this.handler = null;
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
},
getAreaHeight (positions) {
let viewer = uniCore.viewer;
let startP = positions[0];
let endP = positions[positions.length - 1];
if (startP.x != endP.x && startP.y != endP.y && startP.z != endP.z) {
positions.push(positions[0]);
}
const tempPoints = [];
for (let i = 0; i < positions.length; i++) {
let ellipsoid = viewer.scene.globe.ellipsoid;
let cartographic = ellipsoid.cartesianToCartographic(positions[i]);
let lat = Cesium.Math.toDegrees(cartographic.latitude);
let lng = Cesium.Math.toDegrees(cartographic.longitude);
tempPoints.push([lng, lat]);
}
let line = turf.lineString(tempPoints);
let chunk = turf.lineChunk(line, 10, { units: 'meters' });
const tempArray = [];
chunk.features.forEach(f => {
f.geometry.coordinates.forEach(c => {
tempArray.push(Cesium.Cartographic.fromDegrees(c[0], c[1]));
})
})
let promise = Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, tempArray);
promise.then((updatedPositions) => {
const max = Math.max.apply(Math, updatedPositions.map(item => { return item.height }))
const min = Math.min.apply(Math, updatedPositions.map(item => { return item.height }))
this.buildingHeight = Math.ceil(min);
this.isDraw = !this.isDraw; // 禁用绘制按钮
})
},
createPoint (worldPosition) {
let viewer = uniCore.viewer;
const point = viewer.entities.add({
position: worldPosition,
point: {
color: Cesium.Color.WHITE,
pixelSize: 5,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
},
});
return point;
},
drawShape (positionData, mat) {
let viewer = uniCore.viewer;
let shape = viewer.entities.add({
polygon: {
hierarchy: positionData,
material: mat,
outline: true,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 4,
}
});
return shape;
},
drawPolyline (positions) {
let viewer = uniCore.viewer;
if (positions.length < 1) return;
let startP = positions[0];
let endP = positions[positions.length - 1];
if (startP.x != endP.x && startP.y != endP.y && startP.z != endP.z) {
positions.push(positions[0]);
}
return viewer.entities.add({
name: 'polyline',
polyline: {
positions: positions,
width: 2.0,
material: Cesium.Color.BLACK,
clampToGround: true
}
})
},
induationAnalysis () {
// 创建输入框
let modelName = prompt("请输入模型名称");
let modelHeight = prompt("请输入楼栋高度");
let modelLayerNum = prompt("请输入楼层数量");
if (modelName !== null && modelName !== "" && modelHeight !== null && modelHeight !== "" && modelLayerNum !== null && modelLayerNum !== "") {
// 销毁当前方法左键点击事件
if (this.handler) {
this.handler.destroy();
this.handler = undefined;
}
// 数字转字符串以对接后端
let axisList = this.activeShapePoints.map(e => { return { x: e.x.toString(), y: e.y.toString(), z: e.z.toString() } })
let shapeObj = {};
shapeObj.name = modelName;
shapeObj.modelHeight = modelHeight;
shapeObj.layerNum = modelLayerNum;
shapeObj.axisList = axisList;
shapeObj.color = "#ffffff";
shapeObj.isShow = 1;
// 新增label
axios.post(INSERT_BUILDING, shapeObj)
.then((res) => {
// console.log(res);
// 更新标签状态
this.initCreate();
this.loading = true;
})
.catch(error => {
console.log(error);
this.$notify.error({
title: '错误',
message: '网络错误,无法更新数据',
});
this.loading = false;
})
this.clearAllEntities();
function guid () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}
},
clearAllEntities () {
let viewer = uniCore.viewer;
const length = this.tempEntities.length;
for (let i = 0; i < length; i++) {
viewer.entities.remove(this.tempEntities[i]);
}
this.tempEntities = [];
this.activeShapePoints = [];
this.isDraw = !this.isDraw;
this.floatingPoint = undefined;
this.activeShape = undefined;
if (this.handler) {
this.handler.destroy();
this.handler = undefined;
}
},
// TODO: 点击高亮
highLightEntity () {
// 清除高亮颜色
function clearColor () {
try {
window.lastPickEntity.id._polygon.material = window.lastEntityColor;
} catch (error) {
// console.log(`clearColor error:${error}`);
}
}
// 高亮选中材质颜色
function colorSet (pickObj) {
try {
// 高亮颜色设置
pickObj.id._polygon.material = Cesium.Color.fromCssColorString('rgba(238, 85, 34,0.7)');
} catch (error) {
console.log(`colorSet error:${error}`);
}
}
// 保存选中材质颜色
function colorSave (pickObj) {
try {
window.lastEntityColor = Object.freeze(pickObj.id._polygon.material);
window.lastPickEntity = pickObj;
} catch (error) {
console.log(`colorSave error:${error}`);
}
}
const handler = new Cesium.ScreenSpaceEventHandler(uniCore.viewer.scene.canvas);
handler.setInputAction(function (e) {
const pickObj = uniCore.viewer.scene.pick(e.position);
clearColor()
if (Cesium.defined(pickObj)) {
try {
// 保证选到的是简单建模的模型
if (!!pickObj && !!pickObj.id._description._value) {
colorSave(pickObj)
colorSet(pickObj)
}
}
catch { (err) => { console.log(err); } }
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
},
// TODO: 批量生成模型
generationModel () {
// 给定楼层名称、坐标、楼层高度、楼层数量,批量生成楼层
let randomData = [
{
"name": "xx大厦",
"modelHeight": 150,
"layerNum": 13,
"axis": {
"x": "113.11684446037646",
"y": "28.260540253905273"
}
},
{
"name": "xx楼",
"modelHeight": 120,
"layerNum": 5,
"axis": {
"x": "113.12138590577335",
"y": "28.261241565931535"
}
}
]
// 需要根据建筑单个坐标,生成一个可用的axisList(建筑多边形轮廓),需要用到模型样式预定义
this.modelPerset(randomData[1].axis)
},
// TODO: 模型样式预定义
modelPerset (axis) {
const centerLongitude = axis.x;
const centerLatitude = axis.y;
// 正方体
// 正方形的边长,单位为米
const sideLength = 100;
// 创建中心点的Cartographic
const center = new Cesium.Cartographic(Cesium.Math.toRadians(centerLongitude), Cesium.Math.toRadians(centerLatitude));
// 计算正方形的四个角的Cartographic
const leftBottom = new Cesium.Cartographic(center.longitude - sideLength / (2 * Cesium.Ellipsoid.WGS84._radii.x * Math.cos(center.latitude)), center.latitude - sideLength / (2 * Cesium.Ellipsoid.WGS84._radii.y));
const rightBottom = new Cesium.Cartographic(center.longitude + sideLength / (2 * Cesium.Ellipsoid.WGS84._radii.x * Math.cos(center.latitude)), center.latitude - sideLength / (2 * Cesium.Ellipsoid.WGS84._radii.y));
const rightTop = new Cesium.Cartographic(center.longitude + sideLength / (2 * Cesium.Ellipsoid.WGS84._radii.x * Math.cos(center.latitude)), center.latitude + sideLength / (2 * Cesium.Ellipsoid.WGS84._radii.y));
const leftTop = new Cesium.Cartographic(center.longitude - sideLength / (2 * Cesium.Ellipsoid.WGS84._radii.x * Math.cos(center.latitude)), center.latitude + sideLength / (2 * Cesium.Ellipsoid.WGS84._radii.y));
// 将Cartographic转换为世界坐标
const leftBottomWorld = Cesium.Ellipsoid.WGS84.cartographicToCartesian(leftBottom);
const rightBottomWorld = Cesium.Ellipsoid.WGS84.cartographicToCartesian(rightBottom);
const rightTopWorld = Cesium.Ellipsoid.WGS84.cartographicToCartesian(rightTop);
const leftTopWorld = Cesium.Ellipsoid.WGS84.cartographicToCartesian(leftTop);
// 最终所需的正方体坐标
let res = [leftBottomWorld, rightBottomWorld, rightTopWorld, leftTopWorld]
console.log(res);
},
},
mounted () {
// // TODO:获取缓存数据,接后端后可考虑删除
// if (localStorage.getItem('paintData') !== null) {
// this.testData = JSON.parse(localStorage.getItem('paintData'));
// }
this.highLightEntity();
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
::v-deep .el-card__body {
padding: 20px 20px 20px 20px;
}
.box-card ::-webkit-scrollbar {
display: none;
}
::v-deep .box-card {
position: absolute;
top: 3%;
left: 3%;
width: 300px;
z-index: 1;
background: rgb(26 26 26 / 83%);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0px 24px 54px 0px rgba(35, 41, 50, 0.5);
border-radius: 15px;
padding: 0 24px 10px 24px;
margin-bottom: 12px;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
.el-table {
border-radius: 15px;
}
.title {
font-size: 18px;
font-weight: bold;
text-shadow: 1px 1px #000;
color: #fefeff;
display: block;
margin-bottom: 10px;
}
hr {
margin-left: 24px;
margin-bottom: 10px;
border: none;
border-bottom: 1px solid #ffffff1a;
}
.button {
display: inline-flex;
margin: 5px 10px;
color: white;
background: #4d4d4dd1;
border-radius: 10px;
padding: 7px 20px;
cursor: pointer;
transition: 0.3s;
}
.mainButton {
background: #105bc5;
font-weight: 700;
padding: 7px 40px;
}
.subButton {
background: #979797cc;
}
.mainButton:hover {
background: #009fff;
box-shadow: 0px 0px 54px 0px #009fffa8;
}
.subButton:hover {
background: #d7d7d7cc;
box-shadow: 0px 0px 54px 0px #d7d7d7a8;
}
}
</style>API代码示例(服务器需自行配置):
js
let baseURL = process.env.VUE_APP_DEV_API || ""
export const GET_BUILDING_LIST = baseURL + "/bim/building/list"
export const INSERT_BUILDING = baseURL + "/bim/building/add"
export const UPDATE_BUILDING_ISSHOW = baseURL + "/bim/building/update"
export const DELETE_BUILDING = baseURL + "/bim/building/del"接口配置可参考 示例项目 Apifox 文档 中有关于 building 的接口响应示例。
调用代码示例
vue
<template>
<div id="unicoreContainer">
<!-- 简易建模窗口卡片开始 -->
<cmSet ref="cmSetId"></cmSet>
<!-- 简易建模窗口卡片结束 -->
</div>
</template>
<script>
import { UniCore } from 'unicore-sdk'
import { config } from 'unicore-sdk/unicore.config'
import 'unicore-sdk/Widgets/widgets.css'
import cmSet from '@/components/createModelSet/index'; //简易建模组件
export default {
components: {
cmSet
},
// 生命周期 - 挂载完成(可以访问DOM元素)
mounted () {
this.init();
// 可以不断调用 initCreate 方法实现实时轮询最新数据,如
setInterval(() => {
this.$refs.cmSetId.initCreate();
}, 1000)
},
// 方法集合
methods: {
/**
* 通用图形引擎初始化
*/
init () {
// 初始化UniCore
// 目前采用Cesium的地形&底图数据,这里配置Cesium的token
let accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxNjEwMzI4My01MjBmLTQzYzktOGZiMS0wMDRhZjE0N2IyMGIiLCJpZCI6MTc1NzkyLCJpYXQiOjE3MTM3NzQ3OTh9.zU-R4MNvHr8rvn1v28PQfDImyutnpPF2lmEgGeSPckQ";
// 初始化unicore
let uniCore = new UniCore(config, accessToken);
uniCore.init("unicoreContainer");
window.uniCore = uniCore;
let viewer = uniCore.viewer;
// 视角初始化
uniCore.position.buildingPosition(viewer, [113.12380548015745, 28.250758831850005, 700], -20, -45, 1);
}
}
}
</script>
<style scoped>
#unicoreContainer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: black;
}
</style>调用代码示例中的关键代码
js
setInterval(() => {
this.$refs.cmSetId.initCreate();
}, 1000)