import Check from "./Check.js"; import defaultValue from "./defaultValue.js"; import defined from "./defined.js"; import DeveloperError from "./DeveloperError.js"; import JulianDate from "./JulianDate.js"; /** * An interval defined by a start and a stop time; optionally including those times as part of the interval. * Arbitrary data can optionally be associated with each instance for used with {@link TimeIntervalCollection}. * * @alias TimeInterval * @constructor * * @param {object} [options] Object with the following properties: * @param {JulianDate} [options.start=new JulianDate()] The start time of the interval. * @param {JulianDate} [options.stop=new JulianDate()] The stop time of the interval. * @param {boolean} [options.isStartIncluded=true] true if options.start is included in the interval, false otherwise. * @param {boolean} [options.isStopIncluded=true] true if options.stop is included in the interval, false otherwise. * @param {object} [options.data] Arbitrary data associated with this interval. * * @example * // Create an instance that spans August 1st, 1980 and is associated * // with a Cartesian position. * const timeInterval = new Cesium.TimeInterval({ * start : Cesium.JulianDate.fromIso8601('1980-08-01T00:00:00Z'), * stop : Cesium.JulianDate.fromIso8601('1980-08-02T00:00:00Z'), * isStartIncluded : true, * isStopIncluded : false, * data : Cesium.Cartesian3.fromDegrees(39.921037, -75.170082) * }); * * @example * // Create two instances from ISO 8601 intervals with associated numeric data * // then compute their intersection, summing the data they contain. * const left = Cesium.TimeInterval.fromIso8601({ * iso8601 : '2000/2010', * data : 2 * }); * * const right = Cesium.TimeInterval.fromIso8601({ * iso8601 : '1995/2005', * data : 3 * }); * * //The result of the below intersection will be an interval equivalent to * //const intersection = Cesium.TimeInterval.fromIso8601({ * // iso8601 : '2000/2005', * // data : 5 * //}); * const intersection = new Cesium.TimeInterval(); * Cesium.TimeInterval.intersect(left, right, intersection, function(leftData, rightData) { * return leftData + rightData; * }); * * @example * // Check if an interval contains a specific time. * const dateToCheck = Cesium.JulianDate.fromIso8601('1982-09-08T11:30:00Z'); * const containsDate = Cesium.TimeInterval.contains(timeInterval, dateToCheck); */ function TimeInterval(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); /** * Gets or sets the start time of this interval. * @type {JulianDate} */ this.start = defined(options.start) ? JulianDate.clone(options.start) : new JulianDate(); /** * Gets or sets the stop time of this interval. * @type {JulianDate} */ this.stop = defined(options.stop) ? JulianDate.clone(options.stop) : new JulianDate(); /** * Gets or sets the data associated with this interval. * @type {*} */ this.data = options.data; /** * Gets or sets whether or not the start time is included in this interval. * @type {boolean} * @default true */ this.isStartIncluded = defaultValue(options.isStartIncluded, true); /** * Gets or sets whether or not the stop time is included in this interval. * @type {boolean} * @default true */ this.isStopIncluded = defaultValue(options.isStopIncluded, true); } Object.defineProperties(TimeInterval.prototype, { /** * Gets whether or not this interval is empty. * @memberof TimeInterval.prototype * @type {boolean} * @readonly */ isEmpty: { get: function () { const stopComparedToStart = JulianDate.compare(this.stop, this.start); return ( stopComparedToStart < 0 || (stopComparedToStart === 0 && (!this.isStartIncluded || !this.isStopIncluded)) ); }, }, }); const scratchInterval = { start: undefined, stop: undefined, isStartIncluded: undefined, isStopIncluded: undefined, data: undefined, }; /** * Creates a new instance from a {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601} interval. * * @throws DeveloperError if options.iso8601 does not match proper formatting. * * @param {object} options Object with the following properties: * @param {string} options.iso8601 An ISO 8601 interval. * @param {boolean} [options.isStartIncluded=true] true if options.start is included in the interval, false otherwise. * @param {boolean} [options.isStopIncluded=true] true if options.stop is included in the interval, false otherwise. * @param {object} [options.data] Arbitrary data associated with this interval. * @param {TimeInterval} [result] An existing instance to use for the result. * @returns {TimeInterval} The modified result parameter or a new instance if none was provided. */ TimeInterval.fromIso8601 = function (options, result) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("options", options); Check.typeOf.string("options.iso8601", options.iso8601); //>>includeEnd('debug'); const dates = options.iso8601.split("/"); if (dates.length !== 2) { throw new DeveloperError( "options.iso8601 is an invalid ISO 8601 interval." ); } const start = JulianDate.fromIso8601(dates[0]); const stop = JulianDate.fromIso8601(dates[1]); const isStartIncluded = defaultValue(options.isStartIncluded, true); const isStopIncluded = defaultValue(options.isStopIncluded, true); const data = options.data; if (!defined(result)) { scratchInterval.start = start; scratchInterval.stop = stop; scratchInterval.isStartIncluded = isStartIncluded; scratchInterval.isStopIncluded = isStopIncluded; scratchInterval.data = data; return new TimeInterval(scratchInterval); } result.start = start; result.stop = stop; result.isStartIncluded = isStartIncluded; result.isStopIncluded = isStopIncluded; result.data = data; return result; }; /** * Creates an ISO8601 representation of the provided interval. * * @param {TimeInterval} timeInterval The interval to be converted. * @param {number} [precision] The number of fractional digits used to represent the seconds component. By default, the most precise representation is used. * @returns {string} The ISO8601 representation of the provided interval. */ TimeInterval.toIso8601 = function (timeInterval, precision) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("timeInterval", timeInterval); //>>includeEnd('debug'); return `${JulianDate.toIso8601( timeInterval.start, precision )}/${JulianDate.toIso8601(timeInterval.stop, precision)}`; }; /** * Duplicates the provided instance. * * @param {TimeInterval} [timeInterval] The instance to clone. * @param {TimeInterval} [result] An existing instance to use for the result. * @returns {TimeInterval} The modified result parameter or a new instance if none was provided. */ TimeInterval.clone = function (timeInterval, result) { if (!defined(timeInterval)) { return undefined; } if (!defined(result)) { return new TimeInterval(timeInterval); } result.start = timeInterval.start; result.stop = timeInterval.stop; result.isStartIncluded = timeInterval.isStartIncluded; result.isStopIncluded = timeInterval.isStopIncluded; result.data = timeInterval.data; return result; }; /** * Compares two instances and returns true if they are equal, false otherwise. * * @param {TimeInterval} [left] The first instance. * @param {TimeInterval} [right] The second instance. * @param {TimeInterval.DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used. * @returns {boolean} true if the dates are equal; otherwise, false. */ TimeInterval.equals = function (left, right, dataComparer) { return ( left === right || (defined(left) && defined(right) && ((left.isEmpty && right.isEmpty) || (left.isStartIncluded === right.isStartIncluded && left.isStopIncluded === right.isStopIncluded && JulianDate.equals(left.start, right.start) && JulianDate.equals(left.stop, right.stop) && (left.data === right.data || (defined(dataComparer) && dataComparer(left.data, right.data)))))) ); }; /** * Compares two instances and returns true if they are within epsilon seconds of * each other. That is, in order for the dates to be considered equal (and for * this function to return true), the absolute value of the difference between them, in * seconds, must be less than epsilon. * * @param {TimeInterval} [left] The first instance. * @param {TimeInterval} [right] The second instance. * @param {number} [epsilon=0] The maximum number of seconds that should separate the two instances. * @param {TimeInterval.DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used. * @returns {boolean} true if the two dates are within epsilon seconds of each other; otherwise false. */ TimeInterval.equalsEpsilon = function (left, right, epsilon, dataComparer) { epsilon = defaultValue(epsilon, 0); return ( left === right || (defined(left) && defined(right) && ((left.isEmpty && right.isEmpty) || (left.isStartIncluded === right.isStartIncluded && left.isStopIncluded === right.isStopIncluded && JulianDate.equalsEpsilon(left.start, right.start, epsilon) && JulianDate.equalsEpsilon(left.stop, right.stop, epsilon) && (left.data === right.data || (defined(dataComparer) && dataComparer(left.data, right.data)))))) ); }; /** * Computes the intersection of two intervals, optionally merging their data. * * @param {TimeInterval} left The first interval. * @param {TimeInterval} [right] The second interval. * @param {TimeInterval} [result] An existing instance to use for the result. * @param {TimeInterval.MergeCallback} [mergeCallback] A function which merges the data of the two intervals. If omitted, the data from the left interval will be used. * @returns {TimeInterval} The modified result parameter. */ TimeInterval.intersect = function (left, right, result, mergeCallback) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("left", left); //>>includeEnd('debug'); if (!defined(right)) { return TimeInterval.clone(TimeInterval.EMPTY, result); } const leftStart = left.start; const leftStop = left.stop; const rightStart = right.start; const rightStop = right.stop; const intersectsStartRight = JulianDate.greaterThanOrEquals(rightStart, leftStart) && JulianDate.greaterThanOrEquals(leftStop, rightStart); const intersectsStartLeft = !intersectsStartRight && JulianDate.lessThanOrEquals(rightStart, leftStart) && JulianDate.lessThanOrEquals(leftStart, rightStop); if (!intersectsStartRight && !intersectsStartLeft) { return TimeInterval.clone(TimeInterval.EMPTY, result); } const leftIsStartIncluded = left.isStartIncluded; const leftIsStopIncluded = left.isStopIncluded; const rightIsStartIncluded = right.isStartIncluded; const rightIsStopIncluded = right.isStopIncluded; const leftLessThanRight = JulianDate.lessThan(leftStop, rightStop); if (!defined(result)) { result = new TimeInterval(); } result.start = intersectsStartRight ? rightStart : leftStart; result.isStartIncluded = (leftIsStartIncluded && rightIsStartIncluded) || (!JulianDate.equals(rightStart, leftStart) && ((intersectsStartRight && rightIsStartIncluded) || (intersectsStartLeft && leftIsStartIncluded))); result.stop = leftLessThanRight ? leftStop : rightStop; result.isStopIncluded = leftLessThanRight ? leftIsStopIncluded : (leftIsStopIncluded && rightIsStopIncluded) || (!JulianDate.equals(rightStop, leftStop) && rightIsStopIncluded); result.data = defined(mergeCallback) ? mergeCallback(left.data, right.data) : left.data; return result; }; /** * Checks if the specified date is inside the provided interval. * * @param {TimeInterval} timeInterval The interval. * @param {JulianDate} julianDate The date to check. * @returns {boolean} true if the interval contains the specified date, false otherwise. */ TimeInterval.contains = function (timeInterval, julianDate) { //>>includeStart('debug', pragmas.debug); Check.typeOf.object("timeInterval", timeInterval); Check.typeOf.object("julianDate", julianDate); //>>includeEnd('debug'); if (timeInterval.isEmpty) { return false; } const startComparedToDate = JulianDate.compare( timeInterval.start, julianDate ); if (startComparedToDate === 0) { return timeInterval.isStartIncluded; } const dateComparedToStop = JulianDate.compare(julianDate, timeInterval.stop); if (dateComparedToStop === 0) { return timeInterval.isStopIncluded; } return startComparedToDate < 0 && dateComparedToStop < 0; }; /** * Duplicates this instance. * * @param {TimeInterval} [result] An existing instance to use for the result. * @returns {TimeInterval} The modified result parameter or a new instance if none was provided. */ TimeInterval.prototype.clone = function (result) { return TimeInterval.clone(this, result); }; /** * Compares this instance against the provided instance componentwise and returns * true if they are equal, false otherwise. * * @param {TimeInterval} [right] The right hand side interval. * @param {TimeInterval.DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used. * @returns {boolean} true if they are equal, false otherwise. */ TimeInterval.prototype.equals = function (right, dataComparer) { return TimeInterval.equals(this, right, dataComparer); }; /** * Compares this instance against the provided instance componentwise and returns * true if they are within the provided epsilon, * false otherwise. * * @param {TimeInterval} [right] The right hand side interval. * @param {number} [epsilon=0] The epsilon to use for equality testing. * @param {TimeInterval.DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used. * @returns {boolean} true if they are within the provided epsilon, false otherwise. */ TimeInterval.prototype.equalsEpsilon = function (right, epsilon, dataComparer) { return TimeInterval.equalsEpsilon(this, right, epsilon, dataComparer); }; /** * Creates a string representing this TimeInterval in ISO8601 format. * * @returns {string} A string representing this TimeInterval in ISO8601 format. */ TimeInterval.prototype.toString = function () { return TimeInterval.toIso8601(this); }; /** * An immutable empty interval. * * @type {TimeInterval} * @constant */ TimeInterval.EMPTY = Object.freeze( new TimeInterval({ start: new JulianDate(), stop: new JulianDate(), isStartIncluded: false, isStopIncluded: false, }) ); /** * Function interface for merging interval data. * @callback TimeInterval.MergeCallback * * @param {*} leftData The first data instance. * @param {*} rightData The second data instance. * @returns {*} The result of merging the two data instances. */ /** * Function interface for comparing interval data. * @callback TimeInterval.DataComparer * @param {*} leftData The first data instance. * @param {*} rightData The second data instance. * @returns {boolean} true if the provided instances are equal, false otherwise. */ export default TimeInterval;