Mashing Up Google Maps API with Flickr

You now have all the pieces needed to finish up the Flickr and Google Maps mashup. Here’s a step-­by-step walk-­through of the big steps:

  1. Set up a basic Google map.

  2. Have the map respond to changes in the viewport of the map.

  3. Bring together Flickr and GMap into the same HTML page by combining the code into one file—the two pieces are just together but don’t interact.

  4. Wire up the bounding box of the Google map to be the source of the lat/long coordinates.

  5. Write the coordinates into the lat0/lon0 and lat1/lon1 boxes.

  6. Make the pictures show up in the map.

Setting Up a Basic Google Map

To start with, let’s just get a simple Google map set up by using the Google Maps API (which you learned about in Chapter 8):

  1. Make sure you have the Google Maps key needed for your domain. The domain I have is http://examples.mashupguide.net/ch10. You can calculate the corresponding API key:

    http://www.google.com/maps/api_signup?url=http%3A%2F%2Fexamples.mashupguide.net%2Fch10

  2. Copy the following code, substituting your key, to get a map centered on UC Berkeley with the size, map-­type control, and keyboard handlers (you can use the arrow keys to control the map):[143]

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
        <meta ?http-?equiv="content-type" content="text/html; charset=utf-8"/>
        <title>Google Maps JavaScript API Example</title>
        <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=[API_KEY]"
          type="text/javascript"></script>
        <script type="text/javascript">
    
        //<![CDATA[
    
        function load() {
          if (GBrowserIsCompatible()) {
            var map = new GMap2(document.getElementById("map"));
            window.map = map;
            map.setCenter(new GLatLng(37.872035,-122.257844), 13);
            map.addControl(new GSmallMapControl());
            map.addControl(new GMapTypeControl());
          }
        }
    
        //]]>
        </script>
      </head>
      <body onload="load()" onunload="GUnload()">
        <div id="map" style="width: 800px; height: 600px"></div>
      </body>
    </html>
                   

Making the Map Respond to Changes in the Viewport of the Map

The next thing to pull off is to have the map respond to changes in the viewport of the map (that is, when the user has panned or zoomed the map). The mechanism to use is Google Maps events:

http://www.google.com/apis/maps/documentation/#Events_overview

You can get a list of supported events here:

http://www.google.com/apis/maps/documentation/reference.html#GMap2

The relevant event we need here is the moveend event, the one that is fired once the viewport of the map has stopped changing (as opposed to the move event, which is fired during the changing of the viewport). To see this event in action, load the Google map you just created and use the JavaScript Shell to add a listener for the moveend event:

onMapMoveEnd = function () {alert("You moved or zoomed the map");}

function () { alert("You moved or zoomed the map"); }

GEvent.addListener(map,'moveend', onMapMoveEnd);

[object Object]

With that event listener added, every time you finish panning or zooming the map, an alert box pops up with the message “You moved or zoomed the map.”

Let’s now write some code that displays the bounding box in a <div> element, updating this information every time the map is moved. We are doing this as a stepping-­stone to feeding the bounding box information to flickrgeo.php. Here’s the code:[144]

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta ?http-?equiv="content-type" content="text/html; charset=utf-8"/>
    <title>gmap.2.html</title>
    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=[API_KEY]"
      type="text/javascript"></script>
    <script type="text/javascript">

    //<![CDATA[

    function updateStatus() {
      var div = document.getElementById('mapinfo');
      div.innerHTML = map.getBounds();
      return (1);
    }

    function onMapMove() {

      updateStatus();
    }

    function onMapZoom(oldZoom, newZoom) {

      updateStatus();
    }

    function load() {
      if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map"));
        window.map = map;
        map.setCenter(new GLatLng(37.872035,-122.257844), 13);
        map.addControl(new GSmallMapControl());
        map.addControl(new GMapTypeControl());
        window.kh = new GKeyboardHandler(map);

        GEvent.addListener(map,'moveend',onMapMove);
        GEvent.addListener(map,'zoomend',onMapZoom);
        updateStatus();
      }
    }

    //]]>
    </script>
  </head>

  <body onload="load()" onunload="GUnload()">
    <div id="map" style="width: 800px; height: 600px"></div>
    <div id="mapinfo"></div>
  </body>
</html>
         

Bringing Together the Flickr and GMap Code

At this point, you are now ready to bring together the Flickr elements (the input form hooked up to flickrgeo.php) and the Google map. The first thing to do is to display the two parts on the same page without having them interact. Getting things displaying side by side ensures that you have the proper dependencies worked out. Once you get there, then you can wire the two pieces together. The first thing to do is to copy and paste code from your Flickr code and GMap code into one file. Here is one possible way to do it:

http://examples.mashupguide.net/ch10/gmapflickr1.html

Wiring Up the Bounding Box of the Google Map

Let’s get some interaction going between the Flickr parts and the Google map, now that they are contained in the same HTML page. Let’s wire up the bounding box of the Google map to be the source of the lat/long coordinates. Now, when you move or zoom the Google map, the new coordinates are written into the form elements (the lat0/lon0 and lat1/lon1 boxes) for the Flickr search.[145]

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta ?http-?equiv="content-type" content="text/html; charset=utf-8" />
    <title>gmapflickr.2.html</title>
    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=[API_KEY]"
      type="text/javascript"></script>
    <script type="text/javascript">

    //<![CDATA[

    function updateStatus() {
      var div = document.getElementById('mapinfo');
      div.innerHTML = map.getBounds();

      document.forms[0].lat0.value = map.getBounds().getSouthWest().lat();
      document.forms[0].lon0.value = map.getBounds().getSouthWest().lng();
      document.forms[0].lat1.value = map.getBounds().getNorthEast().lat();
      document.forms[0].lon1.value = map.getBounds().getNorthEast().lng();

      get_pictures();
    }

    function onMapMove() {
      updateStatus();
    }

    function onMapZoom(oldZoom, newZoom) {
      updateStatus();
    }

    function load() {

      ...
    }

    //]]>
    </script>
    <script type="text/javascript" src="/lib/yui/build/yahoo/yahoo.js"></script>
    <script type="text/javascript" src="/lib/yui/build/event/event.js"></script>
    <script type="text/javascript"
            src="/lib/yui/build/connection/connection.js"></script>
    <script type="text/javascript">
    //<![CDATA[
    function rspToHTML(rsp) {
      var s = "";
      // http://farm{farm-id}.static.flickr.com/{server-id}/{id}_{secret}_[mstb].jpg
      // http://www.flickr.com/photos/{user-id}/{photo-id}
      s = "total number available is: " + rsp.photos.total + "<br/>";

      for (var i=0; i < rsp.photos.photo.length; i++) {
        photo = rsp.photos.photo[i];
        t_url = "http://farm" + photo.farm + ".static.flickr.com/" + photo.server +
          "/" + photo.id + "_" + photo.secret + "_" + "t.jpg";
        p_url = "http://www.flickr.com/photos/" + photo.owner + "/" + photo.id;
        s +=  '<a href="' + p_url + '">' + '<img alt="'+ photo.title + '"src="' +
          t_url + '"/>' + '</a>';
      }
      return s;
    }

    var handleSuccess = function(o){

        ...
      }
   }

    var handleFailure = function(o){

      ...
    }

    var callback =
    {
      ...
    };

    function get_pictures() {

      ...
    }
    //]]>
    </script>
  </head>

  <body onload="load()" onunload="GUnload()">
    <form action="#" onsubmit="get_pictures(); return false;">
      <label>Search for photos with the following tag:</label>
      <input type="text" size="20" name="tags" value="flower" />
      <label> located at: lat0,lon0,lat1,lon1:</label>
      <input type="text" size="10" name="lat0" value="-90.0" />
      <input type="text" size="10" name="lon0" value="-180.0" />
      <input type="text" size="10" name="lat1" value="90.0" />
      <input type="text" size="10" name="lon1" value="180.0" />
      <label>at page</label><input type="text" size="4" name="page" value="1" />
      <label>with</label>
      <input type="text" size="3" name="per_page" value="1" />
      <label> per page.</label>
      <button type="submit">Go!</button>
    </form>
  <div id="pics"></div>
  <div id="map" style="width: 800px; height: 600px"></div>
  <div id="mapinfo"></div>
  </body>
</html>
         

Note that as soon as the page is loaded, the load function is called, which in turn calls updateStatus. The result is a search for photos using the starting parameters in the form. That is, geotagged photos tagged with flower are retrieved and displayed. You can change the starting photos by changing the default value for the <input> element to tags.

Making the Pictures Show Up in the Map

In this section, you’ll complete the wiring between the Flickr results and the map. I’ll show you how to display the images in the list on the map. This is done by creating markers for each of the photos and adding those markers as overlays to the map. That involves generating HTML to put into the markers.

I’ll remind you how to add overlays to a Google map using the API:

point = new GLatLng (37.87309185260284, -122.25508689880371);
marker = new GMarker(point);
map.addOverlay(marker);
         

Here’s the code with the new stuff in bold (see Figure 10-2):[146]

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta ?http-?equiv="content-type" content="text/html; charset=utf-8" />
    <title>gmapflickr.html</title>
    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=[API_KEY]"
      type="text/javascript"></script>
    <script type="text/javascript">

    //<![CDATA[
    // set up a blank object to hold markers that are added to the map
    markersInMap = {}

    function updateStatus() {
      var div = document.getElementById('mapinfo');
      div.innerHTML = map.getBounds();

      document.forms[0].lat0.value = map.getBounds().getSouthWest().lat();
      document.forms[0].lon0.value = map.getBounds().getSouthWest().lng();
      document.forms[0].lat1.value = map.getBounds().getNorthEast().lat();
      document.forms[0].lon1.value = map.getBounds().getNorthEast().lng();

      get_pictures();
    }

    // Creates a marker at the given point with the given msg.
    function createMarker(point, msg) {
      var marker = new GMarker(point);
      GEvent.addListener(marker, "click", function() {
        marker.openInfoWindowHtml(msg);
      });
      return marker;
    }

    function photos_to_markers(rsp) {

      // loop through the photos
      for (var i=0; i < rsp.photos.photo.length; i++) {
        var photo = rsp.photos.photo[i];
        // check whether marker already exists
        if (!(photo.id in markersInMap)) {
          var point = new GLatLng (photo.latitude, photo.longitude);
          var msg = photo.title + "<br>" + genPhotoLink(photo);
          map.addOverlay(createMarker(point, msg));
          markersInMap[photo.id] = "";  // don't know what to store so far.
        }
      }
    }

    function onMapMove() {
      updateStatus();
    }

    function onMapZoom(oldZoom, newZoom) {
      updateStatus();
    }

    function load() {

      ...
    }

    //]]>
    </script>
    <script type="text/javascript" src="/lib/yui/build/yahoo/yahoo.js"></script>
    <script type="text/javascript" src="/lib/yui/build/event/event.js"></script>
    <script type="text/javascript"
            src="/lib/yui/build/connection/connection.js"></script>
    <script type="text/javascript">
    //<![CDATA[

    function genPhotoLink(photo) {
        var t_url = "http://farm" + photo.farm + ".static.flickr.com/" +
          photo.server + "/" + photo.id + "_" + photo.secret + "_" + "t.jpg";
        var p_url = "http://www.flickr.com/photos/" + photo.owner + "/" + photo.id;

        return '<a href="' + p_url + '">' + '<img alt="'+ photo.title + '"src="' +
          t_url + '"/>' + '</a>';
    }

    function rspToHTML(rsp) {

      ...
    }

    var handleSuccess = function(o){
      div = document.getElementById('pics');
      div.innerHTML = "";  // blank out the div

      if(o.responseText !== undefined){
        //let's deposit the response in a global variable
        //so that we can look at it via the shell.
        window.response = o.responseText;
        window.rsp = eval('(' + o.responseText + ')');
        div.innerHTML = rspToHTML(window.rsp);
        photos_to_markers(window.rsp);
      }
   }

    var handleFailure = function(o){

      ...
    }

    var callback =
    {
      ...
    };

    function get_pictures() {

      ...
    }
    //]]>
    </script>
  </head>

  <body onload="load()" onunload="GUnload()">
    <form action="#" onsubmit="get_pictures(); return false;">
      <label>Search for photos with the following tag:</label>
      <input type="text" size="20" name="tags" value="flower" />
      <label> located at: lat0,lon0,lat1,lon1:</label>
      <input type="text" size="10" name="lat0" value="-90.0" />
      <input type="text" size="10" name="lon0" value="-180.0" />
      <input type="text" size="10" name="lat1" value="90.0" />
      <input type="text" size="10" name="lon1" value="180.0" />
      <label>at page</label><input type="text" size="4" name="page" value="1" />
      <label>with</label>
      <input type="text" size="3" name="per_page" value="1" />
      <label> per page.</label>
      <button type="submit">Go!</button>
    </form>
  <div id="pics"></div>
  <div id="map" style="width: 800px; height: 600px"></div>
  <div id="mapinfo"></div>
  </body>
</html>
         

This is just a beginning of a mashup between Flickr geotagged photos and Google Maps. Some ideas for elaborating this mashup include the following:

  • Refining the look and feel of the mashup (including removing <div id="mapinfo">, which currently displays the bounding box of the map)

  • Dealing with the fact that clicking a marker and its consequent window opening moves the map

  • Clustering photos that are at the same location (as is done in the Flickr map interface)