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 (enableHighAccuracy
, timeout
, maximumAge
). 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
andmaximumAge
.
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.
Search box
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:
v
, set to3.exp
, means that the experimental release of the Google JavaScript Maps API V3 is requested.sensor
indicates whether the web app uses a sensor (such as a GPS sensor) to determine the user's location. It is set tofalse
because in this case Geo retrieves the user's location through the Geolocation API.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.
SearchBox
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 thetimeout
to 8000ms andmaximumAge
to 0ms, and click thecurrentPositionButton
. - Uncheck the
enableHighAccuracy
box and click thecurrentPositionButton
: 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 thanmaximumAge
is available, the current position will be retrieved from the cache and you should notice an improvement in terms of performance. - Set the
timeout
andmaximumumAge
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 toGeoMap.showMap
by theMapSwitcher,
or toGeoMap.showPosition
by theGeolocationBox
. - An
errorCallback
, set toGeoMap.handleGeolocationErrors
. options
, a PositionOptions object built by theGeolocationBox
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.