Using the Geolocation API
The Geolocation API Specification is a W3C Recommendation that provides access to a device’s geolocation through scripting. The API provides two methods for accessing a device’s geolocation and can be used for data collection that includes positional information in the form of latitude and longitude. While easy enough to implement, the API can be a bit challenging without a solid understanding of how it works. This article explains some of the less discussed aspects of using the Geolocation API in an effort toward a better implementation.
If you want to learn more about the Geolocation API Specification, see the W3C website or there is also a tutorial located on the Mozilla Developer Network. These two sites were used in developing the information presented here.
Some Things to Know First
There is more to it than writing some JavaScript to find a device’s location. In fact, there are some potential pitfalls, that if not properly considered, may limit the success of using the API in an application.
No Guarantees
The device supplies location information to the Geolocation API. So, if accuracy is important, it is worth checking to ensure the target device has or is capable of using GPS because API allows for location information to be inferred from network signals including WiFi, cellular and other sources.
Secondly, even though most modern browsers include the Geolocation API, it is still good practice to have the script check before trying to get the device’s location (see full script in Showing the Location on a Map).
Battery
Continued use of the GPS on a device can result in increased battery consumption. Therefore, if the implementation of the Geolocation API uses GPS for extended periods, users may need to plug into a power source or intermittently recharge the device.
User Permission
The user must explicitly grant permission before the Geolocation API can access location information. This makes sense in our world of web-based tracking but users will likely need to be informed about the purpose of collecting location information and how it is handled.
Security
In addition to explicitly granting permission, the Chromium Project (to include Chrome, Opera and others) are moving to restrict the use of the Geolocation API to secure origins (HTTPS). See The Chromium Projects for more information.
Asynchronous
The Geolocation API provides location information asynchronously. Meaning that scripts requesting a device’s location will not wait for the information to be returned before continuing execution. This behavior is by design as it can take several seconds to obtain location information. The easiest way to compensate for this behavior is to use a callback to handle the position information when it becomes available.
Options
The Geolocation API stores the latest position in a cache so it can be more resource friendly. Positions in the cache are managed by a maximum age. When a cached position passes it maximum age, it is replaced by a new one during the next request. The allowable maximum age is defined in a PositionOptions{} object and passed to the method requesting the device’s location.
Objects Used in Requesting a Device’s Location
The two methods provided in the API to access a device’s location are getCurrentPosition() and watchPosition(). Both methods take a SuccessCallback() function and optionally take an ErrorCallback() function and a PositionOptions{} object. When invoked, either method will return a Position{} object containing the device’s location to the specified SuccessCallback() function. If an error occurs during the attempt, as in the case of permission denied or a timeout, the ErrorCallback() function, if specified, is passed a PositionError{} object. The PositionError{} object can be used to make decisions about whether to continue to try to get a device’s location or to inform the use about what went wrong with the current request.
Position{} Object:The Position{} object contains the coordinates (latitude and longitude) along with the associated accuracy information and a DOM timestamp. The coordinates and the altitude are relative to the WGS84 datum (EPSG:4326). The cords{} property of the Position{} object only has to contain the coordinates and accuracy properties according to the API, but many browsers include additional properties. An full outline of the Position{} object is shown here.
Position{} Object
Position ├─ coords │ └─ latitude double - decimal degrees │ └─ longitude double - decimal degrees │ └─ altitude double - meters │ └─ accuracy double - meters │ └─ altidudeAccuracy double - meters │ └─ heading double - true north compass heading │ └─ speed double - meters per second └─ timestamp DOM timestamp - date and time of position
Example of the Position{} object:
{coords: { accuracy:80, altitude:null, altitudeAccuracy:null, heading:null, latitude:30.123456, longitude:-97.123456, speed:null}, timestamp: 1450411162743 }
PositionError{} Object
The PositionError{} object is passed to the specified ErrorCallback() function if an error occurs while attempting to get a device’s location. The PositionError{} object simply has an error code and an error message properties.
PositionError ├─ code - short │ └─ 1 = PERMISSION_DENIED │ └─ 2 = POSITION_UNAVAILABLE │ └─ 3 = TIMEOUT └─ message – Detailed description of the error.
Example of the PositionError{} object:
{code: 1, message: "User denied Geolocation"}
PositionOptions{} Object
The final object used when requesting a device’s location is the PositionOptions{}. The PositionOptions{} object is essential when attempting to get the best results possible or when managing the maximum age of cached positions. The PositionOptions{} object can have any combination of the following three properties.
PositionOptions ├─ enableHighAccuracy boolean - False may utilize geolocation providers other than the GPS ├─ timeout milliseconds - how long to try to get a location before returning an error └─ maximumAge milliseconds - the maximum age of a cached position before attempting to get an updated location, otherwise the cached position is used
Example of the PositionOptions{} object:
{maximumAge: 5000, timeout: 20000, enableHighAccuracy: true}
Requesting a Single Position
The getCurrentPosition() method is a single request to get a device’s location. The following script defines the PositionOptions{} object, a success callback function named logPosition() and an error callback function named logError(). The last line invokes the getCurrentPosition() method from the navigator.geolocation object in the browser. Upon successfully obtaining a device’s location, the logPosition() function is passed the position PositionObject{}.
// options // maximumAge : don’t use positions cached for more than 5 seconds // timeout: only try for 20 seconds to get position. Then return error // enableHighAccuracy: get the best result possible (GPS) var options = {"maximumAge": 5000, "timeout": 20000, "enableHighAccuracy": true} // success callback that is invoked when location is retrieved function logPosition(position) { console.log(position); } // error callback that is invoked when an error occurs function logError(err) { console.log(err); } navigator.geolocation.getCurrentPosition(logPosition, logError,options);
Continuously Watching the Position
The Geolocation API also allows for monitoring the current location by using the watchPosition() method. Watching a position means an updated position object is repeatedly passed to the success callback function whenever the device’s location changes. The watchPosition() method will continue passing positions until the clearWatch() method is called. The watchPosition() method takes the same arguments as the getCurrentPosition() method.
// options // maximumAge : don’t use positions cached for more than 5 seconds // timeout: only try for 20 seconds to get position. Then return error // enableHighAccuracy: get the best result possible (GPS) var options = {"maximumAge": 5000, "timeout": 20000, "enableHighAccuracy": true} // success callback that is invoked when location is retrieved function logPosition(position) { console.log(position); } // error callback that is invoked when an error occurs function logError(err) { console.log(err); } // cancel the watchPosition() activity function stop() { navigator.geolocation.clearWatch(watchId); } var watchId = navigator.geolocation.watchPosition(logPosition, logError,options);
Showing the Location on the Map
Let’s use the whatchPosition() example to make an example HTML page that displays the current location on a Leaflet map. The demo of showing a deive's location ona a map can be seen watchPosition() can be seen here.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <title>Geolocation on Leaflet Map</title> <!-- Add the Leaflet stylesheet and script from the cdn --> <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" /> <script src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script> <!-- using the leaflet easy button (https://github.com/CliffCloud/Leaflet.EasyButton) --> <link href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet"> <link rel="stylesheet" href="easy-button.css" /> <script src="easy-button.js"></script> <style> /* style for fullscreen map */ html, body, #map { height:100%; width:100%; padding:0px; margin:0px; color: #000000; } /* style for statusbar */ .statusbar { border-left: solid 1px #a0a0a0; border-right: solid 1px #a0a0a0; border-bottom: solid 1px #a0a0a0; background-color: #f0f0f0; text-align: left; position:absolute; bottom:0; width:100%; } .statusbar ul { margin-left: 0; padding-left: 0; display: inline; ; } .statusbar ul li { margin-left: 0; padding: 3px 15px; list-style: none; display: inline; width: 200px; } #statusbar ul li.first { margin-left: 0; border-left: none; list-style: none; display: inline; } </style> </head> <body> <!-- the leaflet map --> <div id="map"></div> <!-- status bar for position info --> <div class="statusbar"> <ul> <li><span>latitude: </span><span id="lat">0</span></li> <li><span>longitude: </span><span id="lng">0</span></li> <li><span>zoom: </span><span id="zoom">0</span></li> <li><span>accuracy: </span><span id="accuracy">0</span></li> </ul> </div> <script> // *********** leaflet map functions *************** // create the Leaflet map var map = L.map('map').setView([37.78, -92.85], 4); // add openstreetmap tile layer as a basemap L.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpg', { subdomains: '1234', attribution: '<a href="http://www.mapquest.com/" target="_blank">© OpenStreetMap and contributors</a>| \ <a href="https://github.com/CliffCloud/Leaflet.EasyButton" target="_blank">EasyButton</a>' }).addTo(map); // update the zoom level in status bar when it changes map.on('zoomend', function(e) { document.getElementById('zoom').innerText=map.getZoom(); }); // create a circle to be drawn on the map that shows the device's location var circle = L.circleMarker( [37.78, -92.85], {color: '#ffffff',fillColor: '#0099FF',fillOpacity: 0.8, 'weight': 3, 'opacity':.9}) circle.bindPopup("You are here"); // the easy button to toggle watchPosition() see //https://github.com/CliffCloud/Leaflet.EasyButton L.easyButton({ id: "toggle-watch", position: "topleft", leafletClasses: true, states:[{ stateName: "toggle-watch-state", onClick: function(button, map){ // define function for easyButton to toggle watchPosition() toggleWatchPosition(); }, title: "Toggle watch position.", icon: "fa-location-arrow" }] }).addTo(map); // *********** geolocation api functions *************** // initialize watchId to identify the watchPosition() activity var watchId = 0 // set the options for use with the watchPosition() method var options = {"maximumAge": 5000, "timeout": 20000, "enableHighAccuracy": true} // define the success callback for the watchPosition() method // fired everytime the position is updated function showPosition(position) { // move circle to current location circle.setLatLng([position.coords.latitude,position.coords.longitude]); // pan the map to the current location map.panTo(new L.LatLng(position.coords.latitude, position.coords.longitude)); // update statusbar with position information document.getElementById("lat").innerText=parseFloat(position.coords.latitude).toFixed(6); // get latitude from position document.getElementById("lng").innerText=parseFloat(position.coords.longitude).toFixed(6); // get longitude from position document.getElementById("accuracy").innerText=parseInt(position.coords.accuracy); // get accuracy from position document.getElementById("zoom").innerText=map.getZoom(); // get map zoom from map } // define the error callback for the watchPosition() method // fired when attempt to get location fails function showError(err) { alert(err.message); } // function to toggle watchPosition() method function toggleWatchPosition() { if ("geolocation" in navigator) { // check that the geolocation api is available if (watchId > 0) { // if watch id > 0 then cancel the existing watch navigator.geolocation.clearWatch(watchId); // clear/cancel the watch watchId = 0; // reset watchId variable map.removeLayer(circle); // remove the circle from the map document.getElementById("toggle-watch").style.color="#000000";// reset button icon to black } else { // if wachId == 0 then start a new watch watchId = navigator.geolocation.watchPosition(showPosition, showError,options); // start watchPosition() and assign watchId circle.addTo(map); // add circle on map for position document.getElementById('toggle-watch').style.color="#0099FF"; // set button icon to blue } } else { alert("The Geolocation API is not available in your browser."); // inform the user the api is not available in their browser } } </script> </body> </html>
See a demo of the above html page and script it the here.