/** * @name: index * @author: Administrator * @date: 2023-11-20 16:05 * @description:index * @update: 2023-11-20 16:05 */ import BaseTileset from "../index"; import { html } from "./_element"; import { html2, css2 } from "./_element2"; import Dialog from '../../../../Element/Dialog'; import EventBinding from '../../../../Element/Dialog/eventBinding'; import BaseDialog from '../../../../../BaseDialog' import { syncData } from '../../../../../Global/MultiViewportMode' import { syncSplitData } from '../../../../../Global/SplitScreen' import { setActiveViewer, closeRotateAround, closeViewFollow} from '../../../../../Global/global' class BIM extends BaseTileset { #updateModelTimeout /** * @constructor * @description 加载BIM模型 * @param sdk {object} sdk * @param options {object} 模型参数 * @param options.id {string} 对象id * @param options.show=true {boolean} 模型显隐 * @param options.name {string} 名称 * @param options.url {string} 资源地址 * @param options.position {object} 模型位置 * @param options.position.lng {number} 经度 * @param options.position.lat {number} 纬度 * @param options.position.alt {number} 高度 * */ constructor(sdk, options = {}, _Dialog = {}) { super(sdk, options) this.picking = false this.features = options.features || [] this.exportStateArray = [] this.Dialog = _Dialog this._elms = {}; this._EventBinding = new EventBinding() this.Dialog.exportState = (e) => { this.exportState(e) } this.Dialog.exportProperty = (e) => { this.exportProperty(this.exportStateArray) } this.features = new Map() this.on() } get type() { return "bim" } get name() { return this.newData.name } set name(v) { this.newData.name = v this._elms.name && this._elms.name.forEach((item) => { item.value = v }) } get lng() { return this.newData.lng } set lng(v) { this.newData.lng = v this.updateModel(this.newData.lng, this.newData.lat, this.newData.height, this.newData.roll, this.newData.heading, this.newData.pitch, this.newData.scale) this._elms.lng && this._elms.lng.forEach((item) => { item.value = v }) } get lat() { return this.newData.lat } set lat(v) { this.newData.lat = v this.updateModel(this.newData.lng, this.newData.lat, this.newData.height, this.newData.roll, this.newData.heading, this.newData.pitch, this.newData.scale) this._elms.lat && this._elms.lat.forEach((item) => { item.value = v }) } get height() { return this.newData.height } set height(v) { this.newData.height = v this.updateModel(this.newData.lng, this.newData.lat, this.newData.height, this.newData.roll, this.newData.heading, this.newData.pitch, this.newData.scale) this._elms.height && this._elms.height.forEach((item) => { item.value = v }) } get roll() { return this.newData.roll } set roll(v) { this.newData.roll = v this.updateModel(this.newData.lng, this.newData.lat, this.newData.height, this.newData.roll, this.newData.heading, this.newData.pitch, this.newData.scale) this._elms.roll && this._elms.roll.forEach((item) => { item.value = v }) } get heading() { return this.newData.heading } set heading(v) { this.newData.heading = v this.updateModel(this.newData.lng, this.newData.lat, this.newData.height, this.newData.roll, this.newData.heading, this.newData.pitch, this.newData.scale) this._elms.heading && this._elms.heading.forEach((item) => { item.value = v }) } get pitch() { return this.newData.pitch } set pitch(v) { this.newData.pitch = v this.updateModel(this.newData.lng, this.newData.lat, this.newData.height, this.newData.roll, this.newData.heading, this.newData.pitch, this.newData.scale) this._elms.pitch && this._elms.pitch.forEach((item) => { item.value = v }) } get scale() { return this.newData.scale } set scale(v) { this.newData.scale = v this.updateModel(this.newData.lng, this.newData.lat, this.newData.height, this.newData.roll, this.newData.heading, this.newData.pitch, this.newData.scale) this._elms.scale && this._elms.scale.forEach((item) => { item.value = v }) } // get transparency() { // return this.newData.transparency // } // set transparency(v) { // this.newData.transparency = v // this.entity.style = new Cesium.Cesium3DTileStyle({ // color: "color('rgba(255,255,255," + this.newData.transparency + ")')", // show: true, // }); // this.entity.transparency = Number(this.newData.transparency) // this._elms.transparency && this._elms.transparency.forEach((item) => { // item.value = v // }) // } async loadSceneTree(url) { // Cesium.ExperimentalFeatures.enableModelExperimental = true; let array = url.split('/') array[array.length - 1] = 'scenetree.json' await Cesium.Resource.fetchJson({ url: array.join('/') }).then(res => { this.scenetree = res const initData = (array) => { array.forEach(item => { if (this.features.has(item.id)) { this.features.get(item.id).sphere = item.sphere } else { this.features.set(item.id, { sphere: item.sphere }) } if (item.children) { initData(item.children) } }) } initData(res.scenes[0].children) // res.scenes[0].children.forEach(item => { // if (this.features.has(item.id)) { // this.features.get(item.id).sphere = item.sphere // } // else { // this.features.set(item.id, {sphere: item.sphere}) // } // }) }) } // 编辑框 async edit(state) { let _this = this this.originalOptions = this.deepCopyObj(this.options) if (this._DialogObject && this._DialogObject.close) { this._DialogObject.close() this._DialogObject = null } if (state) { this._DialogObject = await new Dialog(this.sdk, this.newData, { title: 'BIM模型属性', left: '180px', top: '100px', resetCallBack: () => { this.reset() this.Dialog.resetCallBack && this.Dialog.resetCallBack() }, confirmCallBack: (info) => { this.name = this.name.trim() if (!this.name) { this.name = '未命名对象' } this.oldData.name = this.newData.name this.oldData.height = this.newData.height this.oldData.lng = this.newData.lng this.oldData.lat = this.newData.lat // this.oldData.transparency = this.newData.transparency this.oldData.scale = this.newData.scale this.oldData.roll = this.newData.roll this.oldData.heading = this.newData.heading this.oldData.pitch = this.newData.pitch this._DialogObject.close() let features = new Map() this.features.forEach((item, key) => { let data = { ...item } delete data.features features.set(key, data) }) this.Dialog.confirmCallBack && this.Dialog.confirmCallBack({ ...this.oldData, features: features, type: this.type }) syncSplitData(this.sdk, this.oldData.id) }, removeCallBack: () => { this.Dialog.removeCallBack && this.Dialog.removeCallBack() }, closeCallBack: () => { this.reset() // this.newData.transparency = this.oldData.transparency // this.newData.name = this.oldData.name // this.newData.height = this.oldData.height // this.newData.lng = this.oldData.lng // this.newData.lat = this.oldData.lat // this.newData.scale = this.oldData.scale // this.entity.style = new Cesium.Cesium3DTileStyle({ // color: "color('rgba(255,255,255," + this.newData.transparency + ")')", // show: true, // }); this.editObj.destroy() this.Dialog.closeCallBack && this.Dialog.closeCallBack() }, showCallBack: (show) => { this.newData.show = show this.entity && (this.entity.show = show) this.Dialog.showCallBack && this.Dialog.showCallBack() }, rotateCallBack: () => { if (this.rotationEditing) { this.rotationEditing = false } else { this.rotationEditing = true } }, translationalCallBack: () => { if (this.positionEditing) { this.positionEditing = false } else { this.positionEditing = true } } }) this._DialogObject._element.body.className = this._DialogObject._element.body.className + ' tileset-bim' // 内容部分 let contentElm = document.createElement('div'); contentElm.innerHTML = html() this._DialogObject.contentAppChild(contentElm) let all_elm = contentElm.getElementsByTagName("*") this._EventBinding.on(this, all_elm) this._elms = this._EventBinding.element } else { if (this._DialogObject && this._DialogObject.close) { this._DialogObject.close() this._DialogObject = null } } } async featureEdit(state, feature) { let _this = this this._element_style = null if (this._DialogObject && this._DialogObject.close) { this._DialogObject.close() this._DialogObject = null } if (state) { // console.log(this.entity) // console.log(this.entity.root.children[0].content) // console.log(feature.getProperty('id'), feature.getProperty('name'), feature.getProperty('state')) let name = feature.getProperty('name') // console.log(feature.getProperty('descriptions')) let data = { id: feature.getProperty('id'), name: name, state: feature.getProperty('state') || 0, descriptions: feature.getProperty('descriptions') || [ { id: this.randomString(), key: "点击此处可编辑", value: "" } ] } switch (feature.getProperty('state')) { case '0': data.stateCH = '' break case '1': data.stateCH = '已完成' break case '2': data.stateCH = '未完成' break case '3': data.stateCH = '修建中' break } // let feature = this.entity.root.children[0].content.getFeature(0) // console.log(id,feature, this.entity) // return this._element_style = document.createElement('style'); this._element_style.type = 'text/css'; this._element_style.setAttribute('data-name', 'YJ_style_dialog'); this._element_style.innerHTML = css2(); this._DialogObject = await new BaseDialog(this.sdk.viewer._container, { title: this.oldData.name + '-----设置状态', left: 'calc(50% - 200px)', top: 'calc(50% - 200px)', closeCallBack: () => { this.Dialog.closeCallBack && this.Dialog.closeCallBack() } }) await this._DialogObject.init() document.getElementsByTagName('head')[0].appendChild(this._element_style); // 内容部分 let content = document.createElement('div'); content.innerHTML = html2() // 名称 let e_name = content.querySelector("input[name='name']") e_name.value = name //状态 let e_state = content.querySelector("select[name='state-select']") e_state.value = data.state e_state.addEventListener('change', (e) => { data.state = e.target.value switch (data.state) { case '0': data.stateCH = '' break case '1': data.stateCH = '已完成' break case '2': data.stateCH = '未完成' break case '3': data.stateCH = '修建中' break } }); //自定义属性 let e_property = content.getElementsByClassName('property')[0] for (let i = 0; i < data.descriptions.length; i++) { createPropertyItem(data.descriptions[i], i) } function createPropertyItem(item) { let html = `
` // document.createRange().createContextualFragment(html) let newElement = document.createElement("div"); newElement.innerHTML = html let itemElm = newElement.getElementsByClassName('property-item')[0] let e_key = itemElm.querySelector("input[name='key']") let e_value = itemElm.querySelector("input[name='value']") let e_add = itemElm.getElementsByClassName('add')[0] let e_delete = itemElm.getElementsByClassName('delete')[0] e_key.addEventListener('input', (e) => { item.key = e.target.value }) e_value.addEventListener('input', (e) => { item.value = e.target.value }) e_add.addEventListener('click', () => { let newItem = { id: _this.randomString(), key: "点击此处可编辑", value: "" } data.descriptions.push(newItem) createPropertyItem(newItem) }) e_delete.addEventListener('click', (e) => { for (let i = 0; i < data.descriptions.length; i++) { if (data.descriptions[i].id === item.id) { data.descriptions.splice(i, 1) break } } e_property.removeChild(itemElm) // let item = { // key: "点击此处可编辑", // value: "" // } // createPropertyItem(item) }) e_property.appendChild(itemElm) } let target = this._DialogObject._element.foot.getElementsByClassName('translational')[0] this._DialogObject.contentAppChild(content) // foot部分 let confirmBtn = document.createElement('button'); confirmBtn.className = 'confirm'; confirmBtn.innerHTML = '确认' this._DialogObject.footAppChild(confirmBtn, target) confirmBtn.addEventListener('click', () => { let flag = false for (let i = 0; i < this.features.length; i++) { if (this.features[i].id == data.id) { this.features[i] = data flag = true break } } if (!flag) { this.features.push(data) } feature.setProperty('state', data.state) feature.setProperty('descriptions', data.descriptions) let color = '#fff' switch (data.state) { case '0': color = '#fff' break; case '1': color = '#f00' break; case '2': color = '#0f0' break; case '3': color = '#00f' break; default: } feature.color = Cesium.Color.fromCssColorString(color) this._DialogObject.close() this.Dialog.confirmCallBack && this.Dialog.confirmCallBack({ ...this.newData, features: this.features }) }); } else { if (this._element_style) { document.getElementsByTagName('head')[0].removeChild(this._element_style) this._element_style = null } if (this._DialogObject && this._DialogObject.close) { this._DialogObject.close() this._DialogObject = null } } } reset() { if (!this.entity) { return } // this.transparency = this.oldData.transparency this.name = this.oldData.name this.height = this.oldData.height this.lng = this.oldData.lng this.lat = this.oldData.lat this.roll = this.oldData.roll this.heading = this.oldData.heading this.pitch = this.oldData.pitch this.scale = this.oldData.scale } //更新模型位置 updateModel(_tx, _ty, _tz, _rx = 0, _ry = 0, _rz = 0, s = 1) { if (!this.tileset.root.transform) { if (window.ELEMENT) { window.ELEMENT.Message.closeAll(); window.ELEMENT.Message({ message: '该模型不支持移动和旋转!', type: 'warning', duration: 1500 }); } console.warn('该模型不支持移动和旋转!') return } if ((!_tx && _tx!==0) || (!_ty && _ty!==0) || (!_tz && _tz!==0)) { return } let mx = Cesium.Matrix3.fromRotationX( Cesium.Math.toRadians(_rx) ) let my = Cesium.Matrix3.fromRotationY( Cesium.Math.toRadians(_ry) ) let mz = Cesium.Matrix3.fromRotationZ( Cesium.Math.toRadians(_rz) ) // 平移 let m = Cesium.Transforms.eastNorthUpToFixedFrame(new Cesium.Cartesian3.fromDegrees(_tx, _ty, _tz)) // 旋转 let rotationX = Cesium.Matrix4.fromRotationTranslation(mx) let rotationY = Cesium.Matrix4.fromRotationTranslation(my) let rotationZ = Cesium.Matrix4.fromRotationTranslation(mz) let originalMatrix = new Cesium.Matrix4() Cesium.Matrix4.multiply(m, rotationX, originalMatrix) Cesium.Matrix4.multiply(originalMatrix, rotationY, originalMatrix) Cesium.Matrix4.multiply(originalMatrix, rotationZ, originalMatrix) const scale = Cesium.Matrix4.fromUniformScale(s); Cesium.Matrix4.multiply(originalMatrix, scale, this.entity._root.transform) if (!this.editObj.activeAxis) { this.editObj.position = { lng: _tx, lat: _ty, alt: _tz } } if (!this.editObj.activeCircle) { this.editObj.rotate = { x: _rx, y: _ry, z: _rz } } this.editObj && this.editObj.update() clearTimeout(this.#updateModelTimeout) this.#updateModelTimeout = setTimeout(() => { clearTimeout(this.#updateModelTimeout) let center = this.cartesian3Towgs84(this.entity.boundingSphere.center, this.sdk.viewer) let circle = turf.circle([center.lng, center.lat], this.entity.boundingSphere.radius / 1000, { steps: 360, units: 'kilometers' }); for (let [key, entity] of this.sdk.entityMap) { if (entity.type === 'BillboardObject' && entity.heightMode == 3) { let pt = turf.point([entity.lng, entity.lat]); if (turf.booleanPointInPolygon(pt, circle)) { entity.updateHeight() } } else { if(entity.label) { entity.label.show = entity.label.show } } } }, 300); } // exportProperty(states) { // console.log(this.features) // let fieldKeys = ['name', '', '', '', '', '', '', '', '', '', '', '', 'stateCH', 'descriptions'] // let fieldLabels = ['构件名称', '体积', '墩全高H', '墩身高h', '底部高程', '承台宽', '承台长', '承台高', '族', '桩径', '桩长', '结构材质', '完成情况', '自定义属性'] // let dataStr = fieldLabels.toString() + '\r\n'; // for (let i = 0; i < this.features.length; i++) { // for (let j = 0; j < states.length; j++) { // if (this.features[i].state == states[j]) { // fieldKeys.forEach(key => { // if (Array.isArray(this.features[i][key])) { // let str = '' // for (let k in this.features[i][key]) { // str += `${this.features[i][key][k].key + ':' + this.features[i][key][k].value}\n` // } // dataStr += `"${str}"\t` // } // else { // // 加引号是为了使换行符在单元格内正常显示 // dataStr += `"${this.features[i][key] ? this.features[i][key] : ''}"\t,`; // } // }); // dataStr += '\r\n'; // break // } // } // } // // encodeURIComponent 解决中文乱码 // const url = "data:text/xls;charset=utf-8,\ufeff" + encodeURIComponent(dataStr); // console.log(url) // // const link = document.createElement("a"); // // link.href = url; // // link.download = this.oldData.name + "--构件属性.xls"; // // link.style.display = 'none'; // // document.body.appendChild(link); // // link.click(); // // document.body.removeChild(link); //释放标签 // } getScenetree() { return this.scenetree } // 设置feature颜色 featureColor(id, color) { if (this.features.has(id)) { let features = this.features.get(id).features for (let key in features) { if (features[key].content._model) { features[key].color = Cesium.Color.fromCssColorString(color) } features[key].customColor = Cesium.Color.fromCssColorString(color) } this.features.get(id).customColor = Cesium.Color.fromCssColorString(color) } } getFeatureColor(id) { if (this.features.has(id)) { if (this.features.get(id).customColor) { return this.features.get(id).customColor } let features = this.features.get(id).features for (let key in features) { if (features[key].content._model) { return features[key].customColor || features[key].color } } } } // 设置feature显隐 featureShow(id, show) { if (this.features.has(id)) { let features = this.features.get(id).features for (let key in features) { if (features[key].content._model) { features[key].show = show } features[key].customShow = show } this.features.get(id).customShow = show } } //飞到feature位置 async featureFlyto(id) { if (this.features.has(id)) { let sphere = this.features.get(id).sphere let center = new Cesium.Cartesian3( sphere[0], sphere[1], sphere[2] ) let srcMatInv = this.entity._root.originalTransform srcMatInv = Cesium.Matrix4.inverse(srcMatInv, new Cesium.Matrix4()) let curMat = this.entity._root.transform let mat = Cesium.Matrix4.multiply(curMat, srcMatInv, new Cesium.Matrix4()) let center2 = Cesium.Matrix4.multiplyByPoint( mat, center, new Cesium.Cartesian3() ) let wgs84 = this.cartesian3Towgs84(center2, this.sdk.viewer) let cartesian3 = Cesium.Cartesian3.fromDegrees( wgs84.lng, wgs84.lat, wgs84.alt + sphere[3] ) setActiveViewer(0) closeRotateAround(this.sdk) closeViewFollow(this.sdk) this.sdk.viewer.camera.flyTo({ destination: cartesian3 }) // this.entity.style = await new Cesium.Cesium3DTileStyle({ // color: "color('rgba(255,255,255,0.2)')" // }); this.features.forEach((item, key) => { if (key === id) { let color = this.getFeatureColor(id) if (this.features.has(id) && color) { let features = this.features.get(id).features for (let k in features) { if (features[k].content._model) { features[k].color = color } features[k].customAlpha = 1 } this.features.get(id).customAlpha = 1 } // this.featureColor(id, `rgba(${Cesium.Color.floatToByte(color.red)},${Cesium.Color.floatToByte(color.green)},${Cesium.Color.floatToByte(color.blue)},${color.alpha})`) } else { let color = this.getFeatureColor(key) if (this.features.has(key) && color) { let features = this.features.get(key).features for (let k in features) { if (features[k].content._model) { features[k].color = Cesium.Color.fromCssColorString(`rgba(${Cesium.Color.floatToByte(color.red)},${Cesium.Color.floatToByte(color.green)},${Cesium.Color.floatToByte(color.blue)},${color.alpha * 0.2})`) } features[k].customAlpha = color.alpha * 0.2 } this.features.get(key).customAlpha = color.alpha * 0.2 } // this.featureColor(key, `rgba(${Cesium.Color.floatToByte(color.red)},${Cesium.Color.floatToByte(color.green)},${Cesium.Color.floatToByte(color.blue)},${color.alpha * 0.2})`) } }) // this.entity.readyPromise.then(()=>{ // this.featureColor(id, '#ffffff') // }) } else { this.features.forEach((item, key) => { let features = this.features.get(key).features let color = this.getFeatureColor(key) if (color) { for (let k in features) { if (features[k].content._model) { features[k].color = color } features[k].customAlpha = 1 } this.features.get(key).customAlpha = color.alpha * 0.2 } }) } } // 导出属性 exportProperty(states) { if (this.exportStateArray.length === 0) { window.ELEMENT && window.ELEMENT.Message({ message: '未选择属性导出选项!', type: 'warning', duration: 1500 }); return } let fieldKeys = ['name', '', '', '', '', '', '', '', '', '', '', '', 'stateCH', 'descriptions'] let fieldLabels = ['构件名称', '体积', '墩全高H', '墩身高h', '底部高程', '承台宽', '承台长', '承台高', '族', '桩径', '桩长', '结构材质', '完成情况', '自定义属性'] var url = 'data:application/vnd.ms-excel;base64,', tmplWorkbookXML = '' + 'Axel Richter{created}' + '' + '' + '' + '' + '{worksheets}' , tmplWorksheetXML = '{rows}
' , tmplCellXML = '{data}' , base64 = function (s) { return window.btoa(unescape(encodeURIComponent(s))) } , format = function (s, c) { return s.replace(/{(\w+)}/g, function (m, p) { return c[p]; }) } var ctx = ""; var workbookXML = ""; var worksheetsXML = ""; var rowsXML = ""; var pil = 0; for (var i = 0; i < this.features.length; i++) { for (let j = 0; j < states.length; j++) { if (this.features[i].state == states[j]) { if (i == 0) { rowsXML += '' + '构件名称' + '体积' + '墩全高H' + '墩身高h' + '底部高程' + '承台宽' + '承台长' + '承台高' + '' + '桩径' + '桩长' + '结构材质' + '完成情况' + '自定义属性' + ''; } rowsXML += ''; for (var key in fieldKeys) { if (Array.isArray(this.features[i][fieldKeys[key]])) { let str = '' for (let k in this.features[i][fieldKeys[key]]) { str += `${this.features[i][fieldKeys[key]][k].key + ':' + this.features[i][fieldKeys[key]][k].value} ` } ctx = { nameType: 'String', data: str }; } else { ctx = { nameType: 'String', data: this.features[i][fieldKeys[key]] || "0" }; } rowsXML += format(tmplCellXML, ctx); } rowsXML += ''; if (i > 0 && (i / 60000) % 1 === 0) { pil++; ctx = { rows: rowsXML, nameWS: 'Sheet' + i }; worksheetsXML += format(tmplWorksheetXML, ctx); rowsXML = ""; rowsXML += '' + '构件名称' + '体积' + '墩全高H' + '墩身高h' + '底部高程' + '承台宽' + '承台长' + '承台高' + '' + '桩径' + '桩长' + '结构材质' + '完成情况' + '自定义属性' + ''; } } } } ctx = { rows: rowsXML, nameWS: 'Sheet' }; worksheetsXML += format(tmplWorksheetXML, ctx); rowsXML = ""; ctx = { created: (new Date()).getTime(), worksheets: worksheetsXML }; workbookXML = format(tmplWorkbookXML, ctx); var link = document.createElement("A"); link.href = url + base64(workbookXML); link.download = this.oldData.name + "--构件属性.xls" link.target = '_blank'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } exportState(e) { let checkbox = e.target.getElementsByTagName('input')[0] checkbox.checked = !checkbox.checked if (checkbox.checked) { this.exportStateArray.push(checkbox.value) this.exportStateArray = Array.from(new Set(this.exportStateArray)) } else { for (let i = 0; i < this.exportStateArray.length; i++) { if (this.exportStateArray[i] == checkbox.value) { this.exportStateArray.splice(i, 1) break } } } } } export default BIM