aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/MapBox/AnimationUtility.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/MapBox/AnimationUtility.ts')
-rw-r--r--src/client/views/nodes/MapBox/AnimationUtility.ts508
1 files changed, 223 insertions, 285 deletions
diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts
index 11b335a96..a5cff4668 100644
--- a/src/client/views/nodes/MapBox/AnimationUtility.ts
+++ b/src/client/views/nodes/MapBox/AnimationUtility.ts
@@ -1,12 +1,13 @@
-import mapboxgl from "mapbox-gl";
-import { MercatorCoordinate } from "mapbox-gl";
-import { MapRef } from "react-map-gl";
+import mapboxgl from 'mapbox-gl';
+import { MercatorCoordinate } from 'mapbox-gl';
+import { MapRef } from 'react-map-gl';
import * as React from 'react';
-import * as d3 from "d3";
+import * as d3 from 'd3';
import * as turf from '@turf/turf';
-import { Position } from "@turf/turf";
-import { Feature, GeoJsonProperties, Geometry } from "geojson";
-import { action, computed, observable, runInAction } from "mobx";
+import { Position } from '@turf/turf';
+import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
+import { observer } from 'mobx-react';
+import { action, computed, observable, runInAction } from 'mobx';
export enum AnimationStatus {
START = 'start',
@@ -21,15 +22,15 @@ export enum AnimationSpeed {
}
export class AnimationUtility {
- private SMOOTH_FACTOR = 0.95
- private ROUTE_COORDINATES: Position[] = [];
+ private SMOOTH_FACTOR = 0.95;
+ private ROUTE_COORDINATES: Position[] = [];
@observable
private PATH: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>;
-
+
private PATH_DISTANCE: number;
private FLY_IN_START_PITCH = 40;
- private FIRST_LNG_LAT: {lng: number, lat: number};
+ private FIRST_LNG_LAT: { lng: number; lat: number };
private START_ALTITUDE = 3_000_000;
private MAP_REF: MapRef | null;
@@ -37,27 +38,27 @@ export class AnimationUtility {
@observable private animationSpeed: AnimationSpeed;
@observable
- private previousLngLat: {lng: number, lat: number};
+ private previousLngLat: { lng: number; lat: number };
private previousAltitude: number | null = null;
private previousPitch: number | null = null;
-
+
private currentStreetViewBearing: number = 0;
private terrainDisplayed: boolean;
@computed get flyInEndBearing() {
- return this.isStreetViewAnimation ?
- this.calculateBearing(
- {
- lng: this.ROUTE_COORDINATES[0][0],
- lat: this.ROUTE_COORDINATES[0][1]
- },
- {
- lng: this.ROUTE_COORDINATES[1][0],
- lat: this.ROUTE_COORDINATES[1][1]
- }
- )
+ return this.isStreetViewAnimation
+ ? this.calculateBearing(
+ {
+ lng: this.ROUTE_COORDINATES[0][0],
+ lat: this.ROUTE_COORDINATES[0][1],
+ },
+ {
+ lng: this.ROUTE_COORDINATES[1][0],
+ lat: this.ROUTE_COORDINATES[1][1],
+ }
+ )
: -20;
}
@@ -67,15 +68,14 @@ export class AnimationUtility {
const coords: mapboxgl.LngLatLike = [this.previousLngLat.lng, this.previousLngLat.lat];
// console.log('MAP REF: ', this.MAP_REF)
// console.log("current elevation: ", this.MAP_REF?.queryTerrainElevation(coords));
- let altitude = (this.MAP_REF ? (this.MAP_REF.queryTerrainElevation(coords) ?? 0) : 0);
- if (altitude === 0){
- altitude+=50;
+ let altitude = this.MAP_REF ? this.MAP_REF.queryTerrainElevation(coords) ?? 0 : 0;
+ if (altitude === 0) {
+ altitude += 50;
}
- if (this.previousAltitude){
+ if (this.previousAltitude) {
return this.lerp(altitude, this.previousAltitude, 0.02);
}
return altitude;
-
}
@computed get flyInStartBearing() {
@@ -96,16 +96,15 @@ export class AnimationUtility {
const horizontalDistance = 500;
let pitch;
- if (heightAboveGround >= 0){
- pitch = (90- Math.atan(heightAboveGround/horizontalDistance) * (180/Math.PI));
- }
- else {
+ if (heightAboveGround >= 0) {
+ pitch = 90 - Math.atan(heightAboveGround / horizontalDistance) * (180 / Math.PI);
+ } else {
pitch = 80;
}
console.log(Math.max(50, Math.min(pitch, 85)));
- if (this.previousPitch){
+ if (this.previousPitch) {
return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02);
}
return Math.max(50, Math.min(pitch, 85));
@@ -138,7 +137,7 @@ export class AnimationUtility {
const normalizedDistance = Math.min(1, (this.PATH_DISTANCE - MIN_DISTANCE) / (MAX_DISTANCE - MIN_DISTANCE));
const easedDistance = d3.easeExpOut(Math.min(normalizedDistance, 1));
- switch (this.animationSpeed){
+ switch (this.animationSpeed) {
case AnimationSpeed.SLOW:
scalingFactor = 250;
break;
@@ -148,13 +147,13 @@ export class AnimationUtility {
case AnimationSpeed.FAST:
scalingFactor = 85;
break;
- default:
+ default:
scalingFactor = 150;
break;
}
- const duration = Math.min(MAX_DURATION, (easedDistance * MAX_DISTANCE) * (this.isStreetViewAnimation ? scalingFactor*1.12 : scalingFactor));
-
+ const duration = Math.min(MAX_DURATION, easedDistance * MAX_DISTANCE * (this.isStreetViewAnimation ? scalingFactor * 1.12 : scalingFactor));
+
return duration;
}
@@ -163,26 +162,18 @@ export class AnimationUtility {
// calculate new flyToDuration and animationDuration
this.animationSpeed = speed;
}
-
+
@action
public updateIsStreetViewAnimation(isStreetViewAnimation: boolean) {
this.isStreetViewAnimation = isStreetViewAnimation;
}
- @action
+ @action
public setPath = (path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>) => {
this.PATH = path;
- }
+ };
-
- constructor(
- firstLngLat: {lng: number, lat: number},
- routeCoordinates: Position[],
- isStreetViewAnimation: boolean,
- animationSpeed: AnimationSpeed,
- terrainDisplayed: boolean,
- mapRef: MapRef | null
- ) {
+ constructor(firstLngLat: { lng: number; lat: number }, routeCoordinates: Position[], isStreetViewAnimation: boolean, animationSpeed: AnimationSpeed, terrainDisplayed: boolean, mapRef: MapRef | null) {
this.FIRST_LNG_LAT = firstLngLat;
this.previousLngLat = firstLngLat;
this.isStreetViewAnimation = isStreetViewAnimation;
@@ -196,11 +187,11 @@ export class AnimationUtility {
const bearing = this.calculateBearing(
{
lng: routeCoordinates[0][0],
- lat: routeCoordinates[0][1]
+ lat: routeCoordinates[0][1],
},
{
lng: routeCoordinates[1][0],
- lat: routeCoordinates[1][1]
+ lat: routeCoordinates[1][1],
}
);
this.currentStreetViewBearing = bearing;
@@ -216,295 +207,242 @@ export class AnimationUtility {
currentAnimationPhase,
updateAnimationPhase,
updateFrameId,
- }: {
- map: MapRef,
+ }: {
+ map: MapRef;
// path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties>,
// startBearing: number,
// startAltitude: number,
// pitch: number,
- currentAnimationPhase: number,
- updateAnimationPhase: (
- newAnimationPhase: number,
- ) => void,
+ currentAnimationPhase: number;
+ updateAnimationPhase: (newAnimationPhase: number) => void;
updateFrameId: (newFrameId: number) => void;
+ }) => {
+ return new Promise<void>(async resolve => {
+ let startTime: number | null = null;
+
+ const frame = async (currentTime: number) => {
+ if (!startTime) startTime = currentTime;
+ const elapsedSinceLastFrame = currentTime - startTime;
+ const phaseIncrement = elapsedSinceLastFrame / this.animationDuration;
+ const animationPhase = currentAnimationPhase + phaseIncrement;
+
+ // when the duration is complete, resolve the promise and stop iterating
+ if (animationPhase > 1) {
+ resolve();
+ return;
+ }
- }) => {
- return new Promise<void>(async (resolve) => {
- let startTime: number | null = null;
-
- const frame = async (currentTime: number) => {
- if (!startTime) startTime = currentTime;
- const elapsedSinceLastFrame = currentTime - startTime;
- const phaseIncrement = elapsedSinceLastFrame / this.animationDuration;
- const animationPhase = currentAnimationPhase + phaseIncrement;
-
- // when the duration is complete, resolve the promise and stop iterating
- if (animationPhase > 1) {
- resolve();
- return;
- }
+ // calculate the distance along the path based on the animationPhase
+ const alongPath = turf.along(this.PATH, this.PATH_DISTANCE * animationPhase).geometry.coordinates;
+
+ const lngLat = {
+ lng: alongPath[0],
+ lat: alongPath[1],
+ };
+
+ let bearing: number;
+ if (this.isStreetViewAnimation) {
+ bearing = this.lerp(this.currentStreetViewBearing, this.calculateBearing(this.previousLngLat, lngLat), 0.032);
+ this.currentStreetViewBearing = bearing;
+ // bearing = this.calculateBearing(this.previousLngLat, lngLat); // TODO: Calculate actual bearing
+ } else {
+ // slowly rotate the map at a constant rate
+ bearing = this.flyInEndBearing - animationPhase * 200.0;
+ // bearing = startBearing - animationPhase * 200.0;
+ }
+
+ runInAction(() => {
+ this.previousLngLat = lngLat;
+ });
+
+ updateAnimationPhase(animationPhase);
-
- // calculate the distance along the path based on the animationPhase
- const alongPath = turf.along(this.PATH, this.PATH_DISTANCE * animationPhase).geometry
- .coordinates;
-
- const lngLat = {
- lng: alongPath[0],
- lat: alongPath[1],
+ // compute corrected camera ground position, so that he leading edge of the path is in view
+ var correctedPosition = this.computeCameraPosition(
+ this.isStreetViewAnimation,
+ this.currentPitch,
+ bearing,
+ lngLat,
+ this.currentAnimationAltitude,
+ true // smooth
+ );
+
+ // set the pitch and bearing of the camera
+ const camera = map.getFreeCameraOptions();
+ camera.setPitchBearing(this.currentPitch, bearing);
+
+ // set the position and altitude of the camera
+ camera.position = MercatorCoordinate.fromLngLat(correctedPosition, this.currentAnimationAltitude);
+
+ // apply the new camera options
+ map.setFreeCameraOptions(camera);
+
+ this.previousAltitude = this.currentAnimationAltitude;
+ this.previousPitch = this.previousPitch;
+
+ // repeat!
+ const innerFrameId = await window.requestAnimationFrame(frame);
+ updateFrameId(innerFrameId);
};
-
- let bearing: number;
- if (this.isStreetViewAnimation){
- bearing = this.lerp(
- this.currentStreetViewBearing,
- this.calculateBearing(this.previousLngLat, lngLat),
- 0.032
- );
- this.currentStreetViewBearing = bearing;
- // bearing = this.calculateBearing(this.previousLngLat, lngLat); // TODO: Calculate actual bearing
- } else {
- // slowly rotate the map at a constant rate
- bearing = this.flyInEndBearing - animationPhase * 200.0;
- // bearing = startBearing - animationPhase * 200.0;
- }
- runInAction(() => {
- this.previousLngLat = lngLat;
- })
-
- updateAnimationPhase(animationPhase);
-
- // compute corrected camera ground position, so that he leading edge of the path is in view
- var correctedPosition = this.computeCameraPosition(
- this.isStreetViewAnimation,
- this.currentPitch,
- bearing,
- lngLat,
- this.currentAnimationAltitude,
- true // smooth
- );
-
- // set the pitch and bearing of the camera
- const camera = map.getFreeCameraOptions();
- camera.setPitchBearing(this.currentPitch, bearing);
-
-
- // set the position and altitude of the camera
- camera.position = MercatorCoordinate.fromLngLat(
- correctedPosition,
- this.currentAnimationAltitude
- );
-
-
- // apply the new camera options
- map.setFreeCameraOptions(camera);
-
- this.previousAltitude = this.currentAnimationAltitude;
- this.previousPitch = this.previousPitch;
-
- // repeat!
- const innerFrameId = await window.requestAnimationFrame(frame);
- updateFrameId(innerFrameId);
- };
-
- const outerFrameId = await window.requestAnimationFrame(frame);
- updateFrameId(outerFrameId);
+ const outerFrameId = await window.requestAnimationFrame(frame);
+ updateFrameId(outerFrameId);
});
- };
+ };
- public flyInAndRotate = async ({
- map,
- updateFrameId
- }:
- {
- map: MapRef,
- updateFrameId: (newFrameId: number) => void
- }
- ) => {
- return new Promise<{bearing: number, altitude: number}>(async (resolve) => {
- let start: number | null;
-
- var currentAltitude;
- var currentBearing;
- var currentPitch;
-
- // the animation frame will run as many times as necessary until the duration has been reached
- const frame = async (time: number) => {
- if (!start) {
- start = time;
- }
-
- // otherwise, use the current time to determine how far along in the duration we are
- let animationPhase = (time - start) / this.flyToDuration;
-
- // because the phase calculation is imprecise, the final zoom can vary
- // if it ended up greater than 1, set it to 1 so that we get the exact endAltitude that was requested
- if (animationPhase > 1) {
- animationPhase = 1;
- }
-
- currentAltitude = this.START_ALTITUDE + (this.flyInEndAltitude - this.START_ALTITUDE) * d3.easeCubicOut(animationPhase)
- // rotate the camera between startBearing and endBearing
- currentBearing = this.flyInStartBearing + (this.flyInEndBearing - this.flyInStartBearing) * d3.easeCubicOut(animationPhase)
-
- currentPitch = this.FLY_IN_START_PITCH + (this.flyInEndPitch - this.FLY_IN_START_PITCH) * d3.easeCubicOut(animationPhase)
-
- // compute corrected camera ground position, so the start of the path is always in view
- var correctedPosition = this.computeCameraPosition(
- false,
- currentPitch,
- currentBearing,
- this.FIRST_LNG_LAT,
- currentAltitude
- );
-
- // set the pitch and bearing of the camera
- const camera = map.getFreeCameraOptions();
- camera.setPitchBearing(currentPitch, currentBearing);
-
- // set the position and altitude of the camera
- camera.position = MercatorCoordinate.fromLngLat(
- correctedPosition,
- currentAltitude
- );
-
- // apply the new camera options
- map.setFreeCameraOptions(camera);
-
- // when the animationPhase is done, resolve the promise so the parent function can move on to the next step in the sequence
- if (animationPhase === 1) {
- resolve({
- bearing: currentBearing,
- altitude: currentAltitude,
- });
-
- // return so there are no further iterations of this frame
- return;
- }
-
- const innerFrameId = await window.requestAnimationFrame(frame);
- updateFrameId(innerFrameId);
-
- };
-
- const outerFrameId = await window.requestAnimationFrame(frame);
- updateFrameId(outerFrameId);
+ public flyInAndRotate = async ({ map, updateFrameId }: { map: MapRef; updateFrameId: (newFrameId: number) => void }) => {
+ return new Promise<{ bearing: number; altitude: number }>(async resolve => {
+ let start: number | null;
+
+ var currentAltitude;
+ var currentBearing;
+ var currentPitch;
+
+ // the animation frame will run as many times as necessary until the duration has been reached
+ const frame = async (time: number) => {
+ if (!start) {
+ start = time;
+ }
+
+ // otherwise, use the current time to determine how far along in the duration we are
+ let animationPhase = (time - start) / this.flyToDuration;
+
+ // because the phase calculation is imprecise, the final zoom can vary
+ // if it ended up greater than 1, set it to 1 so that we get the exact endAltitude that was requested
+ if (animationPhase > 1) {
+ animationPhase = 1;
+ }
+
+ currentAltitude = this.START_ALTITUDE + (this.flyInEndAltitude - this.START_ALTITUDE) * d3.easeCubicOut(animationPhase);
+ // rotate the camera between startBearing and endBearing
+ currentBearing = this.flyInStartBearing + (this.flyInEndBearing - this.flyInStartBearing) * d3.easeCubicOut(animationPhase);
+
+ currentPitch = this.FLY_IN_START_PITCH + (this.flyInEndPitch - this.FLY_IN_START_PITCH) * d3.easeCubicOut(animationPhase);
+
+ // compute corrected camera ground position, so the start of the path is always in view
+ var correctedPosition = this.computeCameraPosition(false, currentPitch, currentBearing, this.FIRST_LNG_LAT, currentAltitude);
+
+ // set the pitch and bearing of the camera
+ const camera = map.getFreeCameraOptions();
+ camera.setPitchBearing(currentPitch, currentBearing);
+
+ // set the position and altitude of the camera
+ camera.position = MercatorCoordinate.fromLngLat(correctedPosition, currentAltitude);
+
+ // apply the new camera options
+ map.setFreeCameraOptions(camera);
+
+ // when the animationPhase is done, resolve the promise so the parent function can move on to the next step in the sequence
+ if (animationPhase === 1) {
+ resolve({
+ bearing: currentBearing,
+ altitude: currentAltitude,
+ });
+
+ // return so there are no further iterations of this frame
+ return;
+ }
+
+ const innerFrameId = await window.requestAnimationFrame(frame);
+ updateFrameId(innerFrameId);
+ };
+
+ const outerFrameId = await window.requestAnimationFrame(frame);
+ updateFrameId(outerFrameId);
});
- };
+ };
- previousCameraPosition: {lng: number, lat: number} | null = null;
+ previousCameraPosition: { lng: number; lat: number } | null = null;
- lerp = (start: number, end: number, amt: number) => {
+ lerp = (start: number, end: number, amt: number) => {
return (1 - amt) * start + amt * end;
- }
-
- computeCameraPosition = (
- isStreetViewAnimation: boolean,
- pitch: number,
- bearing: number,
- targetPosition: {lng: number, lat: number},
- altitude: number,
- smooth = false
- ) => {
+ };
+
+ computeCameraPosition = (isStreetViewAnimation: boolean, pitch: number, bearing: number, targetPosition: { lng: number; lat: number }, altitude: number, smooth = false) => {
const bearingInRadian = (bearing * Math.PI) / 180;
- const pitchInRadian = ((90 - pitch)* Math.PI) / 180;
+ const pitchInRadian = ((90 - pitch) * Math.PI) / 180;
let correctedLng = targetPosition.lng;
let correctedLat = targetPosition.lat;
if (!isStreetViewAnimation) {
- const lngDiff =
- ((altitude / Math.tan(pitchInRadian)) *
- Math.sin(-bearingInRadian)) /
- 70000; // ~70km/degree longitude
- const latDiff =
- ((altitude / Math.tan(pitchInRadian)) *
- Math.cos(-bearingInRadian)) /
- 110000 // 110km/degree latitude
+ const lngDiff = ((altitude / Math.tan(pitchInRadian)) * Math.sin(-bearingInRadian)) / 70000; // ~70km/degree longitude
+ const latDiff = ((altitude / Math.tan(pitchInRadian)) * Math.cos(-bearingInRadian)) / 110000; // 110km/degree latitude
correctedLng = targetPosition.lng + lngDiff;
correctedLat = targetPosition.lat - latDiff;
-
}
-
+
const newCameraPosition = {
- lng: correctedLng,
- lat: correctedLat
+ lng: correctedLng,
+ lat: correctedLat,
};
-
+
if (smooth) {
- if (this.previousCameraPosition) {
- newCameraPosition.lng = this.lerp(newCameraPosition.lng, this.previousCameraPosition.lng, this.SMOOTH_FACTOR);
- newCameraPosition.lat = this.lerp(newCameraPosition.lat, this.previousCameraPosition.lat, this.SMOOTH_FACTOR);
- }
+ if (this.previousCameraPosition) {
+ newCameraPosition.lng = this.lerp(newCameraPosition.lng, this.previousCameraPosition.lng, this.SMOOTH_FACTOR);
+ newCameraPosition.lat = this.lerp(newCameraPosition.lat, this.previousCameraPosition.lat, this.SMOOTH_FACTOR);
+ }
}
-
- this.previousCameraPosition = newCameraPosition
-
+
+ this.previousCameraPosition = newCameraPosition;
+
return newCameraPosition;
- };
-
- public static createGeoJSONCircle = (center: number[], radiusInKm: number, points = 64): Feature<Geometry, GeoJsonProperties>=> {
+ };
+
+ public static createGeoJSONCircle = (center: number[], radiusInKm: number, points = 64): Feature<Geometry, GeoJsonProperties> => {
const coords = {
- latitude: center[1],
- longitude: center[0],
+ latitude: center[1],
+ longitude: center[0],
};
const km = radiusInKm;
const ret = [];
- const distanceX = km / (111.320 * Math.cos((coords.latitude * Math.PI) / 180));
+ const distanceX = km / (111.32 * Math.cos((coords.latitude * Math.PI) / 180));
const distanceY = km / 110.574;
let theta;
let x;
let y;
for (let i = 0; i < points; i += 1) {
- theta = (i / points) * (2 * Math.PI);
- x = distanceX * Math.cos(theta);
- y = distanceY * Math.sin(theta);
- ret.push([coords.longitude + x, coords.latitude + y]);
+ theta = (i / points) * (2 * Math.PI);
+ x = distanceX * Math.cos(theta);
+ y = distanceY * Math.sin(theta);
+ ret.push([coords.longitude + x, coords.latitude + y]);
}
ret.push(ret[0]);
return {
- type: 'Feature',
- geometry: {
- type: 'Polygon',
- coordinates: [ret],
- },
- properties: {}
+ type: 'Feature',
+ geometry: {
+ type: 'Polygon',
+ coordinates: [ret],
+ },
+ properties: {},
};
- }
+ };
- private calculateBearing(
- from: { lng: number; lat: number },
- to: { lng: number; lat: number }
- ): number {
+ private calculateBearing(from: { lng: number; lat: number }, to: { lng: number; lat: number }): number {
const lon1 = from.lng;
const lat1 = from.lat;
const lon2 = to.lng;
const lat2 = to.lat;
-
+
const lon1Rad = (lon1 * Math.PI) / 180;
const lon2Rad = (lon2 * Math.PI) / 180;
const lat1Rad = (lat1 * Math.PI) / 180;
const lat2Rad = (lat2 * Math.PI) / 180;
-
+
const y = Math.sin(lon2Rad - lon1Rad) * Math.cos(lat2Rad);
- const x =
- Math.cos(lat1Rad) * Math.sin(lat2Rad) -
- Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(lon2Rad - lon1Rad);
-
- let bearing = Math.atan2(y,x);
-
+ const x = Math.cos(lat1Rad) * Math.sin(lat2Rad) - Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(lon2Rad - lon1Rad);
+
+ let bearing = Math.atan2(y, x);
+
// Convert bearing from radians to degrees
bearing = (bearing * 180) / Math.PI;
-
+
// Ensure the bearing is within [0, 360)
if (bearing < 0) {
- bearing += 360;
+ bearing += 360;
}
-
- return bearing;
- }
-
-} \ No newline at end of file
+ return bearing;
+ }
+}