From e9b59a2790006af60206af7f676ad4a0ccceee13 Mon Sep 17 00:00:00 2001 From: zaultavangar Date: Thu, 14 Dec 2023 12:43:40 -0500 Subject: adding animation functionality --- .../views/nodes/MapBox/AnimationSpeedIcons.tsx | 35 + src/client/views/nodes/MapBox/AnimationUtility.ts | 429 ++++++++++++ src/client/views/nodes/MapBox/MapAnchorMenu.tsx | 118 +++- src/client/views/nodes/MapBox/MapBox.scss | 50 ++ src/client/views/nodes/MapBox/MapBox.tsx | 776 ++++++++++++++++++--- src/client/views/nodes/MapBox/MarkerIcons.tsx | 69 +- 6 files changed, 1291 insertions(+), 186 deletions(-) create mode 100644 src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx create mode 100644 src/client/views/nodes/MapBox/AnimationUtility.ts (limited to 'src') diff --git a/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx new file mode 100644 index 000000000..d54a175b2 --- /dev/null +++ b/src/client/views/nodes/MapBox/AnimationSpeedIcons.tsx @@ -0,0 +1,35 @@ +import * as React from "react"; + +export const slowSpeedIcon: JSX.Element = ( + + + + + + + +); + +export const mediumSpeedIcon: JSX.Element = ( + + + + + + + +); + +export const fastSpeedIcon: JSX.Element = ( + + + + + +); + diff --git a/src/client/views/nodes/MapBox/AnimationUtility.ts b/src/client/views/nodes/MapBox/AnimationUtility.ts new file mode 100644 index 000000000..256acbf13 --- /dev/null +++ b/src/client/views/nodes/MapBox/AnimationUtility.ts @@ -0,0 +1,429 @@ +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 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"; + +export enum AnimationStatus { + START = 'start', + RESUME = 'resume', + RESTART = 'restart', +} + +export enum AnimationSpeed { + SLOW = '1x', + MEDIUM = '2x', + FAST = '3x', +} + +@observer +export class AnimationUtility { + private SMOOTH_FACTOR = 0.95 + private ROUTE_COORDINATES: Position[] = []; + private PATH: turf.helpers.Feature; + private FLY_IN_START_PITCH = 40; + private FIRST_LNG_LAT: {lng: number, lat: number}; + private START_ALTITUDE = 3_000_000; + + @observable private isStreetViewAnimation: boolean; + @observable private animationSpeed: AnimationSpeed; + + + private previousLngLat: {lng: number, lat: number}; + + private currentStreetViewBearing: number = 0; + + @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] + } + ) + : -20; + } + + @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; + } + + @computed get flyInEndPitch() { + return this.isStreetViewAnimation ? 80 : 50; + } + + @computed get flyToDuration() { + switch (this.animationSpeed) { + case AnimationSpeed.SLOW: + return 4000; + case AnimationSpeed.MEDIUM: + return 2500; + case AnimationSpeed.FAST: + return 1250; + default: + return 2500; + } + } + + @computed get animationDuration(): number { + return 20_000; + // compute path distance for a standard speed + // get animation speed + // get isStreetViewAnimation (should be slower if so) + } + + @action + public updateAnimationSpeed(speed: AnimationSpeed) { + // calculate new flyToDuration and animationDuration + this.animationSpeed = speed; + } + + @action + public updateIsStreetViewAnimation(isStreetViewAnimation: boolean) { + this.isStreetViewAnimation = isStreetViewAnimation; + } + + + constructor( + firstLngLat: {lng: number, lat: number}, + routeCoordinates: Position[], + isStreetViewAnimation: boolean, + animationSpeed: AnimationSpeed + ) { + this.FIRST_LNG_LAT = firstLngLat; + this.previousLngLat = firstLngLat; + this.ROUTE_COORDINATES = routeCoordinates; + this.PATH = turf.lineString(routeCoordinates); + + const bearing = this.calculateBearing( + { + lng: routeCoordinates[0][0], + lat: routeCoordinates[0][1] + }, + { + lng: routeCoordinates[1][0], + lat: routeCoordinates[1][1] + } + ); + 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 ({ + map, + // path, + // startBearing, + // startAltitude, + // pitch, + currentAnimationPhase, + updateAnimationPhase, + updateFrameId, + }: { + map: MapRef, + // path: turf.helpers.Feature, + // startBearing: number, + // startAltitude: number, + // pitch: number, + currentAnimationPhase: number, + updateAnimationPhase: ( + newAnimationPhase: number, + ) => void, + updateFrameId: (newFrameId: number) => void; + + }) => { + return new Promise(async (resolve) => { + const pathDistance = turf.lineDistance(this.PATH); + console.log("PATH DISTANCE: ", pathDistance); + 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, pathDistance * 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.028 // Adjust the transition speed as needed + ); + 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; + } + + 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, + bearing, + lngLat, + this.flyInEndAltitude, + true // smooth + ); + + // set the pitch and bearing of the camera + const camera = map.getFreeCameraOptions(); + camera.setPitchBearing(this.flyInEndPitch, 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 + ); + + + // apply the new camera options + map.setFreeCameraOptions(camera); + + // repeat! + 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; + + 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 + ) => { + const bearingInRadian = (bearing * 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 + + correctedLng = targetPosition.lng + lngDiff; + correctedLat = targetPosition.lat - latDiff; + + } + + const newCameraPosition = { + 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); + } + } + + this.previousCameraPosition = newCameraPosition + + return newCameraPosition; + }; + + public static createGeoJSONCircle = (center: number[], radiusInKm: number, points = 64): Feature=> { + const coords = { + latitude: center[1], + longitude: center[0], + }; + const km = radiusInKm; + const ret = []; + const distanceX = km / (111.320 * 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]); + } + ret.push(ret[0]); + return { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ret], + }, + properties: {} + }; + } + + 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); + + // Convert bearing from radians to degrees + bearing = (bearing * 180) / Math.PI; + + // Ensure the bearing is within [0, 360) + if (bearing < 0) { + bearing += 360; + } + + return bearing; + } + + +} \ No newline at end of file diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx index 2c2879900..f4e24d9c1 100644 --- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx +++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx @@ -32,11 +32,11 @@ import { Autocomplete, Checkbox, FormControlLabel, TextField } from '@mui/materi import { MapboxApiUtility, TransportationType } from './MapboxApiUtility'; import { MapBox } from './MapBox'; import { List } from '../../../../fields/List'; -import { MapboxColor, MarkerIcons } from './MarkerIcons'; -import { CirclePicker } from 'react-color'; +import { MarkerIcons } from './MarkerIcons'; +import { CirclePicker, ColorState } from 'react-color'; import { Position } from 'geojson'; -type MapAnchorMenuType = 'standard' | 'route' | 'calendar' | 'customize'; +type MapAnchorMenuType = 'standard' | 'routeCreation' | 'calendar' | 'customize' | 'route'; @observer export class MapAnchorMenu extends AntimodeMenu { @@ -61,17 +61,28 @@ export class MapAnchorMenu extends AntimodeMenu { public DisplayRoute: (routeInfoMap: Record | undefined, type: TransportationType) => void = unimplementedFunction; public HideRoute: () => void = unimplementedFunction; - public AddNewRouteToMap: (coordinates: Position[], origin: string, destination: string) => void = unimplementedFunction; + public AddNewRouteToMap: (coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => void = unimplementedFunction; public CreatePin: (feature: any) => void = unimplementedFunction; public UpdateMarkerColor: (color: string) => void = unimplementedFunction; public UpdateMarkerIcon: (iconKey: string) => void = unimplementedFunction; + public OpenAnimationPanel: (routeDoc: Doc | undefined) => void = unimplementedFunction; + + @observable + menuType: MapAnchorMenuType = 'standard'; + + @action + public setMenuType = (menuType: MapAnchorMenuType) => { + this.menuType = menuType; + } private allMapPinDocs: Doc[] = []; - private pinDoc: Doc | undefined = undefined + private pinDoc: Doc | undefined = undefined; + + private routeDoc: Doc | undefined = undefined; private title: string | undefined = undefined; @@ -81,6 +92,11 @@ export class MapAnchorMenu extends AntimodeMenu { this.title = StrCast(pinDoc.title ? pinDoc.title : `${NumCast(pinDoc.longitude)}, ${NumCast(pinDoc.latitude)}`) ; } + public setRouteDoc(routeDoc: Doc){ + this.routeDoc = routeDoc; + this.title = StrCast(routeDoc.title ?? 'Map route') + } + public setAllMapboxPins(pinDocs: Doc[]) { this.allMapPinDocs = pinDocs; pinDocs.forEach((p, idx) => { @@ -148,12 +164,11 @@ export class MapAnchorMenu extends AntimodeMenu { // return this.top // } - @observable - menuType: MapAnchorMenuType = 'standard'; + @action DirectionsClick = () => { - this.menuType = 'route'; + this.menuType = 'routeCreation'; } @action @@ -175,6 +190,26 @@ export class MapAnchorMenu extends AntimodeMenu { } } + @action + onMarkerColorChange = (color: ColorState) => { + if (this.pinDoc){ + this.pinDoc.markerColor = color.hex; + } + } + + revertToOriginalMarker = () => { + if (this.pinDoc) { + this.pinDoc.markerType = "MAP_PIN"; + this.pinDoc.markerColor = "#ff5722"; + } + } + + onMarkerIconChange = (iconKey: string) => { + if (this.pinDoc) { + this.pinDoc.markerType = iconKey; + } + } + @observable destinationFeatures: any[] = [] @@ -248,7 +283,8 @@ export class MapAnchorMenu extends AntimodeMenu { if (this.currentRouteInfoMap && this.selectedTransportationType && this.selectedDestinationFeature){ const coordinates = this.currentRouteInfoMap[this.selectedTransportationType].coordinates; console.log(coordinates); - this.AddNewRouteToMap(coordinates, this.title ?? "", this.selectedDestinationFeature.place_name); + console.log(this.selectedDestinationFeature); + this.AddNewRouteToMap(coordinates, this.title ?? "", this.selectedDestinationFeature, this.createPinForDestination); this.HideRoute(); } } @@ -258,11 +294,7 @@ export class MapAnchorMenu extends AntimodeMenu { const markerType = StrCast(this.pinDoc.markerType); const markerColor = StrCast(this.pinDoc.markerColor); - if (markerType.startsWith("MAPBOX")){ - return MarkerIcons.getMapboxIcon(markerColor as MapboxColor); - } else { // font awesome icon - return MarkerIcons.getFontAwesomeIcon(markerType, markerColor); - } + return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor); } return undefined; } @@ -313,7 +345,7 @@ export class MapAnchorMenu extends AntimodeMenu { /> } - {this.menuType === 'route' && + {this.menuType === 'routeCreation' && <> { icon={} color={SettingsManager.userColor} /> + + } + {this.menuType === 'route' && + <> + } + color={SettingsManager.userColor} + /> this.OpenAnimationPanel(this.routeDoc)} /**TODO: fix */ icon={} color={SettingsManager.userColor} /> +
+ } + color={SettingsManager.userColor} + /> +
} color={SettingsManager.userColor} /> + + } {this.menuType === 'customize' && <> @@ -351,7 +403,7 @@ export class MapAnchorMenu extends AntimodeMenu { /> this.revertToOriginalMarker()} icon={} color={SettingsManager.userColor} /> @@ -386,7 +438,7 @@ export class MapAnchorMenu extends AntimodeMenu { {this.menuType === 'standard' &&
{this.title}
} - {this.menuType === 'route' && + {this.menuType === 'routeCreation' &&
{ circleSize={15} circleSpacing={7} width='100%' - onChange={(color) => console.log(color.hex)} + onChange={(color) => this.onMarkerColorChange(color)} />
- {Object.keys(MarkerIcons.FAMarkerIconsMap).map((iconKey) => { - const icon = MarkerIcons.getFontAwesomeIcon(iconKey); - if (icon){ - return ( -
- {}} - icon={MarkerIcons.getFontAwesomeIcon(iconKey, 'white')} - /> -
- ) - } - return null; - })} + {Object.keys(MarkerIcons.FAMarkerIconsMap).map((iconKey) => ( +
+ this.onMarkerIconChange(iconKey)} + icon={MarkerIcons.getFontAwesomeIcon(iconKey, '1x', 'white')} + /> +
+ ))}
+ } + {this.menuType === 'route' && this.routeDoc && +
+ {StrCast(this.routeDoc.title)} +
+ } {buttons} diff --git a/src/client/views/nodes/MapBox/MapBox.scss b/src/client/views/nodes/MapBox/MapBox.scss index bc2f90fbd..d3c6bb14e 100644 --- a/src/client/views/nodes/MapBox/MapBox.scss +++ b/src/client/views/nodes/MapBox/MapBox.scss @@ -82,6 +82,56 @@ } + .animation-panel { + z-index: 900; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + position: absolute; + background-color: rgb(187, 187, 187); + padding: 10px; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + width: 100%; + + #route-to-animate-title { + font-size: 1.25em; + font-weight: bold; + } + + .route-animation-options { + display: flex; + justify-content: flex-start; + align-items: center; + gap: 7px; + + .animation-suboptions{ + display: flex; + justify-content: flex-start; + align-items: center; + gap: 7px; + + label{ + margin-bottom: 0; + } + + .speed-label{ + margin-right: 5px; + } + + #last-divider{ + margin-left: 10px; + margin-right: 10px; + } + } + + + } + + } + + .mapBox-topbar { display: flex; flex-direction: row; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index ac926e1fb..cde68a2e6 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import BingMapsReact from 'bingmaps-react'; // import 'mapbox-gl/dist/mapbox-gl.css'; -import { Button, EditableText, IconButton, Type } from 'browndash-components'; +import { Button, EditableText, IconButton, Size, Type } from 'browndash-components'; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, flow, toJS} from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -51,14 +51,20 @@ import debounce from 'debounce'; import './MapBox.scss'; import { NumberLiteralType } from 'typescript'; // import { GeocoderControl } from './GeocoderControl'; -import mapboxgl, { LngLat, MapLayerMouseEvent } from 'mapbox-gl'; +import mapboxgl, { LngLat, LngLatBoundsLike, 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, faGear } from '@fortawesome/free-solid-svg-icons'; +import { IconLookup, faCircleXmark, faFileExport, faGear, faPause, faPlay, 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'; // amongus /** @@ -142,25 +148,93 @@ export class MapBox extends ViewBoxAnnotatableComponent anno.type === DocumentType.MAPROUTE); } + @computed get updatedRouteCoordinates(): Feature { + if (this.routeToAnimate?.routeCoordinates) { + 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 + const startIndex = Math.floor(index); + const endIndex = Math.ceil(index); + + if (startIndex === endIndex) { + // AnimationPhase is at a whole number (no interpolation needed) + const coordinates = [originalCoordinates[startIndex]]; + const geometry: LineString = { + type: 'LineString', + coordinates, + }; + return { + type: 'Feature', + properties: { + 'routeTitle': StrCast(this.routeToAnimate.title) + }, + geometry: geometry, + }; + } else { + // Interpolate between two coordinates + 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 coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]); + + const geometry: LineString = { + type: 'LineString', + coordinates, + }; + return { + type: 'Feature', + properties: { + 'routeTitle': StrCast(this.routeToAnimate.title) + }, + geometry: geometry, + }; + } + } + return { + type: 'Feature', + properties: {}, + geometry: { + type: 'LineString', + coordinates: [] + }, + }; + } + @computed get selectedRouteCoordinates(): Position[] { + let coordinates: Position[] = []; + if (this.routeToAnimate?.routeCoordinates){ + coordinates = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates)); + } + return coordinates; + } + @computed get allRoutesGeoJson(): FeatureCollection { - const features: Feature[] = this.allRoutes.map(route => { - console.log("Route coords: ", route.coordinates); + const features: Feature[] = this.allRoutes.map((routeDoc: Doc) => { + console.log('Route coords: ', routeDoc.routeCoordinates); const geometry: LineString = { type: 'LineString', - coordinates: JSON.parse(StrCast(route.coordinates)) - } + coordinates: JSON.parse(StrCast(routeDoc.routeCoordinates)), + }; return { - type: 'Feature', - properties: {}, - geometry: geometry + type: 'Feature', + properties: { + 'routeTitle': routeDoc.title}, + geometry: geometry, }; - }); - - return { + }); + + return { type: 'FeatureCollection', - features: features - }; + features: features, + }; } + @computed get SidebarShown() { return this.layoutDoc._layout_showSidebar ? true : false; } @@ -184,7 +258,7 @@ export class MapBox extends ViewBoxAnnotatableComponent this._disposers[key]?.()); } @@ -199,7 +273,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { - let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPin; + let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPinOrRoute; if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) { existingPin = this.createPushpin(NumCast(doc.latitude), NumCast(doc.longitude), StrCast(doc.map)); } @@ -300,10 +374,10 @@ export class MapBox extends ViewBoxAnnotatableComponent { const note = this.getAnchor(true); - if (note && this.selectedPin) { - note.latitude = this.selectedPin.latitude; - note.longitude = this.selectedPin.longitude; - note.map = this.selectedPin.map; + if (note && this.selectedPinOrRoute) { + note.latitude = this.selectedPinOrRoute.latitude; + note.longitude = this.selectedPinOrRoute.longitude; + note.map = this.selectedPinOrRoute.map; } return note as Doc; }); @@ -329,10 +403,10 @@ export class MapBox extends ViewBoxAnnotatableComponent { const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]); - if (note && this.selectedPin) { - note.latitude = this.selectedPin.latitude; - note.longitude = this.selectedPin.longitude; - note.map = this.selectedPin.map; + if (note && this.selectedPinOrRoute) { + note.latitude = this.selectedPinOrRoute.latitude; + note.longitude = this.selectedPinOrRoute.longitude; + note.map = this.selectedPinOrRoute.map; } }), 'create note annotation' @@ -410,11 +484,12 @@ export class MapBox extends ViewBoxAnnotatableComponent { - if (this.selectedPin) { + deselectPinOrRoute = () => { + if (this.selectedPinOrRoute) { // // Removes filter // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove'); // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove'); @@ -435,6 +510,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(); return new Promise>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); @@ -444,22 +520,22 @@ export class MapBox extends ViewBoxAnnotatableComponent { - this.deselectPin(); - this.selectedPin = pinDoc; + this.deselectPinOrRoute(); + this.selectedPinOrRoute = pinDoc; this.bingSearchBarContents = pinDoc.map; // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match'); // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match'); - Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check'); + Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); - this.recolorPin(this.selectedPin, 'green'); + this.recolorPin(this.selectedPinOrRoute, 'green'); - MapAnchorMenu.Instance.Delete = this.deleteSelectedPin; + MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute; MapAnchorMenu.Instance.Center = this.centerOnSelectedPin; MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation; MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag; - const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPin.latitude, this.selectedPin.longitude)); + const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPinOrRoute.latitude, this.selectedPinOrRoute.longitude)); const x = point.x + (this.props.PanelWidth() - this.sidebarWidth()) / 2; const y = point.y + this.props.PanelHeight() / 2 + 32; const cpt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y); @@ -474,7 +550,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { this.props.select(false); - this.deselectPin(); + this.deselectPinOrRoute(); }; /* * Updates values of layout doc to match the current map @@ -520,14 +596,14 @@ export class MapBox extends ViewBoxAnnotatableComponent this.removeMapDocument(pinDoc, this.annotationKey); + removePushpinOrRoute = (pinOrRouteDoc: Doc) => this.removeMapDocument(pinOrRouteDoc, this.annotationKey); /* * Removes pushpin from map render @@ -576,23 +652,25 @@ export class MapBox extends ViewBoxAnnotatableComponent { - if (this.selectedPin) { + deleteSelectedPinOrRoute = undoable(() => { + if (this.selectedPinOrRoute) { // Removes filter - Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove'); - Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove'); - Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove'); + Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPinOrRoute.latitude, 'remove'); + Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPinOrRoute.longitude, 'remove'); + Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPinOrRoute))}`, 'remove'); - this.removePushpin(this.selectedPin); + this.removePushpinOrRoute(this.selectedPinOrRoute); } MapAnchorMenu.Instance.fadeOut(true); document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true); }, 'delete pin'); + + tryHideMapAnchorMenu = (e: PointerEvent) => { let target = document.elementFromPoint(e.x, e.y); while (target) { @@ -608,9 +686,9 @@ export class MapBox extends ViewBoxAnnotatableComponent { - if (this.selectedPin) { + if (this.selectedPinOrRoute) { this._mapRef.current?.flyTo({ - center: [NumCast(this.selectedPin.longitude), NumCast(this.selectedPin.latitude)] + center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)] }) } // if (this.selectedPin) { @@ -776,12 +854,12 @@ export class MapBox extends ViewBoxAnnotatableComponent { - console.log(coordinates); + createMapRoute = undoable((coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => { const mapRoute = Docs.Create.MapRouteDocument( false, [], - {title: `${origin} -> ${destination}`, routeCoordinates: JSON.stringify(coordinates)}, + {title: `${origin} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates)}, ); this.addDocument(mapRoute, this.annotationKey); + if (createPinForDestination) { + this.createPushpin(destination.center[1], destination.center[0], destination.place_name); + } return mapRoute; // mapMarker.infoWindowOpen = true; @@ -853,10 +933,11 @@ export class MapBox extends ViewBoxAnnotatableComponent { const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText); - if (features){ + if (features && !this.isAnimating){ runInAction(() => { this.settingsOpen= false; this.featuresFromGeocodeResults = features; + this.routeToAnimate = undefined; }) } // try { @@ -874,12 +955,65 @@ export class MapBox extends ViewBoxAnnotatableComponent { + if (this._mapRef.current){ + const features = this._mapRef.current.queryRenderedFeatures( + e.point, { + layers: ['map-routes-layer'] + } + ); + + console.error(features); + if (features && features.length > 0 && features[0].properties && features[0].geometry) { + const geometry = features[0].geometry as LineString; + const routeTitle: string = features[0].properties['routeTitle']; + const routeDoc: Doc | undefined = this.allRoutes.find((routeDoc) => routeDoc.title === routeTitle); + this.deselectPinOrRoute(); // TODO: Also deselect route if selected + if (routeDoc){ + this.selectedPinOrRoute = routeDoc; + Doc.setDocFilter(this.rootDoc, LinkedTo, `mapRoute=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); + + // TODO: Recolor route + + MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute; + MapAnchorMenu.Instance.Center = this.centerOnSelectedPin; + MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation; + MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag; + + MapAnchorMenu.Instance.setRouteDoc(routeDoc); + + // TODO: Subject to change + MapAnchorMenu.Instance.setAllMapboxPins( + this.allAnnotations.filter(anno => !anno.layout_unrendered) + ) + + MapAnchorMenu.Instance.DisplayRoute = this.displayRoute; + MapAnchorMenu.Instance.HideRoute = this.hideRoute; + MapAnchorMenu.Instance.AddNewRouteToMap = this.createMapRoute; + MapAnchorMenu.Instance.CreatePin = this.addMarkerForFeature; + MapAnchorMenu.Instance.OpenAnimationPanel = this.openAnimationPanel; + + // this.selectedRouteCoordinates = geometry.coordinates; + + MapAnchorMenu.Instance.setMenuType('route'); + + MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true); + + document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true); + } + } + } + } + + /** * Makes a reverse geocoding API call to retrieve features corresponding to a map click (based on longitude * and latitude). Sets the search results accordingly. * @param e */ - handleMapClick = async (e: MapLayerMouseEvent) => { + handleMapDblClick = async (e: MapLayerMouseEvent) => { e.preventDefault(); const lngLat: LngLat = e.lngLat; const longitude: number = lngLat.lng; @@ -914,18 +1048,18 @@ export class MapBox extends ViewBoxAnnotatableComponent, pinDoc: Doc) => { this.featuresFromGeocodeResults = []; - this.deselectPin(); // TODO: check this method - this.selectedPin = pinDoc; + this.deselectPinOrRoute(); // TODO: check this method + this.selectedPinOrRoute = pinDoc; // this.bingSearchBarContents = pinDoc.map; // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match'); // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match'); - Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check'); + Doc.setDocFilter(this.rootDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); - this.recolorPin(this.selectedPin, 'green'); // TODO: check this method + this.recolorPin(this.selectedPinOrRoute, 'green'); // TODO: check this method - MapAnchorMenu.Instance.Delete = this.deleteSelectedPin; + MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute; MapAnchorMenu.Instance.Center = this.centerOnSelectedPin; MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation; MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag; @@ -941,11 +1075,8 @@ export class MapBox extends ViewBoxAnnotatableComponent { + this.animationPhase = newValue; + }; + + @observable + frameId: number | null = null; + + @action + setFrameId = (frameId: number) => { + this.frameId = frameId; + } + + @action + openAnimationPanel = (routeDoc: Doc | undefined) => { + if (routeDoc){ + MapAnchorMenu.Instance.fadeOut(true); + document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true); + this.routeToAnimate = routeDoc; + } + } + + @observable + animationDuration = 40000; + + @observable + animationAltitude = 12800; + + @observable + pathDistance = 0; + + @observable + isStreetViewAnimation: boolean = false; + + @observable + animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM; + + @action + updateAnimationSpeed = () => { + switch (this.animationSpeed){ + case AnimationSpeed.SLOW: + this.animationSpeed = AnimationSpeed.MEDIUM; + break; + case AnimationSpeed.MEDIUM: + this.animationSpeed = AnimationSpeed.FAST; + break; + case AnimationSpeed.FAST: + this.animationSpeed = AnimationSpeed.SLOW; + break; + default: + this.animationSpeed = AnimationSpeed.MEDIUM; + break; + } + } + @computed get animationSpeedTooltipText(): string { + switch (this.animationSpeed) { + case AnimationSpeed.SLOW: + return '1x speed'; + case AnimationSpeed.MEDIUM: + return '2x speed'; + case AnimationSpeed.FAST: + return '3x speed'; + default: + return '2x speed'; + } + } + @computed get animationSpeedIcon(): JSX.Element{ + switch (this.animationSpeed) { + case AnimationSpeed.SLOW: + return slowSpeedIcon; + case AnimationSpeed.MEDIUM: + return mediumSpeedIcon; + case AnimationSpeed.FAST: + return fastSpeedIcon; + default: + return mediumSpeedIcon; + } + } + + @action + toggleIsStreetViewAnimation = () => { + this.isStreetViewAnimation = !this.isStreetViewAnimation; + } + + @observable + dynamicRouteFeature: Feature = { + type: 'Feature', + properties: {}, + geometry: { + type: 'LineString', + coordinates: [] + } + }; + + + @observable + path: turf.helpers.Feature = { + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [] + }, + properties: {} + }; + + getFeatureFromRouteDoc = (routeDoc: Doc): Feature => { + const geometry: LineString = { + type: 'LineString', + coordinates: JSON.parse(StrCast(routeDoc.routeCoordinates)), + }; + return { + type: 'Feature', + properties: { + 'routeTitle': routeDoc.title}, + geometry: geometry, + }; + } + + @action + playAnimation = (status: AnimationStatus) => { + if (!this._mapRef.current || !this.routeToAnimate){ + 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; + + const path = turf.lineString(this.selectedRouteCoordinates); + + this.settingsOpen = false; + this.path = path; + this.pathDistance = turf.lineDistance(path); + this.isAnimating = true; + runInAction(() => { + return new Promise(async (resolve) => { + let animationUtil; + try { + const targetLngLat = { + lng: this.selectedRouteCoordinates[0][0], + lat: this.selectedRouteCoordinates[0][1], + }; + + animationUtil = new AnimationUtility( + targetLngLat, + this.selectedRouteCoordinates, + this.isStreetViewAnimation, + this.animationSpeed + ); + + + const updateFrameId = (newFrameId: number) => { + this.setFrameId(newFrameId); + } + + const updateAnimationPhase = ( + newAnimationPhase: number, + ) => { + this.setAnimationPhase(newAnimationPhase); + }; + + if (status !== AnimationStatus.RESUME) { + + const result = await animationUtil.flyInAndRotate({ + map: this._mapRef.current!, + // targetLngLat, + // duration 3000 + // startAltitude: 3000000, + // endAltitude: this.isStreetViewAnimation ? 80 : 12000, + // startBearing: 0, + // endBearing: -20, + // startPitch: 40, + // endPitch: this.isStreetViewAnimation ? 80 : 50, + updateFrameId, + }); + + console.log("Bearing: ", result.bearing); + console.log("Altitude: ", result.altitude); + + } + + runInAction(() => { + this.finishedFlyTo = true; + }) + + // follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation + await animationUtil.animatePath({ + map: this._mapRef.current!, + // path: this.path, + // startBearing: -20, + // startAltitude: this.isStreetViewAnimation ? 80 : 12000, + // pitch: this.isStreetViewAnimation ? 80: 50, + currentAnimationPhase: this.animationPhase, + updateAnimationPhase, + updateFrameId, + }); + + // get the bounds of the linestring, use fitBounds() to animate to a final view + const bbox3d = turf.bbox(this.path); + + const bbox2d: LngLatBoundsLike = [bbox3d[0], bbox3d[1], bbox3d[2], bbox3d[3]]; + + this._mapRef.current!.fitBounds(bbox2d, { + duration: 3000, + pitch: 30, + bearing: 0, + padding: 120, + }); + + setTimeout(() => { + resolve(); + }, 10000); + } catch (error: any){ + console.log(error); + console.log('animation util: ', animationUtil); + }}); + + }) + + + } + + + @action + pauseAnimation = () => { + if (this.frameId && this.animationPhase > 0){ + window.cancelAnimationFrame(this.frameId); + this.frameId = null; + this.isAnimating = false; + } + } + + @action + stopAndCloseAnimation = () => { + if (this.frameId){ + window.cancelAnimationFrame(this.frameId); + this.frameId = null; + this.finishedFlyTo = false; + this.isAnimating = false; + this.animationPhase = 0; + this.routeToAnimate = undefined; + // this.selectedRouteCoordinates = []; + } + // reset bearing and pitch to original, zoom out + } + + @action + exportAnimationToVideo = () => { + + } + + getRouteAnimationOptions = (): JSX.Element => { + return ( + <> + { + if (this.isAnimating && this.finishedFlyTo) { + this.pauseAnimation(); + } else if (this.animationPhase > 0) { + this.playAnimation(AnimationStatus.RESUME); // Resume from the current phase + } else { + this.playAnimation(AnimationStatus.START); // Play from the beginning + } + }} + icon={this.isAnimating && this.finishedFlyTo ? + + : + + } + color='black' + size={Size.MEDIUM} + /> + {this.isAnimating && this.finishedFlyTo && + this.playAnimation(AnimationStatus.RESTART)} + icon={} + color='black' + size={Size.MEDIUM} + /> + + } + } + color='black' + size={Size.MEDIUM} + /> + } + color='black' + size={Size.MEDIUM} + /> + {!this.isAnimating && + <> +
+
|
+ + } + /> +
|
+ +
+ + } + + ) + } + @action hideRoute = () => { this.temporaryRouteSource = { @@ -1015,8 +1491,10 @@ export class MapBox extends ViewBoxAnnotatableComponent { - this.featuresFromGeocodeResults = []; - this.settingsOpen = !this.settingsOpen; + if (!this.isAnimating && this.animationPhase == 0) { + this.featuresFromGeocodeResults = []; + this.settingsOpen = !this.settingsOpen; + } } @action @@ -1056,6 +1534,16 @@ export class MapBox extends ViewBoxAnnotatableComponent { + const markerType = StrCast(pinDoc.markerType); + const markerColor = StrCast(pinDoc.markerColor); + + return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null; + + } + + + @@ -1092,21 +1580,22 @@ export class MapBox extends ViewBoxAnnotatableComponent{renderAnnotations(this.transparentFilter)} {renderAnnotations(this.opaqueFilter)} {SnappingManager.GetIsDragging() ? null : renderAnnotations()} + {!this.routeToAnimate && +
+ this.handleSearchChange(e.target.value)} + /> + } + type={Type.TERT} + onClick={(e) => this.toggleSettings()} -
- this.handleSearchChange(e.target.value)} - /> - } - type={Type.TERT} - onClick={(e) => this.toggleSettings()} - - /> -
- {this.settingsOpen && + /> +
+ } + {this.settingsOpen && !this.routeToAnimate &&
@@ -1151,45 +1640,52 @@ export class MapBox extends ViewBoxAnnotatableComponent
} - -
- {this.featuresFromGeocodeResults.length > 0 && ( + {this.routeToAnimate && +
+
+ {StrCast(this.routeToAnimate.title)} +
+
+ {this.getRouteAnimationOptions()} +
+
+ } + {this.featuresFromGeocodeResults.length > 0 && ( +
-

Choose a location for your pin:

- {this.featuresFromGeocodeResults - .filter(feature => feature.place_name) - .map((feature, idx) => ( -
{ - this.handleSearchChange(""); - this.addMarkerForFeature(feature); - }} - > -
- {feature.place_name} +

Choose a location for your pin:

+ {this.featuresFromGeocodeResults + .filter(feature => feature.place_name) + .map((feature, idx) => ( +
{ + this.handleSearchChange(""); + this.addMarkerForFeature(feature); + }} + > +
+ {feature.place_name} +
-
))} - )} -
+ +
+ )} + {!this.isAnimating && this.animationPhase == 0 && + } + {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) && + <> + {!this.isStreetViewAnimation && + <> + + + + } + + + + + + + + + + + } + <> - {this.allPushpins + {!this.isAnimating && this.animationPhase == 0 && this.allPushpins // .filter(anno => !anno.layout_unrendered) .map((pushpin, idx) => ( ) => this.handleMarkerClick(e, pushpin)} - /> + > + {this.getMarkerIcon(pushpin)} + ))} diff --git a/src/client/views/nodes/MapBox/MarkerIcons.tsx b/src/client/views/nodes/MapBox/MarkerIcons.tsx index cf50109ac..146f296c1 100644 --- a/src/client/views/nodes/MapBox/MarkerIcons.tsx +++ b/src/client/views/nodes/MapBox/MarkerIcons.tsx @@ -1,57 +1,46 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faShopify } from '@fortawesome/free-brands-svg-icons'; -import { IconLookup, faBasketball, faBicycle, faBowlFood, faBus, faCameraRetro, faCar, faCartShopping, faFilm, faFootball, faFutbol, faHockeyPuck, faHospital, faHotel, faHouse, faLandmark, faMasksTheater, faMugSaucer, faPersonHiking, faPlane, faSchool, faShirt, faShop, faSquareParking, faStar, faTrainSubway, faTree, faUtensils, faVolleyball } from '@fortawesome/free-solid-svg-icons'; +import { faBasketball, faBicycle, faBowlFood, faBus, faCameraRetro, faCar, faCartShopping, faFilm, faFootball, faFutbol, faHockeyPuck, faHospital, faHotel, faHouse, faLandmark, faLocationDot, faLocationPin, faMapPin, faMasksTheater, faMugSaucer, faPersonHiking, faPlane, faSchool, faShirt, faShop, faSquareParking, faStar, faTrainSubway, faTree, faUtensils, faVolleyball } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import React = require('react'); -export type MapboxColor = 'yellow' | 'red' | 'orange' | 'purple' | 'pink' | 'blue' | 'green'; -type ColorProperties = { - fill: string, - stroke: string -} -type ColorsMap = { - [key in MapboxColor]: ColorProperties; -} - export class MarkerIcons { - static getMapboxIcon = (color: string) => { - return ( - - - - - - - - - - - - - - - - ) - } + // static getMapboxIcon = (color: string) => { + // return ( + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // ) + // } - static getFontAwesomeIcon(key: string, color?: string): JSX.Element | undefined { + static getFontAwesomeIcon(key: string, size: string, color?: string): JSX.Element { const icon: IconProp = MarkerIcons.FAMarkerIconsMap[key]; + const iconProps: any = { icon }; - if (icon) { - const iconProps: any = { icon }; - - if (color) { - iconProps.color = color; - } - - return (); - } + if (color) { + iconProps.color = color; + } + + return (); + - return undefined; } static FAMarkerIconsMap: {[key: string]: IconProp} = { + 'MAP_PIN': faLocationDot, 'RESTAURANT_ICON': faUtensils, 'HOTEL_ICON': faHotel, 'HOUSE_ICON': faHouse, -- cgit v1.2.3-70-g09d2