6 December 2010
Prior experience with ColdFusion and Flash Builder 4 will be helpful in completing the steps in this tutorial.
Intermediate
In this article, I will share what I’ve learned in the past few months about using ColdFusion as a source of real-time data and using Flex combined with Google Maps API for Flash as a means of visualizing that data. The live map below is an example of what you can build using this technique. (If you can’t see the live map, make sure you have Flash enabled). The map provides a real-time view of activity on Flex.org, my blog, and the blogs of several colleagues including James Ward, Christophe Coenraets, Michael Chaize, Anne Petteroe, Holly Schinsky, Ted Patrick, Kevin Hoyt, Ryan Stewart, and Ray Camden. Every time one of these sites is viewed by someone, a dot appears on the map at their approximate location.
Try it out for yourself. Click Load Last Hour to replay the last hour in accelerated time. I’ll let you figure out which color marker goes with each blog.
The easiest way to tap into real-time data is to employ a messaging paradigm called publish/subscribe messaging (commonly referred to as pub/sub). In the application above, the website and blog activity is published to a message destination as small messages. This article covers everything you need to add this kind of real-time data visualization to your website or application using pub/sub messaging. It explains the ColdFusion code that publishes data as well as the Flex code that subscribes to these published messages and displays the data in interesting ways.
I used the following technologies to build this application:
ColdFusion uses the sendGatewayMessage() function to publish messages. Before you can use this nifty function, you need to first configure a data service messaging gateway instance:
Note: By creating an onMessage() function, ColdFusion can receive messages through the gateway. For this article, however, you're only going to focus on sending messages so it's not necessary to have this function.
host="localhost" See the comments in the flex-messaging-gateway.cfg file for more details on this.

Now you can use the sendGatewayMessage() function to broadcast data. The code below publishes a simple struct that contains "Hello World" in the body.
<cfset msg= structNew()/>
<cfset msg['destination'] = "ColdFusionGateway"/>
<cfset msg['body'] = "Hello World"/>
<cfset sendGatewayMessage("cfgw",msg)/>
Notice the sendGatewayMessage() function uses the recently created cfgw gateway instance. Also, notice the destination . This example uses the preconfigured destination named ColdFusionGateway . If you want to see the definition of this destination, check out the following two files:
ColdFusionGateway already defined. Near the bottom of the definition is a section for configuring the communication channel:<channel><channel ref="cf-polling-amf"/></channel>The cf-polling-amf channel is defined in the services-config.xml file.
I'll provide more details on these configuration files in Getting technical: Channel types.
Now you have some simple ColdFusion code that publishes messages. Next, you need to build something to consume them.
Below is code for a basic Flex application that will subscribe to the same destination defined above, ColdFusionGateway. To try this out, simply create a new Flex project in Flash Builder and copy the code below into the main MXML file. Don’t worry about selecting an application server in this sample because you’re defining everything in the code. Sometimes it’s just easier for me to understand everything when it is there in front of me rather than wired into a properties file somewhere.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
applicationComplete="init()">
<fx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.messaging.events.ChannelEvent;
import mx.messaging.messages.IMessage;
private function init():void
{
consumer.subscribe();
}
private function onMessage(message:IMessage):void
{
trace("Message received:" + message.body);
log.text += message.body + "\n";
}
private function onChannelConnect(e:ChannelEvent):void
{
trace("Connected!");
log.text += "Channel connected!\n";
}
private function onChannelDisconnect(e:ChannelEvent):void
{
trace("Disconnected!");
log.text += "Channel disconnected!\n";
}
]]>
</fx:Script>
<fx:Declarations>
<mx:ChannelSet id="channelSet">
<!-- Default Polling Channel -->
<mx:AMFChannel url="http://192.168.1.60:8500/flex2gateway/cfamfpolling"/>
</mx:ChannelSet>
<mx:Consumer id="consumer"
channelSet="{channelSet}"
destination="ColdFusionGateway"
resubscribeAttempts="-1"
resubscribeInterval="2000"
message="onMessage(event.message)"
channelConnect="onChannelConnect(event)"
channelDisconnect="onChannelDisconnect(event)"
fault="Alert.show(event.faultString)"/>
</fx:Declarations>
<s:TextArea id="log" width="100%" height="100%"/>
</s:Application>
The only thing you’ll need to change is the IP address in the ChannelSet to point to your ColdFusion server. The code includes event handlers that fire when the application successfully connects to the server, when a message is received, and when the application gets disconnected from the server. When you build and run the application you should see the "Channel Connected!" message after a few seconds.
Now you can run the ColdFusion code above to send the "Hello world" message and you should see it received in your Flex application. To run the code, simply paste it into a CFM file in your wwwroot directory and then access that file from a browser. It might take a few seconds to see the message because of the type of channel you are using. In Getting technical: Channel types I’ll explain how to improve the response time by creating our own custom channel definition.
Now that you know how to publish data from ColdFusion and consume that data from a Flex application, it's easy to build some really cool stuff. To begin, you'll need to add some additional data to your outbound message from ColdFusion.
Getting the IP address in ColdFusion is straightforward. It's already available to you in #CGI.REMOTE_ADDR# . There are several services that provide IP address to location conversion but my favorite way to do it is to use a local database from MaxMind, a provider of geolocation and online fraud detection tools. The conversion is lightning fast because the database is right on your server. They have several databases available, but I highly recommend using one of the following:
MaxMind provides APIs for C, C#, Perl, PHP, Java, Python, Ruby, VB.NET, JavaScript and even Pascal. Since Java is in the list, it's easy to get this working with ColdFusion as well. Thankfully, I didn't have to do any work on this because Brandon Purcell has already done it and provided step-by-step instructions on his blog. To summarize Brandon's steps:
Now you can use the database to send geographical data via the gateway:
<cfinvoke component="geo" method="ipLookup" returnVariable="location">
<cfinvokeargument name="IP" value="#CGI.REMOTE_ADDR#"/>
</cfinvoke>
<cfset geodata = structNew()/>
<cfset geodata['destination'] = "ColdFusionGateway"/>
<cfset geodata['body']['city'] = "#location.IPCITY#"/>
<cfset geodata['body']['country'] = "#location.COUNTRYLONG#"/>
<cfset geodata['body']['countryshort'] = "#location.COUNTRYSHORT#"/>
<cfset geodata['body']['latitude'] = "#location.IPLATITUDE#"/>
<cfset geodata['body']['longitude'] = "#location.IPLONGITUDE#"/>
<cfset sendGatewayMessage("cfgw",geodata)/>
<cfdump var="#geodata#"/>
Notice that I added a <cfdump> to the end so I can see the results. It's very likely that if you run the code above, it will not show a location. This is because if you are in the same subnet as your server your IP address is private. To test the code, you can use your public IP address in place of #CGI.REMOTE_ADDR#. You should see the geodata information (see Figure 2) when you run the code.

Now the message you are broadcasting contains city, country (in two formats), latitude, and longitude. You can extract these new fields on the Flex side by referencing message.body.city , message.body.country , message.body.countryshort , message.body.latitude , and message.body.longitude .
Now that you have latitude and longitude, you can use any of several different mapping services. You'll find examples of Google, ESRI, MapQuest and Yahoo! Maps in Tour de Flex under the mapping category. In this article, you'll use the Google 3D Maps API for Flash. For information on other Google Maps APIs, visit Google Maps API Family.
I decided to use the 3D Map API for this because it allows you to change the map attitude, pitch, and yaw, which is a lot of fun and always impresses.
Here are the steps to get this working:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:maps="com.google.maps.*"
width="100%" height="100%">
<fx:Script>
<![CDATA[
import com.google.maps.InfoWindowOptions;
import com.google.maps.LatLng;
import com.google.maps.Map3D;
import com.google.maps.MapEvent;
import com.google.maps.MapOptions;
import com.google.maps.MapType;
import com.google.maps.View;
import com.google.maps.controls.MapTypeControl;
import com.google.maps.controls.NavigationControl;
import com.google.maps.geom.Attitude;
import com.google.maps.overlays.Marker;
import com.google.maps.overlays.MarkerOptions;
import com.google.maps.styles.FillStyle;
import mx.controls.Alert;
import mx.messaging.events.ChannelEvent;
import mx.messaging.messages.IMessage;
private function onMapPreinitialize(event:MapEvent):void
{
// Before the maps is rendered, we set a bunch of options
var myMapOptions:MapOptions = new MapOptions();
myMapOptions.zoom = 3.5;
myMapOptions.center = new LatLng(10,-30);
myMapOptions.mapType = MapType.SATELLITE_MAP_TYPE;
myMapOptions.viewMode = View.VIEWMODE_PERSPECTIVE;
myMapOptions.attitude = new Attitude(0,30,0);
myMapOptions.backgroundFillStyle = new FillStyle({color: 0x000000, alpha: 1.0}),
map.setInitOptions(myMapOptions);
}
private function onMapReady(event:MapEvent):void
{
map.addControl(new MapTypeControl()); // selection of map, satellite, hybrid and terrain
map.addControl(new NavigationControl()); // Controls for pan, zoom, attitude
consumer.subscribe(); // Start receiving messages now that the map is ready
}
private function onMessage(message:IMessage):void
{
var city:String = message.body.city;
var country:String = message.body.country;
var countryshort:String = message.body.countryshort;
var latitude:String = message.body.latitude;
var longitude:String = message.body.longitude;
// Sometimes the city and/or country come back blank. I prefer "Unknown"
if(city == "") city = "Unknown";
if(country == "") country = "Unknown";
var location:String = city + ", " + country;
// We need a LatLng object for use with the map
var latlng:LatLng = new LatLng(message.body.latitude, message.body.longitude);
// If the latitude/longitude is populated, go place the marker and an info window
if(latlng != null)
{
// Customize our marker a bit (tons of options are available including a custom icon
var markerOpts:MarkerOptions = new MarkerOptions();
markerOpts.tooltip = location;
markerOpts.radius = 5;
markerOpts.fillStyle = new FillStyle({color:0xff0000});
// Create a new marker with our customizations
var marker:Marker = new Marker(latlng,markerOpts);
map.addOverlay(marker); // Add our new marker to the map
// Customize the popup info window
var infoWindowOpts:InfoWindowOptions = new InfoWindowOptions();
infoWindowOpts.titleHTML = location;
infoWindowOpts.contentHTML = latitude.substr(0,6) + "," + longitude.substr(0,6);
infoWindowOpts.fillStyle = new FillStyle({alpha:0.5});
map.openInfoWindow(latlng,infoWindowOpts); // Add the info window
}
trace("Message received from " + location);
}
private function onChannelConnect(e:ChannelEvent):void
{
trace("Connected!");
}
private function onChannelDisconnect(e:ChannelEvent):void
{
trace("Disconnected!");
}
]]>
</fx:Script>
<fx:Declarations>
<mx:ChannelSet id="channelSet">
<!-- Default Polling Channel -->
<mx:AMFChannel url="http://192.168.1.60:8500/flex2gateway/cfamfpolling"/>
</mx:ChannelSet>
<mx:Consumer id="consumer"
channelSet="{channelSet}"
destination="ColdFusionGateway"
resubscribeAttempts="-1"
resubscribeInterval="2000"
message="onMessage(event.message)"
channelConnect="onChannelConnect(event)"
channelDisconnect="onChannelDisconnect(event)"
fault="Alert.show(event.faultString)"/>
</fx:Declarations>
<maps:Map3D id="map"
mapevent_mappreinitialize="onMapPreinitialize(event)"
mapevent_mapready="onMapReady(event)"
width="100%" height="100%"
key="PUT-YOUR-GOOGLE-API-KEY-HERE"/>
</s:Application>
<ChannelSet/> to point to your ColdFusion server."PUT-YOUR-GOOGLE-API-KEY-HERE" with the API key you obtained.Every time your ColdFusion code broadcasts a new activity message, your Flex application will extract the location from the message and display it on the Google 3D map.
You'll find tons of fun features including the ability to fly to a new location, customize markers, and more in the Google Maps API ActionScript Reference. For more demonstrations of the Google Maps API for Flash see Google's Demo Gallery.
When I first started playing with real-time visualization of geolocation data, I was eager to add tracking to my own blog so I could watch my blog activity on a world map. My blog, however, is not ColdFusion based so I didn't have the luxury of adding a few lines of ColdFusion code to broadcast the needed data. There are several server-side options that could work but my blog is hosted at WordPress.com so my options were very limited. I don't even have access to the server. Late one night I had an idea—I would use the <IMG> tag! It's the most common way external assets are included in HTML.
With a little help from Ray Camden, I figured out how to get a CFM file to behave like an image. On my blog, I simply have an image as follows:
<img src="http://myserver.com/trackimg.cfm" width="1" height="1">
Web browsers think it's a 1×1 transparent PNG.
Here's the source for trackimg.cfm:
<cfsetting enablecfoutputonly="Yes">
<cfsetting showdebugoutput="No">
<cfinvoke component="geo" method="ipLookup" returnVariable="location">
<cfinvokeargument name="IP" value="#CGI.REMOTE_ADDR#"/>
</cfinvoke>
<cfset geodata = structNew()>
<cfset geodata['destination'] = "ColdFusionGateway">
<cfset geodata['body']['city'] = "#location.IPCITY#">
<cfset geodata['body']['country'] = "#location.COUNTRYLONG#">
<cfset geodata['body']['countryshort'] = "#location.COUNTRYSHORT#">
<cfset geodata['body']['latitude'] = "#location.IPLATITUDE#">
<cfset geodata['body']['longitude'] = "#location.IPLONGITUDE#">
<cfset sendGatewayMessage("cfgw",geodata)>
<!--- Read a 1x1 transparent png from disk that I created in PhotoShop --->
<cffile action="readBinary"
file="c:\ColdFusion9\wwwroot\images\dot.png"
variable="trackImage">
<!--- Return the 1x1 bitmap in png format --->
<cfheader name="content-disposition" value="Inline;filename=dot.png">
<cfcontent type="image/png;" variable="#trackImage#">
Using this technique, you can add tracking to any of your websites by simply adding the image tag. In the demo application, you'll notice that different sites show up as different colors. This is done by adding a color variable to the geodata struct . You set the color based on the domain in the referrer ( #CGI.HTTP_REFERRER# ) using code like this:
<cfif findNoCase("gregsramblings.com",#CGI.HTTP_REFERER#)>
<cfset color="EEEE00">
<cfset domain="gregsramblings.com">
<cfelseif findNoCase("flex.org",#CGI.HTTP_REFERER#)>
<cfset color="00FF00">
<cfset domain="flex.org">
<cfelseif findNoCase("jamesward.com",#CGI.HTTP_REFERER#)>
<cfset color="9933FF">
...
...
<cfset geodata['body']['color'] = "#color#">
<cfset geodata['body']['domain'] = "#domain#">
<cfset geodata['body']['url'] = "#CGI.HTTP_REFERER#">
...
...
On the Flex side, you can use message.body.color to set the marker color. You can use message.body.domain and message.body.url in the info window. There are lots of possibilities.
All of the Flex code in this article is set up to receive all messages sent from the ColdFusion server. If you later want to use the same ColdFusion gateway for multiple applications, you're going to need some way to filter messages. This is where subtopics come in. Subtopics provide a means of categorizing messages within a destination. By default, subtopics are enabled as you can see near the top of C:\ColdFusion9\wwwroot\WEB-INF\flex\messaging-config.xml. To publish messages on a specific subtopic, add the following to your struct :
<cfset geodata['headers']['DSSubtopic'] = "BLOGS"> On the Flex side, you need to add subtopic= " BLOGS " to your <mx:Consumer> . Now your Flex application will only receive messages sent specifically on the BLOGS subtopic. Subtopics can be defined using the following format: mainToken[.secondaryToken][.additionalToken]. For example, you could use something like BLOGS.GREG for one site and BLOGS.BEN on another. On the Flex side, you can subscribe to either or you can use wildcards to subscribe to multiple subtopics; for example, subtopic="BLOGS.*" . When you subscribe to a subtopic, the filtering happens at the server, so your application only receives messages that match.
In addition to subtopics, you can also use selectors. For more information on selectors and subtopics, refer to the BlazeDS and LiveCycle Data Services documentation.
You may have noticed that there is sometimes a delay of a few seconds after ColdFusion sends the message until the Flex application receives it. So far you have been using the out-of-the box configuration for ColdFusionGateway . If you look in C:\ColdFusion9\wwwroot\WEB-INF\messaging-config.xml, you'll see that the assigned channel is cf-polling-amf , which is defined in C:\ColdFusion9\wwwroot\WEB-INF\flex\services-config.xml. This is set up to do basic polling, which is not very responsive. You can speed things up significantly by implementing long polling. Here are the steps:
<channel-definition id="cf-longpolling-amf" class="mx.messaging.channels.AMFChannel">
<endpoint url="http://{server.name}:80/{context.root}/flex2gateway/amflongpolling" class="flex.messaging.endpoints.AMFEndpoint"/>
<properties>
<polling-enabled>true</polling-enabled>
<polling-interval-seconds>3</polling-interval-seconds>
<wait-interval-millis>60000</wait-interval-millis>
<client-wait-interval-millis>1</client-wait-interval-millis>
<max-waiting-poll-requests>200</max-waiting-poll-requests>
</properties>
</channel-definition>
ColdFusionGateway destination. The modified XML will look like this:<channels>
<channel ref="cf-longpolling-amf"/>
<!--<channel ref="cf-polling-amf"/>-->
</channels>
ChannelSet :<mx:ChannelSet id="channelSet">
<!-- Default Polling Channel -->
<mx:AMFChannel url="http://192.168.1.60:8500/flex2gateway/amflongpolling"/>
</mx:ChannelSet>
Polling and long polling are not the only two options. BlazeDS also supports HTTP streaming, which has even better performance. Some firewalls, however, don't like a persistent connection, which can be a problem with streaming. You can find more information about endpoints in Holly Schinsky's blog post on the topic and in the LiveCycle Data Services documentation.
LiveCycle Data Services ES2 offers the same messaging features as BlazeDS but it also offers additional capabilities that you need to be aware of.
You can find the complete LiveCycle Data Services 3.1 documentation here.
This article has only addressed the messaging features of LiveCycle Data Services. There are many other compelling facets to LiveCycle Data Services, and I encourage you to explore them. For messaging, it's fairly simple. If you need the features I just listed or if you need highly scalable messaging, then you need LiveCycle Data Services, otherwise BlazeDS will work for you.
So, what's next for the real-time map application?
I've recently added server-side logging and the ability to replay history on the map (for example, load last hour, load last day, and so on). I've also added icons and other features.
For a real-world example of real-time data on a map, visit this page. This application shows real-time activity from ChessJam, an online chess site that I helped develop. In this case, the server is ColdFusion, so I simply added code similar to the sendGatewayMessage() code shown above in the function that handles chess moves. Every time a player makes a move, it shows on the map. I used an image of a map from 1812 as an overlay to give it a little age. The really amazing thing here is that they made a map of the world in 1812 with no GPS and no satellite pictures!

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License