// ViewShed.js import Event from '../../../Event' import MouseTip from '../../../MouseTip' import Tools from '../../../Tools' import EventBinding from '../../Element/Dialog/eventBinding' import Dialog from '../../../BaseDialog' import { html } from './_element' /** * @constructor * @description 可视域分析 * @param sdk * @param {Object} options 选项。 * @param {Number} options.viewPointHeight=1.8 视点高度(m)。 * @param {Number} options.precision=20 精度。 * @param {String} options.visibleAreaColor=#008000 可视区域颜色。 * @param {String} options.invisibleAreaColor=#FF0000 不可视区域颜色。 */ class CircleViewShed extends Tools { #intervalEvents = new Map() constructor(sdk, options = {}, _Dialog = {}) { super(sdk, options) this.viewer = sdk.viewer this.options = {} this.options.visibleAreaColor = options.visibleAreaColor || '#008000' this.options.invisibleAreaColor = options.invisibleAreaColor || '#FF0000' this.ids = [] this.primitives = [] this.viewpointPrimitive = null this._elms = {} this.precision = options.precision this.viewPointHeight = options.viewPointHeight this.Dialog = _Dialog this._EventBinding = new EventBinding() this.html = null YJ.Analysis.AnalysesResults.push(this) CircleViewShed.edit(this) // CircleViewShed.create(this) } get viewPointHeight() { return this.options.viewPointHeight } set viewPointHeight(v) { let viewPointHeight = Math.floor(Number(v) * 10) / 10 if (isNaN(viewPointHeight)) { viewPointHeight = 1.8 } if (viewPointHeight < 0) { viewPointHeight = 0 } this.options.viewPointHeight = viewPointHeight this._elms.viewPointHeight && this._elms.viewPointHeight.forEach(item => { item.value = viewPointHeight }) } get precision() { return this.options.precision } set precision(v) { let precision = Math.floor(Number(v)) if (isNaN(precision)) { precision = 20 } else if (precision < 1) { precision = 1 } this.options.precision = precision this._elms.precision && this._elms.precision.forEach(item => { item.value = precision }) } static create(that) { let count = 0 if (!YJ.Measure.GetMeasureStatus()) { if (that._DialogObject && that._DialogObject.close) { that._DialogObject.close() that._DialogObject = null } let Draw = new YJ.Draw.DrawCircle(that.sdk) Draw.start(async (a, options) => { // that.center = options.center if(!options) { return } that.radius = options.radius let positions = await Cesium.sampleTerrainMostDetailed( that.sdk.viewer.terrainProvider, [Cesium.Cartographic.fromDegrees(options.center.lng, options.center.lat)] ); that.center = { lng: options.center.lng, lat: options.center.lat, alt: positions[0].height } await that.analyse() }) } else { console.log('上一次测量未结束') } } static async edit(that) { if (that._DialogObject && that._DialogObject.close) { that._DialogObject.close() that._DialogObject = null } that._DialogObject = await new Dialog(that.sdk.viewer._container, { title: '圆形视域分析', left: '180px', top: '100px', closeCallBack: () => { that.Dialog.closeCallBack && that.Dialog.closeCallBack() YJ.Measure.SetMeasureStatus(false) } }) await that._DialogObject.init() that._DialogObject._element.body.className = that._DialogObject._element.body.className + ' circle-view-shed' let contentElm = document.createElement('div') contentElm.innerHTML = html() that._DialogObject.contentAppChild(contentElm) let drawElm = document.createElement('button') drawElm.innerHTML = '绘制' drawElm.addEventListener('click', () => { let terrainAvailability = that.viewer.terrainProvider.availability; if (!terrainAvailability) { window.ELEMENT && window.ELEMENT.Message({ message: '未加载地形数据!', type: 'warning', duration: 1500 }); return } CircleViewShed.create(that) }) that._DialogObject.footAppChild(drawElm) let all_elm = contentElm.getElementsByTagName('*') that._EventBinding.on(that, all_elm) that._elms = that._EventBinding.element } analyse() { // this.destroy() let center = [this.center.lng, this.center.lat] let radius = this.radius / 1000 let circle = turf.circle(center, radius, { steps: 180, units: 'kilometers', properties: { foo: 'bar' } }) if (!this.viewpointPrimitive) { this.viewpointPrimitive = this.viewer.scene.primitives.add( new Cesium.PointPrimitiveCollection() ) } if (!this.viewBillboardPrimitive) { this.viewBillboardPrimitive = this.viewer.scene.primitives.add( new Cesium.BillboardCollection() ) } let array = [] let distance = radius / this.precision for (let i = 1; i < circle.geometry.coordinates[0].length; i++) { let line = turf.lineString([center, circle.geometry.coordinates[0][i]]) let array2 = [] for (let j = 1; j <= this.precision; j++) { let sliced = turf.lineSliceAlong(line, 0, distance * j, { units: 'kilometers' }) array2.push([ sliced.geometry.coordinates[1][0], sliced.geometry.coordinates[1][1] ]) } array.push(array2) } let viewPoint = Cesium.Cartesian3.fromDegrees( this.center.lng, this.center.lat, this.center.alt + this.viewPointHeight ) let instances = [] CircleViewShed.getcanvas(this).then(canvas => this.viewBillboardPrimitive.add({ position: viewPoint, image: canvas, width: 200, height: 140, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, disableDepthTestDistance: Number.POSITIVE_INFINITY }) ) this.viewpointPrimitive.add({ position: viewPoint, color: Cesium.Color.AQUA.withAlpha(1), pixelSize: 6 }) let m = 0 let _this = this let key = this.randomString() let intervalEvent = setInterval(() => { if (m >= array.length) { let item = this.#intervalEvents.get(key) item && clearInterval(item.event) return } InBatches(m) m += 1 }, 0) this.#intervalEvents.set(key, { event: intervalEvent }) function InBatches(k) { let instances = [] let i = k for (let j = 0; j < array[i].length; j++) { let pt1 = array[i][j] let pt2 let pt3 let pt4 = array[i][j - 1] if (i == array.length - 1) { pt2 = array[0][j] pt3 = array[0][j - 1] } else { pt2 = array[i + 1][j] pt3 = array[i + 1][j - 1] } if (j == 0) { pt3 = [...center] pt4 = [] } let cpt = [(pt1[0] + pt3[0]) / 2, (pt1[1] + pt3[1]) / 2] let cartographic = Cesium.Cartographic.fromDegrees(cpt[0], cpt[1]) let height = _this.viewer.scene.globe.getHeight(cartographic) let targetPoint = Cesium.Cartesian3.fromDegrees(cpt[0], cpt[1], height) let direction = Cesium.Cartesian3.normalize( Cesium.Cartesian3.subtract( targetPoint, viewPoint, new Cesium.Cartesian3() ), new Cesium.Cartesian3() ) let ray = new Cesium.Ray(viewPoint, direction) let pickedObjects = _this.viewer.scene.drillPickFromRay( ray, _this.primitives ) let result for (let i = 0; i < pickedObjects.length; i++) { if (pickedObjects[i].position) { result = pickedObjects[i] break } } let color = Cesium.Color.LIME if ( result && Math.abs(result.position.x - targetPoint.x) > 0.01 && Math.abs(result.position.y - targetPoint.y) > 0.01 && Math.abs(result.position.z - targetPoint.z) > 0.01 ) { color = Cesium.Color.RED } let polyline = new Cesium.GroundPolylineGeometry({ positions: Cesium.Cartesian3.fromDegreesArray([ ...pt1, ...pt2, ...pt3, ...pt4, ...pt1 ]), width: 2 }) let polygonInstance = new Cesium.GeometryInstance({ geometry: polyline, name: 'ViewershedPolygon', attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor(color), show: new Cesium.ShowGeometryInstanceAttribute(true) //显示或者隐藏 } }) instances.push(polygonInstance) } _this.primitives.push( _this.viewer.scene.primitives.add( new Cesium.GroundPolylinePrimitive({ geometryInstances: instances, appearance: new Cesium.PolylineColorAppearance() }) ) ) } } static getcanvas(that) { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') canvas.width = 220 canvas.height = 140 canvas.style.background = '#000000' let img = new Image() const data = [ { images: that.getSourceRootPath() + '/img/bubble/lng.png', text: '经度:' + parseFloat(that.center.lng.toFixed(10)) + '°' }, { images: that.getSourceRootPath() + '/img/bubble/lat.png', text: '纬度:' + parseFloat(that.center.lat.toFixed(10)) + '°' }, { images: that.getSourceRootPath() + '/img/bubble/h.png', text: '视高:' + that.viewPointHeight + ' m' }, { images: that.getSourceRootPath() + '/img/bubble/radius.png', text: '半径:' + that.radius + ' m' } ] img.src = that.getSourceRootPath() + '/img/bubble/bubble.png' let imagesLoaded = 0 return new Promise(async (resolve, reject) => { img.onload = () => { ctx.drawImage(img, 0, 0, canvas.width, canvas.height) data.forEach((item, index) => { const img = new Image() img.src = item.images img.onload = () => { ctx.drawImage(img, 12, 12 + index * 26) ctx.fillStyle = '#fff' ctx.font = '12px Arial' ctx.fillText(item.text, 44, 28 + index * 26) imagesLoaded++ if (imagesLoaded === data.length) { resolve(canvas) } } }) } }) } destroy() { for (const [key, value] of this.#intervalEvents) { clearInterval(value.event) } this.#intervalEvents = new Map() for (let i = 0; i < this.primitives.length; i++) { this.viewer.scene.primitives.remove(this.primitives[i]) } this.primitives = [] if (this.viewpointPrimitive) { this.viewer.scene.primitives.remove(this.viewpointPrimitive) this.viewpointPrimitive = null } if (this.viewBillboardPrimitive) { this.viewer.scene.primitives.remove(this.viewBillboardPrimitive) this.viewBillboardPrimitive = null } YJ.Measure.SetMeasureStatus(false) } } export default CircleViewShed