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
function callHeatMapData() {
getFlexApp('mySwf').HeatMapData(allPoints,"","","","");
}
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.
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.
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.
function GetKey()
{
getFlexApp('mySwf').MapLoad(key);
}
The flash MapLoad
method initializes the gmap and adds MapEvent.MAP_READY
event.
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.
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.
function callHeatMapData() {
getFlexApp('mySwf').HeatMapData(allPoints,"","","","");
}
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.
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());
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.
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.
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.
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.
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.
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.
function changeColorScheme(control) {
getFlexApp('mySwf').ChangeColorScheme(control.value);
}
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