123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- import binarySearch from "./binarySearch.js";
- import Check from "./Check.js";
- import defaultValue from "./defaultValue.js";
- import defined from "./defined.js";
- import EarthOrientationParametersSample from "./EarthOrientationParametersSample.js";
- import JulianDate from "./JulianDate.js";
- import LeapSecond from "./LeapSecond.js";
- import Resource from "./Resource.js";
- import RuntimeError from "./RuntimeError.js";
- import TimeConstants from "./TimeConstants.js";
- import TimeStandard from "./TimeStandard.js";
- /**
- * Specifies Earth polar motion coordinates and the difference between UT1 and UTC.
- * These Earth Orientation Parameters (EOP) are primarily used in the transformation from
- * the International Celestial Reference Frame (ICRF) to the International Terrestrial
- * Reference Frame (ITRF).
- * This object is normally not instantiated directly, use {@link EarthOrientationParameters.fromUrl}.
- *
- * @alias EarthOrientationParameters
- * @constructor
- *
- * @param {object} [options] Object with the following properties:
- * @param {object} [options.data] The actual EOP data. If neither this
- * parameter nor options.data is specified, all EOP values are assumed
- * to be 0.0.
- * @param {boolean} [options.addNewLeapSeconds=true] True if leap seconds that
- * are specified in the EOP data but not in {@link JulianDate.leapSeconds}
- * should be added to {@link JulianDate.leapSeconds}. False if
- * new leap seconds should be handled correctly in the context
- * of the EOP data but otherwise ignored.
- *
- * @private
- */
- function EarthOrientationParameters(options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- this._dates = undefined;
- this._samples = undefined;
- this._dateColumn = -1;
- this._xPoleWanderRadiansColumn = -1;
- this._yPoleWanderRadiansColumn = -1;
- this._ut1MinusUtcSecondsColumn = -1;
- this._xCelestialPoleOffsetRadiansColumn = -1;
- this._yCelestialPoleOffsetRadiansColumn = -1;
- this._taiMinusUtcSecondsColumn = -1;
- this._columnCount = 0;
- this._lastIndex = -1;
- this._addNewLeapSeconds = defaultValue(options.addNewLeapSeconds, true);
- if (defined(options.data)) {
- // Use supplied EOP data.
- onDataReady(this, options.data);
- } else {
- // Use all zeros for EOP data.
- onDataReady(this, {
- columnNames: [
- "dateIso8601",
- "modifiedJulianDateUtc",
- "xPoleWanderRadians",
- "yPoleWanderRadians",
- "ut1MinusUtcSeconds",
- "lengthOfDayCorrectionSeconds",
- "xCelestialPoleOffsetRadians",
- "yCelestialPoleOffsetRadians",
- "taiMinusUtcSeconds",
- ],
- samples: [],
- });
- }
- }
- /**
- *
- * @param {Resource|string} [url] The URL from which to obtain EOP data. If neither this
- * parameter nor options.data is specified, all EOP values are assumed
- * to be 0.0. If options.data is specified, this parameter is
- * ignored.
- * @param {object} [options] Object with the following properties:
- * @param {boolean} [options.addNewLeapSeconds=true] True if leap seconds that
- * are specified in the EOP data but not in {@link JulianDate.leapSeconds}
- * should be added to {@link JulianDate.leapSeconds}. False if
- * new leap seconds should be handled correctly in the context
- * of the EOP data but otherwise ignored.
- *
- * @example
- * // An example EOP data file, EOP.json:
- * {
- * "columnNames" : ["dateIso8601","modifiedJulianDateUtc","xPoleWanderRadians","yPoleWanderRadians","ut1MinusUtcSeconds","lengthOfDayCorrectionSeconds","xCelestialPoleOffsetRadians","yCelestialPoleOffsetRadians","taiMinusUtcSeconds"],
- * "samples" : [
- * "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,
- * "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,
- * "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
- * ]
- * }
- *
- * @example
- * // Loading the EOP data
- * const eop = await Cesium.EarthOrientationParameters.fromUrl('Data/EOP.json');
- * Cesium.Transforms.earthOrientationParameters = eop;
- */
- EarthOrientationParameters.fromUrl = async function (url, options) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("url", url);
- //>>includeEnd('debug');
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- const resource = Resource.createIfNeeded(url);
- // Download EOP data.
- let eopData;
- try {
- eopData = await resource.fetchJson();
- } catch (e) {
- throw new RuntimeError(
- `An error occurred while retrieving the EOP data from the URL ${resource.url}.`
- );
- }
- return new EarthOrientationParameters({
- addNewLeapSeconds: options.addNewLeapSeconds,
- data: eopData,
- });
- };
- /**
- * A default {@link EarthOrientationParameters} instance that returns zero for all EOP values.
- */
- EarthOrientationParameters.NONE = Object.freeze({
- compute: function (date, result) {
- if (!defined(result)) {
- result = new EarthOrientationParametersSample(0.0, 0.0, 0.0, 0.0, 0.0);
- } else {
- result.xPoleWander = 0.0;
- result.yPoleWander = 0.0;
- result.xPoleOffset = 0.0;
- result.yPoleOffset = 0.0;
- result.ut1MinusUtc = 0.0;
- }
- return result;
- },
- });
- /**
- * Computes the Earth Orientation Parameters (EOP) for a given date by interpolating.
- * If the EOP data has not yet been download, this method returns undefined.
- *
- * @param {JulianDate} date The date for each to evaluate the EOP.
- * @param {EarthOrientationParametersSample} [result] The instance to which to copy the result.
- * If this parameter is undefined, a new instance is created and returned.
- * @returns {EarthOrientationParametersSample} The EOP evaluated at the given date, or
- * undefined if the data necessary to evaluate EOP at the date has not yet been
- * downloaded.
- *
- * @exception {RuntimeError} The loaded EOP data has an error and cannot be used.
- *
- * @see EarthOrientationParameters#fromUrl
- */
- EarthOrientationParameters.prototype.compute = function (date, result) {
- // We cannot compute until the samples are available.
- if (!defined(this._samples)) {
- return undefined;
- }
- if (!defined(result)) {
- result = new EarthOrientationParametersSample(0.0, 0.0, 0.0, 0.0, 0.0);
- }
- if (this._samples.length === 0) {
- result.xPoleWander = 0.0;
- result.yPoleWander = 0.0;
- result.xPoleOffset = 0.0;
- result.yPoleOffset = 0.0;
- result.ut1MinusUtc = 0.0;
- return result;
- }
- const dates = this._dates;
- const lastIndex = this._lastIndex;
- let before = 0;
- let after = 0;
- if (defined(lastIndex)) {
- const previousIndexDate = dates[lastIndex];
- const nextIndexDate = dates[lastIndex + 1];
- const isAfterPrevious = JulianDate.lessThanOrEquals(
- previousIndexDate,
- date
- );
- const isAfterLastSample = !defined(nextIndexDate);
- const isBeforeNext =
- isAfterLastSample || JulianDate.greaterThanOrEquals(nextIndexDate, date);
- if (isAfterPrevious && isBeforeNext) {
- before = lastIndex;
- if (!isAfterLastSample && nextIndexDate.equals(date)) {
- ++before;
- }
- after = before + 1;
- interpolate(this, dates, this._samples, date, before, after, result);
- return result;
- }
- }
- let index = binarySearch(dates, date, JulianDate.compare, this._dateColumn);
- if (index >= 0) {
- // If the next entry is the same date, use the later entry. This way, if two entries
- // describe the same moment, one before a leap second and the other after, then we will use
- // the post-leap second data.
- if (index < dates.length - 1 && dates[index + 1].equals(date)) {
- ++index;
- }
- before = index;
- after = index;
- } else {
- after = ~index;
- before = after - 1;
- // Use the first entry if the date requested is before the beginning of the data.
- if (before < 0) {
- before = 0;
- }
- }
- this._lastIndex = before;
- interpolate(this, dates, this._samples, date, before, after, result);
- return result;
- };
- function compareLeapSecondDates(leapSecond, dateToFind) {
- return JulianDate.compare(leapSecond.julianDate, dateToFind);
- }
- function onDataReady(eop, eopData) {
- if (!defined(eopData.columnNames)) {
- throw new RuntimeError(
- "Error in loaded EOP data: The columnNames property is required."
- );
- }
- if (!defined(eopData.samples)) {
- throw new RuntimeError(
- "Error in loaded EOP data: The samples property is required."
- );
- }
- const dateColumn = eopData.columnNames.indexOf("modifiedJulianDateUtc");
- const xPoleWanderRadiansColumn = eopData.columnNames.indexOf(
- "xPoleWanderRadians"
- );
- const yPoleWanderRadiansColumn = eopData.columnNames.indexOf(
- "yPoleWanderRadians"
- );
- const ut1MinusUtcSecondsColumn = eopData.columnNames.indexOf(
- "ut1MinusUtcSeconds"
- );
- const xCelestialPoleOffsetRadiansColumn = eopData.columnNames.indexOf(
- "xCelestialPoleOffsetRadians"
- );
- const yCelestialPoleOffsetRadiansColumn = eopData.columnNames.indexOf(
- "yCelestialPoleOffsetRadians"
- );
- const taiMinusUtcSecondsColumn = eopData.columnNames.indexOf(
- "taiMinusUtcSeconds"
- );
- if (
- dateColumn < 0 ||
- xPoleWanderRadiansColumn < 0 ||
- yPoleWanderRadiansColumn < 0 ||
- ut1MinusUtcSecondsColumn < 0 ||
- xCelestialPoleOffsetRadiansColumn < 0 ||
- yCelestialPoleOffsetRadiansColumn < 0 ||
- taiMinusUtcSecondsColumn < 0
- ) {
- throw new RuntimeError(
- "Error in loaded EOP data: The columnNames property must include modifiedJulianDateUtc, xPoleWanderRadians, yPoleWanderRadians, ut1MinusUtcSeconds, xCelestialPoleOffsetRadians, yCelestialPoleOffsetRadians, and taiMinusUtcSeconds columns"
- );
- }
- const samples = (eop._samples = eopData.samples);
- const dates = (eop._dates = []);
- eop._dateColumn = dateColumn;
- eop._xPoleWanderRadiansColumn = xPoleWanderRadiansColumn;
- eop._yPoleWanderRadiansColumn = yPoleWanderRadiansColumn;
- eop._ut1MinusUtcSecondsColumn = ut1MinusUtcSecondsColumn;
- eop._xCelestialPoleOffsetRadiansColumn = xCelestialPoleOffsetRadiansColumn;
- eop._yCelestialPoleOffsetRadiansColumn = yCelestialPoleOffsetRadiansColumn;
- eop._taiMinusUtcSecondsColumn = taiMinusUtcSecondsColumn;
- eop._columnCount = eopData.columnNames.length;
- eop._lastIndex = undefined;
- let lastTaiMinusUtc;
- const addNewLeapSeconds = eop._addNewLeapSeconds;
- // Convert the ISO8601 dates to JulianDates.
- for (let i = 0, len = samples.length; i < len; i += eop._columnCount) {
- const mjd = samples[i + dateColumn];
- const taiMinusUtc = samples[i + taiMinusUtcSecondsColumn];
- const day = mjd + TimeConstants.MODIFIED_JULIAN_DATE_DIFFERENCE;
- const date = new JulianDate(day, taiMinusUtc, TimeStandard.TAI);
- dates.push(date);
- if (addNewLeapSeconds) {
- if (taiMinusUtc !== lastTaiMinusUtc && defined(lastTaiMinusUtc)) {
- // We crossed a leap second boundary, so add the leap second
- // if it does not already exist.
- const leapSeconds = JulianDate.leapSeconds;
- const leapSecondIndex = binarySearch(
- leapSeconds,
- date,
- compareLeapSecondDates
- );
- if (leapSecondIndex < 0) {
- const leapSecond = new LeapSecond(date, taiMinusUtc);
- leapSeconds.splice(~leapSecondIndex, 0, leapSecond);
- }
- }
- lastTaiMinusUtc = taiMinusUtc;
- }
- }
- }
- function fillResultFromIndex(eop, samples, index, columnCount, result) {
- const start = index * columnCount;
- result.xPoleWander = samples[start + eop._xPoleWanderRadiansColumn];
- result.yPoleWander = samples[start + eop._yPoleWanderRadiansColumn];
- result.xPoleOffset = samples[start + eop._xCelestialPoleOffsetRadiansColumn];
- result.yPoleOffset = samples[start + eop._yCelestialPoleOffsetRadiansColumn];
- result.ut1MinusUtc = samples[start + eop._ut1MinusUtcSecondsColumn];
- }
- function linearInterp(dx, y1, y2) {
- return y1 + dx * (y2 - y1);
- }
- function interpolate(eop, dates, samples, date, before, after, result) {
- const columnCount = eop._columnCount;
- // First check the bounds on the EOP data
- // If we are after the bounds of the data, return zeros.
- // The 'before' index should never be less than zero.
- if (after > dates.length - 1) {
- result.xPoleWander = 0;
- result.yPoleWander = 0;
- result.xPoleOffset = 0;
- result.yPoleOffset = 0;
- result.ut1MinusUtc = 0;
- return result;
- }
- const beforeDate = dates[before];
- const afterDate = dates[after];
- if (beforeDate.equals(afterDate) || date.equals(beforeDate)) {
- fillResultFromIndex(eop, samples, before, columnCount, result);
- return result;
- } else if (date.equals(afterDate)) {
- fillResultFromIndex(eop, samples, after, columnCount, result);
- return result;
- }
- const factor =
- JulianDate.secondsDifference(date, beforeDate) /
- JulianDate.secondsDifference(afterDate, beforeDate);
- const startBefore = before * columnCount;
- const startAfter = after * columnCount;
- // Handle UT1 leap second edge case
- let beforeUt1MinusUtc = samples[startBefore + eop._ut1MinusUtcSecondsColumn];
- let afterUt1MinusUtc = samples[startAfter + eop._ut1MinusUtcSecondsColumn];
- const offsetDifference = afterUt1MinusUtc - beforeUt1MinusUtc;
- if (offsetDifference > 0.5 || offsetDifference < -0.5) {
- // The absolute difference between the values is more than 0.5, so we may have
- // crossed a leap second. Check if this is the case and, if so, adjust the
- // afterValue to account for the leap second. This way, our interpolation will
- // produce reasonable results.
- const beforeTaiMinusUtc =
- samples[startBefore + eop._taiMinusUtcSecondsColumn];
- const afterTaiMinusUtc =
- samples[startAfter + eop._taiMinusUtcSecondsColumn];
- if (beforeTaiMinusUtc !== afterTaiMinusUtc) {
- if (afterDate.equals(date)) {
- // If we are at the end of the leap second interval, take the second value
- // Otherwise, the interpolation below will yield the wrong side of the
- // discontinuity
- // At the end of the leap second, we need to start accounting for the jump
- beforeUt1MinusUtc = afterUt1MinusUtc;
- } else {
- // Otherwise, remove the leap second so that the interpolation is correct
- afterUt1MinusUtc -= afterTaiMinusUtc - beforeTaiMinusUtc;
- }
- }
- }
- result.xPoleWander = linearInterp(
- factor,
- samples[startBefore + eop._xPoleWanderRadiansColumn],
- samples[startAfter + eop._xPoleWanderRadiansColumn]
- );
- result.yPoleWander = linearInterp(
- factor,
- samples[startBefore + eop._yPoleWanderRadiansColumn],
- samples[startAfter + eop._yPoleWanderRadiansColumn]
- );
- result.xPoleOffset = linearInterp(
- factor,
- samples[startBefore + eop._xCelestialPoleOffsetRadiansColumn],
- samples[startAfter + eop._xCelestialPoleOffsetRadiansColumn]
- );
- result.yPoleOffset = linearInterp(
- factor,
- samples[startBefore + eop._yCelestialPoleOffsetRadiansColumn],
- samples[startAfter + eop._yCelestialPoleOffsetRadiansColumn]
- );
- result.ut1MinusUtc = linearInterp(
- factor,
- beforeUt1MinusUtc,
- afterUt1MinusUtc
- );
- return result;
- }
- export default EarthOrientationParameters;
|