1 /* Copyright 2002-2025 CS GROUP
2 * Licensed to CS GROUP (CS) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * CS licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.orekit.propagation.analytical.tle;
18
19 import java.util.Arrays;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23
24 import org.orekit.errors.OrekitException;
25 import org.orekit.errors.OrekitMessages;
26
27 /** Utility class for TLE parsing, including alpha-5 TLE satellites IDs handling.
28 * <p>
29 * Alpha-5 extends the range of existing 5 digits TLE satellite numbers
30 * by allowing the first digit to be an upper case letter, ignoring 'I'
31 * and 'O' to avoid confusion with numbers '1' and '0'.
32 * </p>
33 * @see <a href="https://www.space-track.org/documentation#tle-alpha5>TLE-alpha5</a>
34 * @author Mark rutten
35 */
36 class ParseUtils {
37
38 /** Letter-number map for satellite number. */
39 private static final int MAX_NUMERIC_SATNUM = 99999;
40
41 /** Letter-number map for satellite number. */
42 private static final Map<Character, Integer> ALPHA5_NUMBERS;
43
44 /** Number-letter map for satellite number. */
45 private static final Map<Integer, Character> ALPHA5_LETTERS;
46
47 /** Scaling factor for alpha5 numbers. */
48 private static final int ALPHA5_SCALING = 10000;
49
50 static {
51 // Generate maps between TLE satellite alphabetic characters and integers.
52 final List<Character> alpha5Letters =
53 Arrays.asList('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J',
54 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T',
55 'U', 'V', 'W', 'X', 'Y', 'Z');
56 ALPHA5_NUMBERS = new HashMap<>(alpha5Letters.size());
57 ALPHA5_LETTERS = new HashMap<>(alpha5Letters.size());
58 for (int i = 0; i < alpha5Letters.size(); ++i) {
59 ALPHA5_NUMBERS.put(alpha5Letters.get(i), i + 10);
60 ALPHA5_LETTERS.put(i + 10, alpha5Letters.get(i));
61 }
62 }
63
64 /** Private constructor for a utility class. */
65 private ParseUtils() {
66 // nothing to do
67 }
68
69 /** Build an alpha5 satellite number.
70 * @param satelliteNumber satellite number, that may exceed the 99999 limit
71 * @param name parameter name
72 * @return satellite number in alpha5 representation
73 */
74 public static String buildSatelliteNumber(final int satelliteNumber, final String name) {
75 if (satelliteNumber > MAX_NUMERIC_SATNUM) {
76 final int highDigits = satelliteNumber / ALPHA5_SCALING;
77 final int lowDigits = satelliteNumber - highDigits * ALPHA5_SCALING;
78
79 final Character alpha = ALPHA5_LETTERS.get(highDigits);
80 if (alpha == null) {
81 throw new OrekitException(OrekitMessages.TLE_INVALID_PARAMETER,
82 satelliteNumber, name, "null");
83 }
84 return alpha + addPadding(name, lowDigits, '0', 4, true, satelliteNumber);
85 } else {
86 return addPadding(name, satelliteNumber, '0', 5, true, satelliteNumber);
87 }
88 }
89
90 /** Add padding characters before an integer.
91 * @param name parameter name
92 * @param k integer to pad
93 * @param c padding character
94 * @param size desired size
95 * @param rightJustified if true, the resulting string is
96 * right justified (i.e. space are added to the left)
97 * @param satelliteNumber satellite number
98 * @return padded string
99 */
100 public static String addPadding(final String name, final int k, final char c,
101 final int size, final boolean rightJustified,
102 final int satelliteNumber) {
103 return addPadding(name, Integer.toString(k), c, size, rightJustified, satelliteNumber);
104 }
105
106 /** Add padding characters to a string.
107 * @param name parameter name
108 * @param string string to pad
109 * @param c padding character
110 * @param size desired size
111 * @param rightJustified if true, the resulting string is
112 * right justified (i.e. space are added to the left)
113 * @param satelliteNumber satellite number
114 * @return padded string
115 */
116 public static String addPadding(final String name, final String string, final char c,
117 final int size, final boolean rightJustified,
118 final int satelliteNumber) {
119
120 if (string.length() > size) {
121 throw new OrekitException(OrekitMessages.TLE_INVALID_PARAMETER,
122 satelliteNumber, name, string);
123 }
124
125 final StringBuilder padding = new StringBuilder();
126 for (int i = 0; i < size; ++i) {
127 padding.append(c);
128 }
129
130 if (rightJustified) {
131 final String concatenated = padding + string;
132 final int l = concatenated.length();
133 return concatenated.substring(l - size, l);
134 }
135
136 return (string + padding).substring(0, size);
137
138 }
139
140 /** Parse a double.
141 * @param line line to parse
142 * @param start start index of the first character
143 * @param length length of the string
144 * @return value of the double
145 */
146 public static double parseDouble(final String line, final int start, final int length) {
147 final String string = line.substring(start, start + length).trim();
148 return string.length() > 0 ? Double.parseDouble(string.replace(' ', '0')) : 0;
149 }
150
151 /** Parse an integer.
152 * @param line line to parse
153 * @param start start index of the first character
154 * @param length length of the string
155 * @return value of the integer
156 */
157 public static int parseInteger(final String line, final int start, final int length) {
158 final String field = line.substring(start, start + length).trim();
159 return field.length() > 0 ? Integer.parseInt(field.replace(' ', '0')) : 0;
160 }
161
162 /** Parse a satellite number.
163 * @param line line to parse
164 * @param start start index of the first character
165 * @param length length of the string
166 * @return value of the integer
167 */
168 public static int parseSatelliteNumber(final String line, final int start, final int length) {
169 String field = line.substring(start, start + length);
170 int satelliteNumber;
171
172 final Integer alpha = ALPHA5_NUMBERS.get(field.charAt(0));
173 if (alpha != null) {
174 satelliteNumber = Integer.parseInt(field.substring(1));
175 satelliteNumber += alpha * ALPHA5_SCALING;
176 } else {
177 field = field.trim();
178 satelliteNumber = field.length() > 0 ? Integer.parseInt(field.replace(' ', '0')) : 0;
179 }
180 return satelliteNumber;
181 }
182
183 /** Parse a year written on 2 digits.
184 * @param line line to parse
185 * @param start start index of the first character
186 * @return value of the year
187 */
188 public static int parseYear(final String line, final int start) {
189 final int year = 2000 + parseInteger(line, start, 2);
190 return (year > 2056) ? (year - 100) : year;
191 }
192
193 }