Commit 582bb6ef authored by Mark Rutten's avatar Mark Rutten
Browse files

Add support for alpha-numeric TLE satellite IDs.

parent 6d834ee0
Pipeline #937 failed with stage
......@@ -19,8 +19,7 @@ package org.orekit.propagation.analytical.tle;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.Objects;
import java.util.*;
import org.hipparchus.Field;
import org.hipparchus.RealFieldElement;
......@@ -111,6 +110,18 @@ public class FieldTLE<T extends RealFieldElement<T>> implements FieldTimeStamped
/** Name of the eccentricity parameter. */
private static final String ECCENTRICITY = "eccentricity";
/** Letter-number map for satellite number */
private static final int MAX_NUMERIC_SATNUM = 99999;
/** Letter-number map for satellite number */
private static final Map<Character, Integer> ALPHA5_NUMBERS;
/** Number-letter map for satellite number */
private static final Map<Integer, Character> ALPHA5_LETTERS;
/** Scaling factor for alpha5 numbers */
private static final int ALPHA5_SCALING = 10000;
/** International symbols for parsing. */
private static final DecimalFormatSymbols SYMBOLS =
new DecimalFormatSymbols(Locale.US);
......@@ -181,6 +192,20 @@ public class FieldTLE<T extends RealFieldElement<T>> implements FieldTimeStamped
/** Driver for ballistic coefficient parameter. */
private final transient ParameterDriver bStarParameterDriver;
/* Generate maps between TLE satellite alphabetic characters and integers.
*/
static {
final List<Character> alpha5Letters = Arrays.asList('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J',
'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z');
ALPHA5_NUMBERS = new HashMap<>(alpha5Letters.size());
ALPHA5_LETTERS = new HashMap<>(alpha5Letters.size());
for(int i=0; i < alpha5Letters.size(); ++i) {
ALPHA5_NUMBERS.put(alpha5Letters.get(i), i + 10);
ALPHA5_LETTERS.put(i + 10, alpha5Letters.get(i));
}
}
/** Simple constructor from unparsed two lines. This constructor uses the {@link
* DataContext#getDefault() default data context}.
*
......@@ -213,8 +238,8 @@ public class FieldTLE<T extends RealFieldElement<T>> implements FieldTimeStamped
final T zero = field.getZero();
// identification
satelliteNumber = parseInteger(line1, 2, 5);
final int satNum2 = parseInteger(line2, 2, 5);
satelliteNumber = parseSatelliteNumber(line1, 2, 5);
final int satNum2 = parseSatelliteNumber(line2, 2, 5);
if (satelliteNumber != satNum2) {
throw new OrekitException(OrekitMessages.TLE_LINES_DO_NOT_REFER_TO_SAME_OBJECT,
line1, line2);
......@@ -445,6 +470,26 @@ public class FieldTLE<T extends RealFieldElement<T>> implements FieldTimeStamped
return line2;
}
/** Build an alpha5 satellite number.
*/
private String buildSatelliteNumber(final int satelliteNumber, final String name) {
String satnumString;
if (satelliteNumber > MAX_NUMERIC_SATNUM) {
int highDigits = satelliteNumber / ALPHA5_SCALING;
int lowDigits = satelliteNumber - highDigits * ALPHA5_SCALING;
Character alpha = ALPHA5_LETTERS.get(highDigits);
if (alpha == null) {
throw new OrekitException(OrekitMessages.TLE_INVALID_PARAMETER,
satelliteNumber, name, alpha);
}
satnumString = alpha + addPadding(name, lowDigits, '0', 4, true);
} else {
satnumString = addPadding(name, satelliteNumber, '0', 5, true);
}
return satnumString;
}
/** Build the line 1 from the parsed elements.
*/
private void buildLine1() {
......@@ -454,7 +499,7 @@ public class FieldTLE<T extends RealFieldElement<T>> implements FieldTimeStamped
buffer.append('1');
buffer.append(' ');
buffer.append(addPadding("satelliteNumber-1", satelliteNumber, '0', 5, true));
buffer.append(buildSatelliteNumber(satelliteNumber, "satelliteNumber-1"));
buffer.append(classification);
buffer.append(' ');
......@@ -539,7 +584,7 @@ public class FieldTLE<T extends RealFieldElement<T>> implements FieldTimeStamped
buffer.append('2');
buffer.append(' ');
buffer.append(addPadding("satelliteNumber-2", satelliteNumber, '0', 5, true));
buffer.append(buildSatelliteNumber(satelliteNumber, "satelliteNumber-2"));
buffer.append(' ');
buffer.append(addPadding(INCLINATION, f34.format(FastMath.toDegrees(inclination)), ' ', 8, true));
......@@ -630,6 +675,26 @@ public class FieldTLE<T extends RealFieldElement<T>> implements FieldTimeStamped
return field.length() > 0 ? Integer.parseInt(field.replace(' ', '0')) : 0;
}
/** Parse a satellite number
* @param line line to parse
* @param start start index of the first character
* @param length length of the string
* @return value of the integer
*/
private int parseSatelliteNumber(final String line, final int start, final int length) {
String field = line.substring(start, start + length);
int satelliteNumber;
if(Character.isUpperCase(field.charAt(0))) {
satelliteNumber = Integer.parseInt(field.substring(1));
satelliteNumber += ALPHA5_NUMBERS.get(field.charAt(0)) * ALPHA5_SCALING;
} else {
field = field.trim();
satelliteNumber = field.length() > 0 ? Integer.parseInt(field.replace(' ', '0')) : 0;
}
return satelliteNumber;
}
/** Parse a year written on 2 digits.
* @param line line to parse
* @param start start index of the first character
......
......@@ -19,8 +19,7 @@ package org.orekit.propagation.analytical.tle;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.Objects;
import java.util.*;
import java.util.regex.Pattern;
import org.hipparchus.util.ArithmeticUtils;
......@@ -109,14 +108,26 @@ public class TLE implements TimeStamped, Serializable {
/** Pattern for line 1. */
private static final Pattern LINE_1_PATTERN =
Pattern.compile("1 [ 0-9]{5}[A-Z] [ 0-9]{5}[ A-Z]{3} [ 0-9]{5}[.][ 0-9]{8} (?:(?:[ 0+-][.][ 0-9]{8})|(?: [ +-][.][ 0-9]{7})) " +
Pattern.compile("1 [ 0-9A-Z&&[^IO]][ 0-9]{4}[A-Z] [ 0-9]{5}[ A-Z]{3} [ 0-9]{5}[.][ 0-9]{8} (?:(?:[ 0+-][.][ 0-9]{8})|(?: [ +-][.][ 0-9]{7})) " +
"[ +-][ 0-9]{5}[+-][ 0-9] [ +-][ 0-9]{5}[+-][ 0-9] [ 0-9] [ 0-9]{4}[ 0-9]");
/** Pattern for line 2. */
private static final Pattern LINE_2_PATTERN =
Pattern.compile("2 [ 0-9]{5} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{7} " +
Pattern.compile("2 [ 0-9A-Z&&[^IO]][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{7} " +
"[ 0-9]{3}[.][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{2}[.][ 0-9]{13}[ 0-9]");
/** Letter-number map for satellite number */
private static final int MAX_NUMERIC_SATNUM = 99999;
/** Letter-number map for satellite number */
private static final Map<Character, Integer> ALPHA5_NUMBERS;
/** Number-letter map for satellite number */
private static final Map<Integer, Character> ALPHA5_LETTERS;
/** Scaling factor for alpha5 numbers */
private static final int ALPHA5_SCALING = 10000;
/** International symbols for parsing. */
private static final DecimalFormatSymbols SYMBOLS =
new DecimalFormatSymbols(Locale.US);
......@@ -187,6 +198,22 @@ public class TLE implements TimeStamped, Serializable {
/** Driver for ballistic coefficient parameter. */
private final transient ParameterDriver bStarParameterDriver;
/*
* Generate maps between TLE satellite alphabetic characters and integers.
*/
static {
final List<Character> alpha5Letters = Arrays.asList('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J',
'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z');
ALPHA5_NUMBERS = new HashMap<>(alpha5Letters.size());
ALPHA5_LETTERS = new HashMap<>(alpha5Letters.size());
for(int i=0; i < alpha5Letters.size(); ++i) {
ALPHA5_NUMBERS.put(alpha5Letters.get(i), i + 10);
ALPHA5_LETTERS.put(i + 10, alpha5Letters.get(i));
}
}
/** Simple constructor from unparsed two lines. This constructor uses the {@link
* DataContext#getDefault() default data context}.
*
......@@ -213,8 +240,8 @@ public class TLE implements TimeStamped, Serializable {
public TLE(final String line1, final String line2, final TimeScale utc) {
// identification
satelliteNumber = parseInteger(line1, 2, 5);
final int satNum2 = parseInteger(line2, 2, 5);
satelliteNumber = parseSatelliteNumber(line1, 2, 5);
final int satNum2 = parseSatelliteNumber(line2, 2, 5);
if (satelliteNumber != satNum2) {
throw new OrekitException(OrekitMessages.TLE_LINES_DO_NOT_REFER_TO_SAME_OBJECT,
line1, line2);
......@@ -449,6 +476,26 @@ public class TLE implements TimeStamped, Serializable {
return line2;
}
/** Build an alpha5 satellite number.
*/
private String buildSatelliteNumber(final int satelliteNumber, final String name) {
String satnumString;
if (satelliteNumber > MAX_NUMERIC_SATNUM) {
int highDigits = satelliteNumber / ALPHA5_SCALING;
int lowDigits = satelliteNumber - highDigits * ALPHA5_SCALING;
Character alpha = ALPHA5_LETTERS.get(highDigits);
if (alpha == null) {
throw new OrekitException(OrekitMessages.TLE_INVALID_PARAMETER,
satelliteNumber, name, alpha);
}
satnumString = alpha + addPadding(name, lowDigits, '0', 4, true);
} else {
satnumString = addPadding(name, satelliteNumber, '0', 5, true);
}
return satnumString;
}
/** Build the line 1 from the parsed elements.
*/
private void buildLine1() {
......@@ -458,7 +505,7 @@ public class TLE implements TimeStamped, Serializable {
buffer.append('1');
buffer.append(' ');
buffer.append(addPadding("satelliteNumber-1", satelliteNumber, '0', 5, true));
buffer.append(buildSatelliteNumber(satelliteNumber, "satelliteNumber-1"));
buffer.append(classification);
buffer.append(' ');
......@@ -543,7 +590,7 @@ public class TLE implements TimeStamped, Serializable {
buffer.append('2');
buffer.append(' ');
buffer.append(addPadding("satelliteNumber-2", satelliteNumber, '0', 5, true));
buffer.append(buildSatelliteNumber(satelliteNumber, "satelliteNumber-2"));
buffer.append(' ');
buffer.append(addPadding(INCLINATION, f34.format(FastMath.toDegrees(inclination)), ' ', 8, true));
......@@ -634,6 +681,26 @@ public class TLE implements TimeStamped, Serializable {
return field.length() > 0 ? Integer.parseInt(field.replace(' ', '0')) : 0;
}
/** Parse a satellite number
* @param line line to parse
* @param start start index of the first character
* @param length length of the string
* @return value of the integer
*/
private int parseSatelliteNumber(final String line, final int start, final int length) {
String field = line.substring(start, start + length);
int satelliteNumber;
if(Character.isUpperCase(field.charAt(0))) {
satelliteNumber = Integer.parseInt(field.substring(1));
satelliteNumber += ALPHA5_NUMBERS.get(field.charAt(0)) * ALPHA5_SCALING;
} else {
field = field.trim();
satelliteNumber = field.length() > 0 ? Integer.parseInt(field.replace(' ', '0')) : 0;
}
return satelliteNumber;
}
/** Parse a year written on 2 digits.
* @param line line to parse
* @param start start index of the first character
......
......@@ -65,7 +65,7 @@ public class FieldTLETest {
}
@Test
public void testSylletry() {
public void testSymmetry() {
doTestSymmetry(Decimal64Field.getInstance());
}
......@@ -207,6 +207,13 @@ public class FieldTLETest {
Assert.assertEquals(tle.getRevolutionNumberAtEpoch(), 6, 0);
Assert.assertEquals(tle.getElementNumber(), 2 , 0);
line1 = "1 T7421U 02021A 02124.48976499 -.00021470 00000-0 -89879-2 0 28";
line2 = "2 T7421 98.7490 199.5121 0001333 133.9522 226.1918 14.26113993 60";
Assert.assertTrue(TLE.isFormatOK(line1, line2));
tle = new FieldTLE<T>(field, line1, line2);
Assert.assertEquals(277421, tle.getSatelliteNumber(), 0);
line1 = "1 27421U 02021A 02124.48976499 -.00021470 00000-0 -89879-2 0 20";
line2 = "2 27421 98.7490 199.5121 0001333 133.9522 226.1918 14*26113993 62";
Assert.assertFalse(TLE.isFormatOK(line1, line2));
......@@ -257,6 +264,8 @@ public class FieldTLETest {
"2 27421 98.7490 199.5121 0001333 133.9522 226.1918 14.26113993 62");
checkSymmetry(field, "1 31928U 98067BA 08269.84884916 .00114257 17652-4 13615-3 0 4412",
"2 31928 51.6257 175.4142 0001703 41.9031 318.2112 16.08175249 68368");
checkSymmetry(field, "1 T7421U 02021A 02124.48976499 -.00021470 00000-0 -89879-2 0 28",
"2 T7421 98.7490 199.5121 0001333 133.9522 226.1918 14.26113993 60");
}
private <T extends RealFieldElement<T>> void checkSymmetry(Field<T> field, String line1, String line2) {
......
......@@ -72,6 +72,13 @@ public class TLETest {
Assert.assertEquals(tle.getRevolutionNumberAtEpoch(), 6, 0);
Assert.assertEquals(tle.getElementNumber(), 2 , 0);
line1 = "1 T7421U 02021A 02124.48976499 -.00021470 00000-0 -89879-2 0 28";
line2 = "2 T7421 98.7490 199.5121 0001333 133.9522 226.1918 14.26113993 60";
Assert.assertTrue(TLE.isFormatOK(line1, line2));
tle = new TLE(line1, line2);
Assert.assertEquals(277421, tle.getSatelliteNumber(), 0);
line1 = "1 27421U 02021A 02124.48976499 -.00021470 00000-0 -89879-2 0 20";
line2 = "2 27421 98.7490 199.5121 0001333 133.9522 226.1918 14*26113993 62";
Assert.assertFalse(TLE.isFormatOK(line1, line2));
......@@ -123,6 +130,8 @@ public class TLETest {
"2 27421 98.7490 199.5121 0001333 133.9522 226.1918 14.26113993 62");
checkSymmetry("1 31928U 98067BA 08269.84884916 .00114257 17652-4 13615-3 0 4412",
"2 31928 51.6257 175.4142 0001703 41.9031 318.2112 16.08175249 68368");
checkSymmetry("1 T7421U 02021A 02124.48976499 -.00021470 00000-0 -89879-2 0 28",
"2 T7421 98.7490 199.5121 0001333 133.9522 226.1918 14.26113993 60");
}
private void checkSymmetry(String line1, String line2) {
......@@ -237,7 +246,7 @@ public class TLETest {
@Test
public void testBug77TooLargeSatelliteNumber1() {
try {
TLE tle = new TLE(1000000, 'U', 1971, 86, "J", 0, 908,
TLE tle = new TLE(340000, 'U', 1971, 86, "J", 0, 908,
new AbsoluteDate(new DateComponents(2012, 26),
new TimeComponents(0.96078249 * Constants.JULIAN_DAY),
TimeScalesFactory.getUTC()),
......@@ -248,7 +257,7 @@ public class TLETest {
Assert.fail("an exception should have been thrown");
} catch (OrekitException oe) {
Assert.assertEquals(OrekitMessages.TLE_INVALID_PARAMETER, oe.getSpecifier());
Assert.assertEquals(1000000, ((Integer) oe.getParts()[0]).intValue());
Assert.assertEquals(340000, ((Integer) oe.getParts()[0]).intValue());
Assert.assertEquals("satelliteNumber-1", oe.getParts()[1]);
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment