/** * @description 批量模型 */ import Dialog from '../../Element/Dialog'; import { html } from "./_element"; import EventBinding from '../../Element/Dialog/eventBinding'; import Base from "../index"; import { syncData } from '../../../Global/MultiViewportMode' import Model from '../BaseSource/BaseModel/Model' import { legp } from '../../Element/datalist' import DrawPolyline from '../../../Draw/drawPolyline' import DrawPolygon from '../../../Draw/drawPolygon' import DrawThreeRect from '../../../Draw/drawThreeRect' import DrawPoint from '../../../Draw/drawPoint' import { setActiveViewer, closeRotateAround, closeViewFollow } from '../../../Global/global' import { setSplitDirection, syncSplitData, setActiveId } from '../../../Global/SplitScreen' class BatchModel extends Base { /** * @constructor * @param sdk * @description 批量模型 * @param options {object} 批量模型属性 * @param options.name=未命名对象 {string} 名称 * @param options.type=polygon {string} 线类型(line,polygon) * @param options.url=polygon {string} 线类型(line,polygon,point) * @param options.spacing= {number} 间距 * @param options.show=true {boolean} * @param Dialog {object} 弹框对象 * @param Dialog.confirmCallBack {function} 弹框确认时的回调 * */ constructor(sdk, options = {}, callback = null, _Dialog = {}) { super(sdk, options); this.viewer = this.sdk.viewer this.options.name = options.name || '批量模型' this.options.type = options.type || '面' this.options.url = options.url || '' this.options.spacing = options.spacing || 50 this.options.positions = options.positions || [] this.options.show = (options.show || options.show === false) ? options.show : true this.callback = callback this.Dialog = _Dialog this._EventBinding = new EventBinding() this._elms = {}; this.pointArr = [] this.sdk.addIncetance(this.options.id, this) // BatchModel.computeDis(this) // if (this.options.positions.length > 0 || this.options.positions.lng) { if ((options.type && options.spacing != undefined) || options.type == '点') { // BatchModel.computeDis(this) let Draw switch (options.type) { case '点': Draw = new DrawPoint(this.sdk) break; case '线': Draw = new DrawPolyline(this.sdk) break; case '面': Draw = new DrawThreeRect(this.sdk) break; default: break; } Draw && Draw.start((a, positions) => { this.options.positions = positions; // this.callback(this.options); (this.options.positions.length || this.options.positions.lng) && BatchModel.computeDis(this) }) } else { this.edit(true) } } // 计算距离 static async computeDis(that) { let fromDegreesArray = [] let arr let posiArr = [] let array = [] if (that.options.type == '面') { that.options.positions.forEach(item => { fromDegreesArray.push(item.lng, item.lat) }) // arr = that.generateInterpolatedPoints(Cesium.Cartesian3.fromDegreesArray(fromDegreesArray), that.options.spacing) arr = await that.computedArea(Cesium.Cartesian3.fromDegreesArray(fromDegreesArray), that.options.spacing) array[0] = arr array[1] = that.calculateRoadAngle(Cesium.Cartesian3.fromDegreesArray(fromDegreesArray)[0], Cesium.Cartesian3.fromDegreesArray(fromDegreesArray)[3]) arr.forEach((item, index) => { const cartographic = Cesium.Cartographic.fromCartesian( item // Cartesian3对象 {x, y, z} ); const longitude = Cesium.Math.toDegrees(cartographic.longitude); const latitude = Cesium.Math.toDegrees(cartographic.latitude); const height = cartographic.height; posiArr.push({ lng: longitude, lat: latitude, alt: height }) }) } else if (that.options.type == '线') { that.options.positions.forEach(item => { fromDegreesArray.push(item.lng, item.lat) }) array = await that.linePoint(Cesium.Cartesian3.fromDegreesArray(fromDegreesArray), that.options.spacing) arr = array[0] that.pointArr = arr arr.forEach((item, index) => { const cartographic = Cesium.Cartographic.fromCartesian( item // Cartesian3对象 {x, y, z} ); const longitude = Cesium.Math.toDegrees(cartographic.longitude); const latitude = Cesium.Math.toDegrees(cartographic.latitude); const height = cartographic.height; posiArr.push({ lng: longitude, lat: latitude, alt: height }) }) } else if (that.options.type == '点') { let height = await that.getClampToHeight({ lng: that.options.positions.lng, lat: that.options.positions.lat }) posiArr = [{ lng: that.options.positions.lng, lat: that.options.positions.lat, alt: height }] // posiArr = [that.options.positions] that.pointArr = posiArr } let params = { type: that.options.type, positions: posiArr, rotate: that.options.type == '点' ? undefined : array[1] } that.callback(params) // posiArr.forEach((item, index) => { // let model = new Model(that.sdk, { // id: 'model' + index, // show: that.options.show, // url: that.options.url, // position: item, // rotate: that.options.type == '点' ? undefined : { x: 0, y: 0, z: array[1] && (array[1][index] || array[1]) } // }) // that.pointArr.push(model) // }) } async linePoint(polygonPositions, spacing) { let boundaryPoints = []; let boundaryAngle = []; for (let i = 0; i < polygonPositions.length - 1; i++) { const start = polygonPositions[i]; const end = polygonPositions[(i + 1) % polygonPositions.length]; const segmentLength = Cesium.Cartesian3.distance(start, end); const segments = Math.ceil(segmentLength / spacing); for (let j = 0; j <= segments; j++) { const ratio = j / segments; let point = Cesium.Cartesian3.lerp( start, end, ratio, new Cesium.Cartesian3() ); const cartographic = Cesium.Cartographic.fromCartesian( point // Cartesian3对象 {x, y, z} ); const longitude = Cesium.Math.toDegrees(cartographic.longitude); const latitude = Cesium.Math.toDegrees(cartographic.latitude); let height = await this.getClampToHeight({ lng: longitude, lat: latitude }) point = Cesium.Cartesian3.fromDegrees(longitude, latitude, height); boundaryPoints.push(point); if (j != segments || i == polygonPositions.length - 2) { boundaryAngle.push(this.calculateRoadAngle(start, end)) } } } return [[...new Set(boundaryPoints .map(p => `${p.x},${p.y},${p.z}`))] .map(str => { const [x, y, z] = str.split(',').map(Number); return new Cesium.Cartesian3(x, y, z); }), boundaryAngle]; } calculateRoadAngle(startPoint, endPoint) { const normal = Cesium.Ellipsoid.WGS84.geodeticSurfaceNormal(startPoint); const enuMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(startPoint, undefined, normal); const inverseMatrix = Cesium.Matrix4.inverse(enuMatrix, new Cesium.Matrix4()); const localEnd = Cesium.Matrix4.multiplyByPoint(inverseMatrix, endPoint, new Cesium.Cartesian3()); const horizontalVec = new Cesium.Cartesian2(localEnd.x, localEnd.y); Cesium.Cartesian2.normalize(horizontalVec, horizontalVec); const north = new Cesium.Cartesian2(1, 0); let angle = Cesium.Cartesian2.angleBetween(north, horizontalVec); angle = Cesium.Math.toDegrees(angle) const cross = Cesium.Cartesian2.cross(north, horizontalVec, new Cesium.Cartesian2()); // return cross < 0 ? angle : - angle; return cross < 0 ? -angle : angle; } generateInterpolatedPoints(polygonPositions, spacing) { // 1. 边界点插值 const boundaryPoints = []; for (let i = 0; i < polygonPositions.length; i++) { const start = polygonPositions[i]; const end = polygonPositions[(i + 1) % polygonPositions.length]; const segmentLength = Cesium.Cartesian3.distance(start, end); const segments = Math.ceil(segmentLength / spacing); for (let j = 0; j <= segments; j++) { const ratio = j / segments; const point = Cesium.Cartesian3.lerp( start, end, ratio, new Cesium.Cartesian3() ); boundaryPoints.push(point); } } // 2. 内部网格生成 const extent = this.computePolygonExtent(polygonPositions); let result = this.createGridFromBBox(extent, this.options.spacing) // const extent = Cesium.Rectangle.fromCartesianArray(polygonPositions); const gridPoints = []; // const polygon = new Cesium.PolygonHierarchy(polygonPositions); var polygon = [] this.options.positions.forEach(item => { polygon.push([item.lng, item.lat]) }) polygon.push(polygon[0]) for (let x = extent.west; x <= extent.east; x += result.lonStep) { for (let y = extent.south; y <= extent.north; y += result.latStep) { const position = Cesium.Cartesian3.fromDegrees(x, y); const point = turf.point([x, y]); const polygonTurf = turf.polygon([polygon]); const isInside = turf.booleanPointInPolygon(point, polygonTurf); isInside && gridPoints.push(position) } } // 3. 合并结果并去重 // return [...new Set([...boundaryPoints, ...gridPoints] return [...new Set([...gridPoints] .map(p => `${p.x},${p.y},${p.z}`))] .map(str => { const [x, y, z] = str.split(',').map(Number); return new Cesium.Cartesian3(x, y, z); }); } createGridFromBBox(bbox, spacing) { const earthRadius = 6378137; // WGS84椭球体长半轴 // 计算经度方向网格数 const lonDistance = Cesium.Cartesian3.distance( Cesium.Cartesian3.fromDegrees(bbox.west, (bbox.south + bbox.north) / 2, 0), Cesium.Cartesian3.fromDegrees(bbox.east, (bbox.south + bbox.north) / 2, 0) ); const lonCount = Math.ceil(lonDistance / spacing); // 计算纬度方向网格数 const latDistance = Cesium.Cartesian3.distance( Cesium.Cartesian3.fromDegrees((bbox.west + bbox.east) / 2, bbox.south, 0), Cesium.Cartesian3.fromDegrees((bbox.west + bbox.east) / 2, bbox.north, 0) ); const latCount = Math.ceil(latDistance / spacing); // 生成网格线 const lonStep = (bbox.east - bbox.west) / lonCount; const latStep = (bbox.north - bbox.south) / latCount; return { lonStep, latStep } } computePolygonExtent(positions) { // 计算多边形经纬度范围 const cartographics = positions.map(p => Cesium.Cartographic.fromCartesian(p)); const lons = cartographics.map(c => Cesium.Math.toDegrees(c.longitude)); const lats = cartographics.map(c => Cesium.Math.toDegrees(c.latitude)); return { west: Math.min(...lons), east: Math.max(...lons), south: Math.min(...lats), north: Math.max(...lats) }; } async computedArea(polygonPositions, spacing) { let dis12 = Cesium.Cartesian3.distance(polygonPositions[0], polygonPositions[1]); let dis23 = Cesium.Cartesian3.distance(polygonPositions[1], polygonPositions[2]); let vec12 = Cesium.Cartesian3.subtract(polygonPositions[1], polygonPositions[0], new Cesium.Cartesian3()); let vec23 = Cesium.Cartesian3.subtract(polygonPositions[2], polygonPositions[1], new Cesium.Cartesian3()); let num12 = Math.ceil(dis12 / spacing); let num23 = Math.ceil(dis23 / spacing); let line1 = [] for (let i = 0; i < num12; i++) { line1.push(await this.calculatePointB(polygonPositions[0], polygonPositions[1], i * spacing)) } let line2 = [] for (let i = 0; i < num12; i++) { line2.push(await this.calculatePointB(polygonPositions[3], polygonPositions[2], i * spacing)) } let allPoints = [] for (let i = 0; i < line1.length; i++) { for (let j = 0; j < num23; j++) { allPoints.push(await this.calculatePointB(line1[i], line2[i], j * spacing)) } } return allPoints } async calculatePointB(pointA, pointC, distance) { // 将输入坐标转换为Cartesian3类型 // const pointA = Cesium.Cartesian3.fromDegrees(a.longitude, a.latitude, a.height); // const pointC = Cesium.Cartesian3.fromDegrees(c.longitude, c.latitude, c.height); // 计算向量AC const vectorAC = Cesium.Cartesian3.subtract(pointC, pointA, new Cesium.Cartesian3()); // 计算向量AC的长度 const lengthAC = Cesium.Cartesian3.magnitude(vectorAC); // 归一化向量AC const unitVector = Cesium.Cartesian3.normalize(vectorAC, new Cesium.Cartesian3()); // 计算点B坐标 const scaledVector = Cesium.Cartesian3.multiplyByScalar(unitVector, distance, new Cesium.Cartesian3()); const pointB = Cesium.Cartesian3.add(pointA, scaledVector, new Cesium.Cartesian3()); const cartographic = Cesium.Cartographic.fromCartesian( pointB // Cartesian3对象 {x, y, z} ); const longitude = Cesium.Math.toDegrees(cartographic.longitude); const latitude = Cesium.Math.toDegrees(cartographic.latitude); let height = await this.getClampToHeight({ lng: longitude, lat: latitude }) let point = Cesium.Cartesian3.fromDegrees(longitude, latitude, height); // 转换回经纬度 // const cartographic = Cesium.Cartographic.fromCartesian(pointB); // return { // longitude: Cesium.Math.toDegrees(cartographic.longitude), // latitude: Cesium.Math.toDegrees(cartographic.latitude), // height: cartographic.height // }; // return pointB return point } get show() { return this.options.show } set show(v) { this.options.show = v for (let i = 0; i < this.pointArr.length; i++) { this.pointArr[i].show = v } } get type() { return this.options.type } set type(v) { this.options.type = v this._elms.type && this._elms.type.forEach(item => { item.value = v }) } get spacing() { return this.options.spacing } set spacing(v) { this.options.spacing = v this._elms.spacing && this._elms.spacing.forEach(item => { item.value = v }) } /** * @description 编辑框 * @param state=false {boolean} 状态: true打开, false关闭 */ async edit(state = false) { let _this = this this.originalOptions = this.deepCopyObj(this.options) // let elms = this.sdk.viewer._container.getElementsByClassName('YJ-custom-base-dialog') // for (let i = elms.length - 1; i >= 0; i--) { // this.sdk.viewer._container.removeChild(elms[i]) // } if (this._DialogObject && this._DialogObject.close) { this._DialogObject.close() this._DialogObject = null } if (state) { this._DialogObject = await new Dialog(this.sdk, this.originalOptions, { title: '默认模型参数设置', left: '180px', top: '100px', confirmCallBack: (options) => { this.name = this.name.trim() if (!this.name) { // this.name = '未命名对象' this.name = '飞线' } let Draw switch (this.options.type) { case '点': Draw = new DrawPoint(this.sdk) break; case '线': Draw = new DrawPolyline(this.sdk) break; case '面': Draw = new DrawThreeRect(this.sdk) break; default: break; } Draw && Draw.start((a, positions) => { this.options.positions = positions; // this.callback(this.options); (this.options.positions.length || this.options.positions.lng) && BatchModel.computeDis(this) }) this.originalOptions = this.deepCopyObj(this.options) this._DialogObject.close() this.Dialog.confirmCallBack && this.Dialog.confirmCallBack(this.originalOptions) syncData(this.sdk, this.options.id) syncSplitData(this.sdk, this.options.id) }, // resetCallBack: () => { // this.reset() // console.log('22222') // this.Dialog.resetCallBack && this.Dialog.resetCallBack() // }, // removeCallBack: () => { // console.log('33333') // this.Dialog.removeCallBack && this.Dialog.removeCallBack() // }, closeCallBack: () => { this.reset() // this.entity.style = new Cesium.Cesium3DTileStyle({ // color: "color('rgba(255,255,255," + this.newData.transparency + ")')", // show: true, // }); this.Dialog.closeCallBack && this.Dialog.closeCallBack() }, addFootElm: [ { tagName: 'button', className: 'flipe-over-y', innerHTML: '重置', event: [ 'click', () => { this.reset() } ] } ] // showCallBack: (show) => { // this.show = show // this.Dialog.showCallBack && this.Dialog.showCallBack() // } }, true) this._DialogObject._element.body.className = this._DialogObject._element.body.className + ' flow-line-surface' let contentElm = document.createElement('div'); contentElm.innerHTML = html() this._DialogObject.contentAppChild(contentElm) // 颜色组件 // let waterColorPicker = new YJColorPicker({ // el: contentElm.getElementsByClassName("flowLine-color")[0], // size: 'mini',//颜色box类型 // alpha: true,//是否开启透明度 // defaultColor: this.color, // disabled: false,//是否禁止打开颜色选择器 // openPickerAni: 'opacity',//打开颜色选择器动画 // sure: (color) => { // this.color = color // },//点击确认按钮事件回调 // clear: () => { // this.color = 'rgba(255,255,255,1)' // },//点击清空按钮事件回调 // }) let all_elm = contentElm.getElementsByTagName("*") this._EventBinding.on(this, all_elm) this._elms = this._EventBinding.element let nameData = [ { name: '点', value: '点', }, { name: '线', value: '线', }, { name: '面', value: '面', } ] let nameDataLegpObject = legp( this._DialogObject._element.content.getElementsByClassName( 'add-type-box' )[0], '.add-type' ) if (nameDataLegpObject) { nameDataLegpObject.legp_search(nameData) let nameDataLegpElm = this._DialogObject._element.content .getElementsByClassName('add-type')[0] .getElementsByTagName('input')[0] this._elms.type = [nameDataLegpElm] nameDataLegpElm.value = this.options.type for (let i = 0; i < nameData.length; i++) { if (nameData[i].value === nameDataLegpElm.value) { nameDataLegpObject.legp_searchActive(nameData[i].value) break } } nameDataLegpElm.addEventListener('input', () => { for (let i = 0; i < nameData.length; i++) { if (nameData[i].value === nameDataLegpElm.value) { this.type = nameData[i].value break } } }) } // this._elms.color = [waterColorPicker] } else { // if (this._element_style) { // document.getElementsByTagName('head')[0].removeChild(this._element_style) // this._element_style = null // } // if (this._DialogObject && this._DialogObject.remove) { // this._DialogObject.remove() // this._DialogObject = null // } } } drawArea() { } reset() { this.name = this.originalOptions.name this.type = this.originalOptions.type this.spacing = this.originalOptions.spacing this.show = this.originalOptions.show this.options.spacing = this.originalOptions.spacing } /** * 飞到对应实体 */ async flyTo(options = {}) { setActiveViewer(0) closeRotateAround(this.sdk) closeViewFollow(this.sdk) if (this.options.customView && this.options.customView.relativePosition && this.options.customView.orientation) { let orientation = { heading: Cesium.Math.toRadians(this.options.customView.orientation.heading || 0.0), pitch: Cesium.Math.toRadians(this.options.customView.orientation.pitch || -60.0), roll: Cesium.Math.toRadians(this.options.customView.orientation.roll || 0.0) } let lng = this.options.customView.relativePosition.lng let lat = this.options.customView.relativePosition.lat let alt = this.options.customView.relativePosition.alt let destination = Cesium.Cartesian3.fromDegrees(lng, lat, alt) let position = { lng: 0, lat: 0 } if (this.options.position) { position = { ...this.options.position } } else if (this.options.positions) { position = { ...this.options.positions[0] } } else if (this.options.center) { position = { ...this.options.center } } else if (this.options.start) { position = { ...this.options.start } } else { if (this.options.hasOwnProperty('lng')) { position.lng = this.options.lng } if (this.options.hasOwnProperty('lat')) { position.lat = this.options.lat } if (this.options.hasOwnProperty('alt')) { position.alt = this.options.alt } } // 如果没有高度值,则获取紧贴高度计算 // if (!position.hasOwnProperty('alt')) { // position.alt = await this.getClampToHeight(position) // } lng = this.options.customView.relativePosition.lng + position.lng lat = this.options.customView.relativePosition.lat + position.lat alt = this.options.customView.relativePosition.alt + position.alt destination = Cesium.Cartesian3.fromDegrees(lng, lat, alt) this.sdk.viewer.camera.flyTo({ destination: destination, orientation: orientation }) } else { let positionArray = [] if (this.options.positions.length > 0) { for (let i = 0; i < this.options.positions.length; i++) { let a = Cesium.Cartesian3.fromDegrees( this.options.positions[i].lng, this.options.positions[i].lat, this.options.positions[i].alt ) positionArray.push(a.x, a.y, a.z) } let BoundingSphere = Cesium.BoundingSphere.fromVertices(positionArray) this.viewer.camera.flyToBoundingSphere(BoundingSphere, { offset: { heading: Cesium.Math.toRadians(0.0), pitch: Cesium.Math.toRadians(-20.0), roll: Cesium.Math.toRadians(0.0) } }) } else if (this.options.positions.lng) { let orientation = { heading: Cesium.Math.toRadians(0.0), pitch: Cesium.Math.toRadians(-60.0), roll: Cesium.Math.toRadians(0.0) } this.sdk.viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(this.options.positions.lng, this.options.positions.lat, this.options.positions.alt + 100), // orientation: orientation }) } } } /** * 删除 */ async remove() { for (let i = 0; i < this.pointArr.length; i++) { this.pointArr[i].remove() } this.pointArr = [] this.positions = [] this.entity = null if (this._DialogObject && !this._DialogObject.isDestroy) { this._DialogObject.close() this._DialogObject = null } await this.sdk.removeIncetance(this.options.id) await syncData(this.sdk, this.options.id) } flicker() { } } export default BatchModel