Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 76 additions & 76 deletions tle.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,7 @@ type TLE struct {
}

// Parses TLE (2LE) and 3LE formats.
// Features:
// - Supports Alpha 5 format
// - Validates the checksums
// - Converts the epoch to a time.Time
// - Converts numbers to correct type
// Features: Supports Alpha 5 format, validates the checksums, converts the epoch to a time.Time, converts numbers to correct type
func Parse(txt string) (TLE, error) {
var (
trimmedTxt = strings.TrimSpace(txt)
Expand All @@ -81,72 +77,10 @@ func Parse(txt string) (TLE, error) {
}

// Parse the NORAD ID from the first line
result.NoradIdStr = result.Line1[2:7]
if _, err := strconv.Atoi(string(result.NoradIdStr[0])); err == nil {
noradInt, err := strconv.Atoi(result.NoradIdStr)
if err != nil {
return TLE{}, err
}
result.NoradId = noradInt
} else {
rest := result.NoradIdStr[1:]
firstChar := result.NoradIdStr[0]
switch firstChar {
case 'A':
result.NoradId, err = strconv.Atoi("10" + rest)
case 'B':
result.NoradId, err = strconv.Atoi("11" + rest)
case 'C':
result.NoradId, err = strconv.Atoi("12" + rest)
case 'D':
result.NoradId, err = strconv.Atoi("13" + rest)
case 'E':
result.NoradId, err = strconv.Atoi("14" + rest)
case 'F':
result.NoradId, err = strconv.Atoi("15" + rest)
case 'G':
result.NoradId, err = strconv.Atoi("16" + rest)
case 'H':
result.NoradId, err = strconv.Atoi("17" + rest)
case 'J':
result.NoradId, err = strconv.Atoi("18" + rest)
case 'K':
result.NoradId, err = strconv.Atoi("19" + rest)
case 'L':
result.NoradId, err = strconv.Atoi("20" + rest)
case 'M':
result.NoradId, err = strconv.Atoi("21" + rest)
case 'N':
result.NoradId, err = strconv.Atoi("22" + rest)
case 'P':
result.NoradId, err = strconv.Atoi("23" + rest)
case 'Q':
result.NoradId, err = strconv.Atoi("24" + rest)
case 'R':
result.NoradId, err = strconv.Atoi("25" + rest)
case 'S':
result.NoradId, err = strconv.Atoi("26" + rest)
case 'T':
result.NoradId, err = strconv.Atoi("27" + rest)
case 'U':
result.NoradId, err = strconv.Atoi("28" + rest)
case 'V':
result.NoradId, err = strconv.Atoi("29" + rest)
case 'W':
result.NoradId, err = strconv.Atoi("30" + rest)
case 'X':
result.NoradId, err = strconv.Atoi("31" + rest)
case 'Y':
result.NoradId, err = strconv.Atoi("32" + rest)
case 'Z':
result.NoradId, err = strconv.Atoi("33" + rest)
default:
return TLE{}, errors.New("invalid NORAD ID Alpha-5 format")
}

if err != nil {
return TLE{}, err
}
result.NoradIdStr = strings.TrimSpace(result.Line1[2:7])
result.NoradId, err = parseNoradId(result.NoradIdStr)
if err != nil {
return TLE{}, err
}

result.Classification = result.Line1[7:8]
Expand Down Expand Up @@ -191,7 +125,7 @@ func Parse(txt string) (TLE, error) {
}

// line 2
secondNoradIdStr := result.Line2[2:7]
secondNoradIdStr := strings.TrimSpace(result.Line2[2:7])
if secondNoradIdStr != result.NoradIdStr {
return TLE{}, errors.New("line 1 and line 2 NORAD IDs do not match")
}
Expand All @@ -217,17 +151,17 @@ func Parse(txt string) (TLE, error) {
return TLE{}, err
}

result.MeanAnomalyDegrees, err = strconv.ParseFloat(result.Line2[43:51], 64)
result.MeanAnomalyDegrees, err = strconv.ParseFloat(strings.TrimSpace(result.Line2[43:51]), 64)
if err != nil {
return TLE{}, err
}

result.MeanMotion, err = strconv.ParseFloat(result.Line2[52:63], 64)
result.MeanMotion, err = strconv.ParseFloat(strings.TrimSpace(result.Line2[52:63]), 64)
if err != nil {
return TLE{}, err
}

result.EpochRevolutionCount, err = strconv.Atoi(result.Line2[63:68])
result.EpochRevolutionCount, err = strconv.Atoi(strings.TrimSpace(result.Line2[63:68]))
if err != nil {
return TLE{}, err
}
Expand Down Expand Up @@ -269,7 +203,7 @@ func convertYearAndDayToDate(twoDigitYear, day string) (time.Time, error) {
nanosecondsFloat := dayFloat * 24.0 * 60.0 * 60.0 * 1e9
ns := time.Duration(nanosecondsFloat)

// subtract a day the .Date adds a day
// subtract a day because the .Date adds a day
return time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC).Add(ns).Add(-time.Hour * 24), nil
}

Expand Down Expand Up @@ -342,3 +276,69 @@ func parseBStar(bstar string) (float64, error) {

return strconv.ParseFloat(parseableStr, 64)
}

func parseNoradId(s string) (int, error) {
if _, err := strconv.Atoi(string(s[0])); err == nil {
noradInt, err := strconv.Atoi(s)
if err != nil {
return 0, err
}
return noradInt, nil
} else {
rest := s[1:]
firstChar := s[0]
switch firstChar {
// I and O are not used
case 'A':
return strconv.Atoi("10" + rest)
case 'B':
return strconv.Atoi("11" + rest)
case 'C':
return strconv.Atoi("12" + rest)
case 'D':
return strconv.Atoi("13" + rest)
case 'E':
return strconv.Atoi("14" + rest)
case 'F':
return strconv.Atoi("15" + rest)
case 'G':
return strconv.Atoi("16" + rest)
case 'H':
return strconv.Atoi("17" + rest)
case 'J':
return strconv.Atoi("18" + rest)
case 'K':
return strconv.Atoi("19" + rest)
case 'L':
return strconv.Atoi("20" + rest)
case 'M':
return strconv.Atoi("21" + rest)
case 'N':
return strconv.Atoi("22" + rest)
case 'P':
return strconv.Atoi("23" + rest)
case 'Q':
return strconv.Atoi("24" + rest)
case 'R':
return strconv.Atoi("25" + rest)
case 'S':
return strconv.Atoi("26" + rest)
case 'T':
return strconv.Atoi("27" + rest)
case 'U':
return strconv.Atoi("28" + rest)
case 'V':
return strconv.Atoi("29" + rest)
case 'W':
return strconv.Atoi("30" + rest)
case 'X':
return strconv.Atoi("31" + rest)
case 'Y':
return strconv.Atoi("32" + rest)
case 'Z':
return strconv.Atoi("33" + rest)
default:
return 0, errors.New("invalid NORAD ID Alpha-5 format")
}
}
}
22 changes: 22 additions & 0 deletions tle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,28 @@ ISS (ZARYA)
assert.Nil(t, err)
assert.Equal(t, localExpected, got)
})

t.Run("Many different TLEs will parse", func(t *testing.T) {
tles := []string{
`ISS (ZARYA)
1 25544U 98067A 20274.51782528 .00000867 00000-0 22813-4 0 9994
2 25544 51.6441 93.0000 0001400 11.0000 349.0000 15.49300070250767`,
`1 58465U 23185D 24363.11104941 .00028654 00000-0 95911-3 0 9999
2 58465 97.4051 67.6717 0007093 330.0760 30.0075 15.30897368 59670`,
`1 33591U 09005A 24363.16300298 .00000451 00000-0 26450-3 0 9992
2 33591 99.0226 60.7854 0014697 104.8667 255.4134 14.13241242819028`,
`1 38771U 12049A 24363.09173364 .00000381 00000-0 19401-3 0 9993
2 38771 98.6040 55.7087 0001839 103.7914 256.3468 14.21393899637091`,
`1 9478U 76101A 24362.62107380 -.00000010 00000-0 00000+0 0 9998
2 9478 6.9318 311.1675 0107070 341.0025 254.4654 0.97590429118242`,
}

for _, raw := range tles {
tle, err := Parse(raw)
assert.Nil(t, err)
assert.NotNil(t, tle)
}
})
}

func TestYearAndDayToDate(t *testing.T) {
Expand Down
Loading