TimeInterval.js 17 KB

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