1 /*
2 * Copyright (C) 2008 Fabien Chereau
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
17 */
18
19 #include "SimbadSearcher.hpp"
20
21 #include "StelUtils.hpp"
22 #include "StelTranslator.hpp"
23 #include <QNetworkReply>
24 #include <QNetworkAccessManager>
25 #include <QDebug>
26 #include <QTimer>
27
SimbadLookupReply(const QString & aurl,QNetworkAccessManager * anetMgr,int delayMs)28 SimbadLookupReply::SimbadLookupReply(const QString& aurl, QNetworkAccessManager* anetMgr, int delayMs)
29 : url(aurl)
30 , reply(Q_NULLPTR)
31 , netMgr(anetMgr)
32 , currentStatus(SimbadLookupQuerying)
33 {
34 if(delayMs <= 0)
35 delayTimerCompleted();
36 else
37 {
38 // First wait before starting query. This avoids sending a query for each autocompletion letter.
39 QTimer::singleShot(delayMs, this, SLOT(delayTimerCompleted()));
40 }
41 }
42
~SimbadLookupReply()43 SimbadLookupReply::~SimbadLookupReply()
44 {
45 if (reply)
46 {
47 disconnect(reply, SIGNAL(finished()), this, SLOT(httpQueryFinished()));
48 reply->abort();
49 //do not use delete here
50 reply->deleteLater();
51 reply = Q_NULLPTR;
52 }
53 }
54
55 //This is provided for the correct deletion of the reply in the RemoteControl plugin
deleteNetworkReply()56 void SimbadLookupReply::deleteNetworkReply()
57 {
58 if(reply)
59 {
60 disconnect(reply, SIGNAL(finished()), this, SLOT(httpQueryFinished()));
61 reply->abort();
62 delete reply;
63 reply = Q_NULLPTR;
64 }
65 }
66
delayTimerCompleted()67 void SimbadLookupReply::delayTimerCompleted()
68 {
69 reply = netMgr->get(QNetworkRequest(url));
70 connect(reply, SIGNAL(finished()), this, SLOT(httpQueryFinished()));
71 }
72
httpQueryFinished()73 void SimbadLookupReply::httpQueryFinished()
74 {
75 if (reply->error()!=QNetworkReply::NoError || reply->bytesAvailable()==0)
76 {
77 currentStatus = SimbadLookupErrorOccured;
78 errorString = QString("%1: %2").arg(q_("Network error")).arg(reply->errorString());
79 emit statusChanged();
80 return;
81 }
82
83 // No error, try to parse the Simbad result
84 QByteArray line;
85 bool found = false;
86 //qDebug() << reply->readAll();
87 bool cooAnswer=false; // if we asked for an object at coordinates
88
89 // We have 2 kinds of answers...
90
91 if (!reply->isSequential())
92 reply->reset();
93
94 while (!reply->atEnd())
95 {
96 line = reply->readLine();
97 if (line.contains("query coo "))
98 cooAnswer=true;
99 if (line.startsWith("::data"))
100 {
101 found = true;
102 line = reply->readLine(); // Discard first header line
103 break;
104 }
105 }
106 if (found)
107 {
108 if (cooAnswer)
109 {
110 cooResult.clear();
111 while (!reply->atEnd())
112 cooResult.append(reply->readLine());
113 //qDebug() << "Cleaned result: " << cooResult;
114 currentStatus = SimbadCoordinateLookupFinished;
115 }
116 else
117 {
118 line = reply->readLine();
119 line.chop(1); // Remove a line break at the end
120 while (!line.isEmpty())
121 {
122 if (line=="No Coord.")
123 {
124 reply->readLine();
125 line = reply->readLine();
126 line.chop(1); // Remove a line break at the end
127 continue;
128 }
129 QList<QByteArray> l = line.split(' ');
130 if (l.size()!=2)
131 {
132 currentStatus = SimbadLookupErrorOccured;
133 errorString = q_("Error parsing position");
134 emit statusChanged();
135 return;
136 }
137 else
138 {
139 bool ok1, ok2;
140 const double ra = l[0].toDouble(&ok1)*M_PI/180.;
141 const double dec = l[1].toDouble(&ok2)*M_PI/180.;
142 if (ok1==false || ok2==false)
143 {
144 currentStatus = SimbadLookupErrorOccured;
145 errorString = q_("Error parsing position");
146 emit statusChanged();
147 return;
148 }
149 Vec3d v;
150 StelUtils::spheToRect(ra, dec, v);
151 line = reply->readLine();
152 line.chop(1); // Remove a line break at the end
153 line.replace("NAME " ,"");
154 resultPositions[line.simplified()]=v; // Remove an extra spaces
155 }
156 line = reply->readLine();
157 line.chop(1); // Remove a line break at the end
158 }
159 currentStatus = SimbadLookupFinished;
160 }
161 emit statusChanged();
162 }
163 }
164
165 // Get a I18n string describing the current status.
getCurrentStatusString() const166 QString SimbadLookupReply::getCurrentStatusString() const
167 {
168 switch (currentStatus)
169 {
170 case SimbadLookupQuerying:
171 return q_("Querying");
172 case SimbadLookupErrorOccured:
173 return q_("Error");
174 case SimbadLookupFinished:
175 return resultPositions.isEmpty() ? q_("Not found") : q_("Found");
176 case SimbadCoordinateLookupFinished:
177 return cooResult.isEmpty() ? q_("Not found") : q_("Found");
178 }
179 return QString();
180 }
181
SimbadSearcher(QObject * parent)182 SimbadSearcher::SimbadSearcher(QObject* parent) : QObject(parent)
183 {
184 networkMgr = new QNetworkAccessManager(this);
185 }
186
187 // Lookup in Simbad for the passed object name.
lookup(const QString & serverUrl,const QString & objectName,int maxNbResult,int delayMs)188 SimbadLookupReply* SimbadSearcher::lookup(const QString& serverUrl, const QString& objectName, int maxNbResult, int delayMs)
189 {
190 // Create the Simbad query
191 QString url(serverUrl);
192 QString query = "format object \"%COO(d;A D)\\n%IDLIST(1)\"\n";
193 query += QString("set epoch J2000\nset limit %1\n query id ").arg(maxNbResult);
194 query += objectName;
195 QByteArray ba = QUrl::toPercentEncoding(query, "", "");
196
197 url += "simbad/sim-script?script=";
198 url += ba.constData();
199 return new SimbadLookupReply(url, networkMgr, delayMs);
200 }
201
202 // Lookup in Simbad for the passed object coordinates.
lookupCoords(const QString & serverUrl,const Vec3d coordsJ2000,int maxNbResult,int delayMs,int radius,bool IDs,bool types,bool spectrum,bool morpho,bool dim)203 SimbadLookupReply* SimbadSearcher::lookupCoords(const QString& serverUrl, const Vec3d coordsJ2000, int maxNbResult, int delayMs,
204 int radius, bool IDs, bool types, bool spectrum, bool morpho, bool dim)
205 {
206 double ra, de;
207 StelUtils::rectToSphe(&ra, &de, coordsJ2000);
208 QString rastring = StelUtils::radToHmsStr(ra);
209 //rastring.replace('h', ' ');
210 //rastring.replace('m', ' ');
211 //rastring.replace('s', ' ');
212 QString destring = StelUtils::radToDmsStr(de, false, true);
213 //destring.replace('d', ' ');
214 destring.replace('\'', 'm');
215 destring.replace('\"', 's');
216
217 // Create the Simbad query
218 QString url(serverUrl);
219 // The query format could be enriched! http://simbad.u-strasbg.fr/simbad/sim-help?Page=sim-fscript#Formats
220 // NOTE: Newlines and other specials must be coded with double-backslash!
221 QString query = "format object \""
222 "At coords (J2000.0) %-20COO(s:;A D):\\n"
223 "Object: %IDLIST(1) (%OTYPE(V))\\n"
224 "Distance from query: %5.2DIST arcsec\\n";
225 if (IDs) query += "Other Identifiers:\\n%IDLIST[ - %-50* \\n]";
226 if (types) query += "Object Type(s): %OTYPELIST(V)\\n";
227 if (spectrum) query += "Spectral Type: %SP(S)\\n";
228 if (morpho) query += "Morph. Type: %MT(M)\\n";
229 if (dim) query += "Dimensions: %3.1DIM(X)'x%3.1DIM(Y)' at %3.0DIM(A) degrees\\n";
230 query += "\\n\\n\"\n"; // add 2 newlines at end of each record
231 query += QString("set epoch J2000\nset limit %1\n query coo ").arg(maxNbResult);
232 query += rastring + " " + destring;
233 query += QString(" radius=%1s ").arg(radius);
234 QByteArray ba = QUrl::toPercentEncoding(query, "", "");
235
236 url += "simbad/sim-script?script=";
237 url += ba.constData();
238 return new SimbadLookupReply(url, networkMgr, delayMs);
239 }
240