UNPKG

terriajs

Version:

Geospatial data visualization platform.

212 lines (196 loc) 7.18 kB
import i18next from "i18next"; import { action, observable, runInAction, makeObservable } from "mobx"; import { RefObject, createRef } from "react"; import CesiumCartographic from "terriajs-cesium/Source/Core/Cartographic"; import URI from "urijs"; import createGuid from "terriajs-cesium/Source/Core/createGuid"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import isDefined from "../../../../Core/isDefined"; import TerriaError from "../../../../Core/TerriaError"; import GeoJsonCatalogItem from "../../../../Models/Catalog/CatalogItems/GeoJsonCatalogItem"; import CommonStrata from "../../../../Models/Definition/CommonStrata"; import createStratumInstance from "../../../../Models/Definition/createStratumInstance"; import Terria from "../../../../Models/Terria"; import ViewerMode from "../../../../Models/ViewerMode"; import { GLYPHS } from "../../../../Styled/Icon"; import StyleTraits from "../../../../Traits/TraitsClasses/StyleTraits"; import MapNavigationItemController from "../../../../ViewModels/MapNavigation/MapNavigationItemController"; interface PropTypes { terria: Terria; } export class MyLocation extends MapNavigationItemController { static id = "my-location"; static displayName = "MyLocation"; readonly terria: Terria; itemRef: RefObject<HTMLDivElement> = createRef(); private readonly _marker: GeoJsonCatalogItem; @observable private watchId: number | undefined; @observable private flown: boolean | undefined; constructor(props: PropTypes) { super(); makeObservable(this); this.terria = props.terria; this._marker = new GeoJsonCatalogItem(createGuid(), props.terria); this.zoomToMyLocation = this.zoomToMyLocation.bind(this); this.handleLocationError = this.handleLocationError.bind(this); this.augmentedVirtualityEnabled = this.augmentedVirtualityEnabled.bind(this); this.followMeEnabled = this.followMeEnabled.bind(this); } get glyph(): any { return GLYPHS.geolocationThick; } get viewerMode(): ViewerMode | undefined { return undefined; } @action.bound getLocation() { const t = i18next.t.bind(i18next); if (navigator.geolocation) { const options = { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 }; if (!this.augmentedVirtualityEnabled()) { // When Augmented Virtuality is not enabled then just get a single position update. navigator.geolocation.getCurrentPosition( this.zoomToMyLocation, this.handleLocationError, options ); } else { // When Augmented Virtuality is enabled then we effectively toggle into watch mode and the position is repeatedly updated. this.watchId = navigator.geolocation.watchPosition( this.zoomToMyLocation, this.handleLocationError, options ); } } else { this.terria.raiseErrorToUser( new TerriaError({ sender: this, title: t("location.errorGettingLocation"), message: t("location.browserCannotProvide") }) ); } } zoomToMyLocation(position: GeolocationPosition) { const t = i18next.t.bind(i18next); const longitude = position.coords.longitude; const latitude = position.coords.latitude; if (this.augmentedVirtualityEnabled()) { // Note: Specifying the value of 27500m here enables this function to approximately mimic the behaviour of // the else case from the cameras initial view and when the viewer pan/zooms out to much. // We use the flag variable flown so that the user is flown to the current location when this function is // first fired, but subsequently the updates are jump location moves, since we assume that the movements are // small and flyTo performs badly when the increments are small (slow and unresponsive). this.terria.augmentedVirtuality.moveTo( CesiumCartographic.fromDegrees(longitude, latitude), 27500, !isDefined(this.flown) ); this.flown = true; } else { // west, south, east, north, result const rectangle = Rectangle.fromDegrees( longitude - 0.1, latitude - 0.1, longitude + 0.1, latitude + 0.1 ); this.terria.currentViewer.zoomTo(rectangle); } runInAction(() => { const name = t("location.myLocation"); this._marker.setTrait(CommonStrata.user, "name", name); this._marker.setTrait(CommonStrata.user, "geoJsonData", { type: "Feature", geometry: { type: "Point", coordinates: [longitude, latitude] }, properties: { title: t<string>("location.location"), longitude: longitude, latitude: latitude } }); // Need to specify rectangle for ideal zoom to work properly in 3D map. const closeUpRange = 0.002; this._marker.setTrait( CommonStrata.user, "rectangle", new Rectangle( longitude - closeUpRange, latitude - closeUpRange, longitude + closeUpRange, latitude + closeUpRange ) ); this._marker.setTrait( CommonStrata.user, "style", createStratumInstance(StyleTraits, { "marker-size": "25", "marker-color": "#08ABD5", stroke: "#ffffff", "stroke-width": 3 }) ); this._marker.setTrait(CommonStrata.user, "disableDepthTest", true); this.terria.workbench.add(this._marker); }); } handleLocationError(err: GeolocationPositionError) { const t = i18next.t.bind(i18next); let message = err.message; if (message && message.indexOf("Only secure origins are allowed") === 0) { // This is actually the recommended way to check for this error. // https://developers.google.com/web/updates/2016/04/geolocation-on-secure-contexts-only const uri = new URI(window.location); const secureUrl = uri.protocol("https").toString(); message = t("location.originError", { secureUrl: secureUrl }); } else if (err.code === err.PERMISSION_DENIED) { message = t("location.permissionDenied"); } else if (err.code === err.POSITION_UNAVAILABLE) { message = t("location.positionUnavailable"); } else if (err.code === err.TIMEOUT) { message = t("location.timeout"); } this.terria.raiseErrorToUser( new TerriaError({ sender: this, title: t("location.errorGettingLocation"), message: message, showDetails: false }) ); } augmentedVirtualityEnabled() { return ( isDefined(this.terria.augmentedVirtuality) && this.terria.augmentedVirtuality.enabled ); } followMeEnabled() { return !!isDefined(this.watchId); } @action.bound disableFollowMe() { if (isDefined(this.watchId)) { navigator.geolocation.clearWatch(this.watchId); this.watchId = undefined; this.flown = undefined; } } handleClick() { if (this.followMeEnabled()) { this.disableFollowMe(); } else { this.getLocation(); } } }