Using The New Yahoo! Maps ActionScript 3 API - Create A SearchMarker Using Your Own Data

The Yahoo! maps AS3 API provides a SearchMarker class that is used to display on the map an icon representing a LocalSearchItem class object. The LocalSearchItem class is used to store data contained in a result returned from searching the Yahoo! Local listings (see the related entry below). This data includes phone, address, website, title, etc. However, you can create your own LocalSearchItem objects and use these objects to create SearchMarker objects and display those on the map. The LocalSearchItem class documentation is a bit confusing on how to create an object of the class, so I thought a blog entry and example (right click on the app to view source code) might be helpful.

To create a LocalSearchItem object you must provide the constructor with an XML object that contains various nodes (for example <addr>, <city>, <state>). What confused me about the XML was that in the documentation for LocalSearchItem it lists a latlon property of type latlon. So I was initially stumped as how I could create an XML object that contained a node with a latlon object. But then Dan Tavelli pointed out to me that the XML results returned from doing a search of Yahoo! Local contained a <lat> and a <lon> node and not some kind of latlon object.

So I experimented with creating an XML object that contained nodes to match the properties of the LocalSearchItem class, except for using separate <lat> and <lon> nodes. Also, I found out that if you want the phone number to display on the SeachMarker object you should use a node named <dphone> not phone (as the LocalSearchItem class documentation lists).

In my example I created an Address object and then called the geocode method. If the Address object can be successfully geocoded, my function handleGeocodeSuccess is executed. Now my Address object has a geocoderResultSet property that I can use to pull out the information I need to help create the XML used to create the LocalSearchItem object. For example to get the latitude value I can use:

address.geocoderResultSet.firstResult.latitude

or to get the city value I can use:

address.geocoderResultSet.firstResult.city

Take a look at class GeocodeResult to see all the values you can access after your address is successfully geocoded.

After building the XML object, I used it to create the LocalSearchItem object. I then used the LocalSearchItem object to create the SearchMarker object and then added the SearchMarker object to the map. When you click on the SearchMarker object the information I used to create the XML is displayed.

Using the above technique, it would not be difficult to get data from your database (using ColdFusion of course!) that included, an address string, a phone number, a URL, a title, etc and then use that data to create SearchMarker objects and display them on your map.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Nice work Bruce! Great example.

I am still left missing the old poi marker. I really prefer the style of it much more than the new ultra-minimalist look. I also found it much more usable, like how the old marker displayed the name of the listing even when collapsed which makes the results stand out from the map more I thought, and the rollover transitions which gave an indication the marker was cilckable. Anyway I'm going to see if I can port the old poi marker over to the as3 marker class. I'll let you know if I ever get it working.
# Posted By Dan | 2/14/08 1:00 PM
Can you provide an example of coupling SearchMarkers with a checkbox that can change the visible property for a group a markers?
# Posted By Christopher Keeler | 2/29/08 10:29 AM
Christopher - unfortunately I just don't have time to create such an example. It does sound like an interesting application. If you create something like that be sure to post a comment with a link.
# Posted By Bruce | 3/3/08 7:06 PM
Thanks for reverse engineering the code they released to us but failed to document.

<strike>I couldn't right-click for the source (did nothing), so I'm still searching for the XML format</strike>. (Couldn't post to your blog either - my Firebird must be funked up, both worked in Satari)
# Posted By Bill Shirley | 3/5/08 1:53 PM
With the new ActionScript3, e4x is builtin to the language. Using it makes your XML much easier to handle and allows it to be type-checked (as opposed to the XML string).

my modified handler...

private function handleGeocodeResult(event:GeocoderEvent):void {
   var resultset:GeocoderResultSet = event.data as GeocoderResultSet;
var result:GeocoderResult = resultset.firstResult as GeocoderResult;
var item:LocalSearchItem = new LocalSearchItem(<listing>                  <id>{result.woeid}</id>
               <title>TITLE</title>
               <addr>{result.line1}</addr>
               <city>{result.city}</city>
               <state>{result.statecode}</state>
               <zip>{result.uzip}</zip>
               <lat>{result.latitude}</lat>
               <lon>{result.longitude}</lon>
               </listing>);
   var marker:SearchMarker = new SearchMarker(item);
   yahooMap.markerManager.addMarker(marker);
}

that the asynch geocode method doesn't hand over a token is a broken API! If I want to volley off 20 geocode requests, how am I to tell the results apart? (so that titles and phones can appropriately be connected)
# Posted By Bill Shirley | 3/5/08 3:48 PM
Bill - thanks for the improved handleGeocodeResult function.
# Posted By Bruce | 3/6/08 6:38 PM
Hi Bruce,

Thanks for your excellent examples! How can I use the example above to add more than one marker (address). Can I use an external xml file to set up a number of addresses? Also, how can I open the "more info" url in a new window (_blank).

Many thanks again!
# Posted By Chris | 3/16/08 1:48 PM
First and foremost thanks for the tutorials. I think I have the same issue as Chris. How do we create multiple Markers and display via external xml? I am sure that I have to implement some sort of for loop, but I'm stuck. Any feedback would be great.
# Posted By Pdub | 4/1/08 1:16 AM
I made a custom component and extended the Marker class to achieve the results I was looking for. Below is some code:

package custom
{
   import com.yahoo.maps.api.core.location.LatLon;
   import com.yahoo.maps.api.markers.Marker;
   import com.yahoo.maps.api.utils.Distance;
   import com.yahoo.maps.api.utils.DistanceResult;
   import com.yahoo.maps.webservices.local.LocalSearchItem;
   
   import flash.display.*;
   import flash.events.*;
   import flash.filters.DropShadowFilter;
   import flash.geom.*;
   import flash.net.URLRequest;
   import flash.net.navigateToURL;
   import flash.text.TextField;
   import flash.text.TextFieldAutoSize;
   import flash.text.TextFormat;
   
   public class CustomSearchMarker extends Marker
   {
      public var markerID:String;
      public var groupID:String;
      public var markerColor:uint;
      
      private var _ds:DropShadowFilter;
      private var _titleText:TextField;
      private var _addressText:TextField;
      private var _cityStZipText:TextField;
      private var _phoneText:TextField;
      private var _urlText:TextField;
      private var _distanceText:TextField;
      private var titleFormat:TextFormat;
      private var markerTextFormat:TextFormat;
      private var urlFormat:TextFormat;
      private var custMarkerShape:Sprite;
      private var lgMarkerShape:Sprite;
      private var closeButton:Sprite;
      private var localSearchItem:LocalSearchItem;
      private var calcDCCDistance:DistanceResult;
      
      public function CustomSearchMarker(color:uint, centerLatLon:LatLon, localSearchItem:LocalSearchItem, localSearchCategory:String, catFilter:String)
      {
         super();
         this.groupID = catFilter;
         this.markerColor = color;
         this.markerID = localSearchItem.id;
         this.localSearchItem = localSearchItem;
         custMarkerShape = new Sprite();
         custMarkerShape.graphics.lineStyle(1,0xFFFFFF);
         custMarkerShape.graphics.beginFill(color,1);
         custMarkerShape.graphics.lineTo(3,-5);
         custMarkerShape.graphics.lineTo(7,-5);
         custMarkerShape.graphics.lineTo(7,-15);
         custMarkerShape.graphics.lineTo(-7,-15);
         custMarkerShape.graphics.lineTo(-7,-5);
         custMarkerShape.graphics.lineTo(-3,-5);
         custMarkerShape.graphics.lineTo(0,0);
         custMarkerShape.graphics.endFill();
         addChild(custMarkerShape);
         custMarkerShape.buttonMode = true;
         custMarkerShape.useHandCursor = true;
         _ds = new DropShadowFilter(3, 45, 0x000000, .7, 2, 2, 1, 3);
         custMarkerShape.addEventListener(MouseEvent.CLICK, onClick);
         custMarkerShape.addEventListener(MouseEvent.MOUSE_OVER, onOver);
         custMarkerShape.addEventListener(MouseEvent.MOUSE_OUT, onOut);
         calcDCCDistance = Distance.getGreatCircleDistance(centerLatLon,localSearchItem.latlon);
         
      }
      public function onClick(event:Event):void{
         promoteToTop(); //Bring the marker to the top of the stack
         custMarkerShape.visible = false; //Hide the little marker
         
         titleFormat = new TextFormat();
         titleFormat.size = 11;
         titleFormat.color = 0xFFFFFF;
         titleFormat.bold = true;
         titleFormat.font = "Arial";
         
         markerTextFormat = new TextFormat();
         markerTextFormat.size = 10;
         markerTextFormat.bold = false;
         markerTextFormat.color = 0x333333;
         markerTextFormat.font = "Arial";
         
         urlFormat = new TextFormat();
         urlFormat.size = 11;
         urlFormat.color = 0x0000FF;
         urlFormat.font = "Arial";
      
         
         //Add Title Textfield
         _titleText = new TextField();
         _titleText.width = 1;
   _titleText.height = 1;
   _titleText.autoSize = TextFieldAutoSize.LEFT;
         _titleText.text = localSearchItem.title;
         
         //Add Address Textfield and concat City, State ZIP phone and distance
         _addressText = new TextField();
         _addressText.width = 0;
         _addressText.height = 0;
         _addressText.autoSize = TextFieldAutoSize.LEFT;         
         _addressText.text = localSearchItem.addr+"\n"+localSearchItem.city + ", " + localSearchItem.state + " " + localSearchItem.zip+"\n"+localSearchItem.phone+"\n"+calcDCCDistance.miles.toFixed(1) + " miles from Dattoli";
         /*
         //Add City, State, Zip Textfield
         _cityStZipText = new TextField();
         _cityStZipText.width = 0;
         _cityStZipText.height = 0;
         _cityStZipText.autoSize = TextFieldAutoSize.LEFT;         
         _cityStZipText.text = localSearchItem.city + ", " + localSearchItem.state + " " + localSearchItem.zip;
         
         //Phone Textfield
         _phoneText = new TextField();
         _phoneText.width = 0;
         _phoneText.height = 0;
         _phoneText.autoSize = TextFieldAutoSize.LEFT;
         _phoneText.text = localSearchItem.phone;
         
         //Distance TextField
         _distanceText = new TextField();
         _distanceText.width = 0;
         _distanceText.height = 0;
         _distanceText.autoSize = TextFieldAutoSize.LEFT;
         _distanceText.text = calcDCCDistance.miles.toFixed(1) + " miles from Dattoli";
         */
         
         //Calculate max width & height of all Textfields so we can use in dynaimic background shape calculations   
         var w:Number = Math.round(_titleText.textWidth + 20);
         var h:Number = _titleText.textHeight + _addressText.textHeight;// + _cityStZipText.textHeight + _phoneText.textHeight + _distanceText.textHeight + 10;         
         var radius:Number = 8;
         var padding:Number = 8;
         if(_addressText.textWidth > _titleText.textWidth){
            w = _addressText.textWidth;
         }
         
         //Add URL Textfield
         if(localSearchItem.websiteURL().length > 0){
         _urlText = new TextField();
         //_urlText.text = "More Info";
         _urlText.htmlText = "<A HREF='event:" + localSearchItem.websiteURL() + "' target='_blank'>More Info</A>";
         _urlText.x = w /2 - _urlText.textWidth + 5;
         _urlText.y = -_urlText.textHeight -padding - 3;
         _urlText.setTextFormat(urlFormat);
         _urlText.addEventListener(TextEvent.LINK, goToMarkerURL);
         h += Math.round(_urlText.textHeight);
         }
         
         //Draw Marker Background Shape
         lgMarkerShape = new Sprite();
         var tipShape:Array;
tipShape = [[0, 0], [7, -7], [w / 2, -7], [w / 2 + radius + padding, -7, w / 2 + radius + padding, -7 -radius], [w / 2 + radius + padding, -h], [w / 2 + padding + radius, -radius-h, w / 2, -radius-h], [-w / 2 - padding , -radius-h], [-w / 2 -padding -radius, -radius-h, -w / 2 -padding -radius, -h],[-w/2 -padding -radius, -7-radius],[-w / 2 - padding -radius, -7, -w /2 - padding, -7], [-7, -7], [0,0]];
    //---------[--1--]--[--2--]--[---3-----]--[-----------4------------------------------------------------------]--[------------5---------------]--[---------------------------6-------------------------]--[---------7------------------]--[--------------------------8------------------------------------]-[-------------------9------------]-[--------------10---------------------------------]--[--11--]--[-12-]
    var len:int = tipShape.length;
   lgMarkerShape.graphics.lineStyle(2,0xFFFFFF);
   lgMarkerShape.graphics.beginFill(markerColor,1);
    for (var i:int = 0; i < len; i++) {
    if (i == 0) {
    lgMarkerShape.graphics.moveTo(tipShape[i][0], tipShape[i][1]);
    }
    else if (tipShape[i].length == 2) {
      lgMarkerShape.graphics.lineTo(tipShape[i][0], tipShape[i][1]);
   }
   else if (tipShape[i].length == 4) {
      lgMarkerShape.graphics.curveTo(tipShape[i][0], tipShape[i][1], tipShape[i][2], tipShape[i][3]);
   }
   }
   lgMarkerShape.graphics.endFill();    
   lgMarkerShape.filters = [_ds];
   lgMarkerShape.useHandCursor = false;
   addChild(lgMarkerShape);
   
   //Draw Close Button
   closeButton = new Sprite();
   closeButton.graphics.lineStyle(2,0xFFFFFF);
   closeButton.graphics.beginFill(0xCCCCCC, 1);
   closeButton.graphics.drawCircle(0,0,8);
   closeButton.graphics.endFill();
   closeButton.graphics.lineStyle(2,0x666666);
   closeButton.graphics.moveTo(-3,-3);
   closeButton.graphics.lineTo(3,3);
   closeButton.graphics.moveTo(-3,3);
   closeButton.graphics.lineTo(3,-3);
   closeButton.x = Math.round(w/2 + padding + (radius /2));
   closeButton.y = Math.round(-h -(padding /2));
   closeButton.buttonMode = true;    
   closeButton.addEventListener(MouseEvent.CLICK, onCloseHandler);
   
   //Layout Textfields
   _titleText.x = Math.round(-w / 2 - padding);
         _titleText.y = Math.round(-h - padding/1.5);
         _addressText.x = _titleText.x;
         _addressText.y = _titleText.y + _titleText.textHeight;
   
   //Set Text Formats
   _titleText.setTextFormat(titleFormat);
   _addressText.setTextFormat(markerTextFormat);
   
   //Add text Children to Background Shape
   lgMarkerShape.addChild(_titleText);
   lgMarkerShape.addChild(_addressText);
         if(localSearchItem.websiteURL().length > 0){
         lgMarkerShape.addChild(_urlText);
         }
         lgMarkerShape.addChild(closeButton); //add this last so it stays on top.
         
      }
      private function onOver(event:MouseEvent):void{
         custMarkerShape.filters = [_ds];
      }
      private function onOut(event:MouseEvent):void{
         custMarkerShape.filters = null;
      }
      private function lgOnOver(event:MouseEvent):void{
         urlFormat.underline = true;
         _urlText.setTextFormat(urlFormat);
      }
      private function lgOnOut(event:MouseEvent):void{
         urlFormat.underline = false;
         _urlText.setTextFormat(urlFormat);
      }
      private function onCloseHandler(event:MouseEvent):void{
         custMarkerShape.removeEventListener(MouseEvent.CLICK, onCloseHandler);
         removeChild(lgMarkerShape);
         custMarkerShape.visible = true;
      }
      private function goToMarkerURL(event:TextEvent):void{
         var urlRequest:URLRequest = new URLRequest(event.text);
         navigateToURL(urlRequest, "_blank");
      }
   }
}
# Posted By Christopher Keeler | 4/1/08 5:33 AM
Some of you guys asked to provide multiple Markers via an RSS Feed:

Here we go:

First of all i set up an HttpService grabbing a yahoo Pipe who actually searches for apartements in craigslist near Stanford University.

Then i'm iterating the result and placing the search Markers.


The Code-Snippet:

public function useHttpService(event:Event):void {
       service = new HTTPService();
       service.url = "http://pipes.yahoo.com/pipes/pipe.run?_id=1mrlkB23...;;
       service.resultFormat = "object";
       service.addEventListener("result", httpResult);
       service.addEventListener("fault", httpFault);
       service.send();
       }
      
       public function httpResult(event:ResultEvent):void
       {
       var results:Object = event.target.lastResult.rss.channel.item;
                  trace(print_r(results,""));
                  trace("-----------------------------");
                  trace("-----------------------------");
                  for (var resultItem:String in results)
                  {
                     
                     var xmlItem:XML =
                     <listing>
                   <title>{results[resultItem].title.substr(0,5)}</title>
<lat>{results[resultItem].lat}</lat>
<lon>{results[resultItem].long}</lon>
<detailurl>{results[resultItem].link}</detailurl>
</listing>
                     var myLocalSearchItem:LocalSearchItem = new LocalSearchItem(xmlItem);
                     var mySearchMarker:SearchMarker = new SearchMarker(myLocalSearchItem);

            _yahooMap.markerManager.addMarker(mySearchMarker);
}


P.S.:
print_r is a php-like print_r method written by me. Good for looking inside objects.
I'll share if you like.

Feature Request to Yahoo:
Yahoo! Please hand in a customFlexMarker !!! Or even a customPOIMarker!!! Or an customLocalSearchItem.
# Posted By Marco | 4/11/08 2:45 AM
HELP! I cannot view the source for some reason.. I'm trying very hard to achive the XML multiple search marker deal above. I'm creating my own XML from my db in php and have a pretty good working version of the maps in the crappy as2 form and having trouble getting it working right in as3.
# Posted By Jaed | 5/15/08 10:12 AM
Try right clicking at the very top of the application (above the visual map). The Yahoo! map actually captures the right-click event.
# Posted By Bruce | 5/15/08 11:13 AM
Flipping screens, moving screen... blah. When do they come up with a screen-only with a virtual keyboard. Just the screen...
http://www.ktservis.com
# Posted By Stanley Pasons | 5/18/08 5:37 AM
can someone help me on how to create lines of latitude and longitude using yahoo map ? Please note that this lines of latitude does not have to be on a particular map but there are actual predefined location for the lines..

All i want is how to create the lines........:(
# Posted By john bank | 7/7/08 11:24 PM
I download Actionscript 3 API (use this link http://rapid4me.com ), if you input Actionscript 3 API you find many links for download this program from rapidshare.
# Posted By ertyh | 2/2/09 9:57 AM
thanks for sharing.. I bookmarked this blog..
# Posted By Notebook Tamiri | 10/28/09 4:42 AM
I'm trying very hard to achive the XML multiple search marker deal above. I'm creating my own XML from my db in php and have a pretty good working version of the maps in the crappy as2 form and having trouble getting it working right in as3.
# Posted By laptop tamiri | 7/1/10 3:40 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1.002. Contact Blog Owner