Wednesday, June 17, 2009

Quake Tracker using Google Maps API v3 and XML/Atom feed

For this, I used the following:
First, you will need to include the google maps api in your header:
<script src="http://maps.google.com/maps/api/js?sensor=true" type="text/javascript">
This line of code is slightly different from the v2 API because an API key isn't needed. This will probably change in the near future. It is also necessary to load jQuery, which can be done by downloading it from the above link, or loading it via Google's AJAX API.
When the document is ready:

   1:  // init map
   2:  var map;
   3:  $(document).ready(function(){
   4:    var latlng = new google.maps.LatLng(35.6802, -121.1165);
   5:    var myOptions = {
   6:       zoom: 3,
   7:      center: latlng,
   8:      mapTypeControl: true,
   9:      mapTypeControlOptions: {style: google.maps.MapTypeControlStyle.DROPDOWN_MENU},
  10:      navigationControl: true,
  11:      navigationControlOptions: {style: google.maps.NavigationControlStyle.SMALL},
  12:      mapTypeId: google.maps.MapTypeId.SATELLITE
  13:      };
  14:   
  15:    map = new google.maps.Map(document.getElementById("map"), myOptions);
  16:   
  17:  });

When the map is ready:

   1:  // SEE : http://www.xml.com/pub/a/2007/10/10/jquery-and-xml.html
   2:   
   3:  $('#map').ready(function(){
   4:    $.ajax({
   5:      type: "GET",
   6:      url: "getxml.php?q=http://www.earthquake.usgs.gov/eqcenter/catalogs/7day-M2.5.xml",
   7:      dataType: "xml",
   8:      success: function(xml){
   9:              $(xml).find('entry').each(function(){
  10:                  // Retrieve all needed values from XML
  11:                  var title = $(this).find('title').text();
  12:                  var updated = $(this).find('updated').text();
  13:                  var link = $(this).find('link').text();
  14:                  var summary = $(this).find('summary').text();
  15:                  var coord = $(this).find('georss\\:point').eq(0).text();
  16:                  if(!coord){var coord = $(this).find('point').text();};
  17:                  var points = coord.split(' ');
  18:                  var latitude = parseFloat(points[0]);
  19:                  var longitude = parseFloat(points[1]);    
  20:                  var elev = $(this).find('georss\\:elev').eq(0).text();
  21:                  if(!elev){var elev = $(this).find('elev').text();};
  22:                  var htmlString = "<b>" + title + "</b>" + "<p>" + summary + "<br>";
  23:                  // create a marker
  24:                  var myLatlng = new google.maps.LatLng(latitude,longitude);
  25:                      var marker = new google.maps.Marker(
  26:                      {
  27:                          position: myLatlng,
  28:                          map: map,
  29:                          title: title
  30:                      });
  31:                  
  32:                      addMarkerBubble(marker, map, htmlString);
  33:                      // Show output below map
  34:                      $('<li></li>')
  35:                          .html(title + ' (updated: ' + updated + ') at ' + points[0] + ', ' + points[1])
  36:                          .appendTo('#output');
  37:              });// end each
  38:          }
  39:      }); // end $.ajax
  40:  });// end function
  41:   
  42:  function addMarkerBubble(marker, map, message){    
  43:      // set balloon
  44:      var infowindow = new google.maps.InfoWindow(
  45:      {
  46:            content: message,
  47:        size: new google.maps.Size(400,200)
  48:      });        
  49:      
  50:      // add listener to marker
  51:      google.maps.event.addListener(marker, 'click' ,function(){
  52:          infowindow.open(map,marker);
  53:      });
  54:  };

Explanations:
First of all, I create a global variable called map, so I can use it in each function.
Then, when the document is ready, I get a geographic coordinate which Google Maps builds from maps.LatLng. This coordinate is in California, because I know there are quakes there.
Next, I've set a number of options (lines 6-12) for the map including:
  • zoom level
  • map center
  • a control to allow user to change the look of the map
  • a control to allow user to change zoom
  • default map type of "SATELLITE", which shows tectonic regions
The Google Maps API v3 makes it very easy to supply these options to the Map constuctor, as you can see on line 15 of the first code snippet. The map is loaded into the DOM object supplied as the first parameter in the constructor.
The next snippet is the AJAX call to retrieve the feed and build the markers and info windows when the GET succeeds.
Inside the function call, I've used jQuery's simplified DOM access for the returned XML. On line 9 of this snippet, I find each 'entry' node and then iterate over each of these nodes to find the text of each node value.
On lines 15-16 and 20-21, I check the variables because some browsers properly process namespaced nodes and some don't. This simple check allows the application to display properly in Chrome, Safari, Firefox, and Internet Explorer.
Next, I create a marker on the map at the given coordinate and give it a title (tooltip).
On line 32, I call the function 'addMarkerBubble' to create an info window and add the event to the map. After this, I add the title and updated date to a div element below the map.

Tuesday, June 16, 2009

JavaScript & CSS Modal Loading DIV

Things I've used in this:
What I do here is build a DIV element and show it on the page while Prototype's Ajax Request object is loading. Then, when the Request is finished, the DIV is removed from the document.

Here is the script necessary to make a request and build/show/remove the DIV.

   1:  /** Main.js
   2:   * @author Jim Schubert
   3:   * (c) 2009
   4:   */
   5:   
   6:  function showContent(file){
   7:      var content = new Ajax.Request(file,
   8:          {
   9:              method:'get',
  10:              onLoading: showLoading(),
  11:              onSuccess: function(request)
  12:              {
  13:                  var contentDIV = document.getElementById('content');
  14:                  contentDIV.innerHTML = "";
  15:                  contentDIV.innerHTML = request.responseText;    
  16:                  
  17:                  closeLoading();                        
  18:              },
  19:              onFailure: function(){ alert('Cannot process your request.');}
  20:              // onComplete: closeLoading()
  21:          });
  22:  };
  23:   
  24:  function onImgError(source) {
  25:    source.src = "img/img_error.png";
  26:    // disable onerror to prevent endless loop
  27:    source.onerror = "";
  28:    return true;
  29:  };        
  30:   
  31:  function showLoading() {
  32:      // create div element
  33:      var overlayDIV = document.createElement('div');
  34:      var loadingDIV = document.createElement('div');
  35:      overlayDIV.setAttribute('id', 'overlay');
  36:      overlayDIV.setAttribute('class', 'overlay');
  37:      overlayDIV.style.visibility = 'visible';
  38:      
  39:      loadingDIV.setAttribute('id', 'loading');
  40:      loadingDIV.setAttribute('class', 'modalPopup');
  41:      loadingDIV.innerHTML = '<center><img src="img/ajax-loader.gif"><br>Loading...</center>';
  42:          
  43:      overlayDIV.appendChild(loadingDIV);
  44:      var content = document.getElementById('bodyDocument');
  45:      if (content) {
  46:          content.appendChild(overlayDIV);
  47:          var overlay = document.getElementById('overlay');
  48:          if (overlay) {
  49:              overlay.style.visibility = 'visible';
  50:              overlay.style.height = '100%';
  51:          }
  52:      }
  53:      
  54:      return true;
  55:  };
  56:   
  57:  function closeLoading(){
  58:      var overlayDIV = document.getElementById('overlay');
  59:      var loadingDIV = document.getElementById('loading');
  60:      
  61:      if (overlayDIV) {
  62:          if (loadingDIV) {
  63:              overlayDIV.removeChild(loadingDIV);
  64:              document.getElementById('bodyDocument').removeChild(overlayDIV);
  65:          }
  66:          // else {alert("can't find loadingDIV")}
  67:      }
  68:      // else{ alert("Can't find overlay.");}
  69:      
  70:      return true;
  71:  };


Here is the stylesheet needed to make the dialog "modal".
   1:  .overlay {
   2:      visibility: hidden;
   3:      position: absolute;
   4:      left: 0px;
   5:      top: 0px;
   6:      width:100%;
   7:      height:100%;
   8:      text-align:center;
   9:      z-index: 1000;    
  10:      filter:alpha(opacity=70) !important;
  11:      opacity:0.8 !important;
  12:      elevation:above !important;
  13:      /* background-color:Gray !important; */
  14:      background-image: url(../img/overlay.png);
  15:  }
  16:   
  17:  .modalPopup 
  18:  {
  19:      display: block;
  20:      margin-left: auto;
  21:      margin-right: auto;    
  22:      width:180px;
  23:      margin: 100px auto;
  24:      background-color:#F1F1F1 !important;
  25:      border:1px solid #000;
  26:      padding:15px;
  27:      text-align:center;
  28:      elevation:above !important;
  29:  }

In order to use this code, you'll have to give your body tag an id of 'bodyDocument', and the div tag to receive your ajax request should be called 'content'.
I've also included an image error detecting script, which replaces broken images with a default image. Unfortunately, if you're trying to produce 100% compliant HTML documents, this won't be compliant unless you're using HTML5. In order to use this, you'll have to do something like <img src='asdf.gif' onerror='javascript:onImgError(this)' alt='image'>
I've left some code commented out, which will help in debugging your efforts. And, in the stylesheet, you have the option of using a flat background instead of a PNG with transparency.

Archive