Ignore:
Timestamp:
Dec 8, 2009, 4:34:19 PM (15 years ago)
Author:
rider
Message:

KML file loaded & Timeline revised

Location:
oceandb/jQuery_Prototype/script
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • oceandb/jQuery_Prototype/script/oceandb.js

    r114 r122  
    11var map;  // Google Map2 物件
    22var ge;   // Google Earth Plugin 物件
    3 var icon;
     3var icon; // Google Map2 icon creator
    44var tm;   // Google Map2 Timeline
    55
     
    104104  // 設定地圖高度
    105105  var map_height=document.documentElement.clientHeight - 68;
     106
    106107  $('#mapcontainer').css( { height: map_height } );
    107108  $('#main').css( { height: map_height } );
     
    123124// 註冊 window 物件的 onResize Event
    124125// 參考: http://docs.jquery.com/Events
    125 $(window).resize(function() {
     126  $(window).resize(function() {
    126127  // 當改變瀏覽器大小時,重新設定地圖高度
    127128  var map_height=document.documentElement.clientHeight - 68;
     
    135136  mapId: "map",               // Id of map div element (required)
    136137  timelineId: "timeline",     // Id of timeline div element (required)
    137   datasets: [
     138  datasets: [                 
    138139      {
    139                 id: "artists",
    140     title: "Artists",
    141     theme: TimeMapDataset.orangeTheme({eventIconPath: "./image"}),
    142     // note that the lines below are now the preferred syntax
    143     type: "basic",
     140    title: "海研三號 OR3-1412",
     141    theme: "red",
     142    type: "kml",        // KML
    144143    options: {
    145         items: [
    146       {
    147         "start" : "1981",
    148                           "end" : "2009-01-11",
    149         "point" : {
    150             "lat" : 23.8123,
    151                   "lon" : 121.123
    152          },
    153         "title" : "中央氣象局",
    154         "options" : {
    155           // set the full HTML for the info window
    156           "infoHtml": "<div class='custominfostyle'><b>中央氣象局.</div>"
    157         }
    158       },
    159       {
    160         "start" : "1991",
    161         "end" : "2010",
    162         "point" : {
    163             "lat" : 22.5234,
    164             "lon" : 120.534
    165          },
    166        "title" : "海洋科技研究中心",
    167        "options" : {
    168          // load HTML from another file via AJAX
    169          // Note that this may break in IE if you're running it with
    170          // a local file, due to cross-site scripting restrictions
    171          "infoUrl": "http://www.nchc.org.tw",
    172          "theme": TimeMapDataset.redTheme({eventIconPath: "./image"})
    173         }
    174             }, 
    175                         {
    176        "start" : "2001",
    177        "end" : "2020",
    178        "point" : {
    179            "lat" : 24.8345,
    180            "lon" : 122.845
    181         },
    182        "title" : "經濟部水利署",
    183        "options" : {
    184          // use the default title/description info window
    185          "description": "Renaissance Man",
    186          "theme": TimeMapDataset.yellowTheme({eventIconPath: "image/"})
    187         }
    188       }
    189                       ]
     144    url: "data/kml/OR3-1412.kml" // Load KML file
     145          }
     146      }
     147      /*
     148      {
     149    title: "海研三號 OR3-1412",
     150    theme: "green",
     151    type: "progressive",
     152    options: {
     153        // Data to be loaded in JSON from a remote URL
     154        type: "json",  // JSON
     155        // url with start/end placeholders
     156        // url: "http://s2home.nchc.org.tw/oid/data/test.json",
     157        // TestCase
     158        // url: "http://www.nickrabinowitz.com/projects/timemap/progsvc.php?start=[start]&end=[end]&callback=",
     159        start: "1989-01-01",
     160        // lower cutoff date for data
     161        dataMinDate: "2009-12-31",
     162        // four months in milliseconds
     163        interval: 10368000000,
     164        // function to turn date into string appropriate for service
     165        formatDate: function(d) {
     166      return TimeMap.util.formatDate(d, 1);
    190167        }
    191     }   
    192       ],
    193       bandIntervals: [     
     168    }
     169      }
     170      */
     171        ],
     172  bandInfo: [ //上軸時間間隔與大小設定   
     173      {
     174         width:          "70%",
     175         intervalUnit:   Timeline.DateTime.MONTH,
     176         intervalPixels: 210
     177      },
     178      {       //下軸時間間隔與大小設定
     179         width:          "30%",                               intervalUnit:   Timeline.DateTime.YEAR,
     180         intervalPixels: 150,
     181         showEventText:  false,
     182         trackHeight:    0.2,
     183         trackGap:       0.2
     184      }
     185  ],
     186  /*
     187  bandIntervals: [     
    194188    Timeline.DateTime.DECADE,
    195189    Timeline.DateTime.CENTURY
    196       ] 
    197   });
    198         // manipulate the timemap further here if you like
     190 
     191        ] */ 
     192    });
    199193 
    200194      if (GBrowserIsCompatible()) {
  • oceandb/jQuery_Prototype/script/timemap.js

    r112 r122  
    11/*!
    2  * TimeMap Copyright 2008 Nick Rabinowitz.
     2 * Timemap.js Copyright 2008 Nick Rabinowitz.
    33 * Licensed under the MIT License (see LICENSE.txt)
    44 */
    55
    6 /**---------------------------------------------------------------------------
    7  * TimeMap
    8  *
     6/**
     7 * @overview
     8 * Timemap.js is intended to sync a SIMILE Timeline with a Google Map.
     9 * Dependencies: Google Maps API v2, SIMILE Timeline v1.2 - 2.3.1
     10 * Thanks to Jorn Clausen (http://www.oe-files.de) for initial concept and code.
     11 *
     12 * @name timemap.js
    913 * @author Nick Rabinowitz (www.nickrabinowitz.com)
    10  * The TimeMap object is intended to sync a SIMILE Timeline with a Google Map.
    11  * Dependencies: Google Maps API v2, SIMILE Timeline v1.2 or v2.2.0
    12  * Thanks to Jörn Clausen (http://www.oe-files.de) for initial concept and code.
    13  *---------------------------------------------------------------------------*/
     14 * @version 1.6pre
     15 */
    1416
    1517// globals - for JSLint
    16 /*global GBrowserIsCompatible, GLargeMapControl, GLatLngBounds, GMap2       */
    17 /*global GMapTypeControl, GDownloadUrl, GEvent, GGroundOverlay, GIcon       */
    18 /*global GMarker, GPolygon, GPolyline, GSize, GLatLng, G_DEFAULT_ICON       */
    19 /*global G_DEFAULT_MAP_TYPES, G_NORMAL_MAP, G_PHYSICAL_MAP, G_HYBRID_MAP    */
    20 /*global G_MOON_VISIBLE_MAP, G_SKY_VISIBLE_MAP, G_SATELLITE_MAP, Timeline   */
    21 
    22 // A couple of aliases to save a few bytes
    23 
    24 
    25 var DT = Timeline.DateTime,
    26 // Google icon path
    27 GIP = "http://www.google.com/intl/en_us/mapfiles/ms/icons/";
     18/*global GBrowserIsCompatible, GLargeMapControl, GMap2, GIcon       */
     19/*global GMapTypeControl, GDownloadUrl, GGroundOverlay              */
     20/*global GMarker, GPolygon, GPolyline, GSize, G_DEFAULT_ICON        */
     21/*global G_HYBRID_MAP, G_MOON_VISIBLE_MAP, G_SKY_VISIBLE_MAP        */
     22
     23(function(){
     24
     25// borrowing some space-saving devices from jquery
     26var
     27  // Will speed up references to window, and allows munging its name.
     28  window = this,
     29  // Will speed up references to undefined, and allows munging its name.
     30  undefined,
     31    // aliases for Timeline objects
     32    Timeline = window.Timeline, DateTime = Timeline.DateTime,
     33    // aliases for Google variables (anything that gets used more than once)
     34    G_DEFAULT_MAP_TYPES = window.G_DEFAULT_MAP_TYPES,
     35    G_NORMAL_MAP = window.G_NORMAL_MAP,
     36    G_PHYSICAL_MAP = window.G_PHYSICAL_MAP,
     37    G_SATELLITE_MAP = window.G_SATELLITE_MAP,
     38    GLatLng = window.GLatLng,
     39    GLatLngBounds = window.GLatLngBounds,
     40    GEvent = window.GEvent,
     41    // Google icon path
     42    GIP = "http://www.google.com/intl/en_us/mapfiles/ms/icons/",
     43    // aliases for class names, allowing munging
     44    TimeMap, TimeMapDataset, TimeMapTheme, TimeMapItem;
    2845
    2946/*----------------------------------------------------------------------------
    30  * TimeMap Class - holds references to timeline, map, and datasets
     47 * TimeMap Class
    3148 *---------------------------------------------------------------------------*/
    3249 
    3350/**
    34  * Creates a new TimeMap with map placemarks synched to timeline events
     51 * @class
     52 * The TimeMap object holds references to timeline, map, and datasets.
    3553 * This will create the visible map, but not the timeline, which must be initialized separately.
    3654 *
     
    3856 * @param {element} tElement     The timeline element.
    3957 * @param {element} mElement     The map element.
    40  * @param {Object} options       A container for optional arguments:
     58 * @param {Object} [options]       A container for optional arguments:<pre>
    4159 *   {Boolean} syncBands            Whether to synchronize all bands in timeline
    4260 *   {GLatLng} mapCenter            Point for map center
     
    4563 *   {Array} mapTypes               The set of maptypes available for the map
    4664 *   {Function/String} mapFilter    How to hide/show map items depending on timeline state;
    47                                     options: "hidePastFuture", "showMomentOnly"
     65                                    options: "hidePastFuture", "showMomentOnly", or function
    4866 *   {Boolean} showMapTypeCtrl      Whether to display the map type control
    4967 *   {Boolean} showMapCtrl          Whether to show map navigation control
     
    5169 *   {Function} openInfoWindow      Function redefining how info window opens
    5270 *   {Function} closeInfoWindow     Function redefining how info window closes
    53  */
    54 function TimeMap(tElement, mElement, options) {
    55     // save elements
     71 * </pre>
     72 */
     73TimeMap = function(tElement, mElement, options) {
     74   
     75    // save DOM elements
     76    /**
     77     * Map element
     78     * @type DOM Element
     79     */
    5680    this.mElement = mElement;
     81    /**
     82     * Timeline element
     83     * @type DOM Element
     84     */
    5785    this.tElement = tElement;
    58     // initialize array of datasets
     86   
     87    /**
     88     * Map of datasets
     89     * @type Object
     90     */
    5991    this.datasets = {};
    60     // initialize filters
    61     this.filters = {};
    62     // initialize map bounds
     92    /**
     93     * Filter chains for this timemap
     94     * @type Object
     95     */
     96    this.chains = {};
     97    /**
     98     * Bounds of the map
     99     * @type GLatLngBounds
     100     */
    63101    this.mapBounds = new GLatLngBounds();
    64102   
    65103    // set defaults for options
    66     // other options can be set directly on the map or timeline
    67     this.opts = options || {};   // make sure the options object isn't null
     104    var defaults = {
     105        mapCenter:          new GLatLng(0,0),
     106        mapZoom:            0,
     107        mapType:            G_PHYSICAL_MAP,
     108        mapTypes:           [G_NORMAL_MAP, G_SATELLITE_MAP, G_PHYSICAL_MAP],
     109        showMapTypeCtrl:    true,
     110        showMapCtrl:        true,
     111        syncBands:          true,
     112        mapFilter:          'hidePastFuture',
     113        centerOnItems:      true,
     114        theme:              'red'
     115    };
     116   
     117    /**
     118     * Container for optional settings passed in the "options" parameter
     119     * @type Object
     120     */
     121    this.opts = options = util.merge(options, defaults);
     122   
     123    // only these options will cascade to datasets and items
     124    options.mergeOnly = ['mergeOnly', 'theme', 'eventIconPath', 'openInfoWindow',
     125                         'closeInfoWindow', 'noPlacemarkLoad', 'noEventLoad']
     126   
    68127    // allow map types to be specified by key
    69     if (typeof(options.mapType) == 'string') {
    70         options.mapType = TimeMap.mapTypes[options.mapType];
    71     }
     128    options.mapType = util.lookup(options.mapType, TimeMap.mapTypes);
    72129    // allow map filters to be specified by key
    73     if (typeof(options.mapFilter) == 'string') {
    74         options.mapFilter = TimeMap.filters[options.mapFilter];
    75     }
    76     // these options only needed for map initialization
    77     var mapCenter =        options.mapCenter || new GLatLng(0,0),
    78         mapZoom =          options.mapZoom || 0,
    79         mapType =          options.mapType || G_PHYSICAL_MAP,
    80         mapTypes =         options.mapTypes || [G_NORMAL_MAP, G_SATELLITE_MAP, G_PHYSICAL_MAP],
    81         showMapTypeCtrl =  ('showMapTypeCtrl' in options) ? options.showMapTypeCtrl : true,
    82         showMapCtrl =      ('showMapCtrl' in options) ? options.showMapCtrl : true;
    83    
    84     // these options need to be saved for later
    85     this.opts.syncBands =        ('syncBands' in options) ? options.syncBands : true;
    86     this.opts.mapFilter =        options.mapFilter || TimeMap.filters.hidePastFuture;
    87     this.opts.centerOnItems =    ('centerMapOnItems' in options) ? options.centerMapOnItems : true;
     130    options.mapFilter = util.lookup(options.mapFilter, TimeMap.filters);
     131    // allow theme options to be specified in options
     132    options.theme = TimeMapTheme.create(options.theme, options);
    88133   
    89134    // initialize map
     135    this.initMap();
     136};
     137
     138/**
     139 * Initialize the map.
     140 */
     141TimeMap.prototype.initMap = function() {
     142    var options = this.opts, map, i;
    90143    if (GBrowserIsCompatible()) {
    91         var map = this.map = new GMap2(this.mElement);
    92         if (showMapCtrl) {
     144   
     145        /**
     146         * The associated GMap object
     147         * @type GMap2
     148         */
     149        this.map = map = new GMap2(this.mElement);
     150       
     151        // set controls
     152        if (options.showMapCtrl) {
    93153            map.addControl(new GLargeMapControl());
    94154        }
    95         if (showMapTypeCtrl) {
     155        if (options.showMapTypeCtrl) {
    96156            map.addControl(new GMapTypeControl());
    97157        }
     158       
    98159        // drop all existing types
    99         var i;
    100160        for (i=G_DEFAULT_MAP_TYPES.length-1; i>0; i--) {
    101161            map.removeMapType(G_DEFAULT_MAP_TYPES[i]);
    102162        }
    103163        // you can't remove the last maptype, so add a new one first
    104         map.addMapType(mapTypes[0]);
     164        map.addMapType(options.mapTypes[0]);
    105165        map.removeMapType(G_DEFAULT_MAP_TYPES[0]);
    106166        // add the rest of the new types
    107         for (i=1; i<mapTypes.length; i++) {
    108             map.addMapType(mapTypes[i]);
    109         }
     167        for (i=1; i<options.mapTypes.length; i++) {
     168            map.addMapType(options.mapTypes[i]);
     169        }
     170        // set basic parameters
    110171        map.enableDoubleClickZoom();
    111172        map.enableScrollWheelZoom();
    112173        map.enableContinuousZoom();
    113174        // initialize map center and zoom
    114         map.setCenter(mapCenter, mapZoom);
     175        map.setCenter(options.mapCenter, options.mapZoom);
    115176        // must be called after setCenter, for reasons unclear
    116         map.setMapType(mapType);
    117     }
    118 }
     177        map.setMapType(options.mapType);
     178    }
     179};
    119180
    120181/**
    121182 * Current library version.
    122  */
    123 TimeMap.version = "1.5pre";
     183 * @type String
     184 */
     185TimeMap.version = "1.6pre";
     186
     187/**
     188 * @name TimeMap.util
     189 * @namespace
     190 * Namespace for TimeMap utility functions.
     191 */
     192var util = TimeMap.util = {};
    124193
    125194/**
    126195 * Intializes a TimeMap.
    127196 *
    128  * This is an attempt to create a general initialization script that will
     197 * <p>This is an attempt to create a general initialization script that will
    129198 * work in most cases. If you need a more complex initialization, write your
    130  * own script instead of using this one.
    131  *
    132  * The idea here is to throw all of the standard intialization settings into
     199 * own script instead of using this one.</p>
     200 *
     201 * <p>The idea here is to throw all of the standard intialization settings into
    133202 * a large object and then pass it to the TimeMap.init() function. The full
    134203 * data format is outlined below, but if you leave elements off the script
    135  * will use default settings instead.
    136  *
    137  * Call TimeMap.init() inside of an onLoad() function (or a jQuery
     204 * will use default settings instead.</p>
     205 *
     206 * <p>Call TimeMap.init() inside of an onLoad() function (or a jQuery
    138207 * $.(document).ready() function, or whatever you prefer). See the examples
    139  * for usage.
     208 * for usage.</p>
    140209 *
    141210 * @param {Object} config   Full set of configuration options.
    142211 *                          See examples/timemapinit_usage.js for format.
     212 * @return {TimeMap}        The initialized TimeMap object, for future reference
    143213 */
    144214TimeMap.init = function(config) {
    145215   
    146216    // check required elements
     217    var err = "TimeMap.init: No id for ";
    147218    if (!('mapId' in config) || !config.mapId) {
    148         throw "TimeMap.init: No id for map";
     219        throw err + "map";
    149220    }
    150221    if (!('timelineId' in config) || !config.timelineId) {
    151         throw "TimeMap.init: No id for timeline";
     222        throw err + "timeline";
    152223    }
    153224   
    154225    // set defaults
    155     config = config || {}; // make sure the config object isn't null
    156     config.options = config.options || {};
    157     config.datasets = config.datasets || [];
    158     config.bandInfo = config.bandInfo || false;
    159     config.scrollTo = config.scrollTo || "earliest";
     226    var defaults = {
     227        options:        {},
     228        datasets:       [],
     229        bands:          false,
     230        bandInfo:       false,
     231        bandIntervals:  "wk",
     232        scrollTo:       "earliest"
     233    };
     234    // merge options and defaults
     235    config = util.merge(config, defaults);
     236
    160237    if (!config.bandInfo && !config.bands) {
    161         var intervals = config.bandIntervals ||
    162             config.options.bandIntervals ||
    163             [DT.WEEK, DT.MONTH];
    164238        // allow intervals to be specified by key
    165         if (typeof(intervals) == 'string') {
    166             intervals = TimeMap.intervals[intervals];
    167         }
    168         // save for later reference
    169         config.options.bandIntervals = intervals;
     239        var intervals = util.lookup(config.bandIntervals, TimeMap.intervals);
    170240        // make default band info
    171241        config.bandInfo = [
     
    180250                intervalPixels: 100,
    181251                showEventText:  false,
    182                 overview: true,
     252                overview:       true,
    183253                trackHeight:    0.4,
    184254                trackGap:       0.2
     
    194264   
    195265    // create the dataset objects
    196     var datasets = [], x, ds, dsOptions, dsId;
     266    var datasets = [], x, ds, dsOptions, topOptions, dsId;
    197267    for (x=0; x < config.datasets.length; x++) {
    198268        ds = config.datasets[x];
    199         dsOptions = ds.options || {};
    200         dsOptions.title = ds.title || '';
    201         dsOptions.theme = ds.theme;
    202         dsOptions.dateParser = ds.dateParser;
     269        // put top-level data into options
     270        topOptions = {
     271            title: ds.title,
     272            theme: ds.theme,
     273            dateParser: ds.dateParser
     274        };
     275        dsOptions = util.merge(ds.options, topOptions);
    203276        dsId = ds.id || "ds" + x;
    204277        datasets[x] = tm.createDataset(dsId, dsOptions);
     
    239312            }
    240313            bands[x] = Timeline.createBandInfo(bandInfo);
    241             if (x > 0 && TimeMap.TimelineVersion() == "1.2") {
     314            if (x > 0 && util.TimelineVersion() == "1.2") {
    242315                // set all to the same layout
    243316                bands[x].eventPainter.setLayout(bands[0].eventPainter.getLayout());
     
    257330            var data = config.datasets[x], options, type, callback, loaderClass, loader;
    258331            // support some older syntax
    259             options = data.options || data.data || {};
     332            options = data.data || data.options || {};
    260333            type = data.type || options.type;
    261             callback = function() { loadManager.increment() };
     334            callback = function() { loadManager.increment(); };
    262335            // get loader class
    263336            loaderClass = (typeof(type) == 'string') ? TimeMap.loaders[type] : type;
     
    275348
    276349/**
    277  * Load manager - static singleton for managing multiple asynchronous loads
     350 * @class Static singleton for managing multiple asynchronous loads
    278351 */
    279352TimeMap.loadManager = new function() {
     
    284357     * @param {TimeMap} tm          TimeMap instance
    285358     * @param {int} target     Number of datasets we're loading
    286      * @param {Object} options      Container for optional functions
     359     * @param {Object} options      Container for optional settings:<pre>
     360     *   {Function} dataLoadedFunction      Custom function replacing default completion function;
     361     *                                      should take one parameter, the TimeMap object
     362     *   {String/Date} scrollTo             Where to scroll the timeline when load is complete
     363     *                                      Options: "earliest", "latest", "now", date string, Date
     364     *   {Function} dataDisplayedFunction   Custom function to fire once data is loaded and displayed;
     365     *                                      should take one parameter, the TimeMap object
     366     * </pre>
    287367     */
    288368    this.init = function(tm, target, config) {
     
    304384   
    305385    /**
    306      * Function to fire when all loads are complete
     386     * Function to fire when all loads are complete.
     387     * Default behavior is to scroll to a given date (if provided) and
     388     * layout the timeline.
    307389     */
    308390    this.complete = function() {
     391        var tm = this.tm;
    309392        // custom function including timeline scrolling and layout
    310393        var func = this.opts.dataLoadedFunction;
     
    332415                        }
    333416                        // either the parse worked, or it was a date to begin with
    334                         if (scrollTo.constructor == Date) d = scrollTo;
     417                        if (scrollTo.constructor == Date) {
     418                            d = scrollTo;
     419                        }
    335420                }
    336                 this.tm.timeline.getBand(0).setCenterVisibleDate(d);
    337             }
    338             this.tm.timeline.layout();
     421                tm.timeline.getBand(0).setCenterVisibleDate(d);
     422            }
     423            tm.timeline.layout();
    339424            // custom function to be called when data is loaded
    340425            func = this.opts.dataDisplayedFunction;
     
    347432
    348433/**
    349  * Map of different data loader functions.
    350  * New loaders should add their loader function to this map; loader
     434 * @namespace
     435 * Namespace for different data loader functions.
     436 * New loaders should add their factories or constructors to this object; loader
    351437 * functions are passed an object with parameters in TimeMap.init().
    352438 */
     
    354440
    355441/**
     442 * @class
    356443 * Basic loader class, for pre-loaded data.
    357444 * Other types of loaders should take the same parameter.
    358445 *
    359  * @param {Object} options          All options for the loader:
     446 * @constructor
     447 * @param {Object} options          All options for the loader:<pre>
    360448 *   {Array} data                       Array of items to load
    361449 *   {Function} preloadFunction         Function to call on data before loading
    362450 *   {Function} transformFunction       Function to call on individual items before loading
     451 * </pre>
    363452 */
    364453TimeMap.loaders.basic = function(options) {
    365     // get standard functions
     454    // get standard functions and document
    366455    TimeMap.loaders.mixin(this, options);
    367     // allow "value" for backwards compatibility
    368     this.data = options.items || options.value || [];
    369 }
     456    /**
     457     * Function to call on data object before loading
     458     * @name TimeMap.loaders.basic#preload
     459     * @function
     460     * @parameter {Object} data     Data to preload
     461     * @return {Object[]} data      Array of item data
     462     */
     463     
     464    /**
     465     * Function to call on a single item data object before loading
     466     * @name TimeMap.loaders.basic#transform
     467     * @function
     468     * @parameter {Object} data     Data to transform
     469     * @return {Object} data        Transformed data for one item
     470     */
     471   
     472    /**
     473     * Array of item data to load.
     474     * @type Object[]
     475     */
     476    this.data = options.items ||
     477        // allow "value" for backwards compatibility
     478        options.value || [];
     479};
    370480
    371481/**
     
    382492    // run callback
    383493    callback();
    384 }
    385 
    386 /**
     494};
     495
     496/**
     497 * @class
    387498 * Generic class for loading remote data with a custom parser function
    388499 *
    389  * @param {Object} options          All options for the loader:
     500 * @constructor
     501 * @param {Object} options          All options for the loader:<pre>
    390502 *   {Array} url                        URL of file to load (NB: must be local address)
    391  *   {Function} parserFunction          Parser function to turn data into JavaScript array
     503 *   {Function} parserFunction          Parser function to turn a string into a JavaScript array
    392504 *   {Function} preloadFunction         Function to call on data before loading
    393505 *   {Function} transformFunction       Function to call on individual items before loading
     506 * </pre>
    394507 */
    395508TimeMap.loaders.remote = function(options) {
    396     // get standard functions
     509    // get standard functions and document
    397510    TimeMap.loaders.mixin(this, options);
    398     // get URL to load
     511    /**
     512     * Parser function to turn a string into a JavaScript array
     513     * @name TimeMap.loaders.remote#parse
     514     * @function
     515     * @parameter {String} s    String to parse
     516     * @return {Array} data     Array of item data
     517     */
     518     
     519    /**
     520     * Function to call on data object before loading
     521     * @name TimeMap.loaders.remote#preload
     522     * @function
     523     * @parameter {Object} data     Data to preload
     524     * @return {Object[]} data      Array of item data
     525     */
     526     
     527    /**
     528     * Function to call on a single item data object before loading
     529     * @name TimeMap.loaders.remote#transform
     530     * @function
     531     * @parameter {Object} data     Data to transform
     532     * @return {Object} data        Transformed data for one item
     533     */
     534   
     535    /**
     536     * URL to load
     537     * @type String
     538     */
    399539    this.url = options.url;
    400 }
    401 
    402 /**
    403  * KML load function.
     540};
     541
     542/**
     543 * Remote load function.
    404544 *
    405545 * @param {TimeMapDataset} dataset  Dataset to load data into
     
    408548TimeMap.loaders.remote.prototype.load = function(dataset, callback) {
    409549    var loader = this;
     550   
     551    // XXX: It would be nice to save the callback function here,
     552    // and be able to cancel it (or cancel all) if needed
     553   
    410554    // get items
    411555    GDownloadUrl(this.url, function(result) {
     
    418562        callback();
    419563    });
    420 }
     564};
    421565
    422566/**
     
    424568 *
    425569 * @param {Function} loader         Loader to add functions to
    426  * @param {Object} options          Options for the loader:
     570 * @param {Object} options          Options for the loader:<pre>
    427571 *   {Function} parserFunction          Parser function to turn data into JavaScript array
    428572 *   {Function} preloadFunction         Function to call on data before loading
    429573 *   {Function} transformFunction       Function to call on individual items before loading
     574 * </pre>
    430575 */
    431576TimeMap.loaders.mixin = function(loader, options) {
     
    435580    loader.preload = options.preloadFunction || dummy;
    436581    loader.transform = options.transformFunction || dummy;
    437 
    438 
    439 /**
    440  * Map of common timeline intervals. Add custom intervals here if you
    441  * want to refer to them by key rather than as literals.
    442  */
    443 TimeMap.intervals = {
    444     'sec': [DT.SECOND, DT.MINUTE],
    445     'min': [DT.MINUTE, DT.HOUR],
    446     'hr': [DT.HOUR, DT.DAY],
    447     'day': [DT.DAY, DT.WEEK],
    448     'wk': [DT.WEEK, DT.MONTH],
    449     'mon': [DT.MONTH, DT.YEAR],
    450     'yr': [DT.YEAR, DT.DECADE],
    451     'dec': [DT.DECADE, DT.CENTURY]
    452 };
    453 
    454 /**
    455  * Map of Google map types. Using keys rather than literals allows
    456  * for serialization of the map type.
    457  */
    458 TimeMap.mapTypes = {
    459     'normal':G_NORMAL_MAP,
    460     'satellite':G_SATELLITE_MAP,
    461     'hybrid':G_HYBRID_MAP,
    462     'physical':G_PHYSICAL_MAP,
    463     'moon':G_MOON_VISIBLE_MAP,
    464     'sky':G_SKY_VISIBLE_MAP
    465582};
    466583
     
    473590 */
    474591TimeMap.prototype.createDataset = function(id, options) {
    475     options = options || {}; // make sure the options object isn't null
    476     if (!("title" in options)) {
    477         options.title = id;
    478     }
    479592    var dataset = new TimeMapDataset(this, options);
    480593    this.datasets[id] = dataset;
    481594    // add event listener
    482595    if (this.opts.centerOnItems) {
    483         var tm = this;
     596        var map = this.map, bounds = this.mapBounds;
    484597        GEvent.addListener(dataset, 'itemsloaded', function() {
    485             var map = tm.map, bounds = tm.mapBounds;
    486             // determine the zoom level from the bounds
    487             map.setZoom(map.getBoundsZoomLevel(bounds));
    488             // determine the center from the bounds
    489             map.setCenter(bounds.getCenter());
     598            // determine the center and zoom level from the bounds
     599            map.setCenter(
     600                bounds.getCenter(),
     601                map.getBoundsZoomLevel(bounds)
     602            );
    490603        });
    491604    }
     
    497610 * iteration method, as it allows for future iterator options.
    498611 *
    499  * @param {Function} f    The function to run
     612 * @param {Function} f    The function to run, taking one dataset as an argument
    500613 */
    501614TimeMap.prototype.each = function(f) {
     
    508621
    509622/**
     623 * Run a function on each item in each dataset in the timemap.
     624 *
     625 * @param {Function} f    The function to run, taking one item as an argument
     626 */
     627TimeMap.prototype.eachItem = function(f) {
     628    this.each(function(ds) {
     629        ds.each(function(item) {
     630            f(item);
     631        });
     632    });
     633};
     634
     635/**
     636 * Get all items from all datasets.
     637 *
     638 * @return {TimeMapItem[]}  Array of all items
     639 */
     640TimeMap.prototype.getItems = function(index) {
     641    var items = [];
     642    this.eachItem(function(item) {
     643        items.push(item);
     644    });
     645    return items;
     646};
     647
     648/**
    510649 * Initialize the timeline - this must happen separately to allow full control of
    511650 * timeline properties.
     
    523662    }
    524663   
    525     // initialize timeline
     664    /**
     665     * The associated timeline object
     666     * @type Timeline
     667     */
    526668    this.timeline = Timeline.create(this.tElement, bands);
    527669   
     
    563705    // filter chain for timeline events
    564706    this.addFilterChain("timeline",
     707        // on
    565708        function(item) {
    566709            item.showEvent();
    567710        },
     711        // off
    568712        function(item) {
    569713            item.hideEvent();
     714        },
     715        // pre
     716        null,
     717        // post
     718        function(tm) {
     719            tm.eventSource._events._index();
     720            tm.timeline.layout();
    570721        }
    571722    );
     
    599750 */
    600751TimeMap.prototype.filter = function(fid) {
    601     var filters = this.filters[fid];
     752    var filterChain = this.chains[fid], chain;
    602753    // if no filters exist, forget it
    603     if (!filters || !filters.chain || filters.chain.length === 0) {
     754    if (!filterChain) {
    604755        return;
     756    }
     757    chain = filterChain.chain;
     758    if (!chain || chain.length === 0) {
     759        return;
     760    }
     761    // pre-filter function
     762    if (filterChain.pre) {
     763        filterChain.pre(this);
    605764    }
    606765    // run items through filter
    607766    this.each(function(ds) {
    608767        ds.each(function(item) {
    609             F_LOOP: {
    610                 for (var i = filters.chain.length - 1; i >= 0; i--) {
    611                     if (!filters.chain[i](item)) {
     768            var done = false;
     769            F_LOOP: while (!done) {
     770                for (var i = chain.length - 1; i >= 0; i--) {
     771                    if (!chain[i](item)) {
    612772                        // false condition
    613                         filters.off(item);
     773                        filterChain.off(item);
    614774                        break F_LOOP;
    615775                    }
    616776                }
    617777                // true condition
    618                 filters.on(item);
     778                filterChain.on(item);
     779                done = true;
    619780            }
    620781        });
    621782    });
     783    // post-filter function
     784    if (filterChain.post) {
     785        filterChain.post(this);
     786    }
    622787};
    623788
     
    628793 * @param {Function} fon    Function to run on an item if filter is true
    629794 * @param {Function} foff   Function to run on an item if filter is false
    630  */
    631 TimeMap.prototype.addFilterChain = function(fid, fon, foff) {
    632     this.filters[fid] = {
     795 * @param {Function} [pre]  Function to run before the filter runs
     796 * @param {Function} [post] Function to run after the filter runs
     797 */
     798TimeMap.prototype.addFilterChain = function(fid, fon, foff, pre, post) {
     799    this.chains[fid] = {
    633800        chain:[],
    634801        on: fon,
    635         off: foff
     802        off: foff,
     803        pre: pre,
     804        post: post
    636805    };
    637806};
     
    642811 * @param {String} fid      Id of the filter chain
    643812 */
    644 TimeMap.prototype.removeFilterChain = function(fid, on, off) {
    645     this.filters[fid] = null;
     813TimeMap.prototype.removeFilterChain = function(fid) {
     814    this.chains[fid] = null;
    646815};
    647816
     
    653822 */
    654823TimeMap.prototype.addFilter = function(fid, f) {
    655     if (this.filters[fid] && this.filters[fid].chain) {
    656         this.filters[fid].chain.push(f);
     824    var filterChain = this.chains[fid];
     825    if (filterChain && filterChain.chain) {
     826        filterChain.chain.push(f);
    657827    }
    658828};
     
    662832 *
    663833 * @param {String} fid      Id of the filter chain
    664  * XXX: Support index here
    665  */
    666 TimeMap.prototype.removeFilter = function(fid) {
    667     if (this.filters[fid] && this.filters[fid].chain) {
    668         this.filters[fid].chain.pop();
    669     }
    670 };
    671 
    672 /**
    673  * Map of different filter functions. Adding new filters to this
    674  * map allows them to be specified by string name.
     834 * @param {Function} [f]    The function to remove
     835 */
     836TimeMap.prototype.removeFilter = function(fid, f) {
     837    var filterChain = this.chains[fid];
     838    if (filterChain && filterChain.chain) {
     839        var chain = filterChain.chain;
     840        if (!f) {
     841            // just remove the last filter added
     842            chain.pop();
     843        }
     844        else {
     845            // look for the specific filter to remove
     846            for(var i = 0; i < chain.length; i++){
     847          if(chain[i] == f){
     848            chain.splice(i, 1);
     849          }
     850        }
     851        }
     852    }
     853};
     854
     855/**
     856 * @namespace
     857 * Namespace for different filter functions. Adding new filters to this
     858 * object allows them to be specified by string name.
    675859 */
    676860TimeMap.filters = {};
    677861
    678862/**
    679  * Static filter function: Hide items not shown on the timeline
     863 * Static filter function: Hide items not in the visible area of the timeline.
    680864 *
    681865 * @param {TimeMapItem} item    Item to test for filter
     
    683867 */
    684868TimeMap.filters.hidePastFuture = function(item) {
    685     var topband = item.dataset.timemap.timeline.getBand(0);
    686     var maxVisibleDate = topband.getMaxVisibleDate().getTime();
    687     var minVisibleDate = topband.getMinVisibleDate().getTime();
    688     if (item.event !== null) {
    689         var itemStart = item.event.getStart().getTime();
    690         var itemEnd = item.event.getEnd().getTime();
     869    var topband = item.dataset.timemap.timeline.getBand(0),
     870        maxVisibleDate = topband.getMaxVisibleDate().getTime(),
     871        minVisibleDate = topband.getMinVisibleDate().getTime();
     872    if (item.event) {
     873        var itemStart = item.event.getStart().getTime(),
     874            itemEnd = item.event.getEnd().getTime();
    691875        // hide items in the future
    692876        if (itemStart > maxVisibleDate) {
     
    703887
    704888/**
    705  * Static filter function: Hide items not shown on the timeline
     889 * Static filter function: Hide items not present at the exact
     890 * center date of the timeline (will only work for duration events).
    706891 *
    707892 * @param {TimeMapItem} item    Item to test for filter
     
    709894 */
    710895TimeMap.filters.showMomentOnly = function(item) {
    711     var topband = item.dataset.timemap.timeline.getBand(0);
    712     var momentDate = topband.getCenterVisibleDate().getTime();
    713     if (item.event !== null) {
    714         var itemStart = item.event.getStart().getTime();
    715         var itemEnd = item.event.getEnd().getTime();
     896    var topband = item.dataset.timemap.timeline.getBand(0),
     897        momentDate = topband.getCenterVisibleDate().getTime();
     898    if (item.event) {
     899        var itemStart = item.event.getStart().getTime(),
     900            itemEnd = item.event.getEnd().getTime();
    716901        // hide items in the future
    717902        if (itemStart > momentDate) {
     
    728913
    729914/*----------------------------------------------------------------------------
    730  * TimeMapDataset Class - holds references to items and visual themes
     915 * TimeMapDataset Class
    731916 *---------------------------------------------------------------------------*/
    732917
    733918/**
    734  * Create a new TimeMap dataset to hold a set of items
     919 * @class
     920 * The TimeMapDataset object holds an array of items and dataset-level
     921 * options and settings, including visual themes.
    735922 *
    736923 * @constructor
    737924 * @param {TimeMap} timemap         Reference to the timemap object
    738  * @param {Object} options          Object holding optional arguments:
     925 * @param {Object} [options]        Object holding optional arguments:<pre>
     926 *   {String} id                        Key for this dataset in the datasets map
    739927 *   {String} title                     Title of the dataset (for the legend)
    740928 *   {String or theme object} theme     Theme settings.
     
    742930 *   {Function} openInfoWindow          Function redefining how info window opens
    743931 *   {Function} closeInfoWindow         Function redefining how info window closes
    744  */
    745 function TimeMapDataset(timemap, options) {
    746     // hold reference to timemap
     932 * </pre>
     933 */
     934TimeMapDataset = function(timemap, options) {
     935
     936    /**
     937     * Reference to parent TimeMap
     938     * @type TimeMap
     939     */
    747940    this.timemap = timemap;
    748     // initialize timeline event source
     941    /**
     942     * EventSource for timeline events
     943     * @type Timeline.EventSource
     944     */
    749945    this.eventSource = new Timeline.DefaultEventSource();
    750     // initialize array of items
     946    /**
     947     * Array of child TimeMapItems
     948     * @type Array
     949     */
    751950    this.items = [];
    752     // for show/hide functions
     951    /**
     952     * Whether the dataset is visible
     953     * @type Boolean
     954     */
    753955    this.visible = true;
    754956   
    755957    // set defaults for options
    756     this.opts = options || {}; // make sure the options object isn't null
    757     this.opts.title = options.title || "";
    758    
    759     // get theme by key or object
    760     if (typeof(options.theme) == "string") {
    761         options.theme = TimeMapDataset.themes[options.theme];
    762     }
    763     this.opts.theme = options.theme || this.timemap.opts.theme || new TimeMapDatasetTheme({});
    764     // allow icon path override in options or timemap options
    765     this.opts.theme.eventIconPath = options.eventIconPath ||
    766         this.timemap.opts.eventIconPath || this.opts.theme.eventIconPath;
    767     this.opts.theme.eventIcon = options.eventIconPath + this.opts.theme.eventIconImage;
    768    
    769     // allow for other data parsers (e.g. Gregorgian) by key or function
    770     if (typeof(options.dateParser) == "string") {
    771         options.dateParser = TimeMapDataset.dateParsers[options.dateParser];
    772     }
    773     this.opts.dateParser = options.dateParser || TimeMapDataset.hybridParser;
    774    
    775     // get functions
    776     this.getItems = function() { return this.items; };
     958    var defaults = {
     959        title:          'Untitled',
     960        dateParser:     TimeMapDataset.hybridParser
     961    };
     962       
     963    /**
     964     * Container for optional settings passed in the "options" parameter
     965     * @type Object
     966     */
     967    this.opts = options = util.merge(options, defaults, timemap.opts);
     968   
     969    // allow date parser to be specified by key
     970    options.dateParser = util.lookup(options.dateParser, TimeMap.dateParsers);
     971    // allow theme options to be specified in options
     972    options.theme = TimeMapTheme.create(options.theme, options);
     973   
     974    /**
     975     * Return an array of this dataset's items
     976     *
     977     * @param {int} [index]     Index of single item to return
     978     * @return {TimeMapItem[]}  Single item, or array of all items if no index was supplied
     979     */
     980    this.getItems = function(index) {
     981        if (index !== undefined) {
     982            if (index < this.items.length) {
     983                return this.items[index];
     984            }
     985            else {
     986                return null;
     987            }
     988        }
     989        return this.items;
     990    };
     991   
     992    /**
     993     * Return the title of the dataset
     994     *
     995     * @return {String}     Dataset title
     996     */
    777997    this.getTitle = function() { return this.opts.title; };
    778 }
    779 
    780 /**
    781  * Wrapper to fix Timeline Gregorian parser for invalid strings
     998};
     999
     1000/**
     1001 * Better Timeline Gregorian parser... shouldn't be necessary :(.
     1002 * Gregorian dates are years with "BC" or "AD"
    7821003 *
    7831004 * @param {String} s    String to parse into a Date object
     
    7851006 */
    7861007TimeMapDataset.gregorianParser = function(s) {
    787     d = DT.parseGregorianDateTime(s);
    788     // check for invalid dates
    789     if (!d.getFullYear()) d = null;
    790     return d;
    791 };
    792 
    793 /**
    794  * Parse dates with the ISO 8601 parser, then fall back on the Gregorian
    795  * parser if the first parse fails
     1008    if (!s) {
     1009        return null;
     1010    } else if (s instanceof Date) {
     1011        return s;
     1012    }
     1013    // look for BC
     1014    var bc = Boolean(s.match(/b\.?c\.?/i));
     1015    // parse - parseInt will stop at non-number characters
     1016    var year = parseInt(s, 10);
     1017    // look for success
     1018    if (!isNaN(year)) {
     1019        // deal with BC
     1020        if (bc) {
     1021            year = 1 - year;
     1022        }
     1023        // make Date and return
     1024        var d = new Date(0);
     1025        d.setUTCFullYear(year);
     1026        return d;
     1027    }
     1028    else {
     1029        return null;
     1030    }
     1031};
     1032
     1033/**
     1034 * Parse date strings with a series of date parser functions, until one works.
     1035 * In order:
     1036 * <ol>
     1037 *  <li>Date.parse() (so Date.js should work here, if it works with Timeline...)</li>
     1038 *  <li>Gregorian parser</li>
     1039 *  <li>The Timeline ISO 8601 parser</li>
     1040 * </ol>
    7961041 *
    7971042 * @param {String} s    String to parse into a Date object
     
    7991044 */
    8001045TimeMapDataset.hybridParser = function(s) {
    801     var d = DT.parseIso8601DateTime(s);
    802     if (!d) {
    803         d = TimeMapDataset.gregorianParser(s);
    804     }
     1046    // try native date parse
     1047    var d = new Date(Date.parse(s));
     1048    if (isNaN(d)) {
     1049        if (typeof(s) == "string") {
     1050            // look for Gregorian dates
     1051            if (s.match(/^-?\d{1,6} ?(a\.?d\.?|b\.?c\.?e?\.?|c\.?e\.?)?$/i)) {
     1052                d = TimeMapDataset.gregorianParser(s);
     1053            }
     1054            // try ISO 8601 parse
     1055            else {
     1056                try {
     1057                    d = DateTime.parseIso8601DateTime(s);
     1058                } catch(e) {
     1059                    d = null;
     1060                }
     1061            }
     1062        }
     1063        else {
     1064            return null;
     1065        }
     1066    }
     1067    // d should be a date or null
    8051068    return d;
    806 };
    807 
    808 /**
    809  * Map of supported date parsers. Add custom date parsers here if you
    810  * want to refer to them by key rather than as a function name.
    811  */
    812 TimeMapDataset.dateParsers = {
    813     'hybrid': TimeMapDataset.hybridParser,
    814     'iso8601': DT.parseIso8601DateTime,
    815     'gregorian': TimeMapDataset.gregorianParser
    8161069};
    8171070
     
    8291082
    8301083/**
    831  * Add items to map and timeline.
     1084 * Add an array of items to the map and timeline.
    8321085 * Each item has both a timeline event and a map placemark.
    8331086 *
    834  * @param {Object} data             Data to be loaded. See loadItem() below for the format.
    835  * @param {Function} transform      If data is not in the above format, transformation function to make it so
     1087 * @param {Object} data             Data to be loaded. See loadItem() for the format.
     1088 * @param {Function} [transform]    If data is not in the above format, transformation function to make it so
     1089 * @see TimeMapDataset#loadItem
    8361090 */
    8371091TimeMapDataset.prototype.loadItems = function(data, transform) {
     
    8421096};
    8431097
    844 /*
     1098/**
    8451099 * Add one item to map and timeline.
    8461100 * Each item has both a timeline event and a map placemark.
    8471101 *
    848  * @param {Object} data         Data to be loaded, in the following format:
     1102 * @param {Object} data         Data to be loaded, in the following format: <pre>
    8491103 *      {String} title              Title of the item (visible on timeline)
    8501104 *      {DateTime} start            Start time of the event on the timeline
     
    8621116 *          {Float} west                Western longitude of the overlay
    8631117 *      {Object} options            Optional arguments to be passed to the TimeMapItem (@see TimeMapItem)
    864  * @param {Function} transform  If data is not in the above format, transformation function to make it so
     1118 * </pre>
     1119 * @param {Function} [transform]    If data is not in the above format, transformation function to make it so
     1120 * @return {TimeMapItem}            The created item (for convenience, as it's already been added)
     1121 * @see TimeMapItem
    8651122 */
    8661123TimeMapDataset.prototype.loadItem = function(data, transform) {
     1124
    8671125    // apply transformation, if any
    8681126    if (transform !== undefined) {
     
    8701128    }
    8711129    // transform functions can return a null value to skip a datum in the set
    872     if (data === null) {
     1130    if (!data) {
    8731131        return;
    8741132    }
    8751133   
    876     // use item theme if provided, defaulting to dataset theme
    877     var options = data.options || {};
    878     if (typeof(options.theme) == "string") {
    879         options.theme = TimeMapDataset.themes[options.theme];
    880     }
    881     var theme = options.theme || this.opts.theme;
    882     theme.eventIconPath = options.eventIconPath || this.opts.theme.eventIconPath;
    883     theme.eventIcon = theme.eventIconPath + theme.eventIconImage;
    884    
    885     var tm = this.timemap;
     1134    // set defaults for options
     1135    options = util.merge(data.options, this.opts);
     1136    // allow theme options to be specified in options
     1137    var theme = options.theme = TimeMapTheme.create(options.theme, options);
    8861138   
    8871139    // create timeline event
    8881140    var parser = this.opts.dateParser, start = data.start, end = data.end, instant;
    889     start = (start === undefined||start === "") ? null : parser(start);
    890     end = (end === undefined||end === "") ? null : parser(end);
    891     instant = (end === undefined);
     1141    start = start ? parser(start) : null;
     1142    end = end ? parser(end) : null;
     1143    instant = !end;
    8921144    var eventIcon = theme.eventIcon,
    8931145        title = data.title,
    8941146        // allow event-less placemarks - these will be always present on map
    8951147        event = null;
    896     if (start !== null) {
     1148    if (start !== null) { 
    8971149        var eventClass = Timeline.DefaultEventSource.Event;
    898         if (TimeMap.TimelineVersion() == "1.2") {
     1150        if (util.TimelineVersion() == "1.2") {
    8991151            // attributes by parameter
    9001152            event = new eventClass(start, end, null, null,
     
    9091161            // attributes in object
    9101162            event = new eventClass({
    911                 "start": start,
    912                 "end": end,
    913                 "instant": instant,
    914                 "text": title,
    915                 "icon": eventIcon,
    916                 "color": theme.eventColor,
    917                 "textColor": textColor
     1163                start: start,
     1164                end: end,
     1165                instant: instant,
     1166                text: title,
     1167                icon: eventIcon,
     1168                color: theme.eventColor,
     1169                textColor: textColor
    9181170            });
    9191171        }
     
    9211173   
    9221174    // set the icon, if any, outside the closure
    923     var markerIcon = ("icon" in data) ? data.icon : theme.icon,
    924         bounds = tm.mapBounds; // save some bytes
    925    
     1175    var markerIcon = theme.icon,
     1176        tm = this.timemap,
     1177        bounds = tm.mapBounds;
    9261178   
    9271179    // internal function: create map placemark
     
    9311183        var placemark = null, type = "", point = null;
    9321184        // point placemark
    933         if ("point" in pdata) {
     1185        if (pdata.point) {
     1186            var lat = pdata.point.lat, lon = pdata.point.lon;
     1187            if (lat === undefined || lon === undefined) {
     1188                // give up
     1189                return null;
     1190            }
    9341191            point = new GLatLng(
    9351192                parseFloat(pdata.point.lat),
     
    9401197                bounds.extend(point);
    9411198            }
    942             placemark = new GMarker(point, { icon: markerIcon });
     1199            // create marker
     1200            placemark = new GMarker(point, {
     1201                icon: markerIcon,
     1202                title: pdata.title
     1203            });
    9431204            type = "marker";
    9441205            point = placemark.getLatLng();
    9451206        }
    9461207        // polyline and polygon placemarks
    947         else if ("polyline" in pdata || "polygon" in pdata) {
     1208        else if (pdata.polyline || pdata.polygon) {
    9481209            var points = [], line;
    949             if ("polyline" in pdata) {
     1210            if (pdata.polyline) {
    9501211                line = pdata.polyline;
    9511212            } else {
    9521213                line = pdata.polygon;
    9531214            }
    954             for (var x=0; x<line.length; x++) {
    955                 point = new GLatLng(
    956                     parseFloat(line[x].lat),
    957                     parseFloat(line[x].lon)
    958                 );
    959                 points.push(point);
    960                 // add point to visible map bounds
    961                 if (tm.opts.centerOnItems) {
    962                     bounds.extend(point);
     1215            if (line && line.length) {
     1216                for (var x=0; x<line.length; x++) {
     1217                    point = new GLatLng(
     1218                        parseFloat(line[x].lat),
     1219                        parseFloat(line[x].lon)
     1220                    );
     1221                    points.push(point);
     1222                    // add point to visible map bounds
     1223                    if (tm.opts.centerOnItems) {
     1224                        bounds.extend(point);
     1225                    }
    9631226                }
    964             }
    965             if ("polyline" in pdata) {
    966                 placemark = new GPolyline(points,
    967                                           theme.lineColor,
    968                                           theme.lineWeight,
    969                                           theme.lineOpacity);
    970                 type = "polyline";
    971                 point = placemark.getVertex(Math.floor(placemark.getVertexCount()/2));
    972             } else {
    973                 placemark = new GPolygon(points,
    974                                          theme.polygonLineColor,
    975                                          theme.polygonLineWeight,
    976                                          theme.polygonLineOpacity,
    977                                          theme.fillColor,
    978                                          theme.fillOpacity);
    979                 type = "polygon";
    980                 point = placemark.getBounds().getCenter();
     1227                if ("polyline" in pdata) {
     1228                    placemark = new GPolyline(points,
     1229                                              theme.lineColor,
     1230                                              theme.lineWeight,
     1231                                              theme.lineOpacity);
     1232                    type = "polyline";
     1233                    point = placemark.getVertex(Math.floor(placemark.getVertexCount()/2));
     1234                } else {
     1235                    placemark = new GPolygon(points,
     1236                                             theme.polygonLineColor,
     1237                                             theme.polygonLineWeight,
     1238                                             theme.polygonLineOpacity,
     1239                                             theme.fillColor,
     1240                                             theme.fillOpacity);
     1241                    type = "polygon";
     1242                    point = placemark.getBounds().getCenter();
     1243                }
    9811244            }
    9821245        }
     
    10191282        for (i=0; i<types.length; i++) {
    10201283            if (types[i] in data) {
    1021                 pdata = {};
     1284                // put in title (only used for markers)
     1285                pdata = {title: title};
    10221286                pdata[types[i]] = data[types[i]];
    10231287                pdataArr.push(pdata);
     
    10291293            // create the placemark
    10301294            var p = createPlacemark(pdataArr[i]);
    1031             // take the first point and type as a default
    1032             point = point || p.point;
    1033             type = type || p.type;
    1034             placemark.push(p.placemark);
     1295            // check that the placemark was valid
     1296            if (p && p.placemark) {
     1297                // take the first point and type as a default
     1298                point = point || p.point;
     1299                type = type || p.type;
     1300                placemark.push(p.placemark);
     1301            }
    10351302        }
    10361303    }
     
    10411308   
    10421309    options.title = title;
    1043     options.type = type || "none";
    1044     options.theme = theme;
     1310    options.type = type;
    10451311    // check for custom infoPoint and convert to GLatLng
    10461312    if (options.infoPoint) {
     
    10581324    if (event !== null) {
    10591325        event.item = item;
    1060         this.eventSource.add(event);
     1326        // allow for custom event loading
     1327        if (!this.opts.noEventLoad) {
     1328            // add event to timeline
     1329            this.eventSource.add(event);
     1330        }
    10611331    }
    10621332    // add placemark(s) if any exist
     
    10681338                item.openInfoWindow();
    10691339            });
    1070             // add placemark and event to map and timeline
    1071             tm.map.addOverlay(placemark[i]);
     1340            // allow for custom placemark loading
     1341            if (!this.opts.noPlacemarkLoad) {
     1342                // add placemark to map
     1343                tm.map.addOverlay(placemark[i]);
     1344            }
    10721345            // hide placemarks until the next refresh
    10731346            placemark[i].hide();
     
    10811354
    10821355/*----------------------------------------------------------------------------
    1083  * Predefined visual themes for datasets, based on Google markers
     1356 * TimeMapTheme Class
    10841357 *---------------------------------------------------------------------------*/
    10851358
    10861359/**
    1087  * Create a new theme for a TimeMap dataset, defining colors and images
     1360 * @class
     1361 * Predefined visual themes for datasets, defining colors and images for
     1362 * map markers and timeline events.
    10881363 *
    10891364 * @constructor
    1090  * @param {Object} options          A container for optional arguments:
     1365 * @param {Object} [options]        A container for optional arguments:<pre>
    10911366 *      {GIcon} icon                    Icon for marker placemarks
    10921367 *      {String} color                  Default color in hex for events, polylines, polygons
     
    11001375 *      {String} fillOpacity            Opacity for polygon fill
    11011376 *      {String} eventColor             Background color for duration events
    1102  *      {URL} eventIcon                 Icon URL for instant events
    1103  */
    1104 function TimeMapDatasetTheme(options) {
     1377 *      {String} eventIconPath          Path to instant event icon directory
     1378 *      {String} eventIconImage         Filename of instant event icon image
     1379 *      {URL} eventIcon                 URL for instant event icons (overrides path + image)
     1380 *      {Boolean} classicTape           Whether to use the "classic" style timeline event tape
     1381 *                                      (NB: this needs additional css to work - see examples/artists.html)
     1382 * </pre>
     1383 */
     1384TimeMapTheme = function(options) {
     1385
    11051386    // work out various defaults - the default theme is Google's reddish color
    1106     options = options || {};
    1107    
    1108     if (!options.icon) {
     1387    var defaults = {
     1388        color:          "#FE766A",
     1389        lineOpacity:    1,
     1390        lineWeight:     2,
     1391        fillOpacity:    0.25,
     1392        eventTextColor: null,
     1393        eventIconPath:  "timemap/images/",
     1394        eventIconImage: "red-circle.png",
     1395        classicTape:    false,
     1396        iconImage:      GIP + "red-dot.png"
     1397    };
     1398   
     1399    // merge defaults with options
     1400    var settings = util.merge(options, defaults);
     1401   
     1402    // kill mergeOnly if necessary
     1403    delete settings.mergeOnly;
     1404   
     1405    // make default map icon if not supplied
     1406    if (!settings.icon) {
    11091407        // make new red icon
    11101408        var markerIcon = new GIcon(G_DEFAULT_ICON);
    1111         this.iconImage = options.iconImage || GIP + "red-dot.png";
    1112         markerIcon.image = this.iconImage;
     1409        markerIcon.image = settings.iconImage;
    11131410        markerIcon.iconSize = new GSize(32, 32);
    11141411        markerIcon.shadow = GIP + "msmarker.shadow.png";
     
    11161413        markerIcon.iconAnchor = new GPoint(16, 33);
    11171414        markerIcon.infoWindowAnchor = new GPoint(18, 3);
    1118     }
    1119    
    1120     this.icon =              options.icon || markerIcon;
    1121     this.color =             options.color || "#FE766A";
    1122     this.lineColor =         options.lineColor || this.color;
    1123     this.polygonLineColor =  options.polygonLineColor || this.lineColor;
    1124     this.lineOpacity =       options.lineOpacity || 1;
    1125     this.polgonLineOpacity = options.polgonLineOpacity || this.lineOpacity;
    1126     this.lineWeight =        options.lineWeight || 2;
    1127     this.polygonLineWeight = options.polygonLineWeight || this.lineWeight;
    1128     this.fillColor =         options.fillColor || this.color;
    1129     this.fillOpacity =       options.fillOpacity || 0.25;
    1130     this.eventColor =        options.eventColor || this.color;
    1131     this.eventTextColor =    options.eventTextColor || null;
    1132     this.eventIconPath =     options.eventIconPath || "timemap/images/";
    1133     this.eventIconImage =    options.eventIconImage || "red-circle.png";
    1134     this.eventIcon =         options.eventIcon || this.eventIconPath + this.eventIconImage;
    1135    
    1136     // whether to use the older "tape" event style for the newer Timeline versions
    1137     // NB: this needs additional css to work - see examples/artists.html
    1138     this.classicTape = ("classicTape" in options) ? options.classicTape : false;
    1139 }
    1140 
    1141 TimeMapDataset.redTheme = function(options) {
    1142     return new TimeMapDatasetTheme(options);
    1143 };
    1144 
    1145 TimeMapDataset.blueTheme = function(options) {
    1146     options = options || {};
    1147     options.iconImage = GIP + "blue-dot.png";
    1148     options.color = "#5A7ACF";
    1149     options.eventIconImage = "blue-circle.png";
    1150     return new TimeMapDatasetTheme(options);
    1151 };
    1152 
    1153 TimeMapDataset.greenTheme = function(options) {
    1154     options = options || {};
    1155     options.iconImage = GIP + "green-dot.png";
    1156     options.color = "#19CF54";
    1157     options.eventIconImage = "green-circle.png";
    1158     return new TimeMapDatasetTheme(options);
    1159 };
    1160 
    1161 TimeMapDataset.ltblueTheme = function(options) {
    1162     options = options || {};
    1163     options.iconImage = GIP + "ltblue-dot.png";
    1164     options.color = "#5ACFCF";
    1165     options.eventIconImage = "ltblue-circle.png";
    1166     return new TimeMapDatasetTheme(options);
    1167 };
    1168 
    1169 TimeMapDataset.purpleTheme = function(options) {
    1170     options = options || {};
    1171     options.iconImage = GIP + "purple-dot.png";
    1172     options.color = "#8E67FD";
    1173     options.eventIconImage = "purple-circle.png";
    1174     return new TimeMapDatasetTheme(options);
    1175 };
    1176 
    1177 TimeMapDataset.orangeTheme = function(options) {
    1178     options = options || {};
    1179     options.iconImage = GIP + "orange-dot.png";
    1180     options.color = "#FF9900";
    1181     options.eventIconImage = "orange-circle.png";
    1182     return new TimeMapDatasetTheme(options);
    1183 };
    1184 
    1185 TimeMapDataset.yellowTheme = function(options) {
    1186     options = options || {};
    1187     options.iconImage = GIP + "yellow-dot.png";
    1188     options.color = "#ECE64A";
    1189     options.eventIconImage = "yellow-circle.png";
    1190     return new TimeMapDatasetTheme(options);
    1191 };
    1192 
    1193 /**
    1194  * Map of themes. Add custom themes to this map if you want
    1195  * to load them by key rather than as an object.
    1196  */
    1197 TimeMapDataset.themes = {
    1198     'red': TimeMapDataset.redTheme(),
    1199     'blue': TimeMapDataset.blueTheme(),
    1200     'green': TimeMapDataset.greenTheme(),
    1201     'ltblue': TimeMapDataset.ltblueTheme(),
    1202     'orange': TimeMapDataset.orangeTheme(),
    1203     'yellow': TimeMapDataset.yellowTheme(),
    1204     'purple': TimeMapDataset.purpleTheme()
     1415        settings.icon = markerIcon;
     1416    }
     1417   
     1418    // cascade some settings as defaults
     1419    defaults = {
     1420        lineColor:          settings.color,
     1421        polygonLineColor:   settings.color,
     1422        polgonLineOpacity:  settings.lineOpacity,
     1423        polygonLineWeight:  settings.lineWeight,
     1424        fillColor:          settings.color,
     1425        eventColor:         settings.color,
     1426        eventIcon:          settings.eventIconPath + settings.eventIconImage
     1427    };
     1428    settings = util.merge(settings, defaults);
     1429   
     1430    // return configured options as theme
     1431    return settings;
     1432};
     1433
     1434/**
     1435 * Create a theme, based on an optional new or pre-set theme
     1436 *
     1437 * @param {TimeMapTheme} [theme]    Existing theme to clone
     1438 * @param {Object} [options]        Container for optional arguments - @see TimeMapTheme()
     1439 * @return {TimeMapTheme}           Configured theme
     1440 */
     1441TimeMapTheme.create = function(theme, options) {
     1442    // test for string matches and missing themes
     1443    if (theme) {
     1444        theme = TimeMap.util.lookup(theme, TimeMap.themes);
     1445    } else {
     1446        return new TimeMapTheme(options);
     1447    }
     1448   
     1449    // see if we need to clone - guessing fewer keys in options
     1450    var clone = false, key;
     1451    for (key in options) {
     1452        if (theme.hasOwnProperty(key)) {
     1453            clone = {};
     1454            break;
     1455        }
     1456    }
     1457    // clone if necessary
     1458    if (clone) {
     1459        for (key in theme) {
     1460            if (theme.hasOwnProperty(key)) {
     1461                clone[key] = options[key] || theme[key];
     1462            }
     1463        }
     1464        // fix event icon path, allowing full image path in options
     1465        clone.eventIcon = options.eventIcon ||
     1466            clone.eventIconPath + clone.eventIconImage;
     1467        return clone;
     1468    }
     1469    else {
     1470        return theme;
     1471    }
    12051472};
    12061473
    12071474
    12081475/*----------------------------------------------------------------------------
    1209  * TimeMapItem Class - holds references to map placemark and timeline event
     1476 * TimeMapItem Class
    12101477 *---------------------------------------------------------------------------*/
    12111478
    12121479/**
    1213  * Create a new TimeMap item with a map placemark and a timeline event
     1480 * @class
     1481 * The TimeMapItem object holds references to one or more map placemarks and
     1482 * an associated timeline event.
    12141483 *
    12151484 * @constructor
     
    12171486 * @param {Event} event             The timeline event
    12181487 * @param {TimeMapDataset} dataset  Reference to the parent dataset object
    1219  * @param {Object} options          A container for optional arguments:
     1488 * @param {Object} [options]        A container for optional arguments:<pre>
    12201489 *   {String} title                     Title of the item
    12211490 *   {String} description               Plain-text description of the item
     
    12261495 *   {Function} openInfoWindow          Function redefining how info window opens
    12271496 *   {Function} closeInfoWindow         Function redefining how info window closes
    1228  */
    1229 function TimeMapItem(placemark, event, dataset, options) {
    1230     // initialize vars
    1231     this.event =     event;
    1232     this.dataset =   dataset;
    1233     this.map =       dataset.timemap.map;
     1497 *   {String/TimeMapTheme} theme        Theme applying to this item, overriding dataset theme
     1498 * </pre>
     1499 */
     1500TimeMapItem = function(placemark, event, dataset, options) {
     1501
     1502    /**
     1503     * This item's timeline event
     1504     * @type Timeline.Event
     1505     */
     1506    this.event = event;
     1507   
     1508    /**
     1509     * This item's parent dataset
     1510     * @type TimeMapDataset
     1511     */
     1512    this.dataset = dataset;
     1513   
     1514    /**
     1515     * The timemap's map object
     1516     * @type GMap2
     1517     */
     1518    this.map = dataset.timemap.map;
    12341519   
    12351520    // initialize placemark(s) with some type juggling
    1236     if (placemark && TimeMap.isArray(placemark) && placemark.length === 0) {
     1521    if (placemark && util.isArray(placemark) && placemark.length === 0) {
    12371522        placemark = null;
    12381523    }
     
    12401525        placemark = placemark[0];
    12411526    }
     1527    /**
     1528     * This item's placemark(s)
     1529     * @type GMarker/GPolyline/GPolygon/GOverlay/Array
     1530     */
    12421531    this.placemark = placemark;
    12431532   
    12441533    // set defaults for options
    1245     this.opts = options || {};
    1246     this.opts.type =        options.type || '';
    1247     this.opts.title =       options.title || '';
    1248     this.opts.description = options.description || '';
    1249     this.opts.infoPoint =   options.infoPoint || null;
    1250     this.opts.infoHtml =    options.infoHtml || '';
    1251     this.opts.infoUrl =     options.infoUrl || '';
    1252    
    1253     // get functions
     1534    var defaults = {
     1535        type: 'none',
     1536        title: 'Untitled',
     1537        description: '',
     1538        infoPoint: null,
     1539        infoHtml: '',
     1540        infoUrl: '',
     1541        closeInfoWindow: TimeMapItem.closeInfoWindowBasic
     1542    };
     1543    this.opts = options = util.merge(options, defaults, dataset.opts);
     1544   
     1545    // select default open function
     1546    if (!options.openInfoWindow) {
     1547        if (options.infoUrl !== "") {
     1548            // load via AJAX if URL is provided
     1549            options.openInfoWindow = TimeMapItem.openInfoWindowAjax;
     1550        } else {
     1551            // otherwise default to basic window
     1552            options.openInfoWindow = TimeMapItem.openInfoWindowBasic;
     1553        }
     1554    }
     1555   
     1556    // getter functions
     1557   
     1558    /**
     1559     * Return the placemark type for this item
     1560     *
     1561     * @return {String}     Placemark type
     1562     */
    12541563    this.getType = function() { return this.opts.type; };
     1564   
     1565    /**
     1566     * Return the title for this item
     1567     *
     1568     * @return {String}     Item title
     1569     */
    12551570    this.getTitle = function() { return this.opts.title; };
     1571   
     1572    /**
     1573     * Return the item's "info point" (the anchor for the map info window)
     1574     *
     1575     * @return {GLatLng}    Info point
     1576     */
    12561577    this.getInfoPoint = function() {
    12571578        // default to map center if placemark not set
     
    12591580    };
    12601581   
    1261     // items initialize visible
     1582    /**
     1583     * Whether the item is visible
     1584     * @type Boolean
     1585     */
    12621586    this.visible = true;
    1263     // placemarks initialize hidden
     1587   
     1588    /**
     1589     * Whether the item's placemark is visible
     1590     * @type Boolean
     1591     */
    12641592    this.placemarkVisible = false;
    1265     // events initialize visible
     1593   
     1594    /**
     1595     * Whether the item's event is visible
     1596     * @type Boolean
     1597     */
    12661598    this.eventVisible = true;
    12671599   
    1268     // allow for custom open/close functions, set at item, dataset, or timemap level
    1269     this.openInfoWindow =   options.openInfoWindow ||
    1270         dataset.opts.openInfoWindow ||
    1271         dataset.timemap.opts.openInfoWindow ||
    1272         false;
    1273     if (!this.openInfoWindow) {
    1274         if (this.opts.infoUrl !== "") {
    1275             // load via AJAX if URL is provided
    1276             this.openInfoWindow = TimeMapItem.openInfoWindowAjax;
    1277         } else {
    1278             // otherwise default to basic window
    1279             this.openInfoWindow = TimeMapItem.openInfoWindowBasic;
    1280         }
    1281     }
    1282     this.closeInfoWindow = options.closeInfoWindow ||
    1283         dataset.opts.closeInfoWindow ||
    1284         dataset.timemap.opts.closeInfoWindow ||
    1285         TimeMapItem.closeInfoWindowBasic;
    1286 }
     1600    /**
     1601     * Open the info window for this item.
     1602     * By default this is the map infoWindow, but you can set custom functions
     1603     * for whatever behavior you want when the event or placemark is clicked
     1604     * @function
     1605     */
     1606    this.openInfoWindow = options.openInfoWindow;
     1607   
     1608    /**
     1609     * Close the info window for this item.
     1610     * By default this is the map infoWindow, but you can set custom functions
     1611     * for whatever behavior you want.
     1612     * @function
     1613     */
     1614    this.closeInfoWindow = options.closeInfoWindow;
     1615};
    12871616
    12881617/**
    1289  * Show the map placemark
     1618 * Show the map placemark(s)
    12901619 */
    12911620TimeMapItem.prototype.showPlacemark = function() {
     
    13031632
    13041633/**
    1305  * Hide the map placemark
     1634 * Hide the map placemark(s)
    13061635 */
    13071636TimeMapItem.prototype.hidePlacemark = function() {
     
    13201649
    13211650/**
    1322  * Show the timeline event
     1651 * Show the timeline event.
    13231652 * NB: Will likely require calling timeline.layout()
    13241653 */
     
    13341663
    13351664/**
    1336  * Show the timeline event
    1337  * NB: Will likely require calling timeline.layout()
     1665 * Hide the timeline event.
     1666 * NB: Will likely require calling timeline.layout(),
     1667 * AND calling eventSource._events._index()  (ugh)
    13381668 */
    13391669TimeMapItem.prototype.hideEvent = function() {
    13401670    if (this.event) {
    1341         if (this.eventVisible == true){
     1671        if (this.eventVisible){
    13421672            this.dataset.timemap.timeline.getBand(0)
    13431673                .getEventSource()._events._events.remove(this.event);
     
    14131743
    14141744/*----------------------------------------------------------------------------
    1415  * Utility functions, attached to TimeMap to avoid namespace issues
     1745 * Utility functions
    14161746 *---------------------------------------------------------------------------*/
    14171747
     
    14221752 * @return {String}         Trimmed string
    14231753 */
    1424 TimeMap.trim = function(str) {
     1754TimeMap.util.trim = function(str) {
    14251755    str = str && String(str) || '';
    14261756    return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
     
    14331763 * @return {Boolean}        Whether the object is an array
    14341764 */
    1435 TimeMap.isArray = function(o) {   
     1765TimeMap.util.isArray = function(o) {   
    14361766    return o && !(o.propertyIsEnumerable('length')) &&
    14371767        typeof o === 'object' && typeof o.length === 'number';
     
    14431773 * @param {XML Node} n      Node in which to look for tag
    14441774 * @param {String} tag      Name of tag to look for
    1445  * @param {String} ns       Optional namespace
     1775 * @param {String} [ns]     XML namespace to look in
    14461776 * @return {String}         Tag value as string
    14471777 */
    1448 TimeMap.getTagValue = function(n, tag, ns) {
     1778TimeMap.util.getTagValue = function(n, tag, ns) {
    14491779    var str = "";
    1450     var nList = TimeMap.getNodeList(n, tag, ns);
     1780    var nList = TimeMap.util.getNodeList(n, tag, ns);
    14511781    if (nList.length > 0) {
    14521782        n = nList[0].firstChild;
     
    14641794 * Empty container for mapping XML namespaces to URLs
    14651795 */
    1466 TimeMap.nsMap = {};
    1467 
    1468 /**
    1469  * Cross-browser implementation of getElementsByTagNameNS
     1796TimeMap.util.nsMap = {};
     1797
     1798/**
     1799 * Cross-browser implementation of getElementsByTagNameNS.
    14701800 * Note: Expects any applicable namespaces to be mapped in
    1471  * TimeMap.nsMap. XXX: There may be better ways to do this.
     1801 * TimeMap.util.nsMap. XXX: There may be better ways to do this.
    14721802 *
    14731803 * @param {XML Node} n      Node in which to look for tag
    14741804 * @param {String} tag      Name of tag to look for
    1475  * @param {String} ns       Optional namespace
     1805 * @param {String} [ns]     XML namespace to look in
    14761806 * @return {XML Node List}  List of nodes with the specified tag name
    14771807 */
    1478 TimeMap.getNodeList = function(n, tag, ns) {
     1808TimeMap.util.getNodeList = function(n, tag, ns) {
     1809    var nsMap = TimeMap.util.nsMap;
    14791810    if (ns === undefined) {
    14801811        // no namespace
    14811812        return n.getElementsByTagName(tag);
    14821813    }
    1483     if (n.getElementsByTagNameNS && TimeMap.nsMap[ns]) {
     1814    if (n.getElementsByTagNameNS && nsMap[ns]) {
    14841815        // function and namespace both exist
    1485         return n.getElementsByTagNameNS(TimeMap.nsMap[ns], tag);
     1816        return n.getElementsByTagNameNS(nsMap[ns], tag);
    14861817    }
    14871818    // no function, try the colon tag name
     
    14931824 *
    14941825 * @param {Object} coords       GLatLng, array, or string to convert
    1495  * @param {Boolean} reversed    Whether the points are KML-style lon/lat, rather than lat/lon
     1826 * @param {Boolean} [reversed]  Whether the points are KML-style lon/lat, rather than lat/lon
    14961827 * @return {Object}             TimeMap.init()-style point
    14971828 */
    1498 TimeMap.makePoint = function(coords, reversed) {
    1499     var latlon = null;
     1829TimeMap.util.makePoint = function(coords, reversed) {
     1830    var latlon = null,
     1831        trim = TimeMap.util.trim;
    15001832    // GLatLng
    15011833    if (coords.lat && coords.lng) {
     
    15031835    }
    15041836    // array of coordinates
    1505     if (TimeMap.isArray(coords)) {
     1837    if (TimeMap.util.isArray(coords)) {
    15061838        latlon = coords;
    15071839    }
    15081840    // string
    1509     if (latlon === null) {
     1841    if (!latlon) {
    15101842        // trim extra whitespace
    1511         coords = TimeMap.trim(coords);
     1843        coords = trim(coords);
    15121844        if (coords.indexOf(',') > -1) {
    15131845            // split on commas
     
    15181850        }
    15191851    }
    1520     if (reversed) latlon.reverse();
     1852    // deal with extra coordinates (i.e. KML altitude)
     1853    if (latlon.length > 2) {
     1854        latlon = latlon.slice(0, 2);
     1855    }
     1856    // deal with backwards (i.e. KML-style) coordinates
     1857    if (reversed) {
     1858        latlon.reverse();
     1859    }
    15211860    return {
    1522         "lat": TimeMap.trim(latlon[0]),
    1523         "lon": TimeMap.trim(latlon[1])
     1861        "lat": trim(latlon[0]),
     1862        "lon": trim(latlon[1])
    15241863    };
    15251864};
     
    15271866/**
    15281867 * Make TimeMap.init()-style polyline/polygons from a whitespace-delimited
    1529  * string of coordinates (such as those in GeoRSS and KML)
    1530  * XXX: Any reason for this to take arrays of GLatLngs as well?
     1868 * string of coordinates (such as those in GeoRSS and KML).
    15311869 *
    15321870 * @param {Object} coords       String to convert
    1533  * @param {Boolean} reversed    Whether the points are KML-style lon/lat, rather than lat/lon
     1871 * @param {Boolean} [reversed]  Whether the points are KML-style lon/lat, rather than lat/lon
    15341872 * @return {Object}             Formated coordinate array
    15351873 */
    1536 TimeMap.makePoly = function(coords, reversed) {
     1874TimeMap.util.makePoly = function(coords, reversed) {
    15371875    var poly = [], latlon;
    1538     var coordArr = TimeMap.trim(coords).split(/[\r\n\f ]+/);
    1539     if (coordArr.length == 0) return [];
     1876    var coordArr = TimeMap.util.trim(coords).split(/[\r\n\f ]+/);
     1877    if (coordArr.length === 0) return [];
    15401878    // loop through coordinates
    15411879    for (var x=0; x<coordArr.length; x++) {
    1542         latlon = (coordArr[x].indexOf(',')) ?
     1880        latlon = (coordArr[x].indexOf(',') > 0) ?
    15431881            // comma-separated coordinates (KML-style lon/lat)
    1544             latlon = coordArr[x].split(",") :
     1882            coordArr[x].split(",") :
    15451883            // space-separated coordinates - increment to step by 2s
    1546             latlon = [coordArr[x], coordArr[++x]];
    1547         if (reversed) latlon.reverse();
     1884            [coordArr[x], coordArr[++x]];
     1885        // deal with extra coordinates (i.e. KML altitude)
     1886        if (latlon.length > 2) {
     1887            latlon = latlon.slice(0, 2);
     1888        }
     1889        // deal with backwards (i.e. KML-style) coordinates
     1890        if (reversed) {
     1891            latlon.reverse();
     1892        }
    15481893        poly.push({
    15491894            "lat": latlon[0],
     
    15581903 *
    15591904 * @param {Date} d          Date to format
    1560  * @param {int} precision   Optional precision indicator:
     1905 * @param {int} [precision] Precision indicator:<pre>
    15611906 *                              3 (default): Show full date and time
    15621907 *                              2: Show full date and time, omitting seconds
    15631908 *                              1: Show date only
     1909 *</pre>
    15641910 * @return {String}         Formatted string
    15651911 */
    1566 TimeMap.formatDate = function(d, precision) {
     1912TimeMap.util.formatDate = function(d, precision) {
    15671913    // default to high precision
    15681914    precision = precision || 3;
     
    15701916    if (d) {
    15711917        // check for date.js support
    1572         if (d.toISOString) {
     1918        if (d.toISOString && precision == 3) {
    15731919            return d.toISOString();
    15741920        }
     
    15981944
    15991945/**
    1600  * Determine the SIMILE Timeline version
    1601  * XXX: quite rough at the moment
     1946 * Determine the SIMILE Timeline version.
    16021947 *
    16031948 * @return {String}     At the moment, only "1.2", "2.2.0", or what Timeline provides
    16041949 */
    1605 TimeMap.TimelineVersion = function() {
     1950TimeMap.util.TimelineVersion = function() {
    16061951    // check for Timeline.version support - added in 2.3.0
    16071952    if (Timeline.version) {
     
    16141959    }
    16151960};
     1961
     1962
     1963/**
     1964 * Identify the placemark type.
     1965 * XXX: Not 100% happy with this implementation, which relies heavily on duck-typing.
     1966 *
     1967 * @param {Object} pm       Placemark to identify
     1968 * @return {String}         Type of placemark, or false if none found
     1969 */
     1970TimeMap.util.getPlacemarkType = function(pm) {
     1971    if ('getIcon' in pm) {
     1972        return 'marker';
     1973    }
     1974    if ('getVertex' in pm) {
     1975        return 'setFillStyle' in pm ? 'polygon' : 'polyline';
     1976    }
     1977    return false;
     1978};
     1979
     1980/**
     1981 * Merge two or more objects, giving precendence to those
     1982 * first in the list (i.e. don't overwrite existing keys).
     1983 * Original objects will not be modified.
     1984 *
     1985 * @param {Object} obj1     Base object
     1986 * @param {Object} [objN]   Objects to merge into base
     1987 * @return {Object}         Merged object
     1988 */
     1989TimeMap.util.merge = function() {
     1990    var opts = {}, args = arguments, obj, key, x, y;
     1991    // must... make... subroutine...
     1992    var mergeKey = function(o1, o2, key) {
     1993        // note: existing keys w/undefined values will be overwritten
     1994        if (o1.hasOwnProperty(key) && o2[key] === undefined) {
     1995            o2[key] = o1[key];
     1996        }
     1997    };
     1998    for (x=0; x<args.length; x++) {
     1999        obj = args[x];
     2000        if (obj) {
     2001            // allow non-base objects to constrain what will be merged
     2002            if (x > 0 && 'mergeOnly' in obj) {
     2003                for (y=0; y<obj.mergeOnly.length; y++) {
     2004                    key = obj.mergeOnly[y];
     2005                    mergeKey(obj, opts, key);
     2006                }
     2007            }
     2008            // otherwise, just merge everything
     2009            else {
     2010                for (key in obj) {
     2011                    mergeKey(obj, opts, key);
     2012                }
     2013            }
     2014        }
     2015    }
     2016    return opts;
     2017};
     2018
     2019/**
     2020 * Attempt look up a key in an object, returning either the value,
     2021 * undefined if the key is a string but not found, or the key if not a string
     2022 *
     2023 * @param {String|Object} key   Key to look up
     2024 * @param {Object} map          Object in which to look
     2025 * @return {Object}             Value, undefined, or key
     2026 */
     2027TimeMap.util.lookup = function(key, map) {
     2028    if (typeof(key) == 'string') {
     2029        return map[key];
     2030    }
     2031    else {
     2032        return key;
     2033    }
     2034};
     2035
     2036
     2037/*----------------------------------------------------------------------------
     2038 * Lookup maps
     2039 * (need to be at end because some call util functions on initialization)
     2040 *---------------------------------------------------------------------------*/
     2041
     2042/**
     2043 * Lookup map of common timeline intervals. 
     2044 * Add custom intervals here if you want to refer to them by key rather
     2045 * than as a function name.
     2046 * @type Object
     2047 */
     2048TimeMap.intervals = {
     2049    sec: [DateTime.SECOND, DateTime.MINUTE],
     2050    min: [DateTime.MINUTE, DateTime.HOUR],
     2051    hr: [DateTime.HOUR, DateTime.DAY],
     2052    day: [DateTime.DAY, DateTime.WEEK],
     2053    wk: [DateTime.WEEK, DateTime.MONTH],
     2054    mon: [DateTime.MONTH, DateTime.YEAR],
     2055    yr: [DateTime.YEAR, DateTime.DECADE],
     2056    dec: [DateTime.DECADE, DateTime.CENTURY]
     2057};
     2058
     2059/**
     2060 * Lookup map of Google map types.
     2061 * @type Object
     2062 */
     2063TimeMap.mapTypes = {
     2064    normal: G_NORMAL_MAP,
     2065    satellite: G_SATELLITE_MAP,
     2066    hybrid: G_HYBRID_MAP,
     2067    physical: G_PHYSICAL_MAP,
     2068    moon: G_MOON_VISIBLE_MAP,
     2069    sky: G_SKY_VISIBLE_MAP
     2070};
     2071
     2072/**
     2073 * Lookup map of supported date parser functions.
     2074 * Add custom date parsers here if you want to refer to them by key rather
     2075 * than as a function name.
     2076 * @type Object
     2077 */
     2078TimeMap.dateParsers = {
     2079    hybrid: TimeMapDataset.hybridParser,
     2080    iso8601: DateTime.parseIso8601DateTime,
     2081    gregorian: TimeMapDataset.gregorianParser
     2082};
     2083 
     2084/**
     2085 * @namespace
     2086 * Pre-set event/placemark themes in a variety of colors.
     2087 * Add custom themes here if you want to refer to them by key rather
     2088 * than as a function name.
     2089 */
     2090TimeMap.themes = {
     2091
     2092    /**
     2093     * Red theme: #FE766A
     2094     * This is the default.
     2095     *
     2096     * @type TimeMapTheme
     2097     */
     2098    red: new TimeMapTheme(),
     2099   
     2100    /**
     2101     * Blue theme: #5A7ACF
     2102     *
     2103     * @type TimeMapTheme
     2104     */
     2105    blue: new TimeMapTheme({
     2106        iconImage: GIP + "blue-dot.png",
     2107        color: "#5A7ACF",
     2108        eventIconImage: "blue-circle.png"
     2109    }),
     2110
     2111    /**
     2112     * Green theme: #19CF54
     2113     *
     2114     * @type TimeMapTheme
     2115     */
     2116    green: new TimeMapTheme({
     2117        iconImage: GIP + "green-dot.png",
     2118        color: "#19CF54",
     2119        eventIconImage: "green-circle.png"
     2120    }),
     2121
     2122    /**
     2123     * Light blue theme: #5ACFCF
     2124     *
     2125     * @type TimeMapTheme
     2126     */
     2127    ltblue: new TimeMapTheme({
     2128        iconImage: GIP + "ltblue-dot.png",
     2129        color: "#5ACFCF",
     2130        eventIconImage: "ltblue-circle.png"
     2131    }),
     2132
     2133    /**
     2134     * Purple theme: #8E67FD
     2135     *
     2136     * @type TimeMapTheme
     2137     */
     2138    purple: new TimeMapTheme({
     2139        iconImage: GIP + "purple-dot.png",
     2140        color: "#8E67FD",
     2141        eventIconImage: "purple-circle.png"
     2142    }),
     2143
     2144    /**
     2145     * Orange theme: #FF9900
     2146     *
     2147     * @type TimeMapTheme
     2148     */
     2149    orange: new TimeMapTheme({
     2150        iconImage: GIP + "orange-dot.png",
     2151        color: "#FF9900",
     2152        eventIconImage: "orange-circle.png"
     2153    }),
     2154
     2155    /**
     2156     * Yellow theme: #ECE64A
     2157     *
     2158     * @type TimeMapTheme
     2159     */
     2160    yellow: new TimeMapTheme({
     2161        iconImage: GIP + "yellow-dot.png",
     2162        color: "#ECE64A",
     2163        eventIconImage: "yellow-circle.png"
     2164    })
     2165};
     2166
     2167// save to window
     2168window.TimeMap = TimeMap;
     2169window.TimeMapDataset = TimeMapDataset;
     2170window.TimeMapTheme = TimeMapTheme;
     2171window.TimeMapItem = TimeMapItem;
     2172
     2173})();
Note: See TracChangeset for help on using the changeset viewer.