Warning, file /education/kstars/kstars/skycomponents/asteroidscomponent.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2005 Thomas Kabelmann <thomas.kabelmann@gmx.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "asteroidscomponent.h" 0008 #include "ksutils.h" 0009 0010 #ifndef KSTARS_LITE 0011 #include "kstars.h" 0012 #endif 0013 #include "ksfilereader.h" 0014 #include "kstarsdata.h" 0015 #include "kstars_debug.h" 0016 #include "Options.h" 0017 #include "solarsystemcomposite.h" 0018 #include "skycomponent.h" 0019 #include "skylabeler.h" 0020 #ifndef KSTARS_LITE 0021 #include "skymap.h" 0022 #else 0023 #include "kstarslite.h" 0024 #endif 0025 #include "skypainter.h" 0026 #include "auxiliary/kspaths.h" 0027 #include "auxiliary/ksnotification.h" 0028 #include "auxiliary/filedownloader.h" 0029 #include "projections/projector.h" 0030 0031 #include <KLocalizedString> 0032 0033 #include <QDebug> 0034 #include <QStandardPaths> 0035 #include <QHttpMultiPart> 0036 #include <QPen> 0037 0038 #include <cmath> 0039 0040 AsteroidsComponent::AsteroidsComponent(SolarSystemComposite *parent) 0041 : BinaryListComponent(this, "asteroids"), SolarSystemListComponent(parent) 0042 { 0043 loadData(); 0044 } 0045 0046 bool AsteroidsComponent::selected() 0047 { 0048 return Options::showAsteroids(); 0049 } 0050 0051 /* 0052 * @short Initialize the asteroids list. 0053 * Reads in the asteroids data from the asteroids.dat file 0054 * and writes it into the Binary File; 0055 * 0056 * The data file is a CSV file with the following columns : 0057 * @li 1 full name [string] 0058 * @li 2 Modified Julian Day of orbital elements [int] 0059 * @li 3 perihelion distance in AU [double] 0060 * @li 4 semi-major axis 0061 * @li 5 eccentricity of orbit [double] 0062 * @li 6 inclination angle of orbit in degrees [double] 0063 * @li 7 argument of perihelion in degrees [double] 0064 * @li 8 longitude of the ascending node in degrees [double] 0065 * @li 9 mean anomaly 0066 * @li 10 time of perihelion passage (YYYYMMDD.DDD) [double] 0067 * @li 11 orbit solution ID [string] 0068 * @li 12 absolute magnitude [float] 0069 * @li 13 slope parameter [float] 0070 * @li 14 Near-Earth Object (NEO) flag [bool] 0071 * @li 15 comet total magnitude parameter [float] (we should remove this column) 0072 * @li 16 comet nuclear magnitude parameter [float] (we should remove this column) 0073 * @li 17 object diameter (from equivalent sphere) [float] 0074 * @li 18 object bi/tri-axial ellipsoid dimensions [string] 0075 * @li 19 geometric albedo [float] 0076 * @li 20 rotation period [float] 0077 * @li 21 orbital period [float] 0078 * @li 22 earth minimum orbit intersection distance [double] 0079 * @li 23 orbit classification [string] 0080 */ 0081 void AsteroidsComponent::loadDataFromText() 0082 { 0083 QString name, full_name, orbit_id, orbit_class, dimensions; 0084 int mJD; 0085 double q, a, e, dble_i, dble_w, dble_N, dble_M, H, G, earth_moid; 0086 long double JD; 0087 float diameter, albedo, rot_period, period; 0088 bool neo; 0089 0090 emitProgressText(i18n("Loading asteroids")); 0091 qCInfo(KSTARS) << "Loading asteroids"; 0092 0093 try 0094 { 0095 KSUtils::JPLParser ast_parser(filepath_txt); 0096 auto fieldMap = ast_parser.fieldMap(); 0097 bool isString = fieldMap.count("epoch_mjd") == 1; 0098 0099 ast_parser.for_each( 0100 [&](const auto & get) 0101 { 0102 full_name = get("full_name").toString(); 0103 full_name = full_name.trimmed(); 0104 int catN = full_name.section(' ', 0, 0).toInt(); 0105 name = full_name.section(' ', 1, -1); 0106 0107 //JM temporary hack to avoid Europa,Io, and Asterope duplication 0108 if (name == i18nc("Asteroid name (optional)", "Europa") || 0109 name == i18nc("Asteroid name (optional)", "Io") || 0110 name == i18nc("Asteroid name (optional)", "Asterope")) 0111 name += i18n(" (Asteroid)"); 0112 0113 // JM 2022.08.26: Try to check if the file is in the new format 0114 // where epoch_mjd field is a string 0115 if (isString) 0116 { 0117 mJD = get("epoch_mjd").toString().toInt(); 0118 period = get("per_y").toString().toDouble(); 0119 } 0120 // If not fall back to old behavior 0121 else 0122 { 0123 mJD = get("epoch.mjd").toInt(); 0124 period = get("per.y").toDouble(); 0125 } 0126 0127 q = get("q").toString().toDouble(); 0128 a = get("a").toString().toDouble(); 0129 e = get("e").toString().toDouble(); 0130 dble_i = get("i").toString().toDouble(); 0131 dble_w = get("w").toString().toDouble(); 0132 dble_N = get("om").toString().toDouble(); 0133 dble_M = get("ma").toString().toDouble(); 0134 orbit_id = get("orbit_id").toString(); 0135 H = get("H").toString().toDouble(); 0136 G = get("G").toString().toDouble(); 0137 neo = get("neo").toString() == "Y"; 0138 diameter = get("diameter").toString().toFloat(); 0139 dimensions = get("extent").toString(); 0140 albedo = get("albedo").toString().toFloat(); 0141 rot_period = get("rot_per").toString().toFloat(); 0142 earth_moid = get("moid").toString().toDouble(); 0143 orbit_class = get("class").toString(); 0144 0145 JD = static_cast<double>(mJD) + 2400000.5; 0146 0147 KSAsteroid *new_asteroid = nullptr; 0148 0149 // Diameter is missing from JPL data 0150 if (name == i18nc("Asteroid name (optional)", "Pluto")) 0151 diameter = 2390; 0152 0153 new_asteroid = 0154 new KSAsteroid(catN, name, QString(), JD, a, e, dms(dble_i), 0155 dms(dble_w), dms(dble_N), dms(dble_M), H, G); 0156 0157 new_asteroid->setPerihelion(q); 0158 new_asteroid->setOrbitID(orbit_id); 0159 new_asteroid->setNEO(neo); 0160 new_asteroid->setDiameter(diameter); 0161 new_asteroid->setDimensions(dimensions); 0162 new_asteroid->setAlbedo(albedo); 0163 new_asteroid->setRotationPeriod(rot_period); 0164 new_asteroid->setPeriod(period); 0165 new_asteroid->setEarthMOID(earth_moid); 0166 new_asteroid->setOrbitClass(orbit_class); 0167 new_asteroid->setPhysicalSize(diameter); 0168 //new_asteroid->setAngularSize(0.005); 0169 0170 appendListObject(new_asteroid); 0171 0172 // Add name to the list of object names 0173 objectNames(SkyObject::ASTEROID).append(name); 0174 objectLists(SkyObject::ASTEROID) 0175 .append(QPair<QString, const SkyObject *>(name, new_asteroid)); 0176 }); 0177 } 0178 catch (const std::runtime_error &e) 0179 { 0180 qCInfo(KSTARS) << "Loading asteroid objects failed."; 0181 qCInfo(KSTARS) << " -> was trying to read " + filepath_txt; 0182 return; 0183 } 0184 } 0185 0186 void AsteroidsComponent::draw(SkyPainter *skyp) 0187 { 0188 Q_UNUSED(skyp) 0189 #ifndef KSTARS_LITE 0190 if (!selected()) 0191 return; 0192 0193 bool hideLabels = !Options::showAsteroidNames() || (SkyMap::Instance()->isSlewing() && Options::hideLabels()); 0194 0195 double labelMagLimit = Options::asteroidLabelDensity(); // Slider min value 0, max value 20. 0196 const double showMagLimit = Options::magLimitAsteroid(); 0197 const double lgmin = log10(MINZOOM); 0198 const double lgmax = log10(MAXZOOM); 0199 const double lgz = log10(Options::zoomFactor()); 0200 const double densityLabelFactor = 10.0; // Value of 10.0 influences the slider mag value [0, 2], 0201 // where a value 5.0 influences the slider mag value [0, 4]. 0202 const double zoomLimit = (lgz - lgmin) / (lgmax - lgmin); // Min-max normalize into [lgmin, lgmax]. 0203 0204 // Map labelMagLimit into interval [0, 20.0 / densityLabelFactor] 0205 labelMagLimit = std::max(1e-3, labelMagLimit) / densityLabelFactor; 0206 // If zooming closer, then the labelMagLimit gets closer to showMagLimit value. 0207 // If sliding density value to the right, then the labelMagLimit gets closer to showMagLimit value. 0208 // It is however assured that labelMagLimit <= showMagLimit. 0209 labelMagLimit = showMagLimit - 20.0 / densityLabelFactor + std::max(zoomLimit, labelMagLimit); 0210 0211 foreach (SkyObject *so, m_ObjectList) 0212 { 0213 KSAsteroid *ast = dynamic_cast<KSAsteroid *>(so); 0214 0215 if (!ast->toDraw() || std::isnan(ast->mag()) || ast->mag() > showMagLimit) 0216 continue; 0217 0218 bool drawn = false; 0219 0220 if (ast->image().isNull() == false) 0221 drawn = skyp->drawPlanet(ast); 0222 else 0223 drawn = skyp->drawAsteroid(ast); 0224 0225 if (drawn && !hideLabels && ast->mag() <= labelMagLimit) 0226 SkyLabeler::AddLabel(ast, SkyLabeler::ASTEROID_LABEL); 0227 } 0228 #endif 0229 } 0230 0231 SkyObject *AsteroidsComponent::objectNearest(SkyPoint *p, double &maxrad) 0232 { 0233 SkyObject *oBest = nullptr; 0234 0235 if (!selected()) 0236 return nullptr; 0237 0238 for (auto o : m_ObjectList) 0239 { 0240 if (!((dynamic_cast<KSAsteroid*>(o)->toDraw()))) 0241 continue; 0242 0243 double r = o->angularDistanceTo(p).Degrees(); 0244 if (r < maxrad) 0245 { 0246 oBest = o; 0247 maxrad = r; 0248 } 0249 } 0250 0251 return oBest; 0252 } 0253 0254 void AsteroidsComponent::updateDataFile(bool isAutoUpdate) 0255 { 0256 delete (downloadJob); 0257 downloadJob = new FileDownloader(); 0258 0259 if (isAutoUpdate == false) 0260 downloadJob->setProgressDialogEnabled(true, i18n("Asteroid Update"), 0261 i18n("Downloading asteroids updates...")); 0262 downloadJob->registerDataVerification([&](const QByteArray & data) 0263 { 0264 return data.startsWith("{\"signature\""); 0265 }); 0266 0267 QObject::connect(downloadJob, SIGNAL(downloaded()), this, SLOT(downloadReady())); 0268 if (isAutoUpdate == false) 0269 QObject::connect(downloadJob, SIGNAL(error(QString)), this, 0270 SLOT(downloadError(QString))); 0271 0272 QUrl url = QUrl("https://ssd-api.jpl.nasa.gov/sbdb_query.api"); 0273 0274 QByteArray mag = QString::number(Options::magLimitAsteroidDownload()).toUtf8(); 0275 QByteArray post_data = 0276 KSUtils::getJPLQueryString("a", 0277 "full_name,neo,H,G,diameter,extent,albedo,rot_per," 0278 "orbit_id,epoch_mjd,e,a,q,i,om,w,ma,per_y,moid,class", 0279 QVector<KSUtils::JPLFilter> { { "H", "LT", mag } }); 0280 downloadJob->post(url, post_data); 0281 } 0282 0283 void AsteroidsComponent::downloadReady() 0284 { 0285 // Comment the first line 0286 QByteArray data = downloadJob->downloadedData(); 0287 0288 // Write data to asteroids.dat 0289 QFile file(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)) 0290 .filePath("asteroids.dat")); 0291 if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) 0292 { 0293 file.write(data); 0294 file.close(); 0295 } 0296 else 0297 qCWarning(KSTARS) << "Failed writing asteroid data to" << file.fileName(); 0298 0299 QString focusedAstroid; 0300 0301 #ifdef KSTARS_LITE 0302 SkyObject *foc = KStarsLite::Instance()->map()->focusObject(); 0303 if (foc && foc->type() == SkyObject::ASTEROID) 0304 { 0305 focusedAstroid = foc->name(); 0306 KStarsLite::Instance()->map()->setFocusObject(nullptr); 0307 } 0308 #else 0309 SkyObject *foc = KStars::Instance()->map()->focusObject(); 0310 if (foc && foc->type() == SkyObject::ASTEROID) 0311 { 0312 focusedAstroid = foc->name(); 0313 KStars::Instance()->map()->setFocusObject(nullptr); 0314 } 0315 0316 #endif 0317 // Reload asteroids 0318 loadData(true); 0319 0320 #ifdef KSTARS_LITE 0321 KStarsLite::Instance()->data()->setFullTimeUpdate(); 0322 if (!focusedAstroid.isEmpty()) 0323 KStarsLite::Instance()->map()->setFocusObject( 0324 KStarsLite::Instance()->data()->objectNamed(focusedAstroid)); 0325 #else 0326 if (!focusedAstroid.isEmpty()) 0327 KStars::Instance()->map()->setFocusObject( 0328 KStars::Instance()->data()->objectNamed(focusedAstroid)); 0329 KStars::Instance()->data()->setFullTimeUpdate(); 0330 #endif 0331 downloadJob->deleteLater(); 0332 } 0333 0334 void AsteroidsComponent::downloadError(const QString &errorString) 0335 { 0336 KSNotification::error(i18n("Error downloading asteroids data: %1", errorString)); 0337 qDebug() << Q_FUNC_INFO << i18n("Error downloading asteroids data: %1", errorString); 0338 downloadJob->deleteLater(); 0339 }