/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2025 * * * * 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 namespace { constexpr std::string_view _loggerCat = "ExoplanetsModule"; // This is the documentation for the data object that is returned from the // `openspace.exoplanets.systemData` function in the Scripting API. // // The data object provides the information needed to create the scene graph nodes // for an exoplanet system. However, depending on what data is available for the // specific planets and star, some values may be undefined. struct [[codegen::Dictionary(ExoplanetSystemData)]] Parameters { // The identifier of the system, which equals the identifier of the host star // created based on the star name. std::string systemId; // The position of the host star, expressed in meters. glm::dvec3 position; // The number of known planets in the system that have enough data to be // visualized. int numPlanets; // The name of the star. std::string starName; // The radius of the star, in meters, if data exists. std::optional starRadius; // An RGB color for the host star, computed from the star's B-V value, if data // exists. std::optional starColor [[codegen::color()]]; // The effective temperature of the star, in Kelvin, if data exists. std::optional starTeff; // The luminosity for the star, in units of solar luminosities [log10(Solar)], if // data exists. std::optional starLuminosity; struct Planet { // An identifier to use for the planet, created based on the planet name. std::string id; // The name of the exoplanet. std::string name; // The radius of the planet, in meters, if data exists. std::optional starRadius; // The semi-major axis of the planet's orbit, in meters. That is, the orbit // size. This is required to be able to be visualize the planetary orbit. double semiMajorAxis; // The uncertainty of the semi-major axis, given as an asymmetric uncertainty // with two values: the lower and upper uncertainty for the orbit size. The // values are given as values relative to the size of the `SemiMajorAxis`. For // example, the lower (first) value is computed as the lower uncertainty // value divided by the value for the semi-major axis. // // If no value is given, there is no uncertainty and the semi-major axis value // is exact. std::optional semiMajorAxisUncertainty; // The eccentricity of the planet's orbit. If it does not exist in the data, // it is given a default value of 0. double eccentricity; // The inclination of the planet's orbit, given as an angle in degrees in the // range 0-360. If it does not exist in the data, it is given a default value // of 90 degrees. double inclination; // The longitude of ascending node, that is, the angle for the direction of // the point where the orbit passes through a reference plane. Also // known as the right ascension of the ascending node. The angle is given as // degrees in the range 0-360. If it does not exist in the data, it is given // a default value of 180 degrees. double ascendingNode; // The argument of periapsis of the orbit, given as an angle in degrees in // the range 0-360. It is the angle between the planet's periapsis (the point // where it is closest to its star) and its ascending node, that is, where it // passes through its reference plane. If it does not exist in the data, it // is given a default value of 90 degrees. double argumentOfPeriapsis; // The epoch to use when computing the planet's initial position in its orbit, // given as an ISO 8601-like date string of the format YYYY-MM-DDTHH:MN:SS. // // Computed based on the \"Time of Conjunction (Transit Midpoint)\" value in // the data from the exoplanet archive. std::string epoch; // Time the planet takes to make a complete orbit around the host star or // system, in days. double period; // A rotation matrix that represents the rotation of the plane of the orbit. // It is computed based on the inclination, ascending node, and argument of // periapsis values. Hence, it may be derived from default values. glm::dmat3 orbitPlaneRotationMatrix; // True, if default values have been used for any of the orbit parameters, due // to missing data. bool hasUsedDefaultValues; }; // A list of data tables for the planets in the system (that have enough data to // be visualized), with data about the planet and its orbit. std::vector planets; // A rotation matrix to use for the entire system's rotation. Broadly speaking, it // rotates the system so that the reference plane is perpendicular to the // line-of-sight from Earth (and so that a 90 degree orbit inclination leads to // an orbit that passes in front of its star, relative to the line-of-sight). glm::dmat3 systemRotation; // A rotation matrix that represents the average of all the orbit planes in the // system. Computed using the planets' average inclinations, ascending nodes, and // arguments of periapsis. glm::dmat3 meanOrbitRotation; // The distance from this system to our solar system, in light years. double distance; }; #include "datastructure_codegen.cpp" } // namespace namespace openspace::exoplanets { ghoul::Dictionary ExoplanetSystem::toDataDictionary() const { ghoul_assert( planetNames.size() == planetsData.size(), "The length of the planet names list must match the planet data list" ); ghoul::Dictionary res; res.setValue("SystemId", makeIdentifier(starName)); const glm::dvec3 starPosInParsec = static_cast(starData.position); if (!isValidPosition(starPosInParsec)) { LERROR(std::format( "Insufficient data available for exoplanet system '{}'. Could not " "determine star position", starName )); return ghoul::Dictionary(); } const glm::dvec3 starPos = starPosInParsec * distanceconstants::Parsec; res.setValue("Position", starPos); res.setValue("NumPlanets", static_cast(planetNames.size())); std::string sanitizedStarName = starName; sanitizeNameString(sanitizedStarName); res.setValue("StarName", sanitizedStarName); if (!std::isnan(starData.radius)) { double radiusInMeter = distanceconstants::SolarRadius * starData.radius; res.setValue("StarRadius", radiusInMeter); } if (!std::isnan(starData.bv)) { res.setValue("StarColor", glm::dvec3(computeStarColor(starData.bv))); } if (!std::isnan(starData.teff)) { res.setValue("StarTeff", static_cast(starData.teff)); } if (!std::isnan(starData.luminosity)) { res.setValue("StarLuminosity", static_cast(starData.luminosity)); } ghoul::Dictionary planets; std::vector inclinations; inclinations.reserve(planetNames.size()); for (size_t i = 0; i < planetNames.size(); i++) { const ExoplanetDataEntry& data = planetsData[i]; const std::string& name = planetNames[i]; const std::string id = makeIdentifier(name); ghoul::Dictionary planet; planet.setValue("Id", id); planet.setValue("Name", name); bool hasUsedDefaultValues = false; if (!std::isnan(data.r)) { double planetRadius = data.r * distanceconstants::JupiterRadius; planet.setValue("Radius", planetRadius); } // Orbit data { planet.setValue( "SemiMajorAxis", static_cast(data.a) * distanceconstants::AstronomicalUnit ); bool hasUpperAUncertainty = !std::isnan(data.aUpper); bool hasLowerAUncertainty = !std::isnan(data.aLower); if (hasUpperAUncertainty && hasLowerAUncertainty) { const float lowerOffset = static_cast(data.aLower / data.a); const float upperOffset = static_cast(data.aUpper / data.a); planet.setValue( "SemiMajorAxisUncertainty", glm::dvec2(lowerOffset, upperOffset) ); } if (!std::isnan(data.ecc)) { planet.setValue("Eccentricity", static_cast(data.ecc)); } else { planet.setValue("Eccentricity", 0.0); hasUsedDefaultValues = true; } // KeplerTranslation requires angles in range [0, 360] auto validAngle = [&hasUsedDefaultValues](float angle, float defaultValue) { if (std::isnan(angle)) { hasUsedDefaultValues = true; return defaultValue; } while (angle < 0.f) { angle += 360.f; } while (angle > 360.f) { angle = -360.f; } return angle; }; float inclination = validAngle(data.i, 90.f); float bigOmega = validAngle(data.bigOmega, 180.f); float omega = validAngle(data.omega, 90.f); inclinations.push_back(inclination); planet.setValue("Inclination", static_cast(inclination)); planet.setValue("AscendingNode", static_cast(bigOmega)); planet.setValue("ArgumentOfPeriapsis", static_cast(omega)); std::string sEpoch; if (!std::isnan(data.tt)) { Time epoch; epoch.setTime("JD " + std::to_string(data.tt)); sEpoch = std::string(epoch.ISO8601()); } else { hasUsedDefaultValues = true; sEpoch = "2009-05-19T07:11:34.080"; } planet.setValue("Epoch", sEpoch); planet.setValue("Period", data.per); const glm::dmat4 rotation = computeOrbitPlaneRotationMatrix( inclination, bigOmega, omega ); const glm::dmat3 rotationMat3 = static_cast(rotation); planet.setValue("OrbitPlaneRotationMatrix", rotationMat3); planet.setValue("HasUsedDefaultValues", hasUsedDefaultValues); } planets.setValue(id, planet); } res.setValue("Planets", planets); // Extra stuff that is useful for rendering const glm::dmat3 exoplanetSystemRotation = computeSystemRotation(starPos); res.setValue("SystemRotation", exoplanetSystemRotation); float meanInclination = 0.f; for (const float& i : inclinations) { meanInclination += i / static_cast(inclinations.size()); } const glm::dmat4 rotation = computeOrbitPlaneRotationMatrix(meanInclination); const glm::dmat3 meanOrbitPlaneRotationMatrix = static_cast(rotation); res.setValue("MeanOrbitRotation", meanOrbitPlaneRotationMatrix); double distanceToOurSystem = glm::length(starPosInParsec) * distanceconstants::Parsec / distanceconstants::LightYear; res.setValue("Distance", distanceToOurSystem); return res; } documentation::Documentation ExoplanetSystem::Documentation() { return codegen::doc("exoplanets_exoplanet_system_data"); } } // namespace openspace::exoplanets