import * as Cesium from 'cesium'
|
import { MapTooltip } from '@ztzf/utils'
|
import { ToolBase } from '../ToolBase'
|
import {
|
getBoundsFromLngLats,
|
getCenterFromBounds,
|
getMetersBetween,
|
buildEllipsePositions,
|
cartesianToLocal,
|
} from '../shapeUtils'
|
|
const DEFAULT_STYLE = {
|
fill: Cesium.Color.fromBytes(45, 140, 240, 99),
|
outline: Cesium.Color.fromBytes(45, 140, 240, 255),
|
}
|
const resolveStyle = style => ({
|
fill: style?.fill || DEFAULT_STYLE.fill,
|
outline: style?.outline || DEFAULT_STYLE.outline,
|
})
|
|
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)
|
}
|
|
const resolveCartographic = point => {
|
if (!point) return null
|
if (Number.isFinite(point.x) && Number.isFinite(point.y) && Number.isFinite(point.z)) {
|
return Cesium.Cartographic.fromCartesian(point)
|
}
|
const lngLat = resolveLngLat(point)
|
if (!lngLat) return null
|
return Cesium.Cartographic.fromDegrees(lngLat.lng, lngLat.lat, lngLat.height || 0)
|
}
|
|
export class EditEllipseTool extends ToolBase {
|
constructor(viewer, options = {}) {
|
super(viewer)
|
this.tooltip = new MapTooltip(viewer)
|
this.dataSource = null
|
this.centerCartesian = null
|
this.semiMajor = 0
|
this.semiMinor = 0
|
this.dragType = null
|
this.centerEntity = null
|
this.majorEntity = null
|
this.minorEntity = null
|
this.ellipseEntity = null
|
this.style = resolveStyle(options?.style)
|
}
|
|
start(data = []) {
|
const inputPoints = Array.isArray(data?.points) ? data.points : data
|
const meta = data?.meta || null
|
const metaCenter = resolveCartesian(meta?.center)
|
if (metaCenter) {
|
this.centerCartesian = metaCenter
|
const centerCarto = Cesium.Cartographic.fromCartesian(metaCenter)
|
const majorFromMeta = Number.isFinite(meta?.semiMajor) ? Number(meta.semiMajor) : 0
|
const minorFromMeta = Number.isFinite(meta?.semiMinor) ? Number(meta.semiMinor) : 0
|
const majorPoint = resolveCartographic(meta?.majorPoint)
|
const minorPoint = resolveCartographic(meta?.minorPoint)
|
const majorDistance = majorFromMeta || (majorPoint ? getMetersBetween(centerCarto, majorPoint) : 0)
|
const minorDistance = minorFromMeta || (minorPoint ? getMetersBetween(centerCarto, minorPoint) : 0)
|
if (majorDistance > 0) this.semiMajor = Math.max(1, majorDistance)
|
if (minorDistance > 0) this.semiMinor = Math.max(1, minorDistance)
|
if (this.semiMinor > this.semiMajor) this.semiMinor = this.semiMajor
|
}
|
|
if (!this.centerCartesian || !this.semiMajor || !this.semiMinor) {
|
const normalized = normalizePoints(inputPoints)
|
const bounds = getBoundsFromLngLats(normalized)
|
const center = getCenterFromBounds(bounds)
|
if (!center) return
|
this.centerCartesian = Cesium.Cartesian3.fromRadians(center.longitude, center.latitude)
|
const eastWest = getMetersBetween(
|
new Cesium.Cartographic(bounds.west, center.latitude),
|
new Cesium.Cartographic(bounds.east, center.latitude)
|
)
|
const northSouth = getMetersBetween(
|
new Cesium.Cartographic(center.longitude, bounds.south),
|
new Cesium.Cartographic(center.longitude, bounds.north)
|
)
|
this.semiMajor = Math.max(1, eastWest / 2)
|
this.semiMinor = Math.max(1, northSouth / 2)
|
}
|
|
this.dataSource = new Cesium.CustomDataSource('edit-ellipse')
|
this.viewer.dataSources.add(this.dataSource)
|
this.createEntities()
|
this.initHandler()
|
}
|
|
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 === 'ellipse-center') this.dragType = 'center'
|
if (picked.customType === 'ellipse-major') this.dragType = 'major'
|
if (picked.customType === 'ellipse-minor') this.dragType = 'minor'
|
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.updateAxisPoints()
|
return
|
}
|
|
const local = cartesianToLocal(this.centerCartesian, position)
|
if (this.dragType === 'major') {
|
this.semiMajor = Math.max(1, Math.abs(local.x))
|
if (this.semiMajor < this.semiMinor) {
|
this.semiMajor = this.semiMinor
|
}
|
}
|
if (this.dragType === 'minor') {
|
this.semiMinor = Math.max(1, Math.abs(local.y))
|
if (this.semiMinor > this.semiMajor) {
|
this.semiMinor = this.semiMajor
|
}
|
}
|
this.updateAxisPoints()
|
}
|
|
handleLeftUp() {
|
if (!this.dragType) return
|
this.dragType = null
|
this.enableMapControl()
|
const positions = buildEllipsePositions(this.centerCartesian, this.semiMajor, this.semiMinor)
|
this.notify('getPolygonPositions', {
|
positions,
|
meta: {
|
center: this.centerCartesian,
|
majorPoint: this.getAxisPoint(this.semiMajor, 0),
|
minorPoint: this.getAxisPoint(0, this.semiMinor),
|
semiMajor: this.semiMajor,
|
semiMinor: this.semiMinor,
|
},
|
})
|
}
|
|
createEntities() {
|
this.ellipseEntity = this.dataSource.entities.add({
|
position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
|
ellipse: {
|
semiMajorAxis: new Cesium.CallbackProperty(() => this.semiMajor, false),
|
semiMinorAxis: new Cesium.CallbackProperty(() => this.semiMinor, false),
|
material: this.style.fill,
|
outline: true,
|
outlineColor: this.style.outline,
|
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: 'ellipse-center',
|
})
|
|
this.majorEntity = this.dataSource.entities.add({
|
position: new Cesium.CallbackProperty(() => this.getAxisPoint(this.semiMajor, 0), false),
|
point: {
|
pixelSize: 10,
|
color: Cesium.Color.fromBytes(255, 255, 255, 200),
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
},
|
customType: 'ellipse-major',
|
})
|
|
this.minorEntity = this.dataSource.entities.add({
|
position: new Cesium.CallbackProperty(() => this.getAxisPoint(0, this.semiMinor), false),
|
point: {
|
pixelSize: 10,
|
color: Cesium.Color.fromBytes(255, 255, 255, 200),
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
},
|
customType: 'ellipse-minor',
|
})
|
}
|
|
updateAxisPoints() {
|
if (!this.majorEntity || !this.minorEntity) return
|
this.majorEntity.position = this.getAxisPoint(this.semiMajor, 0)
|
this.minorEntity.position = this.getAxisPoint(0, this.semiMinor)
|
}
|
|
getPositions() {
|
if (!this.centerCartesian) return []
|
return {
|
positions: buildEllipsePositions(this.centerCartesian, this.semiMajor, this.semiMinor),
|
meta: {
|
center: this.centerCartesian,
|
majorPoint: this.getAxisPoint(this.semiMajor, 0),
|
minorPoint: this.getAxisPoint(0, this.semiMinor),
|
semiMajor: this.semiMajor,
|
semiMinor: this.semiMinor,
|
},
|
}
|
}
|
|
getAxisPoint(eastMeters, northMeters) {
|
const frame = Cesium.Transforms.eastNorthUpToFixedFrame(this.centerCartesian)
|
const local = new Cesium.Cartesian3(eastMeters, northMeters, 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)
|
}
|
|
setStyle(style) {
|
this.style = resolveStyle(style)
|
if (this.ellipseEntity?.ellipse) {
|
this.ellipseEntity.ellipse.material = this.style.fill
|
this.ellipseEntity.ellipse.outlineColor = this.style.outline
|
}
|
}
|
|
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.semiMajor = 0
|
this.semiMinor = 0
|
this.dragType = null
|
}
|
}
|