s2maps-gpu
Version:
S2 Maps GPU - An open source, high-performance, and GPU-accelerated map engine for rendering large-scale, interactive maps.
216 lines (215 loc) • 8.11 kB
JavaScript
import Cache from './cache.js';
/**
* # Time Cache
*
* Stores and manages time source raster data.
*/
export default class TimeCache extends Cache {
camera;
sources = {}; // [sourceName]: { interval: number }
lastFrame;
webworker;
timeSeries;
/**
* @param camera - parent camera
* @param webworker - true if running in a webworker
* @param timeSeries - user defined time series style object
*/
constructor(camera, webworker, timeSeries) {
super();
this.camera = camera;
this.webworker = webworker;
this.#buildTimeSeries(timeSeries);
}
/**
* Add a time source
* @param sourceName - the name of the source
* @param interval - the interval of the source relative to the starting point of the source
*/
addSource(sourceName, interval) {
const step = interval / 4;
this.sources[sourceName] = { interval, step };
// grab timeseries variables
const { startTime, autoPlay, state } = this.timeSeries;
// source data is always added at the beginning of the animation. The first source added triggers the animation
if (autoPlay && state === 'stop')
this.timeSeries.state = 'play';
// build a request for the first frame
const sourceTime = startTime - (startTime % interval);
this.#requestTiles(sourceTime, sourceName, sourceName);
}
/**
* Add source data to the cache and update the time series
* @param id - the id of the tile
* @param time - the time of the source
* @param sourceName - the name of the source
* @param source - the source to add the data to
*/
addSourceData(id, time, sourceName, source) {
this.set(`${id}#${time}#${sourceName}`, source);
}
/**
* Get source data
* @param id - the id of the tile
* @param sourceName - the name of the source
* @returns the source texture information and data
*/
getTextures(id, sourceName) {
const shortName = sourceName.split(':')[0];
const { cursor, endTime } = this.timeSeries;
const timeSource = this.sources[shortName];
if (timeSource === undefined)
return {};
const { step, interval } = timeSource;
// build keys
const curTime = cursor - (cursor % interval);
const nextTime = curTime + interval;
// find source data
const curSource = this.get(`${id}#${curTime}#${sourceName}`);
const nextSource = this.get(`${id}#${nextTime}#${sourceName}`);
// if nextSource is not found, request it
if (curSource === undefined)
this.#requestTiles(curTime, shortName, sourceName, id);
if (nextSource === undefined && nextTime <= endTime)
this.#requestTiles(nextTime, shortName, sourceName, id);
// grab data (if raster its a texture, if vector its a pointer to buffer)
// if no source data, return
const { texture } = curSource ?? {};
if (texture === undefined)
return {};
const { texture: textureNext } = nextSource ?? {};
// build time. Range of 0->4 : 0->1 red; 1->2 green; 2->3 blue; 3->4 alpha
// time increments of source interval
const time = (cursor - curTime) / step;
return { time, texture, textureNext };
}
/**
* update layer positions.
* play state: increment cursor by speed * deltaTime. If cursor >= endTime, set cursor to pause state or startTime.
* pause state: increment cursor by deltaTime. If cursor > pauseDuration, set state to play
* @param now - current time
* @param render - render function to call after animation state is updated
*/
animate(now, render) {
const { timeSeries } = this;
if (timeSeries.state === 'stop')
return;
// if no last frame, set last frame to begin animation and return
if (this.lastFrame === undefined)
this.lastFrame = now;
// find delta
const delta = (now - this.lastFrame) / 1000; // convert to seconds
this.lastFrame = now;
// grab time series variables
const { startTime, endTime, speed, loop } = timeSeries;
// if cursor is exactly endTime, if pauseDuration is set, set to pause, otherwise set to startTime if loop
if (timeSeries.cursor === endTime) {
if (loop)
timeSeries.cursor = startTime;
else
timeSeries.state = 'stop';
}
else {
// increment cursor
timeSeries.cursor += delta * speed;
// if cursor is past endTime, set to endTime to finish animation
if (timeSeries.cursor > endTime)
timeSeries.cursor = endTime;
}
// send off a render
render();
}
/**
* rather than animate, the user can specify a time, and this will update to current time
* @param time - the time to set
*/
setTime(time) {
const { timeSeries } = this;
timeSeries.cursor = time;
}
/**
* Request source data
* @param time - the time of the source
* @param shortName - the short name of the source
* @param sourceName - the name of the source
* @param id - the id of the tile
*/
#requestTiles(time, shortName, sourceName, id) {
const { webworker, camera } = this;
let tiles = [];
if (id !== undefined) {
const tile = camera.getTile(id);
if (tile !== undefined)
tiles = [tile];
else
tiles = [];
}
else {
tiles = camera.getTiles();
}
const tileRequests = [];
// build tile requests
for (const tile of tiles) {
// build time id
const timeID = `${tile.id}#${time}#${sourceName}`;
// if source data is already in cache, return
if (this.has(timeID))
continue;
// place a temporary source in the cache
// this will be replaced by the source data when it arrives
this.set(timeID, {});
const { id, face, i, j, zoom, bbox, type, division } = tile;
tileRequests.push({ id, face, i, j, zoom, bbox, type, division, time });
}
// get list of current tiles in view
if (tileRequests.length > 0) {
if (webworker) {
postMessage({
mapID: camera.id,
type: 'timerequest',
tiles: tileRequests,
sourceNames: [shortName],
});
}
else {
window.S2WorkerPool.timeRequest(camera.id, tileRequests, [shortName]);
}
}
}
/**
* build the time series given user defined time series style guide
* @param timeSeries - user defined time series style
*/
#buildTimeSeries(timeSeries) {
const { startDate, endDate, speed, pauseDuration, loop, autoPlay } = timeSeries;
// setup date to beginning of current day
const date = new Date();
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
// date.set
const dateNum = date.getTime();
// tell the map about the time series
const startTime = parseDate(startDate ?? dateNum);
this.timeSeries = {
startTime,
endTime: parseDate(endDate ?? dateNum + 162000), // 45 hour sequence (basically 2 tiles per face)
speed: speed ?? 1, // 1 hour per second (0 -> no animation)
pauseDuration: pauseDuration ?? 0,
loop: loop ?? false,
autoPlay: autoPlay ?? false,
state: 'stop',
cursor: startTime,
};
}
}
/**
* convert a date to a unix timestamp
* @param d - date string or number
* @returns unix timestamp
*/
function parseDate(d) {
const date = new Date(d);
return date.getTime() / 1000;
}