home / projects / mapattack

Map attack is a mashup using facebook and google maps APIs. A lot of facebook data contains locations, like photo albums, friends hometowns etc. Map attack plots all this information on a map with links and thumbnails.

example screen of map attack

The process is not perfect as much of the user entered facebook data is not fit for purpose, like "Random", "My garden", "The new house" etc. The google map API sometimes resolves this to strange locations like Random, USA.

Main site: apps.facebook/mapattack

Source code

googlemaputils.js Javascript to initialize the map and sequency request the POIs via the geocoder
mapattack_static.js Javascript to handle filtering, progress reporting, marker text formatting.
mapattack_dynamic.js.php Builds JSON dump of all the facebook location data
FbUserInfo Requests and holds basic information about users. Provides utility methods to create profile links
FbLocation Make the necessary queries to the FB API and sifts through the results for locations

How

Page -> FB -> locations -> JSON -> google JS API geocode requests -> geocoder callback -> processing/filtering -> points on map

Firstly the main page gets the information it needs

The UIDs of all the users friends are requested, and then these are fed into the FbUserInfo utility to query further information.

The FbUserInfo object is then passed to the FbLocation object to strip out all the location information and request any additional location data available. At this point the application has all the information it requires.

    (Server)
   index.php    fbui:FbUserInfo FbLocation  FbApi
GET     |               |        |            |
------> |               |        |            | 
        |     get friends()      |            |            
        |------------------------------------>|
        |               |        |            |
        | query(uids)   |        |            | 
        |------------>  |   get profile info  |
        |               |-------------------->|
        |               |        |            |
        |   query(fbui) |        |            |
        |----------------------->| get album info
        |               |        |----------->|           
        |               |        |           

Next the main page uses the location information to build a list of POIs (points of interest). The main page annotates the POIs with nicely formatted HTML. This will be used in the popup windows when a user clicks on a point.

When a facebook users name appears in the HTML a link will be shown. This is supplied by the FbUserInfo object.

    (Server)
   index.php            FbUserInfo 
   -------------------------
   | For each location     |
   |    |                  |
   |    |info_to_html(location)
   |    |----|             |
   |    |<---|             |
   |    | getNameLink()    |
   |    |----------------->|
   -------------------------
        |
        |

Now all the information is printed out as a javascript fragment in the main page. The PHP portion of the application is now complete

Now we're in client-land, javascript running on the users browser. Once the page has loaded, the location data is requested sequentially from the google-maps API.

PHP to javascript

The facebook API is in PHP, but the google-maps API is in javascript. Therefore some kind of conversion or export is necessary. The easiest way of doing this is to put all the values of interest in PHP into an array with meaningful keys, and then use the built in json_encode() function;

Example:

 $values = array('user' => 1748917, 'type' => 'album', 'search' => "Manchester UK", "html" => "fibble foobar");
 echo "var foo=" . json_encode($values) . ";";

var foo = {"user":1748917,"type":"album","search":"Manchester UK","html":"fibble foobar"};

Canvas size

The application must run in a IFrame. This is because javascript in the google-maps API won't work in the regular sandboxed facebook application frame (FBML mode).

Autosizing of the iframe within the parent facebook page is achieved by the following javascript fragment. See IFrame Resize Demo

    FB_RequireFeatures(["CanvasUtil"], function()
    {
      FB.XdComm.Server.init("/mapattack/xd_receiver.html?v=2");
      FB.CanvasClient.startTimerToSizeToContent();
    });

Multiple points

Many points of interest end up geocoded to the same physical location. Unfortunately googlemaps doesn't handle this well. The last marker wins.

To code with this my geocode callback keeps track of markers, and only allows one per physical co-ordinate. Points of interest are listed then chained off these 'unique' points. Each time a unique point is updated its HTML popup window is recalculated to be a concatenation of all the things it describes. e.g. it might be mark 2 hometowns, 3 album locations and 1 current location. To deal with a potentially huge amount of information in the popup window the information is placed inside a div element with scrollbars (overflow) enabled.

Filtering markers

To allow users to sift through a large number of pointers I included filter checkboxes. Clicking these causes markers to be removed or added.

There was a small problem in implementing this. Simply calling hide() on a marker didn't work if the user then changed the zoom level. The marker manager automatically calls show() on all the markers visible at that level. To get this working I swapped to the open sourced MarkerManager and tweaked the code to take into account an externally set filter attribute.

FB query time

Initially my page load time was 20 seconds or so. I tracked the problem down to my use of FB queries. I was making a separate request for each user's albums. Infact this can be made as a single request

// Very slow
foreach ($uids as $uid) {
	$albums=$facebook->api_client->photos_getAlbums($uid,NULL);
}

// Very fast
$fql = 'SELECT name,location,owner,link FROM '.
	'album WHERE owner IN (' . implode(',', $uids) . ')'; 

Bad inputs

Many points of interest obtained from facebook cannot be plotted accurately, for example, locations such as "everywhere", "random", "somewhere", "Manchester/London". Obvious bad inputs such as "random" are marked valid:false from the PHP side of the application. If the google geocoder does know where a point is it returns status code 620.

Some MS Windows upload program(s) sets the location field on albums as "C:/Documents and Settings/User/My Documents/My Pictures/That Wedding Album". This is really retarded.

To allow the user visibility of geocoding failures an "unknown locations" area underneath the map shows things that couldn't be geocoded. Some of these can be interesting. Points marked as valid:false by PHP go straight to the unknown locations box.

Geocoding performance

Google maps API only allows a request every 100ms or so. Therefore requests are made sequentially, the callback from one triggering the request of the next...

Since it may take a significant amount of time to fill the display I did the following

XHTML validity

It is very difficult to have javascript containing quoted HTML inside a XHTML page. I'll spare you the gory details. My solution was to have the javascript in a separate file which is actually a PHP script.