Files
OpenSpace/modules/exoplanets/exoplanetsmodule_lua.inl
Alexander Bock fd64fe7c96 Improvements to the ScriptLog logging (#3389)
* Restructuring the queueScript function
* Don't log the contents of actions that are triggered.  Add the ability to do verbose logging in the scriptlog
2024-09-09 21:16:57 +02:00

829 lines
31 KiB
C++

/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2024 *
* *
* 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 <openspace/scene/scene.h>
#include <ghoul/misc/csvreader.h>
#include <ghoul/misc/stringhelper.h>
#include <algorithm>
#include <map>
#include <string>
#include <string_view>
namespace {
constexpr std::string_view _loggerCat = "ExoplanetsModule";
constexpr std::string_view ExoplanetsGuiPath = "/Milky Way/Exoplanets/Exoplanet Systems/";
// Lua cannot handle backslashes, so replace these with forward slashes
std::string formatPathToLua(const std::filesystem::path& path) {
std::string resPath = path.string();
std::replace(resPath.begin(), resPath.end(), '\\', '/');
return resPath;
}
openspace::exoplanets::ExoplanetSystem findExoplanetSystemInData(
std::string_view starName)
{
using namespace openspace;
using namespace exoplanets;
const ExoplanetsModule* module = global::moduleEngine->module<ExoplanetsModule>();
const std::filesystem::path binPath = module->exoplanetsDataPath();
std::ifstream data(absPath(binPath), std::ios::in | std::ios::binary);
if (!data.good()) {
LERROR(std::format("Failed to open exoplanets data file '{}'", binPath));
return ExoplanetSystem();
}
const std::filesystem::path lutPath = module->lookUpTablePath();
std::ifstream lut(absPath(lutPath));
if (!lut.good()) {
LERROR(std::format("Failed to open exoplanets look-up table '{}'", lutPath));
return ExoplanetSystem();
}
ExoplanetSystem system;
// 1. search lut for the starname and return the corresponding location
// 2. go to that location in the data file
// 3. read sizeof(exoplanet) bytes into an exoplanet object.
ExoplanetDataEntry p;
std::string line;
while (ghoul::getline(lut, line)) {
std::istringstream ss(line);
std::string name;
ghoul::getline(ss, name, ',');
if (name.substr(0, name.length() - 2) != starName) {
continue;
}
std::string location_s;
ghoul::getline(ss, location_s);
long location = std::stol(location_s.c_str());
data.seekg(location);
data.read(reinterpret_cast<char*>(&p), sizeof(ExoplanetDataEntry));
sanitizeNameString(name);
if (!hasSufficientData(p)) {
LWARNING(std::format("Insufficient data for exoplanet '{}'", name));
continue;
}
system.planetNames.push_back(name);
system.planetsData.push_back(p);
updateStarDataFromNewPlanet(system.starData, p);
}
system.starName = starName;
return system;
}
void queueAddSceneGraphNodeScript(const std::string& sgnTableAsString) {
using namespace openspace;
// No sync or send because this will already be called inside a Lua script,
// therefor it has already been synced and sent to the connected nodes and peers
global::scriptEngine->queueScript({
.code = std::format("openspace.addSceneGraphNode({})", sgnTableAsString),
.synchronized = scripting::ScriptEngine::Script::ShouldBeSynchronized::No,
.sendToRemote = scripting::ScriptEngine::Script::ShouldSendToRemote::No
});
}
void createExoplanetSystem(const std::string& starName,
openspace::exoplanets::ExoplanetSystem system)
{
using namespace openspace;
using namespace exoplanets;
const std::string starIdentifier = makeIdentifier(starName);
std::string sanitizedStarName = starName;
sanitizeNameString(sanitizedStarName);
const std::string guiPath = std::format("{}{}", ExoplanetsGuiPath, sanitizedStarName);
SceneGraphNode* existingStarNode = sceneGraphNode(starIdentifier);
if (existingStarNode) {
LERROR(std::format(
"Adding of exoplanet system '{}' failed. The system has already been added",
starName
));
return;
}
const glm::vec3 starPosInParsec = system.starData.position;
if (!isValidPosition(starPosInParsec)) {
LERROR(std::format(
"Insufficient data available for exoplanet system '{}'. Could not determine "
"star position", starName
));
return;
}
const ExoplanetsModule* module = global::moduleEngine->module<ExoplanetsModule>();
const glm::dvec3 starPos =
static_cast<glm::dvec3>(starPosInParsec) * distanceconstants::Parsec;
const glm::dmat3 exoplanetSystemRotation = computeSystemRotation(starPos);
// Star
double radiusInMeter = distanceconstants::SolarRadius;
if (!std::isnan(system.starData.radius)) {
radiusInMeter *= system.starData.radius;
}
std::string colorLayers;
std::optional<glm::vec3> starColor = std::nullopt;
const float bv = system.starData.bv;
if (!std::isnan(bv)) {
starColor = computeStarColor(bv);
const std::filesystem::path starTexture = module->starTexturePath();
if (!starTexture.empty() && !std::filesystem::is_regular_file(starTexture)) {
LWARNING(std::format(
"Could not find specified star texture set in {} module: '{}'",
module->guiName(), starTexture
));
}
colorLayers =
"{"
"Identifier = 'StarColor',"
"Type = 'SolidColor',"
"Color = " + ghoul::to_string(*starColor) + ","
"BlendMode = 'Normal',"
"Enabled = true"
"},"
"{"
"Identifier = 'StarTexture',"
"FilePath = openspace.absPath('" + formatPathToLua(starTexture) + "'),"
"BlendMode = 'Color',"
"Enabled = true"
"}";
}
else {
const std::filesystem::path noDataTexture = module->noDataTexturePath();
colorLayers =
"{"
"Identifier = 'NoDataStarTexture',"
"FilePath = openspace.absPath('" + formatPathToLua(noDataTexture) + "'),"
"BlendMode = 'Color',"
"Enabled = true"
"}";
}
const std::string starGlobeRenderableString = "Renderable = {"
"Type = 'RenderableGlobe',"
"Radii = " + std::to_string(radiusInMeter) + ","
"PerformShading = false,"
"Layers = {"
"ColorLayers = { " + colorLayers + "}"
"}"
"},";
const std::string starParent = "{"
"Identifier = '" + starIdentifier + "',"
"Parent = 'SolarSystemBarycenter',"
"" + starGlobeRenderableString + ""
"Transform = {"
"Rotation = {"
"Type = 'StaticRotation',"
"Rotation = " + ghoul::to_string(exoplanetSystemRotation) + ""
"},"
"Translation = {"
"Type = 'StaticTranslation',"
"Position = " + ghoul::to_string(starPos) + ""
"}"
"},"
"Tag = {'exoplanet_system'},"
"GUI = {"
"Name = '" + sanitizedStarName + " (Star)',"
"Path = '" + guiPath + "'"
"}"
"}";
queueAddSceneGraphNodeScript(starParent);
// Add a label for the star.
// The fade values are set based on the values for the Sun label
const std::string starLabel = "{"
"Identifier = '" + starIdentifier + "_Label',"
"Parent = '" + starIdentifier + "',"
"Renderable = {"
"Type = 'RenderableLabel',"
"Enabled = false,"
"Text = '" + sanitizedStarName + "',"
"FontSize = 70.0,"
"Size = 14.17,"
"MinMaxSize = { 1, 50 },"
"EnableFading = true,"
"FadeUnit = 'pc',"
"FadeDistances = { 1.33, 15.0 },"
"FadeWidths = {1.0, 20.0}"
"},"
"Tag = {'exoplanet_system_labels'},"
"GUI = {"
"Name = '" + sanitizedStarName + " Label',"
"Path = '" + guiPath + "'"
"}"
"}";
queueAddSceneGraphNodeScript(starLabel);
// Planets
const std::filesystem::path planetTexture = module->planetDefaultTexturePath();
if (!planetTexture.empty() && !std::filesystem::is_regular_file(planetTexture)) {
LWARNING(std::format(
"Could not find specified planet default texture set in {} module: '{}'",
module->guiName(), planetTexture
));
}
for (size_t i = 0; i < system.planetNames.size(); i++) {
// Note that we are here overriding some invalid parameters in the planet data.
// Use a reference, so that it is changed down the line
ExoplanetDataEntry& planet = system.planetsData[i];
const std::string planetName = system.planetNames[i];
if (std::isnan(planet.ecc)) {
planet.ecc = 0.f;
}
// KeplerTranslation requires angles in range [0, 360]
auto validAngle = [](float angle, float defaultValue) {
if (std::isnan(angle)) { return defaultValue; }
if (angle < 0.f) { return angle + 360.f; }
if (angle > 360.f) { return angle - 360.f; }
return angle;
};
planet.i = validAngle(planet.i, 90.f);
planet.bigOmega = validAngle(planet.bigOmega, 180.f);
planet.omega = validAngle(planet.omega, 90.f);
Time epoch;
std::string sEpoch;
if (!std::isnan(planet.tt)) {
epoch.setTime("JD " + std::to_string(planet.tt));
sEpoch = std::string(epoch.ISO8601());
}
else {
sEpoch = "2009-05-19T07:11:34.080";
}
double planetRadius;
std::string enabled;
if (std::isnan(planet.r)) {
if (std::isnan(planet.rStar)) {
planetRadius = planet.a * 0.001 * distanceconstants::AstronomicalUnit;
}
else {
planetRadius = planet.rStar * 0.1 * distanceconstants::SolarRadius;
}
enabled = "false";
}
else {
planetRadius = planet.r * distanceconstants::JupiterRadius;
enabled = "true";
}
float periodInSeconds = static_cast<float>(planet.per * SecondsPerDay);
double semiMajorAxisInMeter = planet.a * distanceconstants::AstronomicalUnit;
double semiMajorAxisInKm = semiMajorAxisInMeter * 0.001;
const std::string planetIdentifier = makeIdentifier(planetName);
const std::string planetKeplerTranslation = "{"
"Type = 'KeplerTranslation',"
"Eccentricity = " + std::to_string(planet.ecc) + ","
"SemiMajorAxis = " + std::to_string(semiMajorAxisInKm) + ","
"Inclination = " + std::to_string(planet.i) + ","
"AscendingNode = " + std::to_string(planet.bigOmega) + ","
"ArgumentOfPeriapsis = " + std::to_string(planet.omega) + ","
"MeanAnomaly = 0.0,"
"Epoch = '" + sEpoch + "'," //TT. JD to YYYY MM DD hh:mm:ss
"Period = " + std::to_string(periodInSeconds) + ""
"}";
std::string planetLayers = "";
if (!planetTexture.empty()) {
planetLayers = "{"
"Identifier = 'PlanetTexture',"
"FilePath = openspace.absPath('" + formatPathToLua(planetTexture) + "'),"
"BlendMode = 'Color',"
"Enabled = true"
"}";
}
const std::string planetNode = "{"
"Identifier = '" + planetIdentifier + "',"
"Parent = '" + starIdentifier + "',"
"Renderable = {"
"Type = 'RenderableGlobe',"
"Enabled = " + enabled + ","
"Radii = " + std::to_string(planetRadius) + "," // in meters
"PerformShading = true,"
"Layers = {"
"ColorLayers = {" + planetLayers + "}"
"},"
"LightSourceNode = '" + starIdentifier + "'"
"},"
"Transform = { "
"Translation = " + planetKeplerTranslation + ""
"},"
"GUI = {"
"Name = '" + planetName + "',"
"Path = '" + guiPath + "'"
"}"
"}";
int trailResolution = 1000;
// Increase the resolution for highly eccentric orbits
const float eccentricityThreshold = 0.85f;
if (planet.ecc > eccentricityThreshold) {
trailResolution *= 2;
}
const std::string planetTrailNode = "{"
"Identifier = '" + planetIdentifier + "_Trail',"
"Parent = '" + starIdentifier + "',"
"Renderable = {"
"Type = 'RenderableTrailOrbit',"
"Period = " + std::to_string(planet.per) + ","
"Resolution = " + std::to_string(trailResolution) + ","
"Translation = " + planetKeplerTranslation + ","
"Color = { 1, 1, 1 }"
"},"
"GUI = {"
"Name = '" + planetName + " Trail',"
"Path = '" + guiPath + "'"
"}"
"}";
queueAddSceneGraphNodeScript(planetTrailNode);
queueAddSceneGraphNodeScript(planetNode);
bool hasUpperAUncertainty = !std::isnan(planet.aUpper);
bool hasLowerAUncertainty = !std::isnan(planet.aLower);
if (hasUpperAUncertainty && hasLowerAUncertainty) {
const glm::dmat4 rotation = computeOrbitPlaneRotationMatrix(
planet.i,
planet.bigOmega,
planet.omega
);
const glm::dmat3 rotationMat3 = static_cast<glm::dmat3>(rotation);
const float lowerOffset = static_cast<float>(planet.aLower / planet.a);
const float upperOffset = static_cast<float>(planet.aUpper / planet.a);
const std::filesystem::path discTexture = module->orbitDiscTexturePath();
bool isDiscEnabled = module->showOrbitUncertainty();
const std::string discNode = "{"
"Identifier = '" + planetIdentifier + "_Disc',"
"Parent = '" + starIdentifier + "',"
"Renderable = {"
"Type = 'RenderableOrbitDisc',"
"Enabled = " + (isDiscEnabled ? "true" : "false") + ","
"Texture = openspace.absPath('" +
formatPathToLua(discTexture) +
"'),"
"Size = " + std::to_string(semiMajorAxisInMeter) + ","
"Eccentricity = " + std::to_string(planet.ecc) + ","
"Offset = { " +
std::to_string(lowerOffset) + ", " +
std::to_string(upperOffset) +
"}," //min / max extend
"Opacity = 0.25"
"},"
"Transform = {"
"Rotation = {"
"Type = 'StaticRotation',"
"Rotation = " + ghoul::to_string(rotationMat3) + ""
"}"
"},"
"Tag = {'exoplanet_uncertainty_disc'},"
"GUI = {"
"Name = '" + planetName + " Disc',"
"Path = '" + guiPath + "'"
"}"
"}";
queueAddSceneGraphNodeScript(discNode);
}
}
float meanInclination = 0.f;
for (const ExoplanetDataEntry& p : system.planetsData) {
meanInclination += p.i;
}
meanInclination /= static_cast<float>(system.planetsData.size());
const glm::dmat4 rotation = computeOrbitPlaneRotationMatrix(meanInclination);
const glm::dmat3 meanOrbitPlaneRotationMatrix = static_cast<glm::dmat3>(rotation);
bool isCircleEnabled = module->showComparisonCircle();
glm::vec3 circleColor = module->comparisonCircleColor();
// 1 AU Size Comparison Circle
const std::string circle = "{"
"Identifier = '" + starIdentifier + "_1AU_Circle',"
"Parent = '" + starIdentifier + "',"
"Renderable = {"
"Type = 'RenderableRadialGrid',"
"Enabled = " + (isCircleEnabled ? "true" : "false") + ","
"Radii = { 0.0, 1.0 },"
"Color = " + ghoul::to_string(circleColor) + ","
"CircleSegments = 64,"
"LineWidth = 2.0,"
"},"
"Transform = {"
"Rotation = {"
"Type = 'StaticRotation',"
"Rotation = " + ghoul::to_string(meanOrbitPlaneRotationMatrix) + ""
"},"
"Scale = {"
"Type = 'StaticScale',"
"Scale = " + std::to_string(distanceconstants::AstronomicalUnit) + ""
"}"
"},"
"Tag = {'exoplanet_1au_ring'},"
"GUI = {"
"Name = '1 AU Size Comparison Circle',"
"Path = '" + guiPath + "'"
"}"
"}";
queueAddSceneGraphNodeScript(circle);
// Habitable Zone
bool hasTeff = !std::isnan(system.starData.teff);
bool hasLuminosity = !std::isnan(system.starData.luminosity);
if (hasTeff && hasLuminosity) {
constexpr std::string_view description =
"The habitable zone is the region around a star in which an Earth-like "
"planet can potentially have liquid water on its surface."
"<br><br>"
"The inner boundary is where the greenhouse gases in the atmosphere "
"would trap any incoming infrared radiation, leading to the planet "
"surface becoming so hot that water boils away. The outer boundary is where "
"the greenhouse effect would not be able to maintain surface temperature "
"above freezing anywhere on the planet";
const std::filesystem::path hzTexture = module->habitableZoneTexturePath();
bool isHzEnabled = module->showHabitableZone();
bool useOptimistic = module->useOptimisticZone();
float opacity = module->habitableZoneOpacity();
const std::string zoneDiscNode = "{"
"Identifier = '" + starIdentifier + "_HZ_Disc',"
"Parent = '" + starIdentifier + "',"
"Renderable = {"
"Type = 'RenderableHabitableZone',"
"Enabled = " + (isHzEnabled ? "true" : "false") + ","
"Texture = openspace.absPath('" + formatPathToLua(hzTexture) + "'),"
"Luminosity = " + std::to_string(system.starData.luminosity) + ","
"EffectiveTemperature = " + std::to_string(system.starData.teff) + ","
"Optimistic = " + (useOptimistic ? "true" : "false") + ","
"Opacity = " + std::to_string(opacity) + ""
"},"
"Transform = {"
"Rotation = {"
"Type = 'StaticRotation',"
"Rotation = " + ghoul::to_string(meanOrbitPlaneRotationMatrix) + ""
"}"
"},"
"Tag = {'exoplanet_habitable_zone'},"
"GUI = {"
"Name = '" + starName + " Habitable Zone',"
"Path = '" + guiPath + "',"
"Description = '" + std::string(description) + "'"
"}"
"}";
queueAddSceneGraphNodeScript(zoneDiscNode);
// Star glare
if (starColor.has_value()) {
// This is a little magic to make the size of the glare dependent on the
// size and the temperature of the star. It's kind of based on the fact that
// the luminosity of a star is proportional to: (radius^2)*(temperature^4)
// Maybe a better option would be to compute the size based on the aboslute
// magnitude or star luminosity, but for now this looks good enough.
double size = 59.0 * radiusInMeter;
if (hasTeff) {
constexpr float sunTeff = 5780.f;
size *= std::pow(system.starData.teff / sunTeff, 2.0);
}
const std::filesystem::path glareTexture = module->starGlareTexturePath();
const std::string starGlare = "{"
"Identifier = '" + starIdentifier + "_Glare',"
"Parent = '" + starIdentifier + "',"
"Renderable = {"
"Type = 'RenderablePlaneImageLocal',"
"Size = " + ghoul::to_string(size) + ","
"Origin = 'Center',"
"Billboard = true,"
"Texture = openspace.absPath('"
+ formatPathToLua(glareTexture) +
"'),"
"BlendMode = 'Additive',"
"Opacity = 0.65,"
"MultiplyColor = " + ghoul::to_string(*starColor) + ""
"},"
"GUI = {"
"Name = '" + sanitizedStarName + " Glare',"
"Path = '" + guiPath + "'"
"}"
"}";
queueAddSceneGraphNodeScript(starGlare);
}
}
}
std::vector<std::string> hostStarsWithSufficientData() {
using namespace openspace;
using namespace exoplanets;
const ExoplanetsModule* module = global::moduleEngine->module<ExoplanetsModule>();
if (!module->hasDataFiles()) {
// If no data file path has been configured at all, we just bail out early here
LINFO("No data path was configured for the exoplanets");
return {};
}
const std::filesystem::path lutPath = module->lookUpTablePath();
std::ifstream lookupTableFile(absPath(lutPath));
if (!lookupTableFile.good()) {
LERROR(std::format("Failed to open lookup table file '{}'", lutPath));
return {};
}
const std::filesystem::path binPath = module->exoplanetsDataPath();
std::ifstream data(absPath(binPath), std::ios::in | std::ios::binary);
if (!data.good()) {
LERROR(std::format("Failed to open data file '{}'", binPath));
return {};
}
std::vector<std::string> names;
std::string line;
// Read number of lines
int nExoplanets = 0;
while (ghoul::getline(lookupTableFile, line)) {
++nExoplanets;
}
lookupTableFile.clear();
lookupTableFile.seekg(0);
names.reserve(nExoplanets);
ExoplanetDataEntry p;
while (ghoul::getline(lookupTableFile, line)) {
std::stringstream ss(line);
std::string name;
ghoul::getline(ss, name, ',');
// Remove the last two characters, that specify the planet
name = name.substr(0, name.size() - 2);
// Don't want to list systems where there is not enough data to visualize.
// So, test if there is before adding the name to the list.
std::string location_s;
ghoul::getline(ss, location_s);
long location = std::stol(location_s.c_str());
data.seekg(location);
data.read(reinterpret_cast<char*>(&p), sizeof(ExoplanetDataEntry));
if (hasSufficientData(p)) {
names.push_back(name);
}
}
// For easier read, sort by names and remove duplicates
std::sort(names.begin(), names.end());
names.erase(std::unique(names.begin(), names.end()), names.end());
return names;
}
/**
* Add one or multiple exoplanet systems to the scene, as specified by the input. An input
* string should be the name of the system host star.
*/
[[codegen::luawrap]] void addExoplanetSystem(
std::variant<std::string, std::vector<std::string>> starNames)
{
std::vector<std::string> starsToAdd;
if (std::holds_alternative<std::string>(starNames)) {
// The user provided a single name
const std::string starName = std::get<std::string>(starNames);
starsToAdd.push_back(starName);
}
else {
starsToAdd = std::get<std::vector<std::string>>(starNames);
}
for (const std::string& starName : starsToAdd) {
openspace::exoplanets::ExoplanetSystem systemData =
findExoplanetSystemInData(starName);
if (systemData.planetsData.empty()) {
LERROR(std::format("Exoplanet system '{}' could not be found", starName));
return;
}
createExoplanetSystem(starName, systemData);
}
}
[[codegen::luawrap]] void removeExoplanetSystem(std::string starName) {
using namespace openspace;
using namespace exoplanets;
const std::string starIdentifier = makeIdentifier(std::move(starName));
// No sync or send because this is already inside a Lua script, therefor it has
// already been synced and sent to the connected nodes and peers
global::scriptEngine->queueScript({
.code = "openspace.removeSceneGraphNode('" + starIdentifier + "');",
.synchronized = scripting::ScriptEngine::Script::ShouldBeSynchronized::No,
.sendToRemote = scripting::ScriptEngine::Script::ShouldSendToRemote::No
});
}
/**
* Returns a list with names of the host star of all the exoplanet systems
* that have sufficient data for generating a visualization, based on the
* module's loaded data file.
*/
[[codegen::luawrap]] std::vector<std::string> listOfExoplanets() {
std::vector<std::string> names = hostStarsWithSufficientData();
return names;
}
/**
* Deprecated in favor of 'listOfExoplanets'
*/
[[codegen::luawrap("getListOfExoplanets")]] std::vector<std::string>
listOfExoplanetsDeprecated()
{
LWARNINGC(
"Deprecation",
"'getListOfExoplanets' function is deprecated and should be replaced with "
"'listOfExoplanets'"
);
return listOfExoplanets();
}
[[codegen::luawrap]] void listAvailableExoplanetSystems() {
std::vector<std::string> names = hostStarsWithSufficientData();
std::string output;
for (const std::string& name : names) {
output += name + ", ";
}
output.pop_back();
output.pop_back();
LINFO(std::format(
"There is data available for the following {} exoplanet systems: {}",
names.size(), output
));
}
/**
* Load a set of exoplanets based on custom data, in the form of a CSV file, and add
* them to the rendering. Can be used to load custom datasets, or more recent planets
* than what are included in the internal data file that is released with OpenSpace.
*
* The format and column names in the CSV sould be the same as the ones provided by the
* NASA Exoplanet Archive. https://exoplanetarchive.ipac.caltech.edu/
*
* We recommend downloading the file from the Exoplanet Archive's Composite data table,
* where multiple sources are combined into one row per planet.
* https://exoplanetarchive.ipac.caltech.edu
* /cgi-bin/TblView/nph-tblView?app=ExoTbls&config=PSCompPars
*
* Please remember to include all columns in the file download, as missing data columns
* may lead to an incomplete visualization.
*
* Also, avoid loading too large files of planets, as each added system will affect the
* rendering performance.
*/
[[codegen::luawrap]] void loadExoplanetsFromCsv(std::string csvFile) {
using namespace openspace;
using namespace exoplanets;
using PlanetData = ExoplanetsDataPreparationTask::PlanetData;
std::ifstream inputDataFile(csvFile);
if (!inputDataFile.good()) {
LERROR(std::format("Failed to open input file '{}'", csvFile));
return;
}
std::vector<std::string> columnNames =
ExoplanetsDataPreparationTask::readFirstDataRow(inputDataFile);
const ExoplanetsModule* module = global::moduleEngine->module<ExoplanetsModule>();
const std::filesystem::path
teffBvConversionPath = module->teffToBvConversionFilePath();
std::map<std::string, ExoplanetSystem> hostNameToSystemDataMap;
// Parse the file line by line to compose system information
std::string row;
while (ghoul::getline(inputDataFile, row)) {
PlanetData planetData = ExoplanetsDataPreparationTask::parseDataRow(
row,
columnNames,
"",
module->teffToBvConversionFilePath()
);
LINFO(std::format("Reading data for planet '{}'", planetData.name));
if (!hasSufficientData(planetData.dataEntry)) {
LWARNING(std::format(
"Insufficient data for exoplanet '{}'", planetData.name
));
continue;
}
auto found = hostNameToSystemDataMap.find(planetData.host);
if (found != hostNameToSystemDataMap.end()) {
// Found a match. Add the planet to the system data
ExoplanetSystem& system = found->second;
sanitizeNameString(planetData.name);
system.planetNames.push_back(planetData.name);
system.planetsData.push_back(planetData.dataEntry);
updateStarDataFromNewPlanet(system.starData, planetData.dataEntry);
}
else {
// No host found. Add a new one
ExoplanetSystem system;
sanitizeNameString(planetData.name);
system.planetNames.push_back(planetData.name);
system.planetsData.push_back(planetData.dataEntry);
updateStarDataFromNewPlanet(system.starData, planetData.dataEntry);
hostNameToSystemDataMap[planetData.host] = system;
}
}
// Add all the added exoplanet systems
using K = const std::string;
using V = ExoplanetSystem;
for (const std::pair<K, V>& entry : hostNameToSystemDataMap) {
const std::string& hostName = entry.first;
const ExoplanetSystem& data = entry.second;
createExoplanetSystem(hostName, data);
}
LINFO(std::format(
"Read data for {} exoplanet systems from CSV file: {}. Please wait until "
"they are all finished initializing. You may have to reload the user interface.",
hostNameToSystemDataMap.size(), csvFile
));
}
#include "exoplanetsmodule_lua_codegen.cpp"
} // namespace