diff options
Diffstat (limited to 'src/client/views/nodes/MapBox/MapBox.tsx')
-rw-r--r-- | src/client/views/nodes/MapBox/MapBox.tsx | 384 |
1 files changed, 256 insertions, 128 deletions
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, }} /> </> |