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.radius = 0
|
this.dragType = null
|
this.circleEntity = null
|
this.centerEntity = null
|
this.radiusEntity = null
|
}
|
|
start(points = []) {
|
const normalized = normalizePoints(points)
|
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)
|
this.radius = this.estimateRadius(normalized)
|
|
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') this.dragType = 'radius'
|
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.updateRadiusPoint()
|
return
|
}
|
|
const local = cartesianToLocal(this.centerCartesian, position)
|
this.radius = Math.max(1, Math.sqrt(local.x * local.x + local.y * local.y))
|
this.updateRadiusPoint()
|
}
|
|
handleLeftUp() {
|
if (!this.dragType) return
|
this.dragType = null
|
this.enableMapControl()
|
const positions = buildEllipsePositions(this.centerCartesian, this.radius, this.radius)
|
this.notify('getPolygonPositions', positions)
|
}
|
|
createEntities() {
|
this.circleEntity = this.dataSource.entities.add({
|
position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
|
ellipse: {
|
semiMajorAxis: new Cesium.CallbackProperty(() => this.radius, false),
|
semiMinorAxis: new Cesium.CallbackProperty(() => this.radius, false),
|
material: Cesium.Color.fromBytes(45, 140, 240, 99),
|
outline: true,
|
outlineColor: Cesium.Color.fromBytes(45, 140, 240, 255),
|
outlineWidth: 2,
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
},
|
})
|
|
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.radiusEntity = this.dataSource.entities.add({
|
position: new Cesium.CallbackProperty(() => this.getRadiusPoint(), 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',
|
})
|
}
|
|
updateRadiusPoint() {
|
if (!this.radiusEntity) return
|
this.radiusEntity.position = this.getRadiusPoint()
|
}
|
|
getPositions() {
|
if (!this.centerCartesian) return []
|
return buildEllipsePositions(this.centerCartesian, this.radius, this.radius)
|
}
|
|
getRadiusPoint() {
|
const frame = Cesium.Transforms.eastNorthUpToFixedFrame(this.centerCartesian)
|
const local = new Cesium.Cartesian3(this.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.radius = 0
|
this.dragType = null
|
}
|
}
|