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.

The Object.watch method

The Object.watch, an often overlooked method, is quite powerful and well worth educating yourself on. If you have ever wanted to know every time a value changes and take actions accordingly, this is the function for you. The Object.watch method allows you to take a programmer defined action every time a property is set. In fact, your action actually takes place in the limbo time between a property being assigned by a user and when it is initialized to that value. What does that mean, let's take a look. ...

First off the Object.watch method is called like this:

Object.watch( property:String, handler:Function[, userData:Object] ):Boolean

The first parameter, 'property', is the property of the Object you want watched. So if your Object has a property called color then your property would be "color" ( make sure it is a String ).
The second parameter, 'handler' is a Function object. The handler Function is the function that takes action when a property gets assigned a value.
The third argument, 'userData', is just an optional piece of data you can pass into the handler function. You only get one, so if you want to pass in more than one piece of data you will have to package them up in an array or an Object.

Now let's take a look at some sample code:

// aButton is a Button component placed on the stage.var aButton:Button;
var obj:Object = new Object();
obj.color = "blue";
obj.watch( "color", watcher, this );
function watcher( prop, oldValue, newValue, userData )
{
  trace( prop + " changed from " + oldValue + " to " + newValue );
  trace( "userData = " + userData );
  trace( "obj.color( in watcher ) = " + obj.color );
  return newValue;
  //return oldValue;
  //return;
}

aButton.onRelease = function( )
{
  this._parent.obj.color = "red";
  trace( "obj.color( after assignment ) = " + this._parent.obj.color );
}

This allows you to see it in action. You could of course get 'obj.color' to be assigned many different ways, but this will do for our purposes. What I want you to look at here is the function 'watcher'. Whatever function you create must follow this same definition. All 'watcher' functions should have a the arguments, 'prop(erty)', 'oldValue' and 'newValue' - and in that order. The fourth argument is the optional userData you may have passed in.

What you really get to see in the previous example is what the current value of the property being watched ( obj.color ) is during the function. You also get to see how changing the return value of your handler function affects the properties value after the assignment occurs. You can keep a property from ever being changed by having a simple handler/watcher function that just returns the old value.

Now, how would you use this? The first thing that may come to mind is to watch changes in the properties of MovieClips( _x, _y, _width, _height, etc. ). That is probably the most useful way to use the Object.watch method, however, those properties are actually cleverly disguised getter/setter functions. Getter/setter properties can not be watched with Object.watch(). Don't throw the idea away though. You can use a proxy object to pass the information to the real MovieClip.

For example:

var mc:MovieClip; // some MovieClip you would like to watch.
var proxy:Object = new Object();
proxy._x = 0;
proxy._y = 0;
proxy._width = 100;
proxy._height = 100;
for( var x:String in proxy )
{
  proxy.watch( x, watcher, mc );
}

function watcher( prop:String, oldVal, newVal, clip:MovieClip )
{
  clip[prop] = newVal;
  trace( prop + " changed from " + oldVal + " to " + newVal + " on proxy and mc" );
  return newVal
}

Use this code as the basic idea behind using a proxy to use Object.watch with MovieClip properties. Just assign the value to the proxy instead of directly to the MovieClip and you can used that value as it is being changed in the MovieClip instance. If you actually need a true MovieClip proxy you can just extend the MovieClip class. A basic MovieClipProxy class is available over at senocular.com. In it's current form it doesn't do anything except get the values to and from the original when you use the proxy. You might also want to change the way the constructor works to suit your needs.

On a final note, the yang to the ying that is the Object.watch method is the Object.unwatch method. Once you no longer want to watch a property you can stop by calling Object.unwatch( property:String ).

Functions as Objects - part 2

In Functions as Objects - part 1 I discussed the basics of Function objects and showed some simple examples of using functions as objects. In todays article I will delve into actionscript Function objects, starting with scope and how to manage it as well as when you may need to. The two Function object methods, call and apply, will be discussed with example showing how they are both similar and different. The difference of the apply method will come out in some example of arguments, the one property of Function objects. Finally, we will work through an example showing many different ways you can use function objects within other functions and in the end come up with a very useful function. ...

First on the list of topics is scope. Let's start with what we are referring to when we use the term scope. In Object oriented programming you run into a special property called 'this'. The 'this' property is a reference to the current object. Some people even refer to scope as the this object. How does scope effect Function objects? That is best shown through an example.

For this example to work properly you will need to place a button component on the stage and give it an instance name of 'aButton'( no quotes ).

function scopeTest( )
{
	trace( "this = " + this );
}

aButton.onRelease = scopeTest;

trace( "calling scopeTest( )" );
scopeTest();

//trace( "calling scopeTest.call( aButton )" );
//scopeTest.call( aButton );

//trace( "calling scopeTest.apply( aButton )" );
//scopeTest.apply( aButton );

When you run this code your output window should come up and look like this:

calling scopeTest( )
this = _level0

And if you press ( and release ) the button( named 'aButton' ) it will look like this:

calling scopeTest( )
this = _level0
this = _level0.aButton

Let's see what is happening here. First off we declare and initialize a Function variable 'scopeTest'. Buttons have a variable 'onRelease' that is made to hold Function objects. We pointed aButton's 'onRelease' variable to scopeTest so it could be used to reference the same Function. Next, the we call scopeTest with scopeTest() and get the output: this = _level0. Because the 'onRelease' variable gets called when we release the button ( how ingenious! ), whenever that occurs we get the output: _level0.aButton. This shows how scope can affect the use of Functions. If you wanted the scopeTest function to use any data from the root MovieClip you may not get the correct results, though actionscript can be forgiving in some cases, and you could end up with unpredictable results. So just be clear what scope a function is being run in, if scope matters. In some cases it won't matter. Using the same setup as above plug in this code and test it.

function add2and3( ):Void
{
	var a:Number = 2;
	var b:Number = 3;
	trace( a + " + " + b + " = " + ( a + b ) );
}

aButton.onRelease = add2and3;

trace( "Calling add2and3" );
add2and3();

Now things are different. Whether called from add2and3() or from releasing the button you results are the same. This is because the Function does not have to reference this, the properties of this or the methods of this. It should be noted that this is not an issue in Actionscript 3.0 since functions retain their scope, however I have not done much work in AS3 so I will leave that information for a later time.

Since scope can have such a large affect on Function objects, they would be worthless if you could not put ensure functions got called in the scope you desired. Function object methods to the rescue. The 'call' and 'apply' methods allow you to do just that. Let's start off with an example, in fact let's start with the first example. Uncomment out the last 4 lines and run it. Without pressing the button you should get this:

calling scopeTest( )
this = _level0
calling scopeTest.call( aButton )
this = _level0.aButton
calling scopeTest.apply( aButton )
this = _level0.aButton

Our wishes have been granted. We can now call a function in whatever scope we want. Unfortunately we can't simply type:

aButton.onRelease = scopeTest.call( aButton )

because, as we learned from part 1, scopeTest.call( aButton ) is not a reference to a function. It is a function call, on the scopeTest.call function ( and thus represents Void, or nothing ). We'll get back to dealing with that later.

For now let's focus on 'call' and 'apply'. From the example above, it looks like they do the same thing. They do, the difference is in the parameters used when calling them. For both functions the first parameter is scope. For 'call' the parameters passed in after that are passed into the function you are calling. For 'apply' there is only a second parameter, an array of the parameters that you want to pass into the function you are calling. I will show the difference in an example that uses both of them to get the same results:

function add( a:Number, b:Number ):Void
{
	trace( a + " + " + b + " = " + ( a + b ) );
}

trace( "add.call( null, 2, 3 )" );
add.call( null, 2, 3 );

var numbers:Array = new Array( 2, 3 );
trace( "add.apply( null, numbers )" );
add.apply( null, numbers );

As you can see, 'call' is best used when you know the number of parameters that are needed and that are being applied and 'apply' is best used when you are working with an unknown number of parameters to be applied to a function.

A good place to find an unknown number of parameters for a function call is in a the 'arguments' property of Function objects. The FunctionArgument object 'arguments' is an object that uses array behaviour to get you a list of all the parameters passed into a function, even if they don't have a variable assigned to them. This point is demonstrated in this example:

function traceArgs( arg1, arg2 ):Void
{
	trace( "arg1 = " + arg1 );
	trace( "arg2 = " + arg2 );
	for( var x in arguments ){
		trace( "arguments[" + x + "] = " + arguments[x] );
	}
}

traceArgs( "hello", "one", "goodbye", "fourth" );

From which you get

arg1 = hello
arg2 = one
arguments[3] = fourth
arguments[2] = goodbye
arguments[1] = one
arguments[0] = hello

As you can see this functionality can be very helpful when you don't know how many parameters will be passed into a function.

Along with the power to see all passed in parameters, 'arguments' has 2 properties of its own, 'callee' and 'caller'. The property 'callee' is a reference to the currently executing function. It seems somewhat pointless, but we will be using this in our final example. The 'caller' property is a reference to the function that called the currently executing function. I have used the 'caller' property to take different actions depending on what was calling a function and also used it when working with super classes and static variables. However, 'caller' is often 'undefined' since many functions are not called from other functions and actually isn't even in actionscript 3.

Finally, how can I put all this information together from part 1 and part 2 to become a Function object power user. To be a Function object power user you should be able to use Function objects within Function objects. We are going to build a Function that takes a Function as a parameter and returns a function. The function we are going to build is somewhat complex so we will build and explain it line by line:

function createScopedFunction( scope:Object, method:Function, args:Array ):Function

The first line defines the Function that createScopedFunction will reference. It will take 3 parameters. The first parameter will represent scope( that's why I named it scope :) ) and can take any object. The second parameter, method, is the Function object we are passing in. The third is an Array we are passing in and we named it 'args'. The last part of the definition says that createScopedFunction will return a Function object. Now is a good time to start thinking about what we are trying to accomplish.

The next line looks like this:

var result:Function;

Simply put, we want to return a function so here we have created a variable that we will point to that function. We call that variable 'result'.

The next statement, which covers 3 lines:

result = function() {
  return arguments.callee.method.apply(arguments.callee.scope, arguments.concat(arguments.callee.args) );
};

In this code we are creating a Function object and initializing the variable 'result' with that Function. In breaking this down the first thing I want to talk about are the three "arguments.callee." references. Since arguments is inside the Function referenced 'result' and callee is a reference to the currently executing function. So when the function referenced at 'result' executes arguments.callee will reference the same thing as 'result'. So what about method, scope and args? We will get to that in a moment. For now see that we basically have a function body that looks like this:
return method.apply( scope, arguments.concat( args ) ). Now you should be able to see that we are going to use 'apply' to make a function call to 'method', using 'scope' as a this object and with parameters made up by concatenating 'args' to 'arguments' passed into 'result' and we are going to return the value we get from that function call. Is this starting to make sense?

The last five lines look like this:

  result.scope = scope;
  result.method = method;
  result.args = args;
  return result;
}

Remember arguments.callee.scope, arguments.callee.method and arguments.callee.args? Remember how we said in that instance that arguments.callee referenced the same thing as 'result'? The first 3 lines here add the scope, method and args properties to the Function referenced by 'result' and initialize them with the 'scope', 'method' and 'arg' parameters from the function declaration. We said we were going to return a Function object so the second to last line, "return result;", does just that. And finally we close our function statements. The finished code should look like this:

function createScopedFunction( scope:Object, method:Function, args:Array ):Function
{
  var result:Function;
  result = function() {
    return arguments.callee.method.apply(arguments.callee.scope, arguments.concat(arguments.callee.args) );
  };
  result.scope = scope;
  result.method = method;
  result.args = args;
  return result;
}

If you've ever heard of the Delegate class this is very similar to Delegate.create. It allows you to create a function that gives you the return from a function you call in a given scope. This version allows you the ability to also send parameters. To see it in action let's revamp our very first example so it uses our new Function:

function createScopedFunction( scope:Object, method:Function, args:Array ):Function
{
  var result:Function;
  result = function() {
    return arguments.callee.method.apply(arguments.callee.scope, arguments.concat(arguments.callee.args) );
  };
  result.scope = scope;
  result.method = method;
  result.args = args;
  return result;
}

function scopeTest( ):Void
{
	trace( "this = " + this );
}

aButton.onRelease = createScopedFunction( this, scopeTest );

trace( "Calling scopeTest( );" );
scopeTest();

The results you see should be different than we originally achieved.

----
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.

Functions as Objects - Part 1

For many people who learned how to program line by line, better known as procedurally, and then learned Object-Oriented Programming later, thinking of functions as Objects may not come naturally. Learning functions as objects can be very powerful and, though it doesn't solve every problem ( what does? ), they definately make many problems much easier to solve. In part 1 of Functions as objects I discuss the basics of using Function objects with many examples for each point. ... Because I currently do most of my programming in Actionscript 2.0, the examples in this article will be in that format. However, information should still easily be gleaned from my examples for other languages.

To start thinking about functions as objects, Function objects ( note the capital 'F' ), we will start with the basics of what makes an object. Objects have properties and methods, and Function objects are no different. Function objects, in Actionscript, have one property called 'arguments'. Put simply, 'arguments' is an object that holds the arguments that are passed into a function when it is called. We will talk about the functionality and use of 'arguments' in the part 2 of this article. As for methods, Function objects in actionscript have 2, 'call' and 'apply'. The methods 'call' and 'apply' are very similar and will be discussed more in part 2. To give you a basic idea, they give you some alternative ways to call a function.

Now that we know functions act like an object and look like an object, lets take a look at how to use them as objects. When you have another object, say an Array object you can store it ( or rather its memory location ) in a variable. First, you need a variable that can hold your object. For an Array:

var myArray:Array;

For a Function:

var myFunc:Function;

Now that we have our variables let's point them to our objects. You could point 'myArray' to a new Array object that you create with the 'new' operator. For example:

myArray = new Array()

For Function objects you don't use the 'new' operator, but rather pass a function literal like this:

myFunc = function( ){ return "Hello World" }

You can also point variables to objects, Function or otherwise, that are already in existense by pointing to other variables of the same type. For example:

var myMC:MovieClip = _root

and for a Function object:

var myFunc:Function = _root.onEnterFrame

Finally let's compare the two ways to declare and initialize a variable that points to a Function object:

var myFunc:Function = function( ){ return "Hello World"; }

can be written as:

function myFunc( ){
  return "Hello World";
}

The second way is probably more familiar but they are identical. Either ways you write declare and initialize 'myFunc' you can still call it by doing this:

myFunc()

And still point it to another function like this:

myFunc = function( name:String ):String{
  return "Hello " + name;
}

In my last example I typed the return of myFunc. In simpler English, I said that when I call the function it will return a certain data type ( in this case a String ). This is important for understanding the difference between 'myFunc' and 'myFunc()'. 'myFunc' is a variable that points to a Function object and myFunc() is a call to function that 'myFunc' points to and since I typed that function's return I know it will be a String. The best example is this:

function myFunc( ):String
{
  return "Hello World";
}

trace( "myFunc = " + myFunc ); // outputs myFunc = [Function object]

trace( "myFunc() = " + myFunc() ); // outputs myFunc() = Hello World

Let's make it a little more advanced by using a calling myFunc as different functions:

var myFunc:Function = function()
{
  trace( "Hello World" );
}

var myOtherFunc:Function = myFunc;

myFunc(); // output: Hello World
myOtherFunc(); // output: Hello World

myFunc = function( a, b )
{
  trace( a + " + " + b + " = " + ( a + b ) );
}

myFunc( 2, 3 ); // output: 2 + 3 = 5
myOtherFunc( 2, 3 ); // output: Hello World

In the last example myFunc gets initialized with a function literal and myOtherFunc was initialized by pointing to myFunc. So it is no suprise when they both output the same thing. However, myFunc then points to a new function and outputs what you would expect. myOtherFunc, on the other hand, still outputs 'Hello World'. This happens because it still points to the original Function object.

Hopefully this article gave some insight into using functions as objects. Be watching for part 2 of this article when I discuss scope, FunctionArguments and using functions in other functions.