Yahoo Flex Map Component Part 3 available

Part 3 in my multi part series on building a flex component out of the Yahoo! Maps API is available over at the Flexcoders group.

In part 1 of this series I talked about the reasons why you would want to make a flex component out of something you can do in actionscript. In part 2 I discussed the basics of the component architecture. From that second article you can create a component that works however you still haven't handled most of the reasons to make a component in the first place. In this article I will take care of those issues the issues of data binding, event handling and making it easier for non coders( sometimes called designers or managers :) ) to work with.

YahooFlexMap part 2 is available

Part 2 in my multipart series about creating a Flex component fromt he new Yahoo! map API for actionscript 3 is now available in the my.opera Flexcoders group blog.

In part 1 of my series on making a flex component out of the new Yahoo! Maps API for actionscript 3 I talked about the reasons I was taking the time to componentize something I could easily build with pure actionscript. Today I will be talking through the basics of creating a visual component. While these basics are covered at the LiveDocs for Flex 3, I will go over them in a slightly different way, as well as talk about how they affect the Yahoo! map component in particular.

Also make sure to join the Flexcoders group as it is new and looking for new members. At the very least, feel free to ask questions in the forum.

Yahoo Maps for Flex 3

Thank goodness. Yahoo has finally ported their map application over to Flex 3 ( AS3 ). This means a lot of great things, starting with speed. They can now get the speed improvements from the display model and other enhancements in the new virtual machine. Second, I can quit going back to klunky AS2 code whenever I have to put together some sort of interactive map. ...

I haven't had a chance to test it out yet but it should work pretty well. If not, I'm sure I can find some workarounds :) I'm looking forward to a more robust mapping API/Component that has less of the wierd bugs that you can only get with the old actionscript virtual machine.

But what will the possible Microsoft buyout do to this?

Simple Yahoo! Maps Workaround

The Yahoo! Maps Flash Component and API is a nice system to work with. For a simple to use map plug in for flash that looks decent without much work, its the best there is. However I like to work with it and see what hidden gems I can find. Or, as is the case in this article, delve deep inside of it and try to find out how to make it work as I want it to. It's still technically beta, so it has it's own little quirks. ...

As great as the Yahoo! Maps component is, it has one quirk I've run into a couple of times. If you unload the map while it is loading data and tiles it loads excruciatingly slow when you load the map again. In fact, it seems like it isn't loading at all. Usually when I've created a map for a web page or RIA, I create it as a separate SWF movie to be loaded at runtime. The fix for the problem in this situation is simple, if not obvious.

// make sure yahoo map library is removed from global namespace// so that it will always reload.
this.yMap.onUnload = function()
{
    _global.com.yahoo = null;
}

Unfortunately, this is one of those wild ass guess debug moments - where you just try it and hope it works. I'm not sure what class in the yahoo tree is causing the problems but I figured something was so I just got rid of them all. It probably also helps that the entire yahoo map library is a larger chunk of data so making it all available for garbage collection in that way makes garbage collection actually occur.

Recently, I had a project where my boss wanted me to embed map movie clip in the main movie rather than load it in at runtime. I still had the same type of problem, where the map could get, and is like to be, unloaded while it is in the middle of a data/tile load cycle. Since the Yahoo! map library isn't getting loaded in as it does with an external SWF, deleting the library is worse than the original quirk. Then the map won't even show the copyright info.

So into the problem I dove. I tried everything I could think of. I looked in the classes that I unzipped from the components SWC file to see if I could make some adjustments to unofficial parameters on unload. I used wireshark to see if any HTML requests were missing when the quirk occurred. I even took the SWC apart with Buraks Actionscript Viewer and tried to sort through that mess of code to see if I could find the answer. While I made many interesting discoveries along the way, I made no difference in the issue at hand.

So I made my way to my boss's office to tell him the status. As I walked toward his office I knew he would have a very simple answer for me that would work. I'm stubborn. I'm a programmer. I like to do things my way. I want elegant solutions to my programming problems. He is a designer. He just wants it to work. He's not worried about how it works ( note to other programmers, most people are this way ). I walk into his office and tell him the news. He thinks about it for maybe one second and tells me not to unload it, just move it off the stage.

Don't unload it?! That's like going to a doctor and telling him, "it hurts when I do this." To which the doctor will respond, "Then don't do that." In other words, it makes perfect sense and I can't believe I didn't think about it. Maybe I should have taken a break instead of pushing on for as long as I did to figure it out.

Now, instead of moving it off the stage I just set it's _visible property to false. That just seems a bit more elegant to me, and it just works. In fact in this situation it works better than the other system. Anyone who has used the Yahoo! Maps component knows that before the first tiles load in there is nothing but copyright info. By doing it this way the map loads in the background so when a user click the button to see the map, it comes up instantly.

So to wrap things up, here are my lessons learned:

  • If you can't make it elegant, just make it work
  • Ask for help from people who don't think like you
  • Your mind needs a rest from time to time, take breaks
  • load Yahoo! Maps in the background - they work better that way
  • Sometimes the answers are simple, if you are racking your brain step back and look at things from different perspective

----
Daryl "Deacon" Ducharme is currently Director of Application Development for the Interactive Agency Provis Media Group, LLC which helps organizations enhance identity, connect with customers and increase productivity.

Centering On Markers with Yahoo Maps

At my work, Flash is one of our core competencies. As a format, its ubiquity is probably its most powerful asset. For us, it is the best format to display some of our other core competencies such as video, 3D and rich internet applications. One thing we we're very happy to see was when Yahoo made a Flash Component and API for their mapping tool. We are still waiting for Google to do the same.

When I first dove into the component I unzipped the SWC and just messed with the innards trying to figure out what I could do with it that was not officially documented. While I did figure out some interesting things about driving directions, changing the logo, changing the copyright info as well as a few other things, we never had a client want to do more with it than show a map, at a certain zoom level and add some markers. Recently we had a client who wanted a map with dynamic markers. When they selected a certain group of points of interest it would only show that group. Easy enough. My boss added a little fun into the mix by saying he wanted the map to adjust its zoom and center itself on the displayed markers. Below I describe the thought process of doing something I hadn't done before. ...

So the question was, how do I center on the current markers. There was no zoomAndCenterToMarkers() function so I had to look deeper. It was there that I found the setBounds() function which, "Sets the map to the center of a rectangle specified by a LatLonRect object." That sounds like just the right function for my problem but there is a hitch. How do I get the latitude and longitude of my markers so that I can create the LatLonRect object?

My markers are all created by address. In the future they may be changed by the client so I wasn't assured a stable set of latitude and longitude data. Once again I checked into the API. Unfortunately, I didn't see any documented or undocumented way to just geocode the address. One of the great things of the yahoo maps API, auto-geocoding, was making this more difficult than I thought it should be. Either that or I was making it more difficult than it should be :) Once I thought about it I realized that geocoding is an expensive process. It takes a server call that takes an unknown amount of time, there must be an event that gets called when this occurs. Low and behold, there was my answer. The EVENT_MARKER_GEOCODE_SUCCESS event broadcast from the map. So the next step was to add this:

yMap.addEventListener( com.yahoo.maps.api.flash.YahooMap.EVENT_MARKER_GEOCODE_SUCCESS, Delegate.create( this, this.onGeoCode ) );

The event object that gets passed into my onGeoCode function has a latlon property so I can start to build the data I need.

private function onGeoCode( event:Object ):Void{
  var newCoordinates:LatLon = LatLon( event.latlon );
  this.markerCoordinates.push( newCoordinates );
  this.showAllMarkers();
}

I add the LatLon object to an array of latitude and longitude data with the idea to call a show all markers function that looks like this:

private function showAllMarkers(  ):Void
{
  var coordRect:LatLonRect = new LatLonRect();
  for( var index:String in this.markerCoordinates )
  {
    var coordinates:LatLon = LatLon( this.markerCoordinates[index] );
    if( coordRect.minLat == null || coordinates.lat < coordRect.minLat )
    {
      coordRect.minLat = coordinates.lat;
    }
    if( coordRect.minLon == null || coordinates.lon < coordRect.minLon )
    {
      coordRect.minLon = coordinates.lon;
    }
    if( coordRect.maxLat == null || coordRect.maxLat < coordinates.lat )
    {
      coordRect.maxLat = coordinates.lat;
    }
    if( coordRect.maxLon == null || coordRect.maxLon < coordinates.lon )
    {
      coordRect.maxLon = coordinates.lon;
    }
  }
  this.yMap.setBounds( coordRect );		
}

Simply put, it runs through the array of LatLon objects and creates the LatLonRect object from that data. This example is rather simplistic and doesn't take into account moving between hemispheres because I can be reasonably assured that this project will be limited to a very small area of the planet. Calling showAllMarkers() every time I get new geocode data is expensive and will probably break the application. The solution I came up with was to only call showAllMarkers() once all the markers for a given POI category are geocoded. So in my code I set a variable called markersDisplayedCount that equals the number of markers that are going to be displayed. I then changed my onGeoCode function to look like this:

private function onGeoCode( event:Object ):Void
{
  var newCoordinates:LatLon = LatLon( event.latlon );
  this.markerCoordinates.push( newCoordinates );
  var someMarkersVisible:Boolean = this.markersDisplayedCount > 0;
  var allMarkersGeoCoded:Boolean = this.markerCoordinates.length == this.markersDisplayedCount;
  if( someMarkersVisible && allMarkersGeoCoded )
  {
    this.showAllMarkers();
  }
}

As you can see, I test to see if all the displayed markers have been geoCoded and that some markers are visible before I call the showAllMarkers() function. The reason I add the someMarkersVisible test was to lessen the risk of a fast moving user from breaking the application. It's not an infallible system but it does the job within the confines of the requirements.

A couple final points from all of this. If instead of zooming to and centering on the markers you just want to ensure they are all within the current display, you can replace this:

var coordRect:LatLonRect = new LatLonRect();

from the showAllMarkers() function with this:

var coordRect:LatLonRect = this.yMap.getBounds();

In this case yMap is my YahooMap object and the getBounds() function gets the current LatLonRect of the map. By making this minor change your map will only change, by zooming or recentering, if the markers are outside of its current bounds. The last point I want to make is that if you are dealing with a mapping application that may deal with crossing over hemisphere boundaries, your showAllMarkers() function will have to take this into account. I believe this will involve ranges from -90 degrees to 90 degrees for latitude and -180 degrees to 180 degrees for longitude.

----
Daryl "Deacon" Ducharme is currently "Code Czar" for the Interactive Agency Provis Media Group, LLC which helps organizations enhance identity, connect with customers and increase productivity.