1 /* 2 * Timemap.js Copyright 2010 Nick Rabinowitz. 3 * Licensed under the MIT License (see LICENSE.txt) 4 */ 5 6 /** 7 * @fileOverview 8 * Progressive loader 9 * 10 * @author Nick Rabinowitz (www.nickrabinowitz.com) 11 */ 12 13 // for JSLint 14 /*global TimeMap */ 15 16 /** 17 * @class 18 * Progressive loader class - basically a wrapper for another remote loader that can 19 * load data progressively by date range, depending on timeline position. 20 * 21 * <p>The progressive loader can take either another loader or parameters for 22 * another loader. It expects a loader with a "url" attribute including placeholder 23 * strings [start] and [end] for the start and end dates to retrieve. The assumption 24 * is that the data service can take start and end parameters and return the data for 25 * that date range.</p> 26 * 27 * @example 28 TimeMap.init({ 29 datasets: [ 30 { 31 title: "Progressive JSONP Dataset", 32 type: "progressive", 33 options: { 34 type: "jsonp", 35 url: "http://www.test.com/getsomejson.php?start=[start]&end=[end]callback=" 36 } 37 } 38 ], 39 // etc... 40 }); 41 * 42 * @example 43 TimeMap.init({ 44 datasets: [ 45 { 46 title: "Progressive KML Dataset", 47 type: "progressive", 48 options: { 49 loader: new TimeMap.loaders.kml({ 50 url: "/mydata.kml?start=[start]&end=[end]" 51 }) 52 } 53 } 54 ], 55 // etc... 56 }); 57 * @see <a href="../../examples/progressive.html">Progressive Loader Example</a> 58 * 59 * @constructor 60 * @param {Object} options All options for the loader 61 * @param {TimeMap.loaders.remote} [options.loader] Instantiated loader class (overrides "type") 62 * @param {String} [options.type] Name of loader class to use 63 * @param {String|Date} options.start Start of initial date range, as date or string 64 * @param {Number} options.interval Size in milliseconds of date ranges to load at a time 65 * @param {String|Date} [options.dataMinDate] Minimum date available in data (optional, will avoid 66 * unnecessary service requests if supplied) 67 * @param {String|Date} [options.dataMaxDate] Maximum date available in data (optional, will avoid 68 * unnecessary service requests if supplied) 69 * @param {Function} [options.formatUrl] Function taking (urlTemplate, start, end) and returning 70 * a URL formatted as needed by the service 71 * @param {Function} [options.formatDate={@link TimeMap.util.formatDate}] 72 * Function to turn a date into a string formatted 73 * as needed by the service 74 * @param {mixed} [options[...]] Other options needed by the "type" loader 75 */ 76 TimeMap.loaders.progressive = function(options) { 77 // get loader 78 var loader = options.loader, 79 type = options.type; 80 if (!loader) { 81 // get loader class 82 var loaderClass = (typeof(type) == 'string') ? TimeMap.loaders[type] : type; 83 loader = new loaderClass(options); 84 } 85 86 // quick string/date check 87 function cleanDate(d) { 88 if (typeof(d) == "string") { 89 d = TimeMapDataset.hybridParser(d); 90 } 91 return d; 92 } 93 94 // save loader attributes 95 var baseUrl = loader.url, 96 baseLoadFunction = loader.load, 97 interval = options.interval, 98 formatDate = options.formatDate || TimeMap.util.formatDate, 99 formatUrl = options.formatUrl, 100 zeroDate = cleanDate(options.start), 101 dataMinDate = cleanDate(options.dataMinDate), 102 dataMaxDate = cleanDate(options.dataMaxDate), 103 loaded = {}; 104 105 if (!formatUrl) { 106 formatUrl = function(url, start, end) { 107 return url 108 .replace('[start]', formatDate(start)) 109 .replace('[end]', formatDate(end)); 110 } 111 } 112 113 // We don't start with a TimeMap reference, so we need 114 // to stick the listener in on the first load() call 115 var addListener = function(dataset) { 116 var band = dataset.timemap.timeline.getBand(0); 117 // add listener 118 band.addOnScrollListener(function() { 119 // determine relevant blocks 120 var now = band.getCenterVisibleDate(), 121 currBlock = Math.floor((now.getTime() - zeroDate.getTime()) / interval), 122 currBlockTime = zeroDate.getTime() + (interval * currBlock) 123 nextBlockTime = currBlockTime + interval, 124 prevBlockTime = currBlockTime - interval, 125 // no callback necessary? 126 callback = function() { 127 dataset.timemap.timeline.layout(); 128 }; 129 130 // is the current block loaded? 131 if ((!dataMaxDate || currBlockTime < dataMaxDate.getTime()) && 132 (!dataMinDate || currBlockTime > dataMinDate.getTime()) && 133 !loaded[currBlock]) { 134 // load it 135 // console.log("loading current block (" + currBlock + ")"); 136 loader.load(dataset, callback, new Date(currBlockTime), currBlock); 137 } 138 // are we close enough to load the next block, and is it loaded? 139 if (nextBlockTime < band.getMaxDate().getTime() && 140 (!dataMaxDate || nextBlockTime < dataMaxDate.getTime()) && 141 !loaded[currBlock + 1]) { 142 // load next block 143 // console.log("loading next block (" + (currBlock + 1) + ")"); 144 loader.load(dataset, callback, new Date(nextBlockTime), currBlock + 1); 145 } 146 // are we close enough to load the previous block, and is it loaded? 147 if (prevBlockTime > band.getMinDate().getTime() && 148 (!dataMinDate || prevBlockTime > dataMinDate.getTime()) && 149 !loaded[currBlock - 1]) { 150 // load previous block 151 // console.log("loading prev block (" + (currBlock - 1) + ")"); 152 loader.load(dataset, callback, new Date(prevBlockTime), currBlock - 1); 153 } 154 }); 155 // kill this function so that listener is only added once 156 addListener = false; 157 }; 158 159 /** 160 * Load data based on current time 161 * @name TimeMap.loaders.progressive#load 162 * @function 163 * @param {TimeMapDataset} dataset Dataset to load data into 164 * @param {Function} callback Callback to execute when data is loaded 165 * @param {Date} start Start date to load data from 166 * @param {Number} currBlock Index of the current time block 167 */ 168 loader.load = function(dataset, callback, start, currBlock) { 169 // set start date, defaulting to zero date 170 start = cleanDate(start) || zeroDate; 171 // set current block, defaulting to 0 172 currBlock = currBlock || 0; 173 // set end by interval 174 var end = new Date(start.getTime() + interval); 175 176 // set current block as loaded 177 // XXX: Failed loads will give a false positive here... 178 // but I'm not sure how else to avoid multiple loads :( 179 loaded[currBlock] = true; 180 181 // put dates into URL 182 loader.url = formatUrl(baseUrl, start, end); 183 // console.log(loader.url); 184 185 // load data 186 baseLoadFunction.call(loader, dataset, function() { 187 // add onscroll listener if not yet done 188 if (addListener) { 189 addListener(dataset); 190 } 191 // run callback 192 callback(); 193 }); 194 }; 195 196 return loader; 197 }; 198