Friday, January 30, 2009

Creating Map Overlays with the Google Maps API for Flash

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_ADDED and MapEvent.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.)

Sunday, January 25, 2009

Earthquake Heat Map

Update: An updated application which creates the heat map as a Google map overlay. Read about it here.

The United States Geological Survey (USGS) publishes data on recent earthquakes through various RSS feeds. I came across this recently when I thought I had felt an earthquake. This was a nice opportunity to do something with a heat map, I thought... So I made this mashup with Google Maps and the USGS data.

Nerdy Details

I used a nice heat map component from Michael VanDaniker, the Google Maps Flash API, the AS3 MarkerManager, and Cairngorm. Using the Cairngorm framework was probably a little overkill for a small project like this... but at least it's ready to go if I want to add more functionality.

As usual, you can view the source by right clicking on the app. Click here, or the image above to launch the map.

Monday, January 12, 2009

A Simple Degrafa Color Picker

A comment from my last post suggested I check out com.degrafa.paint.palette.PaletteUtils. Thank you so very much, gwd, for pointing that out! PaletteUtils is very cool! I wish I would have known about it a couple of months ago when I wrote my own color generating code.

PaletteUtils has some static methods that will return an array of colors:

  • getCategoryPalette()
  • getCoolPalette()
  • getHotPalette()
  • getHSBPalette()
  • getInterpolatedPalette()

Below is an example of a simple color picker that uses PaletteUtils to generate colors. Note that I had to fix a few issues with getHSBPalette() and getCategoryPalette() (issues in Degrafa Beta 3, for which I've filed some bugs).

Right click on the app to view the Flex source. Click here for a larger version.

Note: some of the sliders (like Saturation2) are only used in conjunction w/some of the palettes.

Saturday, January 10, 2009

Generating many different colors with HSB color values

Update: A color picker w/Degrafa's built in palettes here

Someone on Flex Coders asked how to generate a series of random colors, and at the same time omit certain shades. I recently had a similar requirement. I used HSB color values to generate the distinct colors, and then converted them to the RGB values that we know and love.

Working w/HSB is more intuitive than RGB

Take a look at the ColorJack color sphere. Hue is an angle from 0 to 360, representing a color. At angle 0, the color is red. This just seems more intuitive than knowing how to combine varying amounts of red, green, and blue.

"Saturation" and "Balance" (or somtimes Brightness?) are also very intuitive. As saturation goes to zero, the color becomes more light. As balance goes to zero, the color becomes more dark. My description is not very scientific, but that's also my point.

My ColorManager class

I needed to generate a large number of fairly distinct colors. My approach was to rotate around the 360 degrees of hues, and vary saturation and brightness. I used this code to convert the HSB color to RGB.

Once you generate the colors, you can retrieve them as an array. Or, you can associate a color with a key name and retrieve it as needed.

// generate 100 colors
var cm:ColorManager = new ColorManager(100);

// configure optional properties
cm.initialHue = 180;  // start at hue angle of 180

// generate colors
cm.generateColors();

// retrieve as array
var a:Array = cm.colors;

// assign/retrieve a key's color just by asking for it
trace( _cm.getColorFor("foo") );
    
// now the key 'foo' is assigned a color
// if you want it later, ask for it
// with cm.getColorFor("foo")

Below is an example app that uses Degrafa to draw a bunch of squares. You can also try it here with more room to play with.