Please note, this is a STATIC archive of website developer.mozilla.org from 03 Nov 2016, cach3.com does not collect or store any user information, there is no "phishing" involved.

Plotting yourself on the map

Using interactive maps to find places and routes has become a common task for web and mobile users: online maps have doubtless changed the way people travel. This article presents the Geolocation API and explores using web map services such as OpenStreetMap and Google Maps to build a geolocator web application.

Mapping technologies

There are plenty of online maps available these days. To get an idea of the global and local map services available, have a look at this list. This article will focus on the most used one (Google Maps) and on the most open one (OpenStreetMap).

OpenStreetMap

OpenStreetMap is a open and free service, built by a community of mappers that contribute and maintain data about roads and much more all over the world. OpenStreetMap provides dynamic and interactive maps to be included in any web page. OpenLayers is the tool for displaying OpenStreetMap data in most modern web browsers, and it has no server-side dependencies. OpenLayers implements a Javascript API for building rich web-based geographic applications.

Google Maps

Google Maps is the most popular online map service, which also offers a JavaScript API for building rich web-based geographic applications. A notable difference is that OpenStreetMap/OpenLayers are free Software, developed for and by the open source software community, while Google Maps is a proprietary solution, provided and owned by Google.

Geolocation API

The Geolocation API is a W3C Recommendation on which both OpenStreepMap and Google Maps rely to access geographical information associated with the hosting device. The Geolocation API is very well explained at a basic level in our Using Geolocation article.

Demo: Geo

Geo is a geolocation web application. It lets users choose among OpenStreetMap, Google Maps, and a hybrid map that is a Google Map with OpenStreetMap tiles placed on it.

This is a screenshot taken in Firefox:

The red marker is the current position or a search result, while green markers represent interesting places around, randomly positioned for demo purposes. 

You can try the demo live or look at the source code on Github.

Geo uses the Geolocation API to get the user's current position and provides UI controls to manipulate various options (enableHighAccuracytimeoutmaximumAge). The demo also provides a search tool that uses the Google Searchbox in the case of Google and Hybrid map searches, and pure XmlHttpRequests to Nominatim in the case of OpenStreetMap searches. 

Note that OpenStreetMap data is free for everyone to use, but OpenStreetMap tile servers are not: heavy use of OpenStreetMap tiles is forbidden without prior permission from OpenStreetMap's System Administrators: please read the Tile Usage Policy and this article on blog.openstreetmap.org if you want to distribute an app that gets use of tiles from openstreetmap.org

Some HTML & CSS to start

Google Maps, OpenStreetMap and Geolocation are Javascript APIs, so showing the HTML and CSS might be considered out of scope here, however it’s important to understand the elements composing the page on which JavaScript operates. Feel free to skip to the JavaScript and Geolocation section if you are only interested in the JavaScript layer.

Below we will briefly examine the main HTML elements of the web app:

  • Action Box
  • Maps

Action box

The action box consists of tabs, a geolocation button, geolocation options and a search box:

Tabs

Using the tabs you can switch between different kind of maps and search engines:

This is the HTML:

<div class="tabs">
    <ul>
        <li>
            <a id="showOpenStreetMap" href="#showOpenStreetMap">OpenStreetMap</a>
        </li><li>
            <a id="showGoogleMap" href="#showGoogleMap">GoogleMap</a>
        </li><li>
            <a id="showHybridMap" href="#showHybridMap">HybridMap</a>
        </li>
    </ul>
</div>

The interaction with tabs is implemented using target elements in the URL, which are managed by a JavaScript object and the CSS :target pseudoclass:

#box .tabs ul li a:target {
    color: white;
    background: navy;
}
Geolocation button and options

The geolocation HTML includes:

  • A currentPositionBtn used to move the map to the current position of the device.
  • A set of 3 inputs to let the user choose the geolocation options: enableHighAccuracy, timeout and maximumAge.

Here is the HTML:

<div class="geolocation">
    <a id="currentPositionBtn" href="javascript:;">
        <div class="currentPositionBtn">
            <div class="north"></div>
            <div class="east"></div>
            <div class="south"></div>
            <div class="west"></div>
        </div>
    </a>
    <div class="positionOptions">
        <input id="enableHighAccuracy" type="checkbox" checked><label for="enableHighAccuracy">enableHighAccuracy</label>
        <input id="timeout" type="text" placeholder="disabled">
        <input id="maximumAge" type="text" placeholder="disabled">
    </div>
</div>

Note that the currentPositionBtn is made entirely in CSS: if you are curious about it have a look at the shapes.css source file.

The search box contains:

  • An input field in which you can insert the name of the place you are looking for.
  • A search button or a dropdown list, depending on the user experience provided by the search tool.

This is the HTML:

<div class="searchBox">
    <div>
        <div id="nominatimSearch">
            <form>
                <input placeholder="disabled" autocomplete="on" />
                <button id="searchBtn">Search</button>
            </form>
        </div>
        <div id="googleSearch">
            <input placeholder="disabled" autocomplete="off" />
        </div>
        <div id="hybridSearch">
            <input placeholder="disabled" autocomplete="off" />
        </div>
    </div>
</div>

Note: If you're wondering what the placeholder and autocomplete attributes are for, read our HTML forms guide.

Map displays

The maps div contains the HTML elements in which interactive maps are loaded:

<div class="maps">
    <div id="openstreetmap"></div>
    <div id="googlemap"></div>
    <div id="hybridmap"></div>
</div>

When activated, each map is displayed across the full screen:

.maps > div  {
    display: none;
    width: 100%;
    height: 100%;
}

JavaScript and Geolocation

Now we've had a brief look at the HTML and CSS for the project, lte's move on to examine the JavaScript in detail.

Geo imports the following OpenLayer and Google Maps javascript libraries:

<script src="OpenLayers-2.13.1/OpenLayers.js"></script>

<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&libraries=places"></script>

Regarding the Google Maps API, the URL contains 2 parameters:

  1. v, set to 3.exp, means that the experimental release of the Google JavaScript Maps API V3 is requested.
  2. sensor indicates whether the web app uses a sensor (such as a GPS sensor) to determine the user's location. It is set to false because in this case Geo retrieves the user's location through the Geolocation API.
  3. libraries. When loading the JavaScript Maps API via the URL you may optionally load additional libraries. Geo loads places for the search features.

Note: Check Google documentation for more information about accepted parameters.

The JavaScript layer of Geo consists of the following modules:

<script src="scripts/utils/js-util.js"></script>
<script src="scripts/utils/geolocation-manager.js"></script>
<script src="scripts/utils/map-factory.js"></script>

<script src="scripts/controllers/map.js"></script>
<script src="scripts/controllers/open-street-map.js"></script>
<script src="scripts/controllers/google-map.js"></script>
<script src="scripts/controllers/hybrid-map.js"></script>
<script src="scripts/controllers/map-switcher.js"></script>
<script src="scripts/controllers/geolocation-box.js"></script>
<script src="scripts/controllers/search-box.js"></script>

<script src="scripts/app.js"></script>
<script src="scripts/index.js"></script>

GeolocationManager is the module that uses the Geolocation API. Feel free to skip to the Retrieving current position section if you are interested in the Geolocation API.

Important: This demo is built using Object Oriented Programming and readability was preferred over design patterns, performance, maintainability and other quality factors.

Here are the Javascript Objects Geo needs to work.

var GeoMap;    

/* Initialize Javascript objects */
GeolocationManager.init();
GeolocationBox.init();
SearchBox.init();
MapSwitcher.init();

Switching among maps

The MapSwitcher is responsible for the tabs status and interaction. It essentially creates a GeoMap to the window object based on the target element specified in the URL (#openstreetmap is the default) , calls methods of SearchBox to switch between search engines and uses the GeolocationManager to get the current position and show an interactive map.

The MapSwitcher constructor initializes DOM objects and attributes to keep track of initialized GeoMaps:

/* Map containers's DOM objects */
this.openstreetmapBox = document.querySelector('#openstreetmap');
this.googlemapBox = document.querySelector('#googlemap');
this.hybridmapBox = document.querySelector('#hybridmap');

/* Attributes to keep track of initialized GeoMaps */
this.openstreetmap = null;
this.googlemap = null;
this.hybridmap = null;

The right tab is selected thanks to the CSS :target pseudoclass we discussed in the HTML & CSS section.

/* Manage target elements */
window.onhashchange = function() {
    self.performSwitch(window.location.hash);
};

this.performSwitch(window.location.hash);

where the performSwitch method is defined as follows:

/*
 * performSwitch
 * Switch to the requested map
 * @param {String} action
 */
performSwitch: function(action) {

    if (action === '#showOpenStreetMap') {
        this.switchToOpenStreetMap();
    }
    else if (action === '#showGoogleMap') {
        this.switchToGoogleMap();
    }
    else if (action === '#showHybridMap') {
        this.switchToHybridMap();
    }
    else { // trigger hashchange for default action
        window.location.hash = 'showOpenStreetMap';
    }
}

For example, when the #openstreetmap tab is selected, the following JavaScript code controls the map display:

/* Show map container */
this.googlemapBox.style.display = 'none';
this.hybridmapBox.style.display = 'none';
this.openstreetmapBox.style.display = 'block';

/* Show the nominatim search bar */
SearchBox.showNominatimSearch();

/* Create a new GeoMap if it doesn't exist already */
if (this.openstreetmap === null) {
    this.openstreetmap = MapFactory.create({
        mapType: 'openstreetmap',
        mapId: 'openstreetmap'
    });

    /* Save the GeoMap */
    GeoMap = this.openstreetmap;

    /* Initializes the map and the search box */
    GeoMap.initMap();
    GeoMap.initSearchBox();
}
else {
    /* Save the GeoMap */
    GeoMap = this.openstreetmap;
}

The MapFactory creates a new Map of the specified type, so that the returned object can be an istance of OpenStreetMap, GoogleMap or HybridMap.

Have a look at the source code to get more information about map creation and switching.

Playing with maps

Geo supports 3 different kind of interactive map:

  • OpenStreetMap
  • Google Maps
  • A hybrid map that is a Google Map with OpenStreetMap tiles placed on it

These maps are loaded in 3 different HTML elements (#openstreetmap, #googlemap and #hybridmap) and controlled by the Javascript Objects represented in the following class diagram:

The OpenStreetMap and GoogleMap objects extend Map to implement the showMap, showPOIs, showPosition and search methods, which make use of the OpenLayers and Google Maps libraries. 

The inheritances are implemented using the JS object literal:

/* OpenStreetMap extends Map */
JS.extend(OpenStreetMap, Map);

/* GoogleMap extends Map */
JS.extend(GoogleMap, Map);

/* HybridMap extends GoogleMap */
JS.extend(HybridMap, GoogleMap);

To see how it works, have a look at the js-util.js source file.

The Map provides attributes used or overrided by the subclasses

function Map(options) {

   /* Attributes */
    this.mapId = options.mapId;
    this.map = null;
    this.searchInput = null;
    this.searchButton = null;
    this.currentPosition = null;
    this.defaultZoom = 15;
    this.defaultPosition = {
        coords: {
            longitude: 12.4830619,
            latitude: 41.8932575
        }
    };

defines the interface for the subclasses

    /*
     * initMap
     * Initializes and show the map
     */
    initMap: function() {
        console.log('Map.initMap()');
    },
    /*
     * initSearchBox
     * Initialize Search Box
     */
    initSearchBox: function() {
        console.log('Map.initSearchBox()');
    },
    /*
     * showPosition
     * Show the specified position on the map
     * @param {Position|OpenLayers.LonLat} position
     */
    showPosition: function(position) {
        console.log('Map.showPosition(position)');
        console.log(position);
    },
    /*
     * search
     * Perform the search based on the specified query
     * @param {String} query
     */
    search: function(query) {
        console.log('Map.search(query)');
        console.log(query);
    },
    /*
     * showPOIs
     * Show the Points Of Interest around the specified position
     * @param {Position|OpenLayers.LonLat} position
     */
    showPOIs: function(position) {
        console.log('Map.showPOIs(position)');
        console.log(position);
    }

and handles geolocation errors

/*
 * handleGeolocationErrors
 * Handles geolocation errors
 * @param {Position} position
 */
handleGeolocationErrors: function(positionError) {
    console.log('Map.handleGeolocationErrors(positionError)');
    console.log(positionError);

    /* Show the error message */
    alert(positionError.message);
 }

PositionError is the object returned by Geolocation API in case of error. Examples of error are PERMISSION_DENIED, POSITION_UNAVAILABLE and TIMEOUT.

Map’s Constructors

Map’s constructors simply initialize DOM objects:

function OpenStreetMap(options) { // extends Map

    Map.call(this, options);

    /* Initialize superclass attributes */
    this.searchInput = document.querySelector('#nominatimSearch input');
    this.searchButton = document.querySelector('#nominatimSearch button');

    /* OpenStreetMap attributes */
    this.mapnik = null;
    this.fromProjection = null;
    this.toProjection = null;
    this.markers = null;
}
function GoogleMap(options) { // extends Map

    Map.call(this, options);

    /* Initialize superclass attributes */
    this.searchInput = document.querySelector('#googleSearch input');

    /* Initialize GoogleMap attributes */
    this.searchBox = null;
}
function HybridMap(options) { // extends GoogleMap

    GoogleMap.call(this, options);

}
The initMap methods

The initMap method initializes and displays the map, using different libraries depending on the class in which the method is defined.

In OpenStreetMap the initMap method uses the OpenLayers library to get OSM tiles, create a new OpenLayers.Map, add layers to the map and center it to the default position.

/*
 * initMap
 * Initializes and shows the map
 */
initMap: function() {
    Map.prototype.initMap.call(this);

    /* Initialize OpenStreetMap attributes */
    this.mapnik = new OpenLayers.Layer.OSM(); // This layer allows accessing OpenStreetMap tiles
    this.fromProjection = new OpenLayers.Projection("EPSG:4326");   // Transform from WGS 1984
    this.toProjection = new OpenLayers.Projection("EPSG:900913"); // to Spherical Mercator Projection        
    this.markers = new OpenLayers.Layer.Markers("Markers");

    /* Initialize superclass attributes */
    this.map = new OpenLayers.Map(this.mapId);

    /* Add layers */
    this.map.addLayer(this.mapnik);
    this.map.addLayer(this.markers);

    /* Show the map */
    var osmPosition = new OpenLayers.LonLat(this.defaultPosition.coords.longitude, this.defaultPosition.coords.latitude).transform(this.fromProjection, this.toProjection);
    this.map.setCenter(osmPosition, this.defaultZoom);
}

Calling OpenLayers.Layer.OSM() with no arguments means that the layer enables accessing OpenStreetMap tiles. Other layers can be specified: OpenCycleMap for example. Check the documentation for further details.

In GoogleMap the initMap method uses the Google Maps library to create a new google.maps.Map based on options passed as parameter,  and center the map to the default position.

/*
 * initMap
 * Initializes and shows the map
 */
initMap: function() {
    Map.prototype.initMap.call(this);
    
    /* Set GoogleMap options */
    var mapOptions = {
        zoom: this.defaultZoom,
    };

    /* Initialize superclass attributes */
    this.map = new google.maps.Map(document.getElementById(this.mapId), mapOptions);

    /* Show the map */
    var gmPosition = new google.maps.LatLng(this.defaultPosition.coords.latitude, this.defaultPosition.coords.longitude);
    this.map.setCenter(gmPosition);
}

In HybridMap the initMap method  uses the Google Maps library to create a new google.maps.Map based on options in which it’s specified to use OSM tiles instead of Google tiles, to keep using Google controls but turning off StreetView.

HybridMap.prototype.initMap = function() {

    Map.prototype.initMap.call(this);

    /* Set GoogleMap options */
    var mapOptions = {
        zoom: this.defaultZoom,
        mapTypeId: "OSM",
        mapTypeControl: false, // disable user toggling between map types (such as ROADMAP and SATELLITE)
        streetViewControl: false // disable Google StreetView
    };

    /* Initialize superclass attributes */
    this.map = new google.maps.Map(document.getElementById(this.mapId), mapOptions);
    this.searchInput = document.querySelector('#hybridSearch input');

    /* Define OSM map type pointing at the OpenStreetMap tile server */
    this.map.mapTypes.set("OSM", new google.maps.ImageMapType({
        getTileUrl: function(coord, zoom) {
            return "https://tile.openstreetmap.org/" + zoom + "/" + coord.x + "/" + coord.y + ".png";
        },
        tileSize: new google.maps.Size(256, 256),
        name: "OpenStreetMap",
        maxZoom: 18
    }));

    /* Show the map *
    var gmPosition = new google.maps.LatLng(this.defaultPosition.coords.latitude, this.defaultPosition.coords.longitude);
    this.map.setCenter(gmPosition);

}

The magic is done invoking the set() method on the google.maps.Map created, and creating a new ImageMapType where the tiles are taken from tile.openstreetmap.org. And this is the result:

More information about this tecnique can be found in this OpenStreetMap's wiki page.

Please note that using the tile.openstreetmap.org URL directly is highly discouraged by the OpenStreetMap's Tile usage policy.

The showPosition method

The showPosition method accepts a position of type Position and shows the map at the specified position.

OpenStreetMap creates an OpenLayers.LonLat from the Position object passed as parameter, and adds a OpenLayers.Marker to the map:

/*
 * showPosition
 * Show the specified position on the map
 * @param {Position} position
 */
showPosition: function(position) {

    /* Retrieve longitude and latitude from Position */
    var plon = position.coords.longitude;
    var plat = position.coords.latitude;

    /* Calculate the OpenStreetMap position */
    var osmPosition = new OpenLayers.LonLat(plon, plat).transform(this.fromProjection, this.toProjection);

    /* Set the center of the map */
    this.map.setCenter(osmPosition, this.defaultZoom);

    if (this.currentPosition === null) { // if this is the first time this method is invoked

        /* Add a marker to the center */
        this.markers.addMarker(new OpenLayers.Marker(osmPosition));

        /* Show POIs only the first time this method is called */
        this.showPOIs(new OpenLayers.LonLat(plon, plat));

        /* Keep track of the current position */
        this.currentPosition = osmPosition;
    }
}

GoogleMap creates a google.maps.LatLng from the Position object passed as parameter, and adds a google.maps.Marker to the map:

/*
 * showPosition
 * Show the specified position on the map
 * @param {Position} position
 */
showPosition: function(position) {
    Map.prototype.showPosition.call(this, position);

    /* Retrieve latitude and longitude from Position */
    var plat = position.coords.latitude;
    var plon = position.coords.longitude;

    /* Calculate the Google Maps position */
    var gmPosition = new google.maps.LatLng(plat, plon);

    /* Set the center of the map */
    this.map.setCenter(gmPosition);

    if (this.currentPosition === null) { // if this is the first time this method is invoked

        /* Add a marker to the center */
        new google.maps.Marker({
            position: gmPosition,
            map: this.map,
            title: 'Current position'
        });

        /* Show POIs only the first time this method is called */
        this.showPOIs(gmPosition);

        /* Keep track of the current position */
        this.currentPosition = gmPosition;
    }
}

The HybridMap relies completely on the the superclass method.

The handleGeolocationErrors method

The handleGeolocationErrors method of each subclass relies on the superclass implementation.

/*
 * handleGeolocationErrors
 * Handles geolocation errors
 * @param {PositionError} position
 */
handleGeolocationErrors: function(positionError) {
    Map.prototype.handleGeolocationErrors.call(this, positionError);
}
The showPOIs method

The showPOIs method displays the Points Of Interest (which are random positioned markers for demo purposes) around the position specified in the parameter, and provides popup windows for each POI.

OpenStreetMap accepts a position of type OpenLayers.LonLat, adds a set of OpenLayers.Marker to the map, and links a OpenLayers.Popup.FramedCloud to each marker.

/*
 * showPOIs
 * Show the Points Of Interest around the specified position
 * @param {OpenLayers.LonLat} position
 */
showPOIs: function(position) {
    Map.prototype.showPOIs.call(this, position);

    /* Retrieve longitude and latitude from OpenLayers.LonLat */
    var plon = position.lon;
    var plat = position.lat;

    /* Show random positioned markers */
    var self = this;
    for (var i = 0; i < 10; i++) {
        var lon = plon - 0 + (Math.random() * 0.01) - 0.005;
        var lat = plat - 0 + (Math.random() * 0.01) - 0.005;
        var mposition = new OpenLayers.LonLat(lon, lat).transform(self.fromProjection, self.toProjection);

        /* Place a marker on the random position */
        var markerIcon = new OpenLayers.Icon('https://openlayers.org/api/img/marker-green.png');
        var marker = new OpenLayers.Marker(mposition, markerIcon);

        /* Create a popup window and associate it to the marker */
        marker.popup = new OpenLayers.Popup.FramedCloud("osmpopup",
                mposition,
                new OpenLayers.Size(200, 200),
                "place " + i,
                null,
                true);

        marker.events.register("click", marker, function(e) {
            self.map.addPopup(this.popup);
        });

        self.markers.addMarker(marker);
    }
}

GoogleMap accepts a position of type google.maps.LatLng, adds a set of google.maps.Marker to the map, and links a google.maps.InfoWindow to each marker.

/*
 * showPOIs
 * Show the Points Of Interest around the specified position
 * @param {google.maps.LatLng} position
 */
showPOIs: function(position) {
    Map.prototype.showPOIs.call(this, position);

    /* Retrieve latitude and longitude from google.maps.LatLng */
    var plat = position.lat();
    var plon = position.lng();

    /* Show random positioned markers */
    var self = this;
    for (var i = 0; i < 10; i++) {
        var lon = plon - 0 + (Math.random() * 0.01) - 0.005;
        var lat = plat - 0 + (Math.random() * 0.01) - 0.005;
        var mposition = new google.maps.LatLng(lat, lon);

        /* Place a marker on the random position */
        var marker = new google.maps.Marker({
            position: mposition,
            map: self.map,
            icon: 'https://maps.google.com/mapfiles/ms/icons/green-dot.png',
            title: 'Hello World!'
        });

        /* Create a popup window and associate it to the marker */
        marker.infowindow = new google.maps.InfoWindow();
        marker.content = 'place ' + i;

        google.maps.event.addListener(marker, 'click', function() {
            this.infowindow.setContent(this.content);
            this.infowindow.open(self.map, this);
        });
    }
}

The HybridMap relies completely on the superclass method.

Performing searches

Geo supports 2 different kind of  search:

  • Nominatim Search
  • Google Search Box

OpenStreetMap uses the Nominatim search, while GoogleMap and HybridMap uses Places.

The SearchBox is the object that controls the user interaction inside the searchBox HTML element.

The search method submits the query to the search engine of the map displayed and shows the search results on the map by calling the Map’s search method.

/*
 * search
 * Submit the query to the search engine of the map displayed and show the search results on the map
 * @param {String} query
 */
search: function(query) {

    /* Perform the search if a query is specified */
    if (query) {
        GeoMap.search(query);
    }
    else {
        alert("Please insert a address");
    }
}
The initSearchBox methods in Map

The initSearchBox method is called when the Map is created. It initializes the search, using different libraries depending on the class in which it is defined.

OpenStreetMap adds an event handler to the search button

/*
 * initSearchBox
 * Initialize Nominatim Search Box
 */
initSearchBox: function() {
    Map.prototype.initSearchBox.call(this);

    var self = this;

    /* Initialize event handlers */
    this.searchButton.onclick = function() {
        self.search(self.searchInput.value);
        return false;
    };
}

while GoogleMap creates a new google.maps.places.SearchBox on the search input and adds listeners to add advanced features.

/*
 * initSearchBox
 * Initialize Google Search Box
 */
initSearchBox: function() {
    Map.prototype.initSearchBox.call(this);

    this.searchBox = new google.maps.places.SearchBox(this.searchInput);

    var self = this;

    /* Listen for the event fired when the user selects an item from the pick list. */
    google.maps.event.addListener(this.searchBox, 'places_changed', function() {
        self.search(self.searchInput.value);
    });

    /* Bias the SearchBox results towards places that are within the bounds of the current map's viewport. */
    google.maps.event.addListener(this.map, 'bounds_changed', function() {
        var bounds = self.map.getBounds();
        self.searchBox.setBounds(bounds);
    });
}

The HybridMap relies on the superclass method, thus providing a Google Search Box.

The search methods in Map

The search method performs the search based on the query passed in as a parameter.

OpenStreetMap explicitly uses XMLHttpRequest to query nominatim.openstreetmap.org and parse a returned JSON object.

/*
 * search
 * Perform the search based on the specified query
 * @param {String} query
 */
search: function(query) {
    Map.prototype.search.call(this, query);

    /* Prepare AJAX communication with nominatim */
    var xhr = new XMLHttpRequest();
    var method = 'GET';
    var url = 'https://nominatim.openstreetmap.org/?q=' + query + '&format=json';

    var self = this;

    /* Send request */
    xhr.open(method, url, true);
    xhr.send();

    /* Handle answer */
    xhr.onreadystatechange = function() {

        /* If success */
        if (this.readyState === 4 && this.status === 200) {

            /* Parse the JSON response */
            var response = JSON.parse(this.responseText);

            /* Take the first result and get geo infos */
            var rlon = response[0].lon - 0;
            var rlat = response[0].lat - 0;

            var position = new OpenLayers.LonLat(rlon, rlat).transform(self.fromProjection, self.toProjection);
            var marker = new OpenLayers.Marker(position);

            /* Set the center of the map */
            self.map.setCenter(position);

            /* Add a marker on the place found */
            self.markers.addMarker(marker);

            /* Display points of interest around the position */
            self.showPOIs(new OpenLayers.LonLat(rlon, rlat));

            /* Print place found */
            self.searchInput.value = response[0].display_name;
        }
    };
}

GoogleMap relies on the google.maps.places.SearchBox inizialized in the initSearchBox method and invokes getPlaces() on it:

/*
 * search
 * Perform the search based on the specified query
 * @param {String} query
 */
search: function(query) {
    Map.prototype.search.call(this, query);

    /* Retrieve the places found and use the first one */
    var places = this.searchBox.getPlaces();
    var place = places[0];

    /* Add a marker on the place found */
    var marker = new google.maps.Marker({
        map: this.map,
        title: place.name,
        position: place.geometry.location
    });

    /* Set the center of the map */
    this.map.setCenter(place.geometry.location);

    /* Display points of interest around the position */
    this.showPOIs(place.geometry.location);
}

The HybridMap relies completely on the superclass method.

Geolocation options

Geo lets the user manipulate geolocation options (enableHighAccuracy, timeout, maximumAge) via the user interface:

GeolocationBox manages the form above. When created, it initializes the DOM objects

/* Initialize DOM objects */
this.currentPositionButton = document.querySelector('#currentPositionBtn');
this.enableHighAccuracyInput = document.querySelector('.geolocationOptions #enableHighAccuracy');
this.timeoutInput = document.querySelector('.geolocationOptions #timeout');
this.maximumAgeInput = document.querySelector('.geolocationOptions #maximumAge');

sets default values for the options object

/* Default option values */
this.defaultEnableHighAccuracy = this.enableHighAccuracyInput.checked; // as configured in the HTML
this.defaultTimeout = 10000; // 10 seconds
this.defaultMaximumAge = 0; // 0 seconds, no-cache

and initializes event handlers.

var self = this;

this.currentPositionButton.onclick = function() {
    self.showCurrentPosition();
    return false;
};

The getPositionOptions method gets customized options from user interface, then builds and returns a PositionOptions object for the Geolocation API

/*
 * getPositionOptions
 * Return the customized options for the Geolocation API
 * @return {Object} options
 */
getPositionOptions: function() {

    var enableHighAccuracy = this.enableHighAccuracyInput.checked;
    var timeout = this.defaultTimeout;

    if (this.timeoutInput.value && this.timeoutInput.value !== "") {
        timeout = this.timeoutInput.value;
    }

    var maximumAge = this.defaultMaximumAge;

    if (this.maximumAgeInput.value && this.maximumAgeInput.value !== "") {
        maximumAge = this.maximumAgeInput.value;
    }
    
    var options = {
        enableHighAccuracy: enableHighAccuracy,
        timeout: timeout,
        maximumAge: maximumAge
    };

    return options;
}

The MDN page about the PositionOptions object explains the type, meaning and trade-offs of the enableHighAccuracy, timeout, and maximumAge fields.

The showCurrentPosition method shows the current position on the map:

/*
 * showCurrentPosition
 * Show the current position on the map
 */
showCurrentPosition: function() {

    var successCallback = GeoMap.showPosition.bind(GeoMap);
    var errorCallback = GeoMap.handleGeolocationErrors.bind(GeoMap);
    var positionOptions = this.getPositionOptions();

    GeolocationManager.getCurrentPosition(successCallback, errorCallback, positionOptions);
}

The GeolocationManager is called to retrieve the user position: a callback function, that is the Map’s showPosition method, is passed in as a parameter to get it called if the device position is successfully retrieved.

For demo purposes you can try the following use cases:

  • Leave the enableHighAccuracy box checked, set the timeout to 8000ms and maximumAge to 0ms, and click the currentPositionButton.
  • Uncheck the enableHighAccuracy box and click the currentPositionButton: this way the web app should be faster in retrieving the device position but might be less precise.
  • Set maximumAge to a value greater than zero milliseconds: if a cached position not older than maximumAge is available, the current position will be retrieved from the cache and you should notice an improvement in terms of performance.
  • Set the timeout and maximumumAge values to zero or less: you should experiance a time out error message.

Retrieving current position

The GeolocationManager is the object that uses the Geolocation API to get the current position of the device, through the getCurrentPosition method. It takes 3 arguments:

  • A successCallback, set to GeoMap.showMap by the MapSwitcher, or to GeoMap.showPosition by the GeolocationBox.
  • An errorCallback, set to GeoMap.handleGeolocationErrors.
  • options, a PositionOptions object built by the GeolocationBox and explained in the previous section.
/*
 * getCurrentPosition
 * Gets the current position of the device
 * @param {Function} successCallback
 * @param {Function} errorCallback
 * @param {PositionOptions} positionOptions
 */
getCurrentPosition: function(successCallback, errorCallback, positionOptions) {
    
    /* If the geolocation object exists in navigator, get the current position of the device */
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(successCallback, errorCallback, positionOptions);
    }
    else { // if the geolocation API is not supported
        errorCallback();
    }

}

Note that, for security reasons, when a web page tries to access location information the user is notified and asked to grant permission:

If the user allows access to their location info, the successCallback is invoked passing a Position object providing coords and timestamp. If the user denies access to their location info, the errorCallback is invoked passing an PositionError object providing an id and a message, such as "user denied Geolocation" or "timeout expired".

If the Geolocation API is not supported by the browser (i.e. navigator.geolocation is undefined), the errorCallback is called with a customized PositionError object as its argument.

What’s next

This article covers the basic features of OpenLayers and Google Maps APIs. If you are interested in learning more advanced features and contributing in enriching the Geo demo, please refer to the OpenLayers documentation and Google Maps documentation.

Regarding the Geolocation API, this article explained how the navigator.geolocation.getCurrentPosition() can be applied in some detail. The navigator.geolocation object provides a further method however — watchPosition() — that we didn't discuss here. watchPosition() is pretty much the same as getCurrentPosition(), including signature, with the difference that the callback functions are called automatically each time the position of the device changes. Have a look at this MDN page to learn more about how to use this method.

Document Tags and Contributors

 Contributors to this page: chrisdavidmills, franciov
 Last updated by: chrisdavidmills,