/**
* Manages Arrow calculation
*
* @alias ArrowProvider
* @constructor
*
* @param {WindDataProvider} windDataProvider The wind data file.
* @param {Object} [options] Object with the following properties:
* @param {int} [options.precalcTimesteps] number of timesteps for precalculation
* @param {float} [options.dseed] d_seed distance
* @param {float} [options.dsep] d_sep distance
* @param {Cesium.Rectangle} [options.rect] rect to calculate arrows for
* @param {float} [options.arrowWidth] width of the arrows
* @param {int} [options.seedSamplePoints] Number of tested Sampling points per timestep
* @param {number} [options.scale] Scale for these arrows
* @param {boolean} [options.random] Distribute arrows randomly, default=false
*/
function ArrowProvider(windDataProvider, options) {
console.log("Creating ArrowProvider...");
options = Cesium.defaultValue(options, Cesium.defaultValue.EMPTY_OBJECT);
this._ready = false;
this.windDataProvider = windDataProvider;
this._scale = Cesium.defaultValue(options.scale,1);
this.trajectoryHelper = new TrajectoryHelper(windDataProvider, 20.0/this._scale);
this._arrows = []; //List of timesteps with list of arrows for each entry
this.rect = Cesium.defaultValue(options.rect,Cesium.Rectangle.MAX_VALUE);
this.dseed = Cesium.defaultValue(options.dseed,0);
this.dsep = Cesium.defaultValue(options.dsep,0);
this.precalcTimesteps = Cesium.defaultValue(options.precalcTimesteps,10);
this.arrowWidth = Cesium.defaultValue(options.arrowWidth,1);
this._currentFirstTime = 0;
this._seedSamplePoints = Cesium.defaultValue(options.seedSamplePoints,200);
this._randomDistribution = Cesium.defaultValue(options.random,false);
this._readyPromise = Cesium.when.defer();
this.initArrows();
}
/**
* Returns a list of arrows on the given timestep
* @param {int} time timestep to return arrows for
* @return {list} list of {@link Arrow} instances
*/
ArrowProvider.prototype.getArrows = function(time){
return this._arrows[time];
}
/**
* Appends a timestep to this arrowprovider
*/
ArrowProvider.prototype.addTimestep = function(){
this._currentFirstTime++;
var newTimestep = this.precalcTimesteps+this._currentFirstTime-1;
console.log("Timestep: " + newTimestep);
// The forward step
this.PropagateArrowsOneStep(1,newTimestep);
this.completeTimeStepWithArrows(newTimestep);
// Propagate the new frame the number of precalcTimesteps back...
for (t=newTimestep; t>=this._currentFirstTime; t--){
dt = 1; //Backward step
this.PropagateArrowsOneStep(dt,t);
}
}
/**
* Initizialize arrows for the first n timesteps
* @private
*/
ArrowProvider.prototype.initArrows = function(){
console.log("...init arrows");
this.completeTimeStepWithArrows(0);
console.log("...forward pass");
for (t=1; t<this.precalcTimesteps; t++){
dt = 1; //Forward step
this.PropagateArrowsOneStep(dt,t);
this.completeTimeStepWithArrows(t);
}
console.log("...backward pass");
for (t=this.precalcTimesteps-2; t>=0; t--){
dt = -1; //Backward step
this.PropagateArrowsOneStep(dt,t);
}
console.log("...done");
this._ready = true;
this._readyPromise.resolve(true);
}
/**
* Returns a list of points, that are uniformly distributed for timestepseeding
* @private
* @return {list} list of {@link Point} instances
*/
ArrowProvider.prototype.getSamplePoints = function(){
//Uniform random distribution of sample points (maybe do something smart here :))
var points = []
if (this._randomDistribution){
for(i = 0; i < this._seedSamplePoints; i++){
points[i] = new Point(
Math.random()*this.rect.width+this.rect.west,
(Math.random()*this.rect.height+this.rect.south)*-1
);
// Now we have to convert it to degree:
points[i].scale(180.0/Math.PI);
}
} else {
sqrtNum = Math.sqrt(this._seedSamplePoints);
dx = this.rect.height/sqrtNum;
dy = this.rect.height/sqrtNum;
i = 0;
for (x = this.rect.west; x < this.rect.east; x+=dx){
for (y = this.rect.south; y < this.rect.north; y+=dy){
points[i] = new Point(
x,
y*-1
);
// Now we have to convert it to degree:
points[i].scale(180.0/Math.PI);
i++;
}
}
}
return points;
}
/**
* Adds all possible arrows for this timestep
* @private
* @param {int} time timestep for calculation
*/
ArrowProvider.prototype.completeTimeStepWithArrows = function(time){
var samplePoints = this.getSamplePoints();
if (this._arrows[time] === undefined) this._arrows[time] = [];
var that = this; // Workaround for function in forEach, that does not know this
samplePoints.forEach(function (point){
var newArrow = new Arrow(that.trajectoryHelper);
newArrow.position[time] = point;
newArrow.width = that.arrowWidth;
newArrow.timeBorn = time;
newArrow.timeDeath = time;
newArrow.calcDataValues(time);
if (newArrow.distanceToMultiple(that._arrows[time], time, that.dseed)){
that._arrows[time].push(newArrow);
}
});
}
/**
* takes all arrows from time-dt and propagates them to time
* @private
* @param {int} dt direction of propagation
* @param {int} time timestep for calculation
*/
ArrowProvider.prototype.PropagateArrowsOneStep = function(dt, time){
var prevTime = time - dt;
var candidates = []; //Possible arrows to be used here...
this._arrows[prevTime].forEach(function(arrow){
if (arrow.isAlive(prevTime) && !arrow.isAlive(time)){
// We want you for Arrow Army!
candidates.push(arrow);
}
});
candidates.sort(function(a, b){
return b.timeData[prevTime].length-a.timeData[prevTime].length; // sort biggest arrows first
});
var that = this; //Workaround
if (this._arrows[time] === undefined) this._arrows[time] = [];
candidates.forEach(function(arrow){
arrow.propagateToTime(prevTime, time);
if (arrow.distanceToMultiple(that._arrows[time], time, that.dsep)){
that._arrows[time].push(arrow);
arrow.timeBorn = Math.min(arrow.timeBorn, time);
arrow.timeDeath = Math.max(arrow.timeDeath, time);
}
});
}