The Google 3D Maps API for Flash/Flex is a very impressive tool. It allows you to do 3d perspective right inside your .swf application similar to Google Earth.

As part of this API, you can specify a custom-designed clickable marker icon to be displayed on your map. Usually, this is done by setting the icon property of MarkerOptions.

var bmp:Bitmap = loader.content as Bitmap;

var markerOptions:MarkerOptions = new MarkerOptions(
    {
        icon: bs,
        iconAlignment: MarkerOptions.ALIGN_BOTTOM | MarkerOptions.ALIGN_HORIZONTAL_CENTER,
        hasShadow: true,
        tooltip: member.name + " - " + member.phone
    }
); 
           
var marker:Marker = new Marker(latLng, markerOptions);
           
marker.addEventListener(MapMouseEvent.CLICK, handleMarkerClick);
           
map.addOverlay(marker);

The problem with using a bitmap that includes transparency is that it captures mouse events even when hovering over the transparent areas. In one map I designed, I’m using billboard markers that contain quite a lot of transparent space. Even when it looks like I’m hovering over the icon in the background, all the mouse events are being gobbled up by the billboard. You can see this in the following screenshot by looking at the tooltip.

I noticed that if I used a custom-drawn Sprite using the graphics drawing commands, the mouse only captures events where I’ve actually drawn something.

In order to solve this problem, I created a custom BitmapSprite class that accepts bitmapData, converts it to one pixel tall rectangle drawing commands, and then added a sharpening filter to clean up the result (since vectorizing the bitmap seemed to have a blurring effect). Adding a filter at the end also has the added benefit of turning on cacheAsBitmap which improves map performance.

BitmapSprite.as

public class BitmapSprite extends Sprite
{
    public function BitmapSprite(data:BitmapData)
    {
        super();
           
        var x:uint;
        var y:uint;
        var alpha:uint;
        var color:uint;
        var width:uint;
        var fillDict:Dictionary = new Dictionary();
        var rectVect:Vector.<Object>;
        var pixelIndex:uint = 0;
        var pixelData:Vector.<uint> = data.getVector(data.rect);
           
        //calculate
        for(y=0; y < data.height; y++)
        {
            for (x=0; x < data.width; x++)
            {
                alpha = pixelData[pixelIndex] >> 24 & 0xFF;
                   
                //only draw pixels that are visible
                if(alpha > 0)
                {
                    color = pixelData[pixelIndex] & 0xFFFFFF;
                    width = 1;
                    if(pixelIndex+1 < pixelData.length)
                    {
                        while(pixelData[pixelIndex] == pixelData[pixelIndex+1])
                        {
                            width++;
                            pixelIndex++;
                            if(pixelIndex+1 == pixelData.length)
                                break;
                            if(x + width == data.width)
                                break;
                        }
                    }
                    rectVect = fillDict[pixelData[pixelIndex]];
                    if(rectVect == null)
                    {
                        rectVect = new Vector.<Object>();
                        fillDict[pixelData[pixelIndex]] = rectVect;
                    }
                       
                    rectVect.push({x: x, y: y, width: width, color: color, alpha: alpha / 255.0});
                       
                    x += width-1;
                }
                else if(pixelIndex == 0 || pixelIndex == pixelData.length-1)
                {
                    //this is either the first or last pixel.
                    //draw it anyway even though it's invisible
                    //so our resulting Sprite has the correct dimensions.
                    rectVect = fillDict[pixelData[pixelIndex]];
                    if(rectVect == null)
                    {
                        rectVect = new Vector.<Object>();
                        fillDict[pixelData[pixelIndex]] = rectVect;
                    }
                    rectVect.push({x: x, y: y, width: width, color: color, alpha: 0});
                }
                pixelIndex++;
            }
        }
           
        //do drawing
        graphics.clear();
        for each(rectVect in fillDict)
        {
            var rectData:Object = rectVect[0];
            graphics.beginFill(rectData.color, rectData.alpha);
               
            for each(rectData in rectVect)
            {
                //do a rectangle
                graphics.drawRect(rectData.x, rectData.y,rectData.width,1);
            }
            graphics.endFill();
        }
    }
}

Usage:

var bmp:Bitmap = loader.content as Bitmap;
var bs:BitmapSprite = new BitmapSprite(bmp.bitmapData);
var sharpen:ConvolutionFilter = new ConvolutionFilter(3, 3, [0, -1, 0, -1, 10, -1, 0, -1, 0], 6);
bs.filters = [sharpen];

var markerOptions:MarkerOptions = new MarkerOptions(
    {
        icon: bs,
        iconAlignment: MarkerOptions.ALIGN_BOTTOM | MarkerOptions.ALIGN_HORIZONTAL_CENTER,
        hasShadow: true,
        tooltip: member.name + " - " + member.phone
    }
); 
           
var marker:Marker = new Marker(latLng, markerOptions);
       
marker.addEventListener(MapMouseEvent.CLICK, handleMarkerClick);
           
map.addOverlay(marker);

After making these changes, map performance doesn’t seem to be affected except for a small increase in the initial load time for each marker. The results speak for themselves.

Post to Twitter

Posted by Andrew, filed under Uncategorized. Date: November 17, 2010, 3:00 pm | 1 Comment »