terriajs
Version:
Geospatial data visualization platform.
419 lines • 18.2 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { action, computed, runInAction, makeObservable, override } from "mobx";
import binarySearch from "terriajs-cesium/Source/Core/binarySearch";
import JulianDate from "terriajs-cesium/Source/Core/JulianDate";
import getChartColorForId from "../Charts/getChartColorForId";
import filterOutUndefined from "../Core/filterOutUndefined";
import isDefined from "../Core/isDefined";
import TerriaError from "../Core/TerriaError";
import ChartableMixin, { calculateDomain } from "../ModelMixins/ChartableMixin";
import CommonStrata from "../Models/Definition/CommonStrata";
import { DATE_SECONDS_PRECISION } from "./TimeVarying";
function DiscretelyTimeVaryingMixin(Base) {
class DiscretelyTimeVaryingMixin extends ChartableMixin(Base) {
constructor(...args) {
super(...args);
makeObservable(this);
}
get hasDiscreteTimes() {
return true;
}
get currentTime() {
const time = super.currentTime;
if (time === undefined || time === null) {
if (this.initialTimeSource === "now") {
return JulianDate.toIso8601(JulianDate.now(), DATE_SECONDS_PRECISION);
}
else if (this.initialTimeSource === "start") {
return this.startTime;
}
else if (this.initialTimeSource === "stop") {
return this.stopTime;
}
else if (this.initialTimeSource === "none") {
return undefined;
}
else {
throw new TerriaError({
sender: this,
title: "Invalid initialTime value",
message: "The `initialTime` property has an invalid value: `" +
this.initialTimeSource +
"`."
});
}
}
return time;
}
get currentTimeAsJulianDate() {
return toJulianDate(this.currentTime);
}
get startTimeAsJulianDate() {
return toJulianDate(this.startTime);
}
get stopTimeAsJulianDate() {
return toJulianDate(this.stopTime);
}
get objectifiedDates() {
if (!isDefined(this.discreteTimesAsSortedJulianDates)) {
return { index: [], dates: [] };
}
const jsDates = this.discreteTimesAsSortedJulianDates.map((julianDate) => JulianDate.toDate(julianDate.time));
return objectifyDates(jsDates);
}
get discreteTimesAsSortedJulianDates() {
const discreteTimes = this.discreteTimes;
if (discreteTimes === undefined) {
return undefined;
}
const asJulian = [];
for (let i = 0; i < discreteTimes.length; i++) {
const dt = discreteTimes[i];
try {
if (dt.time !== undefined) {
const time = JulianDate.fromIso8601(dt.time);
asJulian.push({
time,
tag: dt.tag !== undefined ? dt.tag : dt.time
});
}
}
catch { }
}
asJulian.sort((a, b) => JulianDate.compare(a.time, b.time));
return asJulian;
}
getDiscreteTimeIndex(time) {
const discreteTimes = this.discreteTimesAsSortedJulianDates;
if (discreteTimes === undefined || discreteTimes.length === 0) {
return undefined;
}
// Where does `time` fit in our sequence of discrete times?
const exactIndex = binarySearch(discreteTimes, time, (candidate, currentTime) => JulianDate.compare(candidate.time, currentTime));
// We have this exact time in our discrete times
if (exactIndex >= 0) {
return exactIndex;
}
// This is where `time` could be inserted into the discrete times list so that they're all in sorted order
const nextIndex = ~exactIndex;
if (nextIndex === 0 || this.fromContinuous === "next") {
// Before the first, or we want the next time no matter which is closest
return nextIndex;
}
else if (nextIndex === discreteTimes.length ||
this.fromContinuous === "previous") {
// After the last, or we want the previous time no matter which is closest
return nextIndex - 1;
}
else {
// Get the closest discrete time
const previousTime = discreteTimes[nextIndex - 1].time;
const nextTime = discreteTimes[nextIndex].time;
const timeFromPrevious = JulianDate.secondsDifference(time, previousTime);
const timeToNext = JulianDate.secondsDifference(nextTime, time);
if (timeToNext > timeFromPrevious) {
return nextIndex - 1;
}
else {
return nextIndex;
}
}
}
get currentDiscreteTimeIndex() {
return (this.currentTimeAsJulianDate &&
this.getDiscreteTimeIndex(this.currentTimeAsJulianDate));
}
get nextDiscreteTimeIndex() {
const index = this.currentDiscreteTimeIndex;
if (index === undefined ||
index === this.discreteTimesAsSortedJulianDates.length - 1) {
return undefined;
}
return index + 1;
}
get previousDiscreteTimeIndex() {
const index = this.currentDiscreteTimeIndex;
if (index === undefined || index === 0) {
return undefined;
}
return index - 1;
}
get currentDiscreteJulianDate() {
const index = this.currentDiscreteTimeIndex;
return index === undefined
? undefined
: this.discreteTimesAsSortedJulianDates[index].time;
}
get nextDiscreteJulianDate() {
const index = this.nextDiscreteTimeIndex;
return index === undefined
? undefined
: this.discreteTimesAsSortedJulianDates[index].time;
}
get currentDiscreteTimeTag() {
const index = this.currentDiscreteTimeIndex;
return index === undefined
? undefined
: this.discreteTimesAsSortedJulianDates[index].tag;
}
get previousDiscreteTimeTag() {
const index = this.previousDiscreteTimeIndex;
return index === undefined
? undefined
: this.discreteTimesAsSortedJulianDates[index].tag;
}
get nextDiscreteTimeTag() {
const index = this.nextDiscreteTimeIndex;
return index === undefined
? undefined
: this.discreteTimesAsSortedJulianDates[index].tag;
}
get isPreviousDiscreteTimeAvailable() {
return this.previousDiscreteTimeIndex !== undefined;
}
get isNextDiscreteTimeAvailable() {
return this.nextDiscreteTimeIndex !== undefined;
}
get startTime() {
const time = super.startTime;
if (time === undefined &&
this.discreteTimesAsSortedJulianDates &&
this.discreteTimesAsSortedJulianDates.length > 0) {
return JulianDate.toIso8601(this.discreteTimesAsSortedJulianDates[0].time, DATE_SECONDS_PRECISION);
}
return time;
}
get stopTime() {
const time = super.stopTime;
if (time === undefined &&
this.discreteTimesAsSortedJulianDates &&
this.discreteTimesAsSortedJulianDates.length > 0) {
return JulianDate.toIso8601(this.discreteTimesAsSortedJulianDates[this.discreteTimesAsSortedJulianDates.length - 1].time, DATE_SECONDS_PRECISION);
}
return time;
}
/**
* Try to calculate a multiplier which results in a new time step every {this.multiplierDefaultDeltaStep} seconds. For example, if {this.multiplierDefaultDeltaStep = 5} it would set the `multiplier` so that a new time step (of this dataset) would appear every five seconds (on average) if the timeline is playing.
*/
get multiplier() {
if (super.multiplier)
return super.multiplier;
if (!isDefined(this.startTimeAsJulianDate) ||
!isDefined(this.stopTimeAsJulianDate) ||
!isDefined(this.multiplierDefaultDeltaStep) ||
!isDefined(this.discreteTimesAsSortedJulianDates))
return;
const dSeconds = (this.stopTimeAsJulianDate.dayNumber -
this.startTimeAsJulianDate.dayNumber) *
24 *
60 *
60 +
this.stopTimeAsJulianDate.secondsOfDay -
this.startTimeAsJulianDate.secondsOfDay;
const meanDSeconds = dSeconds / this.discreteTimesAsSortedJulianDates.length;
return meanDSeconds / this.multiplierDefaultDeltaStep;
}
moveToPreviousDiscreteTime(stratumId) {
const index = this.previousDiscreteTimeIndex;
if (index === undefined) {
return;
}
this.setTrait(stratumId, "currentTime", JulianDate.toIso8601(this.discreteTimesAsSortedJulianDates[index].time, DATE_SECONDS_PRECISION));
}
moveToNextDiscreteTime(stratumId) {
const index = this.nextDiscreteTimeIndex;
if (index === undefined) {
return;
}
this.setTrait(stratumId, "currentTime", JulianDate.toIso8601(this.discreteTimesAsSortedJulianDates[index].time, DATE_SECONDS_PRECISION));
}
get momentChart() {
if (!this.showInChartPanel || !this.discreteTimesAsSortedJulianDates)
return;
const points = this.discreteTimesAsSortedJulianDates.map((dt) => ({
x: JulianDate.toDate(dt.time),
y: 0.5,
isSelected: this.currentDiscreteJulianDate &&
this.currentDiscreteJulianDate.equals(dt.time)
}));
const colorId = `color-${this.name}`;
return {
item: this,
id: this.name || "",
name: this.name || "",
categoryName: this.name,
key: `key${this.uniqueId}-${this.name}`,
type: this.chartType || "momentLines",
glyphStyle: this.chartGlyphStyle,
xAxis: { name: "Time", scale: "time" },
points,
domain: { ...calculateDomain(points), y: [0, 1] },
showInChartPanel: this.show && this.showInChartPanel,
isSelectedInWorkbench: this.showInChartPanel,
updateIsSelectedInWorkbench: (isSelected) => {
runInAction(() => {
this.setTrait(CommonStrata.user, "showInChartPanel", isSelected);
});
},
getColor: () => {
return this.chartColor
? this.chartColor
: getChartColorForId(colorId);
},
onClick: (point) => {
runInAction(() => {
this.setTrait(CommonStrata.user, "currentTime", point.x.toISOString());
});
}
};
}
get chartItems() {
return filterOutUndefined([this.momentChart]);
}
}
__decorate([
override
], DiscretelyTimeVaryingMixin.prototype, "currentTime", null);
__decorate([
computed({ equals: JulianDate.equals })
], DiscretelyTimeVaryingMixin.prototype, "currentTimeAsJulianDate", null);
__decorate([
computed({ equals: JulianDate.equals })
], DiscretelyTimeVaryingMixin.prototype, "startTimeAsJulianDate", null);
__decorate([
computed({ equals: JulianDate.equals })
], DiscretelyTimeVaryingMixin.prototype, "stopTimeAsJulianDate", null);
__decorate([
computed
], DiscretelyTimeVaryingMixin.prototype, "objectifiedDates", null);
__decorate([
computed
], DiscretelyTimeVaryingMixin.prototype, "discreteTimesAsSortedJulianDates", null);
__decorate([
computed
], DiscretelyTimeVaryingMixin.prototype, "currentDiscreteTimeIndex", null);
__decorate([
computed
], DiscretelyTimeVaryingMixin.prototype, "nextDiscreteTimeIndex", null);
__decorate([
computed
], DiscretelyTimeVaryingMixin.prototype, "previousDiscreteTimeIndex", null);
__decorate([
computed({ equals: JulianDate.equals })
], DiscretelyTimeVaryingMixin.prototype, "currentDiscreteJulianDate", null);
__decorate([
computed({ equals: JulianDate.equals })
], DiscretelyTimeVaryingMixin.prototype, "nextDiscreteJulianDate", null);
__decorate([
computed
], DiscretelyTimeVaryingMixin.prototype, "currentDiscreteTimeTag", null);
__decorate([
computed
], DiscretelyTimeVaryingMixin.prototype, "previousDiscreteTimeTag", null);
__decorate([
computed
], DiscretelyTimeVaryingMixin.prototype, "nextDiscreteTimeTag", null);
__decorate([
computed
], DiscretelyTimeVaryingMixin.prototype, "isPreviousDiscreteTimeAvailable", null);
__decorate([
computed
], DiscretelyTimeVaryingMixin.prototype, "isNextDiscreteTimeAvailable", null);
__decorate([
override
], DiscretelyTimeVaryingMixin.prototype, "startTime", null);
__decorate([
override
], DiscretelyTimeVaryingMixin.prototype, "stopTime", null);
__decorate([
override
], DiscretelyTimeVaryingMixin.prototype, "multiplier", null);
__decorate([
action
], DiscretelyTimeVaryingMixin.prototype, "moveToPreviousDiscreteTime", null);
__decorate([
action
], DiscretelyTimeVaryingMixin.prototype, "moveToNextDiscreteTime", null);
__decorate([
computed
], DiscretelyTimeVaryingMixin.prototype, "momentChart", null);
__decorate([
computed
], DiscretelyTimeVaryingMixin.prototype, "chartItems", null);
return DiscretelyTimeVaryingMixin;
}
(function (DiscretelyTimeVaryingMixin) {
function isMixedInto(model) {
return model && model.hasDiscreteTimes;
}
DiscretelyTimeVaryingMixin.isMixedInto = isMixedInto;
})(DiscretelyTimeVaryingMixin || (DiscretelyTimeVaryingMixin = {}));
export default DiscretelyTimeVaryingMixin;
function toJulianDate(time) {
if (time === undefined || time === null) {
return undefined;
}
// JS's data parser produces some bizarre dates from bad strings without complaint, so we need to do some basic validation
if (time.includes("NaN")) {
return undefined;
}
const julianDate = JulianDate.fromIso8601(time);
// Don't return an invalid JulianDate
if (isNaN(julianDate.secondsOfDay) || isNaN(julianDate.dayNumber))
return undefined;
return julianDate;
}
/**
* Process an array of dates into layered objects of years, months and days.
* @param {Date[]} An array of dates.
* @return {Object} Returns an object whose keys are years, whose values are objects whose keys are months (0=Jan),
* whose values are objects whose keys are days, whose values are arrays of all the datetimes on that day.
*/
export function objectifyDates(dates) {
const result = { index: [], dates };
for (let i = 0; i < dates.length; i++) {
const date = dates[i];
const year = date.getFullYear();
const century = Math.floor(year / 100);
const month = date.getMonth();
const day = date.getDate();
const hour = date.getHours();
// ObjectifiedDates
if (!result[century]) {
result[century] = { index: [], dates: [] };
result.index.push(century);
}
result[century].dates.push(date);
// ObjectifiedYears
if (!result[century][year]) {
result[century][year] = { index: [], dates: [] };
result[century].index.push(year);
}
result[century][year].dates.push(date);
// ObjectifiedMonths
if (!result[century][year][month]) {
result[century][year][month] = { index: [], dates: [] };
result[century][year].index.push(month);
}
result[century][year][month].dates.push(date);
// ObjectifiedDays
if (!result[century][year][month][day]) {
result[century][year][month][day] = { index: [], dates: [] };
result[century][year][month].index.push(day);
}
result[century][year][month][day].dates.push(date);
// ObjectifiedHours
if (!result[century][year][month][day][hour]) {
result[century][year][month][day][hour] = [];
result[century][year][month][day].index.push(hour);
}
result[century][year][month][day][hour].push(date);
}
return result;
}
//# sourceMappingURL=DiscretelyTimeVaryingMixin.js.map