diff --git a/src/ui/control/geolocate_control.js b/src/ui/control/geolocate_control.js index 6d5164f67e8..b55836fca2a 100644 --- a/src/ui/control/geolocate_control.js +++ b/src/ui/control/geolocate_control.js @@ -18,7 +18,8 @@ type Options = { trackUserLocation?: boolean, showAccuracyCircle?: boolean, showUserLocation?: boolean, - showUserHeading?: boolean + showUserHeading?: boolean, + geolocation?: Geolocation, }; type DeviceOrientationEvent = { @@ -42,34 +43,10 @@ const defaultOptions: Options = { trackUserLocation: false, showAccuracyCircle: true, showUserLocation: true, - showUserHeading: false + showUserHeading: false, + geolocation: window.navigator.geolocation }; -let supportsGeolocation; - -function checkGeolocationSupport(callback) { - if (supportsGeolocation !== undefined) { - callback(supportsGeolocation); - - } else if (window.navigator.permissions !== undefined) { - // navigator.permissions has incomplete browser support - // http://caniuse.com/#feat=permissions-api - // Test for the case where a browser disables Geolocation because of an - // insecure origin - window.navigator.permissions.query({name: 'geolocation'}).then((p) => { - supportsGeolocation = p.state !== 'denied'; - callback(supportsGeolocation); - }); - - } else { - supportsGeolocation = !!window.navigator.geolocation; - callback(supportsGeolocation); - } -} - -let numberOfWatches = 0; -let noTimeout = false; - /** * A `GeolocateControl` control provides a button that uses the browser's geolocation * API to locate the user on the map. @@ -98,6 +75,7 @@ let noTimeout = false; * @param {Object} [options.showAccuracyCircle=true] By default, if `showUserLocation` is `true`, a transparent circle will be drawn around the user location indicating the accuracy (95% confidence level) of the user's location. Set to `false` to disable. Always disabled when `showUserLocation` is `false`. * @param {Object} [options.showUserLocation=true] By default a dot will be shown on the map at the user's location. Set to `false` to disable. * @param {Object} [options.showUserHeading=false] If `true` an arrow will be drawn next to the user location dot indicating the device's heading. This only has affect when `trackUserLocation` is `true`. + * @param {Object} [options.geolocation=window.navigator.geolocation] By default geolocation uses window.navigator.geolocation but you can customize this object * * @example * map.addControl(new mapboxgl.GeolocateControl({ @@ -128,6 +106,10 @@ class GeolocateControl extends Evented { _updateMarkerRotationThrottled: Function; _onDeviceOrientationListener: Function; + _numberOfWatches: number; + _noTimeout: boolean; + _supportsGeolocation: boolean; + constructor(options: Options) { super(); this.options = extend({}, defaultOptions, options); @@ -146,19 +128,20 @@ class GeolocateControl extends Evented { // by referencing the function with .bind(), we can correctly remove from window's event listeners this._onDeviceOrientationListener = this._onDeviceOrientation.bind(this); this._updateMarkerRotationThrottled = throttle(this._updateMarkerRotation, 20); + this._numberOfWatches = 0; } onAdd(map: Map) { this._map = map; this._container = DOM.create('div', `mapboxgl-ctrl mapboxgl-ctrl-group`); - checkGeolocationSupport(this._setupUI); + this._checkGeolocationSupport(this._setupUI); return this._container; } onRemove() { // clear the geolocation watch if exists if (this._geolocationWatchID !== undefined) { - window.navigator.geolocation.clearWatch(this._geolocationWatchID); + this.options.geolocation.clearWatch(this._geolocationWatchID); this._geolocationWatchID = (undefined: any); } @@ -173,8 +156,26 @@ class GeolocateControl extends Evented { DOM.remove(this._container); this._map.off('zoom', this._onZoom); this._map = (undefined: any); - numberOfWatches = 0; - noTimeout = false; + this._numberOfWatches = 0; + this._noTimeout = false; + } + + _checkGeolocationSupport(callback: (supported: boolean) => any) { + if (this._supportsGeolocation !== undefined) { + callback(this._supportsGeolocation); + } else if (window.navigator.permissions !== undefined) { + // navigator.permissions has incomplete browser support + // http://caniuse.com/#feat=permissions-api + // Test for the case where a browser disables Geolocation because of an + // insecure origin + window.navigator.permissions.query({name: 'geolocation'}).then((p) => { + this._supportsGeolocation = p.state !== 'denied'; + callback(this._supportsGeolocation); + }); + } else { + this._supportsGeolocation = !!this.geolocation; + callback(this._supportsGeolocation); + } } /** @@ -385,7 +386,7 @@ class GeolocateControl extends Evented { if (this._geolocationWatchID !== undefined) { this._clearWatch(); } - } else if (error.code === 3 && noTimeout) { + } else if (error.code === 3 && this._noTimeout) { // this represents a forced error state // this was triggered to force immediate geolocation when a watch is already present // see https://github.com/mapbox/mapbox-gl-js/issues/8214 @@ -547,9 +548,9 @@ class GeolocateControl extends Evented { case 'ACTIVE_LOCK': case 'ACTIVE_ERROR': case 'BACKGROUND_ERROR': - // turn off the GeolocateControl - numberOfWatches--; - noTimeout = false; + // turn off the Geolocate Control + this._numberOfWatches--; + this._noTimeout = false; this._watchState = 'OFF'; this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-waiting'); this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-active'); @@ -607,17 +608,17 @@ class GeolocateControl extends Evented { this._geolocateButton.classList.add('mapboxgl-ctrl-geolocate-waiting'); this._geolocateButton.setAttribute('aria-pressed', 'true'); - numberOfWatches++; + this._numberOfWatches++; let positionOptions; - if (numberOfWatches > 1) { + if (this._numberOfWatches > 1) { positionOptions = {maximumAge:600000, timeout:0}; - noTimeout = true; + this._noTimeout = true; } else { positionOptions = this.options.positionOptions; - noTimeout = false; + this._noTimeout = false; } - this._geolocationWatchID = window.navigator.geolocation.watchPosition( + this._geolocationWatchID = this.options.geolocation.watchPosition( this._onSuccess, this._onError, positionOptions); if (this.options.showUserHeading) { @@ -625,7 +626,7 @@ class GeolocateControl extends Evented { } } } else { - window.navigator.geolocation.getCurrentPosition( + this.options.geolocation.getCurrentPosition( this._onSuccess, this._onError, this.options.positionOptions); // This timeout ensures that we still call finish() even if @@ -661,7 +662,7 @@ class GeolocateControl extends Evented { } _clearWatch() { - window.navigator.geolocation.clearWatch(this._geolocationWatchID); + this.options.geolocation.clearWatch(this._geolocationWatchID); window.removeEventListener('deviceorientation', this._onDeviceOrientationListener); window.removeEventListener('deviceorientationabsolute', this._onDeviceOrientationListener);