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 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.radiusLevel1LabelEntity = null
|
this.radiusLevel2LabelEntity = null
|
this.radiusLevel3LabelEntity = null
|
this.dragType = null
|
}
|
}
|