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