Home  >  

Developing Mashup Air Apps: Yahoo Maps Web Services

Author photo
AddThis Social Bookmark Button

This is excerpted from Chapter 18 of the Adobe AIR 1.5 Cookbook by David Tucker, Marco Casario, Koen De Weggheleire, and Rich Tretola. The book includes hands-on recipes to help you solve a variety of tasks and scenarios often encountered when using Adobe AIR to build Rich Internet Applications for the desktop.

Get the Adobe AIR 1.5 Cookbook or download the PDF of this entire chapter.

Consuming Yahoo Maps Web Services

Problem

You want to consume Yahoo Maps (http://developer.yahoo.com/maps/) web services.

Solution

Yahoo Maps web services allow you to integrate interactive map systems in AIR desktop applications. Specifically, the Yahoo Maps APIs enable you to work with map applications and add more features to them, adding value to your applications. You can find loads of useful services on the Web that consume Yahoo APIs to create rich user experiences:

Upcoming.org Events

Robert Oschler uses the Yahoo Maps JS-Flash API to display Upcoming.org events along with traffic, ATMs, bars, and restaurants.

Instant Local Search and Traffic Plotter

This application combines Yahoo Maps’ Ajax with three new Yahoo REST services: Geocoding, Local Search, and Traffic. It also comes with explanations on what needs to be done to write an Ajax API mashup, which is a great way to get started.

Local Events Browser

An innovative group of Yahoo employees pushed Ajax to new levels with the Local Events Browser that blends multiple Yahoo APIs to put events on an interactive map with useful features such as a calendar, tag cloud, and built-in image search.

Flickr Maps

For cities across the United States, Michael Hoch puts Flickr photos on Yahoo Maps using the Flex API. This application is another great example of how the Yahoo Maps APIs give you full control over the look and feel of your mapping application.

You can find a list of Yahoo Maps mashups at http://developer.yahoo.com/maps/applications.html.

In this recipe, you will learn how to consume Yahoo Maps web services to create mashups in AIR.

Discussion

HTML/JavaScript

Yahoo provides two kind of APIs, according to the programming language you use:

  • The Ajax API is designed to use DHTML and JavaScript to host maps. Yahoo provides the JavaScript functions to make mapping a breeze.

  • The ActionScript 3 Flash APIs are designed to embed a map into your website or desktop application using the ActionScript 3 Maps API with Adobe Flash and Flex.

This dual possibility is a perfect match with AIR, which allows you to integrate Ajax as well as ActionScript applications.

Before beginning to write any code, you have to register at http://developer.yahoo.com/wsregapp to obtain an application ID.

Using Ajax, the first step to take is to import the Yahoo Maps Ajax API library in your web page by accessing the API’s remote JavaScript script:

<script type="text/javascript" 
	src="http://api.maps.yahoo.com/ajaxymap?v=3.8&appid=YOUR-APP-ID">

</script>

The Ajax API isn’t on your server and isn’t installed. Once the JavaScript file that is specified in the <script> tag is loaded, the web page is ready to use the maps.

At this point, you can create a container in the web page to display the map:

<body>
<div id="Ymap"></div>
OTHER CONTENT
 </body>

All you have to do now is add the functions and controls to the Map objects with some JavaScript code:

<style type="text/css">

    #YMap
    {
     height: 400px;
     width: 500px;
     }
</style>
<script type="text/javascript">

    var YMap

    function initYMap()
    {
         YMap = new YMap(document.getElementById('YMapDiv'));
         YMap.addPanControl();
         YMap.addTypeControl();
         YMap.addZoomLong();
         YMap.drawZoomAndCenter("New York", 5);
     }

     window.onload = initYMap;
 </script>

</head>

 <body>

 <div id="YMapDiv"></div>
</body>

This script, with just a few lines of code, creates a map that will be placed in the YMapDiv div container and adds three map controls:

  • Pan to have north, south, east, and west directional controls.

  • Type to add the ability to change between satellite, hybrid, and regular maps.

  • Zoom to add a zoom feature. Long specifies a slider vs. + and – controls.

Finally, the drawZoomAndCenter specifies the map’s starting location (New York City for the example) and zoom level.

By using the Yahoo Maps ActionScript 3 component, you can also embed maps in your Flash, Flex, or AIR application to add a map to a wide range of web and desktop applications. Once you’ve obtained an application ID, instead of using Ajax APIs where there is nothing to install, with ActionScript you have to download and install the component released as a SWC file from http://developer.yahoo.com/flash/maps/getLatest.php.

Note

A SWC file is an archive file for Flash and Flex components that makes it easy to exchange components among Flash and Flex developers. To get it into your Flex 3 project, just drop it in your libs folder, or point to it in your project build path. Using Flash, install the provided MXP via the Adobe Extension Manager.

After you have installed the Yahoo Maps ActionScript 3 component, you have to instance a YahooMap object to embed a map:

import com.yahoo.maps.api.YahooMap;

var mapContainer:UIComponent = new UIComponent();
Ymap.init("YOUR-APP-ID", stage.stageWidth, stage.stageHeight);             
mapContainer.addChild(Ymap);
addChild(mapContainer);

Like for any other mashup application, you can’t start developing if you don’t have a good idea of the APIs that are available. A good preparatory step is to refer to the class reference documentation to get familiar with the APIs. You can find the ActionScript 3 class reference documentation at http://developer.yahoo.com/flash/maps/classreference/index.html and the Maps Ajax API Reference Manual at http://developer.yahoo.com/maps/ajax/V3.8/index.html.

Another wise preparation that will save you time in the long run is to clarify the objectives of your mashup. You should have a good idea of the utility of the service that you are integrating and how the user will use it. If your ideas aren’t clear, you can get more ideas by consulting the Yahoo Maps application gallery (http://gallery.yahoo.com/maps), which features working examples that you can use as a starting point for your own applications.

ActionScript/Flex

To create a mashup that uses the Yahoo Maps API, you have to import the Yahoo Maps ActionScript 3 component. If you are using Flash Professional CS4 as an IDE, all you have to do is install the provided MXP using the Adobe Extension Manager and drag the component onto the Stage. In Flex 3, you have to copy the SWC file in the libs folder of your Flex3 project or point to it within your project build path.

This example uses the functions to control the markers in a Yahoo Maps map and saves a search in a local XML file.

The first step is to create an MXML file, called YMapsLocalSearch.as, to begin instancing the YahooMap class. Insert a Script block with the init function:

<mx:Script>
<![CDATA[
    import mx.controls.Alert;
    import mx.controls.AdvancedDataGrid;
    import com.yahoo.maps.api.intl.MapLocales;
    import mx.collections.ArrayCollection;
    import mx.events.ResizeEvent;
    import com.yahoo.maps.webservices.geocoder.GeocoderResult;
    import com.yahoo.maps.webservices.geocoder.GeocoderResultSet;
    import com.yahoo.maps.webservices.geocoder.events.GeocoderEvent;
    import com.yahoo.maps.api.core.location.Address;
    import com.yahoo.maps.api.core.location.LatLon;
    import com.yahoo.maps.api.YahooMapEvent;
    import com.yahoo.maps.api.YahooMap;
    import com.yahoo.maps.api.markers.SimpleMarker;


    private  const  YAHOOID:String = " YOUR_APP_ID ";


    private var _yahooMap:YahooMap;
    private var _address:Address;
    private var file:File;
    private var stream:FileStream;

    [Bindable] private var _geocoderResults:ArrayCollection;

    private function init():void
    {
 
         // create YahooMap instance and listen for map initialize event
         _yahooMap = new YahooMap();
         _yahooMap.addEventListener(YahooMapEvent.MAP_INITIALIZE, handleMapInitialize);
         _yahooMap.init(YAHOOID, mapContainer.width, mapContainer.height);
 
         _yahooMap.addPanControl();
         _yahooMap.addScaleBar();
         _yahooMap.addTypeWidget();
         _yahooMap.addZoomWidget();
 
         mapContainer.addChild(_yahooMap);
 
         _geocoderResults = new ArrayCollection();
         gResultPanel.visible=false;
 
         file = File.desktopDirectory.resolvePath("Ymarkers.xml");
         stream = new FileStream();
 
         if (file.exists)
         {
  
          stream.openAsync(file, FileMode.READ);
  
          stream.addEventListener(ProgressEvent.PROGRESS,progressHandler);
          stream.addEventListener(Event.COMPLETE, completeHandler);
          return
  
          }
 
     }

The addChild method adds the map to the DisplayList of an instance of the UIComponent in the application’s view:

<mx:UIComponent id="mapContainer" width="100%" bottom="0" top="35"/>

A TextInput control and a Button control, which will make up the UI elements of the application, allow the user to insert an address, view it on the map, and save it in a local file. The event handler that responds to the click of the button is as follows:

private function getTextInput(evt:MouseEvent):void
{
     var searchStr:String = searchInput.text;
 
     searchInput.text = "Looking up address...";
 
     _address = new Address(searchStr);
     _address.addEventListener(GeocoderEvent.GEOCODER_SUCCESS, handleGeocodeSuccess);
     _address.geocode();
}

When the GEOCODER_SUCCESS event, contained in the GeocoderEvent class, is triggered, it triggers the handleGeocodeSuccess handler, which loops through each result and adds an item to the suggest list (the TextInput control in the view):

private function handleGeocodeSuccess(event:GeocoderEvent):void
{
     var geocoderResults:GeocoderResultSet = _address.geocoderResultSet;
     // if we only have one result, set the location right away.
 
     if(geocoderResults.found == 1)
     {
          setLocation(geocoderResults.firstResult);
      }
     else if(geocoderResults.found > 1)
     {
          // loop through each result and add an item to the suggest list.
          _geocoderResults = new ArrayCollection();
          var len:int = results.length;
          for(var i:int=0; i<len; i++)
          {
               var result:GeocoderResult = results[i];
   
               var data:Object = {
                    label: result.getLineAddress(),
                    data:result
                }
   
               _geocoderResults.addItem(data);
           }
  
          //show the suggestions panel
          gResultPanel.visible=true;
      }else{
          // reset the text
          searchInput.text = "";
      }
}

You specify the center LatLon point of the map in the setLocation method. A marker is dynamically created from the SimpleMarker class and placed at the address specified by the user in the TextInput. Finally, the saveMarker method is invoked, which saves the address to a local file:

private function setLocation(geocoderResult:GeocoderResult):void
{
 _yahooMap.zoomLevel = geocoderResult.zoomLevel;
 _yahooMap.centerLatLon = geocoderResult.latlon;
 searchInput.text = geocoderResult.getLineAddress();
 
 var marker:SimpleMarker = new SimpleMarker();
 
 marker.address = new Address(geocoderResult.getLineAddress());
 
 _yahooMap.markerManager.addMarker(marker);
 saveMarker(geocoderResult.getLineAddress())
}

Note

The Geocoder allows you to find the specific latitude and longitude for an address string, while the GeocoderEvent class represents the event object passed to the event listener for events dispatched by the Geocoder object.

The complete code for the Flex application is as follows:

<?xml version="1.0" encoding="utf-8"?>

<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="init()"
 layout="absolute"
  showFlexChrome="false"
showStatusBar="false">


<mx:Script>
<![CDATA[
    import mx.controls.Alert;
    import mx.controls.AdvancedDataGrid;
    import com.yahoo.maps.api.intl.MapLocales;
    import mx.collections.ArrayCollection;
    import mx.events.ResizeEvent;
    import com.yahoo.maps.webservices.geocoder.GeocoderResult;
    import com.yahoo.maps.webservices.geocoder.GeocoderResultSet;
    import com.yahoo.maps.webservices.geocoder.events.GeocoderEvent;
    import com.yahoo.maps.api.core.location.Address;
    import com.yahoo.maps.api.core.location.LatLon;
    import com.yahoo.maps.api.YahooMapEvent;
    import com.yahoo.maps.api.YahooMap;
    import com.yahoo.maps.api.markers.SimpleMarker;


    private  const  YAHOOID:String = " YOUR_APP_ID ";


    private var _yahooMap:YahooMap;
    private var _address:Address;
    private var file:File;
    private var stream:FileStream;

    [Bindable] private var _geocoderResults:ArrayCollection;

    private function init():void
    {
 
         // create YahooMap instance and listen for map initialize event
         _yahooMap = new YahooMap();
         _yahooMap.addEventListener(YahooMapEvent.MAP_INITIALIZE, handleMapInitialize);
         _yahooMap.init(YAHOOID, mapContainer.width, mapContainer.height);
 
         _yahooMap.addPanControl();
         _yahooMap.addScaleBar();
         _yahooMap.addTypeWidget();
         _yahooMap.addZoomWidget();
 
         mapContainer.addChild(_yahooMap);
 
         _geocoderResults = new ArrayCollection();
         gResultPanel.visible=false;
 
         file = File.desktopDirectory.resolvePath("Ymarkers.xml");
         stream = new FileStream();
 
         if (file.exists)
         {
  
          stream.openAsync(file, FileMode.READ);
  
          stream.addEventListener(ProgressEvent.PROGRESS,progressHandler);
          stream.addEventListener(Event.COMPLETE, completeHandler);
          return
  
          }
 
     }

    private function handleMapInitialize(event:YahooMapEvent):void
    {
         mapContainer.addEventListener(ResizeEvent.RESIZE, handleContainerResize);
 
         _yahooMap.zoomLevel = 13;
         _yahooMap.centerLatLon = new LatLon(40.81,-96.7);
     }

    private function handleContainerResize(event:ResizeEvent):void
    {
     _yahooMap.setSize( mapContainer.width, mapContainer.height );
     }

    private function getTextInput():void
    {
         var searchStr:String = searchInput.text;
 
         searchInput.text = "Looking up address...";
 
         _address = new Address(searchStr);
         _address.addEventListener(GeocoderEvent.GEOCODER_SUCCESS, handleGeocodeSuccess);
         _address.geocode();
     }

    private function handleGeocodeSuccess(event:GeocoderEvent):void
    {
         var geocoderResults:GeocoderResultSet = _address.geocoderResultSet;
         var results:Array = geocoderResults.results;
 
     // if we only have one result, set the location right away.
     if(geocoderResults.found == 1)
     {
          setLocation(geocoderResults.firstResult);
      }
 
     else if(geocoderResults.found > 1)
     {
          // loop through each result and add an item to the suggest list.
          _geocoderResults = new ArrayCollection();
          var len:int = results.length;
          for(var i:int=0; i<len; i++)
          {
               var result:GeocoderResult = results[i];
   
               var data:Object = {
                label: result.getLineAddress(),
                data:result
                }
   
               _geocoderResults.addItem(data);
           }
  
          //show the suggestions panel
          gResultPanel.visible=true;
  
      }else{
  
          // reset the text
          searchInput.text = "";
      }
     }

    private function setLocation(geocoderResult:GeocoderResult):void
    {
         _yahooMap.zoomLevel = geocoderResult.zoomLevel;
         _yahooMap.centerLatLon = geocoderResult.latlon;
         searchInput.text = geocoderResult.getLineAddress();
 
         var marker:SimpleMarker = new SimpleMarker();
 
         marker.address = new Address(geocoderResult.getLineAddress());
         /*
         marker.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
         marker.addEventListener(Event.REMOVED_FROM_STAGE, onRemoved);
         */
         _yahooMap.markerManager.addMarker(marker);
         saveMarker(geocoderResult.getLineAddress())
     }

    private function handleListChange():void
    {
         // get the selected geocoder result object
         var selectedResult:GeocoderResult = geocoderResultsList.selectedItem.data 
                                                                as GeocoderResult;
         setLocation(selectedResult);
 
         _geocoderResults.removeAll();
         gResultPanel.visible=false;
     }

    private function saveMarker(add:String):void
    {
         var xmlData:XML = <marker/>;
 
         xmlData.address = add;
         xmlData.saveDate = new Date().toString()
 
         var outputString:String = '<?xml version="1.0" encoding="utf-8"?>\n';
         outputString += xmlData.toXMLString();
         outputString = outputString.replace(/\n/g, File.lineEnding);
 
 
         stream.open(file, FileMode.WRITE);
         stream.writeUTFBytes(outputString);
 
         stream.close();
     }

    private function progressHandler(event:ProgressEvent):void
    {
         // opening in progress ....
     }

    private function completeHandler(event:Event):void
    {
 
         var xmlData:XML = XML(stream.readUTFBytes(stream.bytesAvailable));
         stream.close();
 
         var searchStr:String = xmlData.address;
 
         searchInput.text = "Looking up address...";
 
         _address = new Address(searchStr);
         _address.addEventListener(GeocoderEvent.GEOCODER_SUCCESS, handleGeocodeSuccess);
         _address.geocode();
     }
]]>
</mx:Script>

<mx:UIComponent id="mapContainer" width="100%" bottom="0" top="35"/>

<mx:Canvas id="header" width="100%" height="35" backgroundColor="#E5E5E5" 
backgroundAlpha="0.85">

<mx:TextInput id="searchInput" width="275" verticalCenter="0" left="15" 
enter="getTextInput();"/>

<mx:Button label="Search and Save" cornerRadius="1" verticalCenter="0" left="288" 
click="getTextInput()"/>

</mx:Canvas>

<mx:Panel width="343" height="200" layout="absolute" id="gResultPanel" title="Results" 
left="35" top="45">

<mx:List left="0" right="0" top="0" bottom="0" id="geocoderResultsList" 
dataProvider="{_geocoderResults}" change="handleListChange();"/>

</mx:Panel>

</mx:WindowedApplication>

Note

This code is based on and extends the examples of mashups of Yahoo Maps, which are licensed under the BSD (revised) open source license. You can find other examples at http://developer.yahoo.com/flash/maps/examples.

JavaScript/HTML

In JavaScript, the steps to create the mashup example aren’t that different. The only difference is in importing the remote script to instance the Yahoo Maps map:

<script src="http://api.maps.yahoo.com/ajaxymap?v=3.4&amp;appid=YOUR_MAP_ID" 
type="text/javascript"></script>

The web page will use an iframe control to load the map in the nonapplication sandbox. This HTML page will be loaded in the iframe, which is essentially the application root. Following is the code for the parent page:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>

    <script type="text/javascript" src="frameworks/AIRAliases.js" />
    <script type="text/javascript" src="frameworks/AIRIntrospector.js" />

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<title>AIR Cookbook: 18.2 Consuming Yahoo! Maps web services (JavaScript)</title>

</head>

<body>
<h1>AIR Cookbook: 18.2 Consuming Yahoo! Maps web services (JavaScript)</h1>

<iframe
id="yahoomap"
src="mapIframe.html"
sandboxRoot="http://SomeRemoteDomain.com/"
documentRoot="app:/" width="500" height="500"
/>

</body>
</html>

This technique allows you to go beyond the limitations of the security sandboxes. There are two security sandboxes in AIR: the nonapplication sandbox and the application sandbox. The nonapplication sandbox operates just like the browser and doesn’t provide access to the AIR APIs. The application sandbox has restrictions on the JavaScript that can be executed, but it has full access to the AIR APIs. This would also directly impact the Yahoo Maps API.

You can learn more about the important issue of AIR security by reading the HTML Security FAQ (http://labs.adobe.com/wiki/index.php/AIR:HTML_Security_FAQ) and the “Ajax and Mashup Security” white paper from the Open Ajax Alliance (http://www.openajax.org/whitepapers/ajax%20and%20mashup%20security.html).

The core of the application is written in the mapIframe.html page, which is imported in the iframe and which displays a text input where the user can insert the address and a button. The script to instance the Yahoo Maps map is loaded in this page:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="http://api.maps.yahoo.com/ajaxymap?v=3.4&amp;appid=YOUR_YAHOO_APP_ID_HERE" 
type="text/javascript />

<script type="text/javascript" src="mapIframe.js" />

<style>
    #map {
    width: 100%;
    height: 450;
    }
</style>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Yahoo! Maps</title>
</head>
<body onLoad="init();">

    <input id="location" type="text" value="Insert the address" />

    <input type="submit" name="go" id="go" value="Show destination" onclick="loadAddress()" />

    <div id="map"></div>

</body>
</html>

By using a technique known as bridging, it is possible to access the assets that are specified in the HTML root page, the one that uses the iframe. This way, it is possible to access the nonapplication sandbox to call functions in the application sandbox. By using the parentSandboxBridge property, the application sandbox can set up a bridge interface:

var parentBridge = new Object();
parentBridge.doSomething = doSomething;
parentBridge.doSomethingElse = doSomethingElse;
document.getElementById( 'yahoomap' ).contentWindow.parentSandboxBridge = parentBridge;

The yahoomap element is the id of the iframe in the root page:

<iframe
id="yahoomap"
src="mapIframe.html"
sandboxRoot="http://SomeRemoteDomain.com/"
documentRoot="app:/" width="500" height="500"
/>

This way, the nonapplication sandbox code can invoke the methods doSomething and doSomethingElse from the application sandbox using the following code:

window.parentSandboxBridge. doSomething ();

This is the code for the imported mapIframe.js script:

// const
var DEFAULT_TEXT = 'Insert the address';
var STARTADDR = 'Rome, Italy';

var map = null;

function init()
{
     map = new YMap( document.getElementById( 'map' ) );
     map.addTypeControl();
     map.addPanControl();
     map.addZoomShort();
     map.drawZoomAndCenter( STARTADDR, 5 );
 
     // Add an event to report to our Logger
     YEvent.Capture(map, EventsList.MouseClick, reportPosition);
 
}

function loadAddress()
{
 
     var geo = document.getElementById( 'location' ).value;
 
     if( geo != DEFAULT_TEXT )
     {
            map.drawZoomAndCenter( geo );
      }
}


 function reportPosition(_e, _c)
{
     // It is optional to specify the location of the Logger.
     // Do so by sending a YCoordPoint to the initPos function.
     var mapmapCoordCenter = map.convertLatLonXY(map.getCenterLatLon());
     YLog.initPos(mapCoordCenter); //call initPos to set the starting location
     // Printing to the Logger
     YLog.print("You Made a MouseClick!");
     YLog.print("Latitude:" + _c.Lat);
     YLog.print("Longitude:" + _c.Lon);
 
     YLog.print("Adding marker at....");
     YLog.print("Latitude:" + _c.Lat + "Longitude:" + _c.Lon);
  }

The map is loaded and positioned on the point chosen by the user in the text input in the loadAddress function. The map is placed in the new location with the drawZoomAndCenter(geo) method.

With a small specification that uses the application sandbox as a proxy between sandboxes, you can run this application using AIR.

Skip to another section of the Adobe AIR 1.5 Cookbook, Chapter 18:
Consuming Flickr Web Services

Go to the catalog page for the Adobe AIR 1.5 Cookbook. Download the PDF of this entire chapter. View the book's table of contents.

Read more from David Tucker. David Tucker's Atom feed

Read more from Rich Tretola. Rich Tretola's Atom feed richtretola on Twitter

Read more from Marco Casario. Marco Casario's Atom feed

Comments

1 Comments

Seth said:

Yahoo api has MASHED privacy to DEATH.

Leave a comment


Tag Cloud

Question of the Week: Dream App

If you had an unlimited budget and unlimited resources what application would you build and why would you build it?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.