Feature/satellites (#2185)

* Remove planet geometry and simplespheregeometry
* Only use a single TLE loading implementation
* Add caching to the satellite loader;  Add Lua function to load kepler file
* Fix RenderablePlanetProjection specification
* Add OMM loading funtion;  Remove mean motion from Kepler parameters
* Replace TLETranslation class with GPTranslation and support OMM files
* Support loading SMDB files in kepler functions
* Merge RenderableSatellites and RenderableSmallBody with RenderableOrbitalKepler
* Update submodules
* Adapt existing satellites to new OMM file type
* Remove TLE helper
* Remove SSSB shared file and adapt sssb assets
This commit is contained in:
Alexander Bock
2022-08-02 13:11:50 +02:00
committed by GitHub
parent 351eb33d61
commit 7bc9e99b87
94 changed files with 3757 additions and 2723 deletions
+4 -6
View File
@@ -26,19 +26,18 @@ include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake)
set(HEADER_FILES
horizonsfile.h
kepler.h
speckloader.h
rendering/renderableconstellationbounds.h
rendering/renderablefluxnodes.h
rendering/renderablehabitablezone.h
rendering/renderablerings.h
rendering/renderableorbitalkepler.h
rendering/renderablesatellites.h
rendering/renderablesmallbody.h
rendering/renderablestars.h
rendering/renderabletravelspeed.h
translation/gptranslation.h
translation/keplertranslation.h
translation/spicetranslation.h
translation/tletranslation.h
translation/horizonstranslation.h
rotation/spicerotation.h
)
@@ -46,6 +45,7 @@ source_group("Header Files" FILES ${HEADER_FILES})
set(SOURCE_FILES
horizonsfile.cpp
kepler.cpp
spacemodule_lua.inl
speckloader.cpp
rendering/renderableconstellationbounds.cpp
@@ -53,13 +53,11 @@ set(SOURCE_FILES
rendering/renderablehabitablezone.cpp
rendering/renderablerings.cpp
rendering/renderableorbitalkepler.cpp
rendering/renderablesatellites.cpp
rendering/renderablesmallbody.cpp
rendering/renderablestars.cpp
rendering/renderabletravelspeed.cpp
translation/gptranslation.cpp
translation/keplertranslation.cpp
translation/spicetranslation.cpp
translation/tletranslation.cpp
translation/horizonstranslation.cpp
rotation/spicerotation.cpp
)
+751
View File
@@ -0,0 +1,751 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/space/kepler.h>
#include <ghoul/filesystem/cachemanager.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/misc.h>
#include <scn/scn.h>
#include <scn/tuple_return.h>
#include <fstream>
#include <optional>
#include <sstream>
namespace {
constexpr std::string_view _loggerCat = "Kepler";
constexpr int8_t CurrentCacheVersion = 1;
// The list of leap years only goes until 2056 as we need to touch this file then
// again anyway ;)
constexpr const std::array<int, 36> LeapYears = {
1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996,
2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040,
2044, 2048, 2052, 2056
};
constexpr const std::array<int, 12> DaysOfMonths = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
// Count the number of full days since the beginning of 2000 to the beginning of
// the parameter 'year'
int countDays(int year) {
// Find the position of the current year in the vector, the difference
// between its position and the position of 2000 (for J2000) gives the
// number of leap years
constexpr const int Epoch = 2000;
constexpr const int DaysRegularYear = 365;
constexpr const int DaysLeapYear = 366;
if (year == Epoch) {
return 0;
}
// Get the position of the most recent leap year
const auto lb = std::lower_bound(LeapYears.begin(), LeapYears.end(), year);
// Get the position of the epoch
const auto y2000 = std::find(LeapYears.begin(), LeapYears.end(), Epoch);
// The distance between the two iterators gives us the number of leap years
const int nLeapYears = static_cast<int>(std::abs(std::distance(y2000, lb)));
const int nYears = std::abs(year - Epoch);
const int nRegularYears = nYears - nLeapYears;
// Get the total number of days as the sum of leap years + non leap years
const int result = nRegularYears * DaysRegularYear + nLeapYears * DaysLeapYear;
return result;
}
// Returns the number of leap seconds that lie between the {year, dayOfYear}
// time point and { 2000, 1 }
int countLeapSeconds(int year, int dayOfYear) {
// Find the position of the current year in the vector; its position in the vector
// gives the number of leap seconds
struct LeapSecond {
int year;
int dayOfYear;
bool operator<(const LeapSecond& rhs) const {
return std::tie(year, dayOfYear) < std::tie(rhs.year, rhs.dayOfYear);
}
};
constexpr const LeapSecond LeapEpoch = { 2000, 1 };
// List taken from: https://www.ietf.org/timezones/data/leap-seconds.list
constexpr const std::array<LeapSecond, 28> LeapSeconds = {
LeapSecond{ 1972, 1 },
LeapSecond{ 1972, 183 },
LeapSecond{ 1973, 1 },
LeapSecond{ 1974, 1 },
LeapSecond{ 1975, 1 },
LeapSecond{ 1976, 1 },
LeapSecond{ 1977, 1 },
LeapSecond{ 1978, 1 },
LeapSecond{ 1979, 1 },
LeapSecond{ 1980, 1 },
LeapSecond{ 1981, 182 },
LeapSecond{ 1982, 182 },
LeapSecond{ 1983, 182 },
LeapSecond{ 1985, 182 },
LeapSecond{ 1988, 1 },
LeapSecond{ 1990, 1 },
LeapSecond{ 1991, 1 },
LeapSecond{ 1992, 183 },
LeapSecond{ 1993, 182 },
LeapSecond{ 1994, 182 },
LeapSecond{ 1996, 1 },
LeapSecond{ 1997, 182 },
LeapSecond{ 1999, 1 },
LeapSecond{ 2006, 1 },
LeapSecond{ 2009, 1 },
LeapSecond{ 2012, 183 },
LeapSecond{ 2015, 182 },
LeapSecond{ 2017, 1 }
};
// Get the position of the last leap second before the desired date
LeapSecond date{ year, dayOfYear };
const auto it = std::lower_bound(LeapSeconds.begin(), LeapSeconds.end(), date);
// Get the position of the Epoch
const auto y2000 = std::lower_bound(
LeapSeconds.begin(),
LeapSeconds.end(),
LeapEpoch
);
// The distance between the two iterators gives us the number of leap years
const int nLeapSeconds = static_cast<int>(std::abs(std::distance(y2000, it)));
return nLeapSeconds;
}
int daysIntoGivenYear(int year, int month, int dayOfMonth) {
// month and dayCount are zero-based.
month -= 1;
int dayCount = dayOfMonth - 1;
constexpr int February = 1;
const bool isInLeapYear =
std::find(LeapYears.begin(), LeapYears.end(), year) != LeapYears.end();
for (int m = 0; m < month; ++m) {
dayCount += DaysOfMonths[m];
if (m == February && isInLeapYear) {
dayCount += 1;
}
}
return dayCount;
}
double calculateSemiMajorAxis(double meanMotion) {
constexpr const double GravitationalConstant = 6.6740831e-11;
constexpr const double MassEarth = 5.9721986e24;
constexpr const double muEarth = GravitationalConstant * MassEarth;
// Use Kepler's 3rd law to calculate semimajor axis
// a^3 / P^2 = mu / (2pi)^2
// <=> a = ((mu * P^2) / (2pi^2))^(1/3)
// with a = semimajor axis
// P = period in seconds
// mu = G*M_earth
const double period =
std::chrono::seconds(std::chrono::hours(24)).count() / meanMotion;
constexpr const double pisq = glm::pi<double>() * glm::pi<double>();
const double semiMajorAxis = pow(
(muEarth * period * period) / (4 * pisq),
1.0 / 3.0
);
// We need the semi major axis in km instead of m
return semiMajorAxis / 1000.0;
}
double epochFromSubstring(const std::string& epoch) {
// The epochString is in the form:
// YYDDD.DDDDDDDD
// With YY being the last two years of the launch epoch, the first DDD the day of
// the year and the remaning a fractional part of the day
// The main overview of this function:
// 1. Reconstruct the full year from the YY part
// 2. Calculate the number of days since the beginning of the year
// 3. Convert the number of days to a number of seconds
// 4. Get the number of leap seconds since January 1st, 2000 and remove them
// 5. Adjust for the fact the epoch starts on 1st Januaray at 12:00:00, not
// midnight
// According to https://celestrak.com/columns/v04n03/
// Apparently, US Space Command sees no need to change the two-line element set
// format yet since no artificial earth satellites existed prior to 1957. By their
// reasoning, two-digit years from 57-99 correspond to 1957-1999 and those from
// 00-56 correspond to 2000-2056. We'll see each other again in 2057!
// 1,2. Get the full year and days
auto [res, year, daysInYear] = scn::scan_tuple<int, double>(epoch, "{:2}{}");
if (!res) {
throw ghoul::RuntimeError(fmt::format("Error parsing epoch '{}'", epoch));
}
year += year > 57 ? 1900 : 2000;
const int daysSince2000 = countDays(year);
// 3
using namespace std::chrono;
const int SecondsPerDay = static_cast<int>(seconds(hours(24)).count());
//Need to subtract 1 from daysInYear since it is not a zero-based count
const double nSecondsSince2000 = (daysSince2000 + daysInYear - 1) * SecondsPerDay;
// 4
// We need to remove additional leap seconds past 2000 and add them prior to
// 2000 to sync up the time zones
const double nLeapSecondsOffset = countLeapSeconds(
year,
static_cast<int>(std::floor(daysInYear))
);
// 5
const double nSecondsEpochOffset =
static_cast<double>(seconds(hours(12)).count());
// Combine all of the values
return nSecondsSince2000 - nLeapSecondsOffset - nSecondsEpochOffset;
}
double epochFromYMDdSubstring(const std::string& epoch) {
// The epochString is in the form:
// YYYYMMDD.ddddddd
// With YYYY as the year, MM the month (1 - 12), DD the day of month (1-31),
// and dddd the fraction of that day.
// The main overview of this function:
// 1. Read the year value
// 2. Calculate the number of days since the beginning of the year
// 3. Convert the number of days to a number of seconds
// 4. Get the number of leap seconds since January 1st, 2000 and remove them
// 5. Adjust for the fact the epoch starts on 1st January at 12:00:00, not
// midnight
// 1, 2
auto [res, year, monthNum, dayOfMonthNum, fractionOfDay] =
scn::scan_tuple<int, int, int, double>(epoch, "{:4}{:2}{:2}{}");
if (!res) {
throw ghoul::RuntimeError(fmt::format("Error parsing epoch '{}'", epoch));
}
const int daysSince2000 = countDays(year);
int wholeDaysInto = daysIntoGivenYear(year, monthNum, dayOfMonthNum);
double daysInYear = static_cast<double>(wholeDaysInto) + fractionOfDay;
// 3
using namespace std::chrono;
const int SecondsPerDay = static_cast<int>(seconds(hours(24)).count());
//Need to subtract 1 from daysInYear since it is not a zero-based count
const double nSecondsSince2000 = (daysSince2000 + daysInYear - 1) * SecondsPerDay;
// 4
// We need to remove additional leap seconds past 2000 and add them prior to
// 2000 to sync up the time zones
const double nLeapSecondsOffset = -countLeapSeconds(
year,
static_cast<int>(std::floor(daysInYear))
);
// 5
const double offset = static_cast<double>(seconds(hours(12)).count());
// Combine all of the values
return nSecondsSince2000 + nLeapSecondsOffset - offset;
}
double epochFromOmmString(const std::string& epoch) {
// The epochString is in the form:
// YYYY-MM-DDThh:mm:ss[.d->d][Z]
// or
// YYYY-DDDThh:mm:ss[.d->d][Z]
// The main overview of this function:
// 0. Determine which type it is
// 1. Read the year value
// 2. Calculate the number of days since the beginning of the year
// 3. Convert the number of days to a number of seconds
// 4. Get the number of leap seconds since January 1st, 2000 and remove them
// 5. Add the hh:mm:ss component
// 6. Adjust for the fact the epoch starts on 1st January at 12:00:00, not
// midnight
std::string e = epoch;
if (e.back() == 'Z') {
e.pop_back();
}
struct {
int year;
int nDays;
int hours;
int minutes;
double seconds;
} date;
// 1, 2
const size_t pos = epoch.find('T');
if (pos == 10) {
// We have the first form
int month;
int days;
auto res = scn::scan(
epoch, "{:4}-{:2}-{:2}T{:2}:{:2}:{}",
date.year, month, days, date.hours, date.minutes, date.seconds
);
if (!res) {
throw ghoul::RuntimeError(fmt::format("Error parsing epoch '{}'", epoch));
}
date.nDays = daysIntoGivenYear(date.year, month, days);
}
else if (pos == 8) {
// We have the second form
auto res = scn::scan(
epoch, "{:4}-{:3}T{:2}:{:2}:{}",
date.year, date.nDays, date.hours, date.minutes, date.seconds
);
if (!res) {
throw ghoul::RuntimeError(fmt::format("Error parsing epoch '{}'", epoch));
}
}
else {
throw ghoul::RuntimeError(fmt::format("Malformed epoch string '{}'", epoch));
}
const int daysSince2000 = countDays(date.year);
// 3
using namespace std::chrono;
const int SecondsPerDay = static_cast<int>(seconds(hours(24)).count());
const double nSecondsSince2000 = (daysSince2000 + date.nDays) * SecondsPerDay;
// 4
// We need to remove additional leap seconds past 2000 and add them prior to
// 2000 to sync up the time zones
const double nLeapSecondsOffset = -countLeapSeconds(
date.year,
static_cast<int>(std::floor(date.nDays))
);
// 5
const long long totalSeconds =
std::chrono::seconds(std::chrono::hours(date.hours)).count() +
std::chrono::seconds(std::chrono::minutes(date.minutes)).count();
// 6
const long long offset = std::chrono::seconds(std::chrono::hours(12)).count();
// Combine all of the values
return
nSecondsSince2000 + totalSeconds + nLeapSecondsOffset - offset + date.seconds;
}
} // namespace
namespace openspace::kepler {
std::vector<Parameters> readTleFile(std::filesystem::path file) {
ghoul_assert(std::filesystem::is_regular_file(file), "File must exist");
std::vector<Parameters> result;
std::ifstream f;
f.open(file);
int lineNum = 1;
std::string header;
while (std::getline(f, header)) {
Parameters p;
// Header
p.name = header;
// First line
// Field Columns Content
// 1 01-01 Line number
// 2 03-07 Satellite number
// 3 08-08 Classification (U = Unclassified)
// 4 10-11 International Designator (Last two digits of launch year)
// 5 12-14 International Designator (Launch number of the year)
// 6 15-17 International Designator(piece of the launch) A
// 7 19-20 Epoch Year(last two digits of year)
// 8 21-32 Epoch(day of the year and fractional portion of the day)
// 9 34-43 First Time Derivative of the Mean Motion divided by two
// 10 45-52 Second Time Derivative of Mean Motion divided by six
// 11 54-61 BSTAR drag term(decimal point assumed)[10] - 11606 - 4
// 12 63-63 The "Ephemeris type"
// 13 65-68 Element set number.Incremented when a new TLE is generated
// 14 69-69 Checksum (modulo 10)
std::string firstLine;
std::getline(f, firstLine);
if (f.bad() || firstLine[0] != '1') {
throw ghoul::RuntimeError(fmt::format(
"Malformed TLE file '{}' at line {}", file, lineNum + 1
));
}
// The id only contains the last two digits of the launch year, so we have to
// patch it to the full year
{
std::string id = firstLine.substr(9, 6);
std::string prefix = [y = id.substr(0, 2)](){
int year = std::atoi(y.c_str());
return year >= 57 ? "19" : "20";
}();
p.id = fmt::format("{}{}-{}", prefix, id.substr(0, 2), id.substr(3));
}
p.epoch = epochFromSubstring(firstLine.substr(18, 14)); // should be 13?
// Second line
// Field Columns Content
// 1 01-01 Line number
// 2 03-07 Satellite number
// 3 09-16 Inclination (degrees)
// 4 18-25 Right ascension of the ascending node (degrees)
// 5 27-33 Eccentricity (decimal point assumed)
// 6 35-42 Argument of perigee (degrees)
// 7 44-51 Mean Anomaly (degrees)
// 8 53-63 Mean Motion (revolutions per day)
// 9 64-68 Revolution number at epoch (revolutions)
// 10 69-69 Checksum (modulo 10)
std::string secondLine;
std::getline(f, secondLine);
if (f.bad() || secondLine[0] != '2') {
throw ghoul::RuntimeError(fmt::format(
"Malformed TLE file '{}' at line {}", file, lineNum + 1
));
}
std::stringstream stream;
stream.exceptions(std::ios::failbit);
// Get inclination
stream.str(secondLine.substr(8, 8));
stream >> p.inclination;
stream.clear();
// Get Right ascension of the ascending node
stream.str(secondLine.substr(17, 8));
stream >> p.ascendingNode;
stream.clear();
// Get Eccentricity
stream.str("0." + secondLine.substr(26, 7));
stream >> p.eccentricity;
stream.clear();
// Get argument of periapsis
stream.str(secondLine.substr(34, 8));
stream >> p.argumentOfPeriapsis;
stream.clear();
// Get mean anomaly
stream.str(secondLine.substr(43, 8));
stream >> p.meanAnomaly;
stream.clear();
// Get mean motion
stream.str(secondLine.substr(52, 11));
float meanMotion;
stream >> meanMotion;
p.semiMajorAxis = calculateSemiMajorAxis(meanMotion);
p.period = std::chrono::seconds(std::chrono::hours(24)).count() / meanMotion;
result.push_back(p);
lineNum = lineNum + 3;
}
return result;
}
std::vector<Parameters> readOmmFile(std::filesystem::path file) {
ghoul_assert(std::filesystem::is_regular_file(file), "File must exist");
std::vector<Parameters> result;
std::ifstream f;
f.open(file);
int lineNum = 1;
std::optional<Parameters> current = std::nullopt;
std::string line;
while (std::getline(f, line)) {
if (line.empty()) {
continue;
}
// Tokenize the line
std::vector<std::string> parts = ghoul::tokenizeString(line, '=');
for (std::string& p : parts) {
ghoul::trimWhitespace(p);
}
if (parts.size() != 2) {
throw ghoul::RuntimeError(fmt::format(
"Malformed line '{}' at {}", line, lineNum
));
}
if (parts[0] == "CCSDS_OMM_VERS") {
if (parts[1] != "2.0") {
LWARNINGC(
"OMM",
fmt::format(
"Only version 2.0 is currently supported but found {}. "
"Parsing might fail",
parts[1]
)
);
}
// We start a new value so we need to store the last one...
if (current.has_value()) {
result.push_back(*current);
}
// ... and start a new one
current = Parameters();
}
ghoul_assert(current.has_value(), "No current element");
if (parts[0] == "OBJECT_NAME") {
current->name = parts[1];
}
else if (parts[0] == "OBJECT_ID") {
current->id = parts[1];
}
else if (parts[0] == "EPOCH") {
current->epoch = epochFromOmmString(parts[1]);
}
else if (parts[0] == "MEAN_MOTION") {
float mm = std::stof(parts[1]);
current->semiMajorAxis = calculateSemiMajorAxis(mm);
current->period = std::chrono::seconds(std::chrono::hours(24)).count() / mm;
}
else if (parts[0] == "SEMI_MAJOR_AXIS") {
}
else if (parts[0] == "ECCENTRICITY") {
current->eccentricity = std::stof(parts[1]);
}
else if (parts[0] == "INCLINATION") {
current->inclination = std::stof(parts[1]);
}
else if (parts[0] == "RA_OF_ASC_NODE") {
current->ascendingNode = std::stof(parts[1]);
}
else if (parts[0] == "ARG_OF_PERICENTER") {
current->argumentOfPeriapsis = std::stof(parts[1]);
}
else if (parts[0] == "MEAN_ANOMALY") {
current->meanAnomaly = std::stof(parts[1]);
}
}
if (current.has_value()) {
result.push_back(*current);
}
return result;
}
std::vector<Parameters> readSbdbFile(std::filesystem::path file) {
constexpr int NDataFields = 9;
constexpr std::string_view ExpectedHeader = "full_name,epoch_cal,e,a,i,om,w,ma,per";
ghoul_assert(std::filesystem::is_regular_file(file), "File must exist");
std::ifstream f;
f.open(file);
std::string line;
std::getline(f, line);
if (line != ExpectedHeader) {
throw ghoul::RuntimeError(fmt::format(
"Expected JPL SBDB file to start with '{}' but found '{}' instead",
ExpectedHeader, line
));
}
std::vector<Parameters> result;
while (std::getline(f, line)) {
constexpr double AuToKm = 1.496e8;
std::vector<std::string> parts = ghoul::tokenizeString(line, ',');
if (parts.size() != NDataFields) {
throw ghoul::RuntimeError(fmt::format(
"Malformed line {}, expected 8 data fields, got {}", line, parts.size()
));
}
Parameters p;
ghoul::trimWhitespace(parts[0]);
p.name = parts[0];
p.epoch = epochFromYMDdSubstring(parts[1]);
p.eccentricity = std::stod(parts[2]);
p.semiMajorAxis = std::stod(parts[3]) * AuToKm;
auto importAngleValue = [](const std::string& angle) {
if (angle.empty()) {
return 0.0;
}
double output = std::stod(angle);
output = std::fmod(output, 360.0);
if (output < 0.0) {
output += 360.0;
}
return output;
};
p.inclination = importAngleValue(parts[4]);
p.ascendingNode = importAngleValue(parts[5]);
p.argumentOfPeriapsis = importAngleValue(parts[6]);
p.meanAnomaly = importAngleValue(parts[7]);
p.period =
std::stod(parts[8]) * std::chrono::seconds(std::chrono::hours(24)).count();
result.push_back(std::move(p));
}
return result;
}
void saveCache(const std::vector<Parameters>& params, std::filesystem::path file) {
std::ofstream stream(file, std::ofstream::binary);
stream.write(reinterpret_cast<const char*>(&CurrentCacheVersion), sizeof(int8_t));
uint32_t size = static_cast<uint32_t>(params.size());
stream.write(reinterpret_cast<const char*>(&size), sizeof(uint32_t));
for (const Parameters& param : params) {
uint32_t nameLength = static_cast<uint32_t>(param.name.size());
stream.write(reinterpret_cast<const char*>(&nameLength), sizeof(uint32_t));
stream.write(param.name.data(), nameLength * sizeof(char));
uint32_t idLength = static_cast<uint32_t>(param.id.size());
stream.write(reinterpret_cast<const char*>(&idLength), sizeof(uint32_t));
stream.write(param.id.data(), idLength * sizeof(char));
stream.write(reinterpret_cast<const char*>(&param.inclination), sizeof(double));
stream.write(reinterpret_cast<const char*>(&param.semiMajorAxis), sizeof(double));
stream.write(reinterpret_cast<const char*>(&param.ascendingNode), sizeof(double));
stream.write(reinterpret_cast<const char*>(&param.eccentricity), sizeof(double));
stream.write(
reinterpret_cast<const char*>(&param.argumentOfPeriapsis),
sizeof(double)
);
stream.write(reinterpret_cast<const char*>(&param.meanAnomaly), sizeof(double));
stream.write(reinterpret_cast<const char*>(&param.epoch), sizeof(double));
stream.write(reinterpret_cast<const char*>(&param.period), sizeof(double));
}
}
std::optional<std::vector<Parameters>> loadCache(std::filesystem::path file) {
std::ifstream stream(file, std::ifstream::binary);
int8_t version = 0;
stream.read(reinterpret_cast<char*>(&version), sizeof(int8_t));
if (version != CurrentCacheVersion) {
LINFO("The format of the cached file has changed");
return std::nullopt;
}
uint32_t size = 0;
stream.read(reinterpret_cast<char*>(&size), sizeof(uint32_t));
std::vector<Parameters> res;
res.reserve(size);
for (uint32_t i = 0; i < size; i++) {
Parameters param;
uint32_t nameLength = 0;
stream.read(reinterpret_cast<char*>(&nameLength), sizeof(uint32_t));
param.name.resize(nameLength);
stream.read(param.name.data(), nameLength * sizeof(char));
uint32_t idLength = 0;
stream.read(reinterpret_cast<char*>(&idLength), sizeof(uint32_t));
param.id.resize(idLength);
stream.read(param.id.data(), idLength * sizeof(char));
stream.read(reinterpret_cast<char*>(&param.inclination), sizeof(double));
stream.read(reinterpret_cast<char*>(&param.semiMajorAxis), sizeof(double));
stream.read(reinterpret_cast<char*>(&param.ascendingNode), sizeof(double));
stream.read(reinterpret_cast<char*>(&param.eccentricity), sizeof(double));
stream.read(reinterpret_cast<char*>(&param.argumentOfPeriapsis), sizeof(double));
stream.read(reinterpret_cast<char*>(&param.meanAnomaly), sizeof(double));
stream.read(reinterpret_cast<char*>(&param.epoch), sizeof(double));
stream.read(reinterpret_cast<char*>(&param.period), sizeof(double));
res.push_back(std::move(param));
}
return res;
}
std::vector<Parameters> readFile(std::filesystem::path file, Format format) {
std::filesystem::path cachedFile = FileSys.cacheManager()->cachedFilename(file);
if (std::filesystem::is_regular_file(cachedFile)) {
LINFO(fmt::format(
"Cached file {} used for Kepler file {}", cachedFile, file
));
std::optional<std::vector<Parameters>> res = loadCache(cachedFile);
if (res.has_value()) {
return *res;
}
// If there is no value in the optional, the cached loading failed
}
std::vector<Parameters> res;
switch (format) {
case Format::TLE:
res = readTleFile(file);
break;
case Format::OMM:
res = readOmmFile(file);
break;
case Format::SBDB:
res = readSbdbFile(file);
break;
default:
throw ghoul::MissingCaseException();
}
LINFO(fmt::format("Saving cache {} for Kepler file {}", cachedFile, file));
saveCache(res, cachedFile);
return res;
}
} // namespace openspace::kepler
+110
View File
@@ -0,0 +1,110 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_SPACE___KEPLER___H__
#define __OPENSPACE_MODULE_SPACE___KEPLER___H__
#include <filesystem>
#include <string>
#include <vector>
namespace openspace::kepler {
struct Parameters {
// Some human-readable name for the object represented by this kepler parameter set
std::string name;
// Some form of unique identifier for the object represented by this data
std::string id;
double inclination = 0.0;
double semiMajorAxis = 0.0;
double ascendingNode = 0.0;
double eccentricity = 0.0;
double argumentOfPeriapsis = 0.0;
double meanAnomaly = 0.0;
double epoch = 0.0;
double period = 0.0;
};
/**
* Reads the object information from the provided \p file and returns them as individual
* values.
*
* \param file The file to the TLE file. This file must be a valid file
* \return Information about all of the contained objects in the \p file
*
* \pre \p file must be a file and must exist
* \throw ghoul::RuntimeError If the provided \p file is not a valid TLE file
*/
std::vector<Parameters> readTleFile(std::filesystem::path file);
/**
* Reads the object information from the provided \p file and returns them as individual
* values.
*
* \param file The file to the OMM file. This file must be a valid file
* \return Information about all of the contained objects in the \p file
*
* \pre \p file must be a file and must exist
* \throw ghoul::RuntimeError If the provided \p file is not a valid OMM file
*/
std::vector<Parameters> readOmmFile(std::filesystem::path file);
/**
* Reads the object information from a CSV file following JPL's Small Body Database
* format, which provides the Epoch, eccentricity, semi-major axis (in AU), inclination,
* ascending node, argument of periapsis, mean anomaly, and period in that order.
*
* \param file The CSV file containing the information about the objects
* \return Information about all of the contained objects in the \p file
*
* \pre \p file must be a file and must exist
* \throw ghoul::RuntimeError If the provided \p is not a valid JPL SBDB CSV format
*/
std::vector<Parameters> readSbdbFile(std::filesystem::path file);
/**
* The different formats that the #readFile function is capable of loading
*/
enum class Format {
TLE,
OMM,
SBDB
};
/**
* Reads the object information from the provided file.
*
* \param file The file containing the information about the objects
* \param format The format of the provided \p file
* \return Information about all of the contained objects in the \p file
*
* \pre \p file must be a file and must exist
* \throw ghoul::RuntimeError If the provided \p is not in the provided file
*/
std::vector<Parameters> readFile(std::filesystem::path file, Format format);
} // namespace openspace::kepler
#endif // __OPENSPACE_MODULE_SPACE___KEPLER___H__
@@ -25,7 +25,6 @@
#include <modules/space/rendering/renderableorbitalkepler.h>
#include <modules/space/translation/keplertranslation.h>
#include <modules/space/translation/tletranslation.h>
#include <modules/space/spacemodule.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/rendering/renderengine.h>
@@ -42,130 +41,10 @@
#include <chrono>
#include <fstream>
#include <math.h>
#include <random>
#include <vector>
namespace {
constexpr std::array<int, 36> LeapYears = {
1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996,
2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040,
2044, 2048, 2052, 2056
};
constexpr std::array<int, 12> DaysOfMonths = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
// Find the position of the current year in the vector; its position in
// the vector gives the number of leap seconds
struct LeapSecond {
int year;
int dayOfYear;
bool operator<(const LeapSecond& rhs) const {
return std::tie(year, dayOfYear) < std::tie(rhs.year, rhs.dayOfYear);
}
};
constexpr LeapSecond LeapEpoch = { 2000, 1 };
// List taken from: https://www.ietf.org/timezones/data/leap-seconds.list
constexpr std::array<LeapSecond, 28> LeapSeconds = {
LeapSecond{ 1972, 1 },
LeapSecond{ 1972, 183 },
LeapSecond{ 1973, 1 },
LeapSecond{ 1974, 1 },
LeapSecond{ 1975, 1 },
LeapSecond{ 1976, 1 },
LeapSecond{ 1977, 1 },
LeapSecond{ 1978, 1 },
LeapSecond{ 1979, 1 },
LeapSecond{ 1980, 1 },
LeapSecond{ 1981, 182 },
LeapSecond{ 1982, 182 },
LeapSecond{ 1983, 182 },
LeapSecond{ 1985, 182 },
LeapSecond{ 1988, 1 },
LeapSecond{ 1990, 1 },
LeapSecond{ 1991, 1 },
LeapSecond{ 1992, 183 },
LeapSecond{ 1993, 182 },
LeapSecond{ 1994, 182 },
LeapSecond{ 1996, 1 },
LeapSecond{ 1997, 182 },
LeapSecond{ 1999, 1 },
LeapSecond{ 2006, 1 },
LeapSecond{ 2009, 1 },
LeapSecond{ 2012, 183 },
LeapSecond{ 2015, 182 },
LeapSecond{ 2017, 1 }
};
// Count the number of full days since the beginning of 2000 to the beginning of
// the parameter 'year'
int countDays(int year) {
// Find the position of the current year in the vector, the difference
// between its position and the position of 2000 (for J2000) gives the
// number of leap years
constexpr int Epoch = 2000;
constexpr int DaysRegularYear = 365;
constexpr int DaysLeapYear = 366;
if (year == Epoch) {
return 0;
}
// Get the position of the most recent leap year
const auto lb = std::lower_bound(LeapYears.begin(), LeapYears.end(), year);
// Get the position of the epoch
const auto y2000 = std::find(LeapYears.begin(), LeapYears.end(), Epoch);
// The distance between the two iterators gives us the number of leap years
const int nLeapYears = static_cast<int>(std::abs(std::distance(y2000, lb)));
const int nYears = std::abs(year - Epoch);
const int nRegularYears = nYears - nLeapYears;
// Get the total number of days as the sum of leap years + non leap years
const int result = nRegularYears * DaysRegularYear + nLeapYears * DaysLeapYear;
return result;
}
// Returns the number of leap seconds that lie between the {year, dayOfYear}
// time point and { 2000, 1 }
int countLeapSeconds(int year, int dayOfYear) {
// Get the position of the last leap second before the desired date
LeapSecond date{ year, dayOfYear };
const auto it = std::lower_bound(LeapSeconds.begin(), LeapSeconds.end(), date);
// Get the position of the Epoch
const auto y2000 = std::lower_bound(
LeapSeconds.begin(),
LeapSeconds.end(),
LeapEpoch
);
// The distance between the two iterators gives us the number of leap years
const int nLeapSeconds = static_cast<int>(std::abs(std::distance(y2000, it)));
return nLeapSeconds;
}
int daysIntoGivenYear(int year, int month, int dayOfMonth) {
//month and dayCount are zero-based.
month -= 1;
int dayCount = dayOfMonth - 1;
const int February = 1;
const bool isInLeapYear =
std::find(LeapYears.begin(), LeapYears.end(), year)
!= LeapYears.end();
for (int m = 0; m < month; ++m) {
dayCount += DaysOfMonths[m];
if (m == February && isInLeapYear) {
dayCount += 1;
}
}
return dayCount;
}
constexpr openspace::properties::Property::PropertyInfo PathInfo = {
"Path",
"Path",
@@ -213,10 +92,30 @@ namespace {
"Contiguous Size of Render Block",
"Number of objects to render sequentially from StartRenderIdx"
};
constexpr openspace::properties::Property::PropertyInfo ContiguousModeInfo = {
"ContiguousMode",
"Contiguous Mode",
"If enabled, then the contiguous set of objects starting from StartRenderIdx "
"of size RenderSize will be rendered. If disabled, then the number of objects "
"defined by UpperLimit will rendered from an evenly dispersed sample of the "
"full length of the data file."
};
struct [[codegen::Dictionary(RenderableOrbitalKepler)]] Parameters {
// [[codegen::verbatim(PathInfo.description)]]
std::string path;
std::filesystem::path path;
enum class [[codegen::map(openspace::kepler::Format)]] Format {
// A NORAD-style Two-Line element
TLE,
// Orbit Mean-Elements Message in the KVN notation
OMM,
// JPL's Small Bodies Database
SBDB
};
// The file format that is contained in the file
Format format;
// [[codegen::verbatim(SegmentQualityInfo.description)]]
int segmentQuality;
@@ -235,6 +134,9 @@ namespace {
// [[codegen::verbatim(RenderSizeInfo.description)]]
std::optional<int> renderSize;
// [[codegen::verbatim(ContiguousModeInfo.description)]]
std::optional<bool> contiguousMode;
};
#include "renderableorbitalkepler_codegen.cpp"
} // namespace
@@ -245,140 +147,20 @@ documentation::Documentation RenderableOrbitalKepler::Documentation() {
return codegen::doc<Parameters>("space_renderableorbitalkepler");
}
double RenderableOrbitalKepler::calculateSemiMajorAxis(double meanMotion) const {
constexpr double GravitationalConstant = 6.6740831e-11;
constexpr double MassEarth = 5.9721986e24;
constexpr double muEarth = GravitationalConstant * MassEarth;
// Use Kepler's 3rd law to calculate semimajor axis
// a^3 / P^2 = mu / (2pi)^2
// <=> a = ((mu * P^2) / (2pi^2))^(1/3)
// with a = semimajor axis
// P = period in seconds
// mu = G*M_earth
const double period =
std::chrono::seconds(std::chrono::hours(24)).count() / meanMotion;
constexpr double pisq = glm::pi<double>() * glm::pi<double>();
const double semiMajorAxis = pow((muEarth * period*period) / (4 * pisq), 1.0 / 3.0);
// We need the semi major axis in km instead of m
return semiMajorAxis / 1000.0;
}
double RenderableOrbitalKepler::epochFromSubstring(const std::string& epochString) const {
// The epochString is in the form:
// YYDDD.DDDDDDDD
// With YY being the last two years of the launch epoch, the first DDD the day
// of the year and the remaning a fractional part of the day
// The main overview of this function:
// 1. Reconstruct the full year from the YY part
// 2. Calculate the number of days since the beginning of the year
// 3. Convert the number of days to a number of seconds
// 4. Get the number of leap seconds since January 1st, 2000 and remove them
// 5. Adjust for the fact the epoch starts on 1st Januaray at 12:00:00, not
// midnight
// According to https://celestrak.com/columns/v04n03/
// Apparently, US Space Command sees no need to change the two-line element
// set format yet since no artificial earth satellites existed prior to 1957.
// By their reasoning, two-digit years from 57-99 correspond to 1957-1999 and
// those from 00-56 correspond to 2000-2056. We'll see each other again in 057!
// 1. Get the full year
std::string yearPrefix =
std::atoi(epochString.substr(0, 2).c_str()) > 57 ? "19" : "20";
const int year = std::atoi((yearPrefix + epochString.substr(0, 2)).c_str());
const int daysSince2000 = countDays(year);
// 2.
double daysInYear = std::atof(epochString.substr(2).c_str());
// 3
using namespace std::chrono;
const int SecondsPerDay = static_cast<int>(seconds(hours(24)).count());
//Need to subtract 1 from daysInYear since it is not a zero-based count
const double nSecondsSince2000 = (daysSince2000 + daysInYear - 1) * SecondsPerDay;
// 4
// We need to remove additional leap seconds past 2000 and add them prior to
// 2000 to sync up the time zones
const double nLeapSecondsOffset = -countLeapSeconds(
year,
static_cast<int>(std::floor(daysInYear))
);
// 5
const double nSecondsEpochOffset = static_cast<double>(seconds(hours(12)).count());
// Combine all of the values
const double epoch = nSecondsSince2000 + nLeapSecondsOffset - nSecondsEpochOffset;
return epoch;
}
double RenderableOrbitalKepler::epochFromYMDdSubstring(const std::string& epochString) {
// The epochString is in the form:
// YYYYMMDD.ddddddd
// With YYYY as the year, MM the month (1 - 12), DD the day of month (1-31),
// and dddd the fraction of that day.
// The main overview of this function:
// 1. Read the year value
// 2. Calculate the number of days since the beginning of the year
// 3. Convert the number of days to a number of seconds
// 4. Get the number of leap seconds since January 1st, 2000 and remove them
// 5. Adjust for the fact the epoch starts on 1st January at 12:00:00, not
// midnight
// 1
int year = std::atoi(epochString.substr(0, 4).c_str());
const int daysSince2000 = countDays(year);
// 2.
int monthNum = std::atoi(epochString.substr(4, 2).c_str());
int dayOfMonthNum = std::atoi(epochString.substr(6, 2).c_str());
int wholeDaysInto = daysIntoGivenYear(year, monthNum, dayOfMonthNum);
double fractionOfDay = std::atof(epochString.substr(9, 7).c_str());
double daysInYear = static_cast<double>(wholeDaysInto) + fractionOfDay;
// 3
using namespace std::chrono;
const int SecondsPerDay = static_cast<int>(seconds(hours(24)).count());
//Need to subtract 1 from daysInYear since it is not a zero-based count
const double nSecondsSince2000 = (daysSince2000 + daysInYear - 1) * SecondsPerDay;
// 4
// We need to remove additional leap seconds past 2000 and add them prior to
// 2000 to sync up the time zones
const double nLeapSecondsOffset = -countLeapSeconds(
year,
static_cast<int>(std::floor(daysInYear))
);
// 5
const double nSecondsEpochOffset = static_cast<double>(seconds(hours(12)).count());
// Combine all of the values
const double epoch = nSecondsSince2000 + nLeapSecondsOffset - nSecondsEpochOffset;
return epoch;
}
RenderableOrbitalKepler::RenderableOrbitalKepler(const ghoul::Dictionary& dict)
: Renderable(dict)
, _segmentQuality(SegmentQualityInfo, 2, 1, 10)
, _startRenderIdx(StartRenderIdxInfo, 0, 0, 1)
, _sizeRender(RenderSizeInfo, 1, 1, 2)
, _path(PathInfo)
, _contiguousMode(ContiguousModeInfo, false)
{
_reinitializeTrailBuffers = std::function<void()>([this] { initializeGL(); });
const Parameters p = codegen::bake<Parameters>(dict);
addProperty(_opacity);
_segmentQuality = static_cast<unsigned int>(p.segmentQuality);
_segmentQuality.onChange(_reinitializeTrailBuffers);
_segmentQuality.onChange([this]() { initializeGL(); });
addProperty(_segmentQuality);
_appearance.lineColor = p.color;
@@ -386,13 +168,37 @@ RenderableOrbitalKepler::RenderableOrbitalKepler(const ghoul::Dictionary& dict)
_appearance.lineWidth = p.lineWidth.value_or(2.f);
addPropertySubOwner(_appearance);
_path = p.path;
_path.onChange(_reinitializeTrailBuffers);
_path = p.path.string();
_path.onChange([this]() { initializeGL(); });
addProperty(_path);
_format = codegen::map<kepler::Format>(p.format);
_startRenderIdx = p.startRenderIdx.value_or(0);
_startRenderIdx.onChange([this]() {
if (_contiguousMode) {
if ((_numObjects - _startRenderIdx) < _sizeRender) {
_sizeRender = static_cast<unsigned int>(_numObjects - _startRenderIdx);
}
_updateDataBuffersAtNextRender = true;
}
});
addProperty(_startRenderIdx);
_sizeRender = p.renderSize.value_or(0u);
_sizeRender.onChange([this]() {
if (_contiguousMode) {
if (_sizeRender > (_numObjects - _startRenderIdx)) {
_startRenderIdx = static_cast<unsigned int>(_numObjects - _sizeRender);
}
}
_updateDataBuffersAtNextRender = true;
});
addProperty(_sizeRender);
_contiguousMode = p.contiguousMode.value_or(false);
_contiguousMode.onChange([this]() { _updateDataBuffersAtNextRender = true; });
addProperty(_contiguousMode);
}
void RenderableOrbitalKepler::initializeGL() {
@@ -420,14 +226,6 @@ void RenderableOrbitalKepler::initializeGL() {
_uniformCache.opacity = _programObject->uniformLocation("opacity");
updateBuffers();
double maxSemiMajorAxis = 0.0;
for (const KeplerParameters& kp : _data) {
if (kp.semiMajorAxis > maxSemiMajorAxis) {
maxSemiMajorAxis = kp.semiMajorAxis;
}
}
setBoundingSphere(maxSemiMajorAxis * 1000);
}
void RenderableOrbitalKepler::deinitializeGL() {
@@ -447,14 +245,16 @@ bool RenderableOrbitalKepler::isReady() const {
return _programObject != nullptr;
}
void RenderableOrbitalKepler::render(const RenderData& data, RendererTasks&) {
if (_data.empty()) {
return;
}
void RenderableOrbitalKepler::update(const UpdateData&) {
if (_updateDataBuffersAtNextRender) {
_updateDataBuffersAtNextRender = false;
initializeGL();
updateBuffers();
}
}
void RenderableOrbitalKepler::render(const RenderData& data, RendererTasks&) {
if (_vertexBufferData.empty()) {
return;
}
_programObject->activate();
@@ -472,9 +272,7 @@ void RenderableOrbitalKepler::render(const RenderData& data, RendererTasks&) {
);
// Because we want the property to work similar to the planet trails
const float fade = static_cast<float>(
pow(_appearance.lineFade.maxValue() - _appearance.lineFade, 2.0)
);
const float fade = pow(_appearance.lineFade.maxValue() - _appearance.lineFade, 2.f);
_programObject->setUniform(_uniformCache.projection, data.camera.projectionMatrix());
_programObject->setUniform(_uniformCache.color, _appearance.lineColor);
@@ -482,7 +280,7 @@ void RenderableOrbitalKepler::render(const RenderData& data, RendererTasks&) {
glLineWidth(_appearance.lineWidth);
const size_t nrOrbits = _data.size();
const size_t nrOrbits = _segmentSize.size();
gl::GLint vertices = 0;
//glDepthMask(false);
@@ -499,21 +297,83 @@ void RenderableOrbitalKepler::render(const RenderData& data, RendererTasks&) {
}
void RenderableOrbitalKepler::updateBuffers() {
readDataFile(_path);
std::vector<kepler::Parameters> parameters = kepler::readFile(
_path.value(),
_format
);
_numObjects = parameters.size();
if (_startRenderIdx < 0 || _startRenderIdx >= _numObjects) {
throw ghoul::RuntimeError(fmt::format(
"Start index {} out of range [0, {}]", _startRenderIdx, _numObjects
));
}
long long endElement = _startRenderIdx + _sizeRender - 1;
endElement = (endElement >= _numObjects) ? _numObjects - 1 : endElement;
if (endElement < 0 || endElement >= _numObjects) {
throw ghoul::RuntimeError(fmt::format(
"End index {} out of range [0, {}]", endElement, _numObjects
));
}
_startRenderIdx.setMaxValue(static_cast<unsigned int>(_numObjects - 1));
_sizeRender.setMaxValue(static_cast<unsigned int>(_numObjects));
if (_sizeRender == 0u) {
_sizeRender = static_cast<unsigned int>(_numObjects);
}
if (_contiguousMode) {
if (_startRenderIdx >= parameters.size() ||
(_startRenderIdx + _sizeRender) >= parameters.size())
{
throw ghoul::RuntimeError(fmt::format(
"Tried to load {} objects but only {} are available",
_startRenderIdx + _sizeRender, parameters.size()
));
}
// Extract subset that starts at _startRenderIdx and contains _sizeRender obejcts
parameters = std::vector<kepler::Parameters>(
parameters.begin() + _startRenderIdx,
parameters.begin() + _startRenderIdx + _sizeRender
);
}
else {
// First shuffle the whole array
std::default_random_engine rng;
std::shuffle(parameters.begin(), parameters.end(), rng);
// Then take the first _sizeRender values
parameters = std::vector<kepler::Parameters>(
parameters.begin(),
parameters.begin() + _sizeRender
);
}
_segmentSize.clear();
for (const kepler::Parameters& p : parameters) {
const double scale = static_cast<double>(_segmentQuality) * 10.0;
_segmentSize.push_back(
static_cast<size_t>(scale + (scale / pow(1 - p.eccentricity, 1.2)))
);
}
size_t nVerticesTotal = 0;
int numOrbits = static_cast<int>(_data.size());
int numOrbits = static_cast<int>(parameters.size());
for (int i = 0; i < numOrbits; ++i) {
nVerticesTotal += _segmentSize[i] + 1;
}
_vertexBufferData.resize(nVerticesTotal);
size_t vertexBufIdx = 0;
KeplerTranslation keplerTranslator;
for (int orbitIdx = 0; orbitIdx < numOrbits; ++orbitIdx) {
const KeplerParameters& orbit = _data[orbitIdx];
const kepler::Parameters& orbit = parameters[orbitIdx];
_keplerTranslator.setKeplerElements(
keplerTranslator.setKeplerElements(
orbit.eccentricity,
orbit.semiMajorAxis,
orbit.inclination,
@@ -526,9 +386,9 @@ void RenderableOrbitalKepler::updateBuffers() {
for (size_t j = 0 ; j < (_segmentSize[orbitIdx] + 1); ++j) {
double timeOffset = orbit.period *
static_cast<double>(j)/ static_cast<double>(_segmentSize[orbitIdx]);
static_cast<double>(j) / static_cast<double>(_segmentSize[orbitIdx]);
glm::dvec3 position = _keplerTranslator.position({
glm::dvec3 position = keplerTranslator.position({
{},
Time(timeOffset + orbit.epoch),
Time(0.0)
@@ -556,7 +416,7 @@ void RenderableOrbitalKepler::updateBuffers() {
);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(TrailVBOLayout), nullptr);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(TrailVBOLayout), nullptr);
glEnableVertexAttribArray(1);
glVertexAttribPointer(
@@ -569,6 +429,14 @@ void RenderableOrbitalKepler::updateBuffers() {
);
glBindVertexArray(0);
double maxSemiMajorAxis = 0.0;
for (const kepler::Parameters& kp : parameters) {
if (kp.semiMajorAxis > maxSemiMajorAxis) {
maxSemiMajorAxis = kp.semiMajorAxis;
}
}
setBoundingSphere(maxSemiMajorAxis * 1000);
}
} // namespace opensapce
@@ -28,6 +28,7 @@
#include <openspace/rendering/renderable.h>
#include <modules/base/rendering/renderabletrail.h>
#include <modules/space/kepler.h>
#include <modules/space/translation/keplertranslation.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/scalar/uintproperty.h>
@@ -47,65 +48,21 @@ public:
void deinitializeGL() override;
bool isReady() const override;
void update(const UpdateData& data) override;
void render(const RenderData& data, RendererTasks& rendererTask) override;
/**
* Reads the provided data file and calls the KeplerTranslation::setKeplerElments
* method with the correct values. If \p filename is a valid data file but contains
* disallowed values (see KeplerTranslation::setKeplerElements), a
* KeplerTranslation::RangeError is thrown.
*
* \param filename The path to the file that contains the data file.
*
* \throw ghoul::RuntimeError if the data file does not exist or there is a
* problem with its format.
* \pre The \p filename must exist
*/
virtual void readDataFile(const std::string& filename) = 0;
protected:
static documentation::Documentation Documentation();
double calculateSemiMajorAxis(double meanMotion) const;
double epochFromSubstring(const std::string& epochString) const;
double epochFromYMDdSubstring(const std::string& epochString);
private:
void updateBuffers();
std::function<void()> _reinitializeTrailBuffers;
std::function<void()> _updateStartRenderIdxSelect;
std::function<void()> _updateRenderSizeSelect;
struct KeplerParameters {
double inclination = 0.0;
double semiMajorAxis = 0.0;
double ascendingNode = 0.0;
double eccentricity = 0.0;
double argumentOfPeriapsis = 0.0;
double meanAnomaly = 0.0;
double meanMotion = 0.0;
double epoch = 0.0;
double period = 0.0;
};
bool _updateDataBuffersAtNextRender = false;
std::streamoff _numObjects;
bool _isFileReadinitialized = false;
inline static constexpr double convertAuToKm = 1.496e8;
inline static constexpr double convertDaysToSecs = 86400.0;
std::vector<KeplerParameters> _data;
std::streamoff _numObjects;
std::vector<size_t> _segmentSize;
properties::UIntProperty _segmentQuality;
properties::UIntProperty _startRenderIdx;
properties::UIntProperty _sizeRender;
properties::Property::OnChangeHandle _startRenderIdxCallbackHandle;
properties::Property::OnChangeHandle _sizeRenderCallbackHandle;
private:
struct Vertex {
glm::vec3 position = glm::vec3(0.f);
glm::vec3 color = glm::vec3(0.f);
glm::vec2 texcoord = glm::vec2(0.f);
};
/// The layout of the VBOs
struct TrailVBOLayout {
@@ -117,20 +74,17 @@ private:
double period = 0.0;
};
KeplerTranslation _keplerTranslator;
/// The backend storage for the vertex buffer object containing all points for this
/// trail.
/// The backend storage for the vertex buffer object containing all points
std::vector<TrailVBOLayout> _vertexBufferData;
GLuint _vertexArray;
GLuint _vertexBuffer;
ghoul::opengl::ProgramObject* _programObject;
properties::StringProperty _path;
properties::BoolProperty _contiguousMode;
kepler::Format _format;
RenderableTrail::Appearance _appearance;
glm::vec3 _position = glm::vec3(0.f);
UniformCache(modelView, projection, lineFade, inGameTime, color, opacity,
numberOfSegments) _uniformCache;
@@ -1,253 +0,0 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/space/rendering/renderablesatellites.h>
#include <modules/space/translation/keplertranslation.h>
#include <modules/space/translation/tletranslation.h>
#include <modules/space/spacemodule.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/engine/globals.h>
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/util/time.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/filesystem/file.h>
#include <ghoul/misc/csvreader.h>
#include <ghoul/opengl/programobject.h>
#include <ghoul/logging/logmanager.h>
#include <chrono>
#include <math.h>
#include <filesystem>
#include <fstream>
#include <vector>
namespace {
constexpr std::string_view _loggerCat = "Satellites";
constexpr openspace::properties::Property::PropertyInfo SegmentsInfo = {
"Segments",
"Segments",
"The number of segments to use for each orbit ellipse"
};
struct [[codegen::Dictionary(RenderableSatellites)]] Parameters {
// [[codegen::verbatim(SegmentsInfo.description)]]
double segments;
};
#include "renderablesatellites_codegen.cpp"
}
namespace openspace {
documentation::Documentation RenderableSatellites::Documentation() {
return codegen::doc<Parameters>(
"space_renderablesatellites",
RenderableOrbitalKepler::Documentation()
);
}
RenderableSatellites::RenderableSatellites(const ghoul::Dictionary& dictionary)
: RenderableOrbitalKepler(dictionary)
{
// Commented out right now as its not super clear how it works with inheritance. We'd
// probably want a codegen::check function that only does the checking without
// actually creating a Parameter objects
// codegen::bake<Parameters>(dictionary);
addProperty(_startRenderIdx);
addProperty(_sizeRender);
_updateStartRenderIdxSelect = [this]() {
if ((_numObjects - _startRenderIdx) < _sizeRender) {
_sizeRender = static_cast<unsigned int>(_numObjects - _startRenderIdx);
}
updateBuffers();
};
_updateRenderSizeSelect = [this]() {
if (_sizeRender > (_numObjects - _startRenderIdx)) {
_startRenderIdx = static_cast<unsigned int>(_numObjects - _sizeRender);
}
updateBuffers();
};
_startRenderIdxCallbackHandle = _startRenderIdx.onChange(_updateStartRenderIdxSelect);
_sizeRenderCallbackHandle = _sizeRender.onChange(_updateRenderSizeSelect);
}
void RenderableSatellites::readDataFile(const std::string& filename) {
if (!std::filesystem::is_regular_file(filename)) {
throw ghoul::RuntimeError(fmt::format(
"Satellite TLE file {} does not exist", filename
));
}
_data.clear();
_segmentSize.clear();
std::ifstream file;
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
file.open(filename);
std::streamoff numberOfLines = std::count(
std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>(),
'\n'
);
file.seekg(std::ios_base::beg); // reset iterator to beginning of file
_numObjects = numberOfLines / nLineEntriesPerSatellite;
if (!_isFileReadinitialized) {
_isFileReadinitialized = true;
initializeFileReading();
}
std::string line = "-";
std::string name;
long long endElement = _startRenderIdx + _sizeRender - 1;
endElement = (endElement >= _numObjects) ? _numObjects - 1 : endElement;
//Burn lines if not starting at first element
for (unsigned int k = 0; k < _startRenderIdx; ++k) {
skipSingleEntryInFile(file);
}
for (std::streamoff i = _startRenderIdx; i <= endElement; i++) {
//Read title line
std::getline(file, name);
KeplerParameters keplerElements;
std::getline(file, line);
if (line[0] == '1') {
// First line
// Field Columns Content
// 1 01-01 Line number
// 2 03-07 Satellite number
// 3 08-08 Classification (U = Unclassified)
// 4 10-11 International Designator (Last two digits of launch year)
// 5 12-14 International Designator (Launch number of the year)
// 6 15-17 International Designator(piece of the launch) A
name += " " + line.substr(2, 15);
if (_startRenderIdx == i && _sizeRender == 1) {
LINFO(fmt::format(
"Set render block to start at object {}",
name
));
}
// 7 19-20 Epoch Year(last two digits of year)
// 8 21-32 Epoch(day of the year and fractional portion of the day)
// 9 34-43 First Time Derivative of the Mean Motion divided by two
// 10 45-52 Second Time Derivative of Mean Motion divided by six
// 11 54-61 BSTAR drag term(decimal point assumed)[10] - 11606 - 4
// 12 63-63 The "Ephemeris type"
// 13 65-68 Element set number.Incremented when a new TLE is generated
// 14 69-69 Checksum (modulo 10)
keplerElements.epoch = epochFromSubstring(line.substr(18, 14));
}
else {
throw ghoul::RuntimeError(fmt::format(
"File {} entry {} does not have '1' header", filename, i + 1
));
}
std::getline(file, line);
if (line[0] == '2') {
// Second line
// Field Columns Content
// 1 01-01 Line number
// 2 03-07 Satellite number
// 3 09-16 Inclination (degrees)
// 4 18-25 Right ascension of the ascending node (degrees)
// 5 27-33 Eccentricity (decimal point assumed)
// 6 35-42 Argument of perigee (degrees)
// 7 44-51 Mean Anomaly (degrees)
// 8 53-63 Mean Motion (revolutions per day)
// 9 64-68 Revolution number at epoch (revolutions)
// 10 69-69 Checksum (modulo 10)
std::stringstream stream;
stream.exceptions(std::ios::failbit);
// Get inclination
stream.str(line.substr(8, 8));
stream >> keplerElements.inclination;
stream.clear();
// Get Right ascension of the ascending node
stream.str(line.substr(17, 8));
stream >> keplerElements.ascendingNode;
stream.clear();
// Get Eccentricity
stream.str("0." + line.substr(26, 7));
stream >> keplerElements.eccentricity;
stream.clear();
// Get argument of periapsis
stream.str(line.substr(34, 8));
stream >> keplerElements.argumentOfPeriapsis;
stream.clear();
// Get mean anomaly
stream.str(line.substr(43, 8));
stream >> keplerElements.meanAnomaly;
stream.clear();
// Get mean motion
stream.str(line.substr(52, 11));
stream >> keplerElements.meanMotion;
}
else {
throw ghoul::RuntimeError(fmt::format(
"File {} entry {} does not have '2' header", filename, i + 1
));
}
// Calculate the semi major axis based on the mean motion using kepler's laws
keplerElements.semiMajorAxis = calculateSemiMajorAxis(keplerElements.meanMotion);
using namespace std::chrono;
double period = seconds(hours(24)).count() / keplerElements.meanMotion;
keplerElements.period = period;
_data.push_back(keplerElements);
_segmentSize.push_back(_segmentQuality * 16);
}
file.close();
}
void RenderableSatellites::initializeFileReading() {
_startRenderIdx.setMaxValue(static_cast<unsigned int>(_numObjects - 1));
_sizeRender.setMaxValue(static_cast<unsigned int>(_numObjects));
if (_sizeRender == 0u) {
_sizeRender = static_cast<unsigned int>(_numObjects);
}
}
void RenderableSatellites::skipSingleEntryInFile(std::ifstream& file) {
std::string line;
for (unsigned int i = 0; i < nLineEntriesPerSatellite; i++) {
std::getline(file, line);
}
}
}
@@ -1,424 +0,0 @@
/****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/space/rendering/renderablesmallbody.h>
#include <modules/space/rendering/renderablesatellites.h>
#include <modules/space/translation/keplertranslation.h>
#include <modules/space/translation/tletranslation.h>
#include <modules/space/spacemodule.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/engine/globals.h>
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/util/time.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/filesystem/file.h>
#include <ghoul/misc/csvreader.h>
#include <ghoul/opengl/programobject.h>
#include <ghoul/logging/logmanager.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <math.h>
#include <vector>
namespace {
constexpr std::string_view _loggerCat = "SmallSolarSystemBody";
constexpr openspace::properties::Property::PropertyInfo ContiguousModeInfo = {
"ContiguousMode",
"Contiguous Mode",
"If enabled, then the contiguous set of objects starting from StartRenderIdx "
"of size RenderSize will be rendered. If disabled, then the number of objects "
"defined by UpperLimit will rendered from an evenly dispersed sample of the "
"full length of the data file"
};
constexpr openspace::properties::Property::PropertyInfo UpperLimitInfo = {
"UpperLimit",
"Upper Limit",
"Upper limit on the number of objects for this renderable, regardless of "
"how many objects are contained in the data file. Produces an evenly-distributed"
"sample from the data file"
};
double importAngleValue(const std::string& angle) {
if (angle.empty()) {
return 0.0;
}
double output = std::stod(angle);
output = std::fmod(output, 360.0);
if (output < 0.0) {
output += 360.0;
}
return output;
}
std::string& formatObjectName(std::string& name) {
const std::string trimChars = "\t\n\v\f\r\" ";
name.erase(0, name.find_first_not_of(trimChars));
name.erase(name.find_last_not_of(trimChars) + 1);
return name;
}
struct [[codegen::Dictionary(RenderableSmallBody)]] Parameters {
// [[codegen::verbatim(ContiguousModeInfo.description)]]
std::optional<bool> contiguousMode;
// [[codegen::verbatim(UpperLimitInfo.description)]]
std::optional<int> upperLimit;
};
#include "renderablesmallbody_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation RenderableSmallBody::Documentation() {
return codegen::doc<Parameters>(
"space_renderablesmallbody",
RenderableOrbitalKepler::Documentation()
);
}
RenderableSmallBody::RenderableSmallBody(const ghoul::Dictionary& dictionary)
: RenderableOrbitalKepler(dictionary)
, _contiguousMode(ContiguousModeInfo, false)
, _upperLimit(UpperLimitInfo, 1000, 1, 1000000)
{
codegen::bake<Parameters>(dictionary);
addProperty(_startRenderIdx);
addProperty(_sizeRender);
addProperty(_contiguousMode);
addProperty(_upperLimit);
if (dictionary.hasValue<double>(UpperLimitInfo.identifier)) {
_upperLimit = static_cast<unsigned int>(
dictionary.value<double>(UpperLimitInfo.identifier));
}
else {
_upperLimit = 0u;
}
if (dictionary.hasValue<bool>(ContiguousModeInfo.identifier)) {
_contiguousMode = dictionary.value<bool>(ContiguousModeInfo.identifier);
}
else {
_contiguousMode = false;
}
_updateStartRenderIdxSelect = std::function<void()>([this] {
if (_contiguousMode) {
if ((_numObjects - _startRenderIdx) < _sizeRender) {
_sizeRender = static_cast<unsigned int>(_numObjects - _startRenderIdx);
}
_updateDataBuffersAtNextRender = true;
}
});
_updateRenderSizeSelect = std::function<void()>([this] {
if (_contiguousMode) {
if (_sizeRender > (_numObjects - _startRenderIdx)) {
_startRenderIdx = static_cast<unsigned int>(_numObjects - _sizeRender);
}
_updateDataBuffersAtNextRender = true;
}
});
_updateRenderUpperLimitSelect = std::function<void()>([this] {
if (!_contiguousMode) {
_updateDataBuffersAtNextRender = true;
}
});
_updateContiguousModeSelect = std::function<void()>([this] {
_updateDataBuffersAtNextRender = true;
});
_startRenderIdxCallbackHandle = _startRenderIdx.onChange(_updateStartRenderIdxSelect);
_sizeRenderCallbackHandle = _sizeRender.onChange(_updateRenderSizeSelect);
_upperLimitCallbackHandle = _upperLimit.onChange(_updateRenderUpperLimitSelect);
_contiguousModeCallbackhandle =
_contiguousMode.onChange(_updateContiguousModeSelect);
}
void RenderableSmallBody::readDataFile(const std::string& filename) {
if (!std::filesystem::is_regular_file(filename)) {
throw ghoul::RuntimeError(fmt::format(
"JPL SBDB file {} does not exist", filename
));
}
std::ifstream file;
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
file.open(filename);
std::streamoff numberOfLines = std::count(
std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>(),
'\n'
);
file.seekg(std::ios_base::beg); // reset iterator to beginning of file
_data.clear();
_sbNames.clear();
_segmentSize.clear();
std::string line;
unsigned int csvLine = 0;
int fieldCount = 0;
const std::string expectedHeaderLine = "full_name,epoch_cal,e,a,i,om,w,ma,per";
try {
std::getline(file, line); // get rid of first line (header)
numberOfLines -= 1;
if (_numObjects != numberOfLines) {
_isFileReadinitialized = false;
}
_numObjects = numberOfLines;
float lineSkipFraction = 1.0;
if (!_isFileReadinitialized) {
_isFileReadinitialized = true;
initializeFileReading();
}
unsigned int startElement = 0;
unsigned int endElement;
if (_contiguousMode) {
lineSkipFraction = 1.0;
startElement = _startRenderIdx;
endElement = _startRenderIdx + _sizeRender - 1;
}
else {
lineSkipFraction = static_cast<float>(_upperLimit)
/ static_cast<float>(_numObjects);
endElement = static_cast<unsigned int>(_numObjects - 1);
}
if (line.compare(expectedHeaderLine) != 0) {
LERROR(fmt::format(
"File {} does not have the appropriate JPL SBDB header at line 1",
filename
));
return;
}
unsigned int sequentialLineErrors = 0;
endElement =
(endElement >= _numObjects) ?
static_cast<unsigned int>(_numObjects - 1) :
endElement;
// Burn lines if not starting at first element
for (unsigned int k = 0; k < startElement; ++k) {
skipSingleLineInFile(file);
}
bool firstDataLine = true;
int lastLineCount = -1;
for (csvLine = startElement + 1;
csvLine <= endElement + 1;
csvLine++, sequentialLineErrors++)
{
float currLineFraction = static_cast<float>(csvLine - 1) * lineSkipFraction;
int currLineCount = static_cast<int>(currLineFraction);
if (currLineCount > lastLineCount) {
try {
readOrbitalParamsFromThisLine(firstDataLine, fieldCount, csvLine,
file);
sequentialLineErrors = 0;
}
catch (std::invalid_argument&) {
constexpr std::string_view errMsg = "Unable to convert field {} to "
"double value (invalid_argument exception). Ignoring line {}/{} "
"of {}";
LINFO(fmt::format(
errMsg,
fieldCount, csvLine + 1, numberOfLines, filename
));
}
catch (std::out_of_range&) {
constexpr std::string_view errMsg = "Unable to convert field {} to "
"double value (out_of_range exception). Ignoring line {}/{} of "
"{}";
LINFO(fmt::format(
errMsg,
fieldCount, csvLine + 1, numberOfLines, filename
));
}
catch (std::ios_base::failure&) {
throw;
}
if (sequentialLineErrors == 4) {
_data.clear();
_sbNames.clear();
LERROR(fmt::format(
"Abandoning data file {} (too many sequential line errors)",
filename
));
break;
}
}
else {
skipSingleLineInFile(file);
}
lastLineCount = currLineCount;
firstDataLine = false;
}
}
catch (const std::ios_base::failure&) {
LERROR(fmt::format(
"File read exception (ios_base::failure) while trying to read field {} at "
"line {}/{} of {}",
fieldCount, csvLine + 1, numberOfLines, filename
));
}
}
void RenderableSmallBody::initializeFileReading() {
_startRenderIdx.setMaxValue(static_cast<unsigned int>(_numObjects - 1));
_sizeRender.setMaxValue(static_cast<unsigned int>(_numObjects));
if (_sizeRender == 0u) {
_sizeRender = static_cast<unsigned int>(_numObjects);
}
_upperLimit.setMaxValue(static_cast<unsigned int>(_numObjects));
if (_upperLimit == 0u) {
_upperLimit = static_cast<unsigned int>(_numObjects);
}
}
void RenderableSmallBody::skipSingleLineInFile(std::ifstream& file) {
std::string line;
std::getline(file, line);
}
void RenderableSmallBody::readOrbitalParamsFromThisLine(bool firstDataLine,
int& fieldCount,
unsigned int& csvLine,
std::ifstream& file)
{
const int numDataFields = 8;
std::string name;
std::string field;
KeplerParameters keplerElements;
//If there was a read/conversion error in the previous line, then read the remainder
// of that line and throw it out first before proceeding with the next line.
if (fieldCount != (numDataFields + 1) && !firstDataLine) {
std::getline(file, field);
}
fieldCount = 0;
// Object designator string
std::getline(file, name, ',');
if (_startRenderIdx > 0 && _startRenderIdx == (csvLine - 1) && _sizeRender == 1) {
formatObjectName(name);
LINFO(fmt::format("Set render block to start at object {}", name));
}
fieldCount++;
// Epoch
if (!std::getline(file, field, ',')) {
throw std::invalid_argument(
"Unable to read epoch from line" + std::to_string(csvLine + 1)
);
}
keplerElements.epoch = epochFromYMDdSubstring(field);
fieldCount++;
// Eccentricity (unit-less)
if (!std::getline(file, field, ',')) {
throw std::invalid_argument(
"Unable to read eccentricity from line" + std::to_string(csvLine + 1)
);
}
keplerElements.eccentricity = std::stod(field);
fieldCount++;
// Semi-major axis (astronomical units - au)
if (!std::getline(file, field, ',')) {
throw std::invalid_argument(
"Unable to read semi-major axis from line" + std::to_string(csvLine + 1)
);
}
keplerElements.semiMajorAxis = std::stod(field);
keplerElements.semiMajorAxis *= convertAuToKm;
fieldCount++;
// Inclination (degrees)
if (!std::getline(file, field, ',')) {
throw std::invalid_argument(
"Unable to read inclination from line" + std::to_string(csvLine + 1)
);
}
keplerElements.inclination = importAngleValue(field);
fieldCount++;
// Longitude of ascending node (degrees)
if (!std::getline(file, field, ',')) {
throw std::invalid_argument(
"Unable to read ascending node from line" + std::to_string(csvLine + 1)
);
}
keplerElements.ascendingNode = importAngleValue(field);
fieldCount++;
// Argument of Periapsis (degrees)
if (!std::getline(file, field, ',')) {
throw std::invalid_argument(
"Unable to read arg of periapsis from line" + std::to_string(csvLine + 1)
);
}
keplerElements.argumentOfPeriapsis = importAngleValue(field);
fieldCount++;
// Mean Anomaly (degrees)
if (!std::getline(file, field, ',')) {
throw std::invalid_argument(
"Unable to read mean anomaly from line" + std::to_string(csvLine + 1)
);
}
keplerElements.meanAnomaly = importAngleValue(field);
fieldCount++;
// Period (days)
if (!std::getline(file, field)) {
throw std::invalid_argument(
"Unable to read period from line" + std::to_string(csvLine + 1)
);
}
keplerElements.period = std::stod(field);
keplerElements.period *= convertDaysToSecs;
fieldCount++;
_data.push_back(keplerElements);
_sbNames.push_back(name);
const double scale = static_cast<double>(_segmentQuality) * 10.0;
_segmentSize.push_back(
static_cast<size_t>(scale + (scale / pow(1 - keplerElements.eccentricity, 1.2)))
);
}
} // namespace openspace
+8 -10
View File
@@ -27,14 +27,13 @@
#include <modules/space/rendering/renderableconstellationbounds.h>
#include <modules/space/rendering/renderablefluxnodes.h>
#include <modules/space/rendering/renderablehabitablezone.h>
#include <modules/space/rendering/renderableorbitalkepler.h>
#include <modules/space/rendering/renderablerings.h>
#include <modules/space/rendering/renderablesatellites.h>
#include <modules/space/rendering/renderablesmallbody.h>
#include <modules/space/rendering/renderablestars.h>
#include <modules/space/rendering/renderabletravelspeed.h>
#include <modules/space/translation/keplertranslation.h>
#include <modules/space/translation/spicetranslation.h>
#include <modules/space/translation/tletranslation.h>
#include <modules/space/translation/gptranslation.h>
#include <modules/space/translation/horizonstranslation.h>
#include <modules/space/rotation/spicerotation.h>
#include <openspace/documentation/documentation.h>
@@ -83,8 +82,7 @@ void SpaceModule::internalInitialize(const ghoul::Dictionary& dictionary) {
fRenderable->registerClass<RenderableFluxNodes>("RenderableFluxNodes");
fRenderable->registerClass<RenderableHabitableZone>("RenderableHabitableZone");
fRenderable->registerClass<RenderableRings>("RenderableRings");
fRenderable->registerClass<RenderableSatellites>("RenderableSatellites");
fRenderable->registerClass<RenderableSmallBody>("RenderableSmallBody");
fRenderable->registerClass<RenderableOrbitalKepler>("RenderableOrbitalKepler");
fRenderable->registerClass<RenderableStars>("RenderableStars");
fRenderable->registerClass<RenderableTravelSpeed>("RenderableTravelSpeed");
@@ -94,7 +92,7 @@ void SpaceModule::internalInitialize(const ghoul::Dictionary& dictionary) {
fTranslation->registerClass<KeplerTranslation>("KeplerTranslation");
fTranslation->registerClass<SpiceTranslation>("SpiceTranslation");
fTranslation->registerClass<TLETranslation>("TLETranslation");
fTranslation->registerClass<GPTranslation>("GPTranslation");
fTranslation->registerClass<HorizonsTranslation>("HorizonsTranslation");
ghoul::TemplateFactory<Rotation>* fRotation =
@@ -120,13 +118,12 @@ std::vector<documentation::Documentation> SpaceModule::documentations() const {
RenderableFluxNodes::Documentation(),
RenderableHabitableZone::Documentation(),
RenderableRings::Documentation(),
RenderableSatellites::Documentation(),
RenderableSmallBody::Documentation(),
RenderableOrbitalKepler::Documentation(),
RenderableStars::Documentation(),
RenderableTravelSpeed::Documentation(),
SpiceRotation::Documentation(),
SpiceTranslation::Documentation(),
TLETranslation::Documentation()
GPTranslation::Documentation()
};
}
@@ -135,7 +132,8 @@ scripting::LuaLibrary SpaceModule::luaLibrary() const {
"space",
{
codegen::lua::ConvertFromRaDec,
codegen::lua::ConvertToRaDec
codegen::lua::ConvertToRaDec,
codegen::lua::ReadKeplerFile
}
};
}
+39
View File
@@ -22,6 +22,8 @@
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include "kepler.h"
namespace {
/**
@@ -77,6 +79,43 @@ namespace {
return { raDecPair.first, raDecPair.second, deg.z };
}
[[codegen::luawrap]]
std::vector<ghoul::Dictionary> readKeplerFile(std::filesystem::path p, std::string type)
{
openspace::kepler::Format f;
if (type == "TLE") {
f = openspace::kepler::Format::TLE;
}
else if (type == "OMM") {
f = openspace::kepler::Format::OMM;
}
else if (type == "SBDB") {
f = openspace::kepler::Format::SBDB;
}
else {
throw ghoul::lua::LuaError(fmt::format("Unsupported format '{}'", type));
}
std::vector<openspace::kepler::Parameters> params = openspace::kepler::readFile(p, f);
std::vector<ghoul::Dictionary> res;
res.reserve(params.size());
for (const openspace::kepler::Parameters& param : params) {
ghoul::Dictionary d;
d.setValue("Name", param.name);
d.setValue("ID", param.id);
d.setValue("inclination", param.inclination);
d.setValue("SemiMajorAxis", param.semiMajorAxis);
d.setValue("AscendingNode", param.ascendingNode);
d.setValue("Eccentricity", param.eccentricity);
d.setValue("ArgumentOfPeriapsis", param.argumentOfPeriapsis);
d.setValue("MeanAnomaly", param.meanAnomaly);
d.setValue("Epoch", param.epoch);
d.setValue("Period", param.period);
res.push_back(d);
}
return res;
}
#include "spacemodule_lua_codegen.cpp"
} // namespace
@@ -28,7 +28,7 @@
#include <openspace/util/task.h>
#include <openspace/util/time.h>
#include <modules/space/rendering/renderablesatellites.h>
#include <modules/space/rendering/renderableorbitalkepler.h>
#include <modules/space/translation/keplertranslation.h>
@@ -50,8 +50,6 @@ public:
std::string _gridType;
protected:
private:
std::string _rawVolumeOutputPath;
std::string _dictionaryOutputPath;
@@ -1,4 +1,4 @@
/****************************************************************************************
/*****************************************************************************************
* *
* OpenSpace *
* *
@@ -22,50 +22,71 @@
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_SPACE___RENDERABLESMALLBODY___H__
#define __OPENSPACE_MODULE_SPACE___RENDERABLESMALLBODY___H__
#include <modules/space/translation/gptranslation.h>
#include <modules/space/rendering/renderableorbitalkepler.h>
#include <openspace/rendering/renderable.h>
#include <modules/space/kepler.h>
#include <openspace/documentation/verifier.h>
#include <filesystem>
#include <optional>
#include <modules/base/rendering/renderabletrail.h>
#include <modules/space/translation/keplertranslation.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/scalar/uintproperty.h>
#include <ghoul/glm.h>
#include <ghoul/misc/objectmanager.h>
#include <ghoul/opengl/programobject.h>
namespace {
struct [[codegen::Dictionary(GPTranslation)]] Parameters {
// Specifies the filename of the general pertubation file
std::filesystem::path file;
enum class [[codegen::map(openspace::kepler::Format)]] Format {
// A NORAD-style Two-Line element
TLE,
// Orbit Mean-Elements Message in the KVN notation
OMM,
// JPL's Small Bodies Database
SBDB
};
// The file format that is contained in the file
Format format;
// Specifies the element within the file that should be used in case the file
// provides multiple general pertubation elements. Defaults to 1.
std::optional<int> element [[codegen::greater(0)]];
};
#include "gptranslation_codegen.cpp"
} // namespace
namespace openspace {
namespace documentation { struct Documentation; }
documentation::Documentation GPTranslation::Documentation() {
return codegen::doc<Parameters>("space_transform_gp");
}
class RenderableSmallBody : public RenderableOrbitalKepler {
public:
RenderableSmallBody(const ghoul::Dictionary& dictionary);
static documentation::Documentation Documentation();
GPTranslation::GPTranslation(const ghoul::Dictionary& dictionary) {
const Parameters p = codegen::bake<Parameters>(dictionary);
if (!std::filesystem::is_regular_file(p.file)) {
throw ghoul::RuntimeError("The provided TLE file must exist");
}
private:
void readOrbitalParamsFromThisLine(bool firstDataLine, int& fieldCount,
unsigned int& csvLine, std::ifstream& file);
virtual void readDataFile(const std::string& filename) override;
void initializeFileReading();
void skipSingleLineInFile(std::ifstream& file);
int element = p.element.value_or(1);
std::vector<std::string> _sbNames;
std::function<void()> _updateContiguousModeSelect;
std::function<void()> _updateRenderUpperLimitSelect;
std::vector<kepler::Parameters> parameters = kepler::readFile(
p.file,
codegen::map<kepler::Format>(p.format)
);
if (parameters.size() < element) {
throw ghoul::RuntimeError(fmt::format(
"Requested element {} but only {} are available", element, parameters.size()
));
}
/// The index array that is potentially used in the draw call. If this is empty, no
/// element draw call is used.
std::vector<unsigned int> _indexBufferData;
properties::BoolProperty _contiguousMode;
properties::UIntProperty _upperLimit;
properties::Property::OnChangeHandle _contiguousModeCallbackhandle;
properties::Property::OnChangeHandle _upperLimitCallbackHandle;
};
kepler::Parameters param = parameters[element - 1];
setKeplerElements(
param.eccentricity,
param.semiMajorAxis,
param.inclination,
param.ascendingNode,
param.argumentOfPeriapsis,
param.meanAnomaly,
param.period,
param.epoch
);
}
} // namespace openspace
#endif // __OPENSPACE_MODULE_SPACE___RENDERABLESMALLBODY___H__
@@ -22,37 +22,38 @@
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_SPACE___RENDERABLESATELLITES___H__
#define __OPENSPACE_MODULE_SPACE___RENDERABLESATELLITES___H__
#ifndef __OPENSPACE_MODULE_SPACE___GPTRANSLATION___H__
#define __OPENSPACE_MODULE_SPACE___GPTRANSLATION___H__
#include <modules/space/rendering/renderableorbitalkepler.h>
#include <openspace/rendering/renderable.h>
#include <modules/base/rendering/renderabletrail.h>
#include <modules/space/translation/keplertranslation.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/scalar/uintproperty.h>
#include <ghoul/glm.h>
#include <ghoul/misc/objectmanager.h>
#include <ghoul/opengl/programobject.h>
namespace openspace {
namespace documentation { struct Documentation; }
class RenderableSatellites : public RenderableOrbitalKepler {
/**
* A specialization of the KeplerTranslation that utilizes general pertubation file
* formats to extracts the Keplerian elements
*/
class GPTranslation : public KeplerTranslation {
public:
RenderableSatellites(const ghoul::Dictionary& dictionary);
virtual void readDataFile(const std::string& filename) override;
static documentation::Documentation Documentation();
void initializeFileReading();
/**
* Constructor for the GPTranslation class. The \p dictionary must contain a key for
* the file that contains the general pertubation information as well as the file
* format that is to be used.
*
* \param The ghoul::Dictionary that contains the information for this TLETranslation
*/
explicit GPTranslation(const ghoul::Dictionary& dictionary);
private:
void skipSingleEntryInFile(std::ifstream& file);
const unsigned int nLineEntriesPerSatellite = 3;
/**
* Method returning the openspace::Documentation that describes the ghoul::Dictionary
* that can be passed to the constructor.
*
* \return The openspace::Documentation that describes the ghoul::Dicitonary that can
* be passed to the constructor
*/
static documentation::Documentation Documentation();
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_SPACE___RENDERABLESATELLITES___H__
#endif // __OPENSPACE_MODULE_SPACE___GPTRANSLATION___H__
@@ -71,7 +71,7 @@ public:
glm::dvec3 position(const UpdateData& data) const override;
/**
* Method returning the openspace::Documentation that describes the ghoul::Dictinoary
* Method returning the openspace::Documentation that describes the ghoul::Dictionary
* that can be passed to the constructor.
*
* \return The openspace::Documentation that describes the ghoul::Dicitonary that can
@@ -1,364 +0,0 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/space/translation/tletranslation.h>
#include <openspace/documentation/verifier.h>
#include <ghoul/filesystem/file.h>
#include <ghoul/filesystem/filesystem.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <optional>
#include <vector>
namespace {
// The list of leap years only goes until 2056 as we need to touch this file then
// again anyway ;)
const std::vector<int> LeapYears = {
1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996,
2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040,
2044, 2048, 2052, 2056
};
// Count the number of full days since the beginning of 2000 to the beginning of
// the parameter 'year'
int countDays(int year) {
// Find the position of the current year in the vector, the difference
// between its position and the position of 2000 (for J2000) gives the
// number of leap years
constexpr int Epoch = 2000;
constexpr int DaysRegularYear = 365;
constexpr int DaysLeapYear = 366;
if (year == Epoch) {
return 0;
}
// Get the position of the most recent leap year
const auto lb = std::lower_bound(LeapYears.begin(), LeapYears.end(), year);
// Get the position of the epoch
const auto y2000 = std::find(LeapYears.begin(), LeapYears.end(), Epoch);
// The distance between the two iterators gives us the number of leap years
const int nLeapYears = static_cast<int>(std::abs(std::distance(y2000, lb)));
const int nYears = std::abs(year - Epoch);
const int nRegularYears = nYears - nLeapYears;
// Get the total number of days as the sum of leap years + non leap years
const int result = nRegularYears * DaysRegularYear + nLeapYears * DaysLeapYear;
return result;
}
// Returns the number of leap seconds that lie between the {year, dayOfYear}
// time point and { 2000, 1 }
int countLeapSeconds(int year, int dayOfYear) {
// Find the position of the current year in the vector; its position in
// the vector gives the number of leap seconds
struct LeapSecond {
int year;
int dayOfYear;
bool operator<(const LeapSecond& rhs) const {
return std::tie(year, dayOfYear) < std::tie(rhs.year, rhs.dayOfYear);
}
};
const LeapSecond Epoch = { 2000, 1 };
// List taken from: https://www.ietf.org/timezones/data/leap-seconds.list
static const std::vector<LeapSecond> LeapSeconds = {
{ 1972, 1 },
{ 1972, 183 },
{ 1973, 1 },
{ 1974, 1 },
{ 1975, 1 },
{ 1976, 1 },
{ 1977, 1 },
{ 1978, 1 },
{ 1979, 1 },
{ 1980, 1 },
{ 1981, 182 },
{ 1982, 182 },
{ 1983, 182 },
{ 1985, 182 },
{ 1988, 1 },
{ 1990, 1 },
{ 1991, 1 },
{ 1992, 183 },
{ 1993, 182 },
{ 1994, 182 },
{ 1996, 1 },
{ 1997, 182 },
{ 1999, 1 },
{ 2006, 1 },
{ 2009, 1 },
{ 2012, 183 },
{ 2015, 182 },
{ 2017, 1 }
};
// Get the position of the last leap second before the desired date
LeapSecond date { year, dayOfYear };
const auto it = std::lower_bound(LeapSeconds.begin(), LeapSeconds.end(), date);
// Get the position of the Epoch
const auto y2000 = std::lower_bound(
LeapSeconds.begin(),
LeapSeconds.end(),
Epoch
);
// The distance between the two iterators gives us the number of leap years
const int nLeapSeconds = static_cast<int>(std::abs(std::distance(y2000, it)));
return nLeapSeconds;
}
double epochFromSubstring(const std::string& epochString) {
// The epochString is in the form:
// YYDDD.DDDDDDDD
// With YY being the last two years of the launch epoch, the first DDD the day
// of the year and the remaning a fractional part of the day
// The main overview of this function:
// 1. Reconstruct the full year from the YY part
// 2. Calculate the number of days since the beginning of the year
// 3. Convert the number of days to a number of seconds
// 4. Get the number of leap seconds since January 1st, 2000 and remove them
// 5. Adjust for the fact the epoch starts on 1st Januaray at 12:00:00, not
// midnight
// According to https://celestrak.com/columns/v04n03/
// Apparently, US Space Command sees no need to change the two-line element
// set format yet since no artificial earth satellites existed prior to 1957.
// By their reasoning, two-digit years from 57-99 correspond to 1957-1999 and
// those from 00-56 correspond to 2000-2056. We'll see each other again in 2057!
// 1. Get the full year
std::string yearPrefix = [y = epochString.substr(0, 2)](){
int year = std::atoi(y.c_str());
return year >= 57 ? "19" : "20";
}();
const int year = std::atoi((yearPrefix + epochString.substr(0, 2)).c_str());
const int daysSince2000 = countDays(year);
// 2.
double daysInYear = std::atof(epochString.substr(2).c_str());
// 3
using namespace std::chrono;
const int SecondsPerDay = static_cast<int>(seconds(hours(24)).count());
//Need to subtract 1 from daysInYear since it is not a zero-based count
const double nSecondsSince2000 = (daysSince2000 + daysInYear - 1) * SecondsPerDay;
// 4
// We need to remove additionbal leap seconds past 2000 and add them prior to
// 2000 to sync up the time zones
const double nLeapSecondsOffset = -countLeapSeconds(
year,
static_cast<int>(std::floor(daysInYear))
);
// 5
const double nSecondsEpochOffset = static_cast<double>(
seconds(hours(12)).count()
);
// Combine all of the values
const double epoch = nSecondsSince2000 + nLeapSecondsOffset - nSecondsEpochOffset;
return epoch;
}
double calculateSemiMajorAxis(double meanMotion) {
constexpr double GravitationalConstant = 6.6740831e-11;
constexpr double MassEarth = 5.9721986e24;
constexpr double muEarth = GravitationalConstant * MassEarth;
// Use Kepler's 3rd law to calculate semimajor axis
// a^3 / P^2 = mu / (2pi)^2
// <=> a = ((mu * P^2) / (2pi^2))^(1/3)
// with a = semimajor axis
// P = period in seconds
// mu = G*M_earth
double period = std::chrono::seconds(std::chrono::hours(24)).count() / meanMotion;
constexpr double pisq = glm::pi<double>() * glm::pi<double>();
double semiMajorAxis = pow((muEarth * period*period) / (4 * pisq), 1.0 / 3.0);
// We need the semi major axis in km instead of m
return semiMajorAxis / 1000.0;
}
struct [[codegen::Dictionary(TLETranslation)]] Parameters {
// Specifies the filename of the Two-Line-Element file
std::string file;
// Specifies the line number within the file where the group of 3 TLE lines begins
// (1-based). Defaults to 1
std::optional<int> lineNumber [[codegen::greater(0)]];
};
#include "tletranslation_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation TLETranslation::Documentation() {
return codegen::doc<Parameters>("space_transform_tle");
}
TLETranslation::TLETranslation(const ghoul::Dictionary& dictionary) {
const Parameters p = codegen::bake<Parameters>(dictionary);
int lineNum = p.lineNumber.value_or(1);
readTLEFile(p.file, lineNum);
}
void TLETranslation::readTLEFile(const std::string& filename, int lineNum) {
ghoul_assert(std::filesystem::is_regular_file(filename), "The filename must exist");
std::ifstream file;
file.exceptions(std::ofstream::failbit | std::ofstream::badbit);
file.open(filename);
// All of the Kepler element information
struct {
double inclination = 0.0;
double semiMajorAxis = 0.0;
double ascendingNode = 0.0;
double eccentricity = 0.0;
double argumentOfPeriapsis = 0.0;
double meanAnomaly = 0.0;
double meanMotion = 0.0;
double epoch = 0.0;
} keplerElements;
std::string line;
// Loop through and throw out lines until getting to the linNum of interest
for (int i = 1; i < lineNum; ++i) {
std::getline(file, line);
}
std::getline(file, line); // Throw out the TLE title line (1st)
std::getline(file, line); // Get line 1 of TLE format
if (line[0] == '1') {
// First line
// Field Columns Content
// 1 01-01 Line number
// 2 03-07 Satellite number
// 3 08-08 Classification (U = Unclassified)
// 4 10-11 International Designator (Last two digits of launch year)
// 5 12-14 International Designator (Launch number of the year)
// 6 15-17 International Designator(piece of the launch) A
// 7 19-20 Epoch Year(last two digits of year)
// 8 21-32 Epoch(day of the year and fractional portion of the day)
// 9 34-43 First Time Derivative of the Mean Motion divided by two
// 10 45-52 Second Time Derivative of Mean Motion divided by six
// 11 54-61 BSTAR drag term(decimal point assumed)[10] - 11606 - 4
// 12 63-63 The "Ephemeris type"
// 13 65-68 Element set number.Incremented when a new TLE is generated
// 14 69-69 Checksum (modulo 10)
keplerElements.epoch = epochFromSubstring(line.substr(18, 14));
}
else {
throw ghoul::RuntimeError(fmt::format(
"File {} @ line {} does not have '1' header", filename, lineNum + 1
));
}
std::getline(file, line); // Get line 2 of TLE format
if (line[0] == '2') {
// Second line
// Field Columns Content
// 1 01-01 Line number
// 2 03-07 Satellite number
// 3 09-16 Inclination (degrees)
// 4 18-25 Right ascension of the ascending node (degrees)
// 5 27-33 Eccentricity (decimal point assumed)
// 6 35-42 Argument of perigee (degrees)
// 7 44-51 Mean Anomaly (degrees)
// 8 53-63 Mean Motion (revolutions per day)
// 9 64-68 Revolution number at epoch (revolutions)
// 10 69-69 Checksum (modulo 10)
std::stringstream stream;
stream.exceptions(std::ios::failbit);
// Get inclination
stream.str(line.substr(8, 8));
stream >> keplerElements.inclination;
stream.clear();
// Get Right ascension of the ascending node
stream.str(line.substr(17, 8));
stream >> keplerElements.ascendingNode;
stream.clear();
// Get Eccentricity
stream.str("0." + line.substr(26, 7));
stream >> keplerElements.eccentricity;
stream.clear();
// Get argument of periapsis
stream.str(line.substr(34, 8));
stream >> keplerElements.argumentOfPeriapsis;
stream.clear();
// Get mean anomaly
stream.str(line.substr(43, 8));
stream >> keplerElements.meanAnomaly;
stream.clear();
// Get mean motion
stream.str(line.substr(52, 11));
stream >> keplerElements.meanMotion;
}
else {
throw ghoul::RuntimeError(fmt::format(
"File {} @ line {} does not have '2' header", filename, lineNum + 2
));
}
file.close();
// Calculate the semi major axis based on the mean motion using kepler's laws
keplerElements.semiMajorAxis = calculateSemiMajorAxis(keplerElements.meanMotion);
// Converting the mean motion (revolutions per day) to period (seconds per revolution)
using namespace std::chrono;
double period = seconds(hours(24)).count() / keplerElements.meanMotion;
setKeplerElements(
keplerElements.eccentricity,
keplerElements.semiMajorAxis,
keplerElements.inclination,
keplerElements.ascendingNode,
keplerElements.argumentOfPeriapsis,
keplerElements.meanAnomaly,
period,
keplerElements.epoch
);
}
} // namespace openspace
@@ -1,83 +0,0 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_SPACE___TLETRANSLATION___H__
#define __OPENSPACE_MODULE_SPACE___TLETRANSLATION___H__
#include <modules/space/translation/keplertranslation.h>
namespace openspace {
/**
* A specialization of the KeplerTranslation that extracts the Keplerian elements from a
* two-line element as described by the US Space Command
* https://celestrak.com/columns/v04n03
* The ghoul::Dictionary passed to the constructor must contain the pointer to a file that
* will be read.
*/
class TLETranslation : public KeplerTranslation {
public:
struct FileFormatError : public ghoul::RuntimeError {
explicit FileFormatError(std::string offense);
std::string offense;
};
/**
* Constructor for the TLETranslation class. The \p dictionary must contain a key for
* the file that contains the TLE information. The ghoul::Dictionary will be tested
* against the openspace::Documentation returned by Documentation.
* \param The ghoul::Dictionary that contains the information for this TLETranslation
(*/
TLETranslation(const ghoul::Dictionary& dictionary = ghoul::Dictionary());
/**
* Method returning the openspace::Documentation that describes the ghoul::Dictinoary
* that can be passed to the constructor.
* \return The openspace::Documentation that describes the ghoul::Dicitonary that can
* be passed to the constructor
*/
static documentation::Documentation Documentation();
private:
/**
* Reads the provided TLE file and calles the KeplerTranslation::setKeplerElments
* method with the correct values. If \p filename is a valid TLE file but contains
* disallowed values (see KeplerTranslation::setKeplerElements), a
* KeplerTranslation::RangeError is thrown.
*
* \param filename The path to the file that contains the TLE file.
* \param lineNum The line number in the file where the set of 3 TLE lines starts
*
* \throw std::system_error if the TLE file is malformed (does not contain at least
* two lines that start with \c 1 and \c 2.
* \throw KeplerTranslation::RangeError If the Keplerian elements are outside of
* the valid range supported by Kepler::setKeplerElements
* \pre The \p filename must exist
*/
void readTLEFile(const std::string& filename, int lineNum);
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_SPACE___TLETRANSLATION___H__