Kstars

nameresolver.cpp
1 /*
2  SPDX-FileCopyrightText: 2014 Akarsh Simha <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 /* Project Includes */
8 #include "nameresolver.h"
9 #include "catalogobject.h"
10 
11 /* KDE Includes */
12 #ifndef KSTARS_LITE
13 #include <kio/filecopyjob.h>
14 #else
15 #include "kstarslite.h"
16 #endif
17 
18 /* Qt Includes */
19 #include <QUrl>
20 #include <QTemporaryFile>
21 #include <QString>
22 #include <QXmlStreamReader>
23 #include <QNetworkRequest>
24 #include <QNetworkReply>
25 #include <QNetworkAccessManager>
26 #include <QEventLoop>
27 
28 #include <kstars_debug.h>
29 
30 std::pair<bool, CatalogObject> NameResolver::resolveName(const QString &name)
31 {
32  const auto &found_sesame{ NameResolverInternals::sesameResolver(name) };
33  if (!found_sesame.first)
34  {
35  QString msg =
36  xi18n("Error: sesameResolver failed. Could not resolve name on CDS Sesame.");
37  qCDebug(KSTARS) << msg;
38 
39 #ifdef KSTARS_LITE
41 #endif
42  }
43  // More to be done here if the resolved name is SIMBAD
44  return found_sesame;
45 }
46 
47 std::pair<bool, CatalogObject>
48 NameResolver::NameResolverInternals::sesameResolver(const QString &name)
49 {
50  QUrl resolverUrl = QUrl(
51  QString("http://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/-oxpFI/SNV?%1").arg(name));
52 
53  QString msg = xi18n("Attempting to resolve object %1 using CDS Sesame.", name);
54  qCDebug(KSTARS) << msg;
55 
56 #ifdef KSTARS_LITE
58 #endif
59 
60  QNetworkAccessManager manager;
61  QNetworkReply *response = manager.get(QNetworkRequest(resolverUrl));
62  Q_ASSERT(response);
63 
64  // Wait synchronously
65  QEventLoop event;
66  QObject::connect(response, SIGNAL(finished()), &event, SLOT(quit()));
67  event.exec();
68 
69  if (response->error() != QNetworkReply::NoError)
70  {
71  msg = xi18n("Error trying to get XML response from CDS Sesame server: %1",
72  response->errorString());
73  qWarning() << msg;
74 
75 #ifdef KSTARS_LITE
77 #endif
78  return { false, {} };
79  }
80 
81  QXmlStreamReader xml(response->readAll());
82  response->deleteLater();
83  if (xml.atEnd())
84  {
85  // file is empty
86  msg = xi18n("Empty result instead of expected XML from CDS Sesame. Maybe bad "
87  "Internet connection?");
88  qCDebug(KSTARS) << msg;
89 
90 #ifdef KSTARS_LITE
92 #endif
93  return { false, {} };
94  }
95 
96  CatalogObject data{
97  CatalogObject::oid{}, SkyObject::STAR, dms{ 0 }, dms{ 0 }, 0, name, name
98  };
99 
100  bool found{ false };
101  while (!xml.atEnd() && !xml.hasError())
102  {
103  QXmlStreamReader::TokenType token = xml.readNext();
104 
105  if (xml.isStartDocument())
106  continue;
107 
108  if (token == QXmlStreamReader::StartElement)
109  {
110  qCDebug(KSTARS) << "Parsing token with name " << xml.name();
111 
112  if (xml.name() == "Resolver")
113  {
114  found = true;
115  // This is the section we want
116  char resolver = 0;
117  QXmlStreamAttributes attributes = xml.attributes();
118  if (attributes.hasAttribute("name"))
119  resolver =
120  attributes.value("name")
121  .at(0)
122  .toLatin1(); // Expected to be S (Simbad), V (VizieR), or N (NED)
123  else
124  {
125  resolver = 0; // NUL character for unknown resolver
126  qWarning() << "Warning: Unknown resolver "
127  << attributes.value("name ")
128  << " while reading output from CDS Sesame";
129  }
130 
131  qCDebug(KSTARS)
132  << "Resolved by " << resolver << attributes.value("name") << "!";
133 
134  // Start reading the data to pick out the relevant ones
135  while (xml.readNextStartElement())
136  {
137  if (xml.name() == "otype")
138  {
139  const QString typeString = xml.readElementText();
140  data.setType(interpretObjectType(typeString));
141  }
142  else if (xml.name() == "jradeg")
143  {
144  data.setRA0(dms{ xml.readElementText().toDouble() });
145  }
146  else if (xml.name() == "jdedeg")
147  {
148  data.setDec0(dms{ xml.readElementText().toDouble() });
149  }
150  else if (xml.name() == "mag")
151  {
152  attributes = xml.attributes();
153  char band;
154  if (attributes.hasAttribute("band"))
155  {
156  band = attributes.value("band").at(0).toLatin1();
157  }
158  else
159  {
160  qWarning() << "Warning: Magnitude of unknown band found "
161  "while reading output from CDS Sesame";
162  band = 0;
163  }
164 
165  float mag = NaN::f;
166  xml.readNext();
167  if (xml.isCharacters())
168  {
169  qCDebug(KSTARS) << "characters: " << xml.tokenString();
170  mag = xml.tokenString().toFloat();
171  }
172  else if (xml.isStartElement())
173  {
174  while (xml.name() != "v")
175  {
176  qCDebug(KSTARS) << "element: " << xml.name();
177  xml.readNextStartElement();
178  }
179  mag = xml.readElementText().toFloat();
180  qCDebug(KSTARS)
181  << "Got " << xml.tokenString() << " mag = " << mag;
182  while (!xml.atEnd() && xml.readNext() && xml.name() != "mag")
183  ; // finish reading the <mag> tag all the way to </mag>
184  }
185  else
186  qWarning()
187  << "Failed to parse Xml token in magnitude element: "
188  << xml.tokenString();
189 
190  if (band == 'V')
191  {
192  data.setMag(mag);
193  }
194  else if (band == 'B')
195  {
196  data.setFlux(mag); // FIXME: This is bad
197  if (std::isnan(data.mag()))
198  data.setMag(mag); // FIXME: This is bad too
199  }
200  // Don't know what to do with other magnitudes, until we have a magnitude hash
201  }
202  else if (xml.name() == "oname") // Primary identifier
203  {
204  QString contents = xml.readElementText();
205  data.setCatalogIdentifier(contents);
206  }
207  else
208  xml.skipCurrentElement();
209  // TODO: Parse aliases for common names
210  }
211  break;
212  }
213  else
214  continue;
215  }
216  }
217  if (xml.hasError())
218  {
219  msg = xi18n("Error parsing XML from CDS Sesame: %1 on line %2 @ col = %3",
220  xml.errorString(), xml.lineNumber(), xml.columnNumber());
221  qCDebug(KSTARS) << msg;
222 
223 #ifdef KSTARS_LITE
225 #endif
226  return { false, {} };
227  }
228 
229  if (!found)
230  return { false, {} };
231 
232  msg = xi18n("Resolved %1 successfully.", name);
233  qCDebug(KSTARS) << msg;
234 
235 #ifdef KSTARS_LITE
237 #endif
238  qCDebug(KSTARS) << "Object type: " << SkyObject::typeName(data.type())
239  << "; Coordinates: " << data.ra0().Degrees() << ";"
240  << data.dec().Degrees();
241  return { true, data };
242 }
243 
244 // bool NameResolver::NameResolverInternals::getDataFromSimbad( class CatalogObject &data ) {
245 // // TODO: Implement
246 // // 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 );
247 // }
248 
249 SkyObject::TYPE NameResolver::NameResolverInternals::interpretObjectType(const QString &typeString, bool caseSensitive)
250 {
251  // FIXME: Due to the quirks of Sesame (SIMBAD vs NED etc), it
252  // might be very difficult to discern the type in all cases. The
253  // best way TODO things might be to first run the query with NED,
254  // and if it is extragalactic, then trust NED and
255  // accept. Otherwise, or if NED did not return a result, re-run
256  // the query on SIMBAD and VizieR and use that result, if any.
257 
258  // See https://simbad.u-strasbg.fr/simbad/sim-display?data=otypes for Object Classification in SIMBAD
259 
260  // Highest likelihood is a galaxy of some form, so we process that first
261  const QString &s = typeString; // To make the code compact
263 
264  QStringList galaxyTypes;
265  QStringList galaxyGroupTypes;
266  QStringList openClusterTypes;
267  QStringList radioSourceTypes;
268  galaxyTypes << "G"
269  << "LIN"
270  << "AGN"
271  << "GiG"
272  << "GiC"
273  << "H2G"
274  << "BiC"
275  << "GiP"
276  << "HzG"
277  << "rG"
278  << "AG?"
279  << "EmG"
280  << "LSB"
281  << "SBG"
282  << "bCG"
283  << "SyG"
284  << "Sy1"
285  << "Sy2"
286  << "Gxy";
287 
288  galaxyGroupTypes << "GClstr"
289  << "GGroup"
290  << "GPair"
291  << "ClG"
292  << "CGG"
293  << "PaG"
294  << "IG"
295  << "GrG"
296  << "SCG"; // NOTE (FIXME?): Compact groups and pairs of galaxies ar treated like galaxy clusters
297 
298  openClusterTypes << "*Cl"
299  << "Cl*"
300  << "OpC"
301  << "As*"
302  << "St*"
303  << "OCl";
304 
305  radioSourceTypes << "Rad"
306  << "mR"
307  << "cm"
308  << "mm"
309  << "smm"
310  << "HI"
311  << "rB"
312  << "Mas";
313 
314  if (galaxyTypes.contains(s, cs))
315  return SkyObject::GALAXY;
316 
317  if (galaxyGroupTypes.contains(s, cs))
318  return SkyObject::GALAXY_CLUSTER;
319 
320  if (openClusterTypes.contains(s, cs))
321  return SkyObject::OPEN_CLUSTER; // FIXME: NED doesn't distinguish between globular clusters and open clusters!!
322 
323  auto check = [typeString, cs](const QString &type) { return (!QString::compare(typeString, type, cs)); };
324 
325  if (check("GlC") || check("GlCl"))
326  return SkyObject::GLOBULAR_CLUSTER;
327  if (check("Neb") || check("HII") || check("HH")) // FIXME: The last one is Herbig-Haro object
328  return SkyObject::GASEOUS_NEBULA;
329  if (check("SNR")) // FIXME: Simbad returns "ISM" for Veil Nebula (Interstellar Medium??)
330  return SkyObject::SUPERNOVA_REMNANT;
331  if (check("PN") || check("PNeb") || check("PNe") || check("pA*")) // FIXME: The latter is actually Proto PN
332  return SkyObject::PLANETARY_NEBULA;
333  if (typeString == "*")
334  return SkyObject::CATALOG_STAR;
335  if (check("QSO"))
336  return SkyObject::QUASAR;
337  if (check("DN") || check("DNe") || check("DNeb") || check("glb")) // The latter is Bok globule
338  return SkyObject::DARK_NEBULA;
339  if (radioSourceTypes.contains(s, cs))
340  return SkyObject::RADIO_SOURCE;
341  if (typeString == "**")
342  return SkyObject::MULT_STAR;
343  if (typeString.contains('*') && !check("C?*"))
344  return SkyObject::CATALOG_STAR;
345 
346  return SkyObject::TYPE_UNKNOWN;
347  // FIXME: complete this method
348 }
QString errorString() const const
CaseSensitivity
QStringRef value(const QString &namespaceUri, const QString &name) const const
Type type(const QSqlDatabase &db)
QNetworkReply::NetworkError error() const const
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
static KStarsLite * Instance()
Definition: kstarslite.h:77
QString xi18n(const char *text, const TYPE &arg...)
void notificationMessage(QString msg)
Once this signal is emitted, notification with text msg will appear on the screen.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QNetworkReply * get(const QNetworkRequest &request)
bool hasAttribute(const QString &qualifiedName) const const
void deleteLater()
An angle, stored as degrees, but expressible in many ways.
Definition: dms.h:37
std::pair< bool, CatalogObject > resolveName(const QString &name)
Resolve the name of the given DSO and extract data from various sources.
QString name(StandardShortcut id)
const QChar at(int position) const const
QByteArray readAll()
int compare(const QString &other, Qt::CaseSensitivity cs) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
char toLatin1() const const
A simple container object to hold the minimum information for a Deeb Sky Object to be drawn on the sk...
Definition: catalogobject.h:40
QString typeName() const
Definition: skyobject.cpp:389
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Aug 13 2022 04:01:56 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.