Building the map view for Adobe Groups

This post is more than 2 years old.

Earlier this month I pushed up a new feature to the Adobe Groups site - a map of all the user groups. (And by all I mean those who entered geographic information.) This feature makes use of the Google Map API and the new CFMAP tag in ColdFusion 9. I thought folks might want a little explanation of the code behind this feature. I don't put this out as the most efficient maps demo or the coolest, but hopefully it will help others.

To begin, let me talk a bit about how I handled the data portion. I knew that I would need longitude and latitude information for user group addresses. While ColdFusion's cfmap supports creating markers on addresses, if you ask Google to geocode too many of them you will get an error. (See this blog post for more information.) For my solution I added three properties to my Group entity:

property name="address" ormtype="string"; property name="longitude" ormtype="string"; property name="latitude" ormtype="string";

I then exposed a free form text field on the user group's setting pages to allow user group managers to enter any address they wanted. They could use something vague, like a zip code, or enter a full address with a specific street location. I simply suggested that UGMs first try their address on the main Google Maps site to see if they liked the result.

Once a manager entered their address, I needed to convert this address into a longitude and latitude value. Luckily Google provides a nice API for this and you can find a ColdFusion wrapper for it on RIAForge. I made the call that even with Google providing (normally) pretty swift APIs, I'd do the geolocation in a background process. So now whenever a UGM makes a change to their address, I clear any existing longitude and latitude. I then created a simple scheduled task to handle geolocation.

<cfquery name="getaddresses"> select name, address, id from `group` where address is not null and address != '' and (longitude = '' or latitude = '' or longitude is null or latitude is null) </cfquery>

<cfoutput>#getaddresses.recordCount# address to geocode.<p></cfoutput>

<cfloop query="getaddresses">

&lt;cfset geocode = new googlegeocoder3()&gt;
&lt;cfset geo = geocode.googlegeocoder3(address=address)&gt;
&lt;cfif geo.status is "OK"&gt;
	update	`group`
	set	longitude = &lt;cfqueryparam cfsqltype="cf_sql_varchar" value="#geo.longitude#"&gt;,
		latitude = &lt;cfqueryparam cfsqltype="cf_sql_varchar" value="#geo.latitude#"&gt;
	where id = &lt;cfqueryparam cfsqltype="cf_sql_integer" value="#id#"&gt;
	&lt;cfoutput&gt;Set geo info for #name# at #address#&lt;br/&gt;&lt;/cfoutput&gt;
	&lt;!--- possibly set a flag to not try again ---&gt;
	&lt;cfoutput&gt;&lt;font color='red'&gt;Bad geo info for #name# at #address#&lt;/font&gt;&lt;br/&gt;&lt;/cfoutput&gt;



I don't think I need to go over the code above - as you can see it's rather simple. I also did not make use of HQL for the queries. I certainly could have but for the first draft, this worked fine. The end result is that every 30 minutes, I look for groups that need geocoding and try to do that operation.

Ok - so now I should be done. I can just get a list of groups that have longitude and latitude information and dump them into a cfmap tag, right? Well that's what I thought at first. I then discovered that some user groups dared to share a room with other groups in the area. When Google is asked to put two or more markers in the exact same spot, only the last one will render. Barnacles. I decided to try something else. I created a structure keyed by a location. Each group will place their marker HTML into a value specified by the key. Now if two groups meet in one place, both of their sets of information will be placed on the one marker. Here is a portion of the code from the view. Assume longlatdata is an array of ORM entities for groups where they all contain a geocode information.

<cfset longlat = {}> <cfloop index="g" array="#longlatdata#"> <cfset lat = g.getLatitude()> <cfset lon = g.getLongitude()> <cfset key = lat & " " & lon> <cfif not structKeyExists(longlat, key)> <cfset longlat[key] = ""> </cfif> <cfset str = "<img src=""#g.getAvatar()#"" align=""left"" width=""75"" height=""75"" style=""margin-right:5px""> <a href=""/group/#g.getId()#"">" & g.getName() & "</a><br/>" & g.getAddress() & "<br clear=""left""/><br/>"> <cfset longlat[key] &= str> </cfloop>

<cfmap centeraddress="St. Louis, MO, USA" name="ugmMap" showcentermarker="false" zoomlevel="4" width="900" height="500" type="hybrid"> <cfloop item="loc" collection="#longlat#"> <cfset lat = listFirst(loc, " ")> <cfset lon = listLast(loc, " ")> <cfmapitem latitude="#lat#" longitude="#lon#" markerwindowcontent="#longlat[loc]#"> </cfloop> </cfmap>

Again - I assume this is all pretty simple but let me know if anything needs further explanation. The choice of St. Louis to center the map was totally arbitrary. So, any comments? Right now I'm a bit worried about handling more and more groups with addresses. Luckily there is an open source MarkerManager to handle large sets of markers. I plan on looking at that next.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA

Archived Comments

Comment 1 by Tom Kelleher posted on 6/23/2010 at 8:00 AM

I would suggest setting the initial zoom level to 5, so that Asia doesn't display twice:)

Comment 2 by Dave Isenhower posted on 6/23/2010 at 8:01 AM

"Barnacles!" -- that's a sure sign that Spongebob is probably getting more DVD playtime in the Camden household than Star Wars :)

Anyway, thanks for the post! It's given me some great ideas for enhancing our partner-search tool.

Comment 3 by Michael posted on 6/23/2010 at 10:44 AM

An example of your MarkerManager when done would be great.

Comment 4 by Raymond Camden posted on 6/23/2010 at 3:17 PM

@Tom - yeah - the zoom was a bit hard to get right.

@Dave - Absolutely. :)

@Michael - Will do.

Comment 5 by Josh posted on 6/23/2010 at 9:51 PM

A font tag? Really?

Comment 6 by Raymond Camden posted on 6/23/2010 at 9:52 PM

@Josh: It's debug output for a scheduled task. So yes, really. ;)

Comment 7 by Gary Funk posted on 6/24/2010 at 6:54 PM

<cfoutput><span style='color:##FF0000'>Bad geo info for #name# at #address#</span><br/></cfoutput>

Comment 8 by Raymond Camden posted on 6/24/2010 at 8:08 PM

Um, yes? Thats part of the scheduled task. No human sees it.

Comment 9 by Gary Funk posted on 6/25/2010 at 4:37 AM

Then why color it red?

Comment 10 by Raymond Camden posted on 6/25/2010 at 5:36 AM

Heh I did view it myself. Seriously though - this is what y'all focus on??? ;)

Comment 11 by Gary Funk posted on 6/25/2010 at 6:19 AM

It's either this or the oil. This is more fun and educational.

Comment 12 by Gary Funk posted on 6/25/2010 at 6:19 AM

It's either this or the oil. This is more fun and educational.

Comment 13 by Jason posted on 6/25/2010 at 11:01 PM

While CFMAP is cool...How about a ColdFusion Powered ESRI Flex api map? Now thats some really cool stuff...

Comment 14 by Raymond Camden posted on 6/28/2010 at 4:58 PM

Technically, this is very ColdFusion powered. ;) I've looked at Flex and Google Maps before, but not ESRI. Got a URL for that?

Comment 15 by Jason posted on 6/28/2010 at 5:02 PM

Here is the API:

Tons of samples on there. Way more robust capabilities - real GIS stuff here. I use a CF backend and flex frontend for all my stuff these days.

Comment 16 by Raymond Camden posted on 6/28/2010 at 5:04 PM

If I read right, not free for commercial use (which Adobe Groups is):


Comment 17 by Jason posted on 6/28/2010 at 5:11 PM

Yup, I suppose you are correct. You should still check it stuff.

Comment 18 by Raymond Camden posted on 6/28/2010 at 5:11 PM

Will try to. Thanks!

Comment 19 by Green SEO posted on 1/10/2011 at 12:20 PM


Great Explain,

I will definitely will try,