import binarySearch from "../Core/binarySearch.js"; import Check from "../Core/Check.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import DeveloperError from "../Core/DeveloperError.js"; import Event from "../Core/Event.js"; import ExtrapolationType from "../Core/ExtrapolationType.js"; import JulianDate from "../Core/JulianDate.js"; import LinearApproximation from "../Core/LinearApproximation.js"; const PackableNumber = { packedLength: 1, pack: function (value, array, startingIndex) { startingIndex = defaultValue(startingIndex, 0); array[startingIndex] = value; }, unpack: function (array, startingIndex, result) { startingIndex = defaultValue(startingIndex, 0); return array[startingIndex]; }, }; //We can't use splice for inserting new elements because function apply can't handle //a huge number of arguments. See https://code.google.com/p/chromium/issues/detail?id=56588 function arrayInsert(array, startIndex, items) { let i; const arrayLength = array.length; const itemsLength = items.length; const newLength = arrayLength + itemsLength; array.length = newLength; if (arrayLength !== startIndex) { let q = arrayLength - 1; for (i = newLength - 1; i >= startIndex; i--) { array[i] = array[q--]; } } for (i = 0; i < itemsLength; i++) { array[startIndex++] = items[i]; } } function convertDate(date, epoch) { if (date instanceof JulianDate) { return date; } if (typeof date === "string") { return JulianDate.fromIso8601(date); } return JulianDate.addSeconds(epoch, date, new JulianDate()); } const timesSpliceArgs = []; const valuesSpliceArgs = []; function mergeNewSamples(epoch, times, values, newData, packedLength) { let newDataIndex = 0; let i; let prevItem; let timesInsertionPoint; let valuesInsertionPoint; let currentTime; let nextTime; while (newDataIndex < newData.length) { currentTime = convertDate(newData[newDataIndex], epoch); timesInsertionPoint = binarySearch(times, currentTime, JulianDate.compare); let timesSpliceArgsCount = 0; let valuesSpliceArgsCount = 0; if (timesInsertionPoint < 0) { //Doesn't exist, insert as many additional values as we can. timesInsertionPoint = ~timesInsertionPoint; valuesInsertionPoint = timesInsertionPoint * packedLength; prevItem = undefined; nextTime = times[timesInsertionPoint]; while (newDataIndex < newData.length) { currentTime = convertDate(newData[newDataIndex], epoch); if ( (defined(prevItem) && JulianDate.compare(prevItem, currentTime) >= 0) || (defined(nextTime) && JulianDate.compare(currentTime, nextTime) >= 0) ) { break; } timesSpliceArgs[timesSpliceArgsCount++] = currentTime; newDataIndex = newDataIndex + 1; for (i = 0; i < packedLength; i++) { valuesSpliceArgs[valuesSpliceArgsCount++] = newData[newDataIndex]; newDataIndex = newDataIndex + 1; } prevItem = currentTime; } if (timesSpliceArgsCount > 0) { valuesSpliceArgs.length = valuesSpliceArgsCount; arrayInsert(values, valuesInsertionPoint, valuesSpliceArgs); timesSpliceArgs.length = timesSpliceArgsCount; arrayInsert(times, timesInsertionPoint, timesSpliceArgs); } } else { //Found an exact match for (i = 0; i < packedLength; i++) { newDataIndex++; values[timesInsertionPoint * packedLength + i] = newData[newDataIndex]; } newDataIndex++; } } } /** * A {@link Property} whose value is interpolated for a given time from the * provided set of samples and specified interpolation algorithm and degree. * @alias SampledProperty * @constructor * * @param {number|Packable} type The type of property. * @param {Packable[]} [derivativeTypes] When supplied, indicates that samples will contain derivative information of the specified types. * * * @example * //Create a linearly interpolated Cartesian2 * const property = new Cesium.SampledProperty(Cesium.Cartesian2); * * //Populate it with data * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:00:00.00Z'), new Cesium.Cartesian2(0, 0)); * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-02T00:00:00.00Z'), new Cesium.Cartesian2(4, 7)); * * //Retrieve an interpolated value * const result = property.getValue(Cesium.JulianDate.fromIso8601('2012-08-01T12:00:00.00Z')); * * @example * //Create a simple numeric SampledProperty that uses third degree Hermite Polynomial Approximation * const property = new Cesium.SampledProperty(Number); * property.setInterpolationOptions({ * interpolationDegree : 3, * interpolationAlgorithm : Cesium.HermitePolynomialApproximation * }); * * //Populate it with data * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:00:00.00Z'), 1.0); * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:01:00.00Z'), 6.0); * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:02:00.00Z'), 12.0); * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:03:30.00Z'), 5.0); * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:06:30.00Z'), 2.0); * * //Samples can be added in any order. * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:00:30.00Z'), 6.2); * * //Retrieve an interpolated value * const result = property.getValue(Cesium.JulianDate.fromIso8601('2012-08-01T00:02:34.00Z')); * * @see SampledPositionProperty */ function SampledProperty(type, derivativeTypes) { //>>includeStart('debug', pragmas.debug); Check.defined("type", type); //>>includeEnd('debug'); let innerType = type; if (innerType === Number) { innerType = PackableNumber; } let packedLength = innerType.packedLength; let packedInterpolationLength = defaultValue( innerType.packedInterpolationLength, packedLength ); let inputOrder = 0; let innerDerivativeTypes; if (defined(derivativeTypes)) { const length = derivativeTypes.length; innerDerivativeTypes = new Array(length); for (let i = 0; i < length; i++) { let derivativeType = derivativeTypes[i]; if (derivativeType === Number) { derivativeType = PackableNumber; } const derivativePackedLength = derivativeType.packedLength; packedLength += derivativePackedLength; packedInterpolationLength += defaultValue( derivativeType.packedInterpolationLength, derivativePackedLength ); innerDerivativeTypes[i] = derivativeType; } inputOrder = length; } this._type = type; this._innerType = innerType; this._interpolationDegree = 1; this._interpolationAlgorithm = LinearApproximation; this._numberOfPoints = 0; this._times = []; this._values = []; this._xTable = []; this._yTable = []; this._packedLength = packedLength; this._packedInterpolationLength = packedInterpolationLength; this._updateTableLength = true; this._interpolationResult = new Array(packedInterpolationLength); this._definitionChanged = new Event(); this._derivativeTypes = derivativeTypes; this._innerDerivativeTypes = innerDerivativeTypes; this._inputOrder = inputOrder; this._forwardExtrapolationType = ExtrapolationType.NONE; this._forwardExtrapolationDuration = 0; this._backwardExtrapolationType = ExtrapolationType.NONE; this._backwardExtrapolationDuration = 0; } Object.defineProperties(SampledProperty.prototype, { /** * Gets a value indicating if this property is constant. A property is considered * constant if getValue always returns the same result for the current definition. * @memberof SampledProperty.prototype * * @type {boolean} * @readonly */ isConstant: { get: function () { return this._values.length === 0; }, }, /** * Gets the event that is raised whenever the definition of this property changes. * The definition is considered to have changed if a call to getValue would return * a different result for the same time. * @memberof SampledProperty.prototype * * @type {Event} * @readonly */ definitionChanged: { get: function () { return this._definitionChanged; }, }, /** * Gets the type of property. * @memberof SampledProperty.prototype * @type {*} */ type: { get: function () { return this._type; }, }, /** * Gets the derivative types used by this property. * @memberof SampledProperty.prototype * @type {Packable[]} */ derivativeTypes: { get: function () { return this._derivativeTypes; }, }, /** * Gets the degree of interpolation to perform when retrieving a value. * @memberof SampledProperty.prototype * @type {number} * @default 1 */ interpolationDegree: { get: function () { return this._interpolationDegree; }, }, /** * Gets the interpolation algorithm to use when retrieving a value. * @memberof SampledProperty.prototype * @type {InterpolationAlgorithm} * @default LinearApproximation */ interpolationAlgorithm: { get: function () { return this._interpolationAlgorithm; }, }, /** * Gets or sets the type of extrapolation to perform when a value * is requested at a time after any available samples. * @memberof SampledProperty.prototype * @type {ExtrapolationType} * @default ExtrapolationType.NONE */ forwardExtrapolationType: { get: function () { return this._forwardExtrapolationType; }, set: function (value) { if (this._forwardExtrapolationType !== value) { this._forwardExtrapolationType = value; this._definitionChanged.raiseEvent(this); } }, }, /** * Gets or sets the amount of time to extrapolate forward before * the property becomes undefined. A value of 0 will extrapolate forever. * @memberof SampledProperty.prototype * @type {number} * @default 0 */ forwardExtrapolationDuration: { get: function () { return this._forwardExtrapolationDuration; }, set: function (value) { if (this._forwardExtrapolationDuration !== value) { this._forwardExtrapolationDuration = value; this._definitionChanged.raiseEvent(this); } }, }, /** * Gets or sets the type of extrapolation to perform when a value * is requested at a time before any available samples. * @memberof SampledProperty.prototype * @type {ExtrapolationType} * @default ExtrapolationType.NONE */ backwardExtrapolationType: { get: function () { return this._backwardExtrapolationType; }, set: function (value) { if (this._backwardExtrapolationType !== value) { this._backwardExtrapolationType = value; this._definitionChanged.raiseEvent(this); } }, }, /** * Gets or sets the amount of time to extrapolate backward * before the property becomes undefined. A value of 0 will extrapolate forever. * @memberof SampledProperty.prototype * @type {number} * @default 0 */ backwardExtrapolationDuration: { get: function () { return this._backwardExtrapolationDuration; }, set: function (value) { if (this._backwardExtrapolationDuration !== value) { this._backwardExtrapolationDuration = value; this._definitionChanged.raiseEvent(this); } }, }, }); /** * Gets the value of the property at the provided time. * * @param {JulianDate} time The time for which to retrieve the value. * @param {object} [result] The object to store the value into, if omitted, a new instance is created and returned. * @returns {object} The modified result parameter or a new instance if the result parameter was not supplied. */ SampledProperty.prototype.getValue = function (time, result) { //>>includeStart('debug', pragmas.debug); Check.defined("time", time); //>>includeEnd('debug'); const times = this._times; const timesLength = times.length; if (timesLength === 0) { return undefined; } let timeout; const innerType = this._innerType; const values = this._values; let index = binarySearch(times, time, JulianDate.compare); if (index < 0) { index = ~index; if (index === 0) { const startTime = times[index]; timeout = this._backwardExtrapolationDuration; if ( this._backwardExtrapolationType === ExtrapolationType.NONE || (timeout !== 0 && JulianDate.secondsDifference(startTime, time) > timeout) ) { return undefined; } if (this._backwardExtrapolationType === ExtrapolationType.HOLD) { return innerType.unpack(values, 0, result); } } if (index >= timesLength) { index = timesLength - 1; const endTime = times[index]; timeout = this._forwardExtrapolationDuration; if ( this._forwardExtrapolationType === ExtrapolationType.NONE || (timeout !== 0 && JulianDate.secondsDifference(time, endTime) > timeout) ) { return undefined; } if (this._forwardExtrapolationType === ExtrapolationType.HOLD) { index = timesLength - 1; return innerType.unpack(values, index * innerType.packedLength, result); } } const xTable = this._xTable; const yTable = this._yTable; const interpolationAlgorithm = this._interpolationAlgorithm; const packedInterpolationLength = this._packedInterpolationLength; const inputOrder = this._inputOrder; if (this._updateTableLength) { this._updateTableLength = false; const numberOfPoints = Math.min( interpolationAlgorithm.getRequiredDataPoints( this._interpolationDegree, inputOrder ), timesLength ); if (numberOfPoints !== this._numberOfPoints) { this._numberOfPoints = numberOfPoints; xTable.length = numberOfPoints; yTable.length = numberOfPoints * packedInterpolationLength; } } const degree = this._numberOfPoints - 1; if (degree < 1) { return undefined; } let firstIndex = 0; let lastIndex = timesLength - 1; const pointsInCollection = lastIndex - firstIndex + 1; if (pointsInCollection >= degree + 1) { let computedFirstIndex = index - ((degree / 2) | 0) - 1; if (computedFirstIndex < firstIndex) { computedFirstIndex = firstIndex; } let computedLastIndex = computedFirstIndex + degree; if (computedLastIndex > lastIndex) { computedLastIndex = lastIndex; computedFirstIndex = computedLastIndex - degree; if (computedFirstIndex < firstIndex) { computedFirstIndex = firstIndex; } } firstIndex = computedFirstIndex; lastIndex = computedLastIndex; } const length = lastIndex - firstIndex + 1; // Build the tables for (let i = 0; i < length; ++i) { xTable[i] = JulianDate.secondsDifference( times[firstIndex + i], times[lastIndex] ); } if (!defined(innerType.convertPackedArrayForInterpolation)) { let destinationIndex = 0; const packedLength = this._packedLength; let sourceIndex = firstIndex * packedLength; const stop = (lastIndex + 1) * packedLength; while (sourceIndex < stop) { yTable[destinationIndex] = values[sourceIndex]; sourceIndex++; destinationIndex++; } } else { innerType.convertPackedArrayForInterpolation( values, firstIndex, lastIndex, yTable ); } // Interpolate! const x = JulianDate.secondsDifference(time, times[lastIndex]); let interpolationResult; if (inputOrder === 0 || !defined(interpolationAlgorithm.interpolate)) { interpolationResult = interpolationAlgorithm.interpolateOrderZero( x, xTable, yTable, packedInterpolationLength, this._interpolationResult ); } else { const yStride = Math.floor(packedInterpolationLength / (inputOrder + 1)); interpolationResult = interpolationAlgorithm.interpolate( x, xTable, yTable, yStride, inputOrder, inputOrder, this._interpolationResult ); } if (!defined(innerType.unpackInterpolationResult)) { return innerType.unpack(interpolationResult, 0, result); } return innerType.unpackInterpolationResult( interpolationResult, values, firstIndex, lastIndex, result ); } return innerType.unpack(values, index * this._packedLength, result); }; /** * Sets the algorithm and degree to use when interpolating a value. * * @param {object} [options] Object with the following properties: * @param {InterpolationAlgorithm} [options.interpolationAlgorithm] The new interpolation algorithm. If undefined, the existing property will be unchanged. * @param {number} [options.interpolationDegree] The new interpolation degree. If undefined, the existing property will be unchanged. */ SampledProperty.prototype.setInterpolationOptions = function (options) { if (!defined(options)) { return; } let valuesChanged = false; const interpolationAlgorithm = options.interpolationAlgorithm; const interpolationDegree = options.interpolationDegree; if ( defined(interpolationAlgorithm) && this._interpolationAlgorithm !== interpolationAlgorithm ) { this._interpolationAlgorithm = interpolationAlgorithm; valuesChanged = true; } if ( defined(interpolationDegree) && this._interpolationDegree !== interpolationDegree ) { this._interpolationDegree = interpolationDegree; valuesChanged = true; } if (valuesChanged) { this._updateTableLength = true; this._definitionChanged.raiseEvent(this); } }; /** * Adds a new sample. * * @param {JulianDate} time The sample time. * @param {Packable} value The value at the provided time. * @param {Packable[]} [derivatives] The array of derivatives at the provided time. */ SampledProperty.prototype.addSample = function (time, value, derivatives) { const innerDerivativeTypes = this._innerDerivativeTypes; const hasDerivatives = defined(innerDerivativeTypes); //>>includeStart('debug', pragmas.debug); Check.defined("time", time); Check.defined("value", value); if (hasDerivatives) { Check.defined("derivatives", derivatives); } //>>includeEnd('debug'); const innerType = this._innerType; const data = []; data.push(time); innerType.pack(value, data, data.length); if (hasDerivatives) { const derivativesLength = innerDerivativeTypes.length; for (let x = 0; x < derivativesLength; x++) { innerDerivativeTypes[x].pack(derivatives[x], data, data.length); } } mergeNewSamples( undefined, this._times, this._values, data, this._packedLength ); this._updateTableLength = true; this._definitionChanged.raiseEvent(this); }; /** * Adds an array of samples. * * @param {JulianDate[]} times An array of JulianDate instances where each index is a sample time. * @param {Packable[]} values The array of values, where each value corresponds to the provided times index. * @param {Array[]} [derivativeValues] An array where each item is the array of derivatives at the equivalent time index. * * @exception {DeveloperError} times and values must be the same length. * @exception {DeveloperError} times and derivativeValues must be the same length. */ SampledProperty.prototype.addSamples = function ( times, values, derivativeValues ) { const innerDerivativeTypes = this._innerDerivativeTypes; const hasDerivatives = defined(innerDerivativeTypes); //>>includeStart('debug', pragmas.debug); Check.defined("times", times); Check.defined("values", values); if (times.length !== values.length) { throw new DeveloperError("times and values must be the same length."); } if ( hasDerivatives && (!defined(derivativeValues) || derivativeValues.length !== times.length) ) { throw new DeveloperError( "times and derivativeValues must be the same length." ); } //>>includeEnd('debug'); const innerType = this._innerType; const length = times.length; const data = []; for (let i = 0; i < length; i++) { data.push(times[i]); innerType.pack(values[i], data, data.length); if (hasDerivatives) { const derivatives = derivativeValues[i]; const derivativesLength = innerDerivativeTypes.length; for (let x = 0; x < derivativesLength; x++) { innerDerivativeTypes[x].pack(derivatives[x], data, data.length); } } } mergeNewSamples( undefined, this._times, this._values, data, this._packedLength ); this._updateTableLength = true; this._definitionChanged.raiseEvent(this); }; /** * Adds samples as a single packed array where each new sample is represented as a date, * followed by the packed representation of the corresponding value and derivatives. * * @param {number[]} packedSamples The array of packed samples. * @param {JulianDate} [epoch] If any of the dates in packedSamples are numbers, they are considered an offset from this epoch, in seconds. */ SampledProperty.prototype.addSamplesPackedArray = function ( packedSamples, epoch ) { //>>includeStart('debug', pragmas.debug); Check.defined("packedSamples", packedSamples); //>>includeEnd('debug'); mergeNewSamples( epoch, this._times, this._values, packedSamples, this._packedLength ); this._updateTableLength = true; this._definitionChanged.raiseEvent(this); }; /** * Removes a sample at the given time, if present. * * @param {JulianDate} time The sample time. * @returns {boolean} true if a sample at time was removed, false otherwise. */ SampledProperty.prototype.removeSample = function (time) { //>>includeStart('debug', pragmas.debug); Check.defined("time", time); //>>includeEnd('debug'); const index = binarySearch(this._times, time, JulianDate.compare); if (index < 0) { return false; } removeSamples(this, index, 1); return true; }; function removeSamples(property, startIndex, numberToRemove) { const packedLength = property._packedLength; property._times.splice(startIndex, numberToRemove); property._values.splice( startIndex * packedLength, numberToRemove * packedLength ); property._updateTableLength = true; property._definitionChanged.raiseEvent(property); } /** * Removes all samples for the given time interval. * * @param {TimeInterval} time The time interval for which to remove all samples. */ SampledProperty.prototype.removeSamples = function (timeInterval) { //>>includeStart('debug', pragmas.debug); Check.defined("timeInterval", timeInterval); //>>includeEnd('debug'); const times = this._times; let startIndex = binarySearch(times, timeInterval.start, JulianDate.compare); if (startIndex < 0) { startIndex = ~startIndex; } else if (!timeInterval.isStartIncluded) { ++startIndex; } let stopIndex = binarySearch(times, timeInterval.stop, JulianDate.compare); if (stopIndex < 0) { stopIndex = ~stopIndex; } else if (timeInterval.isStopIncluded) { ++stopIndex; } removeSamples(this, startIndex, stopIndex - startIndex); }; /** * Compares this property to the provided property and returns * true if they are equal, false otherwise. * * @param {Property} [other] The other property. * @returns {boolean} true if left and right are equal, false otherwise. */ SampledProperty.prototype.equals = function (other) { if (this === other) { return true; } if (!defined(other)) { return false; } if ( this._type !== other._type || // this._interpolationDegree !== other._interpolationDegree || // this._interpolationAlgorithm !== other._interpolationAlgorithm ) { return false; } const derivativeTypes = this._derivativeTypes; const hasDerivatives = defined(derivativeTypes); const otherDerivativeTypes = other._derivativeTypes; const otherHasDerivatives = defined(otherDerivativeTypes); if (hasDerivatives !== otherHasDerivatives) { return false; } let i; let length; if (hasDerivatives) { length = derivativeTypes.length; if (length !== otherDerivativeTypes.length) { return false; } for (i = 0; i < length; i++) { if (derivativeTypes[i] !== otherDerivativeTypes[i]) { return false; } } } const times = this._times; const otherTimes = other._times; length = times.length; if (length !== otherTimes.length) { return false; } for (i = 0; i < length; i++) { if (!JulianDate.equals(times[i], otherTimes[i])) { return false; } } const values = this._values; const otherValues = other._values; length = values.length; //Since time lengths are equal, values length and other length are guaranteed to be equal. for (i = 0; i < length; i++) { if (values[i] !== otherValues[i]) { return false; } } return true; }; //Exposed for testing. SampledProperty._mergeNewSamples = mergeNewSamples; export default SampledProperty;