198 lines
7.9 KiB
JavaScript
198 lines
7.9 KiB
JavaScript
|
/*
|
||
|
* Timemap.js Copyright 2010 Nick Rabinowitz.
|
||
|
* Licensed under the MIT License (see LICENSE.txt)
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @fileOverview
|
||
|
* Progressive loader
|
||
|
*
|
||
|
* @author Nick Rabinowitz (www.nickrabinowitz.com)
|
||
|
*/
|
||
|
|
||
|
// for JSLint
|
||
|
/*global TimeMap */
|
||
|
|
||
|
/**
|
||
|
* @class
|
||
|
* Progressive loader class - basically a wrapper for another remote loader that can
|
||
|
* load data progressively by date range, depending on timeline position.
|
||
|
*
|
||
|
* <p>The progressive loader can take either another loader or parameters for
|
||
|
* another loader. It expects a loader with a "url" attribute including placeholder
|
||
|
* strings [start] and [end] for the start and end dates to retrieve. The assumption
|
||
|
* is that the data service can take start and end parameters and return the data for
|
||
|
* that date range.</p>
|
||
|
*
|
||
|
* @example
|
||
|
TimeMap.init({
|
||
|
datasets: [
|
||
|
{
|
||
|
title: "Progressive JSONP Dataset",
|
||
|
type: "progressive",
|
||
|
options: {
|
||
|
type: "jsonp",
|
||
|
url: "http://www.test.com/getsomejson.php?start=[start]&end=[end]callback="
|
||
|
}
|
||
|
}
|
||
|
],
|
||
|
// etc...
|
||
|
});
|
||
|
*
|
||
|
* @example
|
||
|
TimeMap.init({
|
||
|
datasets: [
|
||
|
{
|
||
|
title: "Progressive KML Dataset",
|
||
|
type: "progressive",
|
||
|
options: {
|
||
|
loader: new TimeMap.loaders.kml({
|
||
|
url: "/mydata.kml?start=[start]&end=[end]"
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
],
|
||
|
// etc...
|
||
|
});
|
||
|
* @see <a href="../../examples/progressive.html">Progressive Loader Example</a>
|
||
|
*
|
||
|
* @constructor
|
||
|
* @param {Object} options All options for the loader
|
||
|
* @param {TimeMap.loaders.remote} [options.loader] Instantiated loader class (overrides "type")
|
||
|
* @param {String} [options.type] Name of loader class to use
|
||
|
* @param {String|Date} options.start Start of initial date range, as date or string
|
||
|
* @param {Number} options.interval Size in milliseconds of date ranges to load at a time
|
||
|
* @param {String|Date} [options.dataMinDate] Minimum date available in data (optional, will avoid
|
||
|
* unnecessary service requests if supplied)
|
||
|
* @param {String|Date} [options.dataMaxDate] Maximum date available in data (optional, will avoid
|
||
|
* unnecessary service requests if supplied)
|
||
|
* @param {Function} [options.formatUrl] Function taking (urlTemplate, start, end) and returning
|
||
|
* a URL formatted as needed by the service
|
||
|
* @param {Function} [options.formatDate={@link TimeMap.util.formatDate}]
|
||
|
* Function to turn a date into a string formatted
|
||
|
* as needed by the service
|
||
|
* @param {mixed} [options[...]] Other options needed by the "type" loader
|
||
|
*/
|
||
|
TimeMap.loaders.progressive = function(options) {
|
||
|
// get loader
|
||
|
var loader = options.loader,
|
||
|
type = options.type;
|
||
|
if (!loader) {
|
||
|
// get loader class
|
||
|
var loaderClass = (typeof(type) == 'string') ? TimeMap.loaders[type] : type;
|
||
|
loader = new loaderClass(options);
|
||
|
}
|
||
|
|
||
|
// quick string/date check
|
||
|
function cleanDate(d) {
|
||
|
if (typeof(d) == "string") {
|
||
|
d = TimeMapDataset.hybridParser(d);
|
||
|
}
|
||
|
return d;
|
||
|
}
|
||
|
|
||
|
// save loader attributes
|
||
|
var baseUrl = loader.url,
|
||
|
baseLoadFunction = loader.load,
|
||
|
interval = options.interval,
|
||
|
formatDate = options.formatDate || TimeMap.util.formatDate,
|
||
|
formatUrl = options.formatUrl,
|
||
|
zeroDate = cleanDate(options.start),
|
||
|
dataMinDate = cleanDate(options.dataMinDate),
|
||
|
dataMaxDate = cleanDate(options.dataMaxDate),
|
||
|
loaded = {};
|
||
|
|
||
|
if (!formatUrl) {
|
||
|
formatUrl = function(url, start, end) {
|
||
|
return url
|
||
|
.replace('[start]', formatDate(start))
|
||
|
.replace('[end]', formatDate(end));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We don't start with a TimeMap reference, so we need
|
||
|
// to stick the listener in on the first load() call
|
||
|
var addListener = function(dataset) {
|
||
|
var band = dataset.timemap.timeline.getBand(0);
|
||
|
// add listener
|
||
|
band.addOnScrollListener(function() {
|
||
|
// determine relevant blocks
|
||
|
var now = band.getCenterVisibleDate(),
|
||
|
currBlock = Math.floor((now.getTime() - zeroDate.getTime()) / interval),
|
||
|
currBlockTime = zeroDate.getTime() + (interval * currBlock)
|
||
|
nextBlockTime = currBlockTime + interval,
|
||
|
prevBlockTime = currBlockTime - interval,
|
||
|
// no callback necessary?
|
||
|
callback = function() {
|
||
|
dataset.timemap.timeline.layout();
|
||
|
};
|
||
|
|
||
|
// is the current block loaded?
|
||
|
if ((!dataMaxDate || currBlockTime < dataMaxDate.getTime()) &&
|
||
|
(!dataMinDate || currBlockTime > dataMinDate.getTime()) &&
|
||
|
!loaded[currBlock]) {
|
||
|
// load it
|
||
|
// console.log("loading current block (" + currBlock + ")");
|
||
|
loader.load(dataset, callback, new Date(currBlockTime), currBlock);
|
||
|
}
|
||
|
// are we close enough to load the next block, and is it loaded?
|
||
|
if (nextBlockTime < band.getMaxDate().getTime() &&
|
||
|
(!dataMaxDate || nextBlockTime < dataMaxDate.getTime()) &&
|
||
|
!loaded[currBlock + 1]) {
|
||
|
// load next block
|
||
|
// console.log("loading next block (" + (currBlock + 1) + ")");
|
||
|
loader.load(dataset, callback, new Date(nextBlockTime), currBlock + 1);
|
||
|
}
|
||
|
// are we close enough to load the previous block, and is it loaded?
|
||
|
if (prevBlockTime > band.getMinDate().getTime() &&
|
||
|
(!dataMinDate || prevBlockTime > dataMinDate.getTime()) &&
|
||
|
!loaded[currBlock - 1]) {
|
||
|
// load previous block
|
||
|
// console.log("loading prev block (" + (currBlock - 1) + ")");
|
||
|
loader.load(dataset, callback, new Date(prevBlockTime), currBlock - 1);
|
||
|
}
|
||
|
});
|
||
|
// kill this function so that listener is only added once
|
||
|
addListener = false;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Load data based on current time
|
||
|
* @name TimeMap.loaders.progressive#load
|
||
|
* @function
|
||
|
* @param {TimeMapDataset} dataset Dataset to load data into
|
||
|
* @param {Function} callback Callback to execute when data is loaded
|
||
|
* @param {Date} start Start date to load data from
|
||
|
* @param {Number} currBlock Index of the current time block
|
||
|
*/
|
||
|
loader.load = function(dataset, callback, start, currBlock) {
|
||
|
// set start date, defaulting to zero date
|
||
|
start = cleanDate(start) || zeroDate;
|
||
|
// set current block, defaulting to 0
|
||
|
currBlock = currBlock || 0;
|
||
|
// set end by interval
|
||
|
var end = new Date(start.getTime() + interval);
|
||
|
|
||
|
// set current block as loaded
|
||
|
// XXX: Failed loads will give a false positive here...
|
||
|
// but I'm not sure how else to avoid multiple loads :(
|
||
|
loaded[currBlock] = true;
|
||
|
|
||
|
// put dates into URL
|
||
|
loader.url = formatUrl(baseUrl, start, end);
|
||
|
// console.log(loader.url);
|
||
|
|
||
|
// load data
|
||
|
baseLoadFunction.call(loader, dataset, function() {
|
||
|
// add onscroll listener if not yet done
|
||
|
if (addListener) {
|
||
|
addListener(dataset);
|
||
|
}
|
||
|
// run callback
|
||
|
callback();
|
||
|
});
|
||
|
};
|
||
|
|
||
|
return loader;
|
||
|
};
|