aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/MapBox/MapBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/MapBox/MapBox.tsx')
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx1188
1 files changed, 1069 insertions, 119 deletions
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index a420e0101..8b5858e28 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -1,15 +1,18 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import BingMapsReact from 'bingmaps-react';
-import { Button, EditableText, IconButton, Type } from 'browndash-components';
-import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
+// import 'mapbox-gl/dist/mapbox-gl.css';
+
+import { Button, EditableText, IconButton, Size, Type } from 'browndash-components';
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, makeObservable, flow, toJS } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
import { DocCss, Highlight } from '../../../../fields/DocSymbols';
-import { DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { Id } from '../../../../fields/FieldSymbols';
+import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
+import { Docs, DocUtils } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
-import { DocUtils, Docs } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager } from '../../../util/DragManager';
import { LinkManager } from '../../../util/LinkManager';
@@ -24,7 +27,43 @@ import { FieldView, FieldViewProps } from '../FieldView';
import { FormattedTextBox } from '../formattedText/FormattedTextBox';
import { PinProps, PresBox } from '../trails';
import { MapAnchorMenu } from './MapAnchorMenu';
+import {
+ Map as MapboxMap,
+ MapRef,
+ Marker,
+ ControlPosition,
+ FullscreenControl,
+ MapProvider,
+ MarkerProps,
+ NavigationControl,
+ ScaleControl,
+ ViewState,
+ ViewStateChangeEvent,
+ useControl,
+ GeolocateControl,
+ Popup,
+ MapEvent,
+ Source,
+ Layer,
+} from 'react-map-gl';
import './MapBox.scss';
+import { NumberLiteralType } from 'typescript';
+// import { GeocoderControl } from './GeocoderControl';
+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, 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
/**
* MapBox architecture:
@@ -40,6 +79,30 @@ import './MapBox.scss';
*/
const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/ and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS=<your apikey>
+const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3MXIydm92ODdianp6cGYifQ.WFAqbhwxtMHOWSPtu0l2uQ';
+const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
+
+const MAPBOX_REVERSE_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/';
+
+type PopupInfo = {
+ longitude: number;
+ latitude: number;
+ title: string;
+ description: string;
+};
+
+// export type GeocoderControlProps = Omit<GeocoderOptions, 'accessToken' | 'mapboxgl' | 'marker'> & {
+// mapboxAccessToken: string;
+// marker?: Omit<MarkerProps, 'longitude' | 'latitude'>;
+// position: ControlPosition;
+
+// onResult: (...args: any[]) => void;
+// };
+
+type MapMarker = {
+ longitude: number;
+ latitude: number;
+};
/**
* Consider integrating later: allows for drawing, circling, making shapes on map
@@ -65,6 +128,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _dragRef = React.createRef<HTMLDivElement>();
private _sidebarRef = React.createRef<SidebarAnnos>();
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+ private _mapRef: React.RefObject<MapRef> = React.createRef();
private _disposers: { [key: string]: IReactionDisposer } = {};
constructor(props: any) {
@@ -85,6 +149,94 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@computed get allPushpins() {
return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN);
}
+ @computed get allRoutes() {
+ return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE);
+ }
+ @computed get updatedRouteCoordinates(): Feature<Geometry, GeoJsonProperties> {
+ 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<Geometry, GeoJsonProperties>[] = this.allRoutes.map((routeDoc: Doc) => {
+ console.log('Route coords: ', routeDoc.routeCoordinates);
+ const geometry: LineString = {
+ type: 'LineString',
+ coordinates: JSON.parse(StrCast(routeDoc.routeCoordinates)),
+ };
+ return {
+ type: 'Feature',
+ properties: {
+ routeTitle: routeDoc.title,
+ },
+ geometry: geometry,
+ };
+ });
+
+ return {
+ type: 'FeatureCollection',
+ features: features,
+ };
+ }
+
@computed get SidebarShown() {
return this.layoutDoc._layout_showSidebar ? true : false;
}
@@ -105,7 +257,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
componentWillUnmount(): void {
this._unmounting = true;
- this.deselectPin();
+ this.deselectPinOrRoute();
this._rerenderTimeout && clearTimeout(this._rerenderTimeout);
Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
}
@@ -120,7 +272,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
const docs = doc instanceof Doc ? [doc] : doc;
docs.forEach(doc => {
- 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));
}
@@ -221,10 +373,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const sourceAnchorCreator = action(() => {
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;
});
@@ -250,10 +402,10 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const createFunc = undoable(
action(() => {
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'
@@ -328,49 +480,28 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
entityType: 'PopulatedPlace',
};
- // incrementer: number = 0;
- /*
- * Creates Pushpin doc and adds it to the list of annotations
- */
- @action
- createPushpin = undoable((latitude: number, longitude: number, map?: string) => {
- // Stores the pushpin as a MapMarkerDocument
- const pushpin = Docs.Create.PushpinDocument(
- NumCast(latitude),
- NumCast(longitude),
- false,
- [],
- { title: map ?? `lat=${latitude},lng=${longitude}`, map: map }
- // ,'pushpinIDamongus'+ this.incrementer++
- );
- this.addDocument(pushpin, this.annotationKey);
- return pushpin;
- // mapMarker.infoWindowOpen = true;
- }, 'createpin');
-
// The pin that is selected
- @observable selectedPin: Doc | undefined = undefined;
+ @observable selectedPinOrRoute: Doc | undefined;
@action
- deselectPin = () => {
- if (this.selectedPin) {
- // Removes filter
- Doc.setDocFilter(this.layoutDoc, 'latitude', this.selectedPin.latitude, 'remove');
- Doc.setDocFilter(this.layoutDoc, 'longitude', this.selectedPin.longitude, 'remove');
- Doc.setDocFilter(this.layoutDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
-
- const temp = this.selectedPin;
- if (!this._unmounting) {
- this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp));
- }
- const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude));
- this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(temp as Doc));
- if (!this._unmounting) {
- this._bingMap.current.entities.push(newpin);
- }
- this.map_docToPinMap.set(temp, newpin);
- this.selectedPin = undefined;
- this.bingSearchBarContents = this.dataDoc.map;
+ deselectPinOrRoute = () => {
+ if (this.selectedPinOrRoute) {
+ // // Removes filter
+ // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove');
+ // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove');
+ // Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+ // const temp = this.selectedPin;
+ // if (!this._unmounting) {
+ // this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp));
+ // }
+ // const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude));
+ // this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(temp as Doc));
+ // if (!this._unmounting) {
+ // this._bingMap.current.entities.push(newpin);
+ // }
+ // this.map_docToPinMap.set(temp, newpin);
+ // this.selectedPin = undefined;
+ // this.bingSearchBarContents = this.Document.map;
}
};
@@ -383,22 +514,22 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
*/
@action
pushpinClicked = (pinDoc: Doc) => {
- this.deselectPin();
- this.selectedPin = pinDoc;
+ this.deselectPinOrRoute();
+ this.selectedPinOrRoute = pinDoc;
this.bingSearchBarContents = pinDoc.map;
// Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
// Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
- Doc.setDocFilter(this.layoutDoc, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check');
+ Doc.setDocFilter(this.Document, 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);
@@ -413,7 +544,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
mapOnClick = (e: { location: { latitude: any; longitude: any } }) => {
this._props.select(false);
- this.deselectPin();
+ this.deselectPinOrRoute();
};
/*
* Updates values of layout doc to match the current map
@@ -460,14 +591,14 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
/// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER
const anchor = Docs.Create.ConfigDocument({
title: 'MapAnchor:' + this.Document.title,
- text: StrCast(this.selectedPin?.map) || StrCast(this.dataDoc.map) || 'map location',
- config_latitude: NumCast((existingPin ?? this.selectedPin)?.latitude ?? this.dataDoc.latitude),
- config_longitude: NumCast((existingPin ?? this.selectedPin)?.longitude ?? this.dataDoc.longitude),
+ text: StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location',
+ 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: StrCast((existingPin ?? this.selectedPin)?.map) || StrCast(this.dataDoc.map),
+ config_map: StrCast((existingPin ?? this.selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map),
layout_unrendered: true,
- mapPin: existingPin ?? this.selectedPin,
+ mapPin: existingPin ?? this.selectedPinOrRoute,
annotationOn: this.Document,
});
if (anchor) {
@@ -506,7 +637,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
* Removes pin from annotations
*/
@action
- removePushpin = (pinDoc: Doc) => this.removeMapDocument(pinDoc, this.annotationKey);
+ removePushpinOrRoute = (pinOrRouteDoc: Doc) => this.removeMapDocument(pinOrRouteDoc, this.annotationKey);
/*
* Removes pushpin from map render
@@ -516,18 +647,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this._bingMap.current.entities.remove(this.map_docToPinMap.get(pinDoc));
}
this.map_docToPinMap.delete(pinDoc);
- this.selectedPin = undefined;
+ this.selectedPinOrRoute = undefined;
};
@action
- deleteSelectedPin = undoable(() => {
- if (this.selectedPin) {
+ deleteSelectedPinOrRoute = undoable(() => {
+ if (this.selectedPinOrRoute) {
// Removes filter
- Doc.setDocFilter(this.layoutDoc, 'latitude', this.selectedPin.latitude, 'remove');
- Doc.setDocFilter(this.layoutDoc, 'longitude', this.selectedPin.longitude, 'remove');
- Doc.setDocFilter(this.layoutDoc, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
+ Doc.setDocFilter(this.Document, 'latitude', this.selectedPinOrRoute.latitude, 'remove');
+ Doc.setDocFilter(this.Document, 'longitude', this.selectedPinOrRoute.longitude, 'remove');
+ Doc.setDocFilter(this.Document, 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);
@@ -536,6 +667,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
tryHideMapAnchorMenu = (e: PointerEvent) => {
let target = document.elementFromPoint(e.x, e.y);
while (target) {
+ if (target.id === 'route-destination-searcher-listbox') return;
if (target === MapAnchorMenu.top.current) return;
target = target.parentElement;
}
@@ -547,12 +679,17 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
centerOnSelectedPin = () => {
- if (this.selectedPin) {
- this.dataDoc.latitude = this.selectedPin.latitude;
- this.dataDoc.longitude = this.selectedPin.longitude;
- this.dataDoc.map = this.selectedPin.map ?? '';
- this.bingSearchBarContents = this.selectedPin.map;
+ if (this.selectedPinOrRoute) {
+ this._mapRef.current?.flyTo({
+ center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)],
+ });
}
+ // if (this.selectedPin) {
+ // this.dataDoc.latitude = this.selectedPin.latitude;
+ // this.dataDoc.longitude = this.selectedPin.longitude;
+ // this.dataDoc.map = this.selectedPin.map ?? '';
+ // this.bingSearchBarContents = this.selectedPin.map;
+ // }
MapAnchorMenu.Instance.fadeOut(true);
document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu);
};
@@ -584,16 +721,13 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
},
};
- @action
- searchbarOnEdit = (newText: string) => (this.bingSearchBarContents = newText);
-
recolorPin = (pin: Doc, color?: string) => {
- this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
- this.map_docToPinMap.delete(pin);
- const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), color ? { color } : {});
- this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(pin));
- this._bingMap.current.entities.push(newpin);
- this.map_docToPinMap.set(pin, newpin);
+ // this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
+ // this.map_docToPinMap.delete(pin);
+ // const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), color ? { color } : {});
+ // this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(pin));
+ // this._bingMap.current.entities.push(newpin);
+ // this.map_docToPinMap.set(pin, newpin);
};
/*
@@ -664,24 +798,26 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
e,
e,
e => {
+ // move event
if (!dragClone) {
- dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement;
+ dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; // copy draggable pin
dragClone.style.position = 'absolute';
dragClone.style.zIndex = '10000';
- DragManager.Root().appendChild(dragClone);
+ DragManager.Root().appendChild(dragClone); // add clone to root
}
dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`;
return false;
},
e => {
+ // up event
if (!dragClone) return;
DragManager.Root().removeChild(dragClone);
- let target = document.elementFromPoint(e.x, e.y);
+ let target = document.elementFromPoint(e.x, e.y); // element for specified x and y coordinates
while (target) {
if (target === this._ref.current) {
const cpt = this._props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2;
- const y = cpt[1] - 32 /* height of search bar */ - this._props.PanelHeight() / 2;
+ const y = cpt[1] - 20 /* height of search bar */ - this._props.PanelHeight() / 2;
const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y));
this.createPushpin(location.latitude, location.longitude);
break;
@@ -698,8 +834,623 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
};
+ // incrementer: number = 0;
+ /*
+ * Creates Pushpin doc and adds it to the list of annotations
+ */
+ @action
+ createPushpin = undoable((latitude: number, longitude: number, location?: string, wikiData?: string) => {
+ // Stores the pushpin as a MapMarkerDocument
+ const pushpin = Docs.Create.PushpinDocument(
+ NumCast(latitude),
+ NumCast(longitude),
+ false,
+ [],
+ {
+ title: location ?? `lat=${NumCast(latitude)},lng=${NumCast(longitude)}`,
+ map: location,
+ description: '',
+ wikiData: wikiData,
+ markerType: 'MAP_PIN',
+ markerColor: '#ff5722',
+ }
+ // { title: map ?? `lat=${latitude},lng=${longitude}`, map: map },
+ // ,'pushpinIDamongus'+ this.incrementer++
+ );
+ this.addDocument(pushpin, this.annotationKey);
+ console.log(pushpin);
+ return pushpin;
+
+ // mapMarker.infoWindowOpen = true;
+ }, 'createpin');
+
+ @action
+ createMapRoute = undoable((coordinates: Position[], origin: string, destination: any, createPinForDestination: boolean) => {
+ const mapRoute = Docs.Create.MapRouteDocument(false, [], { 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;
+ }, 'createmaproute');
+
searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch();
+ @observable
+ featuresFromGeocodeResults: any[] = [];
+
+ @action
+ addMarkerForFeature = (feature: any) => {
+ const location = feature.place_name;
+ if (feature.center) {
+ const longitude = feature.center[0];
+ const latitude = feature.center[1];
+ const wikiData = feature.properties?.wikiData;
+
+ this.createPushpin(latitude, longitude, location, wikiData);
+
+ if (this._mapRef.current) {
+ this._mapRef.current.flyTo({
+ center: feature.center,
+ });
+ }
+ this.featuresFromGeocodeResults = [];
+ } else {
+ // TODO: handle error
+ }
+ };
+
+ /**
+ * Makes a forward geocoding API call to Mapbox to retrieve locations based on the search input
+ * @param searchText the search input (presumably a location)
+ */
+ handleSearchChange = async (searchText: string) => {
+ const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText);
+ if (features && !this.isAnimating) {
+ runInAction(() => {
+ this.settingsOpen = false;
+ this.featuresFromGeocodeResults = features;
+ this.routeToAnimate = undefined;
+ });
+ }
+ // try {
+ // const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`;
+ // const response = await fetch(url);
+ // const data = await response.json();
+ // runInAction(() => {
+ // this.featuresFromGeocodeResults = data.features;
+ // })
+ // } catch (error: any){
+ // // TODO: handle error in better way
+ // console.log(error);
+ // }
+ };
+ // @action
+ // debouncedCall = React.useCallback(debounce(this.debouncedOnSearchBarChange, 300), []);
+
+ @action
+ handleMapClick = (e: MapLayerMouseEvent) => {
+ 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.Document, 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
+ */
+ handleMapDblClick = async (e: MapLayerMouseEvent) => {
+ e.preventDefault();
+ const lngLat: LngLat = e.lngLat;
+ const longitude: number = lngLat.lng;
+ const latitude: number = lngLat.lat;
+
+ const features = await MapboxApiUtility.reverseGeocodeForFeatures(longitude, latitude);
+ if (features) {
+ runInAction(() => {
+ this.featuresFromGeocodeResults = features;
+ });
+ }
+
+ // // REVERSE GEOCODE TO GET LOCATION DETAILS
+ // try {
+ // const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + "," + latitude.toString()) + '.json' +
+ // `?access_token=${MAPBOX_ACCESS_TOKEN}`;
+ // const response = await fetch(url);
+ // const data = await response.json();
+ // console.log("REV GEOCODE DATA: ", data);
+ // runInAction(() => {
+ // this.featuresFromGeocodeResults = data.features;
+ // })
+ // } catch (error: any){
+ // // TODO: handle error in better way
+ // console.log(error);
+ // }
+ };
+
+ @observable
+ currentPopup: PopupInfo | undefined = undefined;
+
+ @action
+ handleMarkerClick = (e: MarkerEvent<mapboxgl.Marker, MouseEvent>, pinDoc: Doc) => {
+ this.featuresFromGeocodeResults = [];
+ this.deselectPinOrRoute(); // TODO: check this method
+ this.selectedPinOrRoute = pinDoc;
+ // this.bingSearchBarContents = pinDoc.map;
+
+ // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
+ // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
+ Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check');
+
+ this.recolorPin(this.selectedPinOrRoute, 'green'); // TODO: check this method
+
+ MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
+ MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
+ MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
+ MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;
+
+ // pass in the pinDoc
+ MapAnchorMenu.Instance.setPinDoc(pinDoc);
+ 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.setMenuType('standard');
+
+ MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true);
+
+ document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
+ };
+
+ @observable
+ temporaryRouteSource: FeatureCollection = {
+ type: 'FeatureCollection',
+ features: [],
+ };
+
+ @action
+ displayRoute = (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => {
+ if (routeInfoMap) {
+ const newTempRouteSource: FeatureCollection = {
+ type: 'FeatureCollection',
+ features: [
+ {
+ type: 'Feature',
+ properties: {},
+ geometry: {
+ type: 'LineString',
+ coordinates: routeInfoMap[type].coordinates,
+ },
+ },
+ ],
+ };
+ // TODO: Create pin for destination
+ // TODO: Fly to point where full route will be shown
+ this.temporaryRouteSource = newTempRouteSource;
+ }
+ };
+
+ @observable
+ isAnimating: boolean = false;
+
+ @observable
+ isPaused: boolean = false;
+
+ @observable
+ routeToAnimate: Doc | undefined = undefined;
+
+ @observable
+ animationPhase: number = 0;
+
+ @observable
+ finishedFlyTo: boolean = false;
+
+ @action
+ setAnimationPhase = (newValue: number) => {
+ 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<Geometry, GeoJsonProperties> = {
+ type: 'Feature',
+ properties: {},
+ geometry: {
+ type: 'LineString',
+ coordinates: [],
+ },
+ };
+
+ @observable
+ path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = {
+ type: 'Feature',
+ geometry: {
+ type: 'LineString',
+ coordinates: [],
+ },
+ properties: {},
+ };
+
+ getFeatureFromRouteDoc = (routeDoc: Doc): Feature<Geometry, GeoJsonProperties> => {
+ 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<void>(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 (
+ <>
+ <IconButton
+ tooltip={this.isAnimating && this.finishedFlyTo ? 'Pause Animation' : 'Play Animation'}
+ onPointerDown={() => {
+ 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 ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />}
+ color="black"
+ size={Size.MEDIUM}
+ />
+ {this.isAnimating && this.finishedFlyTo && (
+ <IconButton tooltip="Restart animation" onPointerDown={() => this.playAnimation(AnimationStatus.RESTART)} icon={<FontAwesomeIcon icon={faRotate as IconLookup} />} color="black" size={Size.MEDIUM} />
+ )}
+ <IconButton tooltip="Stop and close animation" onPointerDown={this.stopAndCloseAnimation} 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>
+ </>
+ )}
+ </>
+ );
+ };
+
+ @action
+ hideRoute = () => {
+ this.temporaryRouteSource = {
+ type: 'FeatureCollection',
+ features: [],
+ };
+ };
+
+ @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;
+
+ @observable
+ mapStyle: string = 'mapbox://styles/mapbox/streets-v12';
+
+ @observable
+ showTerrain: boolean = false;
+
+ @action
+ toggleSettings = () => {
+ if (!this.isAnimating && this.animationPhase == 0) {
+ this.featuresFromGeocodeResults = [];
+ this.settingsOpen = !this.settingsOpen;
+ }
+ };
+
+ @action
+ changeMapStyle = (e: React.ChangeEvent<HTMLSelectElement>) => {
+ this.mapStyle = `mapbox://styles/mapbox/${e.target.value}`;
+ };
+
+ @action
+ onMapMove = (e: ViewStateChangeEvent) => {
+ this.mapboxMapViewState = e.viewState;
+ };
+
+ @action
+ toggleShowTerrain = () => {
+ this.showTerrain = !this.showTerrain;
+ };
+
+ @action
+ onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const newVal = parseInt(e.target.value);
+ if (!isNaN(newVal) && newVal >= 0) {
+ this.mapboxMapViewState = {
+ ...this.mapboxMapViewState,
+ bearing: parseInt(e.target.value),
+ };
+ }
+ };
+
+ @action
+ onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const newVal = parseInt(e.target.value);
+ if (!isNaN(newVal) && newVal >= 0) {
+ this.mapboxMapViewState = {
+ ...this.mapboxMapViewState,
+ pitch: parseInt(e.target.value),
+ };
+ }
+ };
+
+ getMarkerIcon = (pinDoc: Doc): JSX.Element | null => {
+ const markerType = StrCast(pinDoc.markerType);
+ const markerColor = StrCast(pinDoc.markerColor);
+
+ return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null;
+ };
+
static _firstRender = true;
static _rerenderDelay = 500;
_rerenderTimeout: any;
@@ -720,6 +1471,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return null;
}
+ const scale = this.props.NativeDimScaling?.() || 1;
return (
<div className="mapBox" ref={this._ref}>
<div
@@ -728,40 +1480,176 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
onPointerDown={async e => {
e.button === 0 && !e.ctrlKey && e.stopPropagation();
}}
- style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
- <div className="mapBox-searchbar">
- <EditableText
- // editing
- setVal={(newText: string | number) => typeof newText === 'string' && this.searchbarOnEdit(newText)}
- onEnter={e => this.bingSearch()}
- placeholder={this.bingSearchBarContents || 'enter city/zip/...'}
- textAlign="center"
- />
- <IconButton
- icon={
- <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF">
- <path
- fill="currentColor"
- d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path>
- </svg>
- }
- onClick={this.bingSearch}
- type={Type.TERT}
- />
- <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
- <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
+ style={{ transformOrigin: 'top left', transform: `scale(${scale})`, width: `${100 / scale}%`, height: `${100 / scale}%`, pointerEvents: this.pointerEvents() }}>
+ {!this.routeToAnimate && (
+ <div className="mapBox-searchbar">
+ <TextField fullWidth placeholder="Enter a location" onChange={(e: any) => this.handleSearchChange(e.target.value)} />
+ <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={e => this.toggleSettings()} />
+ </div>
+ )}
+ {this.settingsOpen && !this.routeToAnimate && (
+ <div className="mapbox-settings-panel" style={{ right: `${0 + this.sidebarWidth()}px` }}>
+ <div className="mapbox-style-select">
+ <div>Map Style:</div>
+ <div>
+ <select onChange={this.changeMapStyle}>
+ <option value="streets-v12">Streets</option>
+ <option value="outdoors-v12">Outdoors</option>
+ <option value="light-v11">Light</option>
+ <option value="dark-v11">Dark</option>
+ <option value="satellite-v9">Satellite</option>
+ <option value="satellite-streets-v12">Satellite Streets</option>
+ <option value="navigation-day-v1">Navigation Day</option>
+ <option value="navigation-night-v1">Navigation Night</option>
+ </select>
+ </div>
+ </div>
+ <div className="mapbox-bearing-selection">
+ <div>Bearing: </div>
+ <input value={this.mapboxMapViewState.bearing} min={0} type="number" onChange={this.onBearingChange} />
+ </div>
+ <div className="mapbox-pitch-selection">
+ <div>Pitch: </div>
+ <input value={this.mapboxMapViewState.pitch} min={0} type="number" onChange={this.onPitchChange} />
+ </div>
+ <div className="mapbox-terrain-selection">
+ <div>Show terrain: </div>
+ <input type="checkbox" checked={this.showTerrain} onChange={this.toggleShowTerrain} />
+ </div>
+ </div>
+ )}
+ {this.routeToAnimate && (
+ <div className="animation-panel">
+ <div id="route-to-animate-title">{StrCast(this.routeToAnimate.title)}</div>
+ <div className="route-animation-options">{this.getRouteAnimationOptions()}</div>
</div>
- </div>
+ )}
+ {this.featuresFromGeocodeResults.length > 0 && (
+ <div className="mapbox-geocoding-search-results">
+ <React.Fragment>
+ <h4>Choose a location for your pin: </h4>
+ {this.featuresFromGeocodeResults
+ .filter(feature => feature.place_name)
+ .map((feature, idx) => (
+ <div
+ key={idx}
+ className="search-result-container"
+ onClick={() => {
+ this.handleSearchChange('');
+ this.addMarkerForFeature(feature);
+ }}>
+ <div className="search-result-place-name">{feature.place_name}</div>
+ </div>
+ ))}
+ </React.Fragment>
+ </div>
+ )}
+ <MapProvider>
+ <MapboxMap
+ ref={this._mapRef}
+ mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
+ id="mapbox-map"
+ mapStyle={this.mapStyle}
+ style={{ height: '100%', width: '100%' }}
+ initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState}
+ // {...this.mapboxMapViewState}
+ onMove={this.onMapMove}
+ onClick={this.handleMapClick}
+ onDblClick={this.handleMapDblClick}
+ terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}>
+ <Source id="mapbox-dem" type="raster-dem" url="mapbox://mapbox.mapbox-terrain-dem-v1" tileSize={512} maxzoom={14} />
+ <Source id="temporary-route" type="geojson" data={this.temporaryRouteSource} />
+ <Source id="map-routes" type="geojson" data={this.allRoutesGeoJson} />
+ <Layer id="temporary-route-layer" type="line" source="temporary-route" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#36454F', 'line-width': 4, 'line-dasharray': [1, 1] }} />
+ {!this.isAnimating && this.animationPhase == 0 && <Layer id="map-routes-layer" type="line" source="map-routes" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#FF0000', 'line-width': 4 }} />}
+ {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) && (
+ <>
+ {!this.isStreetViewAnimation && (
+ <>
+ <Source id="animated-route" type="geojson" data={this.updatedRouteCoordinates} />
+ <Layer
+ id="dynamic-animation-line"
+ type="line"
+ source="animated-route"
+ paint={{
+ 'line-color': 'yellow',
+ 'line-width': 4,
+ }}
+ />
+ </>
+ )}
+ <Source id="start-pin-base" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.04)} />
+ <Source id="start-pin-top" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.25)} />
+ <Source id="end-pin-base" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.04)} />
+ <Source id="end-pin-top" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.25)} />
+ <Layer
+ id="start-fill-pin-base"
+ type="fill-extrusion"
+ source="start-pin-base"
+ paint={{
+ 'fill-extrusion-color': '#0bfc03',
+ 'fill-extrusion-height': 1000,
+ }}
+ />
+ <Layer
+ id="start-fill-pin-top"
+ type="fill-extrusion"
+ source="start-pin-top"
+ paint={{
+ 'fill-extrusion-color': '#0bfc03',
+ 'fill-extrusion-base': 1000,
+ 'fill-extrusion-height': 1200,
+ }}
+ />
+ <Layer
+ id="end-fill-pin-base"
+ type="fill-extrusion"
+ source="end-pin-base"
+ paint={{
+ 'fill-extrusion-color': '#eb1c1c',
+ 'fill-extrusion-height': 1000,
+ }}
+ />
+ <Layer
+ id="end-fill-pin-top"
+ type="fill-extrusion"
+ source="end-pin-top"
+ paint={{
+ 'fill-extrusion-color': '#eb1c1c',
+ 'fill-extrusion-base': 1000,
+ 'fill-extrusion-height': 1200,
+ }}
+ />
+ </>
+ )}
+
+ <>
+ {!this.isAnimating &&
+ this.animationPhase == 0 &&
+ this.allPushpins
+ // .filter(anno => !anno.layout_unrendered)
+ .map((pushpin, idx) => (
+ <Marker key={idx} longitude={NumCast(pushpin.longitude)} latitude={NumCast(pushpin.latitude)} anchor="bottom" onClick={(e: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)}>
+ {this.getMarkerIcon(pushpin)}
+ </Marker>
+ ))}
+ </>
- <BingMapsReact
+ {/* {this.mapMarkers.length > 0 && this.mapMarkers.map((marker, idx) => (
+ <Marker key={idx} longitude={marker.longitude} latitude={marker.latitude}/>
+ ))} */}
+ </MapboxMap>
+ </MapProvider>
+
+ {/* <BingMapsReact
onMapReady={this.bingMapReady} //
bingMapsKey={bingApiKey}
height="100%"
mapOptions={this.bingMapOptions}
width="100%"
viewOptions={this.bingViewOptions}
- />
- <div>
+ /> */}
+ {/* <div>
{!this._mapReady
? null
: this.allAnnotations
@@ -791,7 +1679,18 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
focus={returnOne}
/>
))}
- </div>
+ </div> */}
+ {/* <MapBoxInfoWindow
+ key={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})[Id]}
+ {...OmitKeys(this._props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+ place={Docs.Create.MapMarkerDocument(NumCast(40), NumCast(40), false, [], {})}
+ markerMap={this.markerMap}
+ PanelWidth={this.infoWidth}
+ PanelHeight={this.infoHeight}
+ moveDocument={this.moveDocument}
+ isAnyChildContentActive={this.isAnyChildContentActive}
+ whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+ /> */}
</div>
{/* </LoadScript > */}
<div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
@@ -817,3 +1716,54 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
);
}
}
+
+{
+ /* <Autocomplete
+ fullWidth
+ id="map-location-searcher"
+ freeSolo
+ onInputChange={(e, searchText) => this.handleSearchChange(searchText)}
+ onChange={(e, selectedOption) => {
+ this.handleSearchChange(""); // clear input
+ this.addMarkerForFeature(selectedOption);
+ }}
+ options={this.featuresFromGeocodeResults
+ .filter(feature => feature.place_name)
+ .map(feature => feature)}
+ getOptionLabel={(feature) => feature.place_name}
+ renderInput={(params) => (
+ <TextField
+ {...params}
+ placeholder='Enter a location'
+ />
+ )}
+ /> */
+}
+{
+ /* <EditableText
+ // editing
+ setVal={(newText: string | number) => typeof newText === 'string' && this.handleSearchChange(newText)}
+ // onEnter={e => this.bingSearch()}
+ onEnter={e => {}}
+ height={32}
+ // placeholder={this.bingSearchBarContents || 'Enter a location'}
+ placeholder='Enter a location'
+ textAlign="center"
+ /> */
+}
+{
+ /* <IconButton
+ icon={
+ <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="magnifying-glass" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" color="#DFDFDF">
+ <path
+ fill="currentColor"
+ d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"></path>
+ </svg>
+ }
+ onClick={this.bingSearch}
+ type={Type.TERT}
+ />
+ <div style={{ width: 30, height: 30 }} ref={this._dragRef} onPointerDown={this.dragToggle}>
+ <Button tooltip="drag to place a pushpin" icon={<FontAwesomeIcon size={'lg'} icon={'bullseye'} />} />
+ </div> */
+}