I've done quite a few blog entries on cfmap, ColdFusion's wrapper to the Google Map API. While cfmap makes using the API as easy as possible, there may be cases where you want to use the map API natively. For example, ColdFusion's use of the API is tied to version 2 of Google's API. The current version is 3. I decided to take a quick look at what's involved with working with Google's Map API by itself. Here are some examples and notes. Please note that there are multiple wrappers out there to make this process easier - including support for doing maps via a jQuery plugin. I'm intentionally avoiding these as I want a 100% pure solution.
To begin, you may want to take a look at what Google calls the Map API Family. This is a high level directory of all the Map APIs. This blog entry is concerned with the latest version of the JavaScript API. One thing I'll point out right away is that the latest version of the API does not require a developer key. That doesn't mean we get to use the service without limit (you'll see a good example of this later), but it does mean we skip signing up for a developer key. Google makes this easy - but also ties the key to a particular domain. In my cfmap examples I can't tell you how many times I got hit by a dev versus production move.
I began my research by reading the tutorial. It's a good entry, but a bit overly complex. I took their code and stripped it down a bit to make it even simpler. Here is my version of their first code block.
The critical parts of this template are:
All in all - not a lot of code, right? You can test this here. For a slightly more advanced example, this template simply adds a bunch more options. (You can find the full list of options here.)
In this template, I begin with free form address string for Lafayette, LA. I create a new geocoder object and fire off the geocode request. This is asynchronous so a callback function is used to handle the result. If the result was good, we have a location object that contains our longitude and latitude. This can then be passed to the Map initializer. That by itself is it - but I went ahead and took more code from Google's doc to create a simple marker object.
Ok - can we use some ColdFusion now? I wanted to create a simple demo based on database data. I began by writing the query and a simple table output.
I assume nothing here needs explanation but if so, leave a comment. Now let's look at a modified version - one that integrates with Google maps.
So what did I do? I began by adding a link to a JavaScript function called showMap. I passed in the address of the order data I am displaying. showMap is basically the same code as before except now my address value is passed in as an argument.
So - this is just scratching the surface of what's available, but as you can see it isn't too hard to use. I hope this helps - and if anyone has follow up questions, just leave a comment.
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#map_canvas { width: 450px; height: 450px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
function initialize() {
var latlng = new google.maps.LatLng(31, -92);
var myOptions = {
zoom: 8,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas"></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#map_canvas { width: 450px; height: 450px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
function initialize() {
var latlng = new google.maps.LatLng(31, -92);
var myOptions = {
zoom: 8,
center: latlng,
mapTypeId: google.maps.MapTypeId.HYBRID,
panControl:false,
zoomControl:false,
streetViewControl:false,
mapTypeControl:false
};
var map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas"></div>
</body>
</html>
You can run this demo here<. Ok - so far, I've all done is create a map at a certain longitude and latitude. (Keep watch - I'm going to typo the heck out of those words probably.) It's possible your data may contain such information already. It probably doesn't. Google does have a web service you can run to get geolocation information. (You can find a ColdFusion wrapper here.) I use this service at Adobe Groups to geolocate group data on a scheduled basis. But what if you want to geolocate on the fly? Luckily this is possible via JavaScript as well, but you are limited to 2500 requests per day. (See details here.) I'd probably suggest geocoding server side and doing it one time only as opposed to on the fly with every request. But if you do want to do it in JavaScript, here is an example.
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#map_canvas { width: 450px; height: 450px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
function initialize() {
var address = "Lafayette, LA";
geocoder = new google.maps.Geocoder();
geocoder.geocode( { 'address': address}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var myOptions = {
zoom: 8,
center: results[0].geometry.location,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location
});
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas"></div>
</body>
</html>
You can see this demo here.
<cfquery name="getorders" datasource="cfartgallery" maxrows="10">
select orderid, total, orderdate, address, city, state, postalcode
from orders
</cfquery>
<table border="1" width="100%">
<tr>
<th>Order ID</th>
<th>Total</th>
<th>Date</th>
<th>Shipped To</th>
</tr>
<cfoutput query="getorders">
<tr>
<td>#orderid#</td>
<td>#dollarformat(total)#</td>
<td>#dateFormat(orderdate)#</td>
<td>
#address#<br/>
#city#, #state# #postalcode#
</td>
</tr>
</cfoutput>
</table>
<cfquery name="getorders" datasource="cfartgallery" maxrows="10">
select orderid, total, orderdate, address, city, state, postalcode
from orders
</cfquery>
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#map_canvas { width: 450px; height: 450px; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
function showMap(address) {
geocoder = new google.maps.Geocoder();
geocoder.geocode( { 'address': address}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var myOptions = {
zoom: 8,
center: results[0].geometry.location,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);
var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location
});
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
}
</script>
</head>
<body>
<table border="1" width="500">
<tr>
<th width="60">Order #</th>
<th width="100">Total</th>
<th width="100">Date</th>
<th>Shipped To</th>
</tr>
<cfoutput query="getorders">
<tr>
<td>#orderid#</td>
<td>#dollarformat(total)#</td>
<td>#dateFormat(orderdate)#</td>
<td>
#address#<br/>
#city#, #state# #postalcode#<br/>
<cfset fulladdress = address & " " & city & ", " & state & " " & postalcode>
<a href="##" onclick="showMap('#jsStringFormat(fulladdress)#');return false">Show Map</a>
</td>
</tr>
</cfoutput>
</table>
<div id="map_canvas"></div>
</body>
</html>
You can see this demo here.
Archived Comments
Great stuff Ray. I love the v3 API, and wrote a custom tag a few months back that's on RIAForge (http://cfgmap.riaforge.org). The whole thing is heavily documented, but basically it allows one to put some config options as tag attributes, and the tag will create the initialization of the map. It takes an array of object locations to plot on the map. Any locations that are geocoded can have their lat/lng's passed back to the server via a callback option in the tag to update those location's records in the db. There's even an example script that show's how to use the custom map object for getting directions from input and links, recentering the map on specific locations, and more.
http://blog.cutterscrossing...
Great writeup. Have you done any work with google maps where there were too many points in an area and you showed larger icons until you zoom in? I haven't figured out how to implement something like that. That would be a blog post I would be very interested in. =)
@Steve: Very cool. Thats a good example of where custom tags still make a lot of sense.
@Ed: Um - not quite there yet. ;)
I had done some research on the too many markers issue some time ago but it looks like Google has recently documented some solutions for this:
http://code.google.com/apis...
In particular MarkerClusterer looks like what I was envisioning. We'll see how it pans out...
Very easy to follow..Ray. Just have one issue I cannot seem to resolve..
- I'm using your code on my Spry "Map Customer" Tab to display a single cust/address.
- I have everything working fine, except my query will not refresh & grab the current customer?
- Here is my query: <cfquery name="rsCAImap" datasource="salesPipeline">
SELECT wkRptID, wkRptAcqDate, wkRptCustName, wkRptBusAdd, wkRptCustCity, wkRptBusSt, wkRptBusZip
FROM tblWeeklyRpt
WHERE wkRptCd = 'CAI' AND tblWeeklyRpt.wkRptID = <cfqueryparam value="#URL.id_com#" cfsqltype="cf_sql_numeric">
ORDER BY wkRptCustName ASC
</cfquery>
-----------------------------------------
- Here is an example of my page URL: http://192.168.1.77/COE/salespipeline_wrd.cfm?wkRptID=1825
- Is there something I can change in your "Calling Code" below to make sure the Query "grabs" the URL parameter before running?
- Here is your "calling code": <cfoutput query="rsCAImap">
<tr>
<td bgcolor="##FFFFFF"> #wkRptBusAdd#<br/>
#wkRptCustCity#, #wkRptBusSt# #wkRptBusZip#<br/>
<cfset fulladdress = wkRptBusAdd & " " & wkRptCustCity & ", " & wkRptBusSt & " " & wkRptBusZip>
<a href="##" onclick="showMap('#jsStringFormat(fulladdress)#');return false">Show Map</a></td>
</tr>
</cfoutput>
--------------------------------------------
Thanks again for a cool, quick Google map implementation..
jaa
So you have 2 tabs then - tab one a list with links and tab 2 the map?
Ray, I figured it out.. just needed to reference my main form's query, didn't need the other query at all..
- To answer the question, Yes I have multiple SpryTabs, but only one main query, see image below:
- http://cerberus.clearwave.c...
By the way, I also tested your orders2.cfm Demo where it lists all customers down the page..
How does one alter that code to plot multiple addresses on a single map?
No cfmap in my CF7MX.. Thanks again.. ja
You need to use markers. A bit more work, but not terribly so.
Hi
This is perfect for what I was thinking of doing. Quick question is, how can I modify this to accept more than 1 location, e.g. if I wanted to show all accommodation for town?
Thanks
You see how my code adds markers one at a time? You would just... well more. :) Does that make sense?
Hi, Ray
Is there a way to use the API v3 to make a map black and white based on your code (above)?
I found some code I'm using and it works, but the code seems awfully 'verbose' (at least to me). I can provide the url if necessary.
Thanks!
According to the API docs there is a style system you can use:
http://code.google.com/apis...
It doesn't look terribly simple - but I believe this would be the way.
Of course - if you don't mind using the Static Map API (which I've discussed many times on the blog), you can use ColdFusion to download the map and make it BW pretty quickly.
Thank for the quick response!
I 'm using code very similar to the one that page, but I was looking for something a little simpler. Once again, thank you for your generosity. You're one of the true gems of the Coldfusion world!
Going to do a blog entry today or tomorrow on simple/dumb cfimage tricks with Google Static Maps. I will probably forget to post back here - so check the RSS feed. :)
Hi Ray, great post. Do you have any examples using the Google Map API with cfwindow or cfdiv? I thought that re-writing my functions in this format: functionName = function(arguments) {function body} would solve the issue. However, I still haven't had luck getting a google map to render in a cfdiv or cfwindow.
I'd probably just use cfmap if using cfdiv as well. In general, I don't mix CF's front end ajaxy stuff with "native" JS stuff very often. That being said - how is it failing for you?
Thanks for responding so quickly. I borrowed some of your example code to illustrate the problem I'm having:
http://pastebin.com/kzSQvxqW
It seems like the body onload function is not working within the cfwindow. I tried calling initialize() using ColdFusion.Window.onShow(). I am not really sure where the problem is.
You need to move your script tag that loads google maps into your parent document.
I also tried with coldfusion.navigate and cfdiv to load cfmap. Tried to put load google maps in parent, in self. Nothing happens. Perhaps cfmaps or google map api doesn't work with iframe (tested that as well with no success), or cfdiv?
Sorry for the unnecessary post, found it...
Thanks for the post. Have you had any luck or have any knowledge how one would save the google map generated by javascript api v3 to someone's My Maps account AND how to use cf to save as pdf without resorting back to static maps.
I am trying to plot map with lots of markers and google does not give a print button or save to my maps with the map that is returned so was hoping you had some sneaky CF workarounds!
Thanks
Matt
As far as I know there is no way to go to PDF from the JS based map. As to your first question, I'd check the Maps API. That sounds like something that should be possible.
@Matt -
Client side, you can programmatically add a button or link that has the "window.print();" javascript attached to it, allowing the user to easily print the output. Since the js api is client side, there really isn't a way to use cfpdf, or something else, to generate a pdf at the server. A user with Adobe Acrobat can manually change their print driver to create a pdf, but that's about the extent of it.
I experience a CFMAP mystery:
I have a page where there are 3 divs showing different parts of content. When you open the page div 1 is visible and the other 2 are hidden (style="display:none;").
You can click to open (toggle visibility) div 2 or 3.
In div 2 is a google map (cfmap script).
Now here's the mystery:
If Div 2 (with the cfmap) is NOT hidden initially, everything is fine
See: http://www.cyprustripadviso...
Here both div1 and 2 are initially visible.
If Div2 IS hidden initially and you click to toggle visibily of the divs the map has shifted left and up.
See: http://www.cyprustripadviso...
Here only div1 is initially visible. Click "LOCATION" to make visible.
Does anybody has a clue why this can happen? Or had a similar experience?
When I tried your site and clicked LOCATION, _nothing_ was visible. I mean below your top header.
I did a view source and saw this: Google Map License key is not defined.
Looks like your map key isn't rght.
Hi Ray,
I've found the cause of the problem. It has nothing to do with cfdiv/cfmap but it is a problem of google map in general. Google describe what to do on http://code.google.com/apis....
I've sent an email to you with the details on 21-7.
Thanks a lot for your help,
Grietje
Ah ok - I see it. Still a bit buried in email. :)
Ray,
I am still stuck in CF8 so no CFMAP for me, but I did use your examples and have expanded on them via other Google things I've found online.
One mystery though: how in the heck does one generate the tag that users can click to 'get directions'? Nothing I try seems to want to bring that little rectangle up with the get directions link.
Thanks for any light you can shed on this.
RLS
Hmm - not quite sure what you mean. Obviously there is a Directions API, and I've blogged on this, but you're asking about a particular UI element. Do you have an example I can look at?
When you go to maps.google.com and enter an address, they allow you to get the links. When you grab the HTML iFrame code and put it into your site and execute it, you get the white balloon box that has the "Directions" link in it (Example: http://home.tmrme.mobi/#/in....
This white rectangle box with the Directions, Search Nearby, and More links is what I was looking for.
While waiting, I have employed a pretty robust (and cool!) solution, and, yes, it took a lot of digging to figure it out. It uses geolocation and works great in Chrome and IE9, less well on Firefox, and sucks on IE8. But that's beside the point.
So, unless there is a "widget" to produce the white rectangle box, I think I have a workable solution in place now. Still, that would be so much easier and more compatible...
Google Maps has an API for creating those popups. So you would need to check their docs on how to add the popups. I think - stress think - I did a blog post demo showing it. If not though it's not that difficult.
Thanks - I looked all over for it at the time but to no avail. When I tested the early version of the site, I found a very limiting factor was that not all of the people who tested the site had geolocation capabilities or they chose to turn them off. That means the app is basically worthless.
So, I switched to generating the iFrame code the way Google does so at least most phones would be able to see a map. When I did it this way, that neat little directions link showed up just fine.
Not anywhere near as cool as what I had spent a good 12+ hours writing, but usability was primo as this is targeted for the lcd.
Thanks for your reply!
RLS
Oh - I take that back - I could generate the popups, but the "directions" link was not a widget you could easily insert. I had to write my own "Show Directions", which turned out quite awesome. Unfortunately, functionality beat awesome this time. RLS
You can test for Geolocation support. If you do that, you can then show/not show the Directions link.
Thanks - yes, I did know that, unfortunately my Java/JavaScript skills are so poor that I would have spent another 6 hours just figuring that out, and I needed to get this out the door. Fortunately, the solution I implemented is perfect for what is needed...this time.
Hi ray, My Situation is here bit different, I need the following items to be searched in the Google Map:
Address,City,State,Zipcode,County,SubDivision, Lat, Lon
I checked all CFC's of yours at riaforge and others but none is providing county and subdivision
All I can suggest is looking at Google's APIs and see if any support geocoding of county or subdivision. I'd be a bit surprised if subdivision was supported.
What if I need to show in a MAP the available spaces in a new Township? (I'm working for a Real Estate Company in this moment).
Can you use markers? Or are you trying to do shapes? Google Maps has a shapes API that lets you draw ad hoc shapes for stuff like this.
Hello Raymond
Thanks for nice explanation. I am wondering to know is it possible to specify two or more address here and find out the distance and travel time like we did with google maps ?
Yes, Google has a Directions API that will do this. I've blogged on it too.
Hello Raymond
I am unable to find your blog on Direction API will you please provide the url here
Thanks
Go to the search, type directions api. I saw them immediately.
Thanks Ray!
Worked like a charm the first try!
(can any one please guide why it is rendering this way? div height set properly)
https://uploads.disquscdn.c...
Can you share the complete code, even better if it's online where we can run it?