/***************************************************************************************** * * * 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 #include #include #include namespace { constexpr const char* _loggerCat = "Coordinateconversion"; // J2000 Galactic reference frame // Equatorial coordinates of the Galactic north pole constexpr double A0 = glm::radians(192.8595); constexpr double D0 = glm::radians(27.1284); // Galactic longitude of the equatorial north pole constexpr double L0 = glm::radians(122.9320); void parseString(const std::string& str, int& hoursOrDegrees, int& minutes, double& seconds) { // Find hms or dms indicies size_t hOrDIndex = (str.find('h') != std::string::npos) ? str.find('h') : str.find('d'); size_t mIndex = str.find('m'); size_t sIndex = str.find('s'); if (hOrDIndex == std::string::npos || mIndex == std::string::npos || sIndex == std::string::npos) { throw ghoul::lua::LuaRuntimeException(fmt::format( "Ra or Dec '{}' format is incorrect. Correct format is: Ra 'XhYmZs', " "and Dec 'XdYmZs'", str )); } // Construct the number strings std::string sHoursOrDegrees = str.substr(0, hOrDIndex); std::string sMinutes = str.substr(hOrDIndex + 1, mIndex - hOrDIndex - 1); std::string sSeconds = str.substr(mIndex + 1, sIndex - mIndex - 1); // Convert the strings to numbers try { // Hours or degrees must be an integer double temp = std::stod(sHoursOrDegrees); if (std::floor(temp) != temp) { throw ghoul::lua::LuaRuntimeException(fmt::format( "Ra or Dec '{}' format is incorrect. Correct format is: Ra 'XhYmZs', " "and Dec 'XdYmZs', where X must be an integer", str )); } hoursOrDegrees = std::stoi(sHoursOrDegrees); // Minutes must be an integer temp = std::stod(sMinutes); if (std::floor(temp) != temp) { throw ghoul::lua::LuaRuntimeException(fmt::format( "Ra or Dec '{}' format is incorrect. Correct format is: Ra 'XhYmZs', " "and Dec 'XdYmZs', where Y must be an integer", str )); } minutes = std::stoi(sMinutes); // Seconds is a double seconds = std::stod(sSeconds); } catch (const std::invalid_argument&) { throw ghoul::lua::LuaRuntimeException(fmt::format( "Ra or Dec '{}' format is incorrect. Correct format is: Ra 'XhYmZs', " "and Dec 'XdYmZs'", str )); } } void parseRa(const std::string& ra, int& hours, int& minutes, double& seconds) { if (ra.find('d') != std::string::npos) { throw ghoul::lua::LuaRuntimeException(fmt::format( "Ra '{}' format is incorrect. Correct format is: 'XhYmZs'", ra )); } parseString(ra, hours, minutes, seconds); } void parseDec(const std::string& dec, int& degrees, int& minutes, double& seconds) { if (dec.find('h') != std::string::npos) { throw ghoul::lua::LuaRuntimeException(fmt::format( "Dec '{}' format is incorrect. Correct format is: 'XdYmZs'", dec )); } parseString(dec, degrees, minutes, seconds); } bool isRaDecValid(int raH, int raM, double raS, int decD, int decM, double decS) { // Ra if (raH < 0.0 || raH >= 24.0) { LWARNING(fmt::format( "Right ascension hours '{}' is outside the allowed range of 0 to 24 " "hours (exclusive)", raH )); return false; } if (raM < 0.0 || raM >= 60.0) { LWARNING(fmt::format( "Right ascension minutes '{}' is outside the allowed range of 0 to 60 " "minutes (exclusive)", raM )); return false; } if (raS < 0.0 || raS >= 60.0) { LWARNING(fmt::format( "Right ascension seconds '{}' is outside the allowed " "range of 0 to 60 seconds (exclusive)", raS )); return false; } // Dec if (decD < -90.0 || decD > 90.0) { LWARNING(fmt::format("Declination degrees '{}' is outside the allowed range " "of -90 to 90 degrees (inclusive)", decD )); return false; } else if ((decD == -90.0 || decD == 90.0) && (decM != 0 || decS != 0)) { LWARNING( "Total declination is outside the allowed range of -90 to 90 degrees " "(inclusive)" ); return false; } if (decM < 0.0 || decM >= 60.0) { LWARNING(fmt::format( "Declination minutes '{}' is outside the allowed range of 0 to 60 " "minutes (exclusive)", decM )); return false; } if (decS < 0.0 || decS >= 60.0) { LWARNING(fmt::format( "Declination seconds '{}' is outside the allowed range of 0 to 60 " "seconds (exclusive)", decS )); return false; } return true; } } // namespace namespace openspace { // Convert Equatorial coordinates ICRS right ascension and declination (a, d) // into Galactic coordinates (l, b) // Reference: // https://www.atnf.csiro.au/people/Tobias.Westmeier/tools_coords.php glm::dvec3 icrsToGalacticCartesian(double ra, double dec, double distance) { // (Ra, Dec) -> (a, d) double a = glm::radians(ra); double d = glm::radians(dec); // Convert to galactic reference frame double l = L0 - atan2( cos(d) * sin(a - A0), sin(d) * cos(D0) - cos(d) * sin(D0) * cos(a - A0) ); double b = asin(sin(d) * sin(D0) + cos(d) * cos(D0) * cos(a - A0)); // Convert to cartesian glm::dvec3 rGalactic = glm::dvec3( cos(b) * cos(l), cos(b) * sin(l), sin(b) ); return distance * rGalactic; } // Ra format 'XhYmZs', where X and Y are positive integers and Z is a positive double // Dec format 'XdYmZs', where X is a signed integer, Y is a positive integer and Z is a // positive double // Reference: // https://math.stackexchange.com/questions/15323/how-do-i-calculate-the-cartesian- // coordinates-of-stars glm::dvec2 icrsToDecimalDegrees(const std::string& ra, const std::string& dec) { if (ra.size() < 6 || dec.size() < 6) { throw ghoul::lua::LuaRuntimeException(fmt::format( "Ra '{}' or Dec '{}' format is incorrect. Correct format is: Ra 'XhYmZs', " "and Dec 'XdYmZs'", ra, dec )); } // Parse right ascension int raHours, raMinutes; double raSeconds; parseRa(ra, raHours, raMinutes, raSeconds); // Parse declination int decDegrees, decMinutes; double decSeconds; parseDec(dec, decDegrees, decMinutes, decSeconds); const bool isValid = isRaDecValid(raHours, raMinutes, raSeconds, decDegrees, decMinutes, decSeconds ); if (!isValid) { LWARNING(fmt::format( "Ra '{}' or Dec '{}' is outside the allowed range, result may be incorrect", ra, dec )); } // Convert from hours/degrees, minutes, seconds to decimal degrees double sign = std::signbit(static_cast(decDegrees)) ? -1.0 : 1.0; double raDeg = (raHours * 15.0) + (raMinutes * 15.0 / 60.0) + (raSeconds * 15.0 / 3600.0); double decDeg = (abs(decDegrees) + (decMinutes / 60.0) + (decSeconds / 3600.0)) * sign; return glm::dvec2(raDeg, decDeg); } // Convert Galactic coordinates (x, y, z) or (l, b) into Equatorial coordinates ICRS // right ascension and declination in decimal degrees (a, d) plus distance // References: // https://www.atnf.csiro.au/people/Tobias.Westmeier/tools_coords.php, // https://en.wikipedia.org/wiki/Celestial_coordinate_system glm::dvec3 galacticCartesianToIcrs(double x, double y, double z) { // Normalize double distance = sqrt(x*x + y*y + z*z); double nX = x / distance; double nY = y / distance; double nZ = z / distance; // Convert from cartesian // (x, y, z) -> (l, b) double l = atan2(nY, nX); double b = asin(nZ); // Convert to equatorial reference frame double a = atan2( cos(b) * sin(L0 - l), sin(b) * cos(D0) - cos(b) * sin(D0) * cos(L0 - l) ) + A0; double d = asin(sin(b) * sin(D0) + cos(b) * cos(D0) * cos(L0 - l)); return glm::dvec3(glm::degrees(a), glm::degrees(d), distance); } // Return a pair with two formatted strings from the decimal degrees ra and dec // References: // https://www.rapidtables.com/convert/number/degrees-to-degrees-minutes-seconds.html, // https://math.stackexchange.com/questions/15323/how-do-i-calculate-the-cartesian- // coordinates-of-stars std::pair decimalDegreesToIcrs(double ra, double dec) { // Radians to degrees double raDeg = ra; double decDeg = dec; // Check input if (raDeg < 0 || raDeg > 360 || decDeg < -90 || decDeg > 90) { LWARNING(fmt::format( "Ra '{}' or Dec '{}' is outside the allowed range, result may be incorrect", ra, dec )); } // Calculate Ra int raHours = static_cast(std::trunc(raDeg) / 15.0); double raMinutesFull = (raDeg - raHours * 15.0) * 60.0 / 15.0; int raMinutes = static_cast(std::trunc(raMinutesFull)); double raSeconds = (raMinutesFull - raMinutes) * 60.0; // Calculate Dec int decDegrees = static_cast(std::trunc(decDeg)); double decMinutesFull = (abs(decDeg) - abs(decDegrees)) * 60.0; int decMinutes = static_cast(std::trunc(decMinutesFull)); double decSeconds = (decMinutesFull - decMinutes) * 60.0; // Construct strings std::pair result; result.first = std::to_string(raHours) + 'h' + std::to_string(raMinutes) + 'm' + std::to_string(raSeconds) + 's'; result.second = std::to_string(decDegrees) + 'd' + std::to_string(decMinutes) + 'm' + std::to_string(decSeconds) + 's'; // Check results const bool isValid = isRaDecValid(raHours, raMinutes, raSeconds, decDegrees, decMinutes, decSeconds ); if (!isValid) { LWARNING(fmt::format( "Resulting Ra '{}' or Dec '{}' is outside the allowed range, result may be " "incorrect", result.first, result.second )); } return result; } } // namespace openspace