Home > Flex, Google Maps API, tutorial > Intro to Google Maps API for Flex

Intro to Google Maps API for Flex

February 18th, 2009

Google Maps has proven itself to be one of the best mapping systems on the internet. We have also seen a great number of google maps mashups take place (Everything some Subway maps to where is the cheapest gas). I only felt it would be nice to have an intro on how to use the google maps API with in Flex. On that note, it has been four posts since I have done a How-To article, so this is even more fitting. Within this tutorial, I will just go over some basics on how to add and edit Markers on a map and keep track of them in a grid.

First thing we need to do is register for an API key*. All the instructions on how to do this are right here. Next you are also going to need to download the library for it here. You are going to want the map_flex_1_8c.swc. The other file is for AIR, so if you want to make an AIR app, use that one. The source I will include will contain a full eclipse project (libraries and all) but for detailed instructions, use this. It might be a good idea to follow that to create a ‘helloworld’ type program just to make sure you are all set.

Setting up your map is surprisingly easy. With just a few lines, the map can be inserted into your program.

<mx:Hbox>
    <maps:Map xmlns:maps="com.google.maps.*"
 	id="map" mapevent_mapready="onMapReady(event)" 
	width="100%" 
	height="100%" 
	key="{GoogleConstants.KEY}"/>
 </mx:Hbox>

I placed the key inside of a constants file just so I dont have to see that long beast everywhere. Obviously there is the mapevent_mapready event being called (think of that as the creationComplete event). There really isnt too much at the moment for that event.

public function onMapReady(event:MapEvent):void {
    map.setCenter(new LatLng(40.665226,-73.984659), 14, MapType.NORMAL_MAP_TYPE);
}

The map.setCenter function takes in the default latitude and longitude that it will settle over (Brooklyn, NY in this case), the default zoom level, and which map type it will be. Once you run that, you will have a nice map sitting in the middle of Brooklyn. Now that this part is out of the way, lets start adding some meat to this program.

We have our map and it is looking pretty boring. Our end result is going to be a map that starts off with a few markers. On the left side of the map, there is going to be a datagrid. The grid will be loaded with our marker data and when we click the grid, an information window on the map will appear with its name.

Feel free to skip this if you already know what a marker is. A marker is one of the little red images that points to a specific location on the map. Every single marker has to be given a latitude and longitude (in the form of the LatLng object) and it looks something like this:

var latlng:LatLng = new LatLng(md.lat,md.lng);
var marker:Marker= new Marker(latlng);
map.addOverlay(marker);

Welcome back.Right off the bat, I am going to show you the mxml part of it. It will just help explain what this will look like at the end. Through out the rest of the article, I will explain this in more detail.

<mx:HBox width="100%" height="100%">
    <mx:VBox height="100%">
    <mx:RadioButtonGroup id="rbg"/>
    <mx:VBox>
        <mx:RadioButton id="edit" label="Add new markers" group="{rbg}"/>
        <mx:RadioButton id="view" selected="true" label="View Mode" group="{rbg}"/>
     </mx:VBox>
     <mx:HBox>
         <mx:Button label="Zoom in" click="zoomInEvent(event)" />
         <mx:Button label="Zoom out" click="zoomOutEvent(event)" />
     </mx:HBox>    
     <mx:DataGrid id="grid" 
                        itemClick="gridClicked(event)" 
                        dataProvider="{dataProvider}" 
                        width="100%" 
                        height="100%">
                <mx:columns>
                    <mx:DataGridColumn>
                        <mx:itemRenderer>
                          <mx:Component>
                              <mx:Label text="{data.markerData.name}"/>
                          </mx:Component>
                        </mx:itemRenderer>
                  </mx:DataGridColumn>
                </mx:columns>
            </mx:DataGrid>
        </mx:VBox>
 
    <maps:Map xmlns:maps="com.google.maps.*"
 	id="map" mapevent_mapready="onMapReady(event)" 
	width="100%" 
	height="100%" 
	key="{GoogleConstants.KEY}"/>    
    </mx:HBox>

application
As you can see, there will be some controls and a grid on the left hand side, then the map will be on the right hand side. One set of controls will be for zooming in and out and the other set will control whether we are viewing the map or adding new markers to the map. Now lets begin dropping in the actionscript and events to handle our desired functionality.

So here is our default data provider that I will be feeding into both the map and the datagrid.

public static const DEFAULTMARKERS:ArrayCollection = new ArrayCollection([{markerData:new MarkerData(40.6734284,-73.9827536,"Loki")},
                                                          {markerData:new MarkerData(40.6762938,-73.9801574,"Union Hall")},
                                                          {markerData:new MarkerData(40.679327,-73.9816741, "Sheep Station")},
                                                          {markerData:new MarkerData(40.6673291,-73.9878376,"Common Wealth")},
                                                          {markerData:new MarkerData(40.6686494,-73.9852173,"Dram Shop")},
                                                         ]);

Here is a list of some bars within the Brooklyn area. These are going to our default markers and datgrid rows when the application starts. Here is what our onMapReady event will look like:

public function onMapReady(event:MapEvent):void {
     map.setCenter(new LatLng(40.665226,-73.984659), 14, MapType.NORMAL_MAP_TYPE);
     map.addEventListener(MapMouseEvent.CLICK,mapClicked);
     for each (var o:Object in GoogleConstants.DEFAULTMARKERS){
         var md:MarkerData = o["markerData"] as MarkerData;
         var latlng:LatLng = new LatLng(md.lat,md.lng);
         var marker:Marker= new Marker(latlng);
         addMarker(md,marker);
     }
}

The MarketData object is nothing more than an Object that holds the latitude, longitude and name of a marker. Our center is set. We also add and eventlistener for when we click on it. This is what we are going to use to add markers. After that, we just take our defaults and add them. This is all the addMarker function does:

public function addMarker(markerData:MarkerData, marker:Marker):void{
    var o:Object = {markerData:markerData,marker:marker};
    marker.addEventListener(MapMouseEvent.CLICK,markerClicked);
    marker.addEventListener(InfoWindowClosedEvent.NAME,closed);
    markerData.marker = marker;
    map.addOverlay(marker);
    dataProvider.addItem(o);    
    dataProvider.refresh();    
}

We pass in both the markerData and the marker object itself. Then we create our object that will be used for grids dataprovider. The marker object is given both a MapMouseEvent and InfoWindowClosedEvent to listen to. The MapMouseEvent is what is fired off whenever you phsyically click the marker on the map. The InfoWindowClosedEvent is a custom event that I will discuss shortly. We then add the marker to the map and add the object to the grids dataprovider.

So far we have a datagrid and map loaded with our defaults. Now I want to make a window pop up above the marker whenever we click on either the marker or the row in the datagrid. When we do, it will look like this. In the addMarker function, we added our MapMouseEvent event listener, so lets look at that.

public function markerClicked(event:MapMouseEvent):void{
    var marker:Marker = event.target as Marker;
    var inputBox:InputBox = new InputBox();
    inputBox.marker=marker;
    var o:Object = MapUtils.getMarkerDataByLatAndLng(marker.getLatLng().lat(), marker.getLatLng().lng(), dataProvider);
    if (o != null){
        inputBox.textBox.text = o["markerData"].name;
        marker.openInfoWindow(new InfoWindowOptions({customContent:inputBox,width:200,height:50,drawDefaultFrame:true}));
    }
}

I will show you what the InputBox looks like after I expalin what is going on. First we get the marker we clicked on from the event. We then set our inputBox (this will be the UIComponent displayed whenever we click on a marker). I whipped up a utility function that will get the name of the marker by its latitude and longitude. We get it and set that to be displayed and then call marker.openInfoWindow. This will display our customContent (the InputBox in this example) as a width of 200 and height of 50. Finally to clear the confusion, this is the InputBox.

public class InputBox extends HBox{
    public var textBox:TextInput;
    public var marker:Marker;
    public var markerData:MarkerData;
    public var button:Button;
 
    public function InputBox(){
        width=300;
        height=25;
        textBox = new TextInput();
        button = new Button();
        button.label = "Save";
        button.addEventListener(MouseEvent.CLICK,closeOverLay);
        addChild(textBox);            
        addChild(button);
    }
    public function closeOverLay(event:MouseEvent):void{
        var e:InfoWindowClosedEvent = new InfoWindowClosedEvent(textBox.text);
        marker.closeInfoWindow();
        e.marker = marker;
        marker.dispatchEvent(e);
     }
}

It is just a simple HBox with a TextInput and a save button and it should look like this:
inputbox

Whenever you click the save button, it will call the closeOverLay event which closes the window and dispatches an event with the updated information attached to it.
Our gridClick is going to look strikingly similar to our markerClick.

public function gridClicked(event:ListEvent):void{
    var o:Object= dataProvider.getItemAt(event.rowIndex) as Object;
    var marker:Marker = o.marker;
    var inputBox:InputBox = new InputBox();
    inputBox.textBox.text = o["markerData"].name;
    inputBox.marker =marker
    marker.openInfoWindow(new InfoWindowOptions({customContent:inputBox, width:200, height:50, drawDefaultFrame:true}));
}

Once again, we get our marker, then set our data on the InputBox and open up the new InfoWindow. Pretty similar.

We have now reached the point to where we have our grid and our map, both loaded with data. Except now we are able to click on either a row or a marker and get an information window with the name of the bar. However, our InputBox has a save button and a TextInput. Let’s begin dropping in the functionality of adding new markers and edited existing ones.

In our onMapReady event, we added a MapMouseEvent.CLICK, which takes care of adding new markers.

public function mapClicked(event:MapMouseEvent):void{
    if(!view.selected){
        var latLng:LatLng = event.latLng;
        var marker:Marker = new Marker(latLng);
        var inputBox:InputBox = new InputBox();
        inputBox.marker = marker;
        marker.addEventListener(MapMouseEvent.CLICK,markerClicked);
        marker.addEventListener(InfoWindowClosedEvent.NAME,closed);
        map.addOverlay(marker);
        marker.openInfoWindow(new InfoWindowOptions({customContent:inputBox,width:200,height:50,drawDefaultFrame:true}));
    }
}

Not too much is different here. First we make sure we are not in view mode (via the radio button controls). Next, grid the latLng from the event and create the marker from there. Then add the event listeners for both the click and close. Add it to the maps overlays and open the InfoWindow. However, this just adds a new marker on the map. How do we get that data saved and into our grid? See the InfoWindowClosedEvent? This is a custom event that created due to the fact that A) I could not find an eventlistener for it and B)needed something to save the name of the marker.

public class InfoWindowClosedEvent extends Event{
    public static const NAME:String = "com.codeofdoom.events.InfoWindowClosedEvent";
    private var _inputName:String;
    private var _marker:Marker;
    public function InfoWindowClosedEvent(inputName:String){
        _inputName = inputName;
        super(NAME);
    }
    public function get inputName():String{
        return _inputName;
    }
    public function set marker(marker:Marker):void{
        _marker = marker;
    }
    public function get marker():Marker{
        return _marker;
    }
}

Here is the function that handles this event.

public function closed(event:InfoWindowClosedEvent):void{
     var marker:Marker = event.marker;
     var latLng:LatLng = marker.getLatLng();
     var markerData:MarkerData = new MarkerData(latLng.lat(),latLng.lng(),event.inputName);
     var o:Object = MapUtils.getMarkerDataByLatAndLng(latLng.lat(),latLng.lng(),dataProvider);
     if (o == null){
          addMarker(markerData,marker);
     }else{
          o["markerData"] = markerData;
          dataProvider.refresh();
     }
}

In this function, we get the marker that was attached to the event. We then use our utility function to check if that marker already exists. If it does, we are obviously in edit mode. We set the new markerData and refresh our dataprovider. If it does not, its new, so we just pass that to our addMarker function. Now we are set to add more bars and edit existing ones!

Lastly, while it is not detrimental to the basic functionality, I will include the code to handle zooming in and out.

public function zoomInEvent(event:Event):void{
     map.zoomIn();
}
public function zoomOutEvent(event:Event):void{
     map.zoomOut();
}

As you may have noticed, this example consisted primarily of eventListeners. Further more, you can quickly tell its pretty simple to add some in depth use to this. I wanted to put together a somewhat complex example just to show how easy it is to make something extremely useful. With flex becoming a more and more prominent player in the market, learning something as easy to use and as powerful as the google maps api can ensure you will make your rich internet applications even richer. Feel free to leave a comment!

Here is the source. If you would like to see the application running, check it out

**For for your API key, you are able to register it under any domain name/directory you want and you will get the key to run locally. However, when you attempt to publish it online, that is when it checks your domainname/directory structure.

PS
Feel free to check out my second arcticle on the Google Maps API

Flex, Google Maps API, tutorial , , ,

  1. February 18th, 2009 at 13:20 | #1

    Great application. Will share it at http://www.beedigital.net/blog

  2. February 18th, 2009 at 13:55 | #2

    Great write up. Thanks.

  3. Ted
    April 2nd, 2009 at 10:09 | #3

    This is a great article and i was able to get your example up and running quickly. I am trying to provide my own data via external XML file. I’ve got my XML writing to an array collection. Is there an example you could provide to help me get this into the MarkerData?

    Thanks!

  4. June 10th, 2009 at 07:46 | #4

    Great article! I’m loving your website;

  5. abhishek
    June 24th, 2009 at 00:45 | #5

    hello friend,
    Its a very nice article,
    could you just add search part in this?
    :)
    abhishekchess1@gmail.com

  6. abhishek
    June 24th, 2009 at 00:57 | #6

    how can we remove markers here
    abhishekchess1@gmail.com

  7. skwasha
    August 28th, 2009 at 19:12 | #7

    Trying to make this work in an air app and I keep getting “Incompatible override…” errors where the mxml file does: “public function closed(event:InfoWindowClosedEvent):void{…”

    Any ideas?

  8. Tom
    November 4th, 2009 at 17:12 | #8

    “You are going to want the map_flex_1_8c.swc. The other file is for AIR, so if you want to make an AIR app, use that one”
    This is not correct. Use the map_flex_* for AIR applications as well
    I guess the other one is to use within Flash.
    I had the error “component declarations are not allowed here” when using the wrong library.

  9. November 5th, 2009 at 17:43 | #9

    Hello, first of all I want to thank you for this tutorial.
    I have a question: Can you please show me how I can put a tex box instead of the text input? I have only 4 markers and I want to create a text box for each one. Can you please help me? I`m really new with adobe flex…

  10. Jay
    November 13th, 2009 at 18:46 | #10

    Hey guys, I keep getting an error when init geocoder. I am trying to create a .as file and in that I have to following code


    private var geocoder:ClientGeocoder;
    ..
    ..
    public function setMap():void{
    geocoder = new ClientGeocoder();
    gc.addEventListener(GeocodingEvent.GEOCODING_SUCCESS, geocoder_geocodingSuccess);
    gc.addEventListener(GeocodingEvent.GEOCODING_FAILURE, geocoder_geocodingFailure);
    gc.reverseGeocode(new LatLng(lat,lon));
    }
    ..
    ..

    but I keep getting an error

    TypeError: Error #1009: Cannot access a property or method of a null object reference.

    when i say geocoder = new ClientGeocoder();

    Any Ideas?? I have even tried

    geocoder = new ClientGeocoder(new ClientGeocoderOptions(…..));

    but the result is the same. I am using map_flex_1_17.swc.

    Thank you
    Jaysheel

  11. George Marley
    November 17th, 2009 at 05:50 | #11

    Nice Article! Very well written!

  12. AZUbius
    January 14th, 2010 at 11:48 | #12

    Hi, first off, thank you immensely for a well written up article.

    I’m new to AS, and need a little help with an error i’m encountering from some of the snippets of code you posted, in particular these lines:

    public class InputBox extends HBox{
    public class InfoWindowClosedEvent extends Event{

    Flex Builder tells me “Classes must not be nested”, but when i try declaring a package first, ie

    package {
    public class InputBox extends HBox{

    I still get an error.

    Any help you can give as soon as possible would be appriciated.

  13. Andy
    July 23rd, 2010 at 01:45 | #13

    Hey this is awesome! Can you give me a couple pointers on how to hook the ArrayCollection DEFAULTMARKERS instead to a PHP/mySQL database? I’ve been banging my head against a wall for 2 weeks now trying to figure this out…. new to Flex and AS…. Thanks!

  14. Sanna
    October 6th, 2010 at 07:23 | #14

    Hi,

    how do I add several Markers and zoom back enough so that I have see the markers on my map? Thanks, Sanna

  15. April 7th, 2011 at 05:50 | #15

    Sir please tell me that if i click in google map that should display the location details……..
    Plz sir please help me……………………………………….

  16. jasmanik
    July 13th, 2011 at 11:02 | #16

    I tried integrating this is my code, its giving the “error 1000: Ambiguous reference to addOverlay”. The code has two addOverlay functions one is of UIcomponent. Can you please suggest some help.

  17. Vikash
    August 19th, 2012 at 00:40 | #17

    This is just tooo goood example .

  18. Vikash
    September 5th, 2012 at 16:33 | #18

    Hi ,
    This example is a great one . However , I dont have a Google Maps key for Flex . Since they have discontinued , and still i want to use it , can someone please lend me his key if he is not using it anymore ??

  1. February 18th, 2009 at 10:10 | #1
  2. February 20th, 2009 at 13:04 | #2