EarthOrientationParameters.js 17 KB

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