Requirements

Prerequisite knowledge
General familiarity with Flex and ActionScript 3.
 
User level
Intermediate
Required products
Flash Builder 4 (Download trial)

 
Additional Requirements

 
Modest Maps ActionScript 3 release
It has never been easier to build interactive online maps with Flex. There is a wide range of APIs now available. The focus of this article is building a simple map application using the open-source Modest Maps Flex API. Modest Maps offers a number of advantages over other Flex mapping APIs including the ability to point at multiple basemap providers and direct access to the source code. In this article, you will first learn how to build a basic interactive map with zoom and pan functionality. You will then extend the application to include multiple providers and markers. Working examples of the application can be accessed at different stages of development via links referenced in the text.
 

 
Getting started

Before you start coding, you can try the sample application for this section. To view the source, right-click the map and select View Source.
 
After installing Flash Builder 4 and downloading the Modest Maps AS3 archive, open Flash Builder 4 and create a Flex project called mymodestmap. Unzip the contents of the Modest Maps AS3 archive. Copy and paste the com and gs directories from the lib directory in the archive to the src directory of  your mymodestmap Flex project. You could use the supplied SWC, but for this exercise  use the actual source. Now open mymodestmap.mxml, which is in the src/(default package) directory of our Flex project. The first steps are to add an init() function that is called when the application's creation is complete, a container for the map, and some import statements. You can copy the following code into mymodestmap.mxml:
 
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768" creationComplete="init()"> <fx:Script> <![CDATA[ import mx.core.UIComponent; import com.modestmaps.TweenMap; import com.modestmaps.mapproviders.OpenStreetMapProvider; import com.modestmaps.geo.Location; private function init():void { } ]]> </fx:Script> <mx:Canvas id="mappanel" width="100%" height="97%"/> </s:Application>
Your first map will be accessing the OpenStreetMap open source basemap. You'll add this as a UIComponent to the mappanel canvas. You'll need to add some declarations and the initial latitude (lat), longitude (long), and zoom level. The lat/long marks the center of the map. To find the lat/long of a location, there is an excellent tool you can use: Google Maps Latitude, Longitude Popup.
 
The Map class is at the core of the Modest Maps API. The code below creates an instance of TweenMap, which extends Map. The new TweenMap is given a height, width, and a provider. TweenMap is a recent addition to the code base. It improves the fading of tiles and zoom in and out around a fixed point. Next, the code sets the map center and zoom of the TweenMap instance. Finally, the mapCore() function creates the display object hierarchy. Copy the following code to mymodestmap.mxml:
 
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768" creationComplete="init()"> <fx:Scrip> <![CDATA[ import mx.core.UIComponent; import com.modestmaps.TweenMap; import com.modestmaps.mapproviders.OpenStreetMapProvider; import com.modestmaps.geo.Location; [Bindable]private var _map:TweenMap; private var _mapUI:UIComponent; [Bindable]private var _initialLat:Number = 40.668903; [Bindable]private var _initialLong:Number = -111.680145; [Bindable]private var _initialZoom:Number = 6; private function init():void { _map = new TweenMap(mappanel.width, mappanel.height, true, new OpenStreetMapProvider()); _map.setCenterZoom(new Location(_initialLat, _initialLong), _initialZoom); mapCore(); } private function mapCore():void { _mapUI = new UIComponent(); _mapUI.addChild(_map); mappanel.addChild(_mapUI); } </fx:Script> <mx:Canvas id="mappanel" width="100%" height="97%"/> </s:Application>
Now run the application. There you have it—a map of the Western United States. The map can also be panned, in the same way you pan Google Maps. Try it. Welcome to the world of slippy maps.
 

 
Adding zoom

So far so good. You have created a basic interactive map. The next step is to add a zoom capability. There are a number of ways this can be done. Modest Maps comes with a several controls that you can use. This example uses the zoom slider and also enables mouse wheel zoom. You can implement this by adding to the existing code. For the zoom slider, add the following to the import statements:
 
import com.modestmaps.extras.ZoomSlider;
Add the following lines to the mapCore() function:
 
var zoomslider:ZoomSlider = new ZoomSlider(_map); zoomslider.x = mappanel.width - zoomslider.width*2; zoomslider.y = 30; _mapUI.addChild(zoomslider);
It is not the most stylish slider, but you can easily rectify that by accessing and editing the source.
 
To enable the mouse wheel zoom you will need to add more code and a new function. Add this to the declarations:
 
private var _mouseWheelZoomCenter:Location; private var _mouseWheelZoom:int;
To handle any mouse wheel events, add a listener in the mapCore() function:
 
_map.addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);
The event handler is below:
 
private function mouseWheelHandler(e:MouseEvent):void { if (e.delta < 0) { _map.zoomOut(); } else if (e.delta > 0) { _map.zoomIn(); } _mouseWheelZoomCenter = _map.getCenter(); _mouseWheelZoom = _map.getZoom(); }
This code tracks the direction of the mouse wheel and zooms accordingly. I like a hand icon when I pan. To add one, you first need an additional import statement:
 
import com.modestmaps.extras.HandCursor;
Then add it to the mapCore() function as follows:
 
var handcursor:HandCursor = new HandCursor(map); mapUI.addChild(handcursor);
Your code should now look like this:
 
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768" creationComplete="init()"> <fx:Script> <![CDATA[ import mx.core.UIComponent; import com.modestmaps.TweenMap; import com.modestmaps.mapproviders.OpenStreetMapProvider; import com.modestmaps.geo.Location; import com.modestmaps.extras.ZoomSlider; import com.modestmaps.extras.HandCursor; Bindable]privatevar _map:TweenMap; private var_mapUI:UIComponent; [Bindable]private var_initialLat:Number = 40.668903; [Bindable]private var_initialLong:Number = -111.680145; [Bindable]private var_initialZoom:Number = 6; private var_mouseWheelZoomCenter:Location; private var_mouseWheelZoom:int; private var_mouseWheelZoomCenter:Location; private var_mouseWheelZoom:int; private function init():void { _map = new TweenMap(mappanel.width, mappanel.height, true, new OpenStreetMapProvider()); _map.setCenterZoom(new Location(_initialLat, _initialLong), _initialZoom); mapCore(); } private function mapCore():void { _map.addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler); _mapUI = new UIComponent(); _mapUI.addChild(_map); var zoomslider:ZoomSlider = new ZoomSlider(_map); zoomslider.x = mappanel.width - zoomslider.width*2; zoomslider.y = 30; _mapUI.addChild(zoomslider); var handcursor:HandCursor = new HandCursor(_map); _mapUI.addChild(handcursor); mappanel.addChild(_mapUI); } private function mouseWheelHandler(e:MouseEvent):void { if (e.delta < 0) { _map.zoomOut(); } else if (e.delta > 0) { _map.zoomIn(); } _mouseWheelZoomCenter = _map.getCenter(); _mouseWheelZoom = _map.getZoom(); } </fx:Script> <mx:Canvas id="mappanel" width="100%" height="97%"/> </s:Application>
Run the application. Try the slider and mouse wheel. Notice how the pan hand cursor grips as you mouse down and pan.
 
Zoom into West Valley City. Notice the amazing detail. I say "amazing" since OpenStreetMap is a purely voluntary effort. The mapping is being done by people like you and me. I have no association with OpenStreetMap, but I encourage you to go to their web site and get involved.
 

 
Extending the application

You can try a working example and download the code for this section.
 
In this section, you'll extend the code for the application you just built. In this section you'll give the user the ability to switch basemap providers and add points of interest, better known as markers. In this case, the application will load XML data and add markers to the map based on data from this XML. In addition, the application will display a draggable information box, actually a Flex TitleWindow. The information will be updated after a user clicks a marker.
 
 
Dynamically changing basemap providers
Currently the application displays vector roads data provided by OpenStreetMap. Now you'll add satellite imagery from Microsoft Virtual Earth. Inside of your mymodestmap project create a new Flex MXML application and name it myadvancedmodestmap. Copy and paste the code in mymodestmap.mxml to myadvancedmodestmap.mxml, and then add the following import statement:
 
import com.modestmaps.mapproviders.yahoo.YahooHybridMapProvider;
Add two buttons to allow the user to switch basemap providers. Insert this code below the mappanel canvas:
 
<mx:HBox width="100%" horizontalAlign="right"> <mx:Button label="OpenStreetMap" click="handleChangeProvider(event)"/> <mx:Button label="Virtual Earth" click="handleChangeProvider(event)"/> </mx:HBox>
To handle the button clicks, include the following handler function for switching providers. First handleChangeProvider() removes the previous children of mapanel, and then it repopulates the container.
 
private function handleChangeProvider(event:Event):void { mappanel.removeAllChildren(); if(event.target.label == "Virtual Earth") { _map = new TweenMap(mappanel.width, mappanel.height, true, new YahooHybridMapProvider()); } else { _map = new TweenMap(mappanel.width, mappanel.height, true, new OpenStreetMapProvider()); } if(_mouseWheelZoomCenter != null && _mouseWheelZoom > 0) { _map.setCenterZoom(_mouseWheelZoomCenter, _mouseWheelZoom); } else { _map.setCenterZoom(new Location(_initialLat, _initialLong), _initialZoom); } mapCore(); }
When you run the application you should now be able to switch between basemap providers.
 
 
Adding markers
Interactive maps contain markers to highlight points of interest (POI). It is easy to add markers by pulling data from a georeferenced feed such as RSS or geoRSS. For simplicity, this example will create markers using XML data provided in the declarations section as follows:
 
private var srcXML:XML = <root> <location> <name>Alta Ski Resort</name> <lat>40.588063</lat> <long>-111.637659</long> <description>Home of the Greatest Snow on Earth</description> </location> <location> <name>Crested Butte Resort</name> <lat>38.899932</lat> <long>-106.964249</long> <description>Amazing skiing with small town charm</description> </location> </root>;
To add markers, you'll need to create a new Actionscript class. In the src/com directory create a new directory named custom. In the custom directory create a new Actionscript class that extends Sprite and name it PointMarker. This class is a simple Sprite, which contains setters and getters and to which you will add a Bitmap. The class is as follows:
 
package com.custom { import mx.core.UIComponent; import flash.display.Sprite; public class PointMarker extends Sprite { private var _nName:String; private var _nMarker:String; private var _nDescription:String; public function set resortName(name:String):void { _nName = name; } public function get resortName():String { return _nName; } public function set resortDesc(description:String):void { _nDescription = description; } public function get resortDesc():String { return _nDescription; } } }
Now return to myadvancedmodestmap.mxml to load the XML data, create markers for each location, and add them to the map. In myadvancedmodestmap.mxml, import the new class:
 
import com.custom.PointMarker;
Add a new declaration:
 
private var resortMarker:PointMarker;
Add a new function called addMarkers() to place the markers on the map:
 
private function addMarkers():void { for each (var resort:XML in srcXML.location){ resortMarker = new PointMarker(); var markerImage:Bitmap = new MarkerImage() as Bitmap; markerImage.x = -markerImage.width/2 markerImage.y = -markerImage.height/2 resortMarker.addChild(markerImage); resortMarker.resortName = resort.name; resortMarker.resortDesc = resort.description; _map.putMarker(new Location(resort.lat, resort.long), resortMarker); } }
Call addMarkers() from the mapCore() function:
 
addMarkers();
Lastly, just above the init() function embed the marker:
 
[Embed(source="img/marker.png")] protected var MarkerImage:Class;
You'll need to create an img directory under src. Create an image called marker.png and add it to this directory or download the source image from the FlexMappers site.
 
There you have it. Run the application and you should see Alta and Crested Butte ski resorts marked on the map.
 
 
Adding a marker information box
As a final step you will add code to display marker information in a draggable information box. To do this, you will need to create a new MXML component and add listeners to the markers. In the com/custom directory add a new MXML component called InfoPane. Here is the class code for the component, which is a TitleWindow. It simply displays labels in the TitleWindow, and updates them when a user clicks a marker.
 
<?xml version="1.0" encoding="utf-8"?> <mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" width="300" height="300" creationComplete="init()" alpha="0.7"> <mx:Script> <![CDATA[ import flash.geom.Rectangle; private var _containerheight:Number; private var _containerwidth:Number; [Bindable] private var _ntitle:String = "Welcome"; [Bindable] private var _ndesc:String = "My first Flex interactive map"; private function init():void{ this.titleBar.addEventListener(MouseEvent.MOUSE_DOWN, moveWindow); this.titleBar.addEventListener(MouseEvent.MOUSE_UP, stopWindow); } public function set ptitle(value:String):void{ _ntitle = value; } public function set pdesc(value:String):void{ _ndesc = value; } public function set containerWidth(width:Number):void{ _containerwidth = width; } public function set containerHeight(height:Number):void{ _containerheight = height; } private function moveWindow( evt:MouseEvent ):void{ var limitdrag:Rectangle = new Rectangle(0, 0, _containerwidth-this.width, _containerheight-this.height); this.startDrag(false, limitdrag); } private function stopWindow( evt:MouseEvent ):void{ this.stopDrag(); } ]]> </mx:Script> <mx:VBox horizontalAlign="center" width="100%" height="100%" verticalAlign="middle"> <mx:Label id="ntitle" text="{_ntitle}" fontSize="16"/> <mx:Label id="ndesc" fontSize="12" text="{_ndesc}"/> </mx:VBox> </mx:TitleWindow>
Finally, update myadvancedmodestmap.mxml to import the new component and add a declaration:
 
import com.custom.infoPanel; private var _panel:infoPanel;
In the mapCore() function add the new component and size it:
 
_panel = new infoPanel(); _panel.containerHeight = mappanel.height; _panel.containerWidth = mappanel.width; mappanel.addChild(_panel);
In the addMarkers() function add the listener at this point to addMarkers function:
 
resortMarker.addEventListener(MouseEvent.CLICK, handleMouseClick);
Finally, add one more new function to handle marker clicks:
 
private function handleMouseClick(event:MouseEvent):void { _panel.ptitle = event.target.resortName; _panel.pdesc = event.target.resortDesc; }
That's all there is to it. Run the application. In the top left you should now see an information box. The alpha is set to 0.7 so the map can be seen through the panel. You can also drag the panel within the bounds of the map. Click on a marker and see the panel update with marker-specific information.
 

 
Where to go from here

Modest Maps and OpenStreetMap are fully open source. This article walked you through the creation of an open source mapping application using these sources. It's the start of a journey. The field of online mapping and the GeoWeb is still in its infancy, but it's growing quickly. It is an exciting and broad field.
 
Here are a few ideas for extending the application:
 
  • Style the zoom slider
  • Add a listener and handler for marker MouseOver and display a label next to the marker.
  • When the user pans, the application does not store the current center of the map. Switching providers does not reflect the previous center point. What's the fix?
  • Add georeferenced imagery and video.
Here are some of the resources you can refer to: