/* Copyright 2002-2017 CS Systèmes d'Information
* Licensed to CS Systèmes d'Information (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 fr.cs.examples.bodies;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.hipparchus.util.FastMath;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitInternalError;
import org.orekit.errors.OrekitMessages;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.DateComponents;
import org.orekit.time.DateTimeComponents;
import org.orekit.time.TimeComponents;
import org.orekit.time.TimeScale;
import org.orekit.time.TimeScalesFactory;
import org.orekit.utils.Constants;
import fr.cs.examples.Autoconfiguration;
public class DEFile {
private static final int INPOP_DE_NUMBER = 100;
private static final int CONSTANTS_MAX_NUMBER = 400;
private static final int HEADER_LABEL_SIZE = 84;
private static final int HEADER_LABEL_1_OFFSET = 0;
private static final int HEADER_LABEL_2_OFFSET = HEADER_LABEL_1_OFFSET + HEADER_LABEL_SIZE;
private static final int HEADER_LABEL_3_OFFSET = HEADER_LABEL_2_OFFSET + HEADER_LABEL_SIZE;
private static final int HEADER_EPHEMERIS_TYPE_OFFSET = 2840;
private static final int HEADER_RECORD_SIZE_OFFSET = 2856;
private static final int HEADER_START_EPOCH_OFFSET = 2652;
private static final int HEADER_END_EPOCH_OFFSET = 2660;
private static final int HEADER_CONSTANTS_NAMES_OFFSET = 252;
private static final int HEADER_CONSTANTS_VALUES_OFFSET = 0;
private static final int DATA_START_RANGE_OFFSET = 0;
private static final int DATE_END_RANGE_OFFSET = 8;
private String inName;
private String outName;
private InputStream input;
private byte[] first;
private byte[] second;
private List<byte[]> selected;
private int recordSize;
private boolean bigEndian;
private int deNum;
private String label1;
private String label2;
private String label3;
private AbsoluteDate headerStartEpoch;
private AbsoluteDate headerFinalEpoch;
private TimeScale timeScale;
private final Map<String, Double> headerConstants;
/** Program entry point.
* @param args program arguments (unused here)
*/
public static void main(String[] args) {
try {
Autoconfiguration.configureOrekit();
String inName = null;
String outName = null;
List<String> constants = new ArrayList<String>();
boolean allConstants = false;
AbsoluteDate start = AbsoluteDate.PAST_INFINITY;
AbsoluteDate end = AbsoluteDate.FUTURE_INFINITY;
for (int i = 0; i < args.length; ++i) {
if ("-in".equals(args[i])) {
inName = args[++i];
} else if ("-help".equals(args[i])) {
displayUsage(System.out);
} else if ("-constant".equals(args[i])) {
constants.add(args[++i]);
} else if ("-all-constants".equals(args[i])) {
allConstants = true;
} else if ("-start".equals(args[i])) {
start = new AbsoluteDate(args[++i], TimeScalesFactory.getUTC());
} else if ("-end".equals(args[i])) {
end = new AbsoluteDate(args[++i], TimeScalesFactory.getUTC());
} else if ("-out".equals(args[i])) {
outName = args[++i];
} else {
System.err.println("unknown command line option \"" + args[i] + "\"");
displayUsage(System.err);
System.exit(1);
}
}
if (inName == null) {
displayUsage(System.err);
System.exit(1);
}
DEFile de = new DEFile(inName, outName);
de.processHeader();
System.out.println("header label 1 " + de.label1);
System.out.println("header label 2 " + de.label2);
System.out.println("header label 3 " + de.label3);
System.out.println("header start epoch " + de.headerStartEpoch.toString(de.timeScale) +
" (" + de.timeScale.getName() + ")");
System.out.println("header end epoch " + de.headerFinalEpoch.toString(de.timeScale) +
" (" + de.timeScale.getName() + ")");
for (String constant : constants) {
Double value = de.headerConstants.get(constant);
System.out.println(constant + " " + ((value == null) ? "not present" : value));
}
if (allConstants) {
for (Map.Entry<String,Double> entry : de.headerConstants.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
int processed = de.processData(start, end);
System.out.println("data records: " + processed);
if (outName != null) {
de.write();
System.out.println(outName + " file created with " +
de.selected.size() + " selected data records");
}
} catch (IOException ioe) {
ioe.printStackTrace(System.err);
} catch (OrekitException oe) {
oe.printStackTrace(System.err);
}
}
private static void displayUsage(final PrintStream stream) {
stream.print("usage: java DEFile");
stream.print(" -in filename");
stream.print(" [-help]");
stream.print(" [-constant name]");
stream.print(" [-all-constants]");
stream.print(" [-start date]");
stream.print(" [-end date]");
stream.print(" [-out filename]");
stream.println();
}
private DEFile(String inName, String outName) throws FileNotFoundException {
this.inName = inName;
this.outName = outName;
this.input = new FileInputStream(inName);
this.headerConstants = new HashMap<String, Double>();
this.selected = new ArrayList<byte[]>();
}
private void processHeader() throws IOException, OrekitException {
first = readFirstRecord();
second = new byte[recordSize];
readInRecord(second, 0);
label1 = extractString(first, HEADER_LABEL_1_OFFSET, HEADER_LABEL_SIZE);
label2 = extractString(first, HEADER_LABEL_2_OFFSET, HEADER_LABEL_SIZE);
label3 = extractString(first, HEADER_LABEL_3_OFFSET, HEADER_LABEL_SIZE);
// constants defined in the file
for (int i = 0; i < CONSTANTS_MAX_NUMBER; ++i) {
// Note: for extracting the strings from the binary file, it makes no difference
// if the file is stored in big-endian or little-endian notation
final String constantName = extractString(first, HEADER_CONSTANTS_NAMES_OFFSET + i * 6, 6);
if (constantName.length() == 0) {
// no more constants to read
break;
}
final double constantValue = extractDouble(second, HEADER_CONSTANTS_VALUES_OFFSET + 8 * i, bigEndian);
headerConstants.put(constantName, constantValue);
}
final Double timesc = headerConstants.get("TIMESC");
if (timesc != null && !Double.isNaN(timesc) && timesc.intValue() == 1) {
timeScale = TimeScalesFactory.getTCB();
} else {
timeScale = TimeScalesFactory.getTDB();
}
headerStartEpoch = extractDate(first, HEADER_START_EPOCH_OFFSET, bigEndian);
headerFinalEpoch = extractDate(first, HEADER_END_EPOCH_OFFSET, bigEndian);
}
private int processData(AbsoluteDate selectStart, AbsoluteDate selectEnd)
throws IOException, OrekitException {
byte[] data = new byte[recordSize];
int processed = 0;
while (readInRecord(data, 0)) {
// extract time range covered by the record
final AbsoluteDate rangeStart = extractDate(data, DATA_START_RANGE_OFFSET, bigEndian);
final AbsoluteDate rangeEnd = extractDate(data, DATE_END_RANGE_OFFSET, bigEndian);
if (rangeEnd.compareTo(selectStart) >= 0 && rangeStart.compareTo(selectEnd) <= 0) {
selected.add(data.clone());
}
++processed;
}
return processed;
}
private void write() throws IOException {
if (!selected.isEmpty()) {
if (outName != null) {
try (OutputStream out = new FileOutputStream(outName)) {
// patch header epoch
System.arraycopy(selected.get(0), DATA_START_RANGE_OFFSET,
first, HEADER_START_EPOCH_OFFSET,
8);
System.arraycopy(selected.get(selected.size() - 1), DATE_END_RANGE_OFFSET,
first, HEADER_END_EPOCH_OFFSET,
8);
// patch header labels
final AbsoluteDate start = extractDate(first, HEADER_START_EPOCH_OFFSET, bigEndian);
final DateTimeComponents sc = start.getComponents(timeScale);
final AbsoluteDate end = extractDate(first, HEADER_END_EPOCH_OFFSET, bigEndian);
final DateTimeComponents ec = end.getComponents(timeScale);
System.arraycopy(padString("THIS IS NOT A GENUINE JPL DE FILE," +
" THIS IS AN EXCERPT WITH A LIMITED TIME RANGE",
HEADER_LABEL_SIZE), 0,
first, HEADER_LABEL_1_OFFSET,
HEADER_LABEL_SIZE);
System.arraycopy(padString(String.format(Locale.US,
"Start Epoch: JED= %.1f %4d-%s-%02d %02d:%02d:%02.0f",
(start.durationFrom(AbsoluteDate.JULIAN_EPOCH)) /
Constants.JULIAN_DAY,
sc.getDate().getYear(),
sc.getDate().getMonthEnum().getUpperCaseAbbreviation(),
sc.getDate().getDay(),
sc.getTime().getHour(),
sc.getTime().getMinute(),
sc.getTime().getSecond()),
HEADER_LABEL_SIZE), 0,
first, HEADER_LABEL_2_OFFSET,
HEADER_LABEL_SIZE);
System.arraycopy(padString(String.format(Locale.US,
"Final Epoch: JED= %.1f %4d-%s-%02d %02d:%02d:%02.0f",
(end.durationFrom(AbsoluteDate.JULIAN_EPOCH)) /
Constants.JULIAN_DAY,
ec.getDate().getYear(),
ec.getDate().getMonthEnum().getUpperCaseAbbreviation(),
ec.getDate().getDay(),
ec.getTime().getHour(),
ec.getTime().getMinute(),
ec.getTime().getSecond()),
HEADER_LABEL_SIZE), 0,
first, HEADER_LABEL_3_OFFSET,
HEADER_LABEL_SIZE);
// write patched header
out.write(first);
out.write(second);
// write selected data
for (byte[] data : selected) {
out.write(data);
}
}
}
}
}
private byte[] readFirstRecord()
throws OrekitException, IOException {
// read first part of record, up to the record number
final byte[] firstPart = new byte[HEADER_RECORD_SIZE_OFFSET + 4];
if (!readInRecord(firstPart, 0)) {
throw new OrekitException(OrekitMessages.UNABLE_TO_READ_JPL_HEADER, inName);
}
// detect the endian format
bigEndian = detectEndianess(firstPart);
// get the ephemerides type
deNum = extractInt(firstPart, HEADER_EPHEMERIS_TYPE_OFFSET, bigEndian);
// the record size for this file
recordSize = 0;
if (deNum == INPOP_DE_NUMBER) {
// INPOP files have an extended DE format, which includes also the record size
recordSize = extractInt(firstPart, HEADER_RECORD_SIZE_OFFSET, bigEndian) << 3;
} else {
// compute the record size for original JPL files
recordSize = computeRecordSize(firstPart, bigEndian, inName);
}
if (recordSize <= 0) {
throw new OrekitException(OrekitMessages.UNABLE_TO_READ_JPL_HEADER, inName);
}
// build a record with the proper size and finish read of the first complete record
final byte[] record = new byte[recordSize];
System.arraycopy(firstPart, 0, record, 0, firstPart.length);
if (!readInRecord(record, firstPart.length)) {
throw new OrekitException(OrekitMessages.UNABLE_TO_READ_JPL_HEADER, inName);
}
return record;
}
private boolean readInRecord(final byte[] record, final int start)
throws IOException {
int index = start;
while (index != record.length) {
final int n = input.read(record, index, record.length - index);
if (n < 0) {
return false;
}
index += n;
}
return true;
}
/** Detect whether the JPL ephemerides file is stored in big-endian or
* little-endian notation.
* @param record the array containing the binary JPL header
* @return <code>true</code> if the file is stored in big-endian,
* <code>false</code> otherwise
*/
private static boolean detectEndianess(final byte[] record) {
// default to big-endian
boolean bigEndian = true;
// first try to read the DE number in big-endian format
// the number is stored as unsigned int, so we have to convert it properly
final long deNum = extractInt(record, HEADER_EPHEMERIS_TYPE_OFFSET, true) & 0xffffffffL;
// simple heuristic: if the read value is larger than half the range of an integer
// assume the file is in little-endian format
if (deNum > (1 << 15)) {
bigEndian = false;
}
return bigEndian;
}
/** Calculate the record size of a JPL ephemerides file.
* @param record the byte array containing the header record
* @param bigEndian indicates the endianess of the file
* @param name the name of the data file
* @return the record size for this file
* @throws OrekitException if the file contains unexpected data
*/
private static int computeRecordSize(final byte[] record,
final boolean bigEndian,
final String name)
throws OrekitException {
int recordSize = 0;
boolean ok = true;
// JPL files always have 3 position components
final int nComp = 3;
// iterate over the coefficient ptr array and sum up the record size
// the coeffPtr array has the dimensions [12][nComp]
for (int j = 0; j < 12; j++) {
final int nCompCur = (j == 11) ? 2 : nComp;
// the coeffPtr array starts at offset 2696
// Note: the array element coeffPtr[j][0] is not needed for the calculation
final int idx = 2696 + j * nComp * 4;
final int coeffPtr1 = extractInt(record, idx + 4, bigEndian);
final int coeffPtr2 = extractInt(record, idx + 8, bigEndian);
// sanity checks
ok = ok && (coeffPtr1 >= 0 || coeffPtr2 >= 0);
recordSize += coeffPtr1 * coeffPtr2 * nCompCur;
}
// the libration ptr array starts at offset 2844 and has the dimension [3]
// Note: the array element libratPtr[0] is not needed for the calculation
final int libratPtr1 = extractInt(record, 2844 + 4, bigEndian);
final int libratPtr2 = extractInt(record, 2844 + 8, bigEndian);
// sanity checks
ok = ok && (libratPtr1 >= 0 || libratPtr2 >= 0);
recordSize += libratPtr1 * libratPtr2 * nComp + 2;
recordSize <<= 3;
if (!ok || recordSize <= 0) {
throw new OrekitException(OrekitMessages.NOT_A_JPL_EPHEMERIDES_BINARY_FILE, name);
}
return recordSize;
}
/** Extract a date from a record.
* @param record record to parse
* @param offset offset of the double within the record
* @param bigEndian if <code>true</code> the parsed date is extracted in big-endian
* format, otherwise it is extracted in little-endian format
* @return extracted date
*/
private AbsoluteDate extractDate(final byte[] record,
final int offset,
final boolean bigEndian) {
final double t = extractDouble(record, offset, bigEndian);
int jDay = (int) FastMath.floor(t);
double seconds = (t + 0.5 - jDay) * Constants.JULIAN_DAY;
if (seconds >= Constants.JULIAN_DAY) {
++jDay;
seconds -= Constants.JULIAN_DAY;
}
final AbsoluteDate date =
new AbsoluteDate(new DateComponents(DateComponents.JULIAN_EPOCH, jDay),
new TimeComponents(seconds), timeScale);
return date;
}
/** Extract a double from a record.
* <p>Double numbers are stored according to IEEE 754 standard, with
* most significant byte first.</p>
* @param record record to parse
* @param offset offset of the double within the record
* @param bigEndian if <code>true</code> the parsed double is extracted in big-endian
* format, otherwise it is extracted in little-endian format
* @return extracted double
*/
private static double extractDouble(final byte[] record, final int offset, final boolean bigEndian) {
final long l8 = ((long) record[offset + 0]) & 0xffl;
final long l7 = ((long) record[offset + 1]) & 0xffl;
final long l6 = ((long) record[offset + 2]) & 0xffl;
final long l5 = ((long) record[offset + 3]) & 0xffl;
final long l4 = ((long) record[offset + 4]) & 0xffl;
final long l3 = ((long) record[offset + 5]) & 0xffl;
final long l2 = ((long) record[offset + 6]) & 0xffl;
final long l1 = ((long) record[offset + 7]) & 0xffl;
long l;
if (bigEndian) {
l = (l8 << 56) | (l7 << 48) | (l6 << 40) | (l5 << 32) |
(l4 << 24) | (l3 << 16) | (l2 << 8) | l1;
} else {
l = (l1 << 56) | (l2 << 48) | (l3 << 40) | (l4 << 32) |
(l5 << 24) | (l6 << 16) | (l7 << 8) | l8;
}
return Double.longBitsToDouble(l);
}
/** Extract an int from a record.
* @param record record to parse
* @param offset offset of the double within the record
* @param bigEndian if <code>true</code> the parsed int is extracted in big-endian
* format, otherwise it is extracted in little-endian format
* @return extracted int
*/
private static int extractInt(final byte[] record, final int offset, final boolean bigEndian) {
final int l4 = ((int) record[offset + 0]) & 0xff;
final int l3 = ((int) record[offset + 1]) & 0xff;
final int l2 = ((int) record[offset + 2]) & 0xff;
final int l1 = ((int) record[offset + 3]) & 0xff;
if (bigEndian) {
return (l4 << 24) | (l3 << 16) | (l2 << 8) | l1;
} else {
return (l1 << 24) | (l2 << 16) | (l3 << 8) | l4;
}
}
/** Extract a String from a record.
* @param record record to parse
* @param offset offset of the string within the record
* @param length maximal length of the string
* @return extracted string, with whitespace characters stripped
*/
private static String extractString(final byte[] record, final int offset, final int length) {
try {
return new String(record, offset, length, "US-ASCII").trim();
} catch (UnsupportedEncodingException uee) {
throw new OrekitInternalError(uee);
}
}
/** Pad a string into a bytes array.
* @param s string to pad
* @param length length of the padded bytes array
* @return padded bytes array
*/
private static byte[] padString(final String s, final int length) {
final Charset charSet = Charset.forName("US-ASCII");
final byte[] array = new byte[length];
Arrays.fill(array, charSet.encode(" ").get());
final byte[] sb = charSet.encode(s).array();
System.arraycopy(sb, 0, array, 0, FastMath.min(sb.length, array.length));
return array;
}
}