mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-04-30 07:49:31 -05:00
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:
@@ -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,58 +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___RENDERABLESATELLITES___H__
|
||||
#define __OPENSPACE_MODULE_SPACE___RENDERABLESATELLITES___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 {
|
||||
public:
|
||||
RenderableSatellites(const ghoul::Dictionary& dictionary);
|
||||
virtual void readDataFile(const std::string& filename) override;
|
||||
static documentation::Documentation Documentation();
|
||||
void initializeFileReading();
|
||||
|
||||
private:
|
||||
void skipSingleEntryInFile(std::ifstream& file);
|
||||
const unsigned int nLineEntriesPerSatellite = 3;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SPACE___RENDERABLESATELLITES___H__
|
||||
|
||||
@@ -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
|
||||
@@ -1,71 +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___RENDERABLESMALLBODY___H__
|
||||
#define __OPENSPACE_MODULE_SPACE___RENDERABLESMALLBODY___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 RenderableSmallBody : public RenderableOrbitalKepler {
|
||||
public:
|
||||
RenderableSmallBody(const ghoul::Dictionary& dictionary);
|
||||
static documentation::Documentation Documentation();
|
||||
|
||||
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);
|
||||
|
||||
std::vector<std::string> _sbNames;
|
||||
std::function<void()> _updateContiguousModeSelect;
|
||||
std::function<void()> _updateRenderUpperLimitSelect;
|
||||
|
||||
/// 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;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
#endif // __OPENSPACE_MODULE_SPACE___RENDERABLESMALLBODY___H__
|
||||
|
||||
Reference in New Issue
Block a user