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 = `
`
const encoded = encodeURIComponent(svg)
.replace(/'/g, '%27')
.replace(/"/g, '%22')
return `data:image/svg+xml,${encoded}`
}
function generateHighlightedNumberSvgDataUrl(number: number): string {
const svg = `
`
const encoded = encodeURIComponent(svg)
.replace(/'/g, '%27')
.replace(/"/g, '%22')
return `data:image/svg+xml,${encoded}`
}