Source: Core/TimeInterval.js

  1. /*global define*/
  2. define([
  3. './defaultValue',
  4. './defined',
  5. './defineProperties',
  6. './DeveloperError',
  7. './freezeObject',
  8. './JulianDate'
  9. ], function(
  10. defaultValue,
  11. defined,
  12. defineProperties,
  13. DeveloperError,
  14. freezeObject,
  15. JulianDate) {
  16. 'use strict';
  17. /**
  18. * An interval defined by a start and a stop time; optionally including those times as part of the interval.
  19. * Arbitrary data can optionally be associated with each instance for used with {@link TimeIntervalCollection}.
  20. *
  21. * @alias TimeInterval
  22. * @constructor
  23. *
  24. * @param {Object} [options] Object with the following properties:
  25. * @param {JulianDate} [options.start=new JulianDate()] The start time of the interval.
  26. * @param {JulianDate} [options.stop=new JulianDate()] The stop time of the interval.
  27. * @param {Boolean} [options.isStartIncluded=true] <code>true</code> if <code>options.start</code> is included in the interval, <code>false</code> otherwise.
  28. * @param {Boolean} [options.isStopIncluded=true] <code>true</code> if <code>options.stop</code> is included in the interval, <code>false</code> otherwise.
  29. * @param {Object} [options.data] Arbitrary data associated with this interval.
  30. *
  31. * @example
  32. * // Create an instance that spans August 1st, 1980 and is associated
  33. * // with a Cartesian position.
  34. * var timeInterval = new Cesium.TimeInterval({
  35. * start : Cesium.JulianDate.fromIso8601('1980-08-01T00:00:00Z'),
  36. * stop : Cesium.JulianDate.fromIso8601('1980-08-02T00:00:00Z'),
  37. * isStartIncluded : true,
  38. * isStopIncluded : false,
  39. * data : Cesium.Cartesian3.fromDegrees(39.921037, -75.170082)
  40. * });
  41. *
  42. * @example
  43. * // Create two instances from ISO 8601 intervals with associated numeric data
  44. * // then compute their intersection, summing the data they contain.
  45. * var left = Cesium.TimeInterval.fromIso8601({
  46. * iso8601 : '2000/2010',
  47. * data : 2
  48. * });
  49. *
  50. * var right = Cesium.TimeInterval.fromIso8601({
  51. * iso8601 : '1995/2005',
  52. * data : 3
  53. * });
  54. *
  55. * //The result of the below intersection will be an interval equivalent to
  56. * //var intersection = Cesium.TimeInterval.fromIso8601({
  57. * // iso8601 : '2000/2005',
  58. * // data : 5
  59. * //});
  60. * var intersection = new Cesium.TimeInterval();
  61. * Cesium.TimeInterval.intersect(left, right, intersection, function(leftData, rightData) {
  62. * return leftData + rightData;
  63. * });
  64. *
  65. * @example
  66. * // Check if an interval contains a specific time.
  67. * var dateToCheck = Cesium.JulianDate.fromIso8601('1982-09-08T11:30:00Z');
  68. * var containsDate = Cesium.TimeInterval.contains(timeInterval, dateToCheck);
  69. */
  70. function TimeInterval(options) {
  71. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  72. /**
  73. * Gets or sets the start time of this interval.
  74. * @type {JulianDate}
  75. */
  76. this.start = defined(options.start) ? JulianDate.clone(options.start) : new JulianDate();
  77. /**
  78. * Gets or sets the stop time of this interval.
  79. * @type {JulianDate}
  80. */
  81. this.stop = defined(options.stop) ? JulianDate.clone(options.stop) : new JulianDate();
  82. /**
  83. * Gets or sets the data associated with this interval.
  84. * @type {Object}
  85. */
  86. this.data = options.data;
  87. /**
  88. * Gets or sets whether or not the start time is included in this interval.
  89. * @type {Boolean}
  90. * @default true
  91. */
  92. this.isStartIncluded = defaultValue(options.isStartIncluded, true);
  93. /**
  94. * Gets or sets whether or not the stop time is included in this interval.
  95. * @type {Boolean}
  96. * @default true
  97. */
  98. this.isStopIncluded = defaultValue(options.isStopIncluded, true);
  99. }
  100. defineProperties(TimeInterval.prototype, {
  101. /**
  102. * Gets whether or not this interval is empty.
  103. * @memberof TimeInterval.prototype
  104. * @type {Boolean}
  105. * @readonly
  106. */
  107. isEmpty : {
  108. get : function() {
  109. var stopComparedToStart = JulianDate.compare(this.stop, this.start);
  110. return stopComparedToStart < 0 || (stopComparedToStart === 0 && (!this.isStartIncluded || !this.isStopIncluded));
  111. }
  112. }
  113. });
  114. var scratchInterval = {
  115. start : undefined,
  116. stop : undefined,
  117. isStartIncluded : undefined,
  118. isStopIncluded : undefined,
  119. data : undefined
  120. };
  121. /**
  122. * Creates a new instance from an {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601} interval.
  123. *
  124. * @param {Object} options Object with the following properties:
  125. * @param {String} options.iso8601 An ISO 8601 interval.
  126. * @param {Boolean} [options.isStartIncluded=true] <code>true</code> if <code>options.start</code> is included in the interval, <code>false</code> otherwise.
  127. * @param {Boolean} [options.isStopIncluded=true] <code>true</code> if <code>options.stop</code> is included in the interval, <code>false</code> otherwise.
  128. * @param {Object} [options.data] Arbitrary data associated with this interval.
  129. * @param {TimeInterval} [result] An existing instance to use for the result.
  130. * @returns {TimeInterval} The modified result parameter or a new instance if none was provided.
  131. */
  132. TimeInterval.fromIso8601 = function(options, result) {
  133. //>>includeStart('debug', pragmas.debug);
  134. if (!defined(options)) {
  135. throw new DeveloperError('options is required.');
  136. }
  137. if (!defined(options.iso8601)) {
  138. throw new DeveloperError('options.iso8601 is required.');
  139. }
  140. //>>includeEnd('debug');
  141. var dates = options.iso8601.split('/');
  142. var start = JulianDate.fromIso8601(dates[0]);
  143. var stop = JulianDate.fromIso8601(dates[1]);
  144. var isStartIncluded = defaultValue(options.isStartIncluded, true);
  145. var isStopIncluded = defaultValue(options.isStopIncluded, true);
  146. var data = options.data;
  147. if (!defined(result)) {
  148. scratchInterval.start = start;
  149. scratchInterval.stop = stop;
  150. scratchInterval.isStartIncluded = isStartIncluded;
  151. scratchInterval.isStopIncluded = isStopIncluded;
  152. scratchInterval.data = data;
  153. return new TimeInterval(scratchInterval);
  154. }
  155. result.start = start;
  156. result.stop = stop;
  157. result.isStartIncluded = isStartIncluded;
  158. result.isStopIncluded = isStopIncluded;
  159. result.data = data;
  160. return result;
  161. };
  162. /**
  163. * Creates an ISO8601 representation of the provided interval.
  164. *
  165. * @param {TimeInterval} timeInterval The interval to be converted.
  166. * @param {Number} [precision] The number of fractional digits used to represent the seconds component. By default, the most precise representation is used.
  167. * @returns {String} The ISO8601 representation of the provided interval.
  168. */
  169. TimeInterval.toIso8601 = function(timeInterval, precision) {
  170. //>>includeStart('debug', pragmas.debug);
  171. if (!defined(timeInterval)) {
  172. throw new DeveloperError('timeInterval is required.');
  173. }
  174. //>>includeEnd('debug');
  175. return JulianDate.toIso8601(timeInterval.start, precision) + '/' + JulianDate.toIso8601(timeInterval.stop, precision);
  176. };
  177. /**
  178. * Duplicates the provided instance.
  179. *
  180. * @param {TimeInterval} [timeInterval] The instance to clone.
  181. * @param {TimeInterval} [result] An existing instance to use for the result.
  182. * @returns {TimeInterval} The modified result parameter or a new instance if none was provided.
  183. */
  184. TimeInterval.clone = function(timeInterval, result) {
  185. if (!defined(timeInterval)) {
  186. return undefined;
  187. }
  188. if (!defined(result)) {
  189. return new TimeInterval(timeInterval);
  190. }
  191. result.start = timeInterval.start;
  192. result.stop = timeInterval.stop;
  193. result.isStartIncluded = timeInterval.isStartIncluded;
  194. result.isStopIncluded = timeInterval.isStopIncluded;
  195. result.data = timeInterval.data;
  196. return result;
  197. };
  198. /**
  199. * Compares two instances and returns <code>true</code> if they are equal, <code>false</code> otherwise.
  200. *
  201. * @param {TimeInterval} [left] The first instance.
  202. * @param {TimeInterval} [right] The second instance.
  203. * @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used.
  204. * @returns {Boolean} <code>true</code> if the dates are equal; otherwise, <code>false</code>.
  205. */
  206. TimeInterval.equals = function(left, right, dataComparer) {
  207. return left === right ||
  208. defined(left) && defined(right) &&
  209. (left.isEmpty && right.isEmpty ||
  210. left.isStartIncluded === right.isStartIncluded &&
  211. left.isStopIncluded === right.isStopIncluded &&
  212. JulianDate.equals(left.start, right.start) &&
  213. JulianDate.equals(left.stop, right.stop) &&
  214. (left.data === right.data || (defined(dataComparer) && dataComparer(left.data, right.data))));
  215. };
  216. /**
  217. * Compares two instances and returns <code>true</code> if they are within <code>epsilon</code> seconds of
  218. * each other. That is, in order for the dates to be considered equal (and for
  219. * this function to return <code>true</code>), the absolute value of the difference between them, in
  220. * seconds, must be less than <code>epsilon</code>.
  221. *
  222. * @param {TimeInterval} [left] The first instance.
  223. * @param {TimeInterval} [right] The second instance.
  224. * @param {Number} epsilon The maximum number of seconds that should separate the two instances.
  225. * @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used.
  226. * @returns {Boolean} <code>true</code> if the two dates are within <code>epsilon</code> seconds of each other; otherwise <code>false</code>.
  227. */
  228. TimeInterval.equalsEpsilon = function(left, right, epsilon, dataComparer) {
  229. //>>includeStart('debug', pragmas.debug);
  230. if (typeof epsilon !== 'number') {
  231. throw new DeveloperError('epsilon is required and must be a number.');
  232. }
  233. //>>includeEnd('debug');
  234. return left === right ||
  235. defined(left) && defined(right) &&
  236. (left.isEmpty && right.isEmpty ||
  237. left.isStartIncluded === right.isStartIncluded &&
  238. left.isStopIncluded === right.isStopIncluded &&
  239. JulianDate.equalsEpsilon(left.start, right.start, epsilon) &&
  240. JulianDate.equalsEpsilon(left.stop, right.stop, epsilon) &&
  241. (left.data === right.data || (defined(dataComparer) && dataComparer(left.data, right.data))));
  242. };
  243. /**
  244. * Computes the intersection of two intervals, optionally merging their data.
  245. *
  246. * @param {TimeInterval} left The first interval.
  247. * @param {TimeInterval} [right] The second interval.
  248. * @param {TimeInterval} result An existing instance to use for the result.
  249. * @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.
  250. * @returns {TimeInterval} The modified result parameter.
  251. */
  252. TimeInterval.intersect = function(left, right, result, mergeCallback) {
  253. //>>includeStart('debug', pragmas.debug);
  254. if (!defined(left)) {
  255. throw new DeveloperError('left is required.');
  256. }
  257. if (!defined(result)) {
  258. throw new DeveloperError('result is required.');
  259. }
  260. //>>includeEnd('debug');
  261. if (!defined(right)) {
  262. return TimeInterval.clone(TimeInterval.EMPTY, result);
  263. }
  264. var leftStart = left.start;
  265. var leftStop = left.stop;
  266. var rightStart = right.start;
  267. var rightStop = right.stop;
  268. var intersectsStartRight = JulianDate.greaterThanOrEquals(rightStart, leftStart) && JulianDate.greaterThanOrEquals(leftStop, rightStart);
  269. var intersectsStartLeft = !intersectsStartRight && JulianDate.lessThanOrEquals(rightStart, leftStart) && JulianDate.lessThanOrEquals(leftStart, rightStop);
  270. if (!intersectsStartRight && !intersectsStartLeft) {
  271. return TimeInterval.clone(TimeInterval.EMPTY, result);
  272. }
  273. var leftIsStartIncluded = left.isStartIncluded;
  274. var leftIsStopIncluded = left.isStopIncluded;
  275. var rightIsStartIncluded = right.isStartIncluded;
  276. var rightIsStopIncluded = right.isStopIncluded;
  277. var leftLessThanRight = JulianDate.lessThan(leftStop, rightStop);
  278. result.start = intersectsStartRight ? rightStart : leftStart;
  279. result.isStartIncluded = (leftIsStartIncluded && rightIsStartIncluded) || (!JulianDate.equals(rightStart, leftStart) && ((intersectsStartRight && rightIsStartIncluded) || (intersectsStartLeft && leftIsStartIncluded)));
  280. result.stop = leftLessThanRight ? leftStop : rightStop;
  281. result.isStopIncluded = leftLessThanRight ? leftIsStopIncluded : (leftIsStopIncluded && rightIsStopIncluded) || (!JulianDate.equals(rightStop, leftStop) && rightIsStopIncluded);
  282. result.data = defined(mergeCallback) ? mergeCallback(left.data, right.data) : left.data;
  283. return result;
  284. };
  285. /**
  286. * Checks if the specified date is inside the provided interval.
  287. *
  288. * @param {TimeInterval} timeInterval The interval.
  289. * @param {JulianDate} julianDate The date to check.
  290. * @returns {Boolean} <code>true</code> if the interval contains the specified date, <code>false</code> otherwise.
  291. */
  292. TimeInterval.contains = function(timeInterval, julianDate) {
  293. //>>includeStart('debug', pragmas.debug);
  294. if (!defined(timeInterval)) {
  295. throw new DeveloperError('timeInterval is required.');
  296. }
  297. if (!defined(julianDate)) {
  298. throw new DeveloperError('julianDate is required.');
  299. }
  300. //>>includeEnd('debug');
  301. if (timeInterval.isEmpty) {
  302. return false;
  303. }
  304. var startComparedToDate = JulianDate.compare(timeInterval.start, julianDate);
  305. if (startComparedToDate === 0) {
  306. return timeInterval.isStartIncluded;
  307. }
  308. var dateComparedToStop = JulianDate.compare(julianDate, timeInterval.stop);
  309. if (dateComparedToStop === 0) {
  310. return timeInterval.isStopIncluded;
  311. }
  312. return startComparedToDate < 0 && dateComparedToStop < 0;
  313. };
  314. /**
  315. * Duplicates this instance.
  316. *
  317. * @param {TimeInterval} [result] An existing instance to use for the result.
  318. * @returns {TimeInterval} The modified result parameter or a new instance if none was provided.
  319. */
  320. TimeInterval.prototype.clone = function(result) {
  321. return TimeInterval.clone(this, result);
  322. };
  323. /**
  324. * Compares this instance against the provided instance componentwise and returns
  325. * <code>true</code> if they are equal, <code>false</code> otherwise.
  326. *
  327. * @param {TimeInterval} [right] The right hand side interval.
  328. * @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used.
  329. * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
  330. */
  331. TimeInterval.prototype.equals = function(right, dataComparer) {
  332. return TimeInterval.equals(this, right, dataComparer);
  333. };
  334. /**
  335. * Compares this instance against the provided instance componentwise and returns
  336. * <code>true</code> if they are within the provided epsilon,
  337. * <code>false</code> otherwise.
  338. *
  339. * @param {TimeInterval} [right] The right hand side interval.
  340. * @param {Number} epsilon The epsilon to use for equality testing.
  341. * @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals. If omitted, reference equality is used.
  342. * @returns {Boolean} <code>true</code> if they are within the provided epsilon, <code>false</code> otherwise.
  343. */
  344. TimeInterval.prototype.equalsEpsilon = function(right, epsilon, dataComparer) {
  345. return TimeInterval.equalsEpsilon(this, right, epsilon, dataComparer);
  346. };
  347. /**
  348. * Creates a string representing this TimeInterval in ISO8601 format.
  349. *
  350. * @returns {String} A string representing this TimeInterval in ISO8601 format.
  351. */
  352. TimeInterval.prototype.toString = function() {
  353. return TimeInterval.toIso8601(this);
  354. };
  355. /**
  356. * An immutable empty interval.
  357. *
  358. * @type {TimeInterval}
  359. * @constant
  360. */
  361. TimeInterval.EMPTY = freezeObject(new TimeInterval({
  362. start : new JulianDate(),
  363. stop : new JulianDate(),
  364. isStartIncluded : false,
  365. isStopIncluded : false
  366. }));
  367. /**
  368. * Function interface for merging interval data.
  369. * @callback TimeInterval~MergeCallback
  370. *
  371. * @param {Object} leftData The first data instance.
  372. * @param {Object} rightData The second data instance.
  373. * @returns {Object} The result of merging the two data instances.
  374. */
  375. /**
  376. * Function interface for comparing interval data.
  377. * @callback TimeInterval~DataComparer
  378. * @param {Object} leftData The first data instance.
  379. * @param {Object} rightData The second data instance.
  380. * @returns {Boolean} <code>true</code> if the provided instances are equal, <code>false</code> otherwise.
  381. */
  382. return TimeInterval;
  383. });