diff options
-rw-r--r-- | package-lock.json | 10 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 6 | ||||
-rw-r--r-- | src/client/views/nodes/MapBox/AnimationUtility.ts | 147 | ||||
-rw-r--r-- | src/client/views/nodes/MapBox/MapBox.scss | 23 | ||||
-rw-r--r-- | src/client/views/nodes/MapBox/MapBox.tsx | 384 |
6 files changed, 409 insertions, 163 deletions
diff --git a/package-lock.json b/package-lock.json index 311eb9281..b0748aae6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17010,6 +17010,11 @@ "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" }, + "h264-mp4-encoder": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/h264-mp4-encoder/-/h264-mp4-encoder-1.0.12.tgz", + "integrity": "sha512-xih3J+Go0o1RqGjhOt6TwXLWWGqLONRPyS8yoMu/RoS/S8WyEv4HuHp1KBsDDl8srZQ3gw9f95JYkCSjCuZbHQ==" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -32370,6 +32375,11 @@ "loose-envify": "^1.0.0" } }, + "wasm-feature-detect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.6.1.tgz", + "integrity": "sha512-R1i9ED8UlLu/foILNB1ck9XS63vdtqU/tP1MCugVekETp/ySCrBZRk5I/zI67cI1wlQYeSonNm1PLjDHZDNg6g==" + }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 9a9e7675d..5d53a6c4e 100644 --- a/package.json +++ b/package.json @@ -228,6 +228,7 @@ "googleapis": "^40.0.0", "googlephotos": "^0.2.5", "got": "^12.0.1", + "h264-mp4-encoder": "^1.0.12", "howler": "^2.2.3", "html-to-image": "^0.1.3", "html-to-text": "^5.1.1", @@ -341,6 +342,7 @@ "util": "^0.12.4", "uuid": "^3.4.0", "valid-url": "^1.0.9", + "wasm-feature-detect": "^1.6.1", "web-request": "^1.0.7", "webpack-cli": "^4.10.0", "webpack-dev-middleware": "^5.3.1", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3db9f4f06..f5f140ae9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -176,12 +176,16 @@ export class DocumentOptions { _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units"); latitude?: NUMt = new NumInfo('latitude coordinate for map views', false); longitude?: NUMt = new NumInfo('longitude coordinate for map views', false); - routeCoordinates?: STRt = new StrInfo("stores a route's/direction's coordinates (stringified version)"); // for a route document, this stores the route's coordiantes + routeCoordinates?: STRt = new StrInfo("stores a route's/direction's coordinates (stringified version)"); // for a route document, this stores the route's coordinates markerType?: STRt = new StrInfo('Defines the marker type for a pushpin document'); markerColor?: STRt= new StrInfo('Defines the marker color for a pushpin document'); map?: STRt = new StrInfo('text location of map'); map_type?: STRt = new StrInfo('type of map view', false); map_zoom?: NUMt = new NumInfo('zoom of a map view', false); + map_pitch?: NUMt = new NumInfo('pitch of a map view', false); + map_bearing?: NUMt = new NumInfo('bearing of a map view', false); + map_style?: STRt = new StrInfo('mapbox style for a map view', false); + wikiData?: STRt = new StrInfo('WikiData ID related to map location'); description?: STRt = new StrInfo('A description of the document'); _timecodeToShow?: NUMt = new NumInfo('the time that a document should be displayed (e.g., when an annotation shows up as a video plays)', false); diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts index 256acbf13..11b335a96 100644 --- a/src/client/views/nodes/MapBox/AnimationUtility.ts +++ b/src/client/views/nodes/MapBox/AnimationUtility.ts @@ -5,9 +5,8 @@ import * as React from 'react'; import * as d3 from "d3"; import * as turf from '@turf/turf'; import { Position } from "@turf/turf"; -import { Feature, FeatureCollection, GeoJsonProperties, Geometry } from "geojson"; -import { observer } from "mobx-react"; -import { action, computed, observable } from "mobx"; +import { Feature, GeoJsonProperties, Geometry } from "geojson"; +import { action, computed, observable, runInAction } from "mobx"; export enum AnimationStatus { START = 'start', @@ -21,23 +20,32 @@ export enum AnimationSpeed { FAST = '3x', } -@observer export class AnimationUtility { 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 START_ALTITUDE = 3_000_000; + private MAP_REF: MapRef | null; @observable private isStreetViewAnimation: boolean; @observable private animationSpeed: AnimationSpeed; - + @observable 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( @@ -51,38 +59,103 @@ export class AnimationUtility { } ) : -20; + } + + @computed get currentAnimationAltitude(): number { + if (!this.isStreetViewAnimation) return 20_000; + if (!this.terrainDisplayed) return 50; + 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; } + if (this.previousAltitude){ + return this.lerp(altitude, this.previousAltitude, 0.02); + } + return altitude; + + } @computed get flyInStartBearing() { return Math.max(0, Math.min(this.flyInEndBearing + 20, 360)); // between 0 and 360 } @computed get flyInEndAltitude() { - return this.isStreetViewAnimation ? 70 : 10000; + // return this.isStreetViewAnimation ? (this.currentAnimationAltitude + 70 ): 10_000; + return this.currentAnimationAltitude; + } + + @computed get currentPitch(): number { + if (!this.isStreetViewAnimation) return 50; + if (!this.terrainDisplayed) return 80; + else { + // const groundElevation = 0; + const heightAboveGround = this.currentAnimationAltitude; + const horizontalDistance = 500; + + let pitch; + 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){ + return this.lerp(Math.max(50, Math.min(pitch, 85)), this.previousPitch, 0.02); + } + return Math.max(50, Math.min(pitch, 85)); + } } @computed get flyInEndPitch() { - return this.isStreetViewAnimation ? 80 : 50; + return this.currentPitch; } @computed get flyToDuration() { switch (this.animationSpeed) { case AnimationSpeed.SLOW: - return 4000; + return 4_000; case AnimationSpeed.MEDIUM: - return 2500; + return 2_500; case AnimationSpeed.FAST: - return 1250; + return 1_250; default: - return 2500; + return 2_500; } } @computed get animationDuration(): number { - return 20_000; - // compute path distance for a standard speed - // get animation speed - // get isStreetViewAnimation (should be slower if so) + let scalingFactor: number; + const MIN_DISTANCE = 0; + const MAX_DISTANCE = 3_000; // anything greater than 3000 is just set to 1 when normalized + const MAX_DURATION = this.isStreetViewAnimation ? 120_000 : 60_000; + + 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){ + case AnimationSpeed.SLOW: + scalingFactor = 250; + break; + case AnimationSpeed.MEDIUM: + scalingFactor = 150; + break; + case AnimationSpeed.FAST: + scalingFactor = 85; + break; + default: + scalingFactor = 150; + break; + } + + const duration = Math.min(MAX_DURATION, (easedDistance * MAX_DISTANCE) * (this.isStreetViewAnimation ? scalingFactor*1.12 : scalingFactor)); + + return duration; } @action @@ -96,17 +169,29 @@ export class AnimationUtility { this.isStreetViewAnimation = isStreetViewAnimation; } + @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 + animationSpeed: AnimationSpeed, + terrainDisplayed: boolean, + mapRef: MapRef | null ) { this.FIRST_LNG_LAT = firstLngLat; this.previousLngLat = firstLngLat; + this.isStreetViewAnimation = isStreetViewAnimation; + this.MAP_REF = mapRef; + this.ROUTE_COORDINATES = routeCoordinates; this.PATH = turf.lineString(routeCoordinates); + this.PATH_DISTANCE = turf.lineDistance(this.PATH); + this.terrainDisplayed = terrainDisplayed; const bearing = this.calculateBearing( { @@ -119,13 +204,7 @@ export class AnimationUtility { } ); this.currentStreetViewBearing = bearing; - // if (isStreetViewAnimation){ - // this.flyInEndBearing = bearing; - // } - this.isStreetViewAnimation = isStreetViewAnimation; this.animationSpeed = animationSpeed; - // calculate animation duration based on speed - // this.animationDuration = animationDuration; } public animatePath = async ({ @@ -151,8 +230,6 @@ export class AnimationUtility { }) => { return new Promise<void>(async (resolve) => { - const pathDistance = turf.lineDistance(this.PATH); - console.log("PATH DISTANCE: ", pathDistance); let startTime: number | null = null; const frame = async (currentTime: number) => { @@ -169,7 +246,7 @@ export class AnimationUtility { // calculate the distance along the path based on the animationPhase - const alongPath = turf.along(this.PATH, pathDistance * animationPhase).geometry + const alongPath = turf.along(this.PATH, this.PATH_DISTANCE * animationPhase).geometry .coordinates; const lngLat = { @@ -182,7 +259,7 @@ export class AnimationUtility { bearing = this.lerp( this.currentStreetViewBearing, this.calculateBearing(this.previousLngLat, lngLat), - 0.028 // Adjust the transition speed as needed + 0.032 ); this.currentStreetViewBearing = bearing; // bearing = this.calculateBearing(this.previousLngLat, lngLat); // TODO: Calculate actual bearing @@ -192,35 +269,39 @@ export class AnimationUtility { // bearing = startBearing - animationPhase * 200.0; } - this.previousLngLat = lngLat; + 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.flyInEndPitch, + this.currentPitch, bearing, lngLat, - this.flyInEndAltitude, + this.currentAnimationAltitude, true // smooth ); // set the pitch and bearing of the camera const camera = map.getFreeCameraOptions(); - camera.setPitchBearing(this.flyInEndPitch, bearing); + camera.setPitchBearing(this.currentPitch, bearing); - console.log("Corrected pos: ", correctedPosition); - console.log("Start altitude: ", this.flyInEndAltitude); + // set the position and altitude of the camera camera.position = MercatorCoordinate.fromLngLat( correctedPosition, - this.flyInEndAltitude + 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); diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss index d3c6bb14e..e25261729 100644 --- a/src/client/views/nodes/MapBox/MapBox.scss +++ b/src/client/views/nodes/MapBox/MapBox.scss @@ -109,8 +109,14 @@ .animation-suboptions{ display: flex; justify-content: flex-start; + flex-wrap: wrap; align-items: center; gap: 7px; + width: 100%; + + .first-person-label{ + width: '130px' !important; + } label{ margin-bottom: 0; @@ -120,7 +126,7 @@ margin-right: 5px; } - #last-divider{ + #divider{ margin-left: 10px; margin-right: 10px; } @@ -128,6 +134,21 @@ } + } + + .zoom-box { + position: absolute; + z-index: 900; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: white; + font-size: 1.4em; + border-radius: 5px; + bottom: 5px; + left: 5px; + padding: 3px; } diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index cde68a2e6..f4526c490 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -3,7 +3,7 @@ import BingMapsReact from 'bingmaps-react'; // import 'mapbox-gl/dist/mapbox-gl.css'; import { Button, EditableText, IconButton, Size, Type } from 'browndash-components'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, flow, toJS} from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, flow, toJS, autorun} from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc'; @@ -28,6 +28,9 @@ import { FieldView, FieldViewProps } from '../FieldView'; import { FormattedTextBox } from '../formattedText/FormattedTextBox'; import { PinProps, PresBox } from '../trails'; import { MapAnchorMenu } from './MapAnchorMenu'; +import * as HME from "h264-mp4-encoder"; +import {simd} from 'wasm-feature-detect'; + import { Map as MapboxMap, MapRef, @@ -51,20 +54,21 @@ import debounce from 'debounce'; import './MapBox.scss'; import { NumberLiteralType } from 'typescript'; // import { GeocoderControl } from './GeocoderControl'; -import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent, MercatorCoordinate } from 'mapbox-gl'; +import mapboxgl, { LngLat, LngLatBoundsLike, LngLatLike, MapLayerMouseEvent, MercatorCoordinate } from 'mapbox-gl'; import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, MultiLineString, Position } from 'geojson'; import { MarkerEvent } from 'react-map-gl/dist/esm/types'; import { MapboxApiUtility, TransportationType} from './MapboxApiUtility'; import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/material'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; -import { IconLookup, faCircleXmark, faFileExport, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons'; +import { IconLookup, faCircleXmark, faFileExport, faGear, faMinus, faPause, faPlay, faPlus, faRotate } from '@fortawesome/free-solid-svg-icons'; import { MarkerIcons } from './MarkerIcons'; import { SettingsManager } from '../../../util/SettingsManager'; import * as turf from '@turf/turf'; import * as d3 from "d3"; import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility'; import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons'; +import { CirclePicker, ColorState } from 'react-color'; // amongus /** @@ -153,17 +157,20 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const originalCoordinates: Position[] = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates)); // const index = Math.floor(this.animationPhase * originalCoordinates.length); const index = this.animationPhase * (originalCoordinates.length - 1); // Calculate the fractional index + console.log("Animation phase", this.animationPhase); const startIndex = Math.floor(index); const endIndex = Math.ceil(index); + let feature: Feature<Geometry, GeoJsonProperties>; + let geometry: LineString; if (startIndex === endIndex) { // AnimationPhase is at a whole number (no interpolation needed) const coordinates = [originalCoordinates[startIndex]]; - const geometry: LineString = { + geometry = { type: 'LineString', coordinates, }; - return { + feature = { type: 'Feature', properties: { 'routeTitle': StrCast(this.routeToAnimate.title) @@ -175,20 +182,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const startCoord = originalCoordinates[startIndex]; const endCoord = originalCoordinates[endIndex]; const fraction = index - startIndex; - - // Interpolate the coordinates - const interpolatedCoord = [ - startCoord[0] + fraction * (endCoord[0] - startCoord[0]), - startCoord[1] + fraction * (endCoord[1] - startCoord[1]), - ]; + + const interpolator = d3.interpolateArray(startCoord, endCoord); + + const interpolatedCoord = interpolator(fraction); const coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]); - - const geometry: LineString = { + + geometry = { type: 'LineString', coordinates, }; - return { + feature = { type: 'Feature', properties: { 'routeTitle': StrCast(this.routeToAnimate.title) @@ -196,7 +201,25 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps geometry: geometry, }; } + + autorun(() => { + const animationUtil = this.animationUtility; + const concattedCoordinates = geometry.coordinates.concat(originalCoordinates.slice(endIndex)); + const newFeature: Feature<LineString, turf.Properties> = { + type: 'Feature', + properties: {}, + geometry: { + type: 'LineString', + coordinates: concattedCoordinates + } + } + if (animationUtil){ + animationUtil.setPath(newFeature) + } + }) + return feature; } + console.log("ERROR"); return { type: 'Feature', properties: {}, @@ -600,7 +623,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude), config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude), config_map_zoom: NumCast(this.dataDoc.map_zoom), - config_map_type: StrCast(this.dataDoc.map_type), + // config_map_type: StrCast(this.dataDoc.map_type), config_map: StrCast((existingPin ?? this.selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map), layout_unrendered: true, mapPin: existingPin ?? this.selectedPinOrRoute, @@ -1077,9 +1100,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps MapAnchorMenu.Instance.setMenuType('standard'); + // MapAnchorMenu.Instance.jumpTo(NumCast(pinDoc.longitude), NumCast(pinDoc.latitude)-3, true); + MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true); document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true); + + + // this._mapRef.current.flyTo({ + // center: [NumCast(pinDoc.longitude), NumCast(pinDoc.latitude)-3] + // }) + }; @observable @@ -1114,9 +1145,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps isAnimating: boolean = false; @observable - isPaused: boolean = false; - - @observable routeToAnimate: Doc | undefined = undefined; @observable @@ -1138,6 +1166,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.frameId = frameId; } + @observable + animationUtility: AnimationUtility | null = null; + + @action + setAnimationUtility = (util: AnimationUtility) => { + this.animationUtility = util; + } + @action openAnimationPanel = (routeDoc: Doc | undefined) => { if (routeDoc){ @@ -1148,13 +1184,27 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } @observable - animationDuration = 40000; + mapboxMapViewState: ViewState = { + zoom: this.dataDoc.map_zoom ? NumCast(this.dataDoc.map_zoom) : 8, + longitude: this.dataDoc.longitude ? NumCast(this.dataDoc.longitude) : -71.4128, + latitude: this.dataDoc.latitude ? NumCast(this.dataDoc.latitude) : 41.8240, + pitch: this.dataDoc.map_pitch ? NumCast(this.dataDoc.map_pitch) : 0, + bearing: this.dataDoc.map_bearing ? NumCast(this.dataDoc.map_bearing) : 0, + padding: { + top: 0, + bottom: 0, + left: 0, + right: 0 + }, + } - @observable - animationAltitude = 12800; + @computed + get preAnimationViewState() { + if (!this.isAnimating){ + return this.mapboxMapViewState; + } + } - @observable - pathDistance = 0; @observable isStreetViewAnimation: boolean = false; @@ -1162,22 +1212,36 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM; + + @observable + animationLineColor: string = '#ffff00'; + + @action + setAnimationLineColor = (color: ColorState) => { + this.animationLineColor = color.hex; + } + @action updateAnimationSpeed = () => { + let newAnimationSpeed: AnimationSpeed; switch (this.animationSpeed){ case AnimationSpeed.SLOW: - this.animationSpeed = AnimationSpeed.MEDIUM; + newAnimationSpeed = AnimationSpeed.MEDIUM; break; case AnimationSpeed.MEDIUM: - this.animationSpeed = AnimationSpeed.FAST; + newAnimationSpeed = AnimationSpeed.FAST; break; case AnimationSpeed.FAST: - this.animationSpeed = AnimationSpeed.SLOW; + newAnimationSpeed = AnimationSpeed.SLOW; break; default: - this.animationSpeed = AnimationSpeed.MEDIUM; + newAnimationSpeed = AnimationSpeed.MEDIUM; break; } + this.animationSpeed = newAnimationSpeed; + if (this.animationUtility){ + this.animationUtility.updateAnimationSpeed(newAnimationSpeed); + } } @computed get animationSpeedTooltipText(): string { switch (this.animationSpeed) { @@ -1206,7 +1270,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @action toggleIsStreetViewAnimation = () => { - this.isStreetViewAnimation = !this.isStreetViewAnimation; + const newVal = !this.isStreetViewAnimation; + this.isStreetViewAnimation = newVal; + if (this.animationUtility){ + this.animationUtility.updateIsStreetViewAnimation(newVal) + } } @observable @@ -1249,9 +1317,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return; } - if (this.isAnimating){ - return; - } this.animationPhase = status === AnimationStatus.RESUME ? this.animationPhase : 0; this.frameId = AnimationStatus.RESUME ? this.frameId : null; this.finishedFlyTo = AnimationStatus.RESUME ? this.finishedFlyTo : false; @@ -1260,23 +1325,26 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.settingsOpen = false; this.path = path; - this.pathDistance = turf.lineDistance(path); this.isAnimating = true; + runInAction(() => { return new Promise<void>(async (resolve) => { - let animationUtil; - try { const targetLngLat = { lng: this.selectedRouteCoordinates[0][0], lat: this.selectedRouteCoordinates[0][1], }; - animationUtil = new AnimationUtility( + const animationUtil = new AnimationUtility( targetLngLat, this.selectedRouteCoordinates, this.isStreetViewAnimation, - this.animationSpeed + this.animationSpeed, + this.showTerrain, + this._mapRef.current ); + runInAction(() => { + this.setAnimationUtility(animationUtil); + }) const updateFrameId = (newFrameId: number) => { @@ -1338,14 +1406,12 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }); setTimeout(() => { + this.isStreetViewAnimation = false; resolve(); }, 10000); - } catch (error: any){ - console.log(error); - console.log('animation util: ', animationUtil); - }}); + }); - }) + }) } @@ -1361,17 +1427,25 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } @action - stopAndCloseAnimation = () => { + stopAnimation = (close: boolean) => { if (this.frameId){ window.cancelAnimationFrame(this.frameId); - this.frameId = null; - this.finishedFlyTo = false; - this.isAnimating = false; - this.animationPhase = 0; + } + this.animationPhase = 0; + this.frameId = null; + this.finishedFlyTo = false; + this.isAnimating = false; + if (close) { + this.animationSpeed = AnimationSpeed.MEDIUM; + this.isStreetViewAnimation = false; this.routeToAnimate = undefined; - // this.selectedRouteCoordinates = []; + this.animationUtility = null; } - // reset bearing and pitch to original, zoom out + if (this.preAnimationViewState){ + this.mapboxMapViewState = this.preAnimationViewState; + } + + } @action @@ -1404,7 +1478,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps {this.isAnimating && this.finishedFlyTo && <IconButton tooltip='Restart animation' - onPointerDown={() => this.playAnimation(AnimationStatus.RESTART)} + onPointerDown={() => { + this.stopAnimation(false); + this.playAnimation(AnimationStatus.START) + }} icon={<FontAwesomeIcon icon={faRotate as IconLookup}/>} color='black' size={Size.MEDIUM} @@ -1412,45 +1489,47 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } <IconButton + style={{marginRight: '10px'}} tooltip='Stop and close animation' - onPointerDown={this.stopAndCloseAnimation} + onPointerDown={() => this.stopAnimation(true)} icon={<FontAwesomeIcon icon={faCircleXmark as IconLookup}/>} color='black' size={Size.MEDIUM} /> - <IconButton - style={{marginRight: '10px'}} - tooltip='Export to video' - onPointerDown={this.exportAnimationToVideo} - icon={<FontAwesomeIcon icon={faFileExport as IconLookup}/>} - color='black' - size={Size.MEDIUM} - /> - {!this.isAnimating && - <> - <div className='animation-suboptions'> - <div>|</div> - <FormControlLabel - label='Street view animation' - labelPlacement='start' - control={ - <Checkbox - color='success' - checked={this.isStreetViewAnimation} - onChange={this.toggleIsStreetViewAnimation} - /> - } - /> - <div id='last-divider'>|</div> - <IconButton - tooltip={this.animationSpeedTooltipText} - onPointerDown={this.updateAnimationSpeed} - icon={this.animationSpeedIcon} - size={Size.MEDIUM} - /> - </div> - </> - } + <> + <div className='animation-suboptions'> + <div>|</div> + <FormControlLabel + className='first-person-label' + style={{width: '130px'}} + label='1st person animation:' + labelPlacement='start' + control={ + <Checkbox + color='success' + checked={this.isStreetViewAnimation} + onChange={this.toggleIsStreetViewAnimation} + /> + } + /> + <div id='divider'>|</div> + <IconButton + tooltip={this.animationSpeedTooltipText} + onPointerDown={this.updateAnimationSpeed} + icon={this.animationSpeedIcon} + size={Size.MEDIUM} + /> + <div id='divider'>|</div> + <div style={{width: '230px'}}>Select Line Color: </div> + <CirclePicker + circleSize={12} + circleSpacing={5} + width='100%' + colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} + onChange={(color) => this.setAnimationLineColor(color)} + /> + </div> + </> </> ) } @@ -1464,22 +1543,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } - - @observable - mapboxMapViewState: ViewState = { - zoom: 9, - longitude: -71.45, - latitude: 41.82, - pitch: 0, - bearing: 0, - padding: { - top: 0, - bottom: 0, - left: 0, - right: 0 - } - } - @observable settingsOpen: boolean = false; @@ -1487,7 +1550,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps mapStyle: string = 'mapbox://styles/mapbox/streets-v12' @observable - showTerrain: boolean = false; + showTerrain: boolean = true; @action toggleSettings = () => { @@ -1499,39 +1562,85 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @action changeMapStyle = (e: React.ChangeEvent<HTMLSelectElement>) => { - this.mapStyle = `mapbox://styles/mapbox/${e.target.value}` + this.dataDoc.map_style = `mapbox://styles/mapbox/${e.target.value}`; + // this.mapStyle = `mapbox://styles/mapbox/${e.target.value}` } - @action - onMapMove = (e: ViewStateChangeEvent) => { - this.mapboxMapViewState = e.viewState; + @action + onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => { + const bearing = parseInt(e.target.value); + if (!isNaN(bearing) && this._mapRef.current){ + const fixedBearing = Math.max(0, Math.min(360, bearing)); + this._mapRef.current.setBearing(fixedBearing); + this.dataDoc.map_bearing = fixedBearing; + this.mapboxMapViewState = { + ...this.mapboxMapViewState, + bearing: fixedBearing + } + } } - @action - toggleShowTerrain = () => { - this.showTerrain = !this.showTerrain; + @action + onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => { + const pitch = parseInt(e.target.value); + if (!isNaN(pitch) && this._mapRef.current){ + const fixedPitch = Math.max(0, Math.min(85, pitch)); + this._mapRef.current.setPitch(fixedPitch); + this.dataDoc.map_pitch = fixedPitch; + this.mapboxMapViewState = { + ...this.mapboxMapViewState, + pitch: fixedPitch + } + } } - @action - onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => { - const newVal = parseInt(e.target.value) - if (!isNaN(newVal) && newVal >= 0){ + @action + onZoomChange = (e: React.ChangeEvent<HTMLInputElement>) => { + const zoom = parseInt(e.target.value); + if (!isNaN(zoom) && this._mapRef.current){ + const fixedZoom = Math.max(0, Math.min(16, zoom)); + this._mapRef.current.setZoom(fixedZoom); + this.dataDoc.map_zoom = fixedZoom; this.mapboxMapViewState = { ...this.mapboxMapViewState, - bearing: parseInt(e.target.value) + zoom: fixedZoom } } } @action - onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => { - const newVal = parseInt(e.target.value); - if (!isNaN(newVal) && newVal >= 0){ + onStepZoomChange = (increment: boolean) => { + if (this._mapRef.current) { + let newZoom: number; + if (increment) { + console.log('inc') + newZoom = this.mapboxMapViewState.zoom + 1; + + } else { + console.log('dec') + newZoom = this.mapboxMapViewState.zoom - 1; + } + this._mapRef.current.setZoom(newZoom); + this.dataDoc.map_zoom = newZoom; this.mapboxMapViewState = { ...this.mapboxMapViewState, - pitch: parseInt(e.target.value) + zoom: increment ? Math.min(16, newZoom) : Math.max(0, newZoom) } } + + } + + + @action + onMapMove = (e: ViewStateChangeEvent) => { + this.mapboxMapViewState = e.viewState; + this.dataDoc.longitude = e.viewState.longitude; + this.dataDoc.latitude = e.viewState.latitude; + } + + @action + toggleShowTerrain = () => { + this.showTerrain = !this.showTerrain; } getMarkerIcon = (pinDoc: Doc): JSX.Element | null => { @@ -1603,7 +1712,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps </div> <div> <select onChange={this.changeMapStyle}> - <option value='streets-v12'>Streets</option> + <option value='streets-v11'>Streets</option> <option value='outdoors-v12'>Outdoors</option> <option value='light-v11'>Light</option> <option value='dark-v11'>Dark</option> @@ -1617,19 +1726,24 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps <div className='mapbox-bearing-selection'> <div>Bearing: </div> <input - value={this.mapboxMapViewState.bearing} - min={0} + value={NumCast(this.mapboxMapViewState.bearing).toFixed(2)} type='number' onChange={this.onBearingChange}/> </div> <div className='mapbox-pitch-selection'> <div>Pitch: </div> <input - value={this.mapboxMapViewState.pitch} - min={0} + value={NumCast(this.mapboxMapViewState.pitch).toFixed(2)} type='number' onChange={this.onPitchChange}/> - </div> + </div> + <div className='mapbox-pitch-selection'> + <div>Zoom: </div> + <input + value={NumCast(this.mapboxMapViewState.zoom).toFixed(2)} + type='number' + onChange={this.onZoomChange}/> + </div> <div className='mapbox-terrain-selection'> <div>Show terrain: </div> <input @@ -1673,16 +1787,30 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps </React.Fragment> </div> - )} + )} + {/* <div className='zoom-box'> + <IconButton // increment + style={{borderBottom: '1px', borderBottomColor: 'white'}} + onPointerDown={() => this.onStepZoomChange(true)} + icon={<FontAwesomeIcon icon={faPlus as IconLookup} color='black'/>} + size={Size.SMALL} + color={SettingsManager.userColor} + /> + <IconButton // decrement + onPointerDown={() => this.onStepZoomChange(false)} + icon={<FontAwesomeIcon icon={faMinus as IconLookup} color='black'/>} + size={Size.SMALL} + color={SettingsManager.userColor} + /> + </div> */} <MapProvider> <MapboxMap ref={this._mapRef} mapboxAccessToken={MAPBOX_ACCESS_TOKEN} id="mapbox-map" - mapStyle={this.mapStyle} - style={{height: '100%', width: '100%'}} + mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'} + style={{height: NumCast(this.layoutDoc._height), width: NumCast(this.layoutDoc._width)}} initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState} - // {...this.mapboxMapViewState} onMove={this.onMapMove} onClick={this.handleMapClick} onDblClick={this.handleMapDblClick} @@ -1725,8 +1853,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps type='line' source='animated-route' paint={{ - 'line-color': 'yellow', - 'line-width': 4, + 'line-color': this.animationLineColor, + 'line-width': 5, }} /> </> |