Integrating & Customizing Google Maps Using the Google Maps API

Integrating & Customizing Google Maps Using the Google Maps API

I. Introduction

In case you haven't noticed, developers have been putting Google Maps to some very cool uses. One of my favorites is HousingMaps.com which allows users of the CraigsList database to search and map rental housing by city and price range. FoundCity.Net is described as a social mapping tool for creating a personalized map of users' "life on-the-fly." (browse: Pizza category, click on push pins, then a user name to see what they have "tagged"). Some other examples of ingenious uses are Chicago Crime, which locates exactly where all police reported crimes take place, Florida Sexual Predator, shows where sex offenders live throughout metropolitan areas, SFSurvey, San Francisco Restaurants reviews by local people, Recent Earthquakes charts earthquakes that have taken place across the globe over the past seven days, MyConcertDates.com (Live US Concerts) and Seattle Bus Monster, which, besides showing bus routes with stops and the current location of the bus, also includes traffic conditions and webcams, flags accidents and sports events that effect traffic. These developers and others are able to integrate Google maps like this by making use of the Google Maps API

The Google Maps API lets web developers embed Google Maps in their own web pages instead of just linking to them. Using the API, a developer can add overlays to the map (including markers and polylines) and display shadowed "info windows" just like Google Maps. The Google Maps API is a free beta service, available for any web site as long as the maps are freely accessible to end users. Google does, however, retain the right to put advertising on the map at any time in the future. One other thing you will want to keep in mind is that Google plans to upgrade the API periodically, which means that you might have to update your site to use the new versions of the API. The Maps team have said they will post notification of these updates on Google Code and the Maps API discussion group and will try to give API users a month to make the transition, during which both versions of the API will be available.

II. Preliminaries

So, what do you need to do to get started integrating Google Maps in your own imaginative way? The first thing you need to do is sign up to get a Google Maps API Key. (It's free, but you must have a Google Account to get a Maps API key, and your API key will be connected to your Google Account) After you sign up and get your key, some sample code will be generated for a very basic map. Copy and paste this code into a blank .cfm template. Executing the script will return a simple Palo Alto area map in the vicinity of where Google Headquarters is located. Important Note: Your template MUST reside at the exact directory address you entered when you signed up for a key. If you use, for example, both domain.com and www.domain.com, you will need two keys, one for each address. (i.e. keys are specific to case, domain, subdomain, and directory). Hopefully Google will be taking care of this in future API versions.

III. The Basic Script

First lets take a look at the basic script:

<script src="http://maps.google.com/maps?file=api&v=1&key=YOUR_API_KEY_HERE" type="text/javascript"></script>
This part makes the Google map JavaScript script available for your use. *TIP: I learned while doing research it's a good idea to put this on the BOTTOM of the page before the body close tag but after the last div so any HTML is parsed before the javascript file is called. For this basic tutorial though, we'll just leave it as is.

<div id="map" style="width: 400px; height: 300px"></div>
This part sets map dimensions and creates a placeholder for the map. Wherever you place this snippet in the page, whether using tables or css, is where the map will be positioned.

var map = new GMap(document.getElementById("map"));
Creates a GMap Object

map.centerAndZoom(new GPoint(-122.1419, 37.4419), 3);
centers the map according to longitude and latitude coordinates. The second parameter (the 3) determines the visual height or zoom level. 1 displays the lowest (most detailed) view and 16 displays the highest (least detailed) view.

IV. Longitude & Latitude

Ok, so you're going to need precise longitude and latitude coordinates in order to build your maps. Where do you get them? According to Google Maps documentation, at this time the Google Maps API doesn't include any official geocoding service needed to convert addresses to latitude/longitude pairs. However, they do provide a Google Search Link to number of free geocoders available online.

Most of these "Free" geocoding services are free for non-commercial use. However, if yours is a commercial application that requires a large number of repeated lookups, geocoder.us offers a Web Service where you can purchase 20,000 lookups for $50. Or, if you are the Do-It-Yourself type, geocoder.us also provides information about the Perl module they use, Geo::Coder::US along with U.S. Census Bureau TIGER/Line free data. You might also want to take a look at "Geocoding With SQL Server"

V. Grabbing Longitude & Latitude From Google Maps Itself

If none of the options in the preceding section meet your needs, then there is a way you can (unofficially) grab longitude and latitude from google maps itself using cfhttp (I bet you were wondering when I was finally going to get the the cf part of this tutorial :D)

Lets say you have your API key and now want to integrate Google Maps into a member-type website that contains an already existing database of addresses of members. You have added your longitude and latitude fields (fld_lng, fld_lat) but need the pertinent data which you noticed is available whenever you look up an address on Google Maps. So you query your database and pull physical address, city and state. This information will need to be put into the form of one continuous string. For the moment we will just cfset the address of Google Headquarters for the following cfhttp example:

<cfset qmap="1600+Amphitheatre+Parkway+Mountain+View+CA">

Next we will use cfhttp to query Google Maps

<cfhttp method="get"
        url="http://maps.google.com/maps?q=#qmap#&output=js"
        resolveurl="no">
</cfhttp>

Notice &output=js - this is important. If you just use http://maps.google.com/maps?q=#qmap# no map info will be returned.

For now go ahead and cfoutput the cfhttp.filecontent, view source and take a look at what is returned. What you see should look something like this. Notice I highlighted a couple of nodes that will give us our latitude/longitude info, <center lat="37.422845" lng="-122.085035" /> and <point lat="37.422845" lng="-122.085035" /> Now all we need is a little regex

Since I am basically regex illiterate and all attempts at learning Regular Expressions have ended up like this, I head straight for CFLIB.org and pick up the midstring UDF which comes in very handy for isolating our longitude and latitude coordinates.

This might be a good place to stop and review code.

<!--- example1.cfm --->
<!--- the address we want to look up --->

<cfset qmap="1600+Amphitheatre+Parkway+Mountain+View+CA">
<!--- use cfhttp to get the info from google maps --->
<cfhttp method="get"
           url="http://maps.google.com/maps?q=#qmap#&output=js"
           resolveurl="no">
</cfhttp>

<!--- Bring in our UDF --->
<cfinclude template="MidString.cfm">

<!--- Ok lets parse for longitude and latitude --->
<!--- Latitude first --->

<cfset string= cfhttp.filecontent>
<cfset fromstr=
'<center lat="'>
<cfset tostr=
'" lng="'>
<cfset mylat = midstring(string,fromstr,tostr)>

<!--- Longitude next --->
<cfset fromstr='" lng="'>
<cfset tostr=
'"/>'>
<cfset mylng = midstring(string,fromstr,tostr)>

<!--- Basic Google Map API Script Starts Here --->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>
Google Maps Example 1</title>
<script src="http://maps.google.com/maps?file=api&v=1&key=YOUR_API_KEY_HERE" type="text/javascript"></script>
</head>
<body>
<div id=
"map" style="width: 500px; height: 400px"></div>
<script type="text/javascript">
       //<![CDATA[

       // create the instance of GMap

       var map = new GMap(document.getElementById("map"));
       map.addControl(new GSmallMapControl());
       // Output Longitude and Latitude to center and set initial starting point of the map
       map.centerAndZoom(new GPoint(<cfoutput>#mylng#, #mylat#</cfoutput>), 1);
       
       //]]>
</script>
</body>
</html>

The code above basically hits Google Maps twice, once to get the longitude and latitude and then once to get the map by connecting to the API. Depending on how busy Google's servers are, this can slow things down if you have to hit it twice every time a user pulls the data for this address. In reality you only need to get the latitude and longitude info from Google once. After you get it, insert it into the database where you will keep it on hand from then on. The code for this might look something like:

<!--- example2.cfm --->
<!--- query db for member data --->

<cfquery name="qry_memberinfo" datasource="#request.dsn#">
       select fld_memberID
              , fld_membername
              , fld_memberavatar
              , fld_address
              , fld_city
              , fld_state
              , fld_lng
              , fld_lat
       from tbl_members
       where fld_memberID = <cfqueryparam value="#url.id#" cfsqltype="cf_sql_integer">
</cfquery>

<!--- set some params for longitude and latitude (mylng & mylat) --->
<cfparam name="mylng" default="#qry_memberinfo.fld_lng#">
<cfparam name=
"mylat" default="#qry_memberinfo.fld_lat#">

<!---
test to see if the vars contain data & if no data, use cfhttp to get it from google maps
--->

<cfif len(trim(mylng)) IS 0 OR len(trim(mylat)) IS 0>

       <!--- the address we want to look up --->
       <cfset qmap="#qry_memberinfo.fld_address#+#qry_memberinfo.fld_city#+#qry_memberinfo.fld_state#">
       <!--- let's get rid of any spaces first --->
       <cfset qmap = replace(qmap, "chr(32)", "+", "ALL")>

       <cfhttp method="get"
              url="http://maps.google.com/maps?q=#qmap#&output=js"
              resolveurl="no">
       </cfhttp>

       <!--- Bring in our UDF --->
       <cfinclude template="MidString.cfm">

       <!--- Ok lets parse for longitude and latitude --->
       <!--- Latitude first --->
       <cfset string="#cfhttp.filecontent#">
       <cfset fromstr=
'<center lat="'>
       <cfset tostr=
'" lng="'>
       <!--- Reset mylat --->
       <cfset mylat = midstring(string,fromstr,tostr)>
       <!--- Longitude next --->
       <cfset fromstr='" lng="'>
       <cfset tostr=
'"/>'>
       <!--- Reset mylng --->
       <cfset mylng = midstring(string,fromstr,tostr)>

       <!--- Now lets update db, adding the longitude and latitude to this member record --->
       <cfquery name="qry_updatecords" datasource="#request.dsn#">
              update tbl_members
              set fld_lat = '#mylat#'
                 , fld_lng = '#mylng#'
              where fld_memberID = <cfqueryparam value="#qry_memberinfo.memberID#" cfsqltype="cf_sql_integer">
       </cfquery>

       <!--- Done! coordinates captured! --->
</cfif>

<!--- Basic Google Map API Script Starts Here --->

VI. Enhancing the Basic Map

If you tested your code at this point you might be thinking, Well, now we have our basic map, but there is nothing much on it except some little boxes with arrows. It doesn't even have one of those little orange markers pinpointing our address, much less any of the other neat stuff we saw on all those sites we viewed in the introduction.

Adding Controls
This is where the fun part starts; let's start customizing our map. Take a look at the following line in our basic code: map.addControl(new GSmallMapControl()); This line adds zoom & repositioning controls to the top left corner of the map without including the zoom scalebar (view chart) Let's change that to the large pan/zoom control that includes the zoom scalebar and also add the control that lets you toggle between Google map and satellite views

<script type="text/javascript">
       //<![CDATA[

       // create the instance of GMap
       var map = new GMap(document.getElementById("map"));
       // add large Map Control
       map.addControl(new GLargeMapControl());
       // add map/satellite toggle
       map.addControl(new GMapTypeControl());
       // set initial starting point of the map
       map.centerAndZoom(new GPoint(<cfoutput>#mylng#, #mylat#</cfoutput>), 3);

       //]]>
</script>

Adding a Marker
Next we'll create an overlay of Google's default marker pointing to our location by creating a new GPoint() object AFTER centerAndZoom (or the map will be malformed). So...

map.centerAndZoom(new GPoint(<cfoutput>#mylng#, #mylat#</cfoutput>), 3);
// create the GPoint object
var point = new GPoint(<cfoutput>#mylng#, #mylat#</cfoutput>);
// create the marker
var marker = new GMarker(point);
// Add the marker as an overlay
map.addOverlay(marker);

Information Windows
An information window is basically a bubble containing some kind of content. If you want to add an information window pointing to center coordinates with a simple "Hello world" message, (INSTEAD of a marker) you would do it this way:

map.centerAndZoom(new GPoint(<cfoutput>#mylng#, #mylat#</cfoutput>), 3);
map.openInfoWindow(map.getCenterLatLng(), document.createTextNode("Hello world"));

Usually, however, information windows are displayed when a user clicks on the corresponding marker. Lets do this and while we are at it, we will add info to the information window from our database query including an image and a clickable link:

map.centerAndZoom(new GPoint(<cfoutput>#mylng#, #mylat#</cfoutput>), 3);
// create the GPoint object
var point = new GPoint(<cfoutput>#mylng#, #mylat#</cfoutput>);
// create the marker
var marker = new GMarker(point);
//show this output in the infowindow when the pointer is clicked
<cfoutput query="qry_memberinfo">
var myhtml = "<img src='images/#fld_memberavatar#' align='left'><b>#fld_membername#</b><br>#fld_address#<br>#fld_city#, #fld_state#<br><a href='mypage.cfm'>Click Me!</a>";
</cfoutput>
// Add the marker as an overlay
map.addOverlay(marker);
GEvent.addListener(marker, 'click', function() {
marker.openInfoWindowHtml(myhtml);
});

Multiple Markers
Suppose you want to create a map displaying clickable markers for several member locations, each with it's own info window. How would you do that? Well, you would have to decide how you are going to determine your map.centerAndZoom coordinates for centering the map and also for selecting addresses from your database. Let's say that for now we will just use a zip code, #myzipvar#.

<!--- example7.cfm --->
<!--- set the zip code area for default map centering & use cfhttp to get latitude and longitude from Google Maps --->

<cfhttp method="get"
       url=
"http://maps.google.com/maps?q=#myzipvar#&output=js"
       resolveurl=
"no">
</cfhttp>

<!--- Bring in our UDF --->
<cfinclude template="MidString.cfm">

<!--- Ok lets parse for longitude and latitude --->
<!--- Latitude first --->

<cfset string = cfhttp.filecontent>
<cfset fromstr=
'<center lat="'>
<cfset tostr=
'" lng="'>
<cfset mylat = midstring(string,fromstr,tostr)>

<!--- Longitude next --->
<cfset fromstr='" lng="'>
<cfset tostr=
'"/>'>
<cfset mylng = midstring(string,fromstr,tostr)>

<!--- Basic Google Map API Script Starts Here --->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>
Google Maps Example 3</title>
<script src="http://maps.google.com/maps?file=api&v=1&key=YOUR_API_KEY" type="text/javascript"></script>
</head>
<body>
<div id=
"map" style="width: 800px; height: 600px"></div>
<script type="text/javascript">
       //<![CDATA[

       var map = new GMap(document.getElementById("map"));
       map.addControl(new GLargeMapControl());
       map.addControl(new GMapTypeControl());
       // set the cords for centering map
       map.centerAndZoom(new GPoint(<cfoutput>#mylng#, #mylat#</cfoutput>), 4);
       
       <!--- now query db for member info in this zip code --->
       <cfquery name="qry_members" datasource="#request.dsn#">
       select fld_memberID
              , fld_membername
              , fld_memberavatar
              , fld_address
              , fld_city
              , fld_state
              , fld_lng
              , fld_lat
       from tbl_members
       where fld_zip = <cfqueryparam value="#myzipvar#" cfsqltype="cf_sql_varchar">
</cfquery>

<!--- now lets loop through the query --->

<cfoutput query="qry_members">
<!--- set longitude and latitude (mylng2 & mylat2) --->

<cfset mylng2 = "#fld_lng#">
<cfset mylat2 =
"#fld_lat#">

<!--- if no longitude or latitude data then we need to get it --->
<cfif len(trim(mylng2)) IS 0 OR len(trim(mylat2)) IS 0>
<!--- the address we want to look up --->
<cfset qmap=
"#fld_address#+#fld_city#+#fld_state#">
<!--- let's get rid of any spaces first --->

<cfset qmap = replace(qmap,
"chr(32)", "+", "ALL")>
<!--- use cfhttp to get the info from google maps --->

<cfhttp method="get"
       url=
"http://maps.google.com/maps?q=#qmap#&output=js"
       resolveurl=
"no">
</cfhttp>

<!--- Ok lets parse again for longitude and latitude --->
<!--- Latitude first --->

<cfset string= cfhttp.filecontent>
<cfset fromstr=
'<center lat="'>
<cfset tostr=
'" lng="'>
<cfset mylat2 = midstring(string,fromstr,tostr)>

<!--- Longitude next --->
<cfset fromstr='" lng="'>
<cfset tostr=
'"/>'>
<cfset mylng2 = midstring(string,fromstr,tostr)>

<!--- Now lets update db adding the longitude and latitude to this member record --->

<cfquery name="qry_updatecords" datasource="#request.dsn#">
       update tbl_members
       set fld_lat = '#mylat2#'
            , fld_lng = '#mylng2#'
       where fld_memberID = <cfqueryparam value="#fld_memberID#" cfsqltype="cf_sql_integer">
</cfquery>

<!--- done with grabbing missing cords --->
</cfif>

<!--- now lets create our clickable markers --->

// Marker for #fld_membername#
// create the GPoint object

var point#currentrow# = new GPoint(#mylng2#, #mylat2#);
// create the marker
var marker#currentrow# = new GMarker(point#currentrow#);
//show this content in the infowindow when the pointer is clicked

var myhtml#currentrow# = "<img src='images/#fld_memberavatar#' align='left'><b>#fld_membername#</b><br>#fld_address#<br>#fld_city#, #fld_state#<br><a href='mypage.cfm'>Click Me!</a>";
// Add the marker as an overlay
map.addOverlay(marker#currentrow#);
GEvent.addListener(marker#currentrow#, 'click', function() {
marker#currentrow#.openInfoWindowHtml(myhtml#currentrow#);
});
</cfoutput>
//]]>
</script>
</body>
</html>

NOTE: Someone who is proficient in javascript could probably cut down on redundant coding by creating one JavaScript function for returning each individual info window.

Custom Marker Icons
If you checked out any or all of the sites mentioned in the introduction, you may have noticed that some sites displayed markers different from the Google Map default marker. If you didn't notice, then check out Google Ride Finder which uses various colored "mini" markers as an example. If you want, you can show a different icon for each location or type of location. But going over just how to create and implement your own custom icons like this would be a separate tutorial in itself. And since this tutorial has grown more than a little long, I will close by providing some helpful links to head you in the right direction. In the Google Maps API documentation: Icons and Markers, Creating Icons, Using Icon Classes, & Icon Class Reference.

VII. Conclusion
Even though this tutorial turned out longer than average, it is actually only a very basic introduction to the Google Maps API. There is so much more you can do (Polylines to show routes, for example). Hopefully what I shared will inspire you to incorporate Google Maps in your own unique way and maybe later you might write your own Google Maps API tutorial that will help take us all to the next level :D

VIII. Resources

 



All ColdFusion Tutorials By Author: Megan Garrison