Fabricated Technobabble

View Original

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.