nwebb

Flex, Flash, AIR

Archive for May, 2008

GoogleMaps, Custom Cursors & Events

I've just updated the example mapping app with custom cursors ... well, as much as I can for the time-being.

Here are a couple of points relating to issues I encountered (in version 1.3 of the swc):

Possible event bugs

MapMouseEvent.MOUSE_MOVE does not fire in the current version of the swc. This has been reported by others so it's fairly safe to assume that it's a bug (currently this is preventing me from adding the "grab" cursor when the user moves the map).

MapMouseEvent.ROLL_OUT isn't working properly for me either. I have an HBox on the left of my map. When my mouse leaves the map Flex throws null object error (apparently before it gets around to dispatching the event - I pasted the stack trace here.

I got around this by subscribing to the UIComponent's MouseEvent.ROLL_OUT instead:

Actionscript:
  1. _map.addEventListener(MapMouseEvent.ROLL_OVER, mapRollOverHandler);
  2. this.addEventListener(MouseEvent.ROLL_OUT, mapRollOutHandler);//map instance is instantiated inside a UIComponent subclass

Also note that in the above code ROLL_OUT is a constant on MouseEvent, not MapMouseEvent.

Disabling Custom Cursor for map controls

I needed to disable the custom cursor when the mouse rolls over the controls. This was straight forward:

Actionscript:
  1. //"removeCustomCuror" and "addMoveCursor" are custom methods
  2. //which use the CursorManager to add or remove my custom cursor.
  3. _zoomControl = new ZoomControl();
  4. _zoomControl.addEventListener(MouseEvent.MOUSE_OVER, removeCustomCuror);
  5. _zoomControl.addEventListener(MouseEvent.MOUSE_OUT, addMoveCursor);
  6. _map.addControl(_zoomControl);

Disabling Custom Cursor for markers

When it came to markers I expected to be able to do this:

Actionscript:
  1. marker.addEventListener(MouseEvent.ROLL_OVER, removeCustomCuror); // doesn't work

...but found that I had to listen for MapMouseEvent instead:

Actionscript:
  1. marker.addEventListener(MapMouseEvent.ROLL_OVER, removeCustomCuror); //works

I guess this is because markers are map overlays, and so just another part/layer of the map.

Determine Cursor Type At Startup

To do this I listen out to the MOUSE_OVER event from the UIComponent, change the cursor if necessary and remove the listener. It just seemed like less work than determining the position of my mouse or performing a hit-test.

Actionscript:
  1. this.addEventListener(MouseEvent.MOUSE_OVER, determineCursorAtStartUp);
  2.  
  3. /**
  4. * If mouse is over map when application starts, change the cursor
  5. */
  6. private function determineCursorAtStartUp(event:Event):void
  7. {
  8.         addMoveCursor(event); //CursorManager.setCursor(_moveCursor);
  9.         this.removeEventListener(MouseEvent.MOUSE_OVER, determineCursorAtStartUp);
  10. }

Buggy Behaviour In Firefox

Sometimes, when I move the mouse and I'm over the map, the default cursor appears alongside my custom cursor momentarily, but this only happens in Firefox.

4 comments

GoogleMaps 101

GoogleMaps 101

I've already covered some of the basics for getting up and running with the new GoogleMaps AS3 API in Flex, with a Hello World example and an explanation of how to get rid of "Debug Mode" text / geocode when testing locally.

My experiences so far have been really positive. The documentation is well written, and although the AS3 API isn't as fully featured as it's counterpart, the team are bug fixing and adding features on a daily basis (plus taking requests, so be sure to check out the group). You shouldn't really have any major problems getting to grips with the code and finding out what you need. The purpose of this post is simply to distil what I thought may be the most sought after bits of code, in to a handy post.


GoogleMap tips

The code (from point 3 onwards) is taken from this application

1. Adding the GoogleMaps .swc to your project in FlexBuilder 3 is really easy!

2. After you have obtained your key, you can then create your new map. You will need to add it to the stage as the child of a UIComponent:

Actionscript:
  1. //as3 code
  2.         _map = new Map();
  3.         mapContainer.addChild(_map);
  4.  
  5. //mxml code
  6.         <mx:UIComponent id="mapContainer" width="100%" height="100%"/>;
  7.  
  8. /************************************************************
  9. Note: Alternatively you can create a map component by subclassing UIComponent
  10. as shown here: http://www.igorcosta.org/?p=140
  11. *************************************************************/

All code from here-on-in assumes we have created our own map component by subclassing UIComponent - see here for an example.

3. You cannot interact with your map until it is ready! To know when this happens you need to listen out for the MapEvent.MAP_READY event:

Actionscript:
  1. private function init(event:Event):void
  2. {
  3.         _map = new Map();
  4.         _map.key = "paste your key here";
  5.         _map.addEventListener(MapEvent.MAP_READY, onMapReady);
  6.         _map.setSize( new Point (this.width, this.height))
  7.         this.addChild(_map); //add as child of UIComponent
  8. }

4. Once we know our map is ready we can set it up by instantiating controls, adding markers to it and so on. Below is the code needed to centre the map on a particular location as well as add the familiar GoogleMap controls such as the zoom slider and map-type buttons (we'll talk more about locations and LatLng in a moment):

Actionscript:
  1. private function onMapReady(event:Event):void
  2. {
  3.     _map.setCenter( new LatLng(54.4700,-3.4277), 14, MapType.PHYSICAL_MAP_TYPE);
  4.     _map.addControl(new ZoomControl());
  5.     _map.addControl(new PositionControl());
  6.     _map.addControl(new MapTypeControl());
  7.     _map.setZoom(5);
  8.    
  9.         //dispatch event from our map component so that we know basic setup is complete
  10.     dispatchEvent(new Event(Event.COMPLETE));
  11. }

5. Whether you are centring your map as in the above example, or placing a marker on the map, you will at some point need to deal with latitude and longitude. Essentially they are number representing your location on the map. Latitude is used to express how far north or south you are and longitude shows your location in an east-west direction, relative to the Greenwich meridian.

I found the following resources useful for calculating latlng:

http://www.capelinks.com/cape-cod/maps/geocode/ - type the address in to the top input box, and the latitude/longitude appear in xml tags at the bottom of the page

http://www.satsig.net/maps/lat-long-finder.htm - this is useful for when you don't know the address. Drag the map around and it gives you both lat and lng. If you put it in to your map and it doesn't look the same, that's probably due to your zoom level, which you can set.

6. Geocoding is the process of taking an address in the form of a String, and having it converted to lat and lng. You may be thinking "cool, I'll just geocode everything", but a word of warning: Google's geocoder has a max number of queries per second, so it's not wise to loop over addresses and goecode them all. Plus geocoding is an aysnchronous operation, so bear your user in mind and don't make them wait that long. Instead, convert addresses to lat and lng in advance where possible. The tools mentioned above will help - or you can build your own of course :]

Read this thread for information of a geocoding bug ... it will hopefully be fixed by the time you read this post

7. Creating markers is something you will very likely want to do when you've got your map up and running. Here is a quick overview of how I went about it.

First off is a snippet of the xml. Note that I set isBranch="false" on the point nodes - this is so they show as leaf-nodes and not branch nodes in the Flex Tree component...

Actionscript:
  1. //EXAMPLE OF XML STRUCTURE...
  2. <group label="offices" color="0x00FF00">
  3.     <point label="Winchfield Lodge" isBranch="false">
  4.         <address lat="51.284920" lng="-0.909787">Some address</address>
  5.         <phone>12345678</phone>
  6.         <fax>12345678</fax>
  7.         <email>email@address.com</email>
  8.     </point>
  9.     <point label="Hartham Park" isBranch="false">
  10.         <address lat="51.448433" lng="-2.199214">Another address</address>
  11.         <phone>12345678</phone>
  12.         <fax>12345678</fax>
  13.         <email>email@address.com</email>
  14.     </point>
  15. </group>

Next we have the code in the main application file. I send in an XMLList of the point nodes, loop through each one, generate a marker object, extract the latitide and longitude, and then call a method on my map component:

Actionscript:
  1. //CODE WITHIN MY MAIN APPLICATION FILE
  2. /**
  3. * Creates and places markers on the map.
  4. */
  5. private function plotMarkers(points:XMLList):void
  6. {
  7.     for each(var item:XML in points)
  8.     {
  9.         var markerOptionObj:MarkerOptions = getMarkerObject(item);
  10.         var lat:Number = Number(item.address.@lat);
  11.         var lng:Number = Number(item.address.@lng);
  12.         mapComp.setMarkerFromLatAndLng(lat,lng, markerOptionObj);
  13.     }
  14. }
  15.  
  16. /**
  17. * @return An object which specifies the visual attributes of the marker
  18. * (e.g. colour & roll-over text)
  19. */
  20. private function getMarkerObject(item:XML):MarkerOptions
  21. {
  22.     var markerOptionObj:MarkerOptions = new MarkerOptions
  23.                 ({
  24.                     fillStyle:{color:Number(item.parent().@color)},
  25.                     label:item.@label.substr(0,1).toUpperCase().toString(),
  26.                     labelFormat:{color:0x000000, bold:true},
  27.                     strokeStyle:{thickness: 2, color: 0x333333},
  28.                     tooltip: String(item.@label)
  29.                 });
  30.     return markerOptionObj;
  31. }

Finally here is the code within my map component. It takes the lat, lng and marker options object and generates a marker. I also add each marker to an ArrayCollection, to make it easy to loop over the markers later.

Actionscript:
  1. //CODE WITHIN MY MAP COMPONENT
  2. /**
  3. * This takes lat and lng and places a marker at that location.
  4. * @param   lat                The latitude of the marker.
  5. * @param   lng                The longitude of the marker.
  6. * @param   markerOptionObj    An object of type MarkerOptions, defining how the marker should look.
  7. * @return  A reference to the Marker instance created.
  8. */
  9. public function setMarkerFromLatAndLng(lat:Number, lng:Number, markerOptionObj:MarkerOptions = null):void
  10. {
  11.     var latlng:LatLng = new LatLng(lat, lng);
  12.     var marker:Marker = new Marker(latlng);
  13.     marker.addEventListener(MapMouseEvent.CLICK, onMarkerClick);
  14.         if(markerOptionObj != null) marker.setOptions(markerOptionObj);
  15.         _map.addOverlay(marker);
  16.         _markers.addItem(marker); // add to ArrayCollection
  17. }

Hopefully this will give you enough info to get on with building your own map using the API. Any questions related to this post please feel free to leave a comment :]

8 comments

GoogleMap Example

I've knocked together a basic Flex GoogleMap application and I'll run over the basics in the next couple of posts.

Click the image below to view the app.

Click Image To See The Application

8 comments

Next Page »

Bad Behavior has blocked 435 access attempts in the last 7 days.