You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

312 lines
11 KiB

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}`
}