/** * @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 { setSplitDirection, syncSplitData, setActiveId } from '../../../Global/SplitScreen' import { setActiveViewer, closeRotateAround, closeViewFollow } from '../../../Global/global' class Road extends Base { /** * @constructor * @param sdk * @description 道路 * @param options {object} 道路属性 * @param options.name=未命名对象 {string} 名称 * @param options.carRoadWidth=2 {number} 车道宽度 * @param options.sideWidth=2 {number} 人行道宽度 * @param options.positions=[] {array} 道路positions * @param options.roadImage='' {string} 车道贴图 * @param options.sideImage='' {string} 人行道贴图 * @param Dialog {object} 弹框对象 * @param Dialog.confirmCallBack {function} 弹框确认时的回调 * */ constructor(sdk, options = {}, _Dialog = {}) { super(sdk, options); this.viewer = this.sdk.viewer this.options.name = options.name || '道路' this.options.carRoadWidth = options.carRoadWidth || 10 this.options.sideWidth = options.sideWidth || 5 this.options.positions = options.positions || [] this.options.roadImage = options.roadImage || (this.getSourceRootPath() + '/img/roadPhoto.png') this.options.sideImage = options.sideImage || (this.getSourceRootPath() + '/img/sidePhoto.png') this.options.show = (options.show || options.show === false) ? options.show : true this.Dialog = _Dialog this._EventBinding = new EventBinding() this._elms = {}; this.positionArea = [] this.positions = [] this.lineEntity = '' this.sdk.addIncetance(this.options.id, this) Road.create(this) } // 创建道路 static create(that) { let positions = [] that.options.positions.forEach(v => { positions.push(new Cesium.Cartesian3.fromDegrees(v.lng, v.lat, v.alt)) }) let area = [[], [], []] area[1] = that.createLineBufferPolygon2(positions, that.options.carRoadWidth / 2) area[0] = that.createLineBufferPolygonSide(area[1][2], -that.options.sideWidth) area[2] = that.createLineBufferPolygonSide(area[1][1], that.options.sideWidth) if (that.viewer.entities.getById(that.options.id)) { that.viewer.entities.getById(that.options.id)._children.forEach((item) => { that.viewer.entities.remove(item); }); that.viewer.entities.remove(that.viewer.entities.getById(that.options.id)) } that.lineEntity = that.viewer.entities.add(new Cesium.Entity({ id: that.options.id, show: that.options.show })) const myImg = new Image() myImg.src = that.options.roadImage myImg.onload = function () { area[1][0].forEach((item, index) => { that.viewer.entities.add({ // id: that.options.id, parent: that.lineEntity, polygon: { hierarchy: new Cesium.PolygonHierarchy(item), material: new Cesium.ImageMaterialProperty({ image: that.options.roadImage, transparent: true,// 如果图片有透明部分,需要设置为 true repeat: that.calculateTextureRepeat(item, myImg) }), stRotation: that.calculateRoadAngle(positions[index], positions[index + 1]) } }); }) } const myImg2 = new Image() myImg2.src = that.options.sideImage myImg2.onload = function () { area[0].forEach((item, index) => { that.viewer.entities.add({ parent: that.lineEntity, polygon: { hierarchy: new Cesium.PolygonHierarchy(item), material: new Cesium.ImageMaterialProperty({ image: that.options.sideImage, transparent: true,// 如果图片有透明部分,需要设置为 true repeat: that.calculateTextureRepeat(item, myImg2) }), stRotation: that.calculateRoadAngle(positions[index], positions[index + 1]) } }); }) area[2].forEach((item, index) => { that.viewer.entities.add({ polygon: { hierarchy: new Cesium.PolygonHierarchy(item), material: new Cesium.ImageMaterialProperty({ image: that.options.sideImage, transparent: true,// 如果图片有透明部分,需要设置为 true repeat: that.calculateTextureRepeat(item, myImg2) }), stRotation: that.calculateRoadAngle(positions[index], positions[index + 1]) } }); }) } } getArr(arr1, arr2) { arr2 = arr2.reverse() let polygon = [] for (let index = 0; index < arr1.length - 1; index++) { polygon.push([arr1[index], arr1[index + 1], arr2[index + 1], arr2[index]]) } return polygon } calculateRoadAngle(startPoint, endPoint) { // 1. 获取地表法向量 const normal = Cesium.Ellipsoid.WGS84.geodeticSurfaceNormal(startPoint); // 2. 构建精确ENU坐标系 const enuMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(startPoint, undefined, normal); const inverseMatrix = Cesium.Matrix4.inverse(enuMatrix, new Cesium.Matrix4()); // 3. 转换终点并计算水平向量 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); const angle = Cesium.Cartesian2.angleBetween(north, horizontalVec); const cross = Cesium.Cartesian2.cross(north, horizontalVec, new Cesium.Cartesian2()); return cross < 0 ? angle : -angle; } calculatePolygonOrientation(positions) { // 假设 position 是 Cesium.Cartesian3 对象,表示地球上的某个点 var position = positions[0] // 获取东、北、上坐标系 var eastNorthUp = Cesium.Transforms.eastNorthUpToFixedFrame(position); // northAxis 是北方向向量 var northAxis = eastNorthUp.getColumn(1, new Cesium.Cartesian3()); Cesium.Cartesian3.normalize(northAxis, northAxis); const direction = Cesium.Cartesian3.subtract(positions[0], positions[1], new Cesium.Cartesian3()); Cesium.Cartesian3.normalize(direction, direction); const dot = Cesium.Cartesian3.dot(northAxis, direction); const magA = Cesium.Cartesian3.magnitude(northAxis); const magB = Cesium.Cartesian3.magnitude(direction); return Math.acos(dot / (magA * magB)); } calculateTextureRepeat(polygonPositions, textureSize, meterPerPixel = 0.01) { // 验证纹理尺寸 if (!textureSize.width || !textureSize.height) { throw new Error('Texture size must contain width and height in pixels'); } // 创建多边形几何体 const geometry = Cesium.PolygonGeometry.createGeometry( new Cesium.PolygonGeometry({ polygonHierarchy: new Cesium.PolygonHierarchy(polygonPositions), vertexFormat: Cesium.VertexFormat.POSITION_ONLY }) ); // 计算多边形面积(平方米) let area = 0; const indices = geometry.indices; const positions = geometry.attributes.position.values; for (let i = 0; i < indices.length; i += 3) { const i0 = indices[i] * 3; const i1 = indices[i + 1] * 3; const i2 = indices[i + 2] * 3; const p0 = new Cesium.Cartesian3(positions[i0], positions[i0 + 1], positions[i0 + 2]); const p1 = new Cesium.Cartesian3(positions[i1], positions[i1 + 1], positions[i1 + 2]); const p2 = new Cesium.Cartesian3(positions[i2], positions[i2 + 1], positions[i2 + 2]); const cross = Cesium.Cartesian3.cross( Cesium.Cartesian3.subtract(p1, p0, new Cesium.Cartesian3()), Cesium.Cartesian3.subtract(p2, p0, new Cesium.Cartesian3()), new Cesium.Cartesian3() ); area += Cesium.Cartesian3.magnitude(cross) * 0.5; } // 将像素尺寸转换为实际尺寸(平方米) const textureWidthMeters = textureSize.width * meterPerPixel; const textureHeightMeters = textureSize.height * meterPerPixel; const textureArea = textureWidthMeters * textureHeightMeters; // 计算各轴向重复次数 const repeatX = Math.sqrt(area) / textureWidthMeters; const repeatY = Math.sqrt(area) / textureHeightMeters; return new Cesium.Cartesian2(Math.max(1, Math.ceil(repeatX)), 1.0); } swapLastElements(arr1, arr2) { const last = arr1[arr1.length - 1] const first = arr2[0] arr1[arr1.length - 1] = first arr2[0] = last return [arr1, arr2]; } createLineBufferPolygonSide(positions, width) { let area = [] for (let i = 0; i < positions.length - 1; i++) { const start = positions[i]; const end = positions[i + 1]; const dir = Cesium.Cartesian3.subtract(end, start, new Cesium.Cartesian3()); Cesium.Cartesian3.normalize(dir, dir); // 获取垂直向量(基于Z轴) const perp = Cesium.Cartesian3.cross(dir, Cesium.Cartesian3.UNIT_Z, new Cesium.Cartesian3()); Cesium.Cartesian3.normalize(perp, perp); // 生成偏移向量 const offset = Cesium.Cartesian3.multiplyByScalar(perp, width, new Cesium.Cartesian3()); let point1 = Cesium.Cartesian3.add(start, offset, new Cesium.Cartesian3()) let point3 = Cesium.Cartesian3.add(end, offset, new Cesium.Cartesian3()) i == positions.length - 2 ? area.push(start, point1, end, point3) : area.push(start, point1) } let arr = [] for (let i = 0; i < area.length - 2; i += 2) { arr.push([area[i], area[i + 1], area[i + 3], area[i + 2]]) } return arr } createLineBufferPolygon2(positions, width) { let area = [] let leftPositions = []; let rightPositions = []; for (let i = 0; i < positions.length - 1; i++) { const start = positions[i]; // const end = positions[i + 1] || positions[i - 1]; const end = positions[i + 1]; const dir = Cesium.Cartesian3.subtract(end, start, new Cesium.Cartesian3()); Cesium.Cartesian3.normalize(dir, dir); // 获取垂直向量(基于Z轴) const perp = Cesium.Cartesian3.cross(dir, Cesium.Cartesian3.UNIT_Z, new Cesium.Cartesian3()); Cesium.Cartesian3.normalize(perp, perp); // 生成偏移向量 const offset = Cesium.Cartesian3.multiplyByScalar(perp, width, new Cesium.Cartesian3()); const offset2 = Cesium.Cartesian3.multiplyByScalar(perp, -width, new Cesium.Cartesian3()); let point1 = Cesium.Cartesian3.add(start, offset, new Cesium.Cartesian3()) let point2 = Cesium.Cartesian3.add(start, offset2, new Cesium.Cartesian3()) let point3 = Cesium.Cartesian3.add(end, offset, new Cesium.Cartesian3()) let point4 = Cesium.Cartesian3.add(end, offset2, new Cesium.Cartesian3()) if (i == positions.length - 2) { area.push(point1, point2, point3, point4) rightPositions.push(point1) leftPositions.push(point2) leftPositions.push(point4) rightPositions.push(point3) } else { area.push(point1, point2) rightPositions.push(point1) leftPositions.push(point2) } } let arr = [] for (let i = 0; i < area.length - 2; i += 2) { arr.push([area[i], area[i + 1], area[i + 3], area[i + 2]]) } return [arr, rightPositions, leftPositions] } createLineBufferPolygon(viewer, positions, width) { // 计算每个线段的左右偏移点 const leftPositions = []; const rightPositions = []; for (let i = 0; i < positions.length; i++) { const start = positions[i]; const end = positions[i + 1] || positions[i - 1]; // 计算线段方向向量 const direction = Cesium.Cartesian3.subtract(end, start, new Cesium.Cartesian3()); // const direction = Cesium.Cartesian3.subtract(start, end, new Cesium.Cartesian3()); Cesium.Cartesian3.normalize(direction, direction); // 计算垂直向量(使用上向量叉积) const up = Cesium.Cartesian3.UNIT_Z; const perpendicular = Cesium.Cartesian3.cross(direction, up, new Cesium.Cartesian3()); Cesium.Cartesian3.normalize(perpendicular, perpendicular); // 计算左右偏移点 const leftOffset = Cesium.Cartesian3.multiplyByScalar( perpendicular, width, new Cesium.Cartesian3() ); if (width > 0) { rightPositions.unshift(Cesium.Cartesian3.add(start, leftOffset, new Cesium.Cartesian3())); } else if (width < 0) { rightPositions.push(Cesium.Cartesian3.add(start, leftOffset, new Cesium.Cartesian3())); } } return rightPositions } get carRoadWidth() { return this.options.carRoadWidth } set carRoadWidth(v) { this.options.carRoadWidth = v Road.create(this) } get sideWidth() { return this.options.sideWidth } set sideWidth(v) { this.options.sideWidth = v Road.create(this) } /** * @description 编辑框 * @param state=false {boolean} 状态: true打开, false关闭 */ async edit(state = false) { 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.originalOptions, { title: '道路属性', left: '180px', top: '100px', confirmCallBack: (options) => { this.name = this.name.trim() if (!this.name) { this.name = '道路' } 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() this.Dialog.resetCallBack && this.Dialog.resetCallBack() }, closeCallBack: () => { this.reset() this.Dialog.closeCallBack && this.Dialog.closeCallBack() }, showCallBack: (show) => { this.show = show this.Dialog.showCallBack && this.Dialog.showCallBack() } }, true) this._DialogObject._element.body.className = this._DialogObject._element.body.className + ' road-surface' let contentElm = document.createElement('div'); contentElm.innerHTML = html() this._DialogObject.contentAppChild(contentElm) // 下拉选项 // let heightModeData = [ // { // name: '海拔高度', // value: '海拔高度', // key: '0', // }, // { // name: '相对地表', // value: '相对地表', // key: '1', // }, // { // name: '依附模型', // value: '依附模型', // key: '2', // } // ] // let heightModeObject = legp( // this._DialogObject._element.content.getElementsByClassName( // 'road-box' // )[0], // '.road-type' // ) // if (heightModeObject) { // heightModeObject.legp_search(heightModeData) // let heightModeDataLegpElm = this._DialogObject._element.content // .getElementsByClassName('road-type')[0] // .getElementsByTagName('input')[0] // for (let i = 0; i < heightModeData.length; i++) { // if (heightModeData[i].key == this.heightMode) { // heightModeDataLegpElm.value = heightModeData[i].value // heightModeObject.legp_searchActive( // heightModeData[i].value // ) // break // } // } // heightModeDataLegpElm.addEventListener('input', () => { // for (let i = 0; i < heightModeData.length; i++) { // if (heightModeData[i].value === heightModeDataLegpElm.value) { // this.heightMode = heightModeData[i].key // break // } // } // }) // this._elms.height = heightElm // this._elms.heightBox = heightBoxElm // this._elms.heightMode = heightModeDataLegpElm // this._elms.heightConfirm = heightConfirmElm // this._elms.heightModeObject = heightModeObject // heightConfirmElm.addEventListener('click', () => { // this.positionEditing = false // for (let i = 0; i < this.options.positions.length; i++) { // this.options.positions[i].alt = Number((this.options.positions[i].alt + Number(heightElm.value)).toFixed(2)) // this._elms.alt[i].innerHTML = this.options.positions[i].alt // } // let fromDegreesArray = this.renewPositions(this.options.positions) // this.entity.polyline.positions = Cesium.Cartesian3.fromDegreesArrayHeights( // fromDegreesArray // ) // this.positionEditing = false // PolylineObject.closeNodeEdit(this) // }) // } let all_elm = contentElm.getElementsByTagName("*") this._EventBinding.on(this, all_elm) this._elms = this._EventBinding.element } 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 // } } } reset() { if (!this.viewer.entities.getById(this.options.id)) { return } this.name = this.originalOptions.name this.carRoadWidth = this.originalOptions.carRoadWidth this.sideWidth = this.originalOptions.sideWidth this.positions = this.originalOptions.positions this.roadImage = this.originalOptions.roadImage this.sideImage = this.originalOptions.sideImage } /** * 飞到对应实体 */ 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 = [] for (let i = 0; i < this.positions.length; i++) { let a = Cesium.Cartesian3.fromDegrees( this.positions[i][0], this.positions[i][1], this.options.height + this.options.heightDifference / 2 ) 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) } }) } } getSphere() { return new Promise((resolve) => { // entity没有加载完成时 state 不会等于0 所以设置定时器直到获取到为止 const interval = setInterval(() => { const sphere = new Cesium.BoundingSphere() const state = this.sdk.viewer._dataSourceDisplay.getBoundingSphere( this.viewer.entities.getById(this.options.id), false, sphere ) if (state === Cesium.BoundingSphereState.DONE) { clearInterval(interval) } }, 1000) }) } /** * 删除 */ async remove() { this.positions = [] this.lineEntity = null if (this.viewer.entities.getById(this.options.id)) { this.viewer.entities.getById(this.options.id)._children.forEach((item) => { this.viewer.entities.remove(item); }); this.viewer.entities.remove(this.viewer.entities.getById(this.options.id)) } 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 Road