Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Client Side Heat Map in Flex

4.67/5 (4 votes)
17 Sep 2010CPOL4 min read 55.6K   794  
This is a fast and free client side heat mapping tool.
FixedZoom.PNG

Introduction

This is the fastest & free heat mapping tool. It is a client side heat map too. So, if the number of users increase, performance will not drop. It is mainly based on JavaScript & Flex. WE can use ASP.NET, PHP or other server site languages. The server site language only sets points & other configuration to the JavaScript. For loading the flash, it takes some time, but after the load, it is the fastest.

Background

I had the need for a flexible and scalable heat mapping application. A quick Google search lead me to gheat. I found it to be slow and not flexible enough to suit my needs. I also found GHeat.NET which is also slow(in zoom in, zoom out, drag, drop) and if the number of users increase, then its performance is too bad. So I decided to attempt to make heat map tools in client side and using flex.

Using the Code

AutoZoom.PNG
JavaScript
function callHeatMapData() {
getFlexApp('mySwf').HeatMapData(allPoints,"","","","");
//getFlexApp('mySwf').HeatMapData
//	(allPoints,"30.1238660,-92.0706730","5","fire","hybrid");
}

It has auto zoom & auto center functionality. If we do not set zoom & center value, then it will determine the zoom value & center for all the points showable. HeatMapData method gives the flash about the points information. Here, allPoints are the points in string, like this "21.400000,-157.797429#25.850000,-80.185260". The next field is zoom label, if it is "", then it will make auto zoom. The next field is center of the map, if it is "", then it will make auto center. The next field is color scheme, like classic, fire, omg, pbj, pgaitch. If it is "", then it will show classic scheme. The next field is gmap type, if it is "", then it will show normal map.

JavaScript
var allPoints="";
var key = "Key";
    
function SetAllPoints(value)
{
    allPoints = value;
}

function getFlexApp(appName) {
    return window.document[appName];
}
    
function GetKey()
{
    getFlexApp('mySwf').MapLoad(key);
}

Using SetAllPoints method, server script sets all points value to the JavaScript. After the flash load, flash calls the JavaScript method GetKey and gets the gmap key, then flash loads the gmap & after the gmap is ready, flash calls JavaScript callHeatMapData method and gets all points information. Then flash shows the heat map.

How Does it work?

After loading the flash, it set the loading swf position, then it call the JavaScript method GetKey() for get the gmap key.

JavaScript
public function OnLoad():void
{
 	swfLoader.x = (this.width - swfLoader.width)/2;
 	swfLoader.y = (this.height - swfLoader.height)/2;
 	ExternalInterface.addCallback("MapLoad", MapLoad);
 	ExternalInterface.call("GetKey");
}

Then JavaScript GetKey method calls the flash MapLoad method with the gmap key.

JavaScript
function GetKey()
{
    getFlexApp('mySwf').MapLoad(key);
}

The flash MapLoad method initializes the gmap and adds MapEvent.MAP_READY event.

JavaScript
public function MapLoad(key:String):void
{
	map = new Map();
 	map.setStyle("left","0");
 	map.setStyle("right","0");
 	map.setStyle("top","0");
 	map.setStyle("bottom","0");
 	map.id = "map";
 	map.key = key;
 	map.addEventListener(MapEvent.MAP_READY,onMapReady);
 	cnsGoogleMap.addChild(map);
}

After the gmap is Ready, it calls the JavaScript callHeatMapData method for getting the points.

JavaScript
private function onMapReady(event:Event):void {
     
     if (ExternalInterface.available)
     {
     ExternalInterface.addCallback("HeatMapData", HeatMapData);
     ExternalInterface.addCallback("ChangeColorScheme", ChangeColorScheme);
     }
     else
     Alert("fail");
       
     mapType = new ArrayCollection();
     mapType.addItem("Normal");
     mapType.addItem("Hybrid");
     mapType.addItem("Physical");
     mapType.addItem("Satellite");
     cmbMapType.dataProvider = mapType;
     
     map.setMapType(MapType.NORMAL_MAP_TYPE);
    
     ExternalInterface.call("callHeatMapData");
    }

The JavaScript callHeatMapData method then calls the flash HeatMapData method with all information.

JavaScript
function callHeatMapData() {
        getFlexApp('mySwf').HeatMapData(allPoints,"","","","");
        //getFlexApp('mySwf').HeatMapData(allPoints,
        //	"30.1238660,-92.0706730","5","fire","hybrid");
    }

Then the Flash HeatMapData method sets the gmap type, then screen color. Then finds all LatLng from the gmap by the points. Then it sets zoom & center of the gmap. If the zoom & center is auto, then it uses LatLngBounds for determining the zoom & center of the gmap. Then it adds Mouse Down, Mouse Up & Mouse Move event listener. Then it calls InitialImage method for displaying a screen image over the gmap. Then it chooses dots image according to the gmap zoom. Then it calls the GenareteImage method for showing the heat map image.

JavaScript
public function HeatMapData
    (allLatLng:String,center:String,zoom:String,
	colorScreen:String,mapType:String):void
     {   
     
     if(mapType.toLowerCase()=="normal")
     {
     map.setMapType(MapType.NORMAL_MAP_TYPE);
     cmbMapType.selectedIndex = 0;
     }
     else if(mapType.toLowerCase()=="hybrid")
     {
     map.setMapType(MapType.HYBRID_MAP_TYPE);
     cmbMapType.selectedIndex = 1;
     }
     else if(mapType.toLowerCase()=="physical")
     {
     map.setMapType(MapType.PHYSICAL_MAP_TYPE);
     cmbMapType.selectedIndex = 2;
     }
     else if(mapType.toLowerCase()=="satellite")
     {
     map.setMapType(MapType.SATELLITE_MAP_TYPE);
     cmbMapType.selectedIndex = 3;
     }
        	 	
     _screenColor = colorSchemesBitmapData.GetSchemesBitmapData
	(colorScreen.toLowerCase());//Bitmap(imgScheme.content).bitmapData;
 	 var hmo:HeatMapOpacity = new HeatMapOpacity();
     _allZoomOpacity = hmo.BuildZoomMapping(); 
     
     var allPoints:String = allLatLng;
     var points:Array = allPoints.split( '#' );
     
     var bounds:LatLngBounds = new LatLngBounds();
     _allLen = new ArrayCollection();
     
     for ( var i:int = 0; i < points.length; i++ ) {
          	var temp:Array = points[i].toString().split(',');
          	  
          	var latlng:LatLng = new LatLng(Number(temp[0]) ,Number(temp[1]));
  		if(latlng != null)
  		{
 			bounds.extend(latlng);
 			_allLen.addItem(latlng);
     	}
     }
    
     if(zoom==""||zoom == null)
     map.setZoom(map.getBoundsZoomLevel(bounds));
     else
     map.setZoom(Number(zoom));
     
     if(center==""||center==null)
     map.setCenter(bounds.getCenter());
     else
     {
     	var temp:Array = center.split(',');
        var latlng:LatLng = new LatLng(Number(temp[0]) ,Number(temp[1]));
        map.setCenter(latlng);
     }
     
     cnsShow.addEventListener(MouseEvent.MOUSE_DOWN,DragMap);
     cnsShow.addEventListener(MouseEvent.MOUSE_UP,DropMap);
	 cnsShow.addEventListener(MouseEvent.MOUSE_MOVE,MouseMove);
	 ImageGenaretorManager.Instance.InitialImage();
  	 _dotsColor = dotsBitmapData.GetDotBitmapData(getZoom);
	 ImageGenaretorManager.Instance.GenareteImage(); 
	 _mapShown = true;
    }

The InitialImage method takes a bitmapdata whose height & width is similar to the gmap. Initially all its pixel color is white. Then bitmapdata divides into 50X50 tiles. Then sets a default bitmapdata in all the tiles. It uses a _flag array for identifying which tiles should be updated.

JavaScript
public function InitialImage():void
{
	if(_timer != null && _timer.running == true)
	_timer.stop();
	_timer = null;
			
	Application.application.RemoveAllTilesFromHeatMapCanvas();
   _startX =	Application.application.HeatMapCanvas.x;
   _startY =	Application.application.HeatMapCanvas.y;
   _width = Application.application.HeatMapCanvas.width;
   _height = Application.application.HeatMapCanvas.height;
   _rowTilesNumber = Math.ceil(_width/TilesSize);
		   
   _densityCointainer =  
	new BitmapData(_width,_height,false,0xffffff); 
		   
   var currentX:Number = _startX;
   var currentY:Number = _startY;
		  
   var zoomLavel:Number = Application.application.ZoomOpacity
	[Math.floor(Application.application.getZoom)] as Number;   	
		   
   var tempBitmapData:BitmapData = new BitmapData
	(TilesSize,TilesSize,false,
	Application.application.ColorSchemeBitmapData.getPixel(0,255));
   var count:int = 0;
		   	   
   while(currentY<_height)
   {
   	var wid:Number = TilesSize;
   	var hei:Number = TilesSize;
   	
   	if(currentX+wid>_width)
   	wid = _width - currentX;
		   	
   	if(currentY+hei>_height)
   	hei = _height - currentY;
		   	
    var tempBitmap:Bitmap = new Bitmap(tempBitmapData);
    tempBitmap.alpha = zoomLavel/255;
    tempBitmap.height = hei;
    tempBitmap.width = wid;
    tempBitmap.x = 0;
    tempBitmap.y = 0;
    var uiHolder:UIComponent = new UIComponent();
    var bitmapHolder:Sprite = new Sprite();
    uiHolder.addChild(bitmapHolder);
    bitmapHolder.addChild(tempBitmap);
    uiHolder.x = currentX;
    uiHolder.y = currentY;
    uiHolder.name = "Tiles"+count.toString();
   	Application.application.AddToTheHeatMapCanvas(uiHolder);
   	count++;
   	currentX += TilesSize;
   	if(currentX>_width)
   	{
   		currentX = _startX;
   		currentY += TilesSize;
   	}
   }
		   
   _flag = new Array();
   for(var i:int =0;i

The GenareteImage calls another method ProcessForGenareteImage by timer. The ProcessForGenareteImage method finds the pixel co-ordinate for all LatLng, then checks which points are currently in the gmap view area and for those points, changes the intensity of the color (Darker) in the _densityCointainer bitmapdata by call the CopyDot method. Then for all the tiles, check which should be updated. If a tile should be updated, then it draws the tiles according to the color scheme by calling the Colorize method.

JavaScript
public function GenareteImage():void
{ 	
   _timer = new Timer(10);
   _timer.addEventListener(TimerEvent.TIMER, ProcessForGenareteImage);
   _timer.start();	   
}
		
private function ProcessForGenareteImage(evt:TimerEvent):void
{
   _timer.stop();
   
   var dotBitmap:BitmapData = Application.application.DotBitmapData; 
	   
   for each(var latleg:LatLng in Application.application.AllLen)
	{
		var point:Point =  Application.application.GetPointByLetLng(latleg);
		if(point.x>=_startX-dotBitmap.width/2&&point.y>=
					_startY-dotBitmap.height/2
		&&point.x<=_width+dotBitmap.width/2&&point.y<=
					_height+dotBitmap.height/2)
		{	
			_densityCointainer = ProcessImage.Instance.CopyDot
				(_densityCointainer,dotBitmap,point.x,point.y);
		}
	}
		
	var colorBitmap:BitmapData = Application.application.ColorSchemeBitmapData; 
	var zoomLavel:Number = Application.application.ZoomOpacity
			[Math.floor(Application.application.getZoom)] as Number;
	var tempBitmapData:BitmapData = new BitmapData
			(TilesSize,TilesSize,false,colorBitmap.getPixel(0,255));
			
	for(var i:int = 0; i<_flag.length;i++)
	{
		Application.application.RemoveTilesFromHeatMapCanvas
						("Tiles"+i.toString());
		var wid:Number = TilesSize;
		var hei:Number = TilesSize;
	   	
		if((i%_rowTilesNumber)*TilesSize+wid>_width)
		wid = _width - (i%_rowTilesNumber)*TilesSize;
	   	
		if(Math.floor(i/_rowTilesNumber)*TilesSize+hei>_height)
		hei = _height - Math.floor(i/_rowTilesNumber)*TilesSize;
	   		
		var bitmapColor:BitmapData = null;
	   		
		if((_flag[i] as Boolean) == true)
		bitmapColor = ProcessImage.Instance.Colorize
			(_densityCointainer,colorBitmap,(i%_rowTilesNumber)*
			TilesSize,Math.floor(i/_rowTilesNumber)*TilesSize,wid,hei);
		else
		bitmapColor = tempBitmapData;
			
		var tempBitmap:Bitmap = new Bitmap(bitmapColor);
		tempBitmap.alpha = zoomLavel/255;
		tempBitmap.height = hei;
	  	tempBitmap.width = wid;
   		tempBitmap.x = 0;
  		tempBitmap.y = 0;
    		var uiHolder:UIComponent = new UIComponent();
		var bitmapHolder:Sprite = new Sprite();
 		uiHolder.addChild(bitmapHolder);
		bitmapHolder.addChild(tempBitmap);
		uiHolder.x = (i%_rowTilesNumber)*TilesSize;
		uiHolder.y = Math.floor(i/_rowTilesNumber)*TilesSize;
		uiHolder.name = "Tiles"+i.toString();
		Application.application.AddToTheHeatMapCanvas(uiHolder);
	}
}

The CopyDot method changes the intensing of _densityCointainer bitmapdata according to the dot image.

JavaScript
public function CopyDot
	(source:BitmapData, destination:BitmapData,x:int,y:int):BitmapData
 {
 	for(var i:int = x-destination.width/2;  i < x - destination.width / 2 + 
			destination.width; i++)
 	for(var j:int = y-destination.height/2; j < y - destination.height / 2 + 
			destination.height; j++)
 	if(i >= 0 && j >= 0 && i < source.width && j < source.height)
 	{
 	    	var color:uint = destination.getPixel( i - 
		( x - destination.width / 2 ),j - ( y - destination.height / 2));
     		var color1:uint = source.getPixel(i,j);
 		    
     		var co1:uint = color%256;
 		var co2:uint = color1%256;
 	 	var newColor:uint = 0;
 		    	
 	    	if(co1!=255&&co2!=255)
 	    	{
 	    	var t1:Number = ((co1 as Number));
 	    	var t2:Number = ((co2 as Number));
 	    	t1 = (t1 * t2 )/ 255.0;
 	    	co1 = Math.floor(t1);
 	    	}
 		    	
 	    	ImageGenaretorManager.Instance.SetFlag(i,j);
 	    	newColor = co1 * 256 * 256 + co1 * 256 + co1;
 		    	
  		source.setPixel(i,j,newColor);
 	}		
 	return source;
 }

The Colorize method draws the updated tiles according to the color scheme.

JavaScript
public function Colorize(source:BitmapData, colorScheme:BitmapData,
	x:int,y:int,w:int,h:int):BitmapData
 {
 	var newBitmap:BitmapData = new BitmapData(w,h,false,0xffffff);
 			
 	for(var i:int = x; i < x + w ; i++)
 	for(var j:int = y; j < y + h;j++)
 	{
 		var c1:uint = source.getPixel(i,j);
 		c1 = c1 % 256;
 				
 		var c2:uint = colorScheme.getPixel(0,c1);
 		newBitmap.setPixel(i-x,j-y,c2);
 	}
 			
 	return newBitmap;
 }

When Mouse Down event occurs, then it calls InitialImage method for initializing the scene. Then when Mouse Move event occurs, it moves the gmap. Then when Mouse up event occurs, it calls the GenareteImage method for drawing the heat map. In Zoom In, Zoom Out, it changes the gmap zoom, then calls the InitialImage & GenareteImage method for redrawing the heat map.

JavaScript
private function ZoomIn(evt:MouseEvent):void
{
	map.zoomIn();
	_dotsColor = dotsBitmapData.GetDotBitmapData(getZoom);
	ImageGenaretorManager.Instance.InitialImage();
	ImageGenaretorManager.Instance.GenareteImage();
}
	
private function ZoomOut(evt:MouseEvent):void
{
	map.zoomOut();
	_dotsColor = dotsBitmapData.GetDotBitmapData(getZoom);
	ImageGenaretorManager.Instance.InitialImage();
	ImageGenaretorManager.Instance.GenareteImage();	
}
 
private function Click(evt:TimerEvent):void
{
	timer.stop();
	timer = null;
}
 	
private function DoubleClick(evt:MouseEvent):void
{
	if(timer != null)
	{
	_dragPoint = null;
	timer.stop();
	timer = null;
	map.zoomIn();
	_dotsColor = dotsBitmapData.GetDotBitmapData(getZoom);
	ImageGenaretorManager.Instance.InitialImage();
	ImageGenaretorManager.Instance.GenareteImage();
	}
	else
	{
	timer = new Timer(duration);
	timer.addEventListener(TimerEvent.TIMER,Click);
	timer.start();
	}
}
	
private function ReSize():void
{
 	if(_mapShown==true)
 	{
 	ImageGenaretorManager.Instance.InitialImage();
 	ImageGenaretorManager.Instance.GenareteImage();
 	}
 }
	
 private function DragMap(event:MouseEvent):void
 {
 	_dragPoint = new Point(event.stageX,event.stageY);
 	ImageGenaretorManager.Instance.InitialImage();
 	DoubleClick(event);
 }
 	
 private function MouseMove(event:MouseEvent):void
 {
 	if(_dragPoint != null)
 	{
 	var pointCenter:Point = GetPointByLetLng(map.getCenter());
 	pointCenter.x += (_dragPoint.x-event.stageX);
 	pointCenter.y += (_dragPoint.y-event.stageY);
 	var latlet:LatLng =  map.fromViewportToLatLng(pointCenter);
 	map.setCenter(latlet);
 	_dragPoint = new Point(event.stageX,event.stageY);
 	}
 }
 	
 private function DropMap(event:MouseEvent):void
 {		
 	if(_dragPoint != null)
 	{
 	ImageGenaretorManager.Instance.GenareteImage();
 	}
 	_dragPoint = null;
 }

When the user changes the combo box color scheme, then the JavaScript method changeColorScheme calls the flash method ChangeColorScheme with the scheme color name. Then the flash ChangeColorScheme method changes the screen color bitmapdata & calls the InitialImage & GenareteImage <code>method for drawing the heat map according to the new color scheme.

JavaScript
function changeColorScheme(control) {
        getFlexApp('mySwf').ChangeColorScheme(control.value);
}
JavaScript
public function ChangeColorScheme(value:String):void
{
 	_screenColor = colorSchemesBitmapData.GetSchemesBitmapData
	(value.toLowerCase());
	ImageGenaretorManager.Instance.InitialImage();
	ImageGenaretorManager.Instance.GenareteImage();
}

Thanks

Thanks to gheat & GHeat.NET.

History

  • 9th September, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)