import { Cartesian3, Cartographic, Camera, Color, CallbackProperty, HeightReference, LabelStyle, VerticalOrigin, PerspectiveFrustum, DebugCameraPrimitive, Entity, Cartesian2, Math as cesiumMath } from 'cesium' import { Wayline } from './wayline' import { viewer } from './basemap' import * as egm96 from 'egm96-universal' export class WayPoint { position: Cartesian3 wayline: Wayline index: number asl: number alt: number fov: number selected!: boolean heightLabel!: Entity wayPointEntity!: Entity orientation: { heading: number; pitch: number; roll: number } debugCameraPrimitive: any distanceLabel1: any distanceLabel2: any constructor(position: Cartesian3, wayline: Wayline, index: number) { this.position = position this.wayline = wayline this.index = index const cartographic = Cartographic.fromCartesian(position) this.alt = cartographic.height const height = egm96.meanSeaLevel(cesiumMath.toDegrees(cartographic.latitude), cesiumMath.toDegrees(cartographic.longitude)) this.asl = this.alt - height this.createWaypointEntity() const minimapViewer = this.wayline.minimapViewer this.fov = minimapViewer.camera.frustum.fov this.orientation = { heading: minimapViewer.camera.heading, pitch: minimapViewer.camera.pitch, roll: minimapViewer.camera.roll } } private createWaypointEntity(): void { const { position, index } = this const url = generateNumberSvgDataUrl(index + 1) this.wayPointEntity = viewer.entities.add({ position, wayPoint: this, point: { pixelSize: 10, color: Color.RED, heightReference: HeightReference.NONE }, polyline: { positions: new CallbackProperty(() => { const position = this.position const cartographic = Cartographic.fromCartesian(position) const bottom = Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0) return [position, bottom] }, false), width: 2, material: Color.WHITE }, billboard: { image: url, scale: 1.0, verticalOrigin: VerticalOrigin.BOTTOM, disableDepthTestDistance: Number.POSITIVE_INFINITY } }) } updatePosition(position: Cartesian3): void { const pointList = this.wayline.pointList this.position = position this.wayPointEntity.position = position this.heightLabel.position = position this.debugCameraPrimitive._camera.position = position const text = `ASL: ${this.asl.toFixed(1)} m\nHAE: ${this.alt.toFixed(1)} m` this.heightLabel.label.text = text const index = this.index const prev = pointList[index - 1] const next = pointList[index + 1] if (prev) { const distance = Cartesian3.distance( prev.position, position ) const midpoint = Cartesian3.lerp(prev.position, position, 0.5, new Cartesian3()) this.distanceLabel1.position = midpoint this.distanceLabel1.label.text = distance.toFixed(1) + 'm' } if (next) { const distance = Cartesian3.distance( position, next.position ) const midpoint = Cartesian3.lerp(position, next.position, 0.5, new Cartesian3()) this.distanceLabel2.position = midpoint this.distanceLabel2.label.text = distance.toFixed(1) + 'm' } } select(): void { this.selected = true this.wayPointEntity.billboard.image = generateHighlightedNumberSvgDataUrl(this.index + 1) this.showDistanceLabels() this.showHeightLabel() const camera = new Camera(viewer.scene) camera.frustum = new PerspectiveFrustum({ fov: this.fov, aspectRatio: 4 / 3, near: 1, far: 100 }) camera.setView({ destination: this.position, orientation: this.orientation }) const minimapViewer = this.wayline.minimapViewer minimapViewer.camera.setView({ destination: this.position, orientation: this.orientation }) minimapViewer.camera.frustum.fov = this.fov this.debugCameraPrimitive = new DebugCameraPrimitive({ camera, color: Color.YELLOW }) viewer.scene.primitives.add(this.debugCameraPrimitive) } unselect(): void { this.selected = false this.wayPointEntity.billboard.image = generateNumberSvgDataUrl(this.index + 1) viewer.entities.remove(this.distanceLabel0) viewer.entities.remove(this.distanceLabel1) viewer.entities.remove(this.distanceLabel2) viewer.entities.remove(this.heightLabel) viewer.scene.primitives.remove(this.debugCameraPrimitive) } distanceLabel0(distanceLabel0: any) { throw new Error('Method not implemented.') } delete(): void { const pointList = this.wayline.pointList viewer.entities.remove(this.wayPointEntity) viewer.scene.primitives.remove(this.debugCameraPrimitive) pointList.splice(this.index, 1) for (let i = this.index; i < pointList.length; i++) { pointList[i].wayPointEntity.billboard.image = generateNumberSvgDataUrl(i + 1) pointList[i].index = i } } private showHeightLabel(): void { const { position, alt, asl } = this const text = `ASL: ${asl.toFixed(1)} m\nHAE: ${alt.toFixed(1)} m` this.heightLabel = viewer.entities.add({ position, label: { text, font: "14px sans-serif", fillColor: Color.WHITE, outlineColor: Color.BLACK, outlineWidth: 2, style: LabelStyle.FILL_AND_OUTLINE, verticalOrigin: VerticalOrigin.BOTTOM, heightReference: HeightReference.NONE, showBackground: true, backgroundColor: Color.BLACK.withAlpha(0.5), pixelOffset: new Cartesian2(0, -30) } }) } private showDistanceLabels(): void { const index = this.index const pointList = this.wayline.pointList const current = pointList[index] const prev = pointList[index - 1] const next = pointList[index + 1] if (prev) { this.distanceLabel1 = this.createMidpointLabel(prev.position, current.position) } else { this.distanceLabel0 = this.createMidpointLabel(this.wayline.takeOffPoint, this.wayline.top) this.distanceLabel1 = this.createMidpointLabel(this.wayline.top, current.position) } if (next) { this.distanceLabel2 = this.createMidpointLabel(current.position, next.position) } } private createMidpointLabel(point1: Cartesian3, point2: Cartesian3): Entity { const midpoint = Cartesian3.lerp(point1, point2, 0.5, new Cartesian3()) const distance = Cartesian3.distance( point1, point2 ) return viewer.entities.add({ position: midpoint, label: { text: `${distance.toFixed(1)}m`, font: "14px sans-serif", fillColor: Color.WHITE, outlineColor: Color.BLACK, outlineWidth: 2, style: LabelStyle.FILL_AND_OUTLINE, verticalOrigin: VerticalOrigin.BOTTOM, heightReference: HeightReference.NONE, showBackground: true, backgroundColor: Color.BLACK.withAlpha(0.5), disableDepthTestDistance: Number.POSITIVE_INFINITY, eyeOffset: new Cartesian3(0.0, 0.0, -10.0), pixelOffset: new Cartesian2(0, -10) } }) } destroy(): void { viewer.entities.remove(this.wayPointEntity) } } function generateNumberSvgDataUrl(number: number): string { const svg = ` ${number} ` const encoded = encodeURIComponent(svg) .replace(/'/g, '%27') .replace(/"/g, '%22') return `data:image/svg+xml,${encoded}` } function generateHighlightedNumberSvgDataUrl(number: number): string { const svg = ` ${number} ` const encoded = encodeURIComponent(svg) .replace(/'/g, '%27') .replace(/"/g, '%22') return `data:image/svg+xml,${encoded}` }