Advertisement

Turn-By-Turn Directions with the Google Maps API

by

In this tutorial, we will go through the process of creating a small widget that allows users to retrieve turn-by-turn directions to a specified location. We'll be using the Google Maps API, via JavaScript, to provide this rather advanced functionality.

Final Product

Getting An API Key

The only caveat with using Google Maps is we must apply for an API key, but this is a fairly trivial process if you already have a Google/GMail account. Unfortunately due to the requirements of Google we must develop on the domain that we provide Google, i.e. we can not develop on a local server. Fortunately for us the process will be quick and we won't spend a lot of time on the live server. Also, be sure to store you API key in a safe place because I could not find a way to retrieve them once generated, though I guess you could just recreate one.

The State of Affairs

Before we dive into code let me discuss the reason behind this idea. As most developers I spend a lot of time on the web. One particular subset of websites I visit are local businesses who certainly don't have great resources to devote to web design, but hopefully the people that are developing those sites will see articles like this and realize how easy it is to include a full-featured map into any webpage. Almost any website representing a small business has a page dedicated to telling users how to locate their physical location. Often times you get a map with their location pinned on it, which doesn't help users who don't know the area. In this tutorial we are going to change that and let users enter their address and get turn-by-turn directions to any address we want.

Including the Google Maps Javascript Library

Now that the soapbox is out of way lets look into code. The first thing we need to do is include the Javascript library that contains all the Google Maps methods. Google probably generated this code when you created your API key, but that might have pointed to the version 3 API which is still in beta testing. Here is the link to the API version 2 be sure to insert your API key. We are also going to include a file, application.js that will hold our custom functions, I stored mine in a directory at the root level called js. The following code goes within the head section of your page.

<script src="http://maps.google.com/?file=api&v=2&key=INSERT_API_KEY_HERE" type="text/javascript"></script>
<script src="./js/application.js" type="text/javascript"></script>

The HTML Code

In the body section of our page we need some limited markup. I will briefly go over the required bits, and you can look at the source code to see the fluff I included in my demo. The first element is an empty div with an ID of map_canvas, this is the placeholder that we point the Google Maps calls to and it will generate all the map markup within that element.

<div id="map_canvas"></div>

Next I created a div to hold the organization address and the form for the user to enter their address. You can look over this code but it is pretty simple and not very hard to discern it's meeting. Be sure to look at my CSS to see how it is styled in my demo.

<div id="addresses">
    <div class="address-panel">
        <h2>Our Address</h2>
        <address>
          1450 Jayhawk Blvd #223<br />
          Lawrence, KS<br />
          66045
        </address>
    </div>

    <div class="address-panel">
        <h2>Your Address</h2>

        <form action="./index.php" onsubmit="overlayDirections();return false;" method="post">
            <div>
              <label for="street">Street Address</label>
              <input id="street" name="street_address" type="text" />
            </div>
            <div>
              <div class="address-form-column">
                <label for="city">City</label>
                <input id="city" name="city" type="text" />
              </div>

              <div class="address-form-column">
                <label for="state">State</label>
                <select id="state" name="state">
                  <option value="AL">Alabama</option>
                  <option value="AK">Alaska</option>
                  <option value="AZ">Arizona</option>
                  <option value="AR">Arkansas</option>
                  <option value="CA">California</option>
                  <option value="CO">Colorado</option>
                  ...
                </select>
              </div>

              <div class="address-form-column">
                <label for="zip">Zip Code</label>
                <input id="zip" name="zip_code" type="text" maxlength="5" size="5" />
              </div>
            </div>

            <div class="button">
              <input name="submit" type="submit" value="Get Directions" />
            </div>
        </form>
    </div>
</div>

Notice that we are submitting this page to itself this is so we can process the page using PHP if the user has JS disabled. If they have JS enabled we want to execute a function, overlayDirections() which we will take a look at a little later. The bulk of this code goes to the select box that lets the user pick their state, I've condensed it for the sake of those printing this article, but you can grab the full code from the download. Other interesting notes is we set the size and maxlength of the zip code text field to 5. The final thing to take note is that we have assigned ids and names to all of the form elements.

Bootstrapping and Declaring Variables

Alright now we can move into the meat of this tutorial, the JavaScript code. Nearly all the calls we are going to make come from the Google Maps API that we referenced earlier. Google provides excellent documentation and example code on their website so be sure to check it out. I'll try to link to relevant pages as I use them.

The first thing is while we have our HTML page open lets bootstrap the initialize function by setting onload attribute. Note: this could be done in jQuery using the $(document).ready() function.

<body onload="initialize()">

Now we are going to move in to the js/appication.js file. The very first thing we need to do is set some variables. Some code evangelist will probably hunt me down for declaring global variables, but I believe in this case we should be okay. I'll give you the code and then explain how we will use each one.

var gdir, fromAddress, toAddress;
  • gdir: holds the GDirections object used to obtain driving directions results and display them on a map and/or a text panel.
  • fromAddress: a string that holds the full address of the user.
  • toAddress: a string that holds the business/organization address

The initialize() Function

The initialize() function we called earlier will be used to create the Map on the page and place a custom marker of our location.

/*
**
* Bootstrap function to setup map and apply
* custom company marker
*/
function initialize() {
  if (GBrowserIsCompatible()) {
    //settings
    var companyMarkerImage= "./images/jayhawk.gif";
    var companyLatLng     = new GLatLng(38.957101, -95.251469);
    var companyMarkerSize = new GSize(55, 52); //width, height
    
    toAddress = "1450 Jayhawk Blvd #223 Lawrence, KS 66045";

    var defaultZoomLevel  = 13;
    //end settings

    //setup elements
    map   = new GMap2(document.getElementById("map_canvas"));
    gdir  = new GDirections(map, document.getElementById("directions"));

    //error handler
    GEvent.addListener(gdir, "error", handleErrors);

    //set company marker
    var companyMarker = createMarker(companyLatLng, companyMarkerImage, companyMarkerSize);

    //set map center
    map.setCenter(companyLatLng, defaultZoomLevel);
    map.addOverlay(companyMarker);
  }
}

The first thing we need to do is check if the browser is compatible with Google Maps, and for this Google provides the GBrowserIsCompatible() in their API. In essence it returns true if the browser is compatible and allows us to move into the rest of our function. I decided to abstract some of the values to variables at the top of the function so that this could easily be ported to many applications.

  //settings
  var companyMarkerImage= "./images/jayhawk.gif";
  var companyLatLng     = new GLatLng(38.957101, -95.251469);
  var companyMarkerSize = new GSize(55, 52); //width, height
  
  toAddress = "1450 Jayhawk Blvd #223 Lawrence, KS 66045";

  var defaultZoomLevel  = 13;
  //end settings

The companyMarkerImage is a string of the location of a small image we will place at our location on the map. This is something I think is a nice touch to have a custom icon to represent your business which will personalize the generic Google Map view. Next, companyLatLng holds a GLatLng object corresponding to a latitude, longitude point in the world. Don't run out and buy a GPS device to get these numbers we can use maps.google.com . In the search box type your address and when it finds the location click the Link button on the top right of the map. Scroll through the first text box in the modal window and find &sll=....

You can copy and paste those coordinates into the parameters of our GLatLng constructor. This is the point on the map where we will place our custom image. The next variable, companyMarkerSize, holds a GSize object which represents the width and height of your custom marker image. Next we set toAddress which is the address of the business. The final variable, defaultZoomLevel, just tells the map what you want the default zoom level to be ranging from 1 to 18.

  //setup elements
  map   = new GMap2(document.getElementById("map_canvas"));
  gdir  = new GDirections(map, document.getElementById("directions"));

The next line of code creates a GMap2 object. Google describes this as "the central class in the API." This loads the map data and allows us to manipulate what is shown in the map area. It takes one argument a DOM object pointing to the element containing the map, #map_canvas. Next we set gdir to hold the GDirections object. This is the interface we use to query Google Maps for directions. The constructor takes two arguments a map object and a DOM object where we want to put the turn-by-turn directions. I choose to create an empty div below #addresses called #directions.

  //error handler
  GEvent.addListener(gdir, "error", handleErrors);

When using web services we always run the risk of getting an error returned. We can make this as pain free as possible using the GEvent class. In this bit of code we are saying that if we have an error getting the directions to execute a custom callback function, handleErrors in our case. We directly call the addListener() function which registers a callback. It takes 3 arguments a source object, a string referring to the type of event we want to execute the callback on, and a handler which points to a function we want executed. The function, handleErrors, is something we will look at later.

  //set company marker
  var companyMarker = createMarker(companyLatLng, companyMarkerImage, companyMarkerSize);
  
  //set map center
  map.setCenter(companyLatLng, defaultZoomLevel);
  map.addOverlay(companyMarker);

The last few lines in initialize() are used to create our custom marker, I chose a Jayhawk found on KU's homepage. createMarker is a wrapper function I wrote to abstract the code required to create a custom marker. It takes three arguments: a reference to a GLatLng object where we want to place the image on the, a string respresenting the path to an image, and a reference to a GSize object that represents the size of the image. Next we use the setCenter() method of the GMap2 class which takes two arguments a GLatLng object of the coordinates to center on, and an integer for the zoom level. Notice we are passing in the variables we set in the settings block at the top of the initialize() function. The final line of code uses the addOverlay() method. This is what actually adds the custom image to the map.

The initialize() function does a lot of heavy lifting, but it certainly can show for it. After we write the createMarker() function next you will be able to load up the application and see some progress. But first lets recap the initialize() function.

The createMarker() Function

Next we will create a wrapper function that takes all the pain out of creating a marker with a custom image. The reason I choose to abstract this is because it is an involved process and would clutter up our initialize() function even more. Another added benefit is that we can add multiple markers very quickly without repeating a lot of code.

/*
**
* Wrapper function to create/return a marker object
* with custom image
*/
function createMarker(latlng, imageURL, imageSize)
{

    var marker      = new GIcon(G_DEFAULT_ICON, imageURL);
    marker.iconSize = imageSize;

    return new GMarker(latlng, { icon: marker });

}

Considerably smaller than our first function, but just as important. First we declare a new variable, marker, and store a GIcon object. It can take two arguments copy which is a GIcon object that it will copy properties from, and image which is a string representing a path to a custom image. G_DEFAULT_ICON is a constant that represents a default marker, and the imageURL comes from the settings block in initialize(). We only have to set one more property, iconSize which is of type GSize, this represents the size of our custom image and also comes from the settings block. The final line of code returns a GMarker object which takes two arguments latlng, and icon. The first, latlng is a reference to the GLatLng object we declared in the settings block. The next argument is for the GIcon object we just created. That is all we need to do for the map portion of our application to work. You can now load up the page and see how easy it is to get a nice map on our website.

Adding Directions

This is by far my favorite part about this tutorial, allowing users to enter an address and receive back a map with the route highlighted and turn-by-turn directions. Through the use of this API we can condense something that would require thousands of lines of code and an incredible amount of processing resources to just a handful of code.

/*
**
* Looks up the directions, overlays route on map,
* and prints turn-by-turn to #directions.
*/

function overlayDirections()
{
    fromAddress =
      document.getElementById("street").value
      + " " + document.getElementById("city").value
      + " " + document.getElementById("state").options[document.getElementById("state").selectedIndex].value
      + " " + document.getElementById("zip").value;

    gdir.load("from: " + fromAddress + " to: " + toAddress);
}

The first line I have actually extended into five lines for clarity. In essence this grabs all the values from the form and puts a space between each part. I thought this was better than asking the user to enter the whole address into a single text box because that can become confusing.

The second line makes use of the gdir we set in initialize(). We call the load() method and pass a single string argument, which is essentially what we would pass maps.google.com via the search box. The from: and to: keywords help tell Google which address needs to be the starting point and which needs to be the ending point. That is all we need to do for directions, yeah I was shocked too! If you visit your page again you can see this in action.

Handling Errors

Next we are going to declare the handleErrors() function. I grabbed this from Google Sample code on their API website. I won't go into any detail because it fairly straightforward.

  function handleErrors(){
     if (gdir.getStatus().code == G_GEO_UNKNOWN_ADDRESS)
       alert("No corresponding geographic location could be found for one of the specified addresses. This may be due to the fact that the address is relatively new, or it may be incorrect.\nError code: " + gdir.getStatus().code);
     else if (gdir.getStatus().code == G_GEO_SERVER_ERROR)
       alert("A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.\n Error code: " + gdir.getStatus().code);
     else if (gdir.getStatus().code == G_GEO_MISSING_QUERY)
       alert("The HTTP q parameter was either missing or had no value. For geocoder requests, this means that an empty address was specified as input. For directions requests, this means that no query was specified in the input.\n Error code: " + gdir.getStatus().code);
     else if (gdir.getStatus().code == G_GEO_BAD_KEY)
       alert("The given key is either invalid or does not match the domain for which it was given. \n Error code: " + gdir.getStatus().code);
     else if (gdir.getStatus().code == G_GEO_BAD_REQUEST)
       alert("A directions request could not be successfully parsed.\n Error code: " + gdir.getStatus().code);
     else alert("An unknown error occurred.");
  }

It has a long if...elseif...else statement that checks for many error types and alerts the user if any occur. You can modify this if you wish to make the alert less technical.

Degradable

As good web developers we should make sure our website works for as many users as possible, including those with JavaScript disabled. In this situation I chose to redirect those with JS disabled to Google Maps with the search performed so they still get directions. This is done using PHP to evaluate the form and redirect to Google Maps. At the top of your HTML page insert this code:

<?php
  //settings

    $TO    = "1450 Jayhawk Blvd #223 Lawrence, KS 66045"; //company address

  //end settings

  //they seem to have JS disabled, let's redirect them to
  //Google Maps and prefill the query
  if($_POST['submit']) {
    $FROM  = $_POST['street'] . " " . $_POST['city'] . ", " . $_POST['state'] . " " . $_POST['zip'];
    $LOC   = $_POST['language'];

    $url   = "http://maps.google.com/maps?hl=".urlencode($LOC)."&q=from:".urlencode($FROM)."+to:".urlencode($TO)."&ie=UTF8";

    header("Location: " . $url);
  }
?>
...

First we have a settings block again which only has one variable to set, $TO. This is similar to what we did in JavaScript for toAddress, but we need the same string in PHP too. Next we have an if statement to check for POSTed data which means our form was submitted. Now we grab the form values and place them in a string with spaces and store that in a variable, $FROM. Then we store the language value to $LOC, more on this later. The $url variable will hold the string representing the query URL to Google. Notice that we url-encode our values so they safely travel on the redirect. The final line of code uses PHP headers to redirect the user to Google.

Optional: Add Multi-Language Support

As a business you want to reach out to as many people as possible and part of that process is supporting multiple languages. In Google Maps supporting other languages comes at no extra cost to us.

First open up your HTML page and insert the following code between your form tags.

...
<select id="language" name="language">
  <option value="en" selected>English</option>
  <option value="fr">French</option>                  
  <option value="de">German</option>
  <option value="ja">Japanese</option>
  <option value="es">Spanish</option>
</select>
...

Of course if you want to remove any languages just delete the option tag for it, you can also change the default by moving the selected attribute.

Moving to js/application.js, we need to make just two changes. Starting in the overlayDirections() function after we create the string fromAddress add this to grab the selected value from the language select box and save it to our language variable.

...
var language  = document.getElementById("language").options[document.getElementById("language").selectedIndex].value;
...

Next, add an argument to the gdir.load() function, this takes an options set. In our case we only need to declare locale so it knows the proper language and units for the turn-by-turn directions.

...
gdir.load("from: " + fromAddress + " to: " + toAddress, { "locale": language });
...

Note: We already included this in the PHP redirect and if you want to disable this just statically set $LOC.

...
$LOC = 'en'
...

Conclusion

That is all we need for this amazing feature, and I hope you learned a bit about Google Maps along the way. I challenge you as developers to continue to find interesting ways to integrate maps in to your applications. Anytime a model is location aware, you should question if your project has a use for visual representation on a map. Thanks for reading; as always, I am here to help in the comments or on Twitter (@noahhendrix).


Advertisement