File indexing completed on 2024-04-14 03:43:22
0001 /* 0002 SPDX-FileCopyrightText: 2014 Akarsh Simha <akarsh.simha@kdemail.net> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 /* Project Includes */ 0008 #include "nameresolver.h" 0009 #include "catalogobject.h" 0010 0011 /* KDE Includes */ 0012 #ifndef KSTARS_LITE 0013 #include <kio/filecopyjob.h> 0014 #else 0015 #include "kstarslite.h" 0016 #endif 0017 0018 /* Qt Includes */ 0019 #include <QUrl> 0020 #include <QTemporaryFile> 0021 #include <QString> 0022 #include <QXmlStreamReader> 0023 #include <QNetworkRequest> 0024 #include <QNetworkReply> 0025 #include <QNetworkAccessManager> 0026 #include <QEventLoop> 0027 0028 #include <kstars_debug.h> 0029 0030 std::pair<bool, CatalogObject> NameResolver::resolveName(const QString &name) 0031 { 0032 const auto &found_sesame{ NameResolverInternals::sesameResolver(name) }; 0033 if (!found_sesame.first) 0034 { 0035 QString msg = 0036 xi18n("Error: sesameResolver failed. Could not resolve name on CDS Sesame."); 0037 qCDebug(KSTARS) << msg; 0038 0039 #ifdef KSTARS_LITE 0040 KStarsLite::Instance()->notificationMessage(msg); 0041 #endif 0042 } 0043 // More to be done here if the resolved name is SIMBAD 0044 return found_sesame; 0045 } 0046 0047 std::pair<bool, CatalogObject> 0048 NameResolver::NameResolverInternals::sesameResolver(const QString &name) 0049 { 0050 QUrl resolverUrl = QUrl( 0051 QString("http://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/-oxpFI/SNV?%1").arg(name)); 0052 0053 QString msg = xi18n("Attempting to resolve object %1 using CDS Sesame.", name); 0054 qCDebug(KSTARS) << msg; 0055 0056 #ifdef KSTARS_LITE 0057 KStarsLite::Instance()->notificationMessage(msg); 0058 #endif 0059 0060 QNetworkAccessManager manager; 0061 QNetworkReply *response = manager.get(QNetworkRequest(resolverUrl)); 0062 Q_ASSERT(response); 0063 0064 // Wait synchronously 0065 QEventLoop event; 0066 QObject::connect(response, SIGNAL(finished()), &event, SLOT(quit())); 0067 event.exec(); 0068 0069 if (response->error() != QNetworkReply::NoError) 0070 { 0071 msg = xi18n("Error trying to get XML response from CDS Sesame server: %1", 0072 response->errorString()); 0073 qWarning() << msg; 0074 0075 #ifdef KSTARS_LITE 0076 KStarsLite::Instance()->notificationMessage(msg); 0077 #endif 0078 return { false, {} }; 0079 } 0080 0081 QXmlStreamReader xml(response->readAll()); 0082 response->deleteLater(); 0083 if (xml.atEnd()) 0084 { 0085 // file is empty 0086 msg = xi18n("Empty result instead of expected XML from CDS Sesame. Maybe bad " 0087 "Internet connection?"); 0088 qCDebug(KSTARS) << msg; 0089 0090 #ifdef KSTARS_LITE 0091 KStarsLite::Instance()->notificationMessage(msg); 0092 #endif 0093 return { false, {} }; 0094 } 0095 0096 CatalogObject data{ 0097 CatalogObject::oid{}, SkyObject::STAR, dms{ 0 }, dms{ 0 }, 0, name, name 0098 }; 0099 0100 bool found{ false }; 0101 while (!xml.atEnd() && !xml.hasError()) 0102 { 0103 QXmlStreamReader::TokenType token = xml.readNext(); 0104 0105 if (xml.isStartDocument()) 0106 continue; 0107 0108 if (token == QXmlStreamReader::StartElement) 0109 { 0110 qCDebug(KSTARS) << "Parsing token with name " << xml.name(); 0111 0112 if (xml.name() == "Resolver") 0113 { 0114 found = true; 0115 // This is the section we want 0116 char resolver = 0; 0117 QXmlStreamAttributes attributes = xml.attributes(); 0118 if (attributes.hasAttribute("name")) 0119 resolver = 0120 attributes.value("name") 0121 .at(0) 0122 .toLatin1(); // Expected to be S (Simbad), V (VizieR), or N (NED) 0123 else 0124 { 0125 resolver = 0; // NUL character for unknown resolver 0126 qWarning() << "Warning: Unknown resolver " 0127 << attributes.value("name ") 0128 << " while reading output from CDS Sesame"; 0129 } 0130 0131 qCDebug(KSTARS) 0132 << "Resolved by " << resolver << attributes.value("name") << "!"; 0133 0134 // Start reading the data to pick out the relevant ones 0135 while (xml.readNextStartElement()) 0136 { 0137 if (xml.name() == "otype") 0138 { 0139 const QString typeString = xml.readElementText(); 0140 data.setType(interpretObjectType(typeString)); 0141 } 0142 else if (xml.name() == "jradeg") 0143 { 0144 data.setRA0(dms{ xml.readElementText().toDouble() }); 0145 } 0146 else if (xml.name() == "jdedeg") 0147 { 0148 data.setDec0(dms{ xml.readElementText().toDouble() }); 0149 } 0150 else if (xml.name() == "mag") 0151 { 0152 attributes = xml.attributes(); 0153 char band; 0154 if (attributes.hasAttribute("band")) 0155 { 0156 band = attributes.value("band").at(0).toLatin1(); 0157 } 0158 else 0159 { 0160 qWarning() << "Warning: Magnitude of unknown band found " 0161 "while reading output from CDS Sesame"; 0162 band = 0; 0163 } 0164 0165 float mag = NaN::f; 0166 xml.readNext(); 0167 if (xml.isCharacters()) 0168 { 0169 qCDebug(KSTARS) << "characters: " << xml.tokenString(); 0170 mag = xml.tokenString().toFloat(); 0171 } 0172 else if (xml.isStartElement()) 0173 { 0174 while (xml.name() != "v") 0175 { 0176 qCDebug(KSTARS) << "element: " << xml.name(); 0177 xml.readNextStartElement(); 0178 } 0179 mag = xml.readElementText().toFloat(); 0180 qCDebug(KSTARS) 0181 << "Got " << xml.tokenString() << " mag = " << mag; 0182 while (!xml.atEnd() && xml.readNext() && xml.name() != "mag") 0183 ; // finish reading the <mag> tag all the way to </mag> 0184 } 0185 else 0186 qWarning() 0187 << "Failed to parse Xml token in magnitude element: " 0188 << xml.tokenString(); 0189 0190 if (band == 'V') 0191 { 0192 data.setMag(mag); 0193 } 0194 else if (band == 'B') 0195 { 0196 data.setFlux(mag); // FIXME: This is bad 0197 if (std::isnan(data.mag())) 0198 data.setMag(mag); // FIXME: This is bad too 0199 } 0200 // Don't know what to do with other magnitudes, until we have a magnitude hash 0201 } 0202 else if (xml.name() == "oname") // Primary identifier 0203 { 0204 QString contents = xml.readElementText(); 0205 data.setCatalogIdentifier(contents); 0206 } 0207 else 0208 xml.skipCurrentElement(); 0209 // TODO: Parse aliases for common names 0210 } 0211 break; 0212 } 0213 else 0214 continue; 0215 } 0216 } 0217 if (xml.hasError()) 0218 { 0219 msg = xi18n("Error parsing XML from CDS Sesame: %1 on line %2 @ col = %3", 0220 xml.errorString(), xml.lineNumber(), xml.columnNumber()); 0221 qCDebug(KSTARS) << msg; 0222 0223 #ifdef KSTARS_LITE 0224 KStarsLite::Instance()->notificationMessage(msg); 0225 #endif 0226 return { false, {} }; 0227 } 0228 0229 if (!found) 0230 return { false, {} }; 0231 0232 msg = xi18n("Resolved %1 successfully.", name); 0233 qCDebug(KSTARS) << msg; 0234 0235 #ifdef KSTARS_LITE 0236 KStarsLite::Instance()->notificationMessage(msg); 0237 #endif 0238 qCDebug(KSTARS) << "Object type: " << SkyObject::typeName(data.type()) 0239 << "; Coordinates: " << data.ra0().Degrees() << ";" 0240 << data.dec().Degrees(); 0241 return { true, data }; 0242 } 0243 0244 // bool NameResolver::NameResolverInternals::getDataFromSimbad( class CatalogObject &data ) { 0245 // // TODO: Implement 0246 // // QUrl( QString( "http://simbad.u-strasbg.fr/simbad/sim-script?script=output%20console=off%20script=off%0Aformat%20object%20%22%25DIM%22%0A" ) + data.name ); 0247 // } 0248 0249 SkyObject::TYPE NameResolver::NameResolverInternals::interpretObjectType(const QString &typeString, bool caseSensitive) 0250 { 0251 // FIXME: Due to the quirks of Sesame (SIMBAD vs NED etc), it 0252 // might be very difficult to discern the type in all cases. The 0253 // best way TODO things might be to first run the query with NED, 0254 // and if it is extragalactic, then trust NED and 0255 // accept. Otherwise, or if NED did not return a result, re-run 0256 // the query on SIMBAD and VizieR and use that result, if any. 0257 0258 // See https://simbad.u-strasbg.fr/simbad/sim-display?data=otypes for Object Classification in SIMBAD 0259 0260 // Highest likelihood is a galaxy of some form, so we process that first 0261 const QString &s = typeString; // To make the code compact 0262 Qt::CaseSensitivity cs = (caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); 0263 0264 QStringList galaxyTypes; 0265 QStringList galaxyGroupTypes; 0266 QStringList openClusterTypes; 0267 QStringList radioSourceTypes; 0268 galaxyTypes << "G" 0269 << "LIN" 0270 << "AGN" 0271 << "GiG" 0272 << "GiC" 0273 << "H2G" 0274 << "BiC" 0275 << "GiP" 0276 << "HzG" 0277 << "rG" 0278 << "AG?" 0279 << "EmG" 0280 << "LSB" 0281 << "SBG" 0282 << "bCG" 0283 << "SyG" 0284 << "Sy1" 0285 << "Sy2" 0286 << "Gxy"; 0287 0288 galaxyGroupTypes << "GClstr" 0289 << "GGroup" 0290 << "GPair" 0291 << "ClG" 0292 << "CGG" 0293 << "PaG" 0294 << "IG" 0295 << "GrG" 0296 << "SCG"; // NOTE (FIXME?): Compact groups and pairs of galaxies ar treated like galaxy clusters 0297 0298 openClusterTypes << "*Cl" 0299 << "Cl*" 0300 << "OpC" 0301 << "As*" 0302 << "St*" 0303 << "OCl"; 0304 0305 radioSourceTypes << "Rad" 0306 << "mR" 0307 << "cm" 0308 << "mm" 0309 << "smm" 0310 << "HI" 0311 << "rB" 0312 << "Mas"; 0313 0314 if (galaxyTypes.contains(s, cs)) 0315 return SkyObject::GALAXY; 0316 0317 if (galaxyGroupTypes.contains(s, cs)) 0318 return SkyObject::GALAXY_CLUSTER; 0319 0320 if (openClusterTypes.contains(s, cs)) 0321 return SkyObject::OPEN_CLUSTER; // FIXME: NED doesn't distinguish between globular clusters and open clusters!! 0322 0323 auto check = [typeString, cs](const QString &type) { return (!QString::compare(typeString, type, cs)); }; 0324 0325 if (check("GlC") || check("GlCl")) 0326 return SkyObject::GLOBULAR_CLUSTER; 0327 if (check("Neb") || check("HII") || check("HH")) // FIXME: The last one is Herbig-Haro object 0328 return SkyObject::GASEOUS_NEBULA; 0329 if (check("SNR")) // FIXME: Simbad returns "ISM" for Veil Nebula (Interstellar Medium??) 0330 return SkyObject::SUPERNOVA_REMNANT; 0331 if (check("PN") || check("PNeb") || check("PNe") || check("pA*")) // FIXME: The latter is actually Proto PN 0332 return SkyObject::PLANETARY_NEBULA; 0333 if (typeString == "*") 0334 return SkyObject::CATALOG_STAR; 0335 if (check("QSO")) 0336 return SkyObject::QUASAR; 0337 if (check("DN") || check("DNe") || check("DNeb") || check("glb")) // The latter is Bok globule 0338 return SkyObject::DARK_NEBULA; 0339 if (radioSourceTypes.contains(s, cs)) 0340 return SkyObject::RADIO_SOURCE; 0341 if (typeString == "**") 0342 return SkyObject::MULT_STAR; 0343 if (typeString.contains('*') && !check("C?*")) 0344 return SkyObject::CATALOG_STAR; 0345 0346 return SkyObject::TYPE_UNKNOWN; 0347 // FIXME: complete this method 0348 }