import * as Cesium from 'cesium' import { MapTooltip } from '@ztzf/utils' import { ToolBase } from '../ToolBase' import { buildEllipsePositions, cartesianToLocal, formatBufferRadius, getBufferRadiusLabelStyle, getBufferRadiusPoint, } from '../shapeUtils' const normalizePoints = points => (points || []).map(point => ({ lng: point.lng ?? point.longitude, lat: point.lat ?? point.latitude, })) const resolveLngLat = point => { if (!point) return null const lng = point.lng ?? point.longitude const lat = point.lat ?? point.latitude if (Number.isFinite(lng) && Number.isFinite(lat)) { return { lng: Number(lng), lat: Number(lat), height: Number(point.height) || 0 } } return null } const resolveCartesian = point => { if (!point) return null if (Number.isFinite(point.x) && Number.isFinite(point.y) && Number.isFinite(point.z)) { return new Cesium.Cartesian3(point.x, point.y, point.z) } const lngLat = resolveLngLat(point) if (!lngLat) return null return Cesium.Cartesian3.fromDegrees(lngLat.lng, lngLat.lat, lngLat.height || 0) } export class EditBufferCircleTool extends ToolBase { constructor(viewer) { super(viewer) this.tooltip = new MapTooltip(viewer) this.dataSource = null this.centerCartesian = null this.radiusLevel1 = 0 this.radiusLevel2 = 0 this.radiusLevel3 = 0 this.dragType = null this.circleLevel1Entity = null this.circleLevel2Entity = null this.circleLevel3Entity = null this.circleLevel1OutlineEntity = null this.circleLevel2OutlineEntity = null this.circleLevel3OutlineEntity = null this.centerEntity = null this.radiusLevel1Entity = null this.radiusLevel2Entity = null this.radiusLevel3Entity = null this.radiusLevel1LabelEntity = null this.radiusLevel2LabelEntity = null this.radiusLevel3LabelEntity = null } start(data = []) { const inputPoints = Array.isArray(data?.points) ? data.points : data const normalized = normalizePoints(inputPoints) const metaCenter = resolveCartesian(data?.meta?.center) if (metaCenter) { this.centerCartesian = metaCenter } else { if (!normalized.length) return const centerLng = normalized.reduce((sum, p) => sum + p.lng, 0) / normalized.length const centerLat = normalized.reduce((sum, p) => sum + p.lat, 0) / normalized.length this.centerCartesian = Cesium.Cartesian3.fromDegrees(centerLng, centerLat) } const radii = Array.isArray(data?.meta?.bufferRadii) ? data.meta.bufferRadii : null if (radii?.length) { const [r1, r2, r3] = radii this.radiusLevel1 = Number.isFinite(r1) && r1 > 0 ? r1 : 1 this.radiusLevel2 = Number.isFinite(r2) && r2 > 0 ? Math.max(r2, this.radiusLevel1) : this.radiusLevel1 this.radiusLevel3 = Number.isFinite(r3) && r3 > 0 ? Math.max(r3, this.radiusLevel2) : this.radiusLevel2 } else { this.radiusLevel3 = this.estimateRadius(normalized) this.radiusLevel2 = this.radiusLevel3 this.radiusLevel1 = this.radiusLevel3 } this.dataSource = new Cesium.CustomDataSource('edit-buffer-circle') this.viewer.dataSources.add(this.dataSource) this.createEntities() this.initHandler() } estimateRadius(points) { const center = Cesium.Cartesian3.clone(this.centerCartesian) let max = 1 points.forEach(point => { const cartesian = Cesium.Cartesian3.fromDegrees(point.lng, point.lat) const distance = Cesium.Cartesian3.distance(center, cartesian) max = Math.max(max, distance) }) return max } initHandler() { this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas) this.handler.setInputAction(evt => this.handleLeftDown(evt), Cesium.ScreenSpaceEventType.LEFT_DOWN) this.handler.setInputAction(evt => this.handleMouseMove(evt), Cesium.ScreenSpaceEventType.MOUSE_MOVE) this.handler.setInputAction(() => this.handleLeftUp(), Cesium.ScreenSpaceEventType.LEFT_UP) this.tooltip.hide() } handleLeftDown(evt) { const picked = this.viewer.scene.pick(evt.position)?.id if (!picked) return if (picked.customType === 'circle-center') this.dragType = 'center' if (picked.customType === 'circle-radius-1') this.dragType = 'radius-1' if (picked.customType === 'circle-radius-2') this.dragType = 'radius-2' if (picked.customType === 'circle-radius-3') this.dragType = 'radius-3' if (this.dragType) this.disableMapControl() } handleMouseMove(evt) { if (!this.tooltip?.isVisible) { this.tooltip.show('拖动控制点编辑', evt.endPosition) } else { this.tooltip.show('拖动控制点编辑') this.tooltip.move(evt.endPosition) } if (!this.dragType) return const position = this.getPositionFromScreen(evt.endPosition) if (!position) return if (this.dragType === 'center') { this.centerCartesian = position this.updateRadiusPoints() return } const local = cartesianToLocal(this.centerCartesian, position) const radius = Math.max(1, Math.sqrt(local.x * local.x + local.y * local.y)) if (this.dragType === 'radius-1') { this.radiusLevel1 = radius if (this.radiusLevel2 < this.radiusLevel1) this.radiusLevel2 = this.radiusLevel1 if (this.radiusLevel3 < this.radiusLevel2) this.radiusLevel3 = this.radiusLevel2 } if (this.dragType === 'radius-2') { this.radiusLevel2 = Math.max(radius, this.radiusLevel1) if (this.radiusLevel3 < this.radiusLevel2) this.radiusLevel3 = this.radiusLevel2 } if (this.dragType === 'radius-3') { this.radiusLevel3 = Math.max(radius, this.radiusLevel2) } this.updateRadiusPoints() } handleLeftUp() { if (!this.dragType) return this.dragType = null this.enableMapControl() const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3) this.notify('getPolygonPositions', { positions, meta: { center: this.centerCartesian, bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3], controlPoints: [ this.centerCartesian, this.getRadiusPoint(this.radiusLevel1), this.getRadiusPoint(this.radiusLevel2), this.getRadiusPoint(this.radiusLevel3), ], }, }) } createEntities() { this.circleLevel1Entity = this.dataSource.entities.add({ position: new Cesium.CallbackProperty(() => this.centerCartesian, false), ellipse: { semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel1, false), semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel1, false), material: Cesium.Color.fromBytes(247, 20, 20, 128), outline: false, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, }, }) this.circleLevel1OutlineEntity = this.dataSource.entities.add({ polyline: { positions: new Cesium.CallbackProperty(() => { if (!this.centerCartesian || this.radiusLevel1 <= 0) return [] const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel1, this.radiusLevel1) return positions.length ? [...positions, positions[0]] : positions }, false), clampToGround: true, width: 2, material: Cesium.Color.fromBytes(255, 0, 0, 255), }, }) this.circleLevel2Entity = this.dataSource.entities.add({ position: new Cesium.CallbackProperty(() => this.centerCartesian, false), ellipse: { semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel2, false), semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel2, false), material: Cesium.Color.fromBytes(255, 235, 59, 128), outline: false, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, }, }) this.circleLevel2OutlineEntity = this.dataSource.entities.add({ polyline: { positions: new Cesium.CallbackProperty(() => { if (!this.centerCartesian || this.radiusLevel2 <= 0) return [] const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel2, this.radiusLevel2) return positions.length ? [...positions, positions[0]] : positions }, false), clampToGround: true, width: 2, material: Cesium.Color.fromBytes(255, 235, 59, 255), }, }) this.circleLevel3Entity = this.dataSource.entities.add({ position: new Cesium.CallbackProperty(() => this.centerCartesian, false), ellipse: { semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel3, false), semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel3, false), material: Cesium.Color.fromBytes(25, 178, 230, 128), outline: false, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, }, }) this.circleLevel3OutlineEntity = this.dataSource.entities.add({ polyline: { positions: new Cesium.CallbackProperty(() => { if (!this.centerCartesian || this.radiusLevel3 <= 0) return [] const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3) return positions.length ? [...positions, positions[0]] : positions }, false), clampToGround: true, width: 2, material: Cesium.Color.fromBytes(0, 251, 255, 255), }, }) this.centerEntity = this.dataSource.entities.add({ position: new Cesium.CallbackProperty(() => this.centerCartesian, false), point: { pixelSize: 12, color: Cesium.Color.WHITE, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, disableDepthTestDistance: Number.POSITIVE_INFINITY, }, customType: 'circle-center', }) this.radiusLevel1Entity = this.dataSource.entities.add({ position: new Cesium.CallbackProperty(() => this.getRadiusPoint(this.radiusLevel1), false), point: { pixelSize: 10, color: Cesium.Color.fromBytes(255, 255, 255, 200), heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, disableDepthTestDistance: Number.POSITIVE_INFINITY, }, customType: 'circle-radius-1', }) this.radiusLevel2Entity = this.dataSource.entities.add({ position: new Cesium.CallbackProperty(() => this.getRadiusPoint(this.radiusLevel2), false), point: { pixelSize: 10, color: Cesium.Color.fromBytes(255, 255, 255, 200), heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, disableDepthTestDistance: Number.POSITIVE_INFINITY, }, customType: 'circle-radius-2', }) this.radiusLevel3Entity = this.dataSource.entities.add({ position: new Cesium.CallbackProperty(() => this.getRadiusPoint(this.radiusLevel3), false), point: { pixelSize: 10, color: Cesium.Color.fromBytes(255, 255, 255, 200), heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, disableDepthTestDistance: Number.POSITIVE_INFINITY, }, customType: 'circle-radius-3', }) this.radiusLevel1LabelEntity = this.createRadiusLabelEntity(() => this.radiusLevel1) this.radiusLevel2LabelEntity = this.createRadiusLabelEntity(() => this.radiusLevel2) this.radiusLevel3LabelEntity = this.createRadiusLabelEntity(() => this.radiusLevel3) } updateRadiusPoints() { if (this.radiusLevel1Entity) this.radiusLevel1Entity.position = this.getRadiusPoint(this.radiusLevel1) if (this.radiusLevel2Entity) this.radiusLevel2Entity.position = this.getRadiusPoint(this.radiusLevel2) if (this.radiusLevel3Entity) this.radiusLevel3Entity.position = this.getRadiusPoint(this.radiusLevel3) } getPositions() { if (!this.centerCartesian) return [] return { positions: buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3), meta: { center: this.centerCartesian, bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3], controlPoints: [ this.centerCartesian, this.getRadiusPoint(this.radiusLevel1), this.getRadiusPoint(this.radiusLevel2), this.getRadiusPoint(this.radiusLevel3), ], }, } } getRadiusPoint(radius) { return getBufferRadiusPoint(this.centerCartesian, radius) } createRadiusLabelEntity(getRadius) { return this.dataSource.entities.add({ position: new Cesium.CallbackProperty( () => getBufferRadiusPoint(this.centerCartesian, getRadius()), false ), label: { ...getBufferRadiusLabelStyle(), text: new Cesium.CallbackProperty(() => formatBufferRadius(getRadius()), false), }, }) } getPositionFromScreen(screenPosition) { const scene = this.viewer.scene const ray = scene.camera.getPickRay(screenPosition) const cartesian = ray ? scene.globe.pick(ray, scene) : null if (cartesian) return cartesian return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid) } disableMapControl() { const controller = this.viewer.scene.screenSpaceCameraController controller.enableRotate = false controller.enableTranslate = false controller.enableZoom = false } enableMapControl() { const controller = this.viewer.scene.screenSpaceCameraController controller.enableRotate = true controller.enableTranslate = true controller.enableZoom = true } destroy() { if (this.dataSource) { this.dataSource.entities.removeAll() this.viewer.dataSources.remove(this.dataSource) this.dataSource = null } if (this.handler) { this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN) this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE) this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP) this.handler.destroy() this.handler = null } this.tooltip?.destroy() this.tooltip = null this.centerCartesian = null this.radiusLevel1 = 0 this.radiusLevel2 = 0 this.radiusLevel3 = 0 this.circleLevel1OutlineEntity = null this.circleLevel2OutlineEntity = null this.circleLevel3OutlineEntity = null this.radiusLevel1LabelEntity = null this.radiusLevel2LabelEntity = null this.radiusLevel3LabelEntity = null this.dragType = null } }