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 | }; |
---|