Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add geolocation adapter for customizing geolocation in GeolocateControl #11614

Merged
merged 6 commits into from
Apr 18, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 50 additions & 49 deletions src/ui/control/geolocate_control.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import type Map from '../map.js';
import type {AnimationOptions, CameraOptions} from '../camera.js';

type Options = {
positionOptions?: PositionOptions,
fitBoundsOptions?: AnimationOptions & CameraOptions,
trackUserLocation?: boolean,
showAccuracyCircle?: boolean,
showUserLocation?: boolean,
showUserHeading?: boolean
positionOptions: PositionOptions,
fitBoundsOptions: AnimationOptions & CameraOptions,
trackUserLocation: boolean,
showAccuracyCircle: boolean,
showUserLocation: boolean,
showUserHeading: boolean,
geolocation: Geolocation,
};

type DeviceOrientationEvent = {
Expand All @@ -30,7 +31,7 @@ type DeviceOrientationEvent = {
webkitCompassHeading?: number,
}

const defaultOptions: Options = {
const defaultOptions = {
positionOptions: {
enableHighAccuracy: false,
maximumAge: 0,
Expand All @@ -45,31 +46,6 @@ const defaultOptions: Options = {
showUserHeading: false
};

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.
Expand Down Expand Up @@ -98,6 +74,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] `window.navigator.geolocation` by default; you can provide an object with the same shape to customize geolocation handling.
*
* @example
* map.addControl(new mapboxgl.GeolocateControl({
Expand Down Expand Up @@ -127,9 +104,14 @@ class GeolocateControl extends Evented {
_heading: ?number;
_updateMarkerRotationThrottled: Function;

constructor(options: Options) {
_numberOfWatches: number;
_noTimeout: boolean;
_supportsGeolocation: boolean;

constructor(options: $Shape<Options>) {
super();
this.options = extend({}, defaultOptions, options);
const geolocation = window.navigator.geolocation;
this.options = extend({geolocation}, defaultOptions, options);

bindAll([
'_onSuccess',
Expand All @@ -144,19 +126,20 @@ class GeolocateControl extends Evented {
], this);

this._updateMarkerRotationThrottled = throttle(this._updateMarkerRotation, 20);
this._numberOfWatches = 0;
}

onAdd(map: Map): HTMLElement {
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);
}

Expand All @@ -171,8 +154,26 @@ class GeolocateControl extends Evented {
this._container.remove();
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);
}
}

/**
Expand Down Expand Up @@ -383,7 +384,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
Expand Down Expand Up @@ -545,9 +546,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');
Expand Down Expand Up @@ -605,25 +606,25 @@ 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) {
this._addDeviceOrientationListener();
}
}
} 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
Expand Down Expand Up @@ -659,7 +660,7 @@ class GeolocateControl extends Evented {
}

_clearWatch() {
window.navigator.geolocation.clearWatch(this._geolocationWatchID);
this.options.geolocation.clearWatch(this._geolocationWatchID);

window.removeEventListener('deviceorientation', this._onDeviceOrientation);
window.removeEventListener('deviceorientationabsolute', this._onDeviceOrientation);
Expand Down