EarthOrientationParameters.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. import when from "../ThirdParty/when.js";
  2. import binarySearch from "./binarySearch.js";
  3. import defaultValue from "./defaultValue.js";
  4. import defined from "./defined.js";
  5. import EarthOrientationParametersSample from "./EarthOrientationParametersSample.js";
  6. import JulianDate from "./JulianDate.js";
  7. import LeapSecond from "./LeapSecond.js";
  8. import Resource from "./Resource.js";
  9. import RuntimeError from "./RuntimeError.js";
  10. import TimeConstants from "./TimeConstants.js";
  11. import TimeStandard from "./TimeStandard.js";
  12. /**
  13. * Specifies Earth polar motion coordinates and the difference between UT1 and UTC.
  14. * These Earth Orientation Parameters (EOP) are primarily used in the transformation from
  15. * the International Celestial Reference Frame (ICRF) to the International Terrestrial
  16. * Reference Frame (ITRF).
  17. *
  18. * @alias EarthOrientationParameters
  19. * @constructor
  20. *
  21. * @param {Object} [options] Object with the following properties:
  22. * @param {Resource|String} [options.url] The URL from which to obtain EOP data. If neither this
  23. * parameter nor options.data is specified, all EOP values are assumed
  24. * to be 0.0. If options.data is specified, this parameter is
  25. * ignored.
  26. * @param {Object} [options.data] The actual EOP data. If neither this
  27. * parameter nor options.data is specified, all EOP values are assumed
  28. * to be 0.0.
  29. * @param {Boolean} [options.addNewLeapSeconds=true] True if leap seconds that
  30. * are specified in the EOP data but not in {@link JulianDate.leapSeconds}
  31. * should be added to {@link JulianDate.leapSeconds}. False if
  32. * new leap seconds should be handled correctly in the context
  33. * of the EOP data but otherwise ignored.
  34. *
  35. * @example
  36. * // An example EOP data file, EOP.json:
  37. * {
  38. * "columnNames" : ["dateIso8601","modifiedJulianDateUtc","xPoleWanderRadians","yPoleWanderRadians","ut1MinusUtcSeconds","lengthOfDayCorrectionSeconds","xCelestialPoleOffsetRadians","yCelestialPoleOffsetRadians","taiMinusUtcSeconds"],
  39. * "samples" : [
  40. * "2011-07-01T00:00:00Z",55743.0,2.117957047295119e-7,2.111518721609984e-6,-0.2908948,-2.956e-4,3.393695767766752e-11,3.3452143996557983e-10,34.0,
  41. * "2011-07-02T00:00:00Z",55744.0,2.193297093339541e-7,2.115460256837405e-6,-0.29065,-1.824e-4,-8.241832578862112e-11,5.623838700870617e-10,34.0,
  42. * "2011-07-03T00:00:00Z",55745.0,2.262286080161428e-7,2.1191157519929706e-6,-0.2905572,1.9e-6,-3.490658503988659e-10,6.981317007977318e-10,34.0
  43. * ]
  44. * }
  45. *
  46. * @example
  47. * // Loading the EOP data
  48. * var eop = new Cesium.EarthOrientationParameters({ url : 'Data/EOP.json' });
  49. * Cesium.Transforms.earthOrientationParameters = eop;
  50. *
  51. * @private
  52. */
  53. function EarthOrientationParameters(options) {
  54. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  55. this._dates = undefined;
  56. this._samples = undefined;
  57. this._dateColumn = -1;
  58. this._xPoleWanderRadiansColumn = -1;
  59. this._yPoleWanderRadiansColumn = -1;
  60. this._ut1MinusUtcSecondsColumn = -1;
  61. this._xCelestialPoleOffsetRadiansColumn = -1;
  62. this._yCelestialPoleOffsetRadiansColumn = -1;
  63. this._taiMinusUtcSecondsColumn = -1;
  64. this._columnCount = 0;
  65. this._lastIndex = -1;
  66. this._downloadPromise = undefined;
  67. this._dataError = undefined;
  68. this._addNewLeapSeconds = defaultValue(options.addNewLeapSeconds, true);
  69. if (defined(options.data)) {
  70. // Use supplied EOP data.
  71. onDataReady(this, options.data);
  72. } else if (defined(options.url)) {
  73. var resource = Resource.createIfNeeded(options.url);
  74. // Download EOP data.
  75. var that = this;
  76. this._downloadPromise = resource
  77. .fetchJson()
  78. .then(function (eopData) {
  79. onDataReady(that, eopData);
  80. })
  81. .otherwise(function () {
  82. that._dataError =
  83. "An error occurred while retrieving the EOP data from the URL " +
  84. resource.url +
  85. ".";
  86. });
  87. } else {
  88. // Use all zeros for EOP data.
  89. onDataReady(this, {
  90. columnNames: [
  91. "dateIso8601",
  92. "modifiedJulianDateUtc",
  93. "xPoleWanderRadians",
  94. "yPoleWanderRadians",
  95. "ut1MinusUtcSeconds",
  96. "lengthOfDayCorrectionSeconds",
  97. "xCelestialPoleOffsetRadians",
  98. "yCelestialPoleOffsetRadians",
  99. "taiMinusUtcSeconds",
  100. ],
  101. samples: [],
  102. });
  103. }
  104. }
  105. /**
  106. * A default {@link EarthOrientationParameters} instance that returns zero for all EOP values.
  107. */
  108. EarthOrientationParameters.NONE = Object.freeze({
  109. getPromiseToLoad: function () {
  110. return when.resolve();
  111. },
  112. compute: function (date, result) {
  113. if (!defined(result)) {
  114. result = new EarthOrientationParametersSample(0.0, 0.0, 0.0, 0.0, 0.0);
  115. } else {
  116. result.xPoleWander = 0.0;
  117. result.yPoleWander = 0.0;
  118. result.xPoleOffset = 0.0;
  119. result.yPoleOffset = 0.0;
  120. result.ut1MinusUtc = 0.0;
  121. }
  122. return result;
  123. },
  124. });
  125. /**
  126. * Gets a promise that, when resolved, indicates that the EOP data has been loaded and is
  127. * ready to use.
  128. *
  129. * @returns {Promise<void>} The promise.
  130. */
  131. EarthOrientationParameters.prototype.getPromiseToLoad = function () {
  132. return when(this._downloadPromise);
  133. };
  134. /**
  135. * Computes the Earth Orientation Parameters (EOP) for a given date by interpolating.
  136. * If the EOP data has not yet been download, this method returns undefined.
  137. *
  138. * @param {JulianDate} date The date for each to evaluate the EOP.
  139. * @param {EarthOrientationParametersSample} [result] The instance to which to copy the result.
  140. * If this parameter is undefined, a new instance is created and returned.
  141. * @returns {EarthOrientationParametersSample} The EOP evaluated at the given date, or
  142. * undefined if the data necessary to evaluate EOP at the date has not yet been
  143. * downloaded.
  144. *
  145. * @exception {RuntimeError} The loaded EOP data has an error and cannot be used.
  146. *
  147. * @see EarthOrientationParameters#getPromiseToLoad
  148. */
  149. EarthOrientationParameters.prototype.compute = function (date, result) {
  150. // We cannot compute until the samples are available.
  151. if (!defined(this._samples)) {
  152. if (defined(this._dataError)) {
  153. throw new RuntimeError(this._dataError);
  154. }
  155. return undefined;
  156. }
  157. if (!defined(result)) {
  158. result = new EarthOrientationParametersSample(0.0, 0.0, 0.0, 0.0, 0.0);
  159. }
  160. if (this._samples.length === 0) {
  161. result.xPoleWander = 0.0;
  162. result.yPoleWander = 0.0;
  163. result.xPoleOffset = 0.0;
  164. result.yPoleOffset = 0.0;
  165. result.ut1MinusUtc = 0.0;
  166. return result;
  167. }
  168. var dates = this._dates;
  169. var lastIndex = this._lastIndex;
  170. var before = 0;
  171. var after = 0;
  172. if (defined(lastIndex)) {
  173. var previousIndexDate = dates[lastIndex];
  174. var nextIndexDate = dates[lastIndex + 1];
  175. var isAfterPrevious = JulianDate.lessThanOrEquals(previousIndexDate, date);
  176. var isAfterLastSample = !defined(nextIndexDate);
  177. var isBeforeNext =
  178. isAfterLastSample || JulianDate.greaterThanOrEquals(nextIndexDate, date);
  179. if (isAfterPrevious && isBeforeNext) {
  180. before = lastIndex;
  181. if (!isAfterLastSample && nextIndexDate.equals(date)) {
  182. ++before;
  183. }
  184. after = before + 1;
  185. interpolate(this, dates, this._samples, date, before, after, result);
  186. return result;
  187. }
  188. }
  189. var index = binarySearch(dates, date, JulianDate.compare, this._dateColumn);
  190. if (index >= 0) {
  191. // If the next entry is the same date, use the later entry. This way, if two entries
  192. // describe the same moment, one before a leap second and the other after, then we will use
  193. // the post-leap second data.
  194. if (index < dates.length - 1 && dates[index + 1].equals(date)) {
  195. ++index;
  196. }
  197. before = index;
  198. after = index;
  199. } else {
  200. after = ~index;
  201. before = after - 1;
  202. // Use the first entry if the date requested is before the beginning of the data.
  203. if (before < 0) {
  204. before = 0;
  205. }
  206. }
  207. this._lastIndex = before;
  208. interpolate(this, dates, this._samples, date, before, after, result);
  209. return result;
  210. };
  211. function compareLeapSecondDates(leapSecond, dateToFind) {
  212. return JulianDate.compare(leapSecond.julianDate, dateToFind);
  213. }
  214. function onDataReady(eop, eopData) {
  215. if (!defined(eopData.columnNames)) {
  216. eop._dataError =
  217. "Error in loaded EOP data: The columnNames property is required.";
  218. return;
  219. }
  220. if (!defined(eopData.samples)) {
  221. eop._dataError =
  222. "Error in loaded EOP data: The samples property is required.";
  223. return;
  224. }
  225. var dateColumn = eopData.columnNames.indexOf("modifiedJulianDateUtc");
  226. var xPoleWanderRadiansColumn = eopData.columnNames.indexOf(
  227. "xPoleWanderRadians"
  228. );
  229. var yPoleWanderRadiansColumn = eopData.columnNames.indexOf(
  230. "yPoleWanderRadians"
  231. );
  232. var ut1MinusUtcSecondsColumn = eopData.columnNames.indexOf(
  233. "ut1MinusUtcSeconds"
  234. );
  235. var xCelestialPoleOffsetRadiansColumn = eopData.columnNames.indexOf(
  236. "xCelestialPoleOffsetRadians"
  237. );
  238. var yCelestialPoleOffsetRadiansColumn = eopData.columnNames.indexOf(
  239. "yCelestialPoleOffsetRadians"
  240. );
  241. var taiMinusUtcSecondsColumn = eopData.columnNames.indexOf(
  242. "taiMinusUtcSeconds"
  243. );
  244. if (
  245. dateColumn < 0 ||
  246. xPoleWanderRadiansColumn < 0 ||
  247. yPoleWanderRadiansColumn < 0 ||
  248. ut1MinusUtcSecondsColumn < 0 ||
  249. xCelestialPoleOffsetRadiansColumn < 0 ||
  250. yCelestialPoleOffsetRadiansColumn < 0 ||
  251. taiMinusUtcSecondsColumn < 0
  252. ) {
  253. eop._dataError =
  254. "Error in loaded EOP data: The columnNames property must include modifiedJulianDateUtc, xPoleWanderRadians, yPoleWanderRadians, ut1MinusUtcSeconds, xCelestialPoleOffsetRadians, yCelestialPoleOffsetRadians, and taiMinusUtcSeconds columns";
  255. return;
  256. }
  257. var samples = (eop._samples = eopData.samples);
  258. var dates = (eop._dates = []);
  259. eop._dateColumn = dateColumn;
  260. eop._xPoleWanderRadiansColumn = xPoleWanderRadiansColumn;
  261. eop._yPoleWanderRadiansColumn = yPoleWanderRadiansColumn;
  262. eop._ut1MinusUtcSecondsColumn = ut1MinusUtcSecondsColumn;
  263. eop._xCelestialPoleOffsetRadiansColumn = xCelestialPoleOffsetRadiansColumn;
  264. eop._yCelestialPoleOffsetRadiansColumn = yCelestialPoleOffsetRadiansColumn;
  265. eop._taiMinusUtcSecondsColumn = taiMinusUtcSecondsColumn;
  266. eop._columnCount = eopData.columnNames.length;
  267. eop._lastIndex = undefined;
  268. var lastTaiMinusUtc;
  269. var addNewLeapSeconds = eop._addNewLeapSeconds;
  270. // Convert the ISO8601 dates to JulianDates.
  271. for (var i = 0, len = samples.length; i < len; i += eop._columnCount) {
  272. var mjd = samples[i + dateColumn];
  273. var taiMinusUtc = samples[i + taiMinusUtcSecondsColumn];
  274. var day = mjd + TimeConstants.MODIFIED_JULIAN_DATE_DIFFERENCE;
  275. var date = new JulianDate(day, taiMinusUtc, TimeStandard.TAI);
  276. dates.push(date);
  277. if (addNewLeapSeconds) {
  278. if (taiMinusUtc !== lastTaiMinusUtc && defined(lastTaiMinusUtc)) {
  279. // We crossed a leap second boundary, so add the leap second
  280. // if it does not already exist.
  281. var leapSeconds = JulianDate.leapSeconds;
  282. var leapSecondIndex = binarySearch(
  283. leapSeconds,
  284. date,
  285. compareLeapSecondDates
  286. );
  287. if (leapSecondIndex < 0) {
  288. var leapSecond = new LeapSecond(date, taiMinusUtc);
  289. leapSeconds.splice(~leapSecondIndex, 0, leapSecond);
  290. }
  291. }
  292. lastTaiMinusUtc = taiMinusUtc;
  293. }
  294. }
  295. }
  296. function fillResultFromIndex(eop, samples, index, columnCount, result) {
  297. var start = index * columnCount;
  298. result.xPoleWander = samples[start + eop._xPoleWanderRadiansColumn];
  299. result.yPoleWander = samples[start + eop._yPoleWanderRadiansColumn];
  300. result.xPoleOffset = samples[start + eop._xCelestialPoleOffsetRadiansColumn];
  301. result.yPoleOffset = samples[start + eop._yCelestialPoleOffsetRadiansColumn];
  302. result.ut1MinusUtc = samples[start + eop._ut1MinusUtcSecondsColumn];
  303. }
  304. function linearInterp(dx, y1, y2) {
  305. return y1 + dx * (y2 - y1);
  306. }
  307. function interpolate(eop, dates, samples, date, before, after, result) {
  308. var columnCount = eop._columnCount;
  309. // First check the bounds on the EOP data
  310. // If we are after the bounds of the data, return zeros.
  311. // The 'before' index should never be less than zero.
  312. if (after > dates.length - 1) {
  313. result.xPoleWander = 0;
  314. result.yPoleWander = 0;
  315. result.xPoleOffset = 0;
  316. result.yPoleOffset = 0;
  317. result.ut1MinusUtc = 0;
  318. return result;
  319. }
  320. var beforeDate = dates[before];
  321. var afterDate = dates[after];
  322. if (beforeDate.equals(afterDate) || date.equals(beforeDate)) {
  323. fillResultFromIndex(eop, samples, before, columnCount, result);
  324. return result;
  325. } else if (date.equals(afterDate)) {
  326. fillResultFromIndex(eop, samples, after, columnCount, result);
  327. return result;
  328. }
  329. var factor =
  330. JulianDate.secondsDifference(date, beforeDate) /
  331. JulianDate.secondsDifference(afterDate, beforeDate);
  332. var startBefore = before * columnCount;
  333. var startAfter = after * columnCount;
  334. // Handle UT1 leap second edge case
  335. var beforeUt1MinusUtc = samples[startBefore + eop._ut1MinusUtcSecondsColumn];
  336. var afterUt1MinusUtc = samples[startAfter + eop._ut1MinusUtcSecondsColumn];
  337. var offsetDifference = afterUt1MinusUtc - beforeUt1MinusUtc;
  338. if (offsetDifference > 0.5 || offsetDifference < -0.5) {
  339. // The absolute difference between the values is more than 0.5, so we may have
  340. // crossed a leap second. Check if this is the case and, if so, adjust the
  341. // afterValue to account for the leap second. This way, our interpolation will
  342. // produce reasonable results.
  343. var beforeTaiMinusUtc =
  344. samples[startBefore + eop._taiMinusUtcSecondsColumn];
  345. var afterTaiMinusUtc = samples[startAfter + eop._taiMinusUtcSecondsColumn];
  346. if (beforeTaiMinusUtc !== afterTaiMinusUtc) {
  347. if (afterDate.equals(date)) {
  348. // If we are at the end of the leap second interval, take the second value
  349. // Otherwise, the interpolation below will yield the wrong side of the
  350. // discontinuity
  351. // At the end of the leap second, we need to start accounting for the jump
  352. beforeUt1MinusUtc = afterUt1MinusUtc;
  353. } else {
  354. // Otherwise, remove the leap second so that the interpolation is correct
  355. afterUt1MinusUtc -= afterTaiMinusUtc - beforeTaiMinusUtc;
  356. }
  357. }
  358. }
  359. result.xPoleWander = linearInterp(
  360. factor,
  361. samples[startBefore + eop._xPoleWanderRadiansColumn],
  362. samples[startAfter + eop._xPoleWanderRadiansColumn]
  363. );
  364. result.yPoleWander = linearInterp(
  365. factor,
  366. samples[startBefore + eop._yPoleWanderRadiansColumn],
  367. samples[startAfter + eop._yPoleWanderRadiansColumn]
  368. );
  369. result.xPoleOffset = linearInterp(
  370. factor,
  371. samples[startBefore + eop._xCelestialPoleOffsetRadiansColumn],
  372. samples[startAfter + eop._xCelestialPoleOffsetRadiansColumn]
  373. );
  374. result.yPoleOffset = linearInterp(
  375. factor,
  376. samples[startBefore + eop._yCelestialPoleOffsetRadiansColumn],
  377. samples[startAfter + eop._yCelestialPoleOffsetRadiansColumn]
  378. );
  379. result.ut1MinusUtc = linearInterp(
  380. factor,
  381. beforeUt1MinusUtc,
  382. afterUt1MinusUtc
  383. );
  384. return result;
  385. }
  386. export default EarthOrientationParameters;