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 = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 26" width="32" height="26">
|
|
<g fill="none" fill-rule="evenodd">
|
|
<path fill="#00D690" d="M16.8320503 24.75192456L30.9635332 3.5547002c.3063525-.4595287.1821786-1.080398-.2773501-1.3867505C30.5219156 2.058438 30.3289079 2 30.1314829 2H1.86851709c-.55228475 0-1 .4477153-1 1 0 .197425.05843803.3904327.16794971.5547002l14.1314829 21.19722436c.3063525.45952869.9272218.58370256 1.3867505.2773501.1098523-.07323486.2041152-.16749781.2773501-.2773501z"/>
|
|
<text fill="#FFF" font-size="16" font-weight="500">
|
|
<tspan x="50%" y="50%" dy=".25em" text-anchor="middle">${number}</tspan>
|
|
</text>
|
|
</g>
|
|
</svg>
|
|
`
|
|
|
|
const encoded = encodeURIComponent(svg)
|
|
.replace(/'/g, '%27')
|
|
.replace(/"/g, '%22')
|
|
|
|
return `data:image/svg+xml,${encoded}`
|
|
}
|
|
|
|
function generateHighlightedNumberSvgDataUrl(number: number): string {
|
|
const svg = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
viewBox="0 0 42 35" width="42" height="35">
|
|
<defs>
|
|
<filter id="a" width="150.8%" height="171.7%" x="-25.4%" y="-34.2%" filterUnits="objectBoundingBox">
|
|
<feMorphology in="SourceAlpha" operator="dilate" radius="1" result="shadowSpreadOuter1"/>
|
|
<feOffset in="shadowSpreadOuter1" result="shadowOffsetOuter1"/>
|
|
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="2"/>
|
|
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1"/>
|
|
<feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 0.039215686 0 0 0 0 0.933333333 0 0 0 0 0.545098039 0 0 0 1 0"/>
|
|
</filter>
|
|
<path id="b" d="M16.832 3.248l14.132 21.197A1 1 0 0130.13 26H1.87a1 1 0 01-.833-1.555L15.168 3.248a1 1 0 011.664 0z"/>
|
|
</defs>
|
|
<g fill="none" fill-rule="evenodd">
|
|
<g transform="matrix(1 0 0 -1 5 32)">
|
|
<use fill="#000" filter="url(#a)" xlink:href="#b"/>
|
|
<path fill="#00D690" stroke="#FFF" stroke-linejoin="round" stroke-width="2"
|
|
d="M15.955 3.871L30.13 25H1.87L15.955 3.871z"/>
|
|
</g>
|
|
<text fill="#FFF" font-size="14" font-weight="500">
|
|
<tspan x="50%" y="50%" dy=".1em" text-anchor="middle">${number}</tspan>
|
|
</text>
|
|
</g>
|
|
</svg>
|
|
`
|
|
|
|
const encoded = encodeURIComponent(svg)
|
|
.replace(/'/g, '%27')
|
|
.replace(/"/g, '%22')
|
|
|
|
return `data:image/svg+xml,${encoded}`
|
|
}
|