CRD.java
/* Copyright 2002-2022 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.files.ilrs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.ChronologicalComparator;
import org.orekit.time.TimeStamped;
import org.orekit.utils.ImmutableTimeStampedCache;
/**
* This class stores all the information of the Consolidated laser ranging Data Format (CRD) parsed
* by CRDParser. It contains the header and a list of data records.
* @author Bryan Cazabonne
* @since 10.3
*/
public class CRD {
/** List of comments contained in the file. */
private List<String> comments;
/** List of data blocks contain in the CDR file. */
private List<CRDDataBlock> dataBlocks;
/**
* Constructor.
*/
public CRD() {
// Initialise empty lists
this.comments = new ArrayList<>();
this.dataBlocks = new ArrayList<>();
}
/**
* Add a data block to the current list of data blocks.
* @param dataBlock data block to add
*/
public void addDataBlock(final CRDDataBlock dataBlock) {
dataBlocks.add(dataBlock);
}
/**
* Get the comments contained in the file.
* @return the comments contained in the file
*/
public List<String> getComments() {
return comments;
}
/**
* Get the data blocks contain in the file.
* @return the data blocks contain in the file
*/
public List<CRDDataBlock> getDataBlocks() {
return Collections.unmodifiableList(dataBlocks);
}
/**
* Data block containing a set of data contain in the CRD file.
* <p>
* A data block consists of a header, configuration data and
* recorded data (range, angles, meteorological, etc.).
* </p>
*/
public static class CRDDataBlock {
/** Data block header. */
private CRDHeader header;
/** Configuration record. */
private CRDConfiguration configurationRecords;
/** Range records. */
private List<RangeMeasurement> rangeData;
/** Meteorological records. */
private final SortedSet<MeteorologicalMeasurement> meteoData;
/** Pointing angles records. */
private List<AnglesMeasurement> anglesData;
/**
* Constructor.
*/
public CRDDataBlock() {
// Initialise empty lists
this.rangeData = new ArrayList<>();
this.meteoData = new TreeSet<>(new ChronologicalComparator());
this.anglesData = new ArrayList<>();
}
/**
* Get the header of the current data block.
* @return the header of the current data block
*/
public CRDHeader getHeader() {
return header;
}
/**
* Set the header for the current data block.
* @param header the header to set
*/
public void setHeader(final CRDHeader header) {
this.header = header;
}
/**
* Get the system configuration records.
* @return the system configuration records
*/
public CRDConfiguration getConfigurationRecords() {
return configurationRecords;
}
/**
* Set the configuration records for the current data block.
* @param configurationRecords the configuration records to set
*/
public void setConfigurationRecords(final CRDConfiguration configurationRecords) {
this.configurationRecords = configurationRecords;
}
/**
* Add an entry to the list of range data.
* @param range entry to add
*/
public void addRangeData(final RangeMeasurement range) {
rangeData.add(range);
}
/**
* Add an entry to the list of meteorological data.
* @param meteorologicalMeasurement entry to add
*/
public void addMeteoData(final MeteorologicalMeasurement meteorologicalMeasurement) {
meteoData.add(meteorologicalMeasurement);
}
/**
* Add an entry to the list of angles data.
* @param angles entry to add
*/
public void addAnglesData(final AnglesMeasurement angles) {
anglesData.add(angles);
}
/**
* Get the range data for the data block.
* @return an unmodifiable list of range data
*/
public List<RangeMeasurement> getRangeData() {
return Collections.unmodifiableList(rangeData);
}
/**
* Get the angles data for the data block.
* @return an unmodifiable list of angles data
*/
public List<AnglesMeasurement> getAnglesData() {
return Collections.unmodifiableList(anglesData);
}
/**
* Get the meteorological data for the data block.
* @return an unmodifiable list of meteorological data
*/
public Meteo getMeteoData() {
return new Meteo(meteoData);
}
}
/** Range record. */
public static class RangeMeasurement implements TimeStamped {
/** Data epoch. */
private AbsoluteDate date;
/** Time of flight [s]. */
private final double timeOfFlight;
/** Time event reference indicator.
* 0 = ground receive time (at SRP) (two-way)
* 1 = spacecraft bounce time (two-way)
* 2 = ground transmit time (at SRP) (two-way)
* 3 = spacecraft receive time (one-way)
* 4 = spacecraft transmit time (one-way)
* 5 = ground transmit time (at SRP) and spacecraft receive time (one-way)
* 6 = spacecraft transmit time and ground receive time (at SRP) (one-way)
* Currently, only 1 and 2 are used for laser ranging data.
*/
private final int epochEvent;
/** Signal to noise ration. */
private final double snr;
/**
* Constructor.
* @param date data epoch
* @param timeOfFlight time of flight in seconds
* @param epochEvent indicates the time event reference
*/
public RangeMeasurement(final AbsoluteDate date,
final double timeOfFlight,
final int epochEvent) {
this(date, timeOfFlight, epochEvent, Double.NaN);
}
/**
* Constructor.
* @param date data epoch
* @param timeOfFlight time of flight in seconds
* @param epochEvent indicates the time event reference
* @param snr signal to noise ratio (can be Double.NaN if unkonwn)
*/
public RangeMeasurement(final AbsoluteDate date,
final double timeOfFlight,
final int epochEvent, final double snr) {
this.date = date;
this.timeOfFlight = timeOfFlight;
this.epochEvent = epochEvent;
this.snr = snr;
}
/**
* Get the time-of-flight.
* @return the time-of-flight in seconds
*/
public double getTimeOfFlight() {
return timeOfFlight;
}
/**
* Get the indicator for the time event reference.
* <ul>
* <li>0 = ground receive time (at SRP) (two-way)</li>
* <li>1 = spacecraft bounce time (two-way)</li>
* <li>2 = ground transmit time (at SRP) (two-way)</li>
* <li>3 = spacecraft receive time (one-way)</li>
* <li>4 = spacecraft transmit time (one-way)</li>
* <li>5 = ground transmit time (at SRP) and spacecraft receive time (one-way)</li>
* <li>6 = spacecraft transmit time and ground receive time (at SRP) (one-way)</li>
* </ul>
* Currently, only 1 and 2 are used for laser ranging data
* @return the indicator for the time event reference
*/
public int getEpochEvent() {
return epochEvent;
}
/**
* Get the signal to noise ratio.
* @return the signal to noise ratio
*/
public double getSnr() {
return snr;
}
/** {@inheritDoc} */
@Override
public AbsoluteDate getDate() {
return date;
}
}
/** This data record contains a minimal set of meteorological data. */
public static class MeteorologicalMeasurement implements TimeStamped {
/** Data epoch. */
private AbsoluteDate date;
/** Surface pressure [bar]. */
private final double pressure;
/** Surface temperature [K]. */
private final double temperature;
/** Relative humidity at the surface [%]. */
private final double humidity;
/**
* Constructor.
* @param date data epoch
* @param pressure the surface pressure in bars
* @param temperature the surface temperature in degrees Kelvin
* @param humidity the relative humidity at the surface in percents
*/
public MeteorologicalMeasurement(final AbsoluteDate date,
final double pressure, final double temperature,
final double humidity) {
this.date = date;
this.pressure = pressure;
this.temperature = temperature;
this.humidity = humidity;
}
/**
* Get the surface pressure.
* @return the surface pressure in bars
*/
public double getPressure() {
return pressure;
}
/**
* Get the surface temperature.
* @return the surface temperature in degrees Kelvin
*/
public double getTemperature() {
return temperature;
}
/**
* Get the relative humidity at the surface.
* @return the relative humidity at the surface in percents
*/
public double getHumidity() {
return humidity;
}
/** {@inheritDoc} */
@Override
public AbsoluteDate getDate() {
return date;
}
}
/** Pointing angles record. */
public static class AnglesMeasurement implements TimeStamped {
/** Data epoch. */
private AbsoluteDate date;
/** Azimuth [rad]. */
private final double azimuth;
/** Elevation [rad]. */
private final double elevation;
/** Direction flag (0 = transmit & receive ; 1 = transmit ; 2 = receive). */
private final int directionFlag;
/** Angle origin indicator.
* 0 = unknown
* 1 = computed
* 2 = commanded (from predictions)
* 3 = measured (from encoders)
*/
private final int originIndicator;
/** Refraction corrected. */
private final boolean refractionCorrected;
/** Azimuth rate [rad/sec]. */
private final double azimuthRate;
/** Elevation rate [rad/sec]. */
private final double elevationRate;
/**
* Constructor.
* @param date data epoch
* @param azimuth azimuth angle in radians
* @param elevation elevation angle in radians
* @param directionFlag direction flag
* @param originIndicator angle origin indicator
* @param refractionCorrected flag to indicate if the refraction is corrected
* @param azimuthRate azimuth rate in radians per second (equal to Double.NaN if unknown)
* @param elevationRate elevation rate in radians per second (equal to Double.NaN if unknown)
*/
public AnglesMeasurement(final AbsoluteDate date, final double azimuth,
final double elevation, final int directionFlag,
final int originIndicator,
final boolean refractionCorrected,
final double azimuthRate, final double elevationRate) {
this.date = date;
this.azimuth = azimuth;
this.elevation = elevation;
this.directionFlag = directionFlag;
this.originIndicator = originIndicator;
this.refractionCorrected = refractionCorrected;
this.azimuthRate = azimuthRate;
this.elevationRate = elevationRate;
}
/**
* Get the azimuth angle.
* @return the azimuth angle in radians
*/
public double getAzimuth() {
return azimuth;
}
/**
* Get the elevation angle.
* @return the elevation angle in radians
*/
public double getElevation() {
return elevation;
}
/**
* Get the direction flag (0 = transmit & receive ; 1 = transmit ; 2 = receive).
* @return the direction flag
*/
public int getDirectionFlag() {
return directionFlag;
}
/**
* Get the angle origin indicator.
* <p>
* 0 = unknown;
* 1 = computed;
* 2 = commanded (from predictions);
* 3 = measured (from encoders)
* </p>
* @return the angle origin indicator
*/
public int getOriginIndicator() {
return originIndicator;
}
/**
* Get the flag indicating if the refraction is corrected.
* @return true if refraction is corrected
*/
public boolean isRefractionCorrected() {
return refractionCorrected;
}
/**
* Get the azimuth rate.
* <p>
* Is equal to Double.NaN if the value is unknown.
* </p>
* @return the azimuth rate in radians per second
*/
public double getAzimuthRate() {
return azimuthRate;
}
/**
* Get the elevation rate.
* <p>
* Is equal to Double.NaN if the value is unknown.
* </p>
* @return the elevation rate in radians per second
*/
public double getElevationRate() {
return elevationRate;
}
/** {@inheritDoc} */
@Override
public AbsoluteDate getDate() {
return date;
}
}
/** Meteorological data. */
public static class Meteo {
/** Number of neighbors for meteo data interpolation. */
private static final int N_NEIGHBORS = 2;
/** First available date. */
private final AbsoluteDate firstDate;
/** Last available date. */
private final AbsoluteDate lastDate;
/** Previous set of meteorological parameters. */
private transient MeteorologicalMeasurement previousParam;
/** Next set of solar meteorological parameters. */
private transient MeteorologicalMeasurement nextParam;
/** List of meteo data. */
private final transient ImmutableTimeStampedCache<MeteorologicalMeasurement> meteo;
/**
* Constructor.
* @param meteoData list of meteo data
*/
public Meteo(final SortedSet<MeteorologicalMeasurement> meteoData) {
// Size
final int neighborsSize = (meteoData.size() < 2) ? meteoData.size() : N_NEIGHBORS;
// Check neighbors size
if (neighborsSize == 0) {
// Meteo data -> empty cache
this.meteo = ImmutableTimeStampedCache.emptyCache();
// Null epochs (will ne be used)
this.firstDate = null;
this.lastDate = null;
} else {
// Meteo data
this.meteo = new ImmutableTimeStampedCache<MeteorologicalMeasurement>(neighborsSize, meteoData);
// Initialize first and last available dates
this.firstDate = meteoData.first().getDate();
this.lastDate = meteoData.last().getDate();
}
}
/** Get an unmodifiable view of the tabulated meteorological data.
* @return unmodifiable view of the tabulated meteorological data
* @since 11.0
*/
public List<MeteorologicalMeasurement> getData() {
return meteo.getAll();
}
/**
* Get the meteorological parameters at a given date.
* @param date date when user wants the meteorological parameters
* @return the meteorological parameters at date (can be null if
* meteorological data are empty).
*/
public MeteorologicalMeasurement getMeteo(final AbsoluteDate date) {
// Check if meteorological data are available
if (meteo.getNeighborsSize() == 0) {
return null;
}
// Interpolating two neighboring meteorological parameters
bracketDate(date);
if (date.durationFrom(firstDate) <= 0 || date.durationFrom(lastDate) > 0) {
// Date is outside file range
return previousParam;
} else {
// Perform interpolations
final double pressure = getLinearInterpolation(date, previousParam.getPressure(), nextParam.getPressure());
final double temperature = getLinearInterpolation(date, previousParam.getTemperature(), nextParam.getTemperature());
final double humidity = getLinearInterpolation(date, previousParam.getHumidity(), nextParam.getHumidity());
return new MeteorologicalMeasurement(date, pressure, temperature, humidity);
}
}
/**
* Find the data bracketing a specified date.
* @param date date to bracket
*/
private void bracketDate(final AbsoluteDate date) {
// don't search if the cached selection is fine
if (previousParam != null &&
date.durationFrom(previousParam.getDate()) > 0 &&
date.durationFrom(nextParam.getDate()) <= 0) {
return;
}
// Initialize previous and next parameters
if (date.durationFrom(firstDate) <= 0) {
// Current date is before the first date
previousParam = meteo.getEarliest();
nextParam = previousParam;
} else if (date.durationFrom(lastDate) > 0) {
// Current date is after the last date
previousParam = meteo.getLatest();
nextParam = previousParam;
} else {
// Current date is between first and last date
final List<MeteorologicalMeasurement> neighbors = meteo.getNeighbors(date).collect(Collectors.toList());
previousParam = neighbors.get(0);
nextParam = neighbors.get(1);
}
}
/**
* Performs a linear interpolation between two values The weights are computed
* from the time delta between previous date, current date, next date.
* @param date the current date
* @param previousValue the value at previous date
* @param nextValue the value at next date
* @return the value interpolated for the current date
*/
private double getLinearInterpolation(final AbsoluteDate date,
final double previousValue,
final double nextValue) {
// Perform a linear interpolation
final AbsoluteDate previousDate = previousParam.getDate();
final AbsoluteDate currentDate = nextParam.getDate();
final double dt = currentDate.durationFrom(previousDate);
final double previousWeight = currentDate.durationFrom(date) / dt;
final double nextWeight = date.durationFrom(previousDate) / dt;
// Returns the data interpolated at the date
return previousValue * previousWeight + nextValue * nextWeight;
}
}
}