/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2021 * * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr const char* _loggerCat = "Satellites"; static const 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() { documentation::Documentation doc = codegen::doc( "space_renderablesatellites" ); // Insert the parents documentation entries until we have a verifier that can deal // with class hierarchy documentation::Documentation parentDoc = RenderableOrbitalKepler::Documentation(); doc.entries.insert( doc.entries.end(), parentDoc.entries.begin(), parentDoc.entries.end() ); return doc; } 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(dictionary); addProperty(_startRenderIdx); addProperty(_sizeRender); _updateStartRenderIdxSelect = [this]() { if ((_numObjects - _startRenderIdx) < _sizeRender) { _sizeRender = static_cast(_numObjects - _startRenderIdx); } updateBuffers(); }; _updateRenderSizeSelect = [this]() { if (_sizeRender > (_numObjects - _startRenderIdx)) { _startRenderIdx = static_cast(_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(file), std::istreambuf_iterator(), '\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(_numObjects - 1)); _sizeRender.setMaxValue(static_cast(_numObjects)); if (_sizeRender == 0u) { _sizeRender = static_cast(_numObjects); } } void RenderableSatellites::skipSingleEntryInFile(std::ifstream& file) { std::string line; for (unsigned int i = 0; i < nLineEntriesPerSatellite; i++) { std::getline(file, line); } } }