Advertisement

Use Geo Location to Give Your Customers Driving Directions

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

This tutorial is an update to a previous one in which we looked at how to show driving instructions directly on a WordPress Website using the the Google Maps API.

In the first tutorial, our users had to manually enter their address into a form on the website – then the directions would be displayed. This was a good example of how to use the Google Maps API but the ability to detect a users' current location, in addition to being able to enter an address manually, was a feature often requested.

There's quite a lot of ground to cover here, but a large portion of it was covered in the previous tutorial. To save me having to repeat myself, please review the first tutorial "Give your Customers Driving Directions" where you'll find everything that isn't explained in this one.

What We Covered in the Original

This is a list of things we are not going to cover in this tutorial. So feel free to review the original tutorial for detailed explanations:

  1. How to register custom settings in the WordPress Admin Panel. The three settings fields were for:
    • The destination
    • The text to display in the Info Window
    • The initial Zoom Level of the map when first loaded
  2. How to get a really accurate Lat/Lon value for your destination using the Google Maps API V3 Sample
  3. How to set up the shortcodes used in this tutorial

Note: Reading the first tutorial will help you understand the parts that are not explained in this tutorial. However, the code provided in the source files contains everything you need.


What We'll Be Creating


Why Is This Better Than the Original?

In addition to what we achieved in the previous tutorial, we will also:

  1. Detect whether or not the user's browser has geo location capabilities
  2. If it does, allow them to use it instead of entering an address manually
  3. Provide a special link to Google Maps. When clicked (or tapped), this link will open up the native Maps application on the device if it's available and will automatically plan the route. This is especially helpful on iOS and Android devices

Other improvements to the original:

  1. We will look at improved error handling
  2. Previously, the Site Admin had to enter the Latitude & Longitude values of the destination in the WordPress Settings – today we'll look at how to accept these Lat/Lon values or a regular address. This means the Admin can either provide a pin-pointed spot on the map (exact position of a building, for example) or just simply the street address instead.

Step 1 Create a CSS File

We'll be adding a tiny bit of CSS to improve the look/layout of our map and buttons, so we'll create an external CSS stylesheet in our map directory.

Inside your theme folder, your map directory should now look like this:

  • map.php
  • map.js
  • map.css

Step 2 Add Some CSS

In the first tutorial, we added a couple of lines of CSS into the theme's main stylesheet, style.css. Grab those lines and insert them into this new CSS file along with everything you see below.

Note: These styles were written to work with the stock Twenty Eleven theme. You may find that the padding, margins or colours may not suit your theme perfectly. Therefore you should feel free to adjust any of this – it won't affect the functionality :)

	#map-container img { max-width: none; }          /* From original tut */
	#map-container { width: 100%; height: 400px; }   /* From original tut */

	/* reduce the height of the map on smaller screens */
	@media only screen and (max-width: 767px) {
		#map-container { height: 235px; }
	}

	/* A class we'll use to hide some elements later */
	.hidden { display: none; }

	/* Button styles - edit at will! */
	.map-button {
		display: block;
		padding: 5px;
		background: #d9edf7;
		border: 1px solid #bce8f1;
		color: #3a87ad;
		margin: 5px 0;
		border-radius: 3px;
		text-shadow: 1px 1px 1px white;
	}

	.map-button:hover, .map-button:focus {
		background: #b1dce5;
		text-decoration: none;
	}

	/* Cancel out any default padding on 'p' elements */
	#directions p {
		margin-bottom: 0;
	}

	/* Adjust how the input element displays */
	#from-input {
		margin: 5px 0;
		border-radius: 3px;
		padding: 5px;
	}

Now you can go ahead and enqueue the file inside the wpmap_map shortcode.

	wp_register_style('wptuts-style', get_template_directory_uri() . '/map/map.css', '', '', false);
	wp_enqueue_style ('wptuts-style');

Step 3 Add the New HTML for the Buttons

Now let's add the markup for the buttons into our wpmap_directions_input shortcode.

  1. Because we only want our new 'geo' buttons to appear for users that have the capability, we'll wrap our buttons in a div and apply the 'hidden' class that we defined in our CSS. Then we can remove this class later if geo location is enabled.
  2. This time we're sending a parameter to the WPmap.getDirections method ('manual' or 'geo') – this allows us to have the original functionality (where a user enters an address manually) along with the new geo location method.
  3. The empty span tag is where we'll insert the special link that will open up the Map application on mobiles and tablets. There's a bit of work involved with constructing the link correctly, so we'll take a closer look at that later in the JavaScript section of this tutorial.
	function wpmap_directions_input() {

		$address_to = get_option('map_config_address');

		$output = '<div id="directions">
			<p>For Driving Directions, Enter your Address below :</p>
			<input id="from-input" type="text" value="" size="20" placeholder="Enter your address here" />
			<select onchange="" id="unit-input">
				<option value="imperial" selected="selected">Imperial</option>
				<option value="metric">Metric</option>
			</select>
			<a href="#" onclick="WPmap.getDirections(\'manual\'); return false" class="map-button">Get Driving Directions </a><br />
			<input id="map-config-address" type="hidden" value="' . $address_to . '"/>
			<div id="geo-directions" class="hidden">
				<p>Alternatively, you can</p>
				<a href="#" onclick="WPmap.getDirections(\'geo\'); return false" class="map-button">Use your Current Location </a>
				<span id="native-link"></span>
			</div>
		</div>';
		return $output;
	}

Quick Recap

So far, in relation to the original tutorial, we have:

  1. Created a CSS file with some basic styling and enqueued it.
  2. Added extra markup to allow for new buttons that will only be seen by modern browsers.

Next, we'll take a look at the JavaScript modifications. There's quite a lot to this next section, so instead of doing a direct comparison with the original, I'll just do my best to explain what is happening in each method/function and you can review the full source files at the end to see how it all fits together.


Step 4 The JavaScript

Now here comes the fun part. In the first tutorial, our init() method was responsible for instantiating the map in the same format for every page load. This meant that everyone would receive the exact same functionality regardless of device capabilities – it's time to change that!

When a user visits our website using a smartphone, for example, we want to be able to offer them the ability to use their current location instead of manually entering it. Also, we want the ability to launch the native Maps application on the phone and have the route automatically planned.

A Quick Word About Browser Support

The GeoLocation JavaScript API is one of the most well supported of all the so-called HTML5 new features. Over 75% of all browsers seem to support it according to caniuse.com. I think that means we're pretty safe! (We'll be providing a fall-back for older browsers anyway :) )

Now, let's dig into the JavaScript.

Understanding the Code

Put simply, all we are looking to do here is provide the option to use geo location if it's available. If it isn't, users will still be able to enter an address manually.

If you take a look at the simplified control flow (below), you can see that we use the same methods to set-up the map, but a couple more if geo location is enabled.

OK, I think we have a good understanding of what we're trying to accomplish here so now I'll provide an explanation of each method individually – as always, please refer to the source files to see how everything fits together in the same file.

Set Properties

Here we query the DOM to retrieve some properties that we'll use later. We also get a couple of objects from the API that will handle the 'get directions' request.

	var WPmap = {

		// HTML Elements we'll use later!
		mapContainer   : document.getElementById('map-container'),
		dirContainer   : document.getElementById('dir-container'),
		toInput        : document.getElementById('map-config-address'),
		fromInput      : document.getElementById('from-input'),
		unitInput      : document.getElementById('unit-input'),
		geoDirections  : document.getElementById('geo-directions'),
		nativeLinkElem : document.getElementById('native-link'),
		startLatLng    : null,
		destination    : null,
		geoLocation    : null,
		geoLat         : null,
		geoLon         : null,

		// Google Maps API Objects
		dirService     : new google.maps.DirectionsService(),
		dirRenderer    : new google.maps.DirectionsRenderer(),
		map            : null,

		/** WPmap Object continues throughout tutorial **/

init()

This is the first method that will be called when our page is loaded.

  1. The first thing we do is check for geo location capabilities in the browser.
    • If it's available – we run through a few more methods to set-up the additional buttons on the page (we'll look at those shortly)
    • If it isn't available, we skip all of that and move straight on to setting up the destination
  2. The last part of the init() method is the event handler that we use to display a message to the user when directions are requested. Note: This is optional – feel free to remove it.
	init:function () {

		if (WPmap.geoLoc = WPmap.getGeo()) {
			// things to do if the browser supports GeoLocation.
			WPmap.getGeoCoords();
		}

		WPmap.getDestination();

		// listen for when Directions are requested
		google.maps.event.addListener(WPmap.dirRenderer, 'directions_changed', function () {

			infoWindow.close();         //close the first infoWindow
			marker.setVisible(false);   //remove the first marker

			// setup strings to be used.
			var distanceString = WPmap.dirRenderer.directions.routes[0].legs[0].distance.text;

			// set the content of the infoWindow before we open it again.
			infoWindow.setContent('Thanks!<br /> It looks like you\'re about <strong> ' + distanceString + '</strong> away from us. <br />Directions are just below the map');

			// re-open the infoWindow
			infoWindow.open(WPmap.map, marker);
			setTimeout(function () {
				infoWindow.close()
			}, 8000); //close it after 8 seconds.

		});
	}//init

Ok, I have shown the init() method first this time so that you can understand how the control flow will work.

Now I'll show you the methods involved when a user has geo location capabilities.


Detecting Geo Location

getGeo()

It all starts with standard 'feature detection'.

To determine whether a browser supports GeoLocation or not, all we do is check for the existence of the navigator.geolocation object.

	getGeo : function() {
		if (!! navigator.geolocation)
			return navigator.geolocation;
		else
			return undefined;
	},

getGeoCoords()

Now that we know the browser has geo location, we can go ahead and request the current co-ordinates.

  1. We call getCurrentPosition() and pass two parameters – a success callback function and an error callback function
	getGeoCoords : function () {
		WPmap.geoLoc.getCurrentPosition(WPmap.setGeoCoords, WPmap.geoError)
	},

setGeoCoords()

This is our success callback. If we get this far, we have successfully retrieved the coordinates of the user.

  1. position will be an object containing the geo location information so we can go ahead and set the Lat/Lon values to object properties.
  2. Next we call showGeoButton() to show the button for using current location.
  3. Finally we call setNativeMapLink() to construct the link that will open up native map applications.
	setGeoCoords : function (position) {
		WPmap.geoLat = position.coords.latitude;
		WPmap.geoLon = position.coords.longitude;
		WPmap.showGeoButton();
		WPmap.setNativeMapLink();
	},

geoError()

This will handle any errors received from getCurrentPosition() – this is very helpful in development, but in production you may want to remove it as we are providing a fallback to the manual address entry anyway.

	geoError : function(error) {
		var message = "";
		// Check for known errors
		switch (error.code) {
			case error.PERMISSION_DENIED:
				message = "This website does not have permission to use the Geo location API";
				break;
			case error.POSITION_UNAVAILABLE:
				message = "Sorry, your current position cannot be determined, please enter your address instead.";
				break;
			case error.PERMISSION_DENIED_TIMEOUT:
				message = "Sorry, we're having trouble trying to determine your current location, please enter your address instead.";
				break;
		}
		if (message == "") {
			var strErrorCode = error.code.toString();
			message = "The position could not be determined due to an unknown error (Code: " + strErrorCode + ").";
		}
		console.log(message);
	},

showGeoButton

Show the 'get current location' button.

  1. Our approach is to always hide the button, unless both JavaScript and Geo Location are enabled. We accomplish this by removing the .hidden class using .removeClass(). This is a helper method that makes removing classes on HTML elements much simpler (it'll be at the bottom of the source files)
	showGeoButton : function() {
		var geoContainer = document.getElementById('geo-directions');
		geoContainer.removeClass('hidden');
	},

setNativeMapLink()

This is the special link that will open up native map applications on iOS and Android devices. Because we previously saved the current Lat/Lon values to our object, we can now easily generate the link with the correct format.

	setNativeMapLink: function() {
		var locString   = WPmap.geoLat + ',' + WPmap.geoLon;
		var destination = WPmap.toInput.value;
		var newdest     = destination.replace(' ', '');
		WPmap.nativeLinkElem.innerHTML = ('<a href="http://maps.google.com/maps?mrsp=0'
			+ '&amp;daddr='
			+ newdest
			+ '&amp;saddr='
			+ locString
			+ '" class="map-button">Open in Google Maps</a>');
	},

getDestination()

Here we are determining whether the Admin has entered a Lat/Lon value or a regular address in the Options page:

  1. We first test to see if toInput is a Lat/Lon value by using a regular expression.
  2. If it is, then we set WPmap.destination equal to a google.maps.LatLng object.
  3. If it isn't, then we use google.maps.Geocoder() to convert the address into a google.maps.LatLng object and set that as the destination.
  4. Either way, now everything is in place to setup the map using setupMap()
	getDestination:function() {

		var toInput = WPmap.toInput.value;
		var isLatLon  = (/^(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)$/.test(toInput));

		if (isLatLon) {
			var n = WPmap.toInput.value.split(",");
			WPmap.destination = new google.maps.LatLng(n[0], n[1]);
			WPmap.setupMap();
		}
		else {
			geocoder = new google.maps.Geocoder();
			geocoder.geocode( { 'address': WPmap.toInput.value}, function(results, status) {
				WPmap.destination = results[0].geometry.location;
				WPmap.setupMap();
			});
		}

	},

setupMap()

Very similar to the original – setup the map with the marker centered on our destination and the text from the Admin options inside the infoWindow.

	/* Initialize the map */
	setupMap : function() {

		// get the content
		var infoWindowContent = WPmap.mapContainer.getAttribute('data-map-infowindow');
		var initialZoom       = WPmap.mapContainer.getAttribute('data-map-zoom');

		WPmap.map = new google.maps.Map(WPmap.mapContainer, {
			zoom:parseInt(initialZoom), // ensure it comes through as an Integer
			center:WPmap.destination,
			mapTypeId:google.maps.MapTypeId.ROADMAP
		});

		marker = new google.maps.Marker({
			map:WPmap.map,
			position:WPmap.destination,
			draggable:false
		});

		// set the infowindow content
		infoWindow = new google.maps.InfoWindow({
			content:infoWindowContent
		});
		infoWindow.open(WPmap.map, marker);

	},

getDirections()

This is called whenever directions are requested. Its only argument, 'request', will help us determine whether the user clicked the button to use a manually entered address or the 'current location' one.

	getDirections:function (request) {

		// Get the postcode that was entered
		var fromStr = WPmap.fromInput.value;

		var dirRequest = {
			origin      : fromStr,
			destination : WPmap.destination,
			travelMode  : google.maps.DirectionsTravelMode.DRIVING,
			unitSystem  : WPmap.getSelectedUnitSystem()
		};

		// check if user clicked 'use current location'
		if (request == 'geo') {
			var geoLatLng = new google.maps.LatLng( WPmap.geoLat , WPmap.geoLon );
			dirRequest.origin = geoLatLng;
		}

		WPmap.dirService.route(dirRequest, WPmap.showDirections);
	},

showDirections()

Unchanged from the original – it handles the insertion of the directions into the page.

	/**
	 * Output the Directions into the page.
	 */
	showDirections:function (dirResult, dirStatus) {
		if (dirStatus != google.maps.DirectionsStatus.OK) {
			switch (dirStatus) {
				case "ZERO_RESULTS" :
					alert ('Sorry, we can\'t provide directions to that address (you maybe too far away, are you in the same country as us?) Please try again.');
					break;
				case "NOT_FOUND" :
					alert('Sorry we didn\'t understand the address you entered - Please try again.');
					break;
				default :
					alert('Sorry, there was a problem generating the directions. Please try again.')
			}
			return;
		}
		// Show directions
		WPmap.dirRenderer.setMap(WPmap.map);
		WPmap.dirRenderer.setPanel(WPmap.dirContainer);
		WPmap.dirRenderer.setDirections(dirResult);
	},

Finishing off the JavaScript

Outside of the object, there's just the event listener to add that will load the map when the page is ready and the helper function we talked about earlier.

	/* Load the map when the page is ready */
	google.maps.event.addDomListener(window, 'load', WPmap.init);

	/* Function to easily remove any class from an element. */
	HTMLElement.prototype.removeClass = function(remove) {
		var newClassName = "";
		var i;
		var classes = this.className.split(" ");
		for(i = 0; i < classes.length; i++) {
			if(classes[i] !== remove) {
				newClassName += classes[i] + " ";
			}
		}
		this.className = newClassName;
	}

And Finally...

Now to get everything working you just need to put the map folder into your theme and then run through the things we covered in the first tutorial.

  1. Include map.php in your theme's functions.php

    			/** In functions.php **/
    			include('map/map.php');
  2. Enter your destination, infowindow text and zoom level into the fields that we created in Settings. They can be found under Settings -> General -> Map Configuration
  3. Then, on any page or post, enter the three shortcodes
    1. [wpmap_map]
    2. [wpmap_directions_input]
    3. [wpmap_directions_container]

Conclusion

As I've mentioned this is an update to this tutorial and therefore you really need to review both of them to fully understand the entire process. Possibly the easiest way to understand how it all fits together would be to view the source files provided though.

After all of this you should have a pretty neat little map application that will respond to different screen sizes and also add extra functionality to users with modern browsers. All the while providing a good fallback for everyone else.

Advertisement