//\/////

//\  SOMIAnime - You may not remove or change this notice.

//\  Do not sell this as your own work or remove this copyright notice.

//\  $Id: SOMIAnime.js,v 1.84 2008/03/13 19:09:58 imacleod Exp $

//\  

//\  

//\  This software is Copyright © 2006 The Regents of the University of

//\  California. All Rights Reserved.

//\  

//\  Permission to use, copy, modify, and distribute this software and its

//\  documentation for educational, research and non-profit purposes,

//\  without fee, and without a written agreement is hereby granted, provided

//\  that the above copyright notice, this paragraph and the following three

//\  paragraphs appear in all copies.

//\  

//\  Permission to incorporate this software into commercial products may be

//\  obtained by contacting

//\  Technology Transfer Office

//\  9500 Gilman Drive, Mail Code 0910

//\  University of California

//\  La Jolla, CA 92093-0910

//\  (858) 534-5815

//\  invent@ucsd.edu

//\  

//\  This software program and documentation are copyrighted by The Regents of the

//\  University of California. The software program and documentation are

//\  supplied "as is", without any accompanying services from The Regents.

//\  The Regents does not warrant that the operation of the program will be

//\  uninterrupted or error-free. The end-user understands that the program was

//\  developed for research purposes and is advised not to rely exclusively on

//\  the program for any reason.

//\  

//\  IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO

//\  ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR

//\  CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING

//\  OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,

//\  EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF

//\  THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF

//\  CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,

//\  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF

//\  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

//\  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND

//\  THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO

//\  PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR

//\  MODIFICATIONS.

//\  

//\/////


// Below are the ONLY global variables created or used within this file.

// Please think carefully about whether or not you need to modify or

// add to this list because they could conflict with other globals

// introduced in other source files.

//


var global_Topics = new Array();   	// Integer-indexed

var global_ActiveTopic = new Array();     // String-indexed

var global_States = new Array();        // String-indexed

var global_Features = new Array();      // String-indexed

var global_FeatureCollection;
var global_Frames = new Array(); 	// Integer-indexed [D 1 : frames]

var global_featureIDs = new Array();    // Integer-indexed


var global_w3c = (document.getElementById)?true:false;  // Browser MUST be w3c compatible...sorry

var global_debug = false;
var global_shadowDIV = false;
var global_overlayDIV = false;
var global_statusDIV = false;
var global_timelineBarDIV = false;
var global_timelineBarTextDIV = false;
var global_animationEventFadeDIV = false;
var global_currentAnimationEventFadeRate = false;
var global_animationFeatureDIV = false;
var global_animationLegendDIV = false;
var global_newScrollerContent = '';

var global_timelineBarLoadedColor='navy' ;       // TIMELINE BAR COLOR

var global_timelineBarUnloadedColor='lightgrey'; // BGCOLOR OF UNLOADED AREA

var global_timelineBarHeight=20;              // HEIGHT OF TIMELINE BAR IN PIXELS

var global_timelineBarWidth=400;              // WIDTH OF THE BAR IN PIXELS

var global_timelineBarBorderColor='black';       // COLOR OF THE BORDER

var global_timelineBarPercentLoaded=0.0;
var global_timelineBarBackgroundDIV=false;
var global_timelineBarForegroundDIV=false;
global_timelineBarHeight = Math.max(4,global_timelineBarHeight);



// Structures pertaining to state definitions

//


var global_currentTopic;
var global_currentFrameNumber = 0;
var global_currentFadingFrameNumber = false;
var global_totalNumberOfFrames = 0;
var global_currentFrameRateInMilliseconds = 500;
var global_currentFrameDirection = 'forward'; // or 'reverse'

var global_currentAnimationState = 'stop'; // or 'pause' or 'play'

var global_runningAnimationID;
var global_reloadingAnimationID;



function insulateURI (URI,type) {
   var r1 = new RegExp(/^http:/);
   var r2 = new RegExp(document.domain);
   if (! URI.match(r1)) {
      URI = 'http://' + document.domain + URI;
   }

   if (! URI.match(r2)) {
      // Use SOMI Ajax proxy on server to fetch URI from

      // somewhere else.

      //alert('This URL is REMOTE and must be insulated by proxy (' + URI + ')');

      var rx = new RegExp("\&","g");
      URI = URI.replace(rx,"%26");
      URI = '\'' + URI + '\'';
      var host = window.location.host.split(":");
      if ((type != undefined) && (type == 'plain')) {
        URI = "http://" + host[0] + "/cgi-bin/somiProxyPlain.cgi?url=" + URI;
      } else {
        URI = "http://" + host[0] + "/cgi-bin/somiProxy.cgi?url=" + URI;
      };
   } else {
     //alert('This URL is LOCAL and need not be proxied (' + URI + ')');

   }
   return URI;
};


function loadTopics(topicListURI) {
  // ----------- A.1 ------------

  // The topic loading process starts here.

  //

  // ----------------------------


  // 1. Obtain an XMLHttpRequest instance.

  //

  var req = G_newXMLHttpRequest();
  if (! req) {
     // Unsupported browser type

     alert("loadTopics : Error : Could not perform AJAX (needed for our RSS capabilities) functions on this browser.  Sorry.  Please turn off RSS in SOMI.");
     return false;
  }
 

  if (SOMItb != undefined)
     viewerToolboxPresentAnimation();

  // 2. Set the handler function to receive callback notificiations

  //    from the request object.

  //

  if (document.getElementById('animationStatus') != undefined) {
    global_statusDIV = document.getElementById('animationStatus');
  }
  if (global_statusDIV) {
    global_statusDIV.value = 'Fetching available topics...';
  }
  var handlerFunction = G_getReadyStateHandler(req, processTopics);
  req.onreadystatechange = handlerFunction;
  if (! global_overlayDIV) {
    global_overlayDIV = SOMIv.tileWell.overlay;
  }
  if (! global_shadowDIV) {
    global_shadowDIV = SOMIv.tileWell.shadow;
  }

  // 3. Dispatch request for asynchronous handling.

  //

  //    Third parameter in below open() indicates asynchronous request.

  //

  try {
      req.open('GET', insulateURI(topicListURI), true);
      req.send(null);
      // Request for XML document was dispatched.

      // Processing will occur asynchronously.  This

      // procedure did its job.  Just return true and

      // move on.

      return true;
  } catch (e) {
    alert('loadTopics : Error : ' + e.message + ' occurred because ' + e.reason +'.');
    return false;
  }
};


function processTopics (req, fetchedDate) {
  // ----------- A.2 ------------

  // Take XMLHttpRequest (that has "returned") and process specific to

  // the topic list contained in the document fetched.

  // Document handling process starts here.

  //

  // ----------------------------


  // Note date of fetch and perform any notifications

  // to the user here.

  //

  var now = new Date();
  if (global_statusDIV) {
    global_statusDIV.value += 'ok';
  }
  if (global_debug && global_statusDIV) {
    global_statusDIV.value += '  |  Completed : ' + now.toGMTString();
  }

  //   Parse returned content from text into xml DOM

  //

  var dom = G_parseXMLTextIntoDOM(req.responseText);
  req = false;

  //   Parse DOM and update state of SOMI

  //

  var status = readTopics(dom);
  if (status) {
     return true;
  } else {
     alert("processTopics : Error : AJAX failed!");
     return false;
  }
};


function readTopics(dom) {
  // ----------- A.3 ------------

  // Final step in handling our base set of available topics.

  // Take the XML DOM we are expecting and do something with the content.

  //

  // Expected is an XML DOM object, complete, loaded, done.  Ready

  // to be used.  Synchronicity is EXPECTED to have been dealt with

  // prior to calling this function, not after.

  //

  // ----------------------------


  // We're expecting an XML DOM object so it

  // better be one.

  if (typeof dom == undefined) {
    alert("readTopics : Error : Type of returned document is not defined....failure!");
    return false;
  }
  var items = dom;

  //  Step 1) Reset global set of available "topics" to choose from.

  //

  if (global_statusDIV) {
    global_statusDIV.value = '';
  }
  global_Topics = new Array();

  //  Step 2) Remove all actual choices from menu

  //          that user chooses a topic from.

  //

  if (document.getElementById('animationTopicSelect') == undefined) {
    document.getElementById('animationTopic').innerHTML = 'Topic:&nbsp;<select id="animationTopicSelect" type="pulldown" name="animationTopic" onchange="topicPresentInfo(this.value)"></select>';
  }
  var topicSelect = document.getElementById('animationTopicSelect');
  topicSelect.selectedIndex = 0;
  topicSelect.options.length = 0;

  //  Step 3) Process topics in collection.

  //

  var topics = items.getElementsByTagName('topic');
  for (var i = 0, len = topics.length; i < len; i+=1) {
    var topic = topics[i];

    // a. Collect information about this topic and throw onto

    //    a global array of topics.

    //

    var name = topic.getElementsByTagName('name')[0].firstChild.nodeValue;
    var contact = topic.getElementsByTagName('contact')[0].firstChild.nodeValue;
    var presence = topic.getElementsByTagName('presence')[0].firstChild.nodeValue;
    var description = topic.getElementsByTagName('description')[0].firstChild.nodeValue;
    var numberOfVariables = 1;
    var variables = [];
    if (topic.getElementsByTagName('var').length) {
      var vars = topic.getElementsByTagName('var');
      for (var m = 0, t_l = vars.length; m < t_l; m+=1) {
        var v = vars[m];
        var vname = v.getElementsByTagName('name')[0].firstChild.nodeValue;
        var vorder = v.getElementsByTagName('order')[0].firstChild.nodeValue;
        variables[m] = {name : vname, order : vorder};
	numberOfVariables++;
      };
    }
    

    var topicId = false;
    if (topic.getElementsByTagName('id').length) {
      topicId = topic.getElementsByTagName('id')[0].firstChild.nodeValue;
    }
    var autoDisplay = false;
    if (topic.getElementsByTagName('autoDisplay').length) {
      autoDisplay = topic.getElementsByTagName('autoDisplay')[0].firstChild.nodeValue;
    }
    var stateSymbolPattern = false;
    if (topic.getElementsByTagName('stateSymbolPattern').length) {
      stateSymbolPattern = topic.getElementsByTagName('stateSymbolPattern')[0].firstChild.nodeValue;
    }
    var overlaySymbolPattern = false;
    if (topic.getElementsByTagName('overlaySymbolPattern').length) {
      overlaySymbolPattern = topic.getElementsByTagName('overlaySymbolPattern')[0].firstChild.nodeValue;
    }
    var defaultStateSymbolURI = false;
    if (topic.getElementsByTagName('defaultStateSymbolURI').length) {
      defaultStateSymbolURI = topic.getElementsByTagName('defaultStateSymbolURI')[0].firstChild.nodeValue;
    }
    var stateSymbolsURI = false;
    if (topic.getElementsByTagName('stateSymbolsURI').length) {
      stateSymbolsURI = topic.getElementsByTagName('stateSymbolsURI')[0].firstChild.nodeValue;
    }
    var shadowSymbolURI = false;
    if (topic.getElementsByTagName('shadowSymbolURI').length) {
      shadowSymbolURI = topic.getElementsByTagName('shadowSymbolURI')[0].firstChild.nodeValue;
    }
    var featureBaseURI = topic.getElementsByTagName('featureBaseURI')[0].firstChild.nodeValue;
    var eventSourceURI = topic.getElementsByTagName('eventSourceURI')[0].firstChild.nodeValue;
    global_Topics[i] = {id : topicId,
                        name : name,
			numberOfVariables : numberOfVariables,
                        variables : variables,
                        contact : contact,
                        presence : presence,
			autoDisplay : autoDisplay,
                        description : description,
                        stateSymbolPattern : stateSymbolPattern,
                        overlaySymbolPattern : overlaySymbolPattern,
                        defaultStateSymbolURI : defaultStateSymbolURI,
                        stateSymbolsURI : stateSymbolsURI,
                        shadowSymbolURI : shadowSymbolURI,
                        featureBaseURI : featureBaseURI,
                        eventSourceURI : eventSourceURI};

    // b. Add this topic to user-selectable list in form.

    //

    topicSelect.options[topicSelect.options.length] = new Option(name.substring(0,20),i);
  }


  //  Step 4) Display information about the first topic in

  //          in the stack.

  //

  topicPresentInfo(0);

  return true;
};


function topicPresentInfo (topicId) {
  var name = '';
  var description = '';
  
  if (topicId >= 0) {
    var topic = global_Topics[topicId];
    name = topic.name;
    description = topic.description;
  }

  var target = document.getElementById('animationTopicName').getElementsByTagName('font')[0];
  target.innerHTML = 'Topic : <b>' + name + '</b><br>' + description;
  //document.getElementById('animationTopicDescription').getElementsByTagName('font')[0].innerHTML = 'Description : ' + description;

   
  return true;
};


function featurePresentInfo (featureId) {
  //if (featureId >= 0) {

    var feature = global_Features[featureId];
    document.getElementById('animationFeatureInfo').getElementsByTagName('font')[0].innerHTML = 'Feature : <b>' + feature.id + '</b>&nbsp;&nbsp;Longitude:&nbsp;' + feature.longitude + '&nbsp;&nbsp;Latitude:&nbsp;' + feature.latitude;
  
  //;

    // If feature is on the current map somewhere then

    // "flash" it (e.g. enlarge briefly) to show user

    // where it is.

    //

    if (feature.onMap) {
      if (document.getElementById('feature-' + feature.id)) {
        var featureElement = document.getElementById('feature-' + feature.id);
        var icon = featureElement.getElementsByTagName('img')[0];

        // Recenter on feature!

        //

        var y = parseInt(featureElement.style.top) + parseInt(icon.width / 2);
        var x = parseInt(featureElement.style.left) + parseInt(icon.height / 2);
        var dim = SOMIv.dimensions;
        var currentTileXFromCenterView = getTileXOfViewCenter();
        var currentTileYFromCenterView = getTileYOfViewCenter();
        var dx = -1 * (x - currentTileXFromCenterView);
        var dy = -1 * (y - currentTileYFromCenterView);
        positionTileWRTViewer({x : dx, y : dy});
        SOMIv.start = {x: x, y: y}; // this is reset each time that the mouse is pressed anew

        dim.delta_x += dx;
        dim.delta_y += dy;

        SOMIv.pressed = false;

        // "flash" the feature!

        //

        //icon.width += 3;

        //icon.height += 3;

        icon.style.border = 2;
        setTimeout("featureUnpresentInfo('" + featureId + "')",2000);
      };
    } else {
      document.getElementById('animationFeatureInfo').getElementsByTagName('font')[0].innerHTML += '&nbsp;&nbsp;(off map)';
    }
  return true;
};

function featureUnpresentInfo (featureId) {
  var feature = global_Features[featureId];
  // Return feature icon to normal state.

  //

  if (feature.onMap) {
    if (document.getElementById('feature-' + feature.id)) {
      var featureElement = document.getElementById('feature-' + feature.id);
      var icon = featureElement.getElementsByTagName('img')[0];
      //icon.width -= 3;

      //icon.height -= 3;

      icon.style.border = 0;
    };
  }

  return true;
};


function featureJumpTo (featureId) {
  var feature = false;
  if (typeof featureId != undefined) {
    feature = global_Features[featureId];
    refreshMap(feature.longitude,feature.latitude,'refreshButton');
  } else {
    var featureSelect = document.getElementById('animationFeatureSelect');
    if (featureSelect.selectedIndex > 0) {
      var id = featureSelect.options[featureSelect.selectedIndex].value;
      feature = global_Features[id];
    };
  }
  if (feature) {
    refreshMap(feature.longitude,feature.latitude,'refreshButton');
  }
  return true;
};

function featureGetInfo (featureId) {
  if (featureId >= 0) {
    var feature = global_Features[featureId];
    //"<a style=\"text-decoration:none\" href=\"" + global_FeatureCollection.infoService + id

    
  }
  return true;
};





function selectAnimationByID (topicId) {
  if (topicId == undefined) {
    alert("selectAnimationByID : Error : No animation ID provided.");
    return false;
  }

  // Look for a match and select it.  Nothing more.

  //

  var topicSelect = document.getElementById('animationTopicSelect');

  for (var i = 0, len = topicSelect.length; i < len; i+=1) {
    var option = topicSelect.options[i];

    var value = option.value;
    if (global_Topics[value].id == topicId) {
      topicSelect.selectedIndex = i;
      topicPresentInfo(i);
      if (global_statusDIV) {
        global_statusDIV.value = 'Animation : ' + topicId + ' selected';
      }
      break;
    };
  }
  return true;
};





function loadAnimation() {
  // ----------- A.1 ------------

  // The animation loading process starts here.

  //

  // ----------------------------


  // 1. Obtain an XMLHttpRequest instance.

  //

  var req = G_newXMLHttpRequest();
  if (! req) {
     // Unsupported browser type

     alert("loadAnimation : Error : Could not perform AJAX (needed for our RSS capabilities) functions on this browser.  Sorry.  Please turn off RSS in SOMI.");
     return false;
  }


  // 1.2 Find out which topic is currently selected

  //     and grab it's information from global_Topics.

  //

  var topicSelect = document.getElementById('animationTopicSelect');

  // 1.3 Cancel any currently running activities.

  //

  stopSOMIReload();
  stopSOMIAnimation();

  global_currentTopic = global_Topics[topicSelect.options[topicSelect.selectedIndex].value];
  var symbolsURI = global_currentTopic.stateSymbolsURI;
  var featuresURI = global_currentTopic.featureBaseURI;
  var eventsURI = global_currentTopic.eventSourceURI;
  var featurePresence = global_currentTopic.presence;

  // 1.5 Register the resources (URI, # times fetched, last fetched)

  //

  global_ActiveTopic["features"] = {uri : featuresURI, presence : featurePresence, timesLoaded : 0, lastFetched : null};
  global_ActiveTopic["events"] = {uri : eventsURI, timesLoaded : 0, lastFetched : null};
  global_ActiveTopic["positions"] = {uri : null, timesLoaded : 0, lastFetched : null};

  // 2. If current topic has symbols hard-coded (typical)

  //    then enter the fetch-parse-read process now.  Otherwise

  //    the symbols used will not be determined until the

  //    event states are parsed.

  //

  if (global_currentTopic.stateSymbolsURI) {
    // 2a. Set the handler function to receive callback notificiations

    //     from the request object.

    //

    if (global_statusDIV) {
      global_statusDIV.value = 'Fetching symbol set...';
    }
    var handlerFunction = G_getReadyStateHandler(req, processStateSymbols);
    req.onreadystatechange = handlerFunction;

    // 2b. Dispatch request for asynchronous handling.

    //

    //    Third parameter in below open() indicates asynchronous request.

    //

    try {
      req.open('GET', insulateURI(symbolsURI), true);
      req.send(null);
      // Request for XML document was dispatched.

      // Processing will occur asynchronously.  This

      // procedure did its job.  Just return true and

      // move on.

      return true;
    } catch (e) {
      alert('loadAnimation : Error : ' + e.message + ' occurred because ' + e.reason +'.');
      return false;
    };
  } else {
    if (global_statusDIV)
      global_statusDIV.value = 'Wildcard symbols';
    //  Skip symbol XML fetching...go directly to next stage - collect features.

    //

    setTimeout("loadFeatureBase()",100);
  }
};


function processStateSymbols (req, fetchedDate) {
  // ----------- A.2 ------------

  // Take XMLHttpRequest (that has "returned") and process specific to

  // the state symbols contained in the document fetched.

  // Document handling process starts here.

  //

  // ----------------------------


  var now = new Date();
  if (global_statusDIV) {
    global_statusDIV.value += 'ok';
  }
  if (global_debug && global_statusDIV) {
    global_statusDIV.value += '  |  Completed : ' + now.toGMTString();
  }

  //   Parse returned content from text into xml DOM

  //

  var dom = G_parseXMLTextIntoDOM(req.responseText);
  req = false;

  //   Parse DOM and update state of SOMI

  //

  var status = readStateSymbols(dom);
  if (status) {
     return true;
  } else {
     alert("processStateSymbols : Error : AJAX failed!");
     return false;
  }
};


function readStateSymbols(dom) {
  // ----------- A.3 ------------

  // Final step in handling our symbol set.

  // Take the XML DOM we are expecting and do something with the content.

  //

  // Expected is an XML DOM object, complete, loaded, done.  Ready

  // to be used.  Synchronicity is EXPECTED to have been dealt with

  // prior to calling this function, not after.

  //

  // ----------------------------


  // We're expecting an XML DOM object so it

  // better be one.

  if (typeof dom == undefined) {
    alert("readStateSymbols : Error : Type of returned document is not defined....failure!");
    return false;
  }
  var items = dom;

  //  Step 1) Reset global sets like "states" and "features"

  //

  if (global_statusDIV) {
    global_statusDIV.value = '';
  }
  global_States = new Array();
  global_Features = new Array();

  //  Step 2) Process state definitions

  //

  if (! global_animationLegendDIV) {
    global_animationLegendDIV = document.getElementById('animationLegend');
    global_animationLegendDIV.innerHTML = '<table cellpadding="0" cellspacing="0" width="200" units="pixels" border="0" bgcolor="#FFFFFF">';
  }
  var states = items.getElementsByTagName('state');
  for (var i = 0, len = states.length; i < len; i+=1) {
    var state = states[i];

    var value = state.getElementsByTagName('value')[0].firstChild.nodeValue;
    var symbol = state.getElementsByTagName('symbol')[0].firstChild.nodeValue;
    var label = state.getElementsByTagName('label')[0].firstChild.nodeValue;
    var def = state.getElementsByTagName('default')[0].firstChild.nodeValue;
    global_States[value] = {value : value, label : label, symbol : symbol, dfault : def};
    if (def == 'yes')
      global_currentTopic.defaultStateSymbolURI = global_States[value].symbol;
    global_animationLegendDIV.innerHTML += '<tr><td class="inl" align="center" width="50" units="pixels"><img class="inl" src="' + symbol + '" border="0" ></td><td align="left" width="150" units="pixels"><font style="font-size:8pt;font-family:helvetica, arial, sans-serif;">&nbsp;&nbsp;' + label + '</font></td></tr>';
  }
  global_animationLegendDIV.innerHTML += '</table>';

  //  Step 3) Proceed to the next stage - collect features.

  //

  setTimeout("loadFeatureBase()",100);

  return true;
};



function loadFeatureBase() {
  // ----------- A.1 ------------

  // The feature base handling process starts here.

  // 

  // ----------------------------

  
  // 1. Obtain an XMLHttpRequest instance.

  //

  var req = G_newXMLHttpRequest();
  if (! req) {
     // Unsupported browser type

     alert("loadFeatureBase : Error : Could not perform AJAX (needed for our RSS capabilities) functions on this browser.  Sorry.  Please turn off RSS in SOMI.");
     return false;
  }

  // 2. Set the handler function to receive callback notificiations

  //    from the request object.

  //

  if (global_statusDIV) {
    global_statusDIV.value = 'Fetching feature base...';
  }
  var handlerFunction = G_getReadyStateHandler(req, processFeatureBase);
  req.onreadystatechange = handlerFunction;

  // 3. Dispatch request for asynchronous handling.

  //

  //    Third parameter in below open() indicates asynchronous request.

  //

  try {
      req.open('GET', insulateURI(global_ActiveTopic["features"].uri), true);
      req.send(null);
      // Request for XML document was dispatched.

      // Processing will occur asynchronously.  This

      // procedure did its job.  Just return true and

      // move on.

      return true;
  } catch (e) {
    alert('loadFeatureBase : Error : ' + e.message + ' occurred because ' + e.reason +'.');
    return false;
  }
}; 





function processFeatureBase (req, fetchedDate) {
  // ----------- A.2 ------------

  // Take XMLHttpRequest (that has "returned") and process specific to

  // the feature base contained in the document fetched.

  // Document handling process starts here.

  // 

  // ----------------------------


  // Note date of fetch and perform any notifications

  // to the user here.

  //

  global_ActiveTopic["features"].lastFetched = fetchedDate;
  var now = new Date();
  if (global_statusDIV) {
    global_statusDIV.value += 'ok';
    global_statusDIV.value += 'Parsing feature base';
  }

  if (global_debug && global_statusDIV) {
    global_statusDIV.value += '  |  Completed : ' + now.toGMTString();
  }

  //   Parse returned content from text into xml DOM

  //

  var dom = G_parseXMLTextIntoDOM(req.responseText);
  req = false;
  if (global_statusDIV) {
    global_statusDIV.value += 'ok';
  }

  //   Parse DOM and update state of SOMI

  //

  var status = readFeatureBase(dom);
  if (status) {
     return true;
  } else {
     alert("processFeatureBase : Error : AJAX failed!");
     return false;
  }
};



function readFeatureBase(dom) {
  // ----------- A.3 ------------

  // Final step in handling our base set of features.

  // Take the XML DOM we are expecting and do something with the content.

  //

  // Expected is an XML DOM object, complete, loaded, done.  Ready

  // to be used.  Synchronicity is EXPECTED to have been dealt with

  // prior to calling this function, not after.

  //

  // ----------------------------


  // We're expecting an XML DOM object so it

  // better be one.

  if (typeof dom == undefined) {
    alert("readFeatureBase : Error : Type of returned document is not defined....failure!");
    return false;
  }
  var items = dom;
  if (global_statusDIV) {
    global_statusDIV.value = 'Reading feature base...';
  }

  //  Step 1) Remove all actual choices from menu

  //          that user chooses a feature from.

  //

  if (! global_animationFeatureDIV) {
    global_animationFeatureDIV = document.getElementById('animationFeature');
    global_animationFeatureDIV.innerHTML = 'Feature:&nbsp;<select id="animationFeatureSelect" type="pulldown" name="animationFeature" onchange="featurePresentInfo(this.value)"></select><a href=\"javascript:void(0)\" onClick=\"event.returnValue=false;featureGetInfo()\"><b>Info</b></a>';
  }


  //  Step 2) Note menu user chooses features from.

  //

  var featureSelect = document.getElementById('animationFeatureSelect');


  //  Step 3) Prepare an http POST to "lookup" image coordinates for features

  //          in DOM:

  //            ex. http://trinity/cgi-bin/mssomi4x?cx=-105.0&cy=34.0&proj=lambazeqarea&scale=10000000&width=800&height=800&point=quake24|-102.0|34.0|

  //

  // Be sure to use the "original" map center coordinates to do feature

  // lookups since other code in this interface may be updating the "form"

  // cx and cy with current viewer window position, etc.  We care about

  // the center of the image originally sent to the browser.

  var smxParams = 'cx=' + SOMIv.centerLongitudeFirst;
  smxParams += '&cy=' + SOMIv.centerLatitudeFirst;
  smxParams += '&proj=' + SOMIv.projection;
  smxParams += '&scale=' + SOMIv.scale + '&width=';
  smxParams += SOMIv.dimensions.tileSize.x;
  smxParams += '&height=' + SOMIv.dimensions.tileSize.y;
  var needToDoPositionMapping = false;

 
  //  Step 4) Process feature collection properties.

  //

  var fiSvc;
  var fciSvc;
  if (items.getElementsByTagName('featureInfoService').length) {
    fiSvc = items.getElementsByTagName('featureInfoService')[0].firstChild.nodeValue;
  } else {
    fiSvc = 'http://www.google.com/search?hl=en&lr=&btnG=Search&q=';
  }
  if (items.getElementsByTagName('featureContextImageService').length) {
    fciSvc = items.getElementsByTagName('featureContextImageService')[0].firstChild.nodeValue;
  } else {
    fciSvc = null;
  }
  var fbName = items.getElementsByTagName('featureBaseName')[0].firstChild.nodeValue;
  global_FeatureCollection = {name : fbName, infoService : fiSvc, contextImageService : fciSvc};


  //  Step 5) Process feature set

  //

  var features = items.getElementsByTagName('feature');
  for (var i = 0, len = features.length; i < len; i+=1) {
    var feature = features[i];
    var t_ids = feature.getElementsByTagName('id');
    var t_longitudes = feature.getElementsByTagName('longitude');
    var t_latitudes = feature.getElementsByTagName('latitude');

    if (t_ids.length && t_longitudes.length && t_latitudes.length) {
      // a. Collect information returned for this feature and throw onto

      //    a global array of features.

      //

      if (t_longitudes[0].firstChild != undefined) {
        var id = t_ids[0].firstChild.nodeValue;
        var longitude = t_longitudes[0].firstChild.nodeValue;
        var latitude = t_latitudes[0].firstChild.nodeValue;
        global_Features[id] = {id : id,
                               longitude : longitude,
                               latitude : latitude,
                               xpx : false,
                               ypx : false,
                               onMap : false,
                               frames : 'undefined'};

        // b. Add this feature to user-selectable list in form.

        //

        featureSelect.options[featureSelect.options.length] = new Option(id.substring(0,20),id);

        // c. Add this feature to our pending AJAX request to somi4x for

        //    positioning this item on our map image.

        //

        smxParams += '&point=feature-' + id + '|' + longitude + '|' + latitude + '|';
        needToDoPositionMapping = true;
      };
    };
  }
  G_sortSelectFormElement(featureSelect,true);
  if (global_statusDIV) {
    global_statusDIV.value += 'ok';
  }

  //  Step 6) At least one station was detected in feature set.  AJAX post back

  //          to server (with handler) to lookup position (if on map)

  //          and place in exactly correct location.

  //

  if (needToDoPositionMapping) {
     getFeaturePositions(smxParams);
  }

  global_ActiveTopic["features"].timesLoaded += 1;

  return true;
};




function getFeaturePositions (postString,responseHandler) {
  // ----------- E ------------

  //

  // --------------------------


    // 1. Obtain an XMLHttpRequest instance.

    //

    var req = G_newXMLHttpRequest();
    if (! req) {
      // Unsupported browser type

      alert("getFeaturePositions : Error : AJAX not available.  Sorry.  Please turn off RSS in SOMI.");
      return false;
    }

    // 2. Set the handler function to receive callback notificiations

    //    from the request object.

    //

    if (global_statusDIV) {
      global_statusDIV.value = 'Fetching feature positions...';
    }
    var handlerFunction;
    if (responseHandler != undefined) {
      handlerFunction = G_getReadyStateHandler(req, responseHandler);
    } else {
      handlerFunction = G_getReadyStateHandler(req, processFeaturePositions);
    }
    req.onreadystatechange = handlerFunction;

    // 3. Dispatch request for asynchronous handling.

    //

    //    Third parameter in below open() indicates asynchronous request.

    //

    try {
      // Open an HTTP POST connection to the object geo->image position

      // conversion service for SOMI.

      //

      var host = window.location.host.split(":");
      req.open('POST', 'http://' + host[0] + '/cgi-bin/somi4x', true);

      // Specify that the body of the request contains form data.

      req.setRequestHeader("Content-Type","application/x-www-form-urlencoded");

      // Send form encoded data.

      req.send(postString);
      // Request for XML document was dispatched.

      // Processing will occur asynchronously.  This

      // procedure did its job.  Just return true and

      // move on.

      return true;
    } catch (e) {
      alert('getFeaturePositions : Error : ' + e.message + ' occurred because ' + e.reason +'.  Sorry.  Please turn off RSS in SOMI.');
      return false;
    }
};


function processFeaturePositions(req,fetchedDate) {
  // ----------- F ------------

  // Take XMLHttpRequest (that has "returned") and process specific to

  // the type of information we are expecting.

  // Document handling process starts here.

  //

  // --------------------------


  // Note date of fetch and perform any notifications

  // to the user here.

  //

  global_ActiveTopic["positions"].lastFetched = fetchedDate;
  var now = new Date();
  if (global_statusDIV) {
    global_statusDIV.value += 'ok';
  }
  if (global_debug && global_statusDIV) {
    global_statusDIV.value += '  |  Completed : ' + now.toGMTString();
  }

  //   Parse returned content from text into xml DOM

  //

  var dom = G_parseXMLTextIntoDOM(req.responseText);
  req = false;

  //   Parse DOM and update state of SOMI

  //

  var status = updateFeaturePositions(dom);
  if (status) {
     return true;
  } else {
     alert("processFeaturePositions : Error : AJAX failed  Sorry.  Please turn off RSS in SOMI.");
     return false;
  }
};


function updateFeaturePositions(dom) {
  // ----------- G ------------

  //

  // --------------------------


  // We're expecting an XML DOM object so it

  // better be one.

  if (typeof dom == undefined) {
    alert("updateFeaturePositions : Error : Type of returned document is not defined....failure!");
    return false;
  }
  var items = dom;
  var points = items.getElementsByTagName('point');
  var defaultImage = new Image();
  defaultImage.src = global_currentTopic.defaultStateSymbolURI;

  for (var i = 0, len = points.length; i < len; i+=1) {
    var point = points[i];

    // a. Collect information returned for this point.

    //

    var elementId = point.getElementsByTagName('id')[0].firstChild.nodeValue;
    var parts = elementId.split("-");
    var shadowId = 'shadow-' + parts[1];
    var overlayId = 'overlay-' + parts[1];
    var featureId = parts[1];
    var xPixel = parseInt(point.getElementsByTagName('xPixel')[0].firstChild.nodeValue);
    var yPixel = parseInt(point.getElementsByTagName('yPixel')[0].firstChild.nodeValue);

    // b. Determine if point is on the map or not.

    //

    if ( (xPixel != -1) && (yPixel != -1) && (global_Features[featureId] != undefined)) {
      global_Features[featureId].onMap = true;

      // c. Create, or destroy and create, a floating element

      //    for each feature in the returned set.  If floating

      //    element for current feature is already in the page

      //    then, to simplify matters for browser compatibility,

      //    we simply remove the element (and all children) and

      //    then readd it.

      //

      G_removeDOMElement(elementId);
      G_removeDOMElement(shadowId);
      G_removeDOMElement(overlayId);

      // d. Create a new floating element for current feature, with

      //    with mouseover tooltip, current status, correct image (for the

      //    state display), correct location, etc

      //

      var iSrc = defaultImage.src;
      var type = global_FeatureCollection.name;
      var iWidth;
      var iHeight;
      iWidth = defaultImage.width;
      iHeight = defaultImage.height;
      var iStyleTop;
      var iStyleLeft;
      iStyleTop =  (yPixel - parseInt(iWidth / 2)) + 'px';
      iStyleLeft = (xPixel - parseInt(iHeight / 2)) + 'px';
      // Store the position for transient features.

      global_Features[featureId].xpx = iStyleLeft;
      global_Features[featureId].ypx = iStyleTop;
      var href = 'javascript:void(0)';
      if (global_FeatureCollection.infoService != 'null') {
        href = global_FeatureCollection.infoService + featureId;
      }
  
      // If the type of features we are presenting in the

      // current animation is static, then we embed the

      // element just once, and leave it there.  Otherwise,

      // it will be embedded, and removed when it's given

      // event shows up in the frame set.

      if (global_currentTopic.presence == 'static') {
        if (global_currentTopic.overlaySymbolPattern != false) {
          // Feature icon AND overlay.

          //

          global_overlayDIV.innerHTML +=
           '<a target="_new" id="' + overlayId + '" style="position:absolute;z-index:3;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" href="' + href + '" onMouseOver="' +
               'return overlib(\'<b>' + featureId + '</b>\',CAPTION,\'' + type + '\',TIMEOUT,1000)" onMouseOut="' +
               'return nd()"><img border=0 style="z-index:3;display:block;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" src="' + iSrc + '"></a>';
          global_overlayDIV.innerHTML +=
           '<div id="' + elementId + '" style="position:absolute;z-index:2;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';"><img border=0 style="z-index:2;display:block;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" src="' + iSrc + '">';
        } else {
          // Just the feature icon.  No overlay.  Business as usual.

          // overlib(<img src="http://tsea.pgc.nrcan.gc.ca:8080/JRL/rtxyplot.php?t=X&min=30&s=BLSA">,FULLHTML,STICKY,CAPTION, 'STICKY example');

          if (global_FeatureCollection.contextImageService != null) {
            global_overlayDIV.innerHTML +=
             '<a target="_new" id="' + elementId + '" style="position:absolute;z-index:2;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" href="' + href + '" onMouseOver="' +
               'return overlib(\'<b>' + featureId + '</b>\',BACKGROUND,\'' + global_FeatureCollection.contextImageService + '&j=' + Math.random() + '&s=' + featureId + '\',PADX, 20, 20, PADY, 30, 30, RELY, -5, RELX, 5, WIDTH, 600, HEIGHT, 190, FGCOLOR,\'\')" onMouseOut="return nd()"><img border=0 style="z-index:2;display:block;top:' + iStyleTop + ';left:' + iStyleLeft + ';" src="' + iSrc + '"></a>';
          } else {
            global_overlayDIV.innerHTML +=
             '<a target="_new" id="' + elementId + '" style="position:absolute;z-index:2;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" href="' + href + '" onMouseOver="' +
               'return overlib(\'<b>' + featureId + '</b>\',CAPTION,\'' + type + '\',TIMEOUT,1000)" onMouseOut="return nd()"><img border=0 style="z-index:2;display:block;top:' + iStyleTop + ';left:' + iStyleLeft + ';" src="' + iSrc + '"></a>';
          };
        }  
        //if (global_currentTopic.shadowSymbolURI != false ) {

        //  global_shadowDIV.innerHTML +=

        //   '<div id="' + shadowId + '" style="position:absolute;z-index:2;top:' + iStyleTop +

        //       ';left:' + iStyleLeft + ';"><img border=0 style="z-index:3;display:block;top:' + iStyleTop +

        //       ';left:' + iStyleLeft + ';" src="' + global_currentTopic.shadowSymbolURI + '"></div>';

        //};

      } else {
        // Transient features....nothing to plot at this time.;

      };
    } else {
      // This item is completely off the map....so skip...gone.

    }
    global_featureIDs[i] = featureId;
 }


  initializeEvents(global_ActiveTopic["events"].uri,'animationStatus');

 return true;
};






function initializeEvents(URI, div) {
  // ----------- B.1 ------------

  // The looping of the event syndication handling process starts here.

  //

  // "URI" points to the source of RSS information we are supposed to pull from.

  // "div" is the in-page destination for our converted RSS content

  // ----------------------------

  if (URI.length>1) {
    var status = runEvents(URI,div);
    var reloadRateSelect = document.getElementById('animationReloadRate');
    var reloadRateInMilliseconds = reloadRateSelect.value;
    if (reloadRateInMilliseconds > 0) {
      global_reloadingAnimationID = setInterval('runEvents(unescape("' + URI + '"),"' + div + '")', reloadRateInMilliseconds);
    } else {
      runEvents(unescape(URI),div);
    };
  }
};


function runEvents(URI,div) {
  // ----------- B.2 ------------

  // The event syndication handling process starts here.

  // 

  // ----------------------------

  
  // 1. Obtain an XMLHttpRequest instance.

  //

  var req = G_newXMLHttpRequest();
  if (! req) {
     // Unsupported browser type

     alert("runEvents : Error : Could not perform AJAX (needed for our RSS capabilities) functions on this browser.  Sorry.  Please turn off RSS in SOMI.");
     return false;
  }

  // 2. Set the handler function to receive callback notificiations

  //    from the request object.

  //

  if (global_statusDIV) {
    global_statusDIV.value = 'Fetching events...';
  }
  var handlerFunction = G_getReadyStateHandler(req, processEvents);
  req.onreadystatechange = handlerFunction;

  // 3. Dispatch request for asynchronous handling.

  //

  //    Third parameter in below open() indicates asynchronous request.

  //

  try {
    var isNewer = G_resourceIsNewer(URI,"events",global_ActiveTopic["events"].lastFetched);
    var now = new Date();
    // Ajax - Everything asynchronous....paranoid about

    //        duplicate efforts, however they may have been triggered.

    //

    if (global_ActiveTopic["events"].beingFetched != undefined && global_ActiveTopic["events"].beingFetched) {
      // Duplicate fetch avoided.

      if (global_statusDIV) {
        global_statusDIV.value += 'X..';
      }
      return true;
    }
    if (global_timelineBarDIV) {
       document.getElementById('animationStatus').value = 'Last Checked : ' + now.toGMTString();
    }
    if ((! global_ActiveTopic["events"].lastFetched) || (isNewer)) {
      req.open('GET', insulateURI(URI), true);
      if (isNewer) {
        if (global_statusDIV)
          global_statusDIV.value += 'reloading...';
      } else {
        if (global_statusDIV)
          global_statusDIV.value += 'initializing...';
      }
      global_ActiveTopic["events"].beingFetched = true;
      req.send(null);
      // Request for XML document was dispatched.

      // Processing will occur asynchronously.  This

      // procedure did its job.  Just return true and

      // move on.

      return true;
    } else {
      // Resource has not changed since we last loaded it.

    }
  } catch (e) {
    alert('runEvents : Error : ' + e.message + ' occurred because ' + e.reason +'.');
    return false;
  }
}; 


function processEvents (req,fetchedDate) {
  // ----------- B.3 ------------

  // Take XMLHttpRequest (that has "returned") and process specific to

  // the event dom we are expecting.

  // Document handling process starts here.

  // 

  // ----------------------------


  // Note date of fetch and perform any notifications

  // to the user here.

  //

  if (global_ActiveTopic["events"].beingParsed != undefined && global_ActiveTopic["events"].beingParsed) {
    // Duplicate fetch avoided.

    if (global_statusDIV) {
      global_statusDIV.value += 'X..';
    }
    return true;
  }
  global_ActiveTopic["events"].beingParsed = true;
  global_ActiveTopic["events"].lastFetched = fetchedDate;
  var now = new Date();
  if (global_statusDIV) {
    global_statusDIV.value += 'ok';
  }
  if (global_debug && global_statusDIV) {
    global_statusDIV.value += ' | Completed : ' + now.toGMTString();
  }

  //   Parse returned content from text into xml DOM

  //

  var dom = G_parseXMLTextIntoDOM(req.responseText);
  req = false;
  global_ActiveTopic["events"].beingParsed = false;

  //   Parse DOM and update state of SOMI

  //

  var status = readEvents(dom);
  if (status) {
      return true;
  } else {
      alert("processEvents : Error : AJAX failed!");
      return false;
  }
};


function readEvents(dom) {
  // ----------- B.4 ------------

  // Final step in handling our syndicated events.  Take the XML DOM

  // we are expecting and do something with the content.

  //

  // Expected is an XML DOM object, complete, loaded, done.  Ready

  // to be used.  Synchronicity is EXPECTED to have been dealt with

  // prior to calling this function, not after.

  //

  // ----------------------------


  // Ajax - Everything asynchronous....paranoid about

  //        duplicate efforts, however they may have been triggered.

  //

  if (global_ActiveTopic["events"].beingRead != undefined && global_ActiveTopic["events"].beingRead) {
    if (global_statusDIV) {
      global_statusDIV.value += 'X..';
    }
    return true;
  }
  // We're expecting an XML DOM object so it

  // better be one.

  if (typeof dom == undefined) {
    alert("readEvents : Error : Type of returned document is not defined....failure!");
    return false;
  }
  global_ActiveTopic["events"].beingRead = true;
  var items = dom;
  var a_events = new Array();
  

  // Step 1) Read the event set sequentially, changing

  //         the symbology of mapped features with each

  //         state change present.

  //

  var events = items.getElementsByTagName('event');
  var possibleEvents = events.length;

  items = new Array(); // Memory cleanup

  for (var i = 0, eTotal = 0, len = events.length; i < len; i+=1) {
    var event = events[i];
    if (global_statusDIV) {
      global_statusDIV.value = 'Reading event : ' + i + ' of ' + possibleEvents;
    }

    // a. Collect information returned for this event.

    //

    var id = event.getElementsByTagName('feature_id')[0].firstChild.nodeValue;
    var dateTime = event.getElementsByTagName('dateTime')[0].firstChild.nodeValue;
    // b. Convert event datetime string to date object.

    //

    var o_date = new Date(dateTime);
    if (global_Features[id] != undefined && global_Features[id].onMap) {      
      if (global_currentTopic.stateSymbolPattern) {
        // Multi-variate pattern approach

        a_events[eTotal] = {element : event, feature_id : id, dateTime : o_date, icon : global_currentTopic.stateSymbolPattern, overlay : global_currentTopic.overlaySymbolPattern};
      } else {
        // Fixed symbol table (single variable approach)

        var state = event.getElementsByTagName('var1')[0].firstChild.nodeValue;
        a_events[eTotal] = {element : event, feature_id : id, dateTime : o_date, icon : global_States[state].symbol, overlay : null};
      }
      eTotal+=1;
    } else {
      // Feature pertaining to this event is NOT on the map....so don't bother.

    };
  }
  events = false;  // Clean up memory FAST

  global_ActiveTopic["events"].beingRead = false;


  // Step 1.5 Check to ensure at least two events

  //          were detected.

  //

  if (a_events.length < 2) {
      if (global_statusDIV) {
        global_statusDIV.value = 'Less than 2 events found...abandoning.  Likely a corrupt XML document.';
      }
      stopSOMIReload();
      return true;
  }


  // Step 2) Sort events by date

  //

  if (global_statusDIV) {
    global_statusDIV.value = 'Sorting events...';
  }
  a_events.sort(G_compareEventDates);


  // Step 3) Setup environment for calculating frames,

  //         for each feature, over a given span of time.

  //

  var oldestEvent = a_events[0].dateTime;
  var youngestEvent = a_events[a_events.length - 1].dateTime;
  var frameSpanValue = 1;
  var delta_ms = Math.abs(youngestEvent.getTime() - oldestEvent.getTime());
  var frameSpanUnitFactor = parseInt(document.getElementById('animationFrameUnit').value);
  var spanOfAllEvents = Math.ceil(delta_ms/frameSpanUnitFactor);
  var numberOfFrames = spanOfAllEvents + 1;
  //alert('factor is ' + frameSpanUnitFactor + ' delta_ms is ' + delta_ms + '  spanOfAllEvents is ' + spanOfAllEvents + '  numberOfFrames is ' + numberOfFrames);



  // Step 4) Sequentially read from oldest event to newest

  //         event, while tracking which frame we correspond

  //         to.  Setup .features[] (indexed by integer) as

  //         component of each frame...storing only what is necessary

  //         to effect change within the current frame.

  //

  //         Skip features not on the map.  Yeah!!

  //         Skip features we know nothing about.  Yeah!!

  //

  var currentFrameNumber = 0;
  var currentFrameDate = oldestEvent;
  var actualTotalEvents = a_events.length;
  var t_FeaturesLastFrameState = new Array();
  var t_FeaturesInCurrentFrame = new Array();
  var t_FeaturesInCurrentFrameToProcess = new Array();
  for (var i = 0, len = actualTotalEvents; i < len; i+=1) {
    if (global_statusDIV) {
      global_statusDIV.value = 'Building frame : ' + currentFrameNumber + "       from event " + i + ' of ' + actualTotalEvents;
    }

    // Determine whether to "advance frame" or not.

    //

    var currentEvent = a_events[i];
    if (currentEvent.dateTime > currentFrameDate) {
       var d = Math.abs(currentEvent.dateTime.getTime() - currentFrameDate.getTime());
       var deltaInFrameUnits = Math.floor(d/frameSpanUnitFactor);
       if (deltaInFrameUnits >= frameSpanValue) {
         //alert("Advancing frame by " + deltaInFrameUnits + " to frame number " + currentFrameNumber);

         //alert("Current date of frame is " + currentFrameDate.toGMTString());

         for (var k = 0; k < deltaInFrameUnits; k+=1) {
           // Foreach feature that changed state during the current

           // frame make an entry for the animator....in both "forward"

           // and "reverse" directions.

           //

           while (t_FeaturesInCurrentFrameToProcess.length > 0) {
             var t_fid = t_FeaturesInCurrentFrameToProcess.pop();
             var x = t_FeaturesInCurrentFrame[t_fid];
 
             // Use state for this feature from last recorded

             // state change as the "reverse" state for this frame

             // (if one is available).

             //

             var ricon = null;
             var roverlay = null;
             if (global_currentTopic.presence == 'static') {
               if (t_FeaturesLastFrameState[t_fid] != undefined) {
                 if (t_FeaturesLastFrameState[t_fid].icon != undefined)
                   ricon = t_FeaturesLastFrameState[t_fid].icon;
                 if (t_FeaturesLastFrameState[t_fid].overlay != undefined)
                   roverlay = t_FeaturesLastFrameState[t_fid].overlay; 
               }; 
             } else {
               ricon = x.icon;
               roverlay = x.overlay;
             }
             // Add state change for this feature to this frame before

             // bumping up the frame number, storing state for next

             // frame to use for "reverse" direction, resetting structures

             // and moving on.

             //

             global_Frames[currentFrameNumber].features.push({id : x.id, ficon : x.icon, ricon : ricon, foverlay : x.overlay, roverlay : roverlay});
             t_FeaturesLastFrameState[t_fid] = {icon : x.icon, overlay : x.overlay};
           }
           t_FeaturesInCurrentFrame = new Array(); // Reset

           currentFrameNumber += 1;
           currentFrameDate.setTime(currentFrameDate.getTime() + frameSpanUnitFactor);
           global_Frames[currentFrameNumber] = {date : new Date(currentFrameDate.getTime()), features : new Array()};
           //alert("Now the date of frame is " + currentFrameDate.toGMTString());;

         };
       } else {
         //alert('delta of ' + deltaInFrameUnits + ' not exceed ' + frameSpanValue);

       };
    } else {
      if (! global_Frames[currentFrameNumber]) {
        global_Frames[currentFrameNumber] = {date : new Date(currentFrameDate.getTime()), features : new Array()}; 
      };
    }


    var fid = currentEvent.feature_id;
    if (! global_Features[fid]) {
      // Feature corresponding to this event is not registered in

      // our feature base.  So, obviously we don't know where it is,

      // or what to do with it.  SKIP.

    } else {
      if (! global_Features[fid].onMap) {
        // Feature corresponding to this event is not on the map. SKIP.

      } else {
	// Perform symbol assignment, forward and reverse (w or w/o overlays) here....

	// NOT above in the "reading" stage!!!!!!!!

        if (global_currentTopic.stateSymbolPattern) {
          // Wildcard multi-variate approach


          // Foreach variable for this selected topic:

          //   a) find scalar in event pertaining to this variable : ex. <var1>20.5</var1>.....find 20.5

          //   b) take stateSymbolPattern and replace %VAR1% with 20.5

          for (var z = 1, l_z = global_currentTopic.numberOfVariables + 1; z < l_z; z+=1) {
             var value = currentEvent.element.getElementsByTagName('var' + z)[0].firstChild.nodeValue;
             var s = "%VAR" + z + "%";
             var regexp = new RegExp(s);
             currentEvent.icon = currentEvent.icon.replace(regexp,value);
             currentEvent.overlay = currentEvent.overlay.replace(regexp,value);
          };
        }

        // Take the first state for any given feature, within

        // a given frame and use it as the state for the whole

        // frame....regardless of frame length.

        //

        if (t_FeaturesInCurrentFrame[fid] == undefined) {
          t_FeaturesInCurrentFrame[fid] = {id : fid, icon : currentEvent.icon, overlay : currentEvent.overlay};
          t_FeaturesInCurrentFrameToProcess.push(fid);
        } 

        // Change symbology of "mapped" features to reflect

        // most recent state....if it is on the map.

        //

        if (global_Features[fid].onMap && (global_currentTopic.presence == 'static') && (global_currentTopic.autoDisplay)) {
          // Feature is static, AND on the map somewhere.  Just change the

          // symbology according to the given state, and move on.

          var ename = 'feature-' + fid;
          document.getElementById(ename).getElementsByTagName('img')[0].src = currentEvent.icon;
          if (global_currentTopic.overlaySymbolPattern != false) {
            var oname = 'overlay-' + fid;
            document.getElementById(oname).getElementsByTagName('img')[0].src = currentEvent.overlay;
          };
        };
      };
    };
  }
  t_FeaturesLastFrameState = false; // Memory cleanup

  t_FeaturesInCurrentFrame = false; // Memory cleanup

  t_FeaturesInCurrentFrameToProcess = false; // Memory cleanup


  // Step 5) A successful load.  Make final calculations, notify

  //         user everything is "ready" and present additional

  //         controls.

  //

  global_totalNumberOfFrames = global_Frames.length;
  if (global_statusDIV) {
    global_statusDIV.value = 'Animation loaded  |  Events : ' + a_events.length + '  |  Frames : ' + global_totalNumberOfFrames;
  }
  global_timelineBarDIV = document.getElementById('animationTimeline');
  timelineBarInitialize(global_timelineBarDIV);
  if (global_currentTopic.presence == 'transient') {
    global_animationEventFadeDIV = document.getElementById('animationEventFade');
    global_animationEventFadeDIV.innerHTML = 'Event Fade:&nbsp;<select id="animationEventFadeSelect" type="pulldown" name="animationEventFade" onchange="global_currentAnimationEventFadeRate=this.value"><option value="5">5 frames</option><option value="10" selected>10 frames</option><option value="25">25 frames</option><option value="100">100 frames</option><option value="1000000">never</option></select>';
    global_currentAnimationEventFadeRate = 10;
  }
  global_timelineBarTextDIV = document.getElementById('animationTimelineText').getElementsByTagName('font')[0];
  global_ActiveTopic["events"].timesLoaded += 1;
  a_events = false; // Memory cleanup forced


  return true;
};



function updateScrollerContent () {
   var target = document.getElementById('mapScrollerContent');
   target.innerHTML = global_newScrollerContent;
}; 




function refreshMap(longitude, latitude, submitControlName) {

     // Change [next] map center to location of

     // this item.

     //

     document.main_form.cx.value = longitude;
     document.main_form.cy.value = latitude;

     // Attempt to fake a "click" on the submit control

     //

     var obj = document.getElementById(submitControlName);
     if (document.main_form.Submit) {
        setTimeout('document.main_form.Submit()',500);
     } else {
        setTimeout('document.main_form.submit()',500);
     }
     return true;
};














function advanceFrame () {
  // Step 1) Calculate present, and fading frame numbers.

  //

  if (global_currentFrameNumber > (global_Frames.length - 1)) {
    global_currentFrameNumber = 0;
    if ((global_currentTopic.presence == 'transient') 
        && (global_currentAnimationEventFadeRate < global_Frames.length)) {
      global_currentFadingFrameNumber = global_Frames.length - 1 - global_currentAnimationEventFadeRate;
    } else {
      global_currentFadingFrameNumber = false;
    };
  } else {
    if ((global_currentTopic.presence == 'transient')
        && (global_currentAnimationEventFadeRate < global_Frames.length)) {
      if (global_currentFrameNumber > (global_currentAnimationEventFadeRate - 1)) {
        global_currentFadingFrameNumber = global_currentFrameNumber - global_currentAnimationEventFadeRate;
      } else {
        if (global_currentAnimationEventFadeRate < global_Frames.length)
          global_currentFadingFrameNumber = global_Frames.length - Math.abs(global_currentFrameNumber - global_currentAnimationEventFadeRate - 1);
      };
    } else {
      global_currentFadingFrameNumber = false;
    };
  }

  // Step 2) Update text in timeline bar.

  // 

  global_timelineBarTextDIV.innerHTML = global_Frames[global_currentFrameNumber].date.toGMTString();
 
  // Step 3) Deal with features with state changes

  //         in present frame.

  //

  for (var f = 0, len = global_Frames[global_currentFrameNumber].features.length; f < len; f+=1) {
    if (global_Frames[global_currentFrameNumber].features[f].ficon != null) {
      var fid = global_Frames[global_currentFrameNumber].features[f].id;
      if (global_currentTopic.presence == 'static') {
        var element = document.getElementById('feature-' + fid);
        element.getElementsByTagName('img')[0].src = global_Frames[global_currentFrameNumber].features[f].ficon;
        if (global_currentTopic.overlaySymbolPattern != false) {
          var overlay = document.getElementById('overlay-' + fid);
          overlay.getElementsByTagName('img')[0].src = global_Frames[global_currentFrameNumber].features[f].foverlay;
        };
      } else {
        // Create a new floating element for current feature, with

        //    with mouseover tooltip, current status, correct image (for the

        //    state display), correct location, etc

        //

        var elementId = 'feature-' + fid;
        var shadowId = 'shadow-' + fid;
        var overlayId = 'overlay-' + fid;
        var iStyleTop = global_Features[fid].ypx;
        var iStyleLeft = global_Features[fid].xpx;
        var type = global_FeatureCollection.name;
        var iSrc = global_Frames[global_currentFrameNumber].features[f].ficon;
        var oSrc = global_Frames[global_currentFrameNumber].features[f].foverlay;
        var href = 'javascript:void(0)';
        if (global_FeatureCollection.infoService != 'null') {
          href = global_FeatureCollection.infoService + fid;
        }
        if (global_currentTopic.overlaySymbolPattern != false) {
          // Feature icon AND overlay.

          //

          global_overlayDIV.innerHTML +=
           '<a target="_new" id="' + overlayId + '" style="position:absolute;z-index:2;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" href="' + href + '" onMouseOver="' +
               'return overlib(\'<b>' + fid + '</b> at ' +
	       global_Frames[global_currentFrameNumber].date.toGMTString() + '\',CAPTION,\'' + type + '\',' +
               'TIMEOUT,1000)" onMouseOut="' +
               'return nd()"><img border=0 style="z-index:4;display:block;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" src="' + oSrc + '"></a>';
          global_overlayDIV.innerHTML +=
           '<div id="' + elementId + '" style="position:absolute;z-index:2;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';"><img border=0 style="z-index:3;display:block;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" src="' + iSrc + '"></a>';
        } else {
          // Just the feature icon.  No overlay.  Business as usual.

          //

          global_overlayDIV.innerHTML +=
           '<a target="_new" id="' + elementId + '" style="position:absolute;z-index:2;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" href="' + href + '" onMouseOver="' +
               'return overlib(\'<b>' + fid + '</b> at ' +
	       global_Frames[global_currentFrameNumber].date.toGMTString() + '\',CAPTION,\'' + type + '\',' +
               'TIMEOUT,1000)" onMouseOut="' +
               'return nd()"><img border=0 style="z-index:3;display:block;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" src="' + iSrc + '"></a>';
        }
        if (global_currentTopic.shadowSymbolURI != false) {
          global_shadowDIV.innerHTML +=
           '<div id="' + shadowId + '" style="position:absolute;z-index:2;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';"><img border=0 style="z-index:3;display:block;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" src="' + global_currentTopic.shadowSymbolURI + '"></div>';
        };
      };
    };
  }

  // Step 4) Deal with fading transients...if necessary

  //         Doing a transient REMOVAL using dom.

  //

  if ((global_currentTopic.presence == 'transient') && (global_currentFadingFrameNumber)) {
    for (var f = 0, len = global_Frames[global_currentFadingFrameNumber].features.length; f < len; f+=1) {
      var fid = global_Frames[global_currentFadingFrameNumber].features[f].id;
      G_removeDOMElement('feature-' + fid);
      G_removeDOMElement('shadow-' + fid);
      G_removeDOMElement('overlay-' + fid);
    };
  }

  // Step 5) Update timeline bar.

  //

  timelineBarAdvance(1/global_totalNumberOfFrames);
  global_currentFrameNumber += 1;
};

function retreatFrame () {
  // Step 1) Calculate present, and fading frame numbers.

  //

  global_currentFrameNumber -= 1;
  if (global_currentFrameNumber < 0) {
    global_currentFrameNumber = global_Frames.length - 1;
    if ((global_currentTopic.presence == 'transient')
        && (global_currentAnimationEventFadeRate < global_Frames.length)) {
      global_currentFadingFrameNumber = global_currentAnimationEventFadeRate - 1;
    } else {
      global_currentFadingFrameNumber = false;
    };
  } else {
    if ((global_currentTopic.presence == 'transient')
        && (global_currentAnimationEventFadeRate < global_Frames.length)) {
      global_currentFadingFrameNumber = global_currentFrameNumber + global_currentAnimationEventFadeRate;
      if (global_currentFadingFrameNumber > (global_Frames.length - 1)) {
          global_currentFadingFrameNumber = global_currentAnimationEventFadeRate - 1 - (global_Frames.length - global_currentFrameNumber);
      };
    } else {
      global_currentFadingFrameNumber = false;
    };
  }

  // Step 2) Update timeline bar.

  //

  timelineBarRetreat(1/global_totalNumberOfFrames);
  global_timelineBarTextDIV.innerHTML = global_Frames[global_currentFrameNumber].date.toGMTString();

  // Step 3) Deal with features with state changes

  //         in present frame.

  //

  for (var f = 0, len = global_Frames[global_currentFrameNumber].features.length; f < len; f+=1) {
    if (global_Frames[global_currentFrameNumber].features[f].ricon != null) {
      var fid = global_Frames[global_currentFrameNumber].features[f].id;
      if (global_currentTopic.presence == 'static') {
        var element = document.getElementById('feature-' + fid);
        element.getElementsByTagName('img')[0].src = global_Frames[global_currentFrameNumber].features[f].ricon;
        if (global_currentTopic.overlaySymbolPattern != false) {
          var overlay = document.getElementById('overlay-' + fid);
          overlay.getElementsByTagName('img')[0].src = global_Frames[global_currentFrameNumber].features[f].roverlay;
        };
      } else {
        // Create a new floating element for current feature, with

        //    with mouseover tooltip, current status, correct image (for the

        //    state display), correct location, etc

        //

        var elementId = 'feature-' + fid;
        var shadowId = 'shadow-' + fid;
        var overlayId = 'overlay-' + fid;
        var iStyleTop = global_Features[fid].ypx;
        var iStyleLeft = global_Features[fid].xpx;
        var type = global_FeatureCollection.name;
        var iSrc = global_Frames[global_currentFrameNumber].features[f].ricon;
        var oSrc = global_Frames[global_currentFrameNumber].features[f].roverlay;
        var href = 'javascript:void(0)';
        if (global_FeatureCollection.infoService != 'null') {
          href = global_FeatureCollection.infoService + fid;
        }
        if (global_currentTopic.overlaySymbolPattern != false) {
          // Feature icon AND overlay.

          //

          global_overlayDIV.innerHTML +=
           '<a target="_new" id="' + overlayId + '" style="position:absolute;z-index:2;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" href="' + href + '" onMouseOver="' +
               'return overlib(\'<b>' + fid + '</b> at ' +
               global_Frames[global_currentFrameNumber].date.toGMTString() + '\',CAPTION,\'' + type + '\',' +
               'TIMEOUT,1000)" onMouseOut="' +
               'return nd()"><img border=0 style="z-index:4;display:block;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" src="' + oSrc + '"></a>';
          global_overlayDIV.innerHTML +=
           '<div id="' + elementId + '" style="position:absolute;z-index:2;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';"><img border=0 style="z-index:3;display:block;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" src="' + iSrc + '"></a>';
        } else {
          // Just the feature icon.  No overlay.  Business as usual.

          //

          global_overlayDIV.innerHTML +=
           '<a target="_new" id="' + elementId + '" style="position:absolute;z-index:2;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" href="' + href + '" onMouseOver="' +
               'return overlib(\'<b>' + fid + '</b> at ' +
	       global_Frames[global_currentFrameNumber].date.toGMTString() + '\',CAPTION,\'' + type + '\',' +
               'TIMEOUT,1000)" onMouseOut="' +
               'return nd()"><img border=0 style="z-index:3;display:block;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" src="' + iSrc + '"></a>';
        }
        if (global_currentTopic.shadowSymbolURI != false) {
          global_shadowDIV.innerHTML +=
           '<div id="' + shadowId + '" style="position:absolute;z-index:2;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';"><img border=0 style="z-index:3;display:block;top:' + iStyleTop +
               ';left:' + iStyleLeft + ';" src="' + global_currentTopic.shadowSymbolURI + '"></div>';
        };
      };
    } else {
      if (global_statusDIV) {
        global_statusDIV.value += 'null.';
      };
    };
  }

  // Step 4) Deal with fading transients...if necessary

  //         Doing a transient REMOVAL using dom.

  //

  if ((global_currentTopic.presence == 'transient') && (global_currentFadingFrameNumber)) {
    for (var f = 0, len = global_Frames[global_currentFadingFrameNumber].features.length; f < len; f+=1) {
      var fid = global_Frames[global_currentFadingFrameNumber].features[f].id;
      G_removeDOMElement('feature-' + fid);
      G_removeDOMElement('shadow-' + fid);
      G_removeDOMElement('overlay-' + fid);
    };
  }
};


function stopSOMIReload() {
   clearInterval(global_reloadingAnimationID);
   global_reloadingAnimationID = false;
   global_Features.length = 0;
   global_Frames.length = 0;
   global_currentFrameNumber = 0;
   global_totalNumberOfFrames = 0;

   if (global_timelineBarDIV) {
     global_timelineBarDIV.innerHTML = '';
     global_timelineBarDIV = false;
     global_timelineBarPercentLoaded = 0.0;
   }
   if (global_animationFeatureDIV) {
     global_animationFeatureDIV.innerHTML = '';
     global_animationFeatureDIV = false;
   }
   if (global_animationLegendDIV) {
     global_animationLegendDIV.innerHTML = '';
     global_animationLegendDIV = false;
   }
   if (global_animationEventFadeDIV) {
     global_animationEventFadeDIV.innerHTML = '';
     global_animationEventFadeDIV = false;
   }
   if (global_featureIDs.length > 0) {
     for (var i=0,len=global_featureIDs.length;i<len;i++) {
       G_removeDOMElement('feature-' + global_featureIDs[i]);
       G_removeDOMElement('shadow-' + global_featureIDs[i]);
       G_removeDOMElement('overlay-' + global_featureIDs[i]);
     };
   }
   global_featureIDs.length = 0;
   global_currentAnimationState = 'stop'; // or 'pause' or 'play'

   if (global_statusDIV) {
     global_statusDIV.value += 'Previous animation configuration removed.';
   }
   /* Change label of "play" button, and the action attached to it */
};


function stopSOMIAnimation() {
   clearInterval(global_runningAnimationID);
   SOMIv.tools.animation.isRunning = false;
   global_runningAnimationID = false;
   /* Change label of "play" button, and the action attached to it */
};

function startForwardSOMIAnimation () {
   if (global_runningAnimationID) {
     stopSOMIAnimation();
   }
   global_currentFrameDirection = 'forward';
   if (global_Frames.length < 1) {
     if (global_statusDIV) {
       global_statusDIV.value = 'Animation needs to be reloaded';
     };
   }
   var r = (1000 / global_currentFrameRateInMilliseconds);
   var ratio;
   if (r >= 1) {
     ratio = parseInt(r) + ' f/s';
   } else {
     ratio = (1/r) + 's/f';
   }
   if (global_statusDIV) {
     global_statusDIV.value = 'Playing - forward - at ' + ratio;
   }
   SOMIv.tools.animation.isRunning = true;
   global_runningAnimationID = setInterval(advanceFrame, global_currentFrameRateInMilliseconds);
};

function startReverseSOMIAnimation () {
   if (global_runningAnimationID) {
     stopSOMIAnimation();
   }
   global_currentFrameDirection = 'reverse';
   if (global_Frames.length < 1) {
     if (global_statusDIV) {
       global_statusDIV.value = 'Animation needs to be reloaded';
     };
   }
   var r = (1000 / global_currentFrameRateInMilliseconds);
   var ratio;
   if (r >= 1) {
     ratio = parseInt(r) + ' f/s';
   } else {
     ratio = (1/r) + 's/f';
   }
   if (global_statusDIV) {
     global_statusDIV.value = 'Playing - in reverse - at ' + ratio;
   }
   SOMIv.tools.animation.isRunning = true;
   global_runningAnimationID = setInterval(retreatFrame, global_currentFrameRateInMilliseconds);
};

function increaseFrameRate () {
  var frameRateSelect = document.getElementById('animationFrameRate');
  if (frameRateSelect.selectedIndex > 0) {
    frameRateSelect.selectedIndex -= 1;
    global_currentFrameRateInMilliseconds = frameRateSelect.value;
  }
  if (global_statusDIV) {
    global_statusDIV.value = global_currentFrameRateInMilliseconds + 'ms interval';
  }
  if (global_runningAnimationID) {
    stopSOMIAnimation();
    if (global_currentFrameDirection == 'forward') {
      startForwardSOMIAnimation();
    } else {
      startReverseSOMIAnimation();
    };
  }
};

function decreaseFrameRate () {
  var frameRateSelect = document.getElementById('animationFrameRate');
  if (frameRateSelect.selectedIndex < frameRateSelect.length) {
    frameRateSelect.selectedIndex += 1;
    global_currentFrameRateInMilliseconds = frameRateSelect.value;
  }
  if (global_statusDIV)
    global_statusDIV.value = global_currentFrameRateInMilliseconds + 'ms interval';
  if (global_runningAnimationID) {
    stopSOMIAnimation();
    if (global_currentFrameDirection == 'forward') {
      startForwardSOMIAnimation();
    } else {
      startReverseSOMIAnimation();
    };
  }
};







// The following functions (and their globals at the top of this script)

// were either adapted from, or inspired by:

//

//     Percent Bar - Version 1.0

//     Author: Brian Gosselin of http://scriptasylum.com

//     Script featured on http://www.dynamicdrive.com

//     Note: Modified by Dynamicdrive so incr/decrCount() accepts any percentage

//


function timelineBarAdvance (prcnt) {
  global_timelineBarPercentLoaded += prcnt;
  timelineBarSet(global_timelineBarPercentLoaded);
};

function timelineBarRetreat (prcnt) {
  global_timelineBarPercentLoaded -= prcnt;
  timelineBarSet(global_timelineBarPercentLoaded);
};

function timelineBarSet (prcnt) {
  global_timelineBarPercentLoaded=prcnt;
  if ((global_timelineBarPercentLoaded <= 0.0) && (global_currentFrameDirection == 'reverse')) {
    global_timelineBarPercentLoaded = 1.0;
  }
  if ((global_timelineBarPercentLoaded >= 1.0) && (global_currentFrameDirection == 'forward')) {
    global_timelineBarPercentLoaded = 0.0;
  }
  var newBarWidth = parseInt((global_timelineBarWidth-2) * global_timelineBarPercentLoaded);
  global_timelineBarForegroundDIV.style.width= newBarWidth + 'px';
};

function timelineBarInitialize (div) {
 var txt='';
 div.innerHTML = txt;
 txt+='<div id="animationTimelineBackground" style="position:relative; visibility:hidden; background-color:'+global_timelineBarBorderColor+'; width:'+global_timelineBarWidth+'px; height:'+global_timelineBarHeight+'px;">';
 txt+='<div style="position:absolute; top:1px; left:1px; width:'+(global_timelineBarWidth-2)+'px; height:'+(global_timelineBarHeight-2)+'px; background-color:'+global_timelineBarUnloadedColor+'; z-index:2; font-size:1px;"></div>';
 txt+='<div id="animationTimelineForeground" style="position:absolute; top:1px; left:1px; width:0px; height:'+(global_timelineBarHeight-2)+'px; background-color:'+global_timelineBarLoadedColor+'; z-index:2; font-size:1px;"></div>';
 txt+='<div id="animationTimelineText" style="position:relative; background-color: transparent; font-size:7pt;font-family:helvetica, arial, sans-serif;color:#ffffff;font-weight:bold;z-index:3;"><center><font></font></center></div>';
 txt+='</div>';
 
 div.innerHTML += txt;
 global_timelineBarBackgroundDIV=document.getElementById('animationTimelineBackground');
 global_timelineBarForegroundDIV=document.getElementById('animationTimelineForeground');
 global_timelineBarForegroundDIV.style.width= '0px';
 global_timelineBarBackgroundDIV.style.visibility="visible";
};





function viewerToolboxPresentAnimation () {
  var txt = '';
  SOMItb.innerHTML = txt;
  txt+='<div style="position:relative; border: 1px solid black; background-color:white; width:200px;">';
  txt+='<div style="position:relative; background-color:white; z-index:3;">';
  txt+=' <table>';
  txt+='  <tr><td align="left" colspan=2><img src="/lib/images/tb_movie.png" border=none alt="Animation Icon">&nbsp<img src="/lib/images/animation.png" alt="Animation:"></td></tr>';
  txt+='  <tr><td align="left" colspan=2><div id="animationTopic" style="position:relative; background-color: transparent; font-size:8pt;font-family:helvetica, arial, sans-serif;z-index:0"></div></td></tr>';
  txt+='  <tr><td colspan=2 bgcolor=#FFFFFF valign=bottom align=left><font face="Arial,Helvetica,SanSerif" size=-1>Frame Rate:&nbsp;<select id="animationFrameRate" type="pulldown" name="animationFrameRate"><option value="50">  20.0 f/s<option value="100"> 10.0 f/s<option value="200">  5.0 f/s<option value="400">  2.5 f/s<option value="500">  2.0 f/s<option value="666">  1.5 f/s<option value="1000" selected> 1.0 f/s<option value="2000"> 2.0 s/f<option value="3000"> 3.0 s/f<option value="5000"> 5.0 s/f</select></font></td></tr>';
  txt+='  <tr><td colspan=2 bgcolor=#FFFFFF valign=bottom align=left><font face="Arial,Helvetica,SanSerif" size=-1>Frame Unit:&nbsp;<select id="animationFrameUnit" type="pulldown" name="animationFrameUnit"><option value="1000"> 1 second<option value="60000"> 1 minute<option value="3600000"> 1 hour<option value="86400000" selected> 1 day<option value="604800000"> 1 week<option value="18144000000"> 1 month<option value="217728000000"> 1 year</select></font></td></tr>';
  txt+='  <tr><td colspan=2 bgcolor=#FFFFFF valign=bottom align=left><font face="Arial,Helvetica,SanSerif" size=-1>Reload Rate:&nbsp;<select id="animationReloadRate" type="pulldown" name="animationReloadRate"><option value="10000">10 seconds<option value="20000">20 seconds<option value="30000">30 seconds<option value="60000">1 minute<option value="120000">2 minutes<option value="600000">5 minutes<option value="1000000">10 minutes<option value="3000000">30 minutes<option value="-1" selected>never</select></td></tr>';
  txt+='  <tr><td colspan=2 bgcolor=#FFFFFF valign="top" align="center"><div id="animationEventFade" style="position:relative; background-color: transparent; font-size:8pt;font-family:helvetica, arial, sans-serif;z-index:0"></div></td></tr>';
  txt+='  <tr><td colspan=2 bgcolor=#FFFFFF valign=bottom align=center><font face="Arial,Helvetica,SanSerif" size=-1><textarea id="animationStatus" name="animationStatus" rows="4" cols="20"></textarea><br><a href="javascript:void(0)" style="text-decoration:none" onClick="event.returnValue=false;loadAnimation()"><b>Load Animation</b></a></font></td></tr>';
  txt+='  <tr><td colspan=2 bgcolor=#FFFFFF valign="top" align="center"><img src="/lib/images/pixel.png" width=20 height=1 border=0><div id="animationLegend" style="position:relative; background-color: transparent; font-size:8pt;font-family:helvetica, arial, sans-serif;z-index:0"></div></td></tr>';
  txt+='  <tr><td colspan=2 bgcolor=#FFFFFF valign="top" align="center"><img src="/lib/images/pixel.png" width=20 height=1 border=0><div id="animationFeature" style="position:relative; background-color: transparent; font-size:8pt;font-family:helvetica, arial, sans-serif;z-index:0"></div></td></tr>';
  txt+=' </table>';
  txt+='</div>';
  txt+='</div>';
  SOMItb.innerHTML+=txt;

  var txt = '';
  SOMIap.innerHTML = txt;
  txt+='<center><br><br>';
  txt+=' <table>';
  txt+='  <tr>';
  txt+='   <td class="inl" colspan=2 width="450" height="40" units="pixels" align="center">';
  txt+='<a href="javascript:startReverseSOMIAnimation()"><img src="/lib/images/somiIconPlayReverse_off.png" border="0"/></a>';
  txt+='<a href="javascript:decreaseFrameRate()"><img src="/lib/images/somiIconSlower_off.png" border="0"/></a>';
  txt+='<a href="javascript:stopSOMIAnimation()"><img src="/lib/images/somiIconStop_off.png" border="0"/></a>';
  txt+='<a href="javascript:increaseFrameRate()"><img src="/lib/images/somiIconFaster_off.png" border="0"/></a>';
  txt+='<a href="javascript:startForwardSOMIAnimation()"><img src="/lib/images/somiIconPlayForward_off.png" border="0"/></a>';
  txt+='   </td>';
  txt+='  </tr>';
  txt+='  <tr>';
  txt+='   <td colspan=2 bgcolor=#FFFFFF valign="top" align="center">';
  txt+='    <div id="animationTimeline" style="z-index:0"></div>';
  txt+='   </td>';
  txt+='  </tr>';
  txt+='  <tr>';
  txt+='   <td colspan=2 bgcolor=#FFFFFF valign="top" align="center">';
  txt+='<div id="animationTopicName" style="z-index:0"><font face="Arial,Helvetica,SanSerif" size=-1"></font></div><br>';
  txt+='<div id="animationTopicDescription" style="z-index:0"><font face="Arial,Helvetica,SanSerif" size=-1"></font></div>';
  txt+='   </td>';
  txt+='  </tr>';
  txt+='  <tr>';
  txt+='   <td colspan=2 bgcolor=#FFFFFF valign="top" align="center">';
  txt+='<div id="animationFeatureInfo" style="z-index:0"><font face="Arial,Helvetica,SanSerif" size=-1"></font></div>';
  txt+='   </td>';
  txt+='  </tr>';
  txt+=' </table>';
  txt+='</center>';
  SOMIap.innerHTML+=txt;
};





function viewerToolboxHideAnimation () {
  var txt = '';
  SOMItb.innerHTML = txt;
  SOMIap.innerHTML = txt;
};


