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