Here's an update of my Earth Quake Heat Map. It is an example of two types of custom overlays for Google Maps (Flash API). Overlays in Google Maps are used for things like map markers or for superimposing an image, a polygon, or other data on top of the map. In my first attempt at the heat map, I superimposed Michael VanDaniker's heat map component on top of a Google map. However, I didn't do that as a map overlay, it was merely one component sitting on top of another component, inside a Flex Canvas.
The result of this was that sometimes the heat map would be drawn on top of the map controls (pan/zoom controls, etc). It was pretty apparent that the heat map wasn't actually a part of the Google map.
The solution was to make the heat map into a proper overlay by extending the OverlayBase class. Using composition, I added Michael VanDaniker's HeatMap to the overlay. The result is that now the heat map is rendered underneath the map's pan/zoom controls ... it is actually part of the map! Doing this also removed a layer of event handling, because the map automatically tells the overlay to redraw/reposition itself.
Extending OverlayBase
It's pretty straight forward for a simple overlay. Override two methods, and add two event handlers to detect when the overlay is added/removed.
- Override the
getDefaultPane()method
override public function getDefaultPane(map:IMap):IPane
{
return map.getPaneManager().getPaneById(PaneId.PANE_OVERLAYS);
}
This method is called by the map (I assume) to figure out which "pane" to put your overlay on. There are constants defined in the PaneID class for markers, overlays, etc.
- Override the
postionOverlay()method
override public function positionOverlay(zoom:Boolean):void
{
if (zoom)
{
heatMap.itemRadius = Math.max(20, Math.pow( pane.map.getZoom(),1.5));
}
// positioned at (0,0) by default
heatMap.width = (pane.map as Map).width;
heatMap.height = (pane.map as Map).height;
heatMap.invalidateProperties();
}
This method positions the overlay on the map. It's called frequently when the map is moved/zoomed, the docs suggest avoiding heavy calculations. You can also redraw your component here. For the heat map, the position was always 0,0 because we want the heat map overlay to cover the entire map. I used this method to tell the heat map to redraw it self. For a marker, you would obviously set the x,y coords of the marker's lat/lon.
- Add event handlers for
MapEvent.OVERLAY_ADDEDandMapEvent.OVERLAY_REMOVED
public function GHeatMapOverlay(heatMap:HeatMap)
{
super();
this.heatMap = heatMap;
addEventListener(MapEvent.OVERLAY_ADDED, handleOverlayAdded,false,0,true);
addEventListener(MapEvent.OVERLAY_REMOVED, handleOverlayRemoved,false,0,true);
}
private function handleOverlayAdded(event:Event):void
{
addChild(heatMap);
}
private function handleOverlayRemoved(event:Event):void
{
removeChild(heatMap);
}
HeatMap is a Flex component! This shouldn't work, should it?
You normally cannot create overlays or markers out of Flex components with the Google Maps API for Flash. To create a custom marker you could draw your own graphics (with say the graphics property of a Sprite) or use your own images/icons.
It turns out you can use some Flex components in overlays and map markers. These components, however, need to render themselves using the graphics property (my assumption as to why it works) ... Michael VanDaniker's heat map does just that. I'm really excited about discovering this, because the Degrafa framework also supports this! With a Degrafa GeometryComposition you can draw shapes targeted at the graphics property of a UIComponent, and add that UIComponenet to your maps!
In my next post, I'll show how I created a map marker with Degrafa by extending OverlayBase and implementing the IMarker interface. In the mean time, you can see the heat map overlay and Degrafa markers in action here. (View Source is enabled in the app.)
Fantastic! Can I put this as a demo in the demo gallery for the API?
ReplyDeleteHi Pamela, please do... I learned all of this from reading your examples. You do a tremendous job with helping everyone in the community w/the Google Maps API. Many thanks!
ReplyDeleteGreat work!
ReplyDeleteA couple of quick notes though - I just spent three hours trying to work out why this worked in one of my projects and not the other only to find that it is incompatible with version 1.6 of the flex library but 1.7 onwards is fine!
I also had trouble with line 278 of the HeatMap class (not yours I know but I think this only happens when adding as a map overlay) where getRect was returning a 0 sized rectangle and none of the points were drawn before the next refresh from either zooming or moving the map. Changing lines 278 and 279 to
var rect:Rectangle = new Rectangle(-itemRadius, -itemRadius, unscaledWidth + (itemRadius * 2), unscaledHeight + (itemRadius * 2));
fixes this problem every time. Hopefully that helps someone :)
Thanks
This is great!!
ReplyDeleteI'm working on getting Michael Vandaniker's HeatMap Class to work in a pure AS3 google map environment. I've had to strip out some of the Flex specific stuff to get it working.
http://stoutmedia.com/clients/coralcross/heatmap/
I'm trying to figure out how you got the points to map to the LatLng coordinates and redraw correctly when zooming / panning. Is there some conversion function I'm missing? Like the Google API fromLatLngToPixel()?
It seems like it might be updateDisplayList() in conjunction with updatePoints() in HeatMap? Am I close?
Hi Jacob,
ReplyDeleteIn this example I created a Google Maps overlay out of Michael's heat map. A Google map notifies its overlays that the map is being panned or zoomed by calling the overlay's positionOverlay() method.
Take a look at my class GHeatMapOverlay.as in this app:
http://sunild.com/proto/Earthquakes2/Earthquakes.html
(note this is a second, updated version of my earthquake map, right click to view the source)
In my GHeatMapOverlay class, I extended Google's OverlayBase class and overrode some of its methods, including positionOverlay().
Since we expect the heat map overlay to cover the entire visible area of the Google map, we don't have to worry about positioning the heat map ... it should always be positioned at (0,0).
But we do want the heat map to redraw itself when the Google map is zoomed or panned. So in positionOverlay(), I call invalidateProperties() on Michael's heat map to make it redraw itself.
Hope that helps!
Sunil
Looks like your code is no more working with the last SDK... I even got a compile error about the implementation of the IMarker interface.
ReplyDelete@Tom,
ReplyDeleteI grabbed the latest Google Maps SWC and it looks like Google has added a bunch of new methods to the interface.
Last time I checked (over a year ago), they don't tell you much about this interface in the docs. You can use Flash Builder to determine interface's signature: just create a new class that implements IMarker and Flash Builder will create all the required method stubs.
Below is the list of methods that I got from doing the above. I haven't tried to implement all of these methods or figure out what they should do. If you do, it would be great if you could link back here w/that info!
Sunil
public class MyMarker implements IMarker
{
public function MyMarker(){}
public function isHidden():Boolean {}
public function hide():void {}
public function openInfoWindow(arg0:InfoWindowOptions=null, arg1:Boolean=false):IInfoWindow {}
public function setOptions(arg0:MarkerOptions):void {}
public function getOptions():MarkerOptions {}
public function getLatLng():LatLng {}
public function show():void {}
public function closeInfoWindow():void {}
public function setLatLng(arg0:LatLng):void {}
public function positionOverlay(arg0:Boolean):void {}
public function getDefaultPane(arg0:IMap):IPane {}
public function get foreground():DisplayObject {}
public function set visible(arg0:Boolean):void {}
public function get visible():Boolean {}
public function get shadow():DisplayObject {}
public function set pane(arg0:IPane):void {}
public function get pane():IPane {}
public function getBaseEventDispatcher():Object {}
public function get interfaceChain():Array {}
public function get wrapper():Object {}
public function set wrapper(arg0:Object):void {}
/* Remaining methods look like IEventDispatcher methods */
public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void {}
public function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void {}
public function dispatchEvent(event:Event):Boolean {}
public function hasEventListener(type:String):Boolean {}
public function willTrigger(type:String):Boolean {}
}
Hi,
ReplyDeleteI found you heat map is too good.
when I am trying with Flex 4 it shows black transparent layer of heat map over the Google map.
Is there any workaround please help..
Thanks in advance...