TimeInterval.js 16 KB

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