Skip to content

Commit

Permalink
hide heading arrow until a heading is provided
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewharvey committed Oct 31, 2019
1 parent 0250512 commit e9199a9
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 10 deletions.
1 change: 1 addition & 0 deletions debug/debug.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
},
trackUserLocation: true,
showUserLocation: true,
showUserHeading: true,
fitBoundsOptions: {
maxZoom: 20
}
Expand Down
24 changes: 24 additions & 0 deletions src/css/mapbox-gl.css
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,30 @@ a.mapboxgl-ctrl-logo.mapboxgl-compact {
box-shadow: 0 0 3px rgba(0, 0, 0, 0.35);
}

.mapboxgl-user-location-show-heading .mapboxgl-user-location-heading {
width: 0;
height: 0;
}

.mapboxgl-user-location-show-heading .mapboxgl-user-location-heading::before {
content: "";
width: 0;
height: 0;
border-left: 7.5px solid transparent;
border-bottom: 7.5px solid #1da1f2;
transform: translate(0px, -28px) skewY(-20deg);
position: absolute;
}
.mapboxgl-user-location-show-heading .mapboxgl-user-location-heading::after {
content: "";
width: 0;
height: 0;
border-right: 7.5px solid transparent;
border-bottom: 7.5px solid #1da1f2;
transform: translate(7.5px, -28px) skewY(20deg);
position: absolute;
}

@-webkit-keyframes mapboxgl-user-location-dot-pulse {
0% { -webkit-transform: scale(1); opacity: 1; }
70% { -webkit-transform: scale(3); opacity: 0; }
Expand Down
111 changes: 102 additions & 9 deletions src/ui/control/geolocate_control.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {extend, bindAll, warnOnce} from '../../util/util';
import assert from 'assert';
import LngLat from '../../geo/lng_lat';
import Marker from '../marker';
import throttle from '../../util/throttle';

import type Map from '../map';
import type {AnimationOptions, CameraOptions} from '../camera';
Expand All @@ -15,7 +16,8 @@ type Options = {
positionOptions?: PositionOptions,
fitBoundsOptions?: AnimationOptions & CameraOptions,
trackUserLocation?: boolean,
showUserLocation?: boolean
showUserLocation?: boolean,
showUserHeading?: boolean
};

const defaultOptions: Options = {
Expand All @@ -28,7 +30,8 @@ const defaultOptions: Options = {
maxZoom: 15
},
trackUserLocation: false,
showUserLocation: true
showUserLocation: true,
showUserHeading: false
};
const className = 'mapboxgl-ctrl';

Expand Down Expand Up @@ -61,8 +64,8 @@ function checkGeolocationSupport(callback) {
* Not all browsers support geolocation,
* and some users may disable the feature. Geolocation support for modern
* browsers including Chrome requires sites to be served over HTTPS. If
* geolocation support is not available, the GeolocateControl will not
* be visible.
* geolocation support is not available, the GeolocateControl will show
* as disabled.
*
* The zoom level applied will depend on the accuracy of the geolocation provided by the device.
*
Expand All @@ -77,13 +80,15 @@ function checkGeolocationSupport(callback) {
* @param {Object} [options.fitBoundsOptions={maxZoom: 15}] A [`fitBounds`](#map#fitbounds) options object to use when the map is panned and zoomed to the user's location. The default is to use a `maxZoom` of 15 to limit how far the map will zoom in for very accurate locations.
* @param {Object} [options.trackUserLocation=false] If `true` the Geolocate Control becomes a toggle button and when active the map will receive updates to the user's location as it changes.
* @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`.
*
* @example
* map.addControl(new mapboxgl.GeolocateControl({
* positionOptions: {
* enableHighAccuracy: true
* },
* trackUserLocation: true
* trackUserLocation: true,
* showUserHeading: true
* }));
* @see [Locate the user](https://www.mapbox.com/mapbox-gl-js/example/locate-user/)
*/
Expand All @@ -99,6 +104,7 @@ class GeolocateControl extends Evented {
_lastKnownPosition: any;
_userLocationDotMarker: Marker;
_setup: boolean; // set to true once the control has been setup
_heading: ?number;

constructor(options: Options) {
super();
Expand All @@ -110,8 +116,11 @@ class GeolocateControl extends Evented {
'_finish',
'_setupUI',
'_updateCamera',
'_updateMarker'
'_updateMarker',
'_updateMarkerRotation'
], this);

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

onAdd(map: Map) {
Expand All @@ -137,6 +146,13 @@ class GeolocateControl extends Evented {
this._map = (undefined: any);
}

/**
* Check if the Geolocation API Position is outside the map's maxbounds.
*
* @param position {Position} the Geolocation API Position
* @returns {boolean} Returns `true` if position is outside the map's maxbounds, otherwise returns `false`.
* @private
*/
_isOutOfMapMaxBounds(position: Position) {
const bounds = this._map.getMaxBounds();
const coordinates = position.coords;
Expand Down Expand Up @@ -177,7 +193,14 @@ class GeolocateControl extends Evented {
}
}

/**
* When the Geolocation API returns a new location, update the GeolocateControl.
*
* @param position {Position} the Geolocation API Position
* @private
*/
_onSuccess(position: Position) {
// if the position is outside the map's maxbounds
if (this._isOutOfMapMaxBounds(position)) {
this._setErrorState();

Expand Down Expand Up @@ -234,6 +257,12 @@ class GeolocateControl extends Evented {
this._finish();
}

/**
* Update the camera location to center on the current position
*
* @param position {Position} the Geolocation API Position
* @private
*/
_updateCamera(position: Position) {
const center = new LngLat(position.coords.longitude, position.coords.latitude);
const radius = position.coords.accuracy;
Expand All @@ -245,14 +274,37 @@ class GeolocateControl extends Evented {
});
}

/**
* Update the user location dot Marker to the current position
*
* @param position {Position} the Geolocation API Position
* @private
*/
_updateMarker(position: ?Position) {
if (position) {
this._userLocationDotMarker.setLngLat([position.coords.longitude, position.coords.latitude]).addTo(this._map);

// TODO consider using position.heading in case this is different to deviceorientation
} else {
this._userLocationDotMarker.remove();
}
}

/**
* Update the user location dot Marker rotation to the current heading
*
* @private
*/
_updateMarkerRotation() {
if (this._userLocationDotMarker && this._heading) {
this._userLocationDotMarker.setRotation(this._heading)
this._dotElement.classList.add('mapboxgl-user-location-show-heading');
} else {
this._dotElement.classList.remove('mapboxgl-user-location-show-heading');
this._userLocationDotMarker.setRotation(0);
}
}

_onError(error: PositionError) {
if (this.options.trackUserLocation) {
if (error.code === 1) {
Expand Down Expand Up @@ -311,9 +363,15 @@ class GeolocateControl extends Evented {

// when showUserLocation is enabled, keep the Geolocate button disabled until the device location marker is setup on the map
if (this.options.showUserLocation) {
this._dotElement = DOM.create('div', 'mapboxgl-user-location-dot');

this._userLocationDotMarker = new Marker(this._dotElement);
this._dotElement = DOM.create('div', 'mapboxgl-user-location');
this._dotElement.appendChild(DOM.create('div', 'mapboxgl-user-location-dot'));
this._dotElement.appendChild(DOM.create('div', 'mapboxgl-user-location-heading'));

this._userLocationDotMarker = new Marker({
element: this._dotElement,
rotationAlignment: 'map',
pitchAlignment: 'map'
});

if (this.options.trackUserLocation) this._watchState = 'OFF';
}
Expand All @@ -339,6 +397,29 @@ class GeolocateControl extends Evented {
}
}

/**
* Called on a deviceorientationabsolute or deviceorientation event.
*
* @param deviceOrientationEvent {DeviceOrientationEvent}
* @private
*/
_onDeviceOrientation(deviceOrientationEvent: DeviceOrientationEvent) {
if (!deviceOrientationEvent.absolute && !deviceOrientationEvent.webkitCompassHeading) {
// an absolute orientation or a webkitCompassHeading is required

// disable the listeners since we assume future triggers will be the same
window.removeEventListener('deviceorientationabsolute', this._onDeviceOrientation);
window.removeEventListener('deviceorientation', this._onDeviceOrientation);

return;
}

if (this._userLocationDotMarker) {
this._heading = deviceOrientationEvent.webkitCompassHeading || deviceOrientationEvent.alpha;
this._updateMarkerRotationThrottled();
}
}

/**
* Trigger a geolocation
*
Expand Down Expand Up @@ -422,6 +503,15 @@ class GeolocateControl extends Evented {

this._geolocationWatchID = window.navigator.geolocation.watchPosition(
this._onSuccess, this._onError, this.options.positionOptions);

if (this.options.showUserHeading) {
if ('ondeviceorientationabsolute' in window) {
window.addEventListener('deviceorientationabsolute', this._onDeviceOrientation.bind(this));
} else if ('ondeviceorientation' in window) {
window.addEventListener('deviceorientation', this._onDeviceOrientation.bind(this));
}
}

}
} else {
window.navigator.geolocation.getCurrentPosition(
Expand All @@ -438,6 +528,9 @@ class GeolocateControl extends Evented {
_clearWatch() {
window.navigator.geolocation.clearWatch(this._geolocationWatchID);

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

this._geolocationWatchID = (undefined: any);
this._geolocateButton.classList.remove('mapboxgl-ctrl-geolocate-waiting');
this._geolocateButton.setAttribute('aria-pressed', 'false');
Expand Down
1 change: 0 additions & 1 deletion src/util/throttle.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

/**
* Throttle the given function to run at most every `period` milliseconds.
Throttle the given function to run at most every period milliseconds.
* @private
*/
export default function throttle(fn: () => void, time: number): () => ?TimeoutID {
Expand Down

0 comments on commit e9199a9

Please sign in to comment.