Important
Disclaimer
This page is part of the MydriaTech Knowledge Base. There is no guarantee of correctness or value of the provided content. Send any feedback and/or corrections to kb at mydriatech dot se.
Copyright © 2017 MydriaTech AB. All rights reserved, but feel free to get inspired by the content.

Background and scope

This article will show you how to get the user agents (browsers) location if the client supports and permits it using the Geolocation API.

Helper functions used in this article

To be able to show the result of scripts later in this article on demand next to the script itself, two helper functions are used. The first helper function mydriatech.createSpanHere() ensures that results are delivered next to the source code of the article even if the code is executed much later. The second helper function mydriatech.createRunScriptButtonHere() is used to show a small button with the caption "Run script" that will trigger execution of such examples.

(function() {
    "use strict";
    window.mydriatech = window.mydriatech || {};

    /** @return a new span element right after the current script element */
    mydriatech.createSpanHere = function(innerHtml) {
        var script = document.scripts[document.scripts.length-1];
        var span = document.createElement("span");
        if (innerHtml) {
            span.innerHTML = innerHtml;
        }
        script.parentElement.insertBefore(span, script.nextSibling);
        return span;
    };

    /** @return a new button element right after the current script element */
    mydriatech.createRunScriptButtonHere = function(callback) {
        var script = document.scripts[document.scripts.length-1];
        var button = document.createElement("button");
        button.innerHTML = "Run script";
        button.onclick = function() {
            // Hide the button once it is clicked
            button.style.display = "none";
            callback();
        }
        script.parentElement.insertBefore(button, script.nextSibling);
        return button;
    };
}());

The Geolocation API

The Geolocation API defined by W3C Recommendation is a well supported standard for retrieving or subscribing to the client physical location. It works best for devices that has a GPS.

Detecting if the used APIs are present

To be able to see the output of the code examples in this article, you need to have support for the Geolocation API and allow this page to access to your location.

var spanDetect = mydriatech.createSpanHere();
mydriatech.createRunScriptButtonHere(function() {
        spanDetect.innerHTML = "[Detect] Geolocation API: " + window.navigator.geolocation;
});

Actual script output:

Important attributes

Request PositionOptions attributes

enableHighAccuracy

Optional

Ask the application to provide the best possible position available even if it consumes more power or take a long time.

timeout

Optional

If a position has not yet been acquired within the time frame specified by the timeout value (after any client approval), the error handler will be invoked with the TIMEOUT error code (3).

maximumAge

Optional

The maximum age of a cached position response that is acceptable. The default value is 0 (get a new position).

Returned Position attributes

timestamp

Always present

The time of when the position was acquired in milliseconds since the start of the Unix Epoch.

coords.latitude

Always present

Latitude in decimal degrees.

coords.longitude

Always present

Longitude in decimal degrees.

coords.accuracy

Always present

Accuracy in meters of the latitude and longitude location.

coords.altitude

Meters above (the nominal) sea level

coords.altitudeAccuracy

Accuracy in meters of the altitude. Required when coords.altitude is present.

coords.heading

Direction of device in degrees clockwise relative to the true north.

coords.speed

Horizontal speed of device in m/s.

Get the clients position

The easiest example is to request a single position. As a simple example, we will use the getCurrentPosition(successCallback, errorCallback, properties) call to retrieve the device’s last known (cached) location and it’s current location.

The typical use case for this API call is to adapt the page content once you know where the user is located. If your page is trying to help the user solve a problem, knowing the location would allow you to additionally provide the closest physical location where a problem could be solved if this is what the client has requested.

As a small bonus in the following example we will create an iframe with an OpenStreetMap view of the clients current location, spanning the positions accuracy.

(function() {
    "use strict";
    window.mydriatech = window.mydriatech || {};

    mydriatech.insertOpenStreetMapFrame = function(parentElement, position) {
        var baseUrl = "https://www.openstreetmap.org/export/embed.html";
        // Convert accuracy of meters to lat and long degrees
        var equatorialEarthRadius = 6378137;
        var deltaLatitude = position.coords.accuracy/equatorialEarthRadius;
        var deltaLongitude = deltaLatitude/(Math.cos(Math.PI*position.coords.latitude/180));
        var bbox = "?layer=mapnik&bbox=" +
            (position.coords.longitude-deltaLongitude) + "," +
            (position.coords.latitude -deltaLatitude ) + "," +
            (position.coords.longitude+deltaLongitude) + "," +
            (position.coords.latitude +deltaLatitude );
        var iframe = document.createElement("iframe");
        iframe.setAttribute("src", baseUrl + bbox);
        iframe.style.width = "80vmin";
        iframe.style.height = "80vmin";
        iframe.style.border = "0px";
        iframe.scrolling = "no";
        iframe.frameborder = "0";
        parentElement.appendChild(iframe);
    };

    mydriatech.getLocationCached = function(successCallback, errorCallback) {
        if (window.navigator.geolocation) {
            // Request last known cached position of the device
            window.navigator.geolocation.getCurrentPosition(successCallback,
                errorCallback, {maximumAge: Infinity, timeout: 0});
        } else {
            errorCallback({ code: 2, message: "Geolocation API not found." });
        }
    };

    mydriatech.getLocation = function(successCallback, errorCallback) {
        if (window.navigator.geolocation) {
            // Request a fresh position from the device with high accuracy
            window.navigator.geolocation.getCurrentPosition(successCallback,
                errorCallback, {enableHighAccuracy: true, maximumAge: 0});
        } else {
            errorCallback({ code: 2, message: "Geolocation API not found." });
        }
    };
}());

var spanTextCached = mydriatech.createSpanHere();
var spanText = mydriatech.createSpanHere();
var spanMap = mydriatech.createSpanHere();
mydriatech.createRunScriptButtonHere(function() {
    spanTextCached.innerHTML = "[Cached] Aquiring position...<br/>";
    spanText.innerHTML = "[Fresh] Aquiring position...<br/>";
    mydriatech.getLocationCached(function(position) { spanTextCached.innerHTML = "[Cached] " +
        " Latitude:  " + position.coords.latitude  + "° <br/>" +
        " Longitude: " + position.coords.longitude + "° <br/>" +
        " Accuracy:  " + position.coords.accuracy  + "m <br/>" +
        " Timestamp: " + position.timestamp        + "ms <br/>";
    }, function(posError) { spanTextCached.innerHTML =
        "[Cached] Error code: " + posError.code + " message: " + posError.message + "<br/>";
    });
    mydriatech.getLocation(function(position) { spanText.innerHTML = "[Fresh] " +
        " Latitude:  " + position.coords.latitude  + "° <br/>" +
        " Longitude: " + position.coords.longitude + "° <br/>" +
        " Accuracy:  " + position.coords.accuracy  + "m <br/>" +
        " Timestamp: " + position.timestamp        + "ms <br/>";
        mydriatech.insertOpenStreetMapFrame(spanMap, position);
    }, function(posError) { spanText.innerHTML =
        "[Fresh] Error code: " + posError.code + " message: " + posError.message + "<br/>";
    });
});

Actual script output:

Subscribe to the clients position

A very interesting feature of the the Geolocation API is that your code can receive updates as the device moves.

The typical use case is to continuously adapt the page content as you learn where the user is located. You could for example use this to implement a driving assist tool or trigger location based content in general if the client has requested this.

(function() {
    "use strict";
    window.mydriatech = window.mydriatech || {};

    mydriatech.watchLocation = function(successCallback, errorCallback) {
        if (window.navigator.geolocation) {
            // Request new positions that are at most 10 seconds old
            var watchId = window.navigator.geolocation.watchPosition(successCallback,
                errorCallback, {enableHighAccuracy: true, maximumAge: 10000});
            // Explicitly stop watching for position changes when the page is closed
            window.beforeunload = function(e) {
                navigator.geolocation.clearWatch(watchId);
            };
        } else {
            errorCallback({ code: 2, message: "Geolocation API not found." });
        }
    };
}());

var spanWatch = mydriatech.createSpanHere();
mydriatech.createRunScriptButtonHere(function() {
    spanWatch.innerHTML = "[Watch] Aquiring position...<br/>";
    mydriatech.watchLocation(function(position) { spanWatch.innerHTML = "[Watch] " +
        " Latitude:  " + position.coords.latitude  + "° <br/>" +
        " Longitude: " + position.coords.longitude + "° <br/>" +
        " Accuracy:  " + position.coords.accuracy  + "m <br/>" +
        " Timestamp: " + position.timestamp        + "ms <br/>";
        // This is where location specific content could be triggered
    }, function(posError) { spanWatch.innerHTML =
        "[Watch] Error code: " + posError.code + " Error message: " + posError.message;
    });
});

Actual script output:

Some final words

Please remember that not everyone likes being tracked, so consider using explicit opt-in for location tracking if you care about informed consent.

A client that did not ask for a location based enhancement might be very frustrated having to decline this option when visiting your page. Try to only trigger location based services as a result of a client interaction with the page. For example, when clicking "Show location closest to me" it would be natural if the page asked for permission to know where you are located in order to be able to help you.

Also consider the battery drain that would be required by constantly subscribing for an accurate position. A client might not want to stay very long on a page that is constantly polling the GPS.

Using the sensor’s reported location for security is a very bad idea. The device might have a record of previously visited location or the "secure" location might be publicly known to an attacker. On some devices you can put the device in development mode and manually provide the coordinates that the sensor should report and there is always a way data submitted from a client device (that you don’t control) could be subverted to report anything. For example allowing a password reset when a user is "home" is a very bad idea, since this would allow anyone who knows the account and address of the account holder to hijack the account.

So, only use the location data to enhance the (not security related) user experience when explicitly asked for and not for more than that.