[123] | 1 | /* |
---|
| 2 | * Timemap.js Copyright 2008 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 Usage in TimeMap.init(): |
---|
| 28 | |
---|
| 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 | * |
---|
| 40 | * @example Usage in TimeMap.init(): |
---|
| 41 | |
---|
| 42 | datasets: [ |
---|
| 43 | { |
---|
| 44 | title: "Progressive KML Dataset", |
---|
| 45 | type: "progressive", |
---|
| 46 | options: { |
---|
| 47 | loader: new TimeMap.loaders.kml({ |
---|
| 48 | url: "/mydata.kml?start=[start]&end=[end]" |
---|
| 49 | }) |
---|
| 50 | } |
---|
| 51 | } |
---|
| 52 | ] |
---|
| 53 | * |
---|
| 54 | * @constructor |
---|
| 55 | * @param {Object} options All options for the loader:<pre> |
---|
| 56 | * {TimeMap.loaders.remote} [loader] Instantiated loader class (overrides "type") |
---|
| 57 | * {String} [type] Name of loader class to use |
---|
| 58 | * {String/Date} start Start of initial date range, as date or string |
---|
| 59 | * {Number} interval Size in milliseconds of date ranges to load at a time |
---|
| 60 | * {String/Date} [dataMinDate] Minimum date available in data (optional, will avoid |
---|
| 61 | * unnecessary service requests if supplied) |
---|
| 62 | * {String/Date} [dataMaxDate] Maximum date available in data (optional, will avoid |
---|
| 63 | * unnecessary service requests if supplied) |
---|
| 64 | * {Function} [formatUrl] Function taking (urlTemplate, start, end) and returning |
---|
| 65 | * a URL formatted as needed by the service |
---|
| 66 | * {Function} [formatDate] Function to turn a date into a string formatted |
---|
| 67 | * as needed by the service |
---|
| 68 | * ...more Any other options needed by the "type" loader |
---|
| 69 | * </pre> |
---|
| 70 | */ |
---|
| 71 | TimeMap.loaders.progressive = function(options) { |
---|
| 72 | // get loader |
---|
| 73 | var loader = options.loader, |
---|
| 74 | type = options.type; |
---|
| 75 | if (!loader) { |
---|
| 76 | // get loader class |
---|
| 77 | var loaderClass = (typeof(type) == 'string') ? TimeMap.loaders[type] : type; |
---|
| 78 | loader = new loaderClass(options); |
---|
| 79 | } |
---|
| 80 | |
---|
| 81 | // quick string/date check |
---|
| 82 | function cleanDate(d) { |
---|
| 83 | if (typeof(d) == "string") { |
---|
| 84 | d = TimeMapDataset.hybridParser(d); |
---|
| 85 | } |
---|
| 86 | return d; |
---|
| 87 | } |
---|
| 88 | |
---|
| 89 | // save loader attributes |
---|
| 90 | var baseUrl = loader.url, |
---|
| 91 | baseLoadFunction = loader.load, |
---|
| 92 | interval = options.interval, |
---|
| 93 | formatDate = options.formatDate || TimeMap.util.formatDate, |
---|
| 94 | formatUrl = options.formatUrl, |
---|
| 95 | zeroDate = cleanDate(options.start), |
---|
| 96 | dataMinDate = cleanDate(options.dataMinDate), |
---|
| 97 | dataMaxDate = cleanDate(options.dataMaxDate), |
---|
| 98 | loaded = {}; |
---|
| 99 | |
---|
| 100 | if (!formatUrl) { |
---|
| 101 | formatUrl = function(url, start, end) { |
---|
| 102 | return url |
---|
| 103 | .replace('[start]', formatDate(start)) |
---|
| 104 | .replace('[end]', formatDate(end)); |
---|
| 105 | } |
---|
| 106 | } |
---|
| 107 | |
---|
| 108 | // We don't start with a TimeMap reference, so we need |
---|
| 109 | // to stick the listener in on the first load() call |
---|
| 110 | var addListener = function(dataset) { |
---|
| 111 | var band = dataset.timemap.timeline.getBand(0); |
---|
| 112 | // add listener |
---|
| 113 | band.addOnScrollListener(function() { |
---|
| 114 | // determine relevant blocks |
---|
| 115 | var now = band.getCenterVisibleDate(), |
---|
| 116 | currBlock = Math.floor((now.getTime() - zeroDate.getTime()) / interval), |
---|
| 117 | currBlockTime = zeroDate.getTime() + (interval * currBlock) |
---|
| 118 | nextBlockTime = currBlockTime + interval, |
---|
| 119 | prevBlockTime = currBlockTime - interval, |
---|
| 120 | // no callback necessary? |
---|
| 121 | callback = function() { |
---|
| 122 | dataset.timemap.timeline.layout(); |
---|
| 123 | }; |
---|
| 124 | |
---|
| 125 | // is the current block loaded? |
---|
| 126 | if ((!dataMaxDate || currBlockTime < dataMaxDate.getTime()) && |
---|
| 127 | (!dataMinDate || currBlockTime > dataMinDate.getTime()) && |
---|
| 128 | !loaded[currBlock]) { |
---|
| 129 | // load it |
---|
| 130 | // console.log("loading current block (" + currBlock + ")"); |
---|
| 131 | loader.load(dataset, callback, new Date(currBlockTime), currBlock); |
---|
| 132 | } |
---|
| 133 | // are we close enough to load the next block, and is it loaded? |
---|
| 134 | if (nextBlockTime < band.getMaxDate().getTime() && |
---|
| 135 | (!dataMaxDate || nextBlockTime < dataMaxDate.getTime()) && |
---|
| 136 | !loaded[currBlock + 1]) { |
---|
| 137 | // load next block |
---|
| 138 | // console.log("loading next block (" + (currBlock + 1) + ")"); |
---|
| 139 | loader.load(dataset, callback, new Date(nextBlockTime), currBlock + 1); |
---|
| 140 | } |
---|
| 141 | // are we close enough to load the previous block, and is it loaded? |
---|
| 142 | if (prevBlockTime > band.getMinDate().getTime() && |
---|
| 143 | (!dataMinDate || prevBlockTime > dataMinDate.getTime()) && |
---|
| 144 | !loaded[currBlock - 1]) { |
---|
| 145 | // load previous block |
---|
| 146 | // console.log("loading prev block (" + (currBlock - 1) + ")"); |
---|
| 147 | loader.load(dataset, callback, new Date(prevBlockTime), currBlock - 1); |
---|
| 148 | } |
---|
| 149 | }); |
---|
| 150 | // kill this function so that listener is only added once |
---|
| 151 | addListener = false; |
---|
| 152 | }; |
---|
| 153 | |
---|
| 154 | // override load function |
---|
| 155 | loader.load = function(dataset, callback, start, currBlock) { |
---|
| 156 | // set start date, defaulting to zero date |
---|
| 157 | start = cleanDate(start) || zeroDate; |
---|
| 158 | // set current block, defaulting to 0 |
---|
| 159 | currBlock = currBlock || 0; |
---|
| 160 | // set end by interval |
---|
| 161 | var end = new Date(start.getTime() + interval); |
---|
| 162 | |
---|
| 163 | // set current block as loaded |
---|
| 164 | // XXX: Failed loads will give a false positive here... |
---|
| 165 | // but I'm not sure how else to avoid multiple loads :( |
---|
| 166 | loaded[currBlock] = true; |
---|
| 167 | |
---|
| 168 | // put dates into URL |
---|
| 169 | loader.url = formatUrl(baseUrl, start, end); |
---|
| 170 | // console.log(loader.url); |
---|
| 171 | |
---|
| 172 | // load data |
---|
| 173 | baseLoadFunction.call(loader, dataset, function() { |
---|
| 174 | // add onscroll listener if not yet done |
---|
| 175 | if (addListener) { |
---|
| 176 | addListener(dataset); |
---|
| 177 | } |
---|
| 178 | // run callback |
---|
| 179 | callback(); |
---|
| 180 | }); |
---|
| 181 | }; |
---|
| 182 | |
---|
| 183 | return loader; |
---|
| 184 | }; |
---|