Using the HTML Geolocation API to Add App-Like User Experience to your PWA
Geolocation is more important now than ever before. It is a great way to add an ‘app-like’ feature to your progressive web application. We like to know what business, attractions and destinations are around, where we are, and how to get where we are going.
Browsers offer a common JavaScript geolocation service you can use to enrich your HTML driven experiences.
A common use of geolocation is to show where stores are located and possible driving directions.
Geolocation is not limited to just retail. Delivery and driving services use location to update you and the drivers where they are and how much time remains. I use it to find places of interest relative to my location.
Now that Smartphones are ubiquitous, we want information right now, wherever we are. Popular applications like Google Maps, Yelp and Facebook let us locate businesses and friends near our locations right from our phone.
I have been fascinated with geolocation-based data for several years now and have watched the technology evolve. Before smartphones, determining a user's location was to limited to mapping through a geocoordinated IP database.
To access a geocoordinated database, you either needed to subscribe to a service which would translate an IP address to an approximate latitude and longitude, or you needed to maintain a local database with this information. This also needed to be done on the server, which adds latency.
Either option was not cheap or completely reliable. In my experience, an IP address does not actually correlate to a device's location all the time.
For example, I used to have a Sprint 3G service for my laptop. The account that activated my service was located in the Chicago, IL area. Whenever I visited sites using geolocated content, I would receive content related to the Chicago metro area.
The funny thing is the only time I was in the Chicago metro area was changing planes at the airport! We have had a better way to target a user's location thanks to the http://www.w3.org/TR/geolocation-API/” HTML GeoLocation API.
The HTML Geolocation API
All browsers support and have supported native support for the geolocation API for about a decade. So, the API is safe to use.
Using the geolocation API combined with a map service like Microsoft Bing Maps or Google Maps Platforms provide a rich end user experience. You can add a map service to your site to add more value and app like experiences to extend your brand.
Now you can pinpoint where the visitor is located and add nearby locations to a map, provide driving directions and more. You should be aware these services do charge for some features. You will need to consult their service pages to understand their pricing model so you won’t be surprised.
Even though every browser supports the Geolocation API it is still advisable to feature detect if the API is supported. The geolocation object should be a member of the navigator object, so you can check if it is in the navigator.
Detecting Geolocation Support
if ("geolocation" in navigator) {
} else {
}
A reason you should feature detect is just in case the user has disabled the service. You should also be aware that modern browsers are now gating the API behind HTTPS and some require getting the user’s permission before the API works.
You can use HTTP when working locally, using the localhost origin. This helps facilitate development. The geolocation object has three methods:
- getCurrentPosition: a direct call to get the device’s current position
- watchPosition: triggers when the device’s location changes
- clearWatch: stops or clears the watch created by calling the watchPosition method
Using getCurrentPosition
The simplest use of the geolocation object is to get the device location in a single call using the getCurrentPosition method.
The method has three parameters, a success callback, an error callback and options object. The last two parameters are optional.
The success callback will have a single parameter passed, a Position object. Likewise, the error callback receives a single parameter, a PositionError object.
The options parameter is a PositionOptions object. It has three properties:
- maximumAge: an integer (milliseconds) indicating how old a cached position is valid
- timeout: how many milliseconds before the error handler is invoked, default is no timeout
- enableHighAccuracy: true or false, false by default. Enabling results in more power consumption and more time to collect position.
All PositionOptions properties are optional. If a value is not supplied the geolocation system uses defaults. The Position object passed to the success callback has two properties, coords (a Coordinates object) and timestamp. The coordinates have all the (read only) values we are after:
- latitude : double
- longitude : double
- altitude : double, meters above sea level
- accuracy : accuracy or radius of accuracy in meters
- altitudeAccuracy : accuracy or radius of accuracy in meters
- heading : how many degrees from true North the device is moving
- speed : velocity in meters/second the device is moving
You should note not all values will be supplied. You will always get at least the latitude and longitude. I will review accuracy a little later.
Altitude depends on the device capabilities, so you should account for the values not being provided. Create a graceful enhancement experience when the altitude add value to the application experience.
The timestamp value is a Date object specifying when the location was determined.
if ( navigator.geolocation ) {
navigator.geolocation.getCurrentPosition( setCurrentPosition, positionError, {
enableHighAccuracy: false,
timeout: 15000,
maximumAge: 0
} );
}
function setCurrentPosition( position ) {
document.querySelector( '.accuracy' ).innerHTML = position.coords.accuracy;
document.querySelector( '.altitude' ).innerHTML = position.coords.altitude;
document.querySelector( '.altitudeAccuracy' ).innerHTML = position.coords.altitudeAccuracy;
document.querySelector( '.heading' ).innerHTML = position.coords.heading;
document.querySelector( '.latitude' ).innerHTML = position.coords.latitude;
document.querySelector( '.longitude' ).innerHTML = position.coords.longitude;
document.querySelector( '.speed' ).innerHTML = position.coords.speed;
}
Handling Position Errors
If there was a problem capturing the user’s device position the error callback function will be triggered. The PositionError object contains a numeric Code property, and a message. The code property is an unsigned_short value you can match from the error object’s value.
- UNKNOWN_ERROR : Code 0, The most frustrating error because the position could not be determined and the browser does not know why
- PERMISSION_DENIED : Code 1, The user denied permission to use the geolocation API from the permission prompt
- POSITION_UNAVAILABLE : Code 2, The device location was not available
- TIMEOUT: Code 3, The position could not be collected within the timeout interval
This is an example of how a geolocation error handling callback might work.
function positionError( error ) {
switch ( error.code ) {
case error.PERMISSION_DENIED:
console.error( "User denied the request for Geolocation." );
break;
case error.POSITION_UNAVAILABLE:
console.error( "Location information is unavailable." );
break;
case error.TIMEOUT:
console.error( "The request to get user location timed out." );
break;
case error.UNKNOWN_ERROR:
console.error( "An unknown error occurred." );
break;
}
}
Tracking Position Changes
The getCurrentPosition is great to get the user’s current location. But if your application needs to track the user’s position change, think turn by turn driving directions or tracking a run, having the device provide updated coordinates is more efficient.
Thankfully the geolocation watchPosition method triggers callbacks when the device location is updated. If it were not for this you would be responsible for using a setInterval or requestAnimationFrame method to call getCurrentPosition over and over.
Having the device trigger an update when the device update’s its position is more efficient. This preserves the battery and excessive CPU usage.
The watchPosition method has the same signature as getCurrentPosition, so you can easily update to use the watchPosition method.
The method returns a numeric id, similar to setInterval. You can use this id to later stop the watch.
var geoWatch;
function startWatch() {
if ( !geoWatch ) {
if ( "geolocation" in navigator && "watchPosition" in navigator.geolocation ) {
geoWatch = navigator.geolocation.watchPosition( setCurrentPosition, positionError, {
enableHighAccuracy: false, timeout: 15000, maximumAge: 0
} );
}
}
}
The geolocation clearWatch method accepts the watch id and clears the watch callback. In this example I pass the geoWatch id to the clearWatch method to stop the callback. I further set the variable to undefined because I check if it is being used before calling the watchPosition method.
function stopWatch() {
navigator.geolocation.clearWatch( geoWatch );
geoWatch = undefined;
}
Now you can turn your phone into an expensive step counter!
Just for fun you could also create a progressive web application version of Pokemon Go. The geolocation watchPosition method can be used to track where the player is located so you can display little monsters. I will leave it up to you to figure out how to use the monsters for target practice.
Setting Coordinates Using the Chromium Developer Tools
In Chrome, new Edge and other Chromium based browsers the developers tools have a ‘sensors’ tab. In this panel you can set the device’s latitude and longitude. You can also adjust the device orientation, but that comes in handy with the gyroscope API.
The Geolocation sensor emulation comes with many major world cities already preset. You can add additional locations as needed.
To select a location just press the input field to the right of ‘Geolocation’. It looks like a text input, but it will display a list of available locations.
Pressing the ‘Manage’ button will display a list of locations and the ability to add a new location.
This is a great feature to use when you are developing geolocation dependent applications. The only drawback is you can’t emulate a moving device. But since the watchPosition has the same signature as getCurrentPosition method you can at least make sure your workflow processes the position object correctly.
How Position is Determined
There are three ways devices determine location.
- Wifi
- Satellite
- Cell Tower Triangulation
When using Wifi the network router is normally a fixed location and turns out to be well known. There are several highly accurate services that track that sort of thing and all major platform vendors use one or more of these services to determine a devices position.
Remember I said in the old days you needed to have access to an IP database and the more accurate the database or service the more expensive it was? Well no more, Apple, Google, Microsoft, etc have taken care of this for you, so long as you use the geolocation API.
In my experience these values usually have my location pinned to within 100 feet or about 30 meters. Browsers are not the determining factor when determining the location and accuracy. They simply query the operating system’s interface to the GPS hardware.
The next method is by connecting to a geolocation satellite. These values tend be extremely accurate, usually within 10 meters. The problem is you have to have an unobstructed view to one of the satellites, which is generally when you are outside.
This means when you are inside a building you cannot use this method, but the Wifi method should kick in instead. However, if either of these methods are unavailable and you have a cellular device the last option is to triangulate cell tower positions.
The accuracy from this technique is not very good, typically between 1000 and 3500 meters. Because these methods can vary it is a good idea to always check the accuracy to see if you need to adjust your application's response accordingly. Remember you do not control how the user’s device gets their coordinates, but you can determine how you use the position value based on accuracy.
iOS Approximate Location
Apple introduced a new setting in iOS 14, approximate location. The intent is to protect the user's privacy by providing a broad range of location. Since devices can provide highly accurate locations this could lead to bad guys finding you.
The approximate location setting adds some obfuscation to your location. My interpretation is it will report your position similar to if it were triangulated off cell towers, or roughly within a 1km radius.
The problem this poses is when your application needs precise locations. For example a ride-share application. The driver needs to know where the passenger stands so they can pick them up. Because Apple has not surfaced an option for users to control this on an app by app level it is an all or nothing setting. If the user choses approximate location for their iPhone it affects all apps.
There is no flag or value you application can check to see if the location is accurate or approximate either. You can use the accuracy property to determine how true the position is being reported. Even setting the Highprecision property to true does not change the value.
The value is reported in 15 minute increments. So no updates if the user moves you will not know for 15 minutes.
Summary
Geolocation is one of the cooler, under utilized web platform APIs. While not all web applications can benefit from geolocation, but many can.
The API is fairly simple to implement, but you need to only use it when it offers real value. Over using the API can and will drain the visitor’s battery and consume CPU cycles.
Location is determined by the device, which will use one of three techniques to determine location. Accuracy is provided by the device so you will know how reliable the location data is.
You can get a single location value or use the API to track changes to the device location. So you can display area attractions or monitor a user’s path.