| New file |
| | |
| | | import * as Cesium from 'cesium' |
| | | import { MapTooltip } from '../Tooltip' |
| | | import { ToolBase } from '../ToolBase' |
| | | import { buildEllipsePositions, cartesianToLocal } from '../shapeUtils' |
| | | |
| | | const normalizePoints = points => |
| | | (points || []).map(point => ({ |
| | | lng: point.lng ?? point.longitude, |
| | | lat: point.lat ?? point.latitude, |
| | | })) |
| | | |
| | | 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 |
| | | } |
| | | |
| | | start(data = []) { |
| | | const inputPoints = Array.isArray(data?.points) ? data.points : data |
| | | const normalized = normalizePoints(inputPoints) |
| | | 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.show('拖动控制点编辑', { x: 0, y: 0 }) |
| | | } |
| | | |
| | | 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) { |
| | | 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], |
| | | }, |
| | | }) |
| | | } |
| | | |
| | | 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', |
| | | }) |
| | | } |
| | | |
| | | 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], |
| | | }, |
| | | } |
| | | } |
| | | |
| | | getRadiusPoint(radius) { |
| | | const frame = Cesium.Transforms.eastNorthUpToFixedFrame(this.centerCartesian) |
| | | const local = new Cesium.Cartesian3(radius, 0, 0) |
| | | return Cesium.Matrix4.multiplyByPoint(frame, local, new Cesium.Cartesian3()) |
| | | } |
| | | |
| | | getPositionFromScreen(screenPosition) { |
| | | const scene = this.viewer.scene |
| | | const cartesian = scene.pickPosition(screenPosition) |
| | | 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.dragType = null |
| | | } |
| | | } |