From 2c3ba6f21212d24e74dd0fada521149427081bc2 Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Sat, 3 Dec 2016 22:12:22 +1100 Subject: [PATCH 01/19] take Geolocation accuracy into account in the Geolocate control --- debug/debug.html | 3 +++ js/geo/lng_lat.js | 18 ++++++++++++++++++ js/ui/control/geolocate_control.js | 18 +++++++++++------- test/js/geo/lng_lat.test.js | 6 ++++++ 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/debug/debug.html b/debug/debug.html index f6459f01fd9..988428309a8 100644 --- a/debug/debug.html +++ b/debug/debug.html @@ -45,6 +45,9 @@ positionOptions: { enableHighAccuracy: true }, + fitBoundsOptions: { + maxZoom: 20 + }, watchPosition: true })); map.addControl(new mapboxgl.ScaleControl()); diff --git a/js/geo/lng_lat.js b/js/geo/lng_lat.js index ed26453f78e..99202e3814f 100644 --- a/js/geo/lng_lat.js +++ b/js/geo/lng_lat.js @@ -68,6 +68,24 @@ class LngLat { toString() { return `LngLat(${this.lng}, ${this.lat})`; } + + /** + * Returns a `LngLatBounds` from the coordinates extended by a given `radius`. + * + * @param {number} radius Distance in meters from the coordinates to extend the bounds. + * @returns {LngLatBounds} A new `LngLatBounds` object representing the coordinates extended by the `radius`. + * @example + * var ll = new mapboxgl.LngLat(-73.9749, 40.7736); + * ll.toBounds(100).toArray(); // = [[-73.97501862141328, 40.77351016847229], [-73.97478137858673, 40.77368983152771]] + */ + toBounds(radius) { + const latAccuracy = 360 * radius / 40075017, + lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); + + const LngLatBounds = require('./lng_lat_bounds'); + return new LngLatBounds(new LngLat(this.lng - lngAccuracy, this.lat - latAccuracy), + new LngLat(this.lng + lngAccuracy, this.lat + latAccuracy)); + } } /** diff --git a/js/ui/control/geolocate_control.js b/js/ui/control/geolocate_control.js index 8e3c9a3cab2..341efd3a6a7 100644 --- a/js/ui/control/geolocate_control.js +++ b/js/ui/control/geolocate_control.js @@ -4,6 +4,7 @@ const Evented = require('../../util/evented'); const DOM = require('../../util/dom'); const window = require('../../util/window'); const util = require('../../util/util'); +const LngLat = require('../../geo/lng_lat'); const defaultGeoPositionOptions = { enableHighAccuracy: false, timeout: 6000 /* 6sec */ }; const className = 'mapboxgl-ctrl'; @@ -40,9 +41,12 @@ function checkGeolocationSupport(callback) { * geolocation support is not available, the GeolocateControl will not * be visible. * + * The zoom level applied will depend on the accuracy of the geolocation provided by the device. + * * @implements {IControl} * @param {Object} [options] - * @param {Object} [options.positionOptions={enableHighAccuracy: false, timeout: 6000}] A [PositionOptions](https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions) object. + * @param {Object} [options.positionOptions={enableHighAccuracy: false, timeout: 6000}] A Geolocation API [PositionOptions](https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions) object. + * @param {Object} [options.fitBoundsOptions={maxZoom: 18}] A [`fitBounds`](#Map#fitBounds) options object to use when the map is panned and zoomed to the device location. The default is to use a `maxZoom` of 18 to limit how far the map will zoom in for very accurate locations. * @param {Object} [options.watchPosition=false] If `true` the map will reposition each time the position of the device changes and the control becomes a toggle. * @example * map.addControl(new mapboxgl.GeolocateControl({ @@ -77,12 +81,12 @@ class GeolocateControl extends Evented { } _onSuccess(position) { - this._map.jumpTo({ - center: [position.coords.longitude, position.coords.latitude], - zoom: 17, - bearing: 0, - pitch: 0 - }); + const center = new LngLat(position.coords.longitude, position.coords.latitude); + const radius = position.coords.accuracy; + + this._map.fitBounds(center.toBounds(radius), util.extend({ + maxZoom: (this.options.maxZoom !== undefined) ? this.options.maxZoom : 18 + }, this.options.fitBoundsOptions || {})); this.fire('geolocate', position); this._finish(); diff --git a/test/js/geo/lng_lat.test.js b/test/js/geo/lng_lat.test.js index 0f0e5ff09d5..e01257dfcab 100644 --- a/test/js/geo/lng_lat.test.js +++ b/test/js/geo/lng_lat.test.js @@ -50,5 +50,11 @@ test('LngLat', (t) => { t.end(); }); + t.test('#toBounds', (t) => { + t.deepEqual(new LngLat(0, 0).toBounds(10).toArray(), [[-0.00008983152770714982, -0.00008983152770714982], [0.00008983152770714982, 0.00008983152770714982]]); + t.deepEqual(new LngLat(-73.9749, 40.7736).toBounds(10).toArray(), [[-73.97501862141328, 40.77351016847229], [-73.97478137858673, 40.77368983152771]]); + t.end(); + }); + t.end(); }); From 80bcfab8af49d1c7ba8e0b86cde30babb7f6c8de Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Fri, 9 Dec 2016 12:54:21 +1100 Subject: [PATCH 02/19] add positionMarker option to Geolocate Control and improve watchPosition with an active lock and background mode --- debug/debug.html | 3 +- dist/mapbox-gl.css | 46 ++++- js/ui/control/geolocate_control.js | 314 +++++++++++++++++++++++++++-- 3 files changed, 342 insertions(+), 21 deletions(-) diff --git a/debug/debug.html b/debug/debug.html index f6459f01fd9..54fce6f18b3 100644 --- a/debug/debug.html +++ b/debug/debug.html @@ -45,7 +45,8 @@ positionOptions: { enableHighAccuracy: true }, - watchPosition: true + watchPosition: true, + showMarker: true })); map.addControl(new mapboxgl.ScaleControl()); diff --git a/dist/mapbox-gl.css b/dist/mapbox-gl.css index 841457e2a6e..a80d9251be3 100644 --- a/dist/mapbox-gl.css +++ b/dist/mapbox-gl.css @@ -79,11 +79,51 @@ .mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-in { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27M%2010%206%20C%209.446%206%209%206.4459904%209%207%20L%209%209%20L%207%209%20C%206.446%209%206%209.446%206%2010%20C%206%2010.554%206.446%2011%207%2011%20L%209%2011%20L%209%2013%20C%209%2013.55401%209.446%2014%2010%2014%20C%2010.554%2014%2011%2013.55401%2011%2013%20L%2011%2011%20L%2013%2011%20C%2013.554%2011%2014%2010.554%2014%2010%20C%2014%209.446%2013.554%209%2013%209%20L%2011%209%20L%2011%207%20C%2011%206.4459904%2010.554%206%2010%206%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A"); } -.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate { +.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate { background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%20%20%3Cpath%20style%3D%27fill%3A%23333%3B%27%20d%3D%27M10%204C9%204%209%205%209%205L9%205.1A5%205%200%200%200%205.1%209L5%209C5%209%204%209%204%2010%204%2011%205%2011%205%2011L5.1%2011A5%205%200%200%200%209%2014.9L9%2015C9%2015%209%2016%2010%2016%2011%2016%2011%2015%2011%2015L11%2014.9A5%205%200%200%200%2014.9%2011L15%2011C15%2011%2016%2011%2016%2010%2016%209%2015%209%2015%209L14.9%209A5%205%200%200%200%2011%205.1L11%205C11%205%2011%204%2010%204zM10%206.5A3.5%203.5%200%200%201%2013.5%2010%203.5%203.5%200%200%201%2010%2013.5%203.5%203.5%200%200%201%206.5%2010%203.5%203.5%200%200%201%2010%206.5zM10%208.3A1.8%201.8%200%200%200%208.3%2010%201.8%201.8%200%200%200%2010%2011.8%201.8%201.8%200%200%200%2011.8%2010%201.8%201.8%200%200%200%2010%208.3z%27%20%2F%3E%0D%0A%3C%2Fsvg%3E"); } -.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate.watching { - background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%20%20%3Cpath%20style%3D%27fill%3A%2300f%3B%27%20d%3D%27M10%204C9%204%209%205%209%205L9%205.1A5%205%200%200%200%205.1%209L5%209C5%209%204%209%204%2010%204%2011%205%2011%205%2011L5.1%2011A5%205%200%200%200%209%2014.9L9%2015C9%2015%209%2016%2010%2016%2011%2016%2011%2015%2011%2015L11%2014.9A5%205%200%200%200%2014.9%2011L15%2011C15%2011%2016%2011%2016%2010%2016%209%2015%209%2015%209L14.9%209A5%205%200%200%200%2011%205.1L11%205C11%205%2011%204%2010%204zM10%206.5A3.5%203.5%200%200%201%2013.5%2010%203.5%203.5%200%200%201%2010%2013.5%203.5%203.5%200%200%201%206.5%2010%203.5%203.5%200%200%201%2010%206.5zM10%208.3A1.8%201.8%200%200%200%208.3%2010%201.8%201.8%200%200%200%2010%2011.8%201.8%201.8%200%200%200%2011.8%2010%201.8%201.8%200%200%200%2010%208.3z%27%20%2F%3E%0D%0A%3C%2Fsvg%3E"); +.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate:disabled { + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%20%20%3Cpath%20style%3D%27fill%3A%23aaa%3B%27%20d%3D%27M10%204C9%204%209%205%209%205L9%205.1A5%205%200%200%200%205.1%209L5%209C5%209%204%209%204%2010%204%2011%205%2011%205%2011L5.1%2011A5%205%200%200%200%209%2014.9L9%2015C9%2015%209%2016%2010%2016%2011%2016%2011%2015%2011%2015L11%2014.9A5%205%200%200%200%2014.9%2011L15%2011C15%2011%2016%2011%2016%2010%2016%209%2015%209%2015%209L14.9%209A5%205%200%200%200%2011%205.1L11%205C11%205%2011%204%2010%204zM10%206.5A3.5%203.5%200%200%201%2013.5%2010%203.5%203.5%200%200%201%2010%2013.5%203.5%203.5%200%200%201%206.5%2010%203.5%203.5%200%200%201%2010%206.5zM10%208.3A1.8%201.8%200%200%200%208.3%2010%201.8%201.8%200%200%200%2010%2011.8%201.8%201.8%200%200%200%2011.8%2010%201.8%201.8%200%200%200%2010%208.3z%27%20%2F%3E%0D%0A%3C%2Fsvg%3E"); +} +.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate.active { + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%20%20%3Cpath%20style%3D%27fill%3A%2333b5e5%3B%27%20d%3D%27M10%204C9%204%209%205%209%205L9%205.1A5%205%200%200%200%205.1%209L5%209C5%209%204%209%204%2010%204%2011%205%2011%205%2011L5.1%2011A5%205%200%200%200%209%2014.9L9%2015C9%2015%209%2016%2010%2016%2011%2016%2011%2015%2011%2015L11%2014.9A5%205%200%200%200%2014.9%2011L15%2011C15%2011%2016%2011%2016%2010%2016%209%2015%209%2015%209L14.9%209A5%205%200%200%200%2011%205.1L11%205C11%205%2011%204%2010%204zM10%206.5A3.5%203.5%200%200%201%2013.5%2010%203.5%203.5%200%200%201%2010%2013.5%203.5%203.5%200%200%201%206.5%2010%203.5%203.5%200%200%201%2010%206.5zM10%208.3A1.8%201.8%200%200%200%208.3%2010%201.8%201.8%200%200%200%2010%2011.8%201.8%201.8%200%200%200%2011.8%2010%201.8%201.8%200%200%200%2010%208.3z%27%20%2F%3E%0D%0A%3C%2Fsvg%3E"); +} +.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate.active-error { + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0D%0A%20%20%3Cpath%20style%3D%27fill%3A%23e58978%3B%27%20d%3D%27M10%204C9%204%209%205%209%205L9%205.1A5%205%200%200%200%205.1%209L5%209C5%209%204%209%204%2010%204%2011%205%2011%205%2011L5.1%2011A5%205%200%200%200%209%2014.9L9%2015C9%2015%209%2016%2010%2016%2011%2016%2011%2015%2011%2015L11%2014.9A5%205%200%200%200%2014.9%2011L15%2011C15%2011%2016%2011%2016%2010%2016%209%2015%209%2015%209L14.9%209A5%205%200%200%200%2011%205.1L11%205C11%205%2011%204%2010%204zM10%206.5A3.5%203.5%200%200%201%2013.5%2010%203.5%203.5%200%200%201%2010%2013.5%203.5%203.5%200%200%201%206.5%2010%203.5%203.5%200%200%201%2010%206.5zM10%208.3A1.8%201.8%200%200%200%208.3%2010%201.8%201.8%200%200%200%2010%2011.8%201.8%201.8%200%200%200%2011.8%2010%201.8%201.8%200%200%200%2010%208.3z%27%20%2F%3E%0D%0A%3C%2Fsvg%3E"); +} +.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate.background { + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%2333b5e5%3B%27%20d%3D%27M%2010%2C4%20C%209%2C4%209%2C5%209%2C5%20L%209%2C5.1%20C%207.0357113%2C5.5006048%205.5006048%2C7.0357113%205.1%2C9%20L%205%2C9%20c%200%2C0%20-1%2C0%20-1%2C1%200%2C1%201%2C1%201%2C1%20l%200.1%2C0%20c%200.4006048%2C1.964289%201.9357113%2C3.499395%203.9%2C3.9%20L%209%2C15%20c%200%2C0%200%2C1%201%2C1%201%2C0%201%2C-1%201%2C-1%20l%200%2C-0.1%20c%201.964289%2C-0.400605%203.499395%2C-1.935711%203.9%2C-3.9%20l%200.1%2C0%20c%200%2C0%201%2C0%201%2C-1%20C%2016%2C9%2015%2C9%2015%2C9%20L%2014.9%2C9%20C%2014.499395%2C7.0357113%2012.964289%2C5.5006048%2011%2C5.1%20L%2011%2C5%20c%200%2C0%200%2C-1%20-1%2C-1%20z%20m%200%2C2.5%20c%201.932997%2C0%203.5%2C1.5670034%203.5%2C3.5%200%2C1.932997%20-1.567003%2C3.5%20-3.5%2C3.5%20C%208.0670034%2C13.5%206.5%2C11.932997%206.5%2C10%206.5%2C8.0670034%208.0670034%2C6.5%2010%2C6.5%20Z%27%20%2F%3E%0A%3C%2Fsvg%3E"); +} +.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate.background-error { + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23e54e33%3B%27%20d%3D%27M%2010%2C4%20C%209%2C4%209%2C5%209%2C5%20L%209%2C5.1%20C%207.0357113%2C5.5006048%205.5006048%2C7.0357113%205.1%2C9%20L%205%2C9%20c%200%2C0%20-1%2C0%20-1%2C1%200%2C1%201%2C1%201%2C1%20l%200.1%2C0%20c%200.4006048%2C1.964289%201.9357113%2C3.499395%203.9%2C3.9%20L%209%2C15%20c%200%2C0%200%2C1%201%2C1%201%2C0%201%2C-1%201%2C-1%20l%200%2C-0.1%20c%201.964289%2C-0.400605%203.499395%2C-1.935711%203.9%2C-3.9%20l%200.1%2C0%20c%200%2C0%201%2C0%201%2C-1%20C%2016%2C9%2015%2C9%2015%2C9%20L%2014.9%2C9%20C%2014.499395%2C7.0357113%2012.964289%2C5.5006048%2011%2C5.1%20L%2011%2C5%20c%200%2C0%200%2C-1%20-1%2C-1%20z%20m%200%2C2.5%20c%201.932997%2C0%203.5%2C1.5670034%203.5%2C3.5%200%2C1.932997%20-1.567003%2C3.5%20-3.5%2C3.5%20C%208.0670034%2C13.5%206.5%2C11.932997%206.5%2C10%206.5%2C8.0670034%208.0670034%2C6.5%2010%2C6.5%20Z%27%20%2F%3E%0A%3C%2Fsvg%3E"); +} +.mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate.waiting { + -webkit-animation: mapboxgl-spin 2s infinite linear; + -moz-animation: mapboxgl-spin 2s infinite linear; + -o-animation: mapboxgl-spin 2s infinite linear; + -ms-animation: mapboxgl-spin 2s infinite linear; + animation: mapboxgl-spin 2s infinite linear; +} + +@-webkit-keyframes mapboxgl-spin { + 0% { -webkit-transform: rotate(0deg); } + 100% { -webkit-transform: rotate(360deg); } +} +@-moz-keyframes mapboxgl-spin { + 0% { -moz-transform: rotate(0deg); } + 100% { -moz-transform: rotate(360deg); } +} +@-o-keyframes mapboxgl-spin { + 0% { -o-transform: rotate(0deg); } + 100% { -o-transform: rotate(360deg); } +} +@-ms-keyframes mapboxgl-spin { + 0% { -ms-transform: rotate(0deg); } + 100% { -ms-transform: rotate(360deg); } +} +@-keyframes mapboxgl-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } } .mapboxgl-ctrl-icon.mapboxgl-ctrl-compass > span.arrow { diff --git a/js/ui/control/geolocate_control.js b/js/ui/control/geolocate_control.js index 8e3c9a3cab2..ee3320f324f 100644 --- a/js/ui/control/geolocate_control.js +++ b/js/ui/control/geolocate_control.js @@ -4,10 +4,14 @@ const Evented = require('../../util/evented'); const DOM = require('../../util/dom'); const window = require('../../util/window'); const util = require('../../util/util'); +const assert = require('assert'); const defaultGeoPositionOptions = { enableHighAccuracy: false, timeout: 6000 /* 6sec */ }; const className = 'mapboxgl-ctrl'; +const markerLayerName = '_geolocate-control-marker'; +const markerSourceName = '_geolocate-control-marker-position'; + let supportsGeolocation; function checkGeolocationSupport(callback) { @@ -40,14 +44,22 @@ function checkGeolocationSupport(callback) { * geolocation support is not available, the GeolocateControl will not * be visible. * + * The GeolocateControl has two modes. If `watchPosition` is `false` (default) the control acts as a button, which when pressed will set the map's camera to target the device location. If the device moves, the map won't update. This is most suited for the desktop. If `watchPosition` is `true` the control acts as a toggle button that when active the device's location is actively monitored for changes. In this mode there is a concept of an active lock and background. In active lock the map's camera will automatically update as the device's location changes until the user manually changes the camera (such as panning or zooming). When this happens the control is in background so that the location marker still updates but the camera doesn't. + * * @implements {IControl} * @param {Object} [options] * @param {Object} [options.positionOptions={enableHighAccuracy: false, timeout: 6000}] A [PositionOptions](https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions) object. - * @param {Object} [options.watchPosition=false] If `true` the map will reposition each time the position of the device changes and the control becomes a toggle. + * @param {Object} [options.watchPosition=false] If `true` the Geolocate Control becomes a toggle button and when active the map will receive updates to the device location as it changes. + * @param {Object} [options.showMarker=true] By default a marker will be added to the map with the device's location. Set to `false` to disable. + * @param {Object} [options.markerPaintProperties={'circle-radius': 10, 'circle-color': '#33b5e5', 'circle-stroke-color': '#fff', 'circle-stroke-width': 2}] A [Circle Layer Paint Properties](https://www.mapbox.com/mapbox-gl-style-spec/#paint_circle) object to customize the device location marker. The default is a blue dot with a white stroke. * @example * map.addControl(new mapboxgl.GeolocateControl({ * positionOptions: { * enableHighAccuracy: true + * }, + * watchPosition: true, + * markerPaintProperties: { + * 'circle-color': '#000' * } * })); */ @@ -56,11 +68,19 @@ class GeolocateControl extends Evented { constructor(options) { super(); this.options = options || {}; + + // apply default for options.showMarker + this.options.showMarker = (this.options && 'showMarker' in this.options) ? this.options.showMarker : true; + util.bindAll([ '_onSuccess', '_onError', '_finish', - '_setupUI' + '_setupUI', + '_updateCamera', + '_updateMarker', + '_setupMarker', + '_onClickGeolocate' ], this); } @@ -72,30 +92,148 @@ class GeolocateControl extends Evented { } onRemove() { + // clear the geolocation watch if exists + if (this._geolocationWatchID !== undefined) { + window.navigator.geolocation.clearWatch(this._geolocationWatchID); + this._geolocationWatchID = undefined; + } + + // clear the marker from the map + if (this.options.showMarker) { + if (this._map.getLayer(markerLayerName)) this._map.removeLayer(markerLayerName); + if (this._map.getSource(markerSourceName)) this._map.removeSource(markerSourceName); + } + this._container.parentNode.removeChild(this._container); this._map = undefined; } _onSuccess(position) { + if (this.options.watchPosition) { + // keep a record of the position so that if the state is BACKGROUND and the user + // clicks the button, we can move to ACTIVE_LOCK immediately without waiting for + // watchPosition to trigger _onSuccess + this._lastKnownPosition = position; + + console.log('GPS Success old watch state ${this._watchState}'); + switch (this._watchState) { + case 'WAITING_ACTIVE': + case 'ACTIVE_LOCK': + case 'ACTIVE_ERROR': + this._watchState = 'ACTIVE_LOCK'; + this._geolocateButton.classList.remove('waiting'); + this._geolocateButton.classList.remove('active-error'); + this._geolocateButton.classList.add('active'); + break; + case 'BACKGROUND': + case 'BACKGROUND_ERROR': + this._watchState = 'BACKGROUND'; + this._geolocateButton.classList.remove('waiting'); + this._geolocateButton.classList.remove('background-error'); + this._geolocateButton.classList.add('background'); + break; + default: + assert(false, 'Unexpected watchState ${this._watchState}'); + } + this._geolocateButton.classList.remove('waiting'); + console.log('...new watch state ${this._watchState}'); + console.log('...classList ${this._geolocateButton.classList}'); + } + + // if in normal mode (not watch mode), or if in watch mode and the state is active watch + // then update the camera + if (!this.options.watchPosition || this._watchState === 'ACTIVE_LOCK') { + console.log('update camera location'); + this._updateCamera(position); + } + + // if showMarker and the watch state isn't off then update the marker location + if (this.options.showMarker && this._watchState !== 'OFF') { + console.log('update marker location'); + this._updateMarker(position); + } + + this.fire('geolocate', position); + this._finish(); + } + + _updateCamera(position) { this._map.jumpTo({ center: [position.coords.longitude, position.coords.latitude], zoom: 17, bearing: 0, pitch: 0 }); + } - this.fire('geolocate', position); - this._finish(); + _updateMarker(position) { + if (position) { + this._map.getSource(markerSourceName).setData({ + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "properties": { + "accuracy": position.coords.accuracy + }, + "geometry": { + "type": "Point", + "coordinates": [position.coords.longitude, position.coords.latitude] + } + }] + }); + } else { + this._map.getSource(markerSourceName).setData({ + "type": "FeatureCollection", + "features": [] + }); + } } _onError(error) { + console.log('GPS Error old watch state ${this._watchState}'); + if (this.options.watchPosition) { + switch (this._watchState) { + case 'WAITING_ACTIVE': + this._watchState = 'ACTIVE_ERROR'; + this._geolocateButton.classList.remove('active'); + this._geolocateButton.classList.add('active-error'); + break; + case 'ACTIVE_LOCK': + this._watchState = 'ACTIVE_ERROR'; + this._geolocateButton.classList.remove('active'); + this._geolocateButton.classList.add('active-error'); + this._geolocateButton.classList.add('waiting'); + // turn marker grey + break; + case 'BACKGROUND': + this._watchState = 'BACKGROUND_ERROR'; + this._geolocateButton.classList.remove('background'); + this._geolocateButton.classList.add('background-error'); + this._geolocateButton.classList.add('waiting'); + // turn marker grey + break; + case 'ACTIVE_ERROR': + break; + default: + assert(false, 'Unexpected watchState ${this._watchState}'); + } + console.log('...new watch state ${this._watchState}'); + console.log('...classList ${this._geolocateButton.classList}'); + } + console.log(error); + this.fire('error', error); + + // make the marker greyed out + this._finish(); } _finish() { if (this._timeoutId) { clearTimeout(this._timeoutId); } this._timeoutId = undefined; + + console.log('finish'); } _setupUI(supported) { @@ -107,26 +245,159 @@ class GeolocateControl extends Evented { this._container); this._geolocateButton.type = 'button'; this._geolocateButton.setAttribute('aria-label', 'Geolocate'); - if (this.options.watchPosition) this._geolocateButton.setAttribute('aria-pressed', false); + + if (this.options.watchPosition) { + this._geolocateButton.setAttribute('aria-pressed', false); + this._watchState = 'OFF'; + } + + // when showMarker is enabled, keep the Geolocate button disabled until the device location marker is setup on the map + if (this.options.showMarker) { + if (this.options.watchPosition) this._watchState = 'INITILIZE'; + this._geolocateButton.disabled = true; + this._setupMarker(); + } + this._geolocateButton.addEventListener('click', this._onClickGeolocate.bind(this)); + + // when the camera is changed (and it's not as a result of the Geolocation Control) change + // the watch mode to background watch, so that the marker is updated but not the camera. + if (this.options.watchPosition) { + this._map.on('movestart', (event) => { + if (event.originalEvent) { + // FIXME this only checks for user camera changes, but only camera changes from this control should be ignored, camera changes via the API should also trigger this code path + console.log('movestart event old watch state ${this._watchState}'); + if (this._watchState === 'ACTIVE_LOCK') { + this._watchState = 'BACKGROUND'; + this._geolocateButton.classList.add('background'); + this._geolocateButton.classList.remove('active'); + } + console.log('...new watch state ${this._watchState}'); + console.log('...classList ${this._geolocateButton.classList}'); + } + }); + } + + if (!this.options.showMarker) this.fire('ready'); + } + + _setupMarker() { + const defaultMarkerPaintProperties = { + 'circle-radius': 10, + 'circle-color': '#33b5e5', + 'circle-stroke-color': '#fff', + 'circle-stroke-width': 2 + }; + + // sources can't be added until the Map style is loaded + this._map.on('load', () => { + this._map.addSource(markerSourceName, { + type: 'geojson', + data: { + type: 'FeatureCollection', + features: [] + } + }); + + this._map.addLayer({ + id: markerLayerName, + source: markerSourceName, + type: 'circle', + paint: this.options.markerPaintProperties || defaultMarkerPaintProperties + }); + + if (this.options.watchPosition) this._watchState = 'OFF'; + + this._geolocateButton.disabled = false; + + this.fire('ready'); + }); } _onClickGeolocate() { const positionOptions = util.extend(defaultGeoPositionOptions, this.options && this.options.positionOptions || {}); - // toggle watching the device location if (this.options.watchPosition) { - if (this._geolocationWatchID !== undefined) { - // clear watchPosition - this._geolocateButton.classList.remove('watching'); - this._geolocateButton.setAttribute('aria-pressed', false); + console.log('Click Geolocate old watch state ${this._watchState}'); + + // update watchState and do any outgoing state cleanup + switch (this._watchState) { + case 'OFF': + // turn on the Geolocate Control + this._watchState = 'WAITING_ACTIVE'; + break; + case 'WAITING_ACTIVE': + case 'ACTIVE_LOCK': + case 'ACTIVE_ERROR': + case 'BACKGROUND_ERROR': + // turn off the Geolocate Control + this._watchState = 'OFF'; + this._geolocateButton.classList.remove('waiting'); + this._geolocateButton.classList.remove('active'); + this._geolocateButton.classList.remove('active-error'); + this._geolocateButton.classList.remove('background'); + this._geolocateButton.classList.remove('background-error'); + break; + case 'BACKGROUND': + this._watchState = 'ACTIVE_LOCK'; + this._geolocateButton.classList.remove('background'); + // set camera to last known location + if (this._lastKnownPosition) this._updateCamera(this._lastKnownPosition); + break; + default: + assert(false, 'Unexpected watchState ${this._watchState}'); + } + + // incoming state setup + switch (this._watchState) { + case 'WAITING_ACTIVE': + this._geolocateButton.classList.add('waiting'); + this._geolocateButton.classList.add('active'); + break; + case 'ACTIVE_LOCK': + this._geolocateButton.classList.add('active'); + break; + case 'ACTIVE_ERROR': + this._geolocateButton.classList.add('waiting'); + this._geolocateButton.classList.add('active-error'); + break; + case 'BACKGROUND': + this._geolocateButton.classList.add('background'); + break; + case 'BACKGROUND_ERROR': + this._geolocateButton.classList.add('waiting'); + this._geolocateButton.classList.add('background-error'); + break; + case 'OFF': + break; + default: + assert(false, 'Unexpected watchState ${this._watchState}'); + } + + console.log('...new watch state ${this._watchState}'); + console.log('...classList ${this._geolocateButton.classList}'); + + // manage geolocation.watchPosition / geolocation.clearWatch + if (this._watchState === 'OFF' && this._geolocationWatchID !== undefined) { + // clear watchPosition as we've changed to an OFF state + + console.log('clear watch'); window.navigator.geolocation.clearWatch(this._geolocationWatchID); + this._geolocationWatchID = undefined; - } else { - // enable watchPosition - this._geolocateButton.classList.add('watching'); + this._geolocateButton.classList.remove('waiting'); + this._geolocateButton.setAttribute('aria-pressed', false); + + if (this.options.showMarker) { + this._updateMarker(null); + } + } else if (this._geolocationWatchID === undefined) { + // enable watchPosition since watchState is not OFF and there is no watchPosition already running + + this._geolocateButton.classList.add('waiting'); this._geolocateButton.setAttribute('aria-pressed', true); + this._geolocationWatchID = window.navigator.geolocation.watchPosition( this._onSuccess, this._onError, positionOptions); } @@ -144,21 +415,30 @@ class GeolocateControl extends Evented { module.exports = GeolocateControl; /** - * geolocate event. + * Fired on each Geolocation API position update which returned as success. * * @event geolocate * @memberof GeolocateControl * @instance - * @property {Position} data The returned [Position](https://developer.mozilla.org/en-US/docs/Web/API/Position) object from the callback in [Geolocation.getCurrentPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition). + * @property {Position} data The returned [Position](https://developer.mozilla.org/en-US/docs/Web/API/Position) object from the callback in [Geolocation.getCurrentPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) or [Geolocation.watchPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition). * */ /** - * error event. + * Fired on each Geolocation API position update which returned as an error. * * @event error * @memberof GeolocateControl * @instance - * @property {PositionError} data The returned [PositionError](https://developer.mozilla.org/en-US/docs/Web/API/PositionError) object from the callback in [Geolocation.getCurrentPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition). + * @property {PositionError} data The returned [PositionError](https://developer.mozilla.org/en-US/docs/Web/API/PositionError) object from the callback in [Geolocation.getCurrentPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) or [Geolocation.watchPosition()](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition). + * + */ + +/** + * Fired when the Geolocate Control is ready and able to be clicked. + * + * @event ready + * @memberof GeolocateControl + * @instance * */ From 979802f2d884093a3509de42df4d507249be211d Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Thu, 15 Dec 2016 15:20:11 +1100 Subject: [PATCH 03/19] fix console.logs, add a faded state to the marker when GPS position isn't current, add shadow to marker --- js/ui/control/geolocate_control.js | 78 ++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/js/ui/control/geolocate_control.js b/js/ui/control/geolocate_control.js index ee3320f324f..551736f416b 100644 --- a/js/ui/control/geolocate_control.js +++ b/js/ui/control/geolocate_control.js @@ -10,6 +10,7 @@ const defaultGeoPositionOptions = { enableHighAccuracy: false, timeout: 6000 /* const className = 'mapboxgl-ctrl'; const markerLayerName = '_geolocate-control-marker'; +const markerShadowLayerName = '_geolocate-control-marker-shadow'; const markerSourceName = '_geolocate-control-marker-position'; let supportsGeolocation; @@ -52,6 +53,8 @@ function checkGeolocationSupport(callback) { * @param {Object} [options.watchPosition=false] If `true` the Geolocate Control becomes a toggle button and when active the map will receive updates to the device location as it changes. * @param {Object} [options.showMarker=true] By default a marker will be added to the map with the device's location. Set to `false` to disable. * @param {Object} [options.markerPaintProperties={'circle-radius': 10, 'circle-color': '#33b5e5', 'circle-stroke-color': '#fff', 'circle-stroke-width': 2}] A [Circle Layer Paint Properties](https://www.mapbox.com/mapbox-gl-style-spec/#paint_circle) object to customize the device location marker. The default is a blue dot with a white stroke. + * @param {Object} [options.markerShadowPaintProperties={ 'circle-radius': 14, 'circle-color': '#000', 'circle-opacity': 0.5, 'circle-blur': 0.4, 'circle-translate': [2, 2], 'circle-translate-anchor': 'viewport' }] A [Circle Layer Paint Properties](https://www.mapbox.com/mapbox-gl-style-spec/#paint_circle) object to customize the device location marker, used as a "shadow" layer. The default is a blurred semi-transparent black shadow. + * @param {Object} [options.markerStalePaintProperties={'circle-color': '#a6d5e5', 'circle-opacity': 0.5, 'circle-stroke-opacity': 0.8}] A [Circle Layer Paint Properties](https://www.mapbox.com/mapbox-gl-style-spec/#paint_circle) object applied to the base markerPaintProperties to customize the device location marker in a stale state. The marker is stale when there was a Geolocation error so the previous reported location is used, which may no longer be current. The default is a faded blue dot with a white stroke. * @example * map.addControl(new mapboxgl.GeolocateControl({ * positionOptions: { @@ -115,7 +118,7 @@ class GeolocateControl extends Evented { // watchPosition to trigger _onSuccess this._lastKnownPosition = position; - console.log('GPS Success old watch state ${this._watchState}'); + console.log(`GPS Success old watch state ${this._watchState}`); switch (this._watchState) { case 'WAITING_ACTIVE': case 'ACTIVE_LOCK': @@ -133,11 +136,11 @@ class GeolocateControl extends Evented { this._geolocateButton.classList.add('background'); break; default: - assert(false, 'Unexpected watchState ${this._watchState}'); + assert(false, `Unexpected watchState ${this._watchState}`); } this._geolocateButton.classList.remove('waiting'); - console.log('...new watch state ${this._watchState}'); - console.log('...classList ${this._geolocateButton.classList}'); + console.log(`...new watch state ${this._watchState}`); + console.log(`...classList ${this._geolocateButton.classList}`); } // if in normal mode (not watch mode), or if in watch mode and the state is active watch @@ -153,6 +156,13 @@ class GeolocateControl extends Evented { this._updateMarker(position); } + if (this.options.showMarker) { + // restore any paint properties which were changed to make the marker stale + for (const paintProperty in this._markerStalePaintProperties) { + this._map.setPaintProperty(markerLayerName, paintProperty, this._markerPaintProperties[paintProperty]); + } + } + this.fire('geolocate', position); this._finish(); } @@ -190,7 +200,7 @@ class GeolocateControl extends Evented { } _onError(error) { - console.log('GPS Error old watch state ${this._watchState}'); + console.log(`GPS Error old watch state ${this._watchState}`); if (this.options.watchPosition) { switch (this._watchState) { case 'WAITING_ACTIVE': @@ -215,13 +225,20 @@ class GeolocateControl extends Evented { case 'ACTIVE_ERROR': break; default: - assert(false, 'Unexpected watchState ${this._watchState}'); + assert(false, `Unexpected watchState ${this._watchState}`); } - console.log('...new watch state ${this._watchState}'); - console.log('...classList ${this._geolocateButton.classList}'); + console.log(`...new watch state ${this._watchState}`); + console.log(`...classList ${this._geolocateButton.classList}`); } console.log(error); + if (this.options.showMarker) { + // apply paint properties to make the marker stale + for (const paintProperty in this._markerStalePaintProperties) { + this._map.setPaintProperty(markerLayerName, paintProperty, this._markerStalePaintProperties[paintProperty]); + } + } + this.fire('error', error); // make the marker greyed out @@ -267,14 +284,14 @@ class GeolocateControl extends Evented { this._map.on('movestart', (event) => { if (event.originalEvent) { // FIXME this only checks for user camera changes, but only camera changes from this control should be ignored, camera changes via the API should also trigger this code path - console.log('movestart event old watch state ${this._watchState}'); + console.log(`movestart event old watch state ${this._watchState}`); if (this._watchState === 'ACTIVE_LOCK') { this._watchState = 'BACKGROUND'; this._geolocateButton.classList.add('background'); this._geolocateButton.classList.remove('active'); } - console.log('...new watch state ${this._watchState}'); - console.log('...classList ${this._geolocateButton.classList}'); + console.log(`...new watch state ${this._watchState}`); + console.log(`...classList ${this._geolocateButton.classList}`); } }); } @@ -284,12 +301,29 @@ class GeolocateControl extends Evented { _setupMarker() { const defaultMarkerPaintProperties = { - 'circle-radius': 10, + 'circle-radius': 9, 'circle-color': '#33b5e5', 'circle-stroke-color': '#fff', - 'circle-stroke-width': 2 + 'circle-stroke-width': 3 + }; + const defaultMarkerShadowPaintProperties = { + 'circle-radius': 14, + 'circle-color': '#000', + 'circle-opacity': 0.5, + 'circle-blur': 0.4, + 'circle-translate': [2, 2], + 'circle-translate-anchor': 'viewport' + }; + const defaultMarkerStalePaintProperties = { + 'circle-color': '#a6d5e5', + 'circle-opacity': 0.5, + 'circle-stroke-opacity': 0.8 }; + this._markerPaintProperties = this.options.markerPaintProperties || defaultMarkerPaintProperties; + this._markerShadowPaintProperties = this.options.markerShadowPaintProperties || defaultMarkerShadowPaintProperties; + this._markerStalePaintProperties = util.extend({}, this._markerPaintProperties, this.options.markerStalePaintProperties || defaultMarkerStalePaintProperties); + // sources can't be added until the Map style is loaded this._map.on('load', () => { this._map.addSource(markerSourceName, { @@ -300,11 +334,17 @@ class GeolocateControl extends Evented { } }); + this._map.addLayer({ + id: markerShadowLayerName, + source: markerSourceName, + type: 'circle', + paint: this._markerShadowPaintProperties + }); this._map.addLayer({ id: markerLayerName, source: markerSourceName, type: 'circle', - paint: this.options.markerPaintProperties || defaultMarkerPaintProperties + paint: this._markerPaintProperties }); if (this.options.watchPosition) this._watchState = 'OFF'; @@ -319,7 +359,7 @@ class GeolocateControl extends Evented { const positionOptions = util.extend(defaultGeoPositionOptions, this.options && this.options.positionOptions || {}); if (this.options.watchPosition) { - console.log('Click Geolocate old watch state ${this._watchState}'); + console.log(`Click Geolocate old watch state ${this._watchState}`); // update watchState and do any outgoing state cleanup switch (this._watchState) { @@ -346,7 +386,7 @@ class GeolocateControl extends Evented { if (this._lastKnownPosition) this._updateCamera(this._lastKnownPosition); break; default: - assert(false, 'Unexpected watchState ${this._watchState}'); + assert(false, `Unexpected watchState ${this._watchState}`); } // incoming state setup @@ -372,11 +412,11 @@ class GeolocateControl extends Evented { case 'OFF': break; default: - assert(false, 'Unexpected watchState ${this._watchState}'); + assert(false, `Unexpected watchState ${this._watchState}`); } - console.log('...new watch state ${this._watchState}'); - console.log('...classList ${this._geolocateButton.classList}'); + console.log(`...new watch state ${this._watchState}`); + console.log(`...classList ${this._geolocateButton.classList}`); // manage geolocation.watchPosition / geolocation.clearWatch if (this._watchState === 'OFF' && this._geolocationWatchID !== undefined) { From 7715029f4a39ed95750249a6f64e5755f83b97e6 Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Fri, 16 Dec 2016 15:38:21 +1100 Subject: [PATCH 04/19] add Geolocate Control tests --- package.json | 1 + test/js/ui/control/geolocate.test.js | 102 +++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 test/js/ui/control/geolocate.test.js diff --git a/package.json b/package.json index 9c73a28b35c..b497b762321 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "jsonlint": "^1.6.2", "lodash.template": "^4.4.0", "minifyify": "^7.0.1", + "mock-geolocation": "^1.0.11", "npm-run-all": "^3.0.0", "nyc": "^8.3.0", "pixelmatch": "^4.0.2", diff --git a/test/js/ui/control/geolocate.test.js b/test/js/ui/control/geolocate.test.js new file mode 100644 index 00000000000..3046886a8c6 --- /dev/null +++ b/test/js/ui/control/geolocate.test.js @@ -0,0 +1,102 @@ +'use strict'; + +const test = require('mapbox-gl-js-test').test; +const window = require('../../../../js/util/window'); +const Map = require('../../../../js/ui/map'); +const GeolocateControl = require('../../../../js/ui/control/geolocate_control'); + +// window and navigator globals need to be set for mock-geolocation +global.window = {}; +global.navigator = {}; +const geolocation = require('mock-geolocation'); +geolocation.use(); + +// assign the mock geolocation to window +global.window.navigator = global.navigator; +window.navigator.geolocation = global.window.navigator.geolocation; + +function createMap() { + const container = window.document.createElement('div'); + return new Map({ + container: container, + style: { + version: 8, + sources: {}, + layers: [] + } + }); +} + +test('GeolocateControl with no options', (t) => { + const map = createMap(); + const geolocate = new GeolocateControl(); + map.addControl(geolocate); + t.end(); +}); + +test('GeolocateControl showMarker button state and ready event', (t) => { + const map = createMap(); + const geolocate = new GeolocateControl(); + + map.addControl(geolocate); + + geolocate.on('ready', () => { + t.false(geolocate._geolocateButton.disabled, 'button enabled when control is ready'); + t.end(); + }); +}); + +test('GeolocateControl error event', (t) => { + const map = createMap(); + const geolocate = new GeolocateControl(); + map.addControl(geolocate); + + const click = new window.Event('click'); + + geolocate.on('ready', () => { + geolocate.on('error', (error) => { + t.equal(error.code, 2, 'error code matches'); + t.equal(error.message, 'error message', 'error message matches'); + t.end(); + }); + geolocate._geolocateButton.dispatchEvent(click); + geolocation.sendError({code: 2, message: 'error message'}); + }); +}); + +test('GeolocateControl geolocate event', (t) => { + const map = createMap(); + const geolocate = new GeolocateControl(); + map.addControl(geolocate); + + const click = new window.Event('click'); + + geolocate.on('ready', () => { + geolocate.on('geolocate', (position) => { + t.equal(position.coords.latitude, 10, 'geolocate position latitude'); + t.equal(position.coords.longitude, 20, 'geolocate position longitude'); + t.equal(position.coords.accuracy, 30, 'geolocate position accuracy'); + t.equal(position.timestamp, 40, 'geolocate timestamp'); + t.end(); + }); + geolocate._geolocateButton.dispatchEvent(click); + geolocation.send({latitude: 10, longitude: 20, accuracy: 30, timestamp: 40}); + }); +}); + +test('GeolocateControl no watching map centered on geolocation', (t) => { + const map = createMap(); + const geolocate = new GeolocateControl(); + map.addControl(geolocate); + + const click = new window.Event('click'); + + geolocate.on('ready', () => { + map.on('moveend', () => { + t.deepEqual(map.getCenter(), { lat: 10, lng: 20 }, 'map centered on location'); + t.end(); + }); + geolocate._geolocateButton.dispatchEvent(click); + geolocation.send({latitude: 10, longitude: 20, accuracy: 30}); + }); +}); From 5bb5d483e1384b9f991099923b5742570e269ae5 Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Fri, 16 Dec 2016 19:59:04 +1100 Subject: [PATCH 05/19] add active_lock and background events --- js/ui/control/geolocate_control.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/js/ui/control/geolocate_control.js b/js/ui/control/geolocate_control.js index 551736f416b..8f02542d5b9 100644 --- a/js/ui/control/geolocate_control.js +++ b/js/ui/control/geolocate_control.js @@ -163,6 +163,10 @@ class GeolocateControl extends Evented { } } + if (this._watchState === 'ACTIVE_LOCK') { + this.fire('active_lock'); + } + this.fire('geolocate', position); this._finish(); } @@ -289,6 +293,8 @@ class GeolocateControl extends Evented { this._watchState = 'BACKGROUND'; this._geolocateButton.classList.add('background'); this._geolocateButton.classList.remove('active'); + + this.fire('background'); } console.log(`...new watch state ${this._watchState}`); console.log(`...classList ${this._geolocateButton.classList}`); @@ -441,6 +447,10 @@ class GeolocateControl extends Evented { this._geolocationWatchID = window.navigator.geolocation.watchPosition( this._onSuccess, this._onError, positionOptions); } + + if (this._watchState === 'ACTIVE_LOCK') { + this.fire('active_lock'); + } } else { window.navigator.geolocation.getCurrentPosition( this._onSuccess, this._onError, positionOptions); @@ -482,3 +492,21 @@ module.exports = GeolocateControl; * @instance * */ + +/** + * Fired when the Geolocate Control changes to the active_lock state, which happens either when we first obtain a successful Geolocation API position for the device (a geolocate event will follow), or the user clicks the geolocate button when in the background state which uses the last known position to recenter the map and enter active_lock state (no geolocate event will follow unless the device's location changes). + * + * @event active_lock + * @memberof GeolocateControl + * @instance + * + */ + +/** + * Fired when the Geolocate Control changes to the background state, which happens when a user changes the camera during an active position lock. This only applies when watchPosition is true. In the background state, the marker on the map will update with location updates but the camera will not. + * + * @event background + * @memberof GeolocateControl + * @instance + * + */ From 1273e53afa6107b7d6842eff52c931f3006900a4 Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Fri, 16 Dec 2016 20:00:16 +1100 Subject: [PATCH 06/19] any user camera change or api camera change will move to background state, except camera changes internal to the geolocate control --- js/ui/control/geolocate_control.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/ui/control/geolocate_control.js b/js/ui/control/geolocate_control.js index 8f02542d5b9..0ee52fe206c 100644 --- a/js/ui/control/geolocate_control.js +++ b/js/ui/control/geolocate_control.js @@ -177,6 +177,8 @@ class GeolocateControl extends Evented { zoom: 17, bearing: 0, pitch: 0 + }, { + geolocateSource: true // tag this camera change so it won't cause the control to change to background state }); } @@ -286,8 +288,7 @@ class GeolocateControl extends Evented { // the watch mode to background watch, so that the marker is updated but not the camera. if (this.options.watchPosition) { this._map.on('movestart', (event) => { - if (event.originalEvent) { - // FIXME this only checks for user camera changes, but only camera changes from this control should be ignored, camera changes via the API should also trigger this code path + if (!event.geolocateSource) { console.log(`movestart event old watch state ${this._watchState}`); if (this._watchState === 'ACTIVE_LOCK') { this._watchState = 'BACKGROUND'; From ce517424c27424812ab1134490fada74c76c262f Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Fri, 16 Dec 2016 20:00:27 +1100 Subject: [PATCH 07/19] add more test cases --- test/js/ui/control/geolocate.test.js | 130 +++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/test/js/ui/control/geolocate.test.js b/test/js/ui/control/geolocate.test.js index 3046886a8c6..453ac73952f 100644 --- a/test/js/ui/control/geolocate.test.js +++ b/test/js/ui/control/geolocate.test.js @@ -100,3 +100,133 @@ test('GeolocateControl no watching map centered on geolocation', (t) => { geolocation.send({latitude: 10, longitude: 20, accuracy: 30}); }); }); + +test('GeolocateControl watching map updates recenter on location with marker', (t) => { + const map = createMap(); + const geolocate = new GeolocateControl({ + watchPosition: true, + showMarker: true, + markerPaintProperties: { + 'circle-radius': 10, + 'circle-color': '#000', + 'circle-stroke-color': '#fff', + 'circle-stroke-width': 2 + }, + markerStalePaintProperties: { + 'circle-color': '#f00', + } + }); + map.addControl(geolocate); + + const click = new window.Event('click'); + + geolocate.on('ready', () => { + map.once('moveend', () => { + t.deepEqual(map.getCenter(), { lat: 10, lng: 20 }, 'map centered on location after 1st update'); + t.ok(map.getLayer('_geolocate-control-marker'), 'has marker layer'); + t.equals(map.getPaintProperty('_geolocate-control-marker', 'circle-color'), '#000', 'markerPaintProperty circle-color'); + map.once('moveend', () => { + t.deepEqual(map.getCenter(), { lat: 40, lng: 50 }, 'map centered on location after 2nd update'); + geolocate.once('error', () => { + t.equals(map.getPaintProperty('_geolocate-control-marker', 'circle-color'), '#f00', 'markerStalePaintProperty circle-color'); + t.end(); + }); + geolocation.changeError({code: 1, message: 'message'}); + }); + geolocation.change({latitude: 40, longitude: 50, accuracy: 60}); + }); + geolocate._geolocateButton.dispatchEvent(click); + geolocation.send({latitude: 10, longitude: 20, accuracy: 30}); + }); +}); + +test('GeolocateControl watching map background event', (t) => { + const map = createMap(); + const geolocate = new GeolocateControl({ + watchPosition: true + }); + map.addControl(geolocate); + + const click = new window.Event('click'); + + // when the geolocate control is ready + geolocate.once('ready', () => { + map.once('moveend', () => { + geolocate.once('background', () => { + t.end(); + }); + + // manually pan the map away from the geolocation position which should trigger the 'background' event above + map.jumpTo({ + center: [10, 5] + }); + }); + // click the button to activate it into the enabled watch state + geolocate._geolocateButton.dispatchEvent(click); + // send through a location update which should reposition the map and trigger the 'moveend' event above + geolocation.send({latitude: 10, longitude: 20, accuracy: 30}); + }); +}); + +test('GeolocateControl watching map background state', (t) => { + const map = createMap(); + const geolocate = new GeolocateControl({ + watchPosition: true + }); + map.addControl(geolocate); + + const click = new window.Event('click'); + + // when the geolocate control is ready + geolocate.once('ready', () => { + map.once('moveend', () => { + map.once('moveend', () => { + geolocate.once('geolocate', () => { + t.deepEquals(map.getCenter(), {lng: 10, lat: 5}, 'camera not changed after geolocation update in background state'); + t.end(); + }); + // update the geolocation position, since we are in background state when 'geolocate' is triggered above, the camera shouldn't have changed + geolocation.change({latitude: 0, longitude: 0, accuracy: 10}); + }); + + // manually pan the map away from the geolocation position which should trigger the 'moveend' event above + map.jumpTo({ + center: [10, 5] + }); + }); + // click the button to activate it into the enabled watch state + geolocate._geolocateButton.dispatchEvent(click); + // send through a location update which should reposition the map and trigger the 'moveend' event above + geolocation.send({latitude: 10, longitude: 20, accuracy: 30}); + }); +}); + +test('GeolocateControl active_lock event', (t) => { + console.log('start final test'); + const map = createMap(); + const geolocate = new GeolocateControl({ + watchPosition: true + }); + map.addControl(geolocate); + + const click = new window.Event('click'); + + geolocate.once('ready', () => { + geolocate.once('active_lock', () => { + geolocate.once('background', () => { + geolocate.once('active_lock', () => { + t.end(); + }); + // click the geolocate control button again which should transition back to active_lock state + geolocate._geolocateButton.dispatchEvent(click); + }); + + // manually pan the map away from the geolocation position which should trigger the 'background' event above + map.jumpTo({ + center: [10, 5] + }); + }); + geolocate._geolocateButton.dispatchEvent(click); + geolocation.send({latitude: 10, longitude: 20, accuracy: 30, timestamp: 40}); + }); +}); From fd9347fb4781fd0b079c3c20ac9410a80118796a Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Fri, 16 Dec 2016 21:00:44 +1100 Subject: [PATCH 08/19] when geolocation denied go back to off state --- js/ui/control/geolocate_control.js | 54 ++++++++++++++++++------------ 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/js/ui/control/geolocate_control.js b/js/ui/control/geolocate_control.js index 0ee52fe206c..b7f648db887 100644 --- a/js/ui/control/geolocate_control.js +++ b/js/ui/control/geolocate_control.js @@ -208,30 +208,40 @@ class GeolocateControl extends Evented { _onError(error) { console.log(`GPS Error old watch state ${this._watchState}`); if (this.options.watchPosition) { - switch (this._watchState) { - case 'WAITING_ACTIVE': - this._watchState = 'ACTIVE_ERROR'; - this._geolocateButton.classList.remove('active'); - this._geolocateButton.classList.add('active-error'); - break; - case 'ACTIVE_LOCK': - this._watchState = 'ACTIVE_ERROR'; + if (error.code === 1) { + // PERMISSION_DENIED + this._watchState = 'OFF'; + this._geolocateButton.classList.remove('waiting'); this._geolocateButton.classList.remove('active'); - this._geolocateButton.classList.add('active-error'); - this._geolocateButton.classList.add('waiting'); - // turn marker grey - break; - case 'BACKGROUND': - this._watchState = 'BACKGROUND_ERROR'; + this._geolocateButton.classList.remove('active-error'); this._geolocateButton.classList.remove('background'); - this._geolocateButton.classList.add('background-error'); - this._geolocateButton.classList.add('waiting'); - // turn marker grey - break; - case 'ACTIVE_ERROR': - break; - default: - assert(false, `Unexpected watchState ${this._watchState}`); + this._geolocateButton.classList.remove('background-error'); + } else { + switch (this._watchState) { + case 'WAITING_ACTIVE': + this._watchState = 'ACTIVE_ERROR'; + this._geolocateButton.classList.remove('active'); + this._geolocateButton.classList.add('active-error'); + break; + case 'ACTIVE_LOCK': + this._watchState = 'ACTIVE_ERROR'; + this._geolocateButton.classList.remove('active'); + this._geolocateButton.classList.add('active-error'); + this._geolocateButton.classList.add('waiting'); + // turn marker grey + break; + case 'BACKGROUND': + this._watchState = 'BACKGROUND_ERROR'; + this._geolocateButton.classList.remove('background'); + this._geolocateButton.classList.add('background-error'); + this._geolocateButton.classList.add('waiting'); + // turn marker grey + break; + case 'ACTIVE_ERROR': + break; + default: + assert(false, `Unexpected watchState ${this._watchState}`); + } } console.log(`...new watch state ${this._watchState}`); console.log(`...classList ${this._geolocateButton.classList}`); From a24821eed09e47e681ae29e92e5786e8e6aeecb3 Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Fri, 16 Dec 2016 21:00:55 +1100 Subject: [PATCH 09/19] cleanup --- js/ui/control/geolocate_control.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/js/ui/control/geolocate_control.js b/js/ui/control/geolocate_control.js index b7f648db887..a3eaf1677aa 100644 --- a/js/ui/control/geolocate_control.js +++ b/js/ui/control/geolocate_control.js @@ -257,8 +257,6 @@ class GeolocateControl extends Evented { this.fire('error', error); - // make the marker greyed out - this._finish(); } From 9b6cea3eee7329805c1bf77207626c4e2ee4310a Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Fri, 16 Dec 2016 21:12:27 +1100 Subject: [PATCH 10/19] clear watch if moving back to OFF state due to permission denied --- js/ui/control/geolocate_control.js | 31 +++++++++++++++++----------- test/js/ui/control/geolocate.test.js | 2 +- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/js/ui/control/geolocate_control.js b/js/ui/control/geolocate_control.js index a3eaf1677aa..20e3c515209 100644 --- a/js/ui/control/geolocate_control.js +++ b/js/ui/control/geolocate_control.js @@ -216,6 +216,10 @@ class GeolocateControl extends Evented { this._geolocateButton.classList.remove('active-error'); this._geolocateButton.classList.remove('background'); this._geolocateButton.classList.remove('background-error'); + + if (this._geolocationWatchID !== undefined) { + this._clearWatch(); + } } else { switch (this._watchState) { case 'WAITING_ACTIVE': @@ -248,7 +252,7 @@ class GeolocateControl extends Evented { } console.log(error); - if (this.options.showMarker) { + if (this._watchState !== 'OFF' && this.options.showMarker) { // apply paint properties to make the marker stale for (const paintProperty in this._markerStalePaintProperties) { this._map.setPaintProperty(markerLayerName, paintProperty, this._markerStalePaintProperties[paintProperty]); @@ -436,17 +440,7 @@ class GeolocateControl extends Evented { // manage geolocation.watchPosition / geolocation.clearWatch if (this._watchState === 'OFF' && this._geolocationWatchID !== undefined) { // clear watchPosition as we've changed to an OFF state - - console.log('clear watch'); - window.navigator.geolocation.clearWatch(this._geolocationWatchID); - - this._geolocationWatchID = undefined; - this._geolocateButton.classList.remove('waiting'); - this._geolocateButton.setAttribute('aria-pressed', false); - - if (this.options.showMarker) { - this._updateMarker(null); - } + this._clearWatch(); } else if (this._geolocationWatchID === undefined) { // enable watchPosition since watchState is not OFF and there is no watchPosition already running @@ -469,6 +463,19 @@ class GeolocateControl extends Evented { this._timeoutId = setTimeout(this._finish, 10000 /* 10sec */); } } + + _clearWatch() { + console.log('clear watch'); + window.navigator.geolocation.clearWatch(this._geolocationWatchID); + + this._geolocationWatchID = undefined; + this._geolocateButton.classList.remove('waiting'); + this._geolocateButton.setAttribute('aria-pressed', false); + + if (this.options.showMarker) { + this._updateMarker(null); + } + } } module.exports = GeolocateControl; diff --git a/test/js/ui/control/geolocate.test.js b/test/js/ui/control/geolocate.test.js index 453ac73952f..a93fb665775 100644 --- a/test/js/ui/control/geolocate.test.js +++ b/test/js/ui/control/geolocate.test.js @@ -131,7 +131,7 @@ test('GeolocateControl watching map updates recenter on location with marker', ( t.equals(map.getPaintProperty('_geolocate-control-marker', 'circle-color'), '#f00', 'markerStalePaintProperty circle-color'); t.end(); }); - geolocation.changeError({code: 1, message: 'message'}); + geolocation.changeError({code: 2, message: 'position unavaliable'}); }); geolocation.change({latitude: 40, longitude: 50, accuracy: 60}); }); From ed9078262ae612ee250a69b8d970b77d9cc3e447 Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Wed, 21 Dec 2016 15:06:13 +1100 Subject: [PATCH 11/19] clean up console.log's --- js/ui/control/geolocate_control.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/js/ui/control/geolocate_control.js b/js/ui/control/geolocate_control.js index 20e3c515209..ad1b415c048 100644 --- a/js/ui/control/geolocate_control.js +++ b/js/ui/control/geolocate_control.js @@ -118,7 +118,6 @@ class GeolocateControl extends Evented { // watchPosition to trigger _onSuccess this._lastKnownPosition = position; - console.log(`GPS Success old watch state ${this._watchState}`); switch (this._watchState) { case 'WAITING_ACTIVE': case 'ACTIVE_LOCK': @@ -139,20 +138,16 @@ class GeolocateControl extends Evented { assert(false, `Unexpected watchState ${this._watchState}`); } this._geolocateButton.classList.remove('waiting'); - console.log(`...new watch state ${this._watchState}`); - console.log(`...classList ${this._geolocateButton.classList}`); } // if in normal mode (not watch mode), or if in watch mode and the state is active watch // then update the camera if (!this.options.watchPosition || this._watchState === 'ACTIVE_LOCK') { - console.log('update camera location'); this._updateCamera(position); } // if showMarker and the watch state isn't off then update the marker location if (this.options.showMarker && this._watchState !== 'OFF') { - console.log('update marker location'); this._updateMarker(position); } @@ -206,7 +201,6 @@ class GeolocateControl extends Evented { } _onError(error) { - console.log(`GPS Error old watch state ${this._watchState}`); if (this.options.watchPosition) { if (error.code === 1) { // PERMISSION_DENIED @@ -247,10 +241,7 @@ class GeolocateControl extends Evented { assert(false, `Unexpected watchState ${this._watchState}`); } } - console.log(`...new watch state ${this._watchState}`); - console.log(`...classList ${this._geolocateButton.classList}`); } - console.log(error); if (this._watchState !== 'OFF' && this.options.showMarker) { // apply paint properties to make the marker stale @@ -267,8 +258,6 @@ class GeolocateControl extends Evented { _finish() { if (this._timeoutId) { clearTimeout(this._timeoutId); } this._timeoutId = undefined; - - console.log('finish'); } _setupUI(supported) { @@ -301,7 +290,6 @@ class GeolocateControl extends Evented { if (this.options.watchPosition) { this._map.on('movestart', (event) => { if (!event.geolocateSource) { - console.log(`movestart event old watch state ${this._watchState}`); if (this._watchState === 'ACTIVE_LOCK') { this._watchState = 'BACKGROUND'; this._geolocateButton.classList.add('background'); @@ -309,8 +297,6 @@ class GeolocateControl extends Evented { this.fire('background'); } - console.log(`...new watch state ${this._watchState}`); - console.log(`...classList ${this._geolocateButton.classList}`); } }); } @@ -378,8 +364,6 @@ class GeolocateControl extends Evented { const positionOptions = util.extend(defaultGeoPositionOptions, this.options && this.options.positionOptions || {}); if (this.options.watchPosition) { - console.log(`Click Geolocate old watch state ${this._watchState}`); - // update watchState and do any outgoing state cleanup switch (this._watchState) { case 'OFF': @@ -434,9 +418,6 @@ class GeolocateControl extends Evented { assert(false, `Unexpected watchState ${this._watchState}`); } - console.log(`...new watch state ${this._watchState}`); - console.log(`...classList ${this._geolocateButton.classList}`); - // manage geolocation.watchPosition / geolocation.clearWatch if (this._watchState === 'OFF' && this._geolocationWatchID !== undefined) { // clear watchPosition as we've changed to an OFF state @@ -465,7 +446,6 @@ class GeolocateControl extends Evented { } _clearWatch() { - console.log('clear watch'); window.navigator.geolocation.clearWatch(this._geolocationWatchID); this._geolocationWatchID = undefined; From 64cfd4a58832bce17532ed1d78b4e22420b4ca48 Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Thu, 12 Jan 2017 19:49:10 +1100 Subject: [PATCH 12/19] fix maxZoom option in fitBoundsOptions --- js/ui/control/geolocate_control.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/ui/control/geolocate_control.js b/js/ui/control/geolocate_control.js index ca48f539ce4..b3d2d25d3f6 100644 --- a/js/ui/control/geolocate_control.js +++ b/js/ui/control/geolocate_control.js @@ -8,6 +8,7 @@ const assert = require('assert'); const LngLat = require('../../geo/lng_lat'); const defaultGeoPositionOptions = { enableHighAccuracy: false, timeout: 6000 /* 6sec */ }; +const defaultFitBoundsOptions = { maxZoom: 18 }; const className = 'mapboxgl-ctrl'; const markerLayerName = '_geolocate-control-marker'; @@ -175,9 +176,7 @@ class GeolocateControl extends Evented { const center = new LngLat(position.coords.longitude, position.coords.latitude); const radius = position.coords.accuracy; - this._map.fitBounds(center.toBounds(radius), util.extend({ - maxZoom: (this.options.maxZoom !== undefined) ? this.options.maxZoom : 18 - }, this.options.fitBoundsOptions || {}), { + this._map.fitBounds(center.toBounds(radius), util.extend(defaultFitBoundsOptions, this.options.fitBoundsOptions || {}), { geolocateSource: true // tag this camera change so it won't cause the control to change to background state }); } From a2ef69a5ae1f175bba8256d702a2fb2d404446ac Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Thu, 12 Jan 2017 19:50:18 +1100 Subject: [PATCH 13/19] add fit to accuracy tests and speed up existing tests --- package.json | 1 + test/js/ui/control/geolocate.test.js | 92 +++++++++++++++++++++++++--- 2 files changed, 83 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index b497b762321..afc9cc4db12 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "mock-geolocation": "^1.0.11", "npm-run-all": "^3.0.0", "nyc": "^8.3.0", + "object.map": "^0.2.0", "pixelmatch": "^4.0.2", "pngjs": "^3.0.0", "proxyquire": "^1.7.9", diff --git a/test/js/ui/control/geolocate.test.js b/test/js/ui/control/geolocate.test.js index a93fb665775..62ab04fc128 100644 --- a/test/js/ui/control/geolocate.test.js +++ b/test/js/ui/control/geolocate.test.js @@ -4,6 +4,7 @@ const test = require('mapbox-gl-js-test').test; const window = require('../../../../js/util/window'); const Map = require('../../../../js/ui/map'); const GeolocateControl = require('../../../../js/ui/control/geolocate_control'); +const map = require('object.map'); // window and navigator globals need to be set for mock-geolocation global.window = {}; @@ -27,6 +28,13 @@ function createMap() { }); } +// convert the coordinates of a LngLat object to a fixed number of digits +function lngLatAsFixed(lngLat, digits) { + return map(lngLat, (val) => { + return val.toFixed(digits); + }); +} + test('GeolocateControl with no options', (t) => { const map = createMap(); const geolocate = new GeolocateControl(); @@ -84,20 +92,69 @@ test('GeolocateControl geolocate event', (t) => { }); }); -test('GeolocateControl no watching map centered on geolocation', (t) => { +test('GeolocateControl geolocate fitBoundsOptions', (t) => { const map = createMap(); - const geolocate = new GeolocateControl(); + const geolocate = new GeolocateControl({ + fitBoundsOptions: { + linear: true, + duration: 0, + maxZoom: 10 + } + }); + map.addControl(geolocate); + + const click = new window.Event('click'); + + geolocate.on('ready', () => { + map.once('moveend', (position) => { + t.equal(map.getZoom(), 10, 'geolocate fitBounds maxZoom'); + t.end(); + }); + geolocate._geolocateButton.dispatchEvent(click); + geolocation.send({latitude: 10, longitude: 20, accuracy: 1}); + }); +}); + +test('GeolocateControl no watching map camera on geolocation', (t) => { + const map = createMap(); + const geolocate = new GeolocateControl({ + fitBoundsOptions: { + maxZoom: 20, + linear: true, + duration: 0 + } + }); map.addControl(geolocate); const click = new window.Event('click'); geolocate.on('ready', () => { map.on('moveend', () => { - t.deepEqual(map.getCenter(), { lat: 10, lng: 20 }, 'map centered on location'); + t.deepEqual(lngLatAsFixed(map.getCenter(), 4), { lat: 10, lng: 20 }, 'map centered on location'); + + const mapBounds = map.getBounds(); + + // map bounds should contain or equal accuracy bounds, that is the ensure accuracy bounds doesn't fall outside the map bounds + const accuracyBounds = map.getCenter().toBounds(1000); + t.ok(accuracyBounds.getNorth().toFixed(4) <= mapBounds.getNorth().toFixed(4), 'map contains north of accuracy radius'); + t.ok(accuracyBounds.getSouth().toFixed(4) >= mapBounds.getSouth().toFixed(4), 'map contains south of accuracy radius'); + t.ok(accuracyBounds.getEast().toFixed(4) <= mapBounds.getEast().toFixed(4), 'map contains east of accuracy radius'); + t.ok(accuracyBounds.getWest().toFixed(4) >= mapBounds.getWest().toFixed(4), 'map contains west of accuracy radius'); + + // map bounds should not be too much bigger on all edges of the accuracy bounds (this test will only work for an orthogonal bearing), + // ensures map bounds does not contain buffered accuracy bounds, as if it does there is too much gap around the accuracy bounds + const bufferedAccuracyBounds = map.getCenter().toBounds(1100); + t.notOk( + (bufferedAccuracyBounds.getNorth().toFixed(4) < mapBounds.getNorth().toFixed(4)) && + (bufferedAccuracyBounds.getSouth().toFixed(4) > mapBounds.getSouth().toFixed(4)) && + (bufferedAccuracyBounds.getEast().toFixed(4) < mapBounds.getEast().toFixed(4)) && + (bufferedAccuracyBounds.getWest().toFixed(4) > mapBounds.getWest().toFixed(4)), + 'map bounds is much is larger than the accuracy radius'); + t.end(); }); geolocate._geolocateButton.dispatchEvent(click); - geolocation.send({latitude: 10, longitude: 20, accuracy: 30}); + geolocation.send({latitude: 10, longitude: 20, accuracy: 1000}); }); }); @@ -106,6 +163,10 @@ test('GeolocateControl watching map updates recenter on location with marker', ( const geolocate = new GeolocateControl({ watchPosition: true, showMarker: true, + fitBoundsOptions: { + linear: true, + duration: 0 + }, markerPaintProperties: { 'circle-radius': 10, 'circle-color': '#000', @@ -122,11 +183,11 @@ test('GeolocateControl watching map updates recenter on location with marker', ( geolocate.on('ready', () => { map.once('moveend', () => { - t.deepEqual(map.getCenter(), { lat: 10, lng: 20 }, 'map centered on location after 1st update'); + t.deepEqual(lngLatAsFixed(map.getCenter(), 4), { lat: 10, lng: 20 }, 'map centered on location after 1st update'); t.ok(map.getLayer('_geolocate-control-marker'), 'has marker layer'); t.equals(map.getPaintProperty('_geolocate-control-marker', 'circle-color'), '#000', 'markerPaintProperty circle-color'); map.once('moveend', () => { - t.deepEqual(map.getCenter(), { lat: 40, lng: 50 }, 'map centered on location after 2nd update'); + t.deepEqual(lngLatAsFixed(map.getCenter(), 4), { lat: 40, lng: 50 }, 'map centered on location after 2nd update'); geolocate.once('error', () => { t.equals(map.getPaintProperty('_geolocate-control-marker', 'circle-color'), '#f00', 'markerStalePaintProperty circle-color'); t.end(); @@ -143,7 +204,11 @@ test('GeolocateControl watching map updates recenter on location with marker', ( test('GeolocateControl watching map background event', (t) => { const map = createMap(); const geolocate = new GeolocateControl({ - watchPosition: true + watchPosition: true, + fitBoundsOptions: { + linear: true, + duration: 0 + } }); map.addControl(geolocate); @@ -171,7 +236,11 @@ test('GeolocateControl watching map background event', (t) => { test('GeolocateControl watching map background state', (t) => { const map = createMap(); const geolocate = new GeolocateControl({ - watchPosition: true + watchPosition: true, + fitBoundsOptions: { + linear: true, + duration: 0 + } }); map.addControl(geolocate); @@ -202,10 +271,13 @@ test('GeolocateControl watching map background state', (t) => { }); test('GeolocateControl active_lock event', (t) => { - console.log('start final test'); const map = createMap(); const geolocate = new GeolocateControl({ - watchPosition: true + watchPosition: true, + fitBoundsOptions: { + linear: true, + duration: 0 + } }); map.addControl(geolocate); From d444afa72f86e21d6f698aff3d39342ea7fd34ca Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Thu, 12 Jan 2017 19:50:37 +1100 Subject: [PATCH 14/19] reorder geolocation documentation --- js/ui/control/geolocate_control.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/ui/control/geolocate_control.js b/js/ui/control/geolocate_control.js index b3d2d25d3f6..520d55be589 100644 --- a/js/ui/control/geolocate_control.js +++ b/js/ui/control/geolocate_control.js @@ -47,10 +47,10 @@ function checkGeolocationSupport(callback) { * geolocation support is not available, the GeolocateControl will not * be visible. * - * The GeolocateControl has two modes. If `watchPosition` is `false` (default) the control acts as a button, which when pressed will set the map's camera to target the device location. If the device moves, the map won't update. This is most suited for the desktop. If `watchPosition` is `true` the control acts as a toggle button that when active the device's location is actively monitored for changes. In this mode there is a concept of an active lock and background. In active lock the map's camera will automatically update as the device's location changes until the user manually changes the camera (such as panning or zooming). When this happens the control is in background so that the location marker still updates but the camera doesn't. - * * The zoom level applied will depend on the accuracy of the geolocation provided by the device. * + * The GeolocateControl has two modes. If `watchPosition` is `false` (default) the control acts as a button, which when pressed will set the map's camera to target the device location. If the device moves, the map won't update. This is most suited for the desktop. If `watchPosition` is `true` the control acts as a toggle button that when active the device's location is actively monitored for changes. In this mode there is a concept of an active lock and background. In active lock the map's camera will automatically update as the device's location changes until the user manually changes the camera (such as panning or zooming). When this happens the control is in background so that the location marker still updates but the camera doesn't. + * * @implements {IControl} * @param {Object} [options] * @param {Object} [options.positionOptions={enableHighAccuracy: false, timeout: 6000}] A Geolocation API [PositionOptions](https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions) object. From b969da48951f9f259c330171c6e878c6e2397c95 Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Thu, 12 Jan 2017 20:07:37 +1100 Subject: [PATCH 15/19] eslint fix --- test/js/ui/control/geolocate.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/js/ui/control/geolocate.test.js b/test/js/ui/control/geolocate.test.js index 62ab04fc128..a1ca61c5639 100644 --- a/test/js/ui/control/geolocate.test.js +++ b/test/js/ui/control/geolocate.test.js @@ -106,7 +106,7 @@ test('GeolocateControl geolocate fitBoundsOptions', (t) => { const click = new window.Event('click'); geolocate.on('ready', () => { - map.once('moveend', (position) => { + map.once('moveend', () => { t.equal(map.getZoom(), 10, 'geolocate fitBounds maxZoom'); t.end(); }); From 95cad7a3dae64a8726f7004de71253d5d03c84b9 Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Fri, 20 Jan 2017 13:22:14 +1100 Subject: [PATCH 16/19] change terminology * use showUserLocation and trackUserLocation instead of showMarker and watchPosition * use camelCase for activeLock * use "user" location rather than "device"location * use dot to refer to the marker/symbol on the map representing the user's location --- js/ui/control/geolocate_control.js | 84 ++++++++++++++-------------- test/js/ui/control/geolocate.test.js | 18 +++--- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/js/ui/control/geolocate_control.js b/js/ui/control/geolocate_control.js index 520d55be589..4b0b13e1cc2 100644 --- a/js/ui/control/geolocate_control.js +++ b/js/ui/control/geolocate_control.js @@ -49,25 +49,25 @@ function checkGeolocationSupport(callback) { * * The zoom level applied will depend on the accuracy of the geolocation provided by the device. * - * The GeolocateControl has two modes. If `watchPosition` is `false` (default) the control acts as a button, which when pressed will set the map's camera to target the device location. If the device moves, the map won't update. This is most suited for the desktop. If `watchPosition` is `true` the control acts as a toggle button that when active the device's location is actively monitored for changes. In this mode there is a concept of an active lock and background. In active lock the map's camera will automatically update as the device's location changes until the user manually changes the camera (such as panning or zooming). When this happens the control is in background so that the location marker still updates but the camera doesn't. + * The GeolocateControl has two modes. If `trackUserLocation` is `false` (default) the control acts as a button, which when pressed will set the map's camera to target the user location. If the user moves, the map won't update. This is most suited for the desktop. If `trackUserLocation` is `true` the control acts as a toggle button that when active the user's location is actively monitored for changes. In this mode there is a concept of an active lock and background. In active lock the map's camera will automatically update as the users's location changes until the user manually changes the camera (such as panning or zooming). When this happens the control is in background so the user's location dot still updates but the camera doesn't. * * @implements {IControl} * @param {Object} [options] * @param {Object} [options.positionOptions={enableHighAccuracy: false, timeout: 6000}] A Geolocation API [PositionOptions](https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions) object. - * @param {Object} [options.fitBoundsOptions={maxZoom: 18}] A [`fitBounds`](#Map#fitBounds) options object to use when the map is panned and zoomed to the device location. The default is to use a `maxZoom` of 18 to limit how far the map will zoom in for very accurate locations. - * @param {Object} [options.watchPosition=false] If `true` the Geolocate Control becomes a toggle button and when active the map will receive updates to the device location as it changes. - * @param {Object} [options.showMarker=true] By default a marker will be added to the map with the device's location. Set to `false` to disable. - * @param {Object} [options.markerPaintProperties={'circle-radius': 10, 'circle-color': '#33b5e5', 'circle-stroke-color': '#fff', 'circle-stroke-width': 2}] A [Circle Layer Paint Properties](https://www.mapbox.com/mapbox-gl-style-spec/#paint_circle) object to customize the device location marker. The default is a blue dot with a white stroke. - * @param {Object} [options.markerShadowPaintProperties={ 'circle-radius': 14, 'circle-color': '#000', 'circle-opacity': 0.5, 'circle-blur': 0.4, 'circle-translate': [2, 2], 'circle-translate-anchor': 'viewport' }] A [Circle Layer Paint Properties](https://www.mapbox.com/mapbox-gl-style-spec/#paint_circle) object to customize the device location marker, used as a "shadow" layer. The default is a blurred semi-transparent black shadow. - * @param {Object} [options.markerStalePaintProperties={'circle-color': '#a6d5e5', 'circle-opacity': 0.5, 'circle-stroke-opacity': 0.8}] A [Circle Layer Paint Properties](https://www.mapbox.com/mapbox-gl-style-spec/#paint_circle) object applied to the base markerPaintProperties to customize the device location marker in a stale state. The marker is stale when there was a Geolocation error so the previous reported location is used, which may no longer be current. The default is a faded blue dot with a white stroke. + * @param {Object} [options.fitBoundsOptions={maxZoom: 18}] 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 18 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.userLocationPaintProperties={'circle-radius': 10, 'circle-color': '#33b5e5', 'circle-stroke-color': '#fff', 'circle-stroke-width': 2}] A [Circle Layer Paint Properties](https://www.mapbox.com/mapbox-gl-style-spec/#paint_circle) object to customize the user's location dot. The default is a blue dot with a white stroke. + * @param {Object} [options.userLocationShadowPaintProperties={ 'circle-radius': 14, 'circle-color': '#000', 'circle-opacity': 0.5, 'circle-blur': 0.4, 'circle-translate': [2, 2], 'circle-translate-anchor': 'viewport' }] A [Circle Layer Paint Properties](https://www.mapbox.com/mapbox-gl-style-spec/#paint_circle) object to customize the user's location dot, used as a "shadow" layer. The default is a blurred semi-transparent black shadow. + * @param {Object} [options.userLocationStalePaintProperties={'circle-color': '#a6d5e5', 'circle-opacity': 0.5, 'circle-stroke-opacity': 0.8}] A [Circle Layer Paint Properties](https://www.mapbox.com/mapbox-gl-style-spec/#paint_circle) object applied to the base userLocationPaintProperties to customize the user's location dot in a stale state. The dot is stale when there was a Geolocation error leading to the previous reported location to be used, which may no longer be current. The default is a faded blue dot with a white stroke. * * @example * map.addControl(new mapboxgl.GeolocateControl({ * positionOptions: { * enableHighAccuracy: true * }, - * watchPosition: true, - * markerPaintProperties: { + * trackUserLocation: true, + * userLocationPaintProperties: { * 'circle-color': '#000' * } * })); @@ -78,8 +78,8 @@ class GeolocateControl extends Evented { super(); this.options = options || {}; - // apply default for options.showMarker - this.options.showMarker = (this.options && 'showMarker' in this.options) ? this.options.showMarker : true; + // apply default for options.showUserLocation + this.options.showUserLocation = (this.options && 'showUserLocation' in this.options) ? this.options.showUserLocation : true; util.bindAll([ '_onSuccess', @@ -108,7 +108,7 @@ class GeolocateControl extends Evented { } // clear the marker from the map - if (this.options.showMarker) { + if (this.options.showUserLocation) { if (this._map.getLayer(markerLayerName)) this._map.removeLayer(markerLayerName); if (this._map.getSource(markerSourceName)) this._map.removeSource(markerSourceName); } @@ -118,7 +118,7 @@ class GeolocateControl extends Evented { } _onSuccess(position) { - if (this.options.watchPosition) { + if (this.options.trackUserLocation) { // keep a record of the position so that if the state is BACKGROUND and the user // clicks the button, we can move to ACTIVE_LOCK immediately without waiting for // watchPosition to trigger _onSuccess @@ -148,24 +148,24 @@ class GeolocateControl extends Evented { // if in normal mode (not watch mode), or if in watch mode and the state is active watch // then update the camera - if (!this.options.watchPosition || this._watchState === 'ACTIVE_LOCK') { + if (!this.options.trackUserLocation || this._watchState === 'ACTIVE_LOCK') { this._updateCamera(position); } - // if showMarker and the watch state isn't off then update the marker location - if (this.options.showMarker && this._watchState !== 'OFF') { + // if showUserLocation and the watch state isn't off then update the marker location + if (this.options.showUserLocation && this._watchState !== 'OFF') { this._updateMarker(position); } - if (this.options.showMarker) { + if (this.options.showUserLocation) { // restore any paint properties which were changed to make the marker stale - for (const paintProperty in this._markerStalePaintProperties) { - this._map.setPaintProperty(markerLayerName, paintProperty, this._markerPaintProperties[paintProperty]); + for (const paintProperty in this._userLocationStalePaintProperties) { + this._map.setPaintProperty(markerLayerName, paintProperty, this._userLocationPaintProperties[paintProperty]); } } if (this._watchState === 'ACTIVE_LOCK') { - this.fire('active_lock'); + this.fire('activeLock'); } this.fire('geolocate', position); @@ -205,7 +205,7 @@ class GeolocateControl extends Evented { } _onError(error) { - if (this.options.watchPosition) { + if (this.options.trackUserLocation) { if (error.code === 1) { // PERMISSION_DENIED this._watchState = 'OFF'; @@ -247,10 +247,10 @@ class GeolocateControl extends Evented { } } - if (this._watchState !== 'OFF' && this.options.showMarker) { + if (this._watchState !== 'OFF' && this.options.showUserLocation) { // apply paint properties to make the marker stale - for (const paintProperty in this._markerStalePaintProperties) { - this._map.setPaintProperty(markerLayerName, paintProperty, this._markerStalePaintProperties[paintProperty]); + for (const paintProperty in this._userLocationStalePaintProperties) { + this._map.setPaintProperty(markerLayerName, paintProperty, this._userLocationStalePaintProperties[paintProperty]); } } @@ -274,14 +274,14 @@ class GeolocateControl extends Evented { this._geolocateButton.type = 'button'; this._geolocateButton.setAttribute('aria-label', 'Geolocate'); - if (this.options.watchPosition) { + if (this.options.trackUserLocation) { this._geolocateButton.setAttribute('aria-pressed', false); this._watchState = 'OFF'; } - // when showMarker is enabled, keep the Geolocate button disabled until the device location marker is setup on the map - if (this.options.showMarker) { - if (this.options.watchPosition) this._watchState = 'INITILIZE'; + // when showUserLocation is enabled, keep the Geolocate button disabled until the device location marker is setup on the map + if (this.options.showUserLocation) { + if (this.options.trackUserLocation) this._watchState = 'INITILIZE'; this._geolocateButton.disabled = true; this._setupMarker(); } @@ -291,7 +291,7 @@ class GeolocateControl extends Evented { // when the camera is changed (and it's not as a result of the Geolocation Control) change // the watch mode to background watch, so that the marker is updated but not the camera. - if (this.options.watchPosition) { + if (this.options.trackUserLocation) { this._map.on('movestart', (event) => { if (!event.geolocateSource) { if (this._watchState === 'ACTIVE_LOCK') { @@ -305,7 +305,7 @@ class GeolocateControl extends Evented { }); } - if (!this.options.showMarker) this.fire('ready'); + if (!this.options.showUserLocation) this.fire('ready'); } _setupMarker() { @@ -329,9 +329,9 @@ class GeolocateControl extends Evented { 'circle-stroke-opacity': 0.8 }; - this._markerPaintProperties = this.options.markerPaintProperties || defaultMarkerPaintProperties; - this._markerShadowPaintProperties = this.options.markerShadowPaintProperties || defaultMarkerShadowPaintProperties; - this._markerStalePaintProperties = util.extend({}, this._markerPaintProperties, this.options.markerStalePaintProperties || defaultMarkerStalePaintProperties); + this._userLocationPaintProperties = this.options.userLocationPaintProperties || defaultMarkerPaintProperties; + this._userLocationShadowPaintProperties = this.options.userLocationShadowPaintProperties || defaultMarkerShadowPaintProperties; + this._userLocationStalePaintProperties = util.extend({}, this._userLocationPaintProperties, this.options.userLocationStalePaintProperties || defaultMarkerStalePaintProperties); // sources can't be added until the Map style is loaded this._map.on('load', () => { @@ -347,16 +347,16 @@ class GeolocateControl extends Evented { id: markerShadowLayerName, source: markerSourceName, type: 'circle', - paint: this._markerShadowPaintProperties + paint: this._userLocationShadowPaintProperties }); this._map.addLayer({ id: markerLayerName, source: markerSourceName, type: 'circle', - paint: this._markerPaintProperties + paint: this._userLocationPaintProperties }); - if (this.options.watchPosition) this._watchState = 'OFF'; + if (this.options.trackUserLocation) this._watchState = 'OFF'; this._geolocateButton.disabled = false; @@ -367,7 +367,7 @@ class GeolocateControl extends Evented { _onClickGeolocate() { const positionOptions = util.extend(defaultGeoPositionOptions, this.options && this.options.positionOptions || {}); - if (this.options.watchPosition) { + if (this.options.trackUserLocation) { // update watchState and do any outgoing state cleanup switch (this._watchState) { case 'OFF': @@ -437,7 +437,7 @@ class GeolocateControl extends Evented { } if (this._watchState === 'ACTIVE_LOCK') { - this.fire('active_lock'); + this.fire('activeLock'); } } else { window.navigator.geolocation.getCurrentPosition( @@ -456,7 +456,7 @@ class GeolocateControl extends Evented { this._geolocateButton.classList.remove('waiting'); this._geolocateButton.setAttribute('aria-pressed', false); - if (this.options.showMarker) { + if (this.options.showUserLocation) { this._updateMarker(null); } } @@ -494,16 +494,16 @@ module.exports = GeolocateControl; */ /** - * Fired when the Geolocate Control changes to the active_lock state, which happens either when we first obtain a successful Geolocation API position for the device (a geolocate event will follow), or the user clicks the geolocate button when in the background state which uses the last known position to recenter the map and enter active_lock state (no geolocate event will follow unless the device's location changes). + * Fired when the Geolocate Control changes to the active lock state, which happens either upon first obtaining a successful Geolocation API position for the user (a geolocate event will follow), or the user clicks the geolocate button when in the background state which uses the last known position to recenter the map and enter active lock state (no geolocate event will follow unless the users's location changes). * - * @event active_lock + * @event activeLock * @memberof GeolocateControl * @instance * */ /** - * Fired when the Geolocate Control changes to the background state, which happens when a user changes the camera during an active position lock. This only applies when watchPosition is true. In the background state, the marker on the map will update with location updates but the camera will not. + * Fired when the Geolocate Control changes to the background state, which happens when a user changes the camera during an active position lock. This only applies when trackUserLocation is true. In the background state, the dot on the map will update with location updates but the camera will not. * * @event background * @memberof GeolocateControl diff --git a/test/js/ui/control/geolocate.test.js b/test/js/ui/control/geolocate.test.js index a1ca61c5639..b824a36fc52 100644 --- a/test/js/ui/control/geolocate.test.js +++ b/test/js/ui/control/geolocate.test.js @@ -42,7 +42,7 @@ test('GeolocateControl with no options', (t) => { t.end(); }); -test('GeolocateControl showMarker button state and ready event', (t) => { +test('GeolocateControl showUserLocation button state and ready event', (t) => { const map = createMap(); const geolocate = new GeolocateControl(); @@ -161,8 +161,8 @@ test('GeolocateControl no watching map camera on geolocation', (t) => { test('GeolocateControl watching map updates recenter on location with marker', (t) => { const map = createMap(); const geolocate = new GeolocateControl({ - watchPosition: true, - showMarker: true, + trackUserLocation: true, + showUserLocation: true, fitBoundsOptions: { linear: true, duration: 0 @@ -204,7 +204,7 @@ test('GeolocateControl watching map updates recenter on location with marker', ( test('GeolocateControl watching map background event', (t) => { const map = createMap(); const geolocate = new GeolocateControl({ - watchPosition: true, + trackUserLocation: true, fitBoundsOptions: { linear: true, duration: 0 @@ -236,7 +236,7 @@ test('GeolocateControl watching map background event', (t) => { test('GeolocateControl watching map background state', (t) => { const map = createMap(); const geolocate = new GeolocateControl({ - watchPosition: true, + trackUserLocation: true, fitBoundsOptions: { linear: true, duration: 0 @@ -270,10 +270,10 @@ test('GeolocateControl watching map background state', (t) => { }); }); -test('GeolocateControl active_lock event', (t) => { +test('GeolocateControl activeLock event', (t) => { const map = createMap(); const geolocate = new GeolocateControl({ - watchPosition: true, + trackUserLocation: true, fitBoundsOptions: { linear: true, duration: 0 @@ -284,9 +284,9 @@ test('GeolocateControl active_lock event', (t) => { const click = new window.Event('click'); geolocate.once('ready', () => { - geolocate.once('active_lock', () => { + geolocate.once('activeLock', () => { geolocate.once('background', () => { - geolocate.once('active_lock', () => { + geolocate.once('activeLock', () => { t.end(); }); // click the geolocate control button again which should transition back to active_lock state From 8a8331197eea40d677606d593f9e3c377035b61e Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Fri, 20 Jan 2017 13:29:45 +1100 Subject: [PATCH 17/19] replace object.map dependency --- package.json | 1 - test/js/ui/control/geolocate.test.js | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index afc9cc4db12..b497b762321 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,6 @@ "mock-geolocation": "^1.0.11", "npm-run-all": "^3.0.0", "nyc": "^8.3.0", - "object.map": "^0.2.0", "pixelmatch": "^4.0.2", "pngjs": "^3.0.0", "proxyquire": "^1.7.9", diff --git a/test/js/ui/control/geolocate.test.js b/test/js/ui/control/geolocate.test.js index b824a36fc52..c624348a96a 100644 --- a/test/js/ui/control/geolocate.test.js +++ b/test/js/ui/control/geolocate.test.js @@ -4,7 +4,6 @@ const test = require('mapbox-gl-js-test').test; const window = require('../../../../js/util/window'); const Map = require('../../../../js/ui/map'); const GeolocateControl = require('../../../../js/ui/control/geolocate_control'); -const map = require('object.map'); // window and navigator globals need to be set for mock-geolocation global.window = {}; @@ -30,9 +29,10 @@ function createMap() { // convert the coordinates of a LngLat object to a fixed number of digits function lngLatAsFixed(lngLat, digits) { - return map(lngLat, (val) => { - return val.toFixed(digits); - }); + return Object.keys(lngLat).reduce((previous, current) => { + previous[current] = lngLat[current].toFixed(digits); + return previous; + }, {}); } test('GeolocateControl with no options', (t) => { From add2afd0c5028d47f848b2603a33279584926d14 Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Fri, 20 Jan 2017 16:02:09 +1100 Subject: [PATCH 18/19] fix tests after terminology change --- test/js/ui/control/geolocate.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/js/ui/control/geolocate.test.js b/test/js/ui/control/geolocate.test.js index c624348a96a..93012c29ab4 100644 --- a/test/js/ui/control/geolocate.test.js +++ b/test/js/ui/control/geolocate.test.js @@ -158,7 +158,7 @@ test('GeolocateControl no watching map camera on geolocation', (t) => { }); }); -test('GeolocateControl watching map updates recenter on location with marker', (t) => { +test('GeolocateControl watching map updates recenter on location with dot', (t) => { const map = createMap(); const geolocate = new GeolocateControl({ trackUserLocation: true, @@ -167,13 +167,13 @@ test('GeolocateControl watching map updates recenter on location with marker', ( linear: true, duration: 0 }, - markerPaintProperties: { + userLocationPaintProperties: { 'circle-radius': 10, 'circle-color': '#000', 'circle-stroke-color': '#fff', 'circle-stroke-width': 2 }, - markerStalePaintProperties: { + userLocationStalePaintProperties: { 'circle-color': '#f00', } }); @@ -185,11 +185,11 @@ test('GeolocateControl watching map updates recenter on location with marker', ( map.once('moveend', () => { t.deepEqual(lngLatAsFixed(map.getCenter(), 4), { lat: 10, lng: 20 }, 'map centered on location after 1st update'); t.ok(map.getLayer('_geolocate-control-marker'), 'has marker layer'); - t.equals(map.getPaintProperty('_geolocate-control-marker', 'circle-color'), '#000', 'markerPaintProperty circle-color'); + t.equals(map.getPaintProperty('_geolocate-control-marker', 'circle-color'), '#000', 'userLocationPaintProperty circle-color'); map.once('moveend', () => { t.deepEqual(lngLatAsFixed(map.getCenter(), 4), { lat: 40, lng: 50 }, 'map centered on location after 2nd update'); geolocate.once('error', () => { - t.equals(map.getPaintProperty('_geolocate-control-marker', 'circle-color'), '#f00', 'markerStalePaintProperty circle-color'); + t.equals(map.getPaintProperty('_geolocate-control-marker', 'circle-color'), '#f00', 'userLocationStalePaintProperty circle-color'); t.end(); }); geolocation.changeError({code: 2, message: 'position unavaliable'}); From e3a5582de34280219a674d21ce10a8ba4e41107a Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Tue, 31 Jan 2017 23:53:32 +1100 Subject: [PATCH 19/19] update debug page with new terminology --- debug/debug.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debug/debug.html b/debug/debug.html index 0e0a28ef798..1bb56c7c876 100644 --- a/debug/debug.html +++ b/debug/debug.html @@ -45,8 +45,8 @@ positionOptions: { enableHighAccuracy: true }, - watchPosition: true, - showMarker: true, + trackUserLocation: true, + showUserLocation: true, fitBoundsOptions: { maxZoom: 20 }