1 /*
2  * Stellarium
3  * Copyright (C) 2006 Johannes Gajdosik
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
18  */
19 
20 
21 #include "StelObject.hpp"
22 #include "StelObjectMgr.hpp"
23 #include "StelApp.hpp"
24 #include "StelCore.hpp"
25 #include "StelProjector.hpp"
26 #include "StelUtils.hpp"
27 #include "StelTranslator.hpp"
28 #include "StelSkyDrawer.hpp"
29 #include "RefractionExtinction.hpp"
30 #include "StelLocation.hpp"
31 #include "SolarSystem.hpp"
32 #include "StelModuleMgr.hpp"
33 #include "LandscapeMgr.hpp"
34 #include "planetsephems/sidereal_time.h"
35 #include "planetsephems/precession.h"
36 
37 #include <QRegularExpression>
38 #include <QDebug>
39 #include <QSettings>
40 
41 int StelObject::stelObjectPMetaTypeID = qRegisterMetaType<StelObjectP>();
42 
getEquinoxEquatorialPos(const StelCore * core) const43 Vec3d StelObject::getEquinoxEquatorialPos(const StelCore* core) const
44 {
45 	return core->j2000ToEquinoxEqu(getJ2000EquatorialPos(core), StelCore::RefractionOff);
46 }
47 
getEquinoxEquatorialPosApparent(const StelCore * core) const48 Vec3d StelObject::getEquinoxEquatorialPosApparent(const StelCore* core) const
49 {
50 	return core->j2000ToEquinoxEqu(getJ2000EquatorialPos(core), StelCore::RefractionOn);
51 }
52 
getEquinoxEquatorialPosAuto(const StelCore * core) const53 Vec3d StelObject::getEquinoxEquatorialPosAuto(const StelCore* core) const
54 {
55 	return core->j2000ToEquinoxEqu(getJ2000EquatorialPos(core), StelCore::RefractionAuto);
56 }
57 
58 
59 // Get observer local sidereal coordinate
getSiderealPosGeometric(const StelCore * core) const60 Vec3d StelObject::getSiderealPosGeometric(const StelCore* core) const
61 {
62 	return Mat4d::zrotation(-core->getLocalSiderealTime())* getEquinoxEquatorialPos(core);
63 }
64 
65 // Get observer local sidereal coordinates, deflected by refraction
getSiderealPosApparent(const StelCore * core) const66 Vec3d StelObject::getSiderealPosApparent(const StelCore* core) const
67 {
68 	Vec3d v=getAltAzPosApparent(core); // These already come with refraction!
69 	v = core->altAzToEquinoxEqu(v, StelCore::RefractionOff);
70 	return Mat4d::zrotation(-core->getLocalSiderealTime())*v;
71 }
72 
getAltAzPosGeometric(const StelCore * core) const73 Vec3d StelObject::getAltAzPosGeometric(const StelCore* core) const
74 {
75 	return core->j2000ToAltAz(getJ2000EquatorialPos(core), StelCore::RefractionOff);
76 }
77 
78 // Get observer-centered alt/az position
getAltAzPosApparent(const StelCore * core) const79 Vec3d StelObject::getAltAzPosApparent(const StelCore* core) const
80 {
81 	return core->j2000ToAltAz(getJ2000EquatorialPos(core), StelCore::RefractionOn);
82 }
83 
84 // Get observer-centered alt/az position
getAltAzPosAuto(const StelCore * core) const85 Vec3d StelObject::getAltAzPosAuto(const StelCore* core) const
86 {
87 	return core->j2000ToAltAz(getJ2000EquatorialPos(core), StelCore::RefractionAuto);
88 }
89 
90 // Get observer-centered galactic position
getGalacticPos(const StelCore * core) const91 Vec3d StelObject::getGalacticPos(const StelCore *core) const
92 {
93 	return core->j2000ToGalactic(getJ2000EquatorialPos(core));
94 }
95 
96 // Get observer-centered supergalactic position
getSupergalacticPos(const StelCore * core) const97 Vec3d StelObject::getSupergalacticPos(const StelCore *core) const
98 {
99 	return core->j2000ToSupergalactic(getJ2000EquatorialPos(core));
100 }
101 
102 // Get parallactic angle, which is the deviation between zenith angle and north angle.
103 // Meeus, Astronomical Algorithms, 2nd ed. (1998), p.98.
getParallacticAngle(const StelCore * core) const104 float StelObject::getParallacticAngle(const StelCore* core) const
105 {
106 	const double phi=static_cast<double>(core->getCurrentLocation().latitude)*M_PI/180.0;
107 	const Vec3d siderealPos=getSiderealPosApparent(core);
108 	double delta, ha;
109 	StelUtils::rectToSphe(&ha, &delta, siderealPos);
110 	ha *= -1.0; // We must invert the orientation sense in case of sidereal positions!
111 
112 	// A rare condition! Object exactly in zenith, avoid undefined result.
113 	if ((ha==0.0) && ((delta-phi)==0.0))
114 		return 0.0f;
115 	else
116 		return static_cast<float>(atan2(sin(ha), tan(phi)*cos(delta)-sin(delta)*cos(ha)));
117 }
118 
119 // Checking position an object above mathematical horizon for current location
isAboveHorizon(const StelCore * core) const120 bool StelObject::isAboveHorizon(const StelCore *core) const
121 {
122 	float az, alt;
123 	StelUtils::rectToSphe(&az, &alt, getAltAzPosAuto(core));
124 	return (alt >= 0.f);
125 }
126 
127 // Checking position an object above real horizon for current location
isAboveRealHorizon(const StelCore * core) const128 bool StelObject::isAboveRealHorizon(const StelCore *core) const
129 {
130 	bool r = true;
131 	LandscapeMgr* lmgr = GETSTELMODULE(LandscapeMgr);
132 	if (lmgr->getFlagLandscape())
133 	{
134 		if (lmgr->getLandscapeOpacity(getAltAzPosAuto(core))>0.85f) // landscape displayed
135 			r = false;
136 	}
137 	else
138 		r = isAboveHorizon(core); // check object is below mathematical horizon
139 
140 	return r;
141 }
142 
getVMagnitude(const StelCore * core) const143 float StelObject::getVMagnitude(const StelCore* core) const
144 {
145 	Q_UNUSED(core)
146 	return 99;
147 }
148 
getRTSTime(const StelCore * core,const double altitude) const149 Vec4d StelObject::getRTSTime(const StelCore *core, const double altitude) const
150 {
151 	const StelLocation loc=core->getCurrentLocation();
152 	if (loc.name.contains("->")) // a spaceship
153 		return Vec4d(0., 0., 0., -1000.);
154 
155 	//StelObjectMgr* omgr=GETSTELMODULE(StelObjectMgr);
156 	double ho = 0.;
157 	if (core->getSkyDrawer()->getFlagHasAtmosphere())
158 	{
159 		// canonical" refraction at horizon is -34'. Replace by pressure-dependent value here!
160 		// This fixes 0-degree refraction. Not sure if we use refracted position throughout? Canonical ephemerides have -6/-12/-18 without refraction.
161 		Refraction refraction=core->getSkyDrawer()->getRefraction();
162 		Vec3d zeroAlt(1.0,0.0,0.0);
163 		refraction.backward(zeroAlt);
164 		ho += asin(zeroAlt[2]);
165 	}
166 	if (altitude != 0.)
167 		ho = altitude*M_PI_180; // Not sure if we use refraction for off-zero settings?
168 	const double phi = static_cast<double>(loc.latitude) * M_PI_180;
169 	const double L = static_cast<double>(loc.longitude) * M_PI_180; // OUR longitude. Meeus has it reversed
170 	PlanetP obsPlanet = core->getCurrentPlanet();
171 	const double rotRate = obsPlanet->getSiderealDay();
172 	const double currentJD=core->getJD();
173 	const double currentJDE=core->getJDE();
174 
175 	// And convert to equatorial coordinates of date. We can also use this day's current aberration, given the other uncertainties/omissions.
176 	const Vec3d eq_2=getEquinoxEquatorialPos(core);
177 	double ra2, de2;
178 	StelUtils::rectToSphe(&ra2, &de2, eq_2);
179 	// Around ra~12 there may be a jump between 12h and -12h which could crash interpolation. We better make sure to have either negative RA or RA>24 in this case.
180 	if (cos(ra2)<0.)
181 	{
182 		ra2=StelUtils::fmodpos(ra2, 2*M_PI);
183 	}
184 
185 	// 3. Approximate times:
186 	// we use Sidereal Time of Place!
187 	const double Theta2=obsPlanet->getSiderealTime(currentJD, currentJDE) * (M_PI/180.) + L;  // [radians]
188 	double cosH0=(sin(ho)-sin(phi)*sin(de2))/(cos(phi)*cos(de2));
189 
190 	//omgr->removeExtraInfoStrings(StelObject::DebugAid);
191 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&alpha;<sub>2</sub>: %1=%2 &delta;<sub>2</sub>: %3<br/>").arg(QString::number(ra2, 'f', 4)).arg(StelUtils::radToHmsStr(ra2)).arg(StelUtils::radToDmsStr(de2)));
192 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("h<sub>0</sub>= %1<br/>").arg(StelUtils::radToDmsStr(ho)));
193 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("JD<sub>2</sub>= %1<br/>").arg(QString::number(currentJD, 'f', 5)));
194 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&Theta;<sub>2</sub>= %1<br/>").arg(StelUtils::radToHmsStr(Theta2)));
195 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("cos H<sub>0</sub>= %1<br/>").arg(QString::number(cosH0, 'f', 4)));
196 
197 
198 	double h2=StelUtils::fmodpos(Theta2-ra2, 2.*M_PI); if (h2>M_PI) h2-=2.*M_PI; // Hour angle at currentJD. This should be [-pi, pi]
199 	// Find approximation of transit time
200 	//double JDt=currentJD-h2/(M_PI*2.)*rotRate;
201 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("h<sub>2</sub>= %1<br/>").arg(QString::number(h2, 'f', 4)));
202 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("JD<sub>t</sub>= %1<br/>").arg(QString::number(JDt, 'f', 4)));
203 
204 	// In terms of chapter 15, where m0, m1 and m2 are fractions of day within the current day, we use mr, mt, ms as fractions of day from currentJD, and they lie within [-1...+1].
205 
206 	double mr, ms, flag=0.;
207 	double mt=-h2*(0.5*rotRate/M_PI);
208 
209 	// circumpolar: set rise and set times to lower culmination, i.e. 1/2 rotation from transit
210 	if (fabs(cosH0)>1.)
211 	{
212 		flag = (cosH0<-1.) ? 100 : -100; // circumpolar / never rises
213 		mr   = (cosH0<-1.) ? mt-0.5*rotRate : mt;
214 		ms   = (cosH0<-1.) ? mt+0.5*rotRate : mt;
215 	}
216 	else
217 	{
218 		const double H0 = acos(cosH0);
219 		//omgr->addToExtraInfoString(StelObject::DebugAid, QString("H<sub>0</sub>= %1<br/>").arg(QString::number(H0*M_180_PI, 'f', 6)));
220 
221 		mr = mt - H0*rotRate/(2.*M_PI);
222 		ms = mt + H0*rotRate/(2.*M_PI);
223 	}
224 
225 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("m<sub>t</sub>= %1<br/>").arg(QString::number(mt, 'f', 6)));
226 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("m<sub>r</sub>= %1<br/>").arg(QString::number(mr, 'f', 6)));
227 	//omgr->addToExtraInfoString(StelObject::DebugAid, QString("m<sub>s</sub>= %1<br/>").arg(QString::number(ms, 'f', 6)));
228 
229 	// Not quite done! Do the final tweaks...
230 	if (fabs(cosH0)<1.)
231 	{
232 		// RISE
233 		int iterations=0; // add this to limit the loops, just in case.
234 		double Delta_mr=1.;
235 		while (Delta_mr > 1./8640.) // Do that until accurate to 10 seconds
236 		{
237 			const double theta_mr=obsPlanet->getSiderealTime(currentJD+mr, currentJDE+mr) * (M_PI/180.) + L;  // [radians]
238 			double hr=StelUtils::fmodpos(theta_mr-ra2, 2.*M_PI); if (hr>M_PI) hr-=2.*M_PI; // Hour angle of the rising RA at currentJD. This should be [-pi, pi]
239 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("h<sub>r</sub>': %1 = %2<br/>").arg(QString::number(hr, 'f', 6)).arg(StelUtils::radToHmsStr(hr, true)));
240 
241 			double ar=asin(sin(phi)*sin(de2)+cos(phi)*cos(de2)*cos(hr)); // altitude at this hour angle
242 
243 			Delta_mr= (ar-ho)/(cos(de2)*cos(phi)*sin(hr)) / (M_PI*2.);
244 			Delta_mr=StelUtils::fmodpos(Delta_mr+0.5, 1.0)-0.5; // ensure this is a small correction
245 			mr+=Delta_mr;
246 
247 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("alt<sub>r</sub>': %1 = %2<br/>").arg(QString::number(ar, 'f', 6)).arg(StelUtils::radToDmsStr(ar)));
248 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&Delta;<sub>mr</sub>'= %1<br/>").arg(QString::number(Delta_mr, 'f', 6)));
249 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("m<sub>r</sub>' = %1<br/>").arg(QString::number(mr, 'f', 6)));
250 
251 			if (++iterations >= 5)
252 				break;
253 		}
254 		// SET
255 		iterations=0; // add this to limit the loops, just in case.
256 		double Delta_ms=1.;
257 		while (Delta_ms > 1./8640.) // Do that until accurate to 10 seconds
258 		{
259 			const double theta_ms=obsPlanet->getSiderealTime(currentJD+ms, currentJDE+ms) * (M_PI/180.) + L;  // [radians]
260 			double hs=StelUtils::fmodpos(theta_ms-ra2, 2.*M_PI); if (hs>M_PI) hs-=2.*M_PI; // Hour angle of the setting RA at currentJD. This should be [-pi, pi]
261 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("h<sub>s</sub>': %1 = %2<br/>").arg(QString::number(hs, 'f', 6)).arg(StelUtils::radToHmsStr(hs, true)));
262 
263 			double as=asin(sin(phi)*sin(de2)+cos(phi)*cos(de2)*cos(hs)); // altitude at this hour angle
264 
265 			Delta_ms= (as-ho)/(cos(de2)*cos(phi)*sin(hs)) / (M_PI*2.);
266 			Delta_ms=StelUtils::fmodpos(Delta_ms+0.5, 1.0)-0.5; // ensure this is a small correction
267 			ms+=Delta_ms;
268 
269 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("alt<sub>s</sub>': %1 = %2<br/>").arg(QString::number(as, 'f', 6)).arg(StelUtils::radToDmsStr(as)));
270 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("&Delta;<sub>ms</sub>'= %1<br/>").arg(QString::number(Delta_ms, 'f', 6)));
271 			//omgr->addToExtraInfoString(StelObject::DebugAid, QString("m<sub>s</sub>' = %1<br/>").arg(QString::number(ms, 'f', 6)));
272 
273 			if (++iterations >= 5)
274 				break;
275 		}
276 	}
277 	return Vec4d(currentJD+mr, currentJD+mt, currentJD+ms, flag);
278 }
279 
getSelectPriority(const StelCore * core) const280 float StelObject::getSelectPriority(const StelCore* core) const
281 {
282 	return qMin(getVMagnitudeWithExtinction(core), 15.0f);
283 }
284 
getVMagnitudeWithExtinction(const StelCore * core) const285 float StelObject::getVMagnitudeWithExtinction(const StelCore* core) const
286 {
287 	Vec3d altAzPos = getAltAzPosGeometric(core);
288 	altAzPos.normalize();
289 	float vMag = getVMagnitude(core);
290 	// without the test, planets flicker stupidly in fullsky atmosphere-less view.
291 	if (core->getSkyDrawer()->getFlagHasAtmosphere())
292 		core->getSkyDrawer()->getExtinction().forward(altAzPos, &vMag);
293 	return vMag;
294 }
295 
getAirmass(const StelCore * core) const296 float StelObject::getAirmass(const StelCore *core) const
297 {
298 	double az_app, alt_app;
299 	StelUtils::rectToSphe(&az_app, &alt_app, getAltAzPosApparent(core));
300 	Q_UNUSED(az_app)
301 	if (core->getSkyDrawer()->getFlagHasAtmosphere() && (alt_app>-2.0*M_PI_180)) // Don't compute extinction much below horizon where model is meaningless.
302 	{
303 		const Extinction &extinction=core->getSkyDrawer()->getExtinction();
304 		return extinction.airmass(static_cast<float>(std::cos(M_PI_2-alt_app)), true);
305 	}
306 	else
307 		return -1.f;
308 }
309 
310 // Format the magnitude info string for the object
getMagnitudeInfoString(const StelCore * core,const InfoStringGroup & flags,const int decimals) const311 QString StelObject::getMagnitudeInfoString(const StelCore *core, const InfoStringGroup& flags, const int decimals) const
312 {
313 	if (flags&Magnitude)
314 	{
315 		QString str = QString("%1: <b>%2</b>").arg(q_("Magnitude"), QString::number(getVMagnitude(core), 'f', decimals));
316 		const float airmass = getAirmass(core);
317 		if (airmass>-1.f) // Don't show extincted magnitude much below horizon where model is meaningless.
318 			str += QString(" (%1 <b>%2</b> %3 <b>%4</b> %5)").arg(q_("reduced to"), QString::number(getVMagnitudeWithExtinction(core), 'f', decimals), q_("by"), QString::number(airmass, 'f', 2), q_("Airmasses"));
319 		str += "<br/>" + getExtraInfoStrings(Magnitude).join("");
320 		return str;
321 	}
322 	else
323 		return QString();
324 }
325 
326 // Format the positional info string contain J2000/of date/altaz/hour angle positions for the object
getCommonInfoString(const StelCore * core,const InfoStringGroup & flags) const327 QString StelObject::getCommonInfoString(const StelCore *core, const InfoStringGroup& flags) const
328 {
329 	StelApp& app = StelApp::getInstance();
330 	StelObjectMgr* omgr=GETSTELMODULE(StelObjectMgr);
331 	const bool withAtmosphere = core->getSkyDrawer()->getFlagHasAtmosphere();
332 	const bool withDecimalDegree = app.getFlagShowDecimalDegrees();
333 	const bool useSouthAzimuth = app.getFlagSouthAzimuthUsage();
334 	const bool withTables = app.getFlagUseFormattingOutput();
335 	const bool withDesignations = app.getFlagUseCCSDesignation();
336 	const QString cepoch = qc_("on date", "coordinates for current epoch");
337 	const QString currentPlanet = core->getCurrentPlanet()->getEnglishName();
338 	const QString apparent = " " + (withAtmosphere ? q_("(apparent)") : "");
339 	QString res, firstCoordinate, secondCoordinate;
340 	double az_app, alt_app;
341 	StelUtils::rectToSphe(&az_app,&alt_app,getAltAzPosApparent(core));
342 	Q_UNUSED(az_app)
343 
344 	if (withTables)
345 		res += "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
346 
347 	// TRANSLATORS: Right ascension/Declination
348 	const QString RADec = withDesignations ? QString("&alpha;/&delta;") : qc_("RA/Dec", "celestial coordinate system");
349 
350 	if (flags&RaDecJ2000)
351 	{
352 		double dec_j2000, ra_j2000;
353 		StelUtils::rectToSphe(&ra_j2000,&dec_j2000,getJ2000EquatorialPos(core));
354 		if (withDecimalDegree)
355 		{
356 			firstCoordinate  = StelUtils::radToDecDegStr(ra_j2000,5,false,true);
357 			secondCoordinate = StelUtils::radToDecDegStr(dec_j2000);
358 		}
359 		else
360 		{
361 			firstCoordinate  = StelUtils::radToHmsStr(ra_j2000,true);
362 			secondCoordinate = StelUtils::radToDmsStr(dec_j2000,true);
363 		}
364 
365 		if (withTables)
366 			res += QString("<tr><td>%1 (J2000.0):</td><td style='text-align:right;'>%2/</td><td style='text-align:right;'>%3</td><td></td></tr>").arg(RADec, firstCoordinate, secondCoordinate);
367 		else
368 			res += QString("%1 (J2000.0): %2/%3<br/>").arg(RADec, firstCoordinate, secondCoordinate);
369 		res += getExtraInfoStrings(RaDecJ2000).join("");
370 		res += omgr->getExtraInfoStrings(RaDecJ2000).join("");
371 	}
372 
373 	if (flags&RaDecOfDate)
374 	{
375 		double dec_equ, ra_equ;
376 		StelUtils::rectToSphe(&ra_equ,&dec_equ,getEquinoxEquatorialPos(core));
377 		if (withDecimalDegree)
378 		{
379 			firstCoordinate  = StelUtils::radToDecDegStr(ra_equ,5,false,true);
380 			secondCoordinate = StelUtils::radToDecDegStr(dec_equ);
381 		}
382 		else
383 		{
384 			firstCoordinate  = StelUtils::radToHmsStr(ra_equ,true);
385 			secondCoordinate = StelUtils::radToDmsStr(dec_equ,true);
386 		}
387 
388 		if (withTables)
389 			res += QString("<tr><td>%1 (%4):</td><td style='text-align:right;'>%2/</td><td style='text-align:right;'>%3</td><td></td></tr>").arg(RADec, firstCoordinate, secondCoordinate, cepoch);
390 		else
391 			res += QString("%1 (%4): %2/%3<br/>").arg(RADec, firstCoordinate, secondCoordinate, cepoch);
392 		res += getExtraInfoStrings(RaDecOfDate).join("");
393 		res += omgr->getExtraInfoStrings(RaDecOfDate).join("");
394 	}
395 
396 	if (flags&HourAngle)
397 	{
398 		double dec_sidereal, ra_sidereal, ha_sidereal;
399 		StelUtils::rectToSphe(&ra_sidereal,&dec_sidereal,getSiderealPosGeometric(core));
400 		ra_sidereal = 2.*M_PI-ra_sidereal;
401 		if (withAtmosphere && (alt_app>-2.0*M_PI/180.0)) // Don't show refracted values much below horizon where model is meaningless.
402 		{
403 			StelUtils::rectToSphe(&ra_sidereal,&dec_sidereal,getSiderealPosApparent(core));
404 			ra_sidereal = 2.*M_PI-ra_sidereal;
405 			if (withDecimalDegree)
406 			{
407 				ha_sidereal = ra_sidereal*12/M_PI;
408 				if (ha_sidereal>24.)
409 					ha_sidereal -= 24.;
410 				firstCoordinate  = QString("%1h").arg(ha_sidereal, 0, 'f', 5);
411 				secondCoordinate = StelUtils::radToDecDegStr(dec_sidereal);
412 			}
413 			else
414 			{
415 				firstCoordinate  = StelUtils::radToHmsStr(ra_sidereal,true);
416 				secondCoordinate = StelUtils::radToDmsStr(dec_sidereal,true);
417 			}
418 		}
419 		else
420 		{
421 			if (withDecimalDegree)
422 			{
423 				ha_sidereal = ra_sidereal*12/M_PI;
424 				if (ha_sidereal>24.)
425 					ha_sidereal -= 24.;
426 				firstCoordinate  = QString("%1h").arg(ha_sidereal, 0, 'f', 5);
427 				secondCoordinate = StelUtils::radToDecDegStr(dec_sidereal);
428 			}
429 			else
430 			{
431 				firstCoordinate  = StelUtils::radToHmsStr(ra_sidereal,true);
432 				secondCoordinate = StelUtils::radToDmsStr(dec_sidereal,true);
433 			}
434 		}
435 
436 		// TRANSLATORS: Hour angle/Declination
437 		const QString HADec = withDesignations ? QString("h/&delta;") : qc_("HA/Dec", "celestial coordinate system");
438 
439 		if (withTables)
440 			res += QString("<tr><td>%1:</td><td style='text-align:right;'>%2/</td><td style='text-align:right;'>%3</td><td>%4</td></tr>").arg(HADec, firstCoordinate, secondCoordinate, apparent);
441 		else
442 			res += QString("%1: %2/%3 %4<br/>").arg(HADec, firstCoordinate, secondCoordinate, apparent);
443 		res += getExtraInfoStrings(HourAngle).join("");
444 		res += omgr->getExtraInfoStrings(HourAngle).join("");
445 	}
446 
447 	if (flags&AltAzi)
448 	{
449 		// calculate alt az
450 		double az,alt;
451 		StelUtils::rectToSphe(&az,&alt,getAltAzPosGeometric(core));
452 		double direction = 3.; // N is zero, E is 90 degrees
453 		if (useSouthAzimuth)
454 			direction = 2.;
455 		az = direction*M_PI - az;
456 		if (az > M_PI*2)
457 			az -= M_PI*2;
458 		if (withAtmosphere && (alt_app>-2.0*M_PI/180.0)) // Don't show refracted altitude much below horizon where model is meaningless.
459 		{
460 			if (withDecimalDegree)
461 			{
462 				firstCoordinate  = StelUtils::radToDecDegStr(az);
463 				secondCoordinate = StelUtils::radToDecDegStr(alt_app);
464 			}
465 			else
466 			{
467 				firstCoordinate  = StelUtils::radToDmsStr(az,true);
468 				secondCoordinate = StelUtils::radToDmsStr(alt_app,true);
469 			}
470 		}
471 		else
472 		{
473 			if (withDecimalDegree)
474 			{
475 				firstCoordinate  = StelUtils::radToDecDegStr(az);
476 				secondCoordinate = StelUtils::radToDecDegStr(alt);
477 			}
478 			else
479 			{
480 				firstCoordinate  = StelUtils::radToDmsStr(az,true);
481 				secondCoordinate = StelUtils::radToDmsStr(alt,true);
482 			}
483 		}
484 
485 		// TRANSLATORS: Azimuth/Altitude
486 		const QString AzAlt = (withDesignations ? "A/a" : qc_("Az./Alt.", "celestial coordinate system"));
487 
488 		if (withTables)
489 			res += QString("<tr><td>%1:</td><td style='text-align:right;'>%2/</td><td style='text-align:right;'>%3</td><td>%4</td></tr>").arg(AzAlt, firstCoordinate, secondCoordinate, apparent);
490 		else
491 			res += QString("%1: %2/%3 %4<br/>").arg(AzAlt, firstCoordinate, secondCoordinate, apparent);
492 		res += getExtraInfoStrings(AltAzi).join("");
493 	}
494 
495 	if (flags&GalacticCoord)
496 	{
497 		double glong, glat;
498 		StelUtils::rectToSphe(&glong, &glat, getGalacticPos(core));
499 		if (withDecimalDegree)
500 		{
501 			firstCoordinate  = StelUtils::radToDecDegStr(glong);
502 			secondCoordinate = StelUtils::radToDecDegStr(glat);
503 		}
504 		else
505 		{
506 			firstCoordinate  = StelUtils::radToDmsStr(glong,true);
507 			secondCoordinate = StelUtils::radToDmsStr(glat,true);
508 		}
509 
510 		// TRANSLATORS: Galactic longitude/latitude
511 		const QString GalLongLat = (withDesignations ? "l/b" : qc_("Gal. long./lat.", "celestial coordinate system"));
512 		if (withTables)
513 			res += QString("<tr><td>%1:</td><td style='text-align:right;'>%2/</td><td style='text-align:right;'>%3</td><td></td></tr>").arg(GalLongLat, firstCoordinate, secondCoordinate);
514 		else
515 			res += QString("%1: %2/%3<br/>").arg(GalLongLat, firstCoordinate, secondCoordinate);
516 		res += getExtraInfoStrings(GalacticCoord).join("");
517 	}
518 
519 	if (flags&SupergalacticCoord)
520 	{
521 		double sglong, sglat;
522 		StelUtils::rectToSphe(&sglong, &sglat, getSupergalacticPos(core));
523 		if (withDecimalDegree)
524 		{
525 			firstCoordinate  = StelUtils::radToDecDegStr(sglong);
526 			secondCoordinate = StelUtils::radToDecDegStr(sglat);
527 		}
528 		else
529 		{
530 			firstCoordinate  = StelUtils::radToDmsStr(sglong,true);
531 			secondCoordinate = StelUtils::radToDmsStr(sglat,true);
532 		}
533 
534 		// TRANSLATORS: Supergalactic longitude/latitude
535 		const QString SGalLongLat = (withDesignations ? "SGL/SGB" : qc_("Supergal. long./lat.", "celestial coordinate system"));
536 
537 		if (withTables)
538 			res += QString("<tr><td>%1:</td><td style='text-align:right;'>%2/</td><td style='text-align:right;'>%3</td><td></td></tr>").arg(SGalLongLat, firstCoordinate, secondCoordinate);
539 		else
540 			res += QString("%1: %2/%3<br/>").arg(SGalLongLat, firstCoordinate, secondCoordinate);
541 		res += getExtraInfoStrings(SupergalacticCoord).join("");
542 	}
543 
544 	// N.B. Ecliptical coordinates are particularly earth-bound.
545 	// It may be OK to have terrestrial ecliptical coordinates of J2000.0 (standard epoch) because those are in practice linked with VSOP XY plane,
546 	// and because the ecliptical grid of J2000 is also shown for observers on other planets.
547 	// The formulation here has never computed the true position of any observer planet's orbital plane except for Earth,
548 	// or ever displayed the coordinates in the observer planet's equivalent to Earth's ecliptical coordinates.
549 	// As quick test you can observe if in any "Ecliptic coordinate" as seen from e.g. Mars or Jupiter the Sun was ever close to beta=0 (except if crossing the node...).
550 
551 	// TRANSLATORS: Ecliptic longitude/latitude
552 	const QString EqlLongLat = (withDesignations ? QString("&lambda;/&beta;") :
553 						       qc_("Ecl. long./lat.", "celestial coordinate system") );
554 
555 	if (flags&EclipticCoordJ2000)
556 	{
557 		const double eclJ2000=GETSTELMODULE(SolarSystem)->getEarth()->getRotObliquity(2451545.0);
558 		double ra_equ, dec_equ, lambda, beta;
559 		StelUtils::rectToSphe(&ra_equ,&dec_equ,getJ2000EquatorialPos(core));
560 		StelUtils::equToEcl(ra_equ, dec_equ, eclJ2000, &lambda, &beta);
561 		if (lambda<0) lambda+=2.0*M_PI;
562 		if (withDecimalDegree)
563 		{
564 			firstCoordinate  = StelUtils::radToDecDegStr(lambda);
565 			secondCoordinate = StelUtils::radToDecDegStr(beta);
566 		}
567 		else
568 		{
569 			firstCoordinate  = StelUtils::radToDmsStr(lambda, true);
570 			secondCoordinate = StelUtils::radToDmsStr(beta, true);
571 		}
572 
573 		if (withTables)
574 			res += QString("<tr><td>%1 (J2000.0):</td><td style='text-align:right;'>%2/</td><td style='text-align:right;'>%3</td><td></td></tr>").arg(EqlLongLat, firstCoordinate, secondCoordinate);
575 		else
576 			res += QString("%1 (J2000.0): %2/%3<br/>").arg(EqlLongLat, firstCoordinate, secondCoordinate);
577 		res += getExtraInfoStrings(EclipticCoordJ2000).join("");
578 	}
579 
580 	if ((flags&EclipticCoordOfDate) && (QString("Earth Sun").contains(currentPlanet)))
581 	{
582 		const double jde=core->getJDE();
583 		double eclJDE = GETSTELMODULE(SolarSystem)->getEarth()->getRotObliquity(jde);
584 		if (StelApp::getInstance().getCore()->getUseNutation())
585 		{
586 			double deltaEps, deltaPsi;
587 			getNutationAngles(jde, &deltaPsi, &deltaEps);
588 			eclJDE+=deltaEps;
589 		}
590 		double ra_equ, dec_equ, lambdaJDE, betaJDE;
591 
592 		StelUtils::rectToSphe(&ra_equ,&dec_equ,getEquinoxEquatorialPos(core));
593 		StelUtils::equToEcl(ra_equ, dec_equ, eclJDE, &lambdaJDE, &betaJDE);
594 		if (lambdaJDE<0) lambdaJDE+=2.0*M_PI;
595 		if (withDecimalDegree)
596 		{
597 			firstCoordinate  = StelUtils::radToDecDegStr(lambdaJDE);
598 			secondCoordinate = StelUtils::radToDecDegStr(betaJDE);
599 		}
600 		else
601 		{
602 			firstCoordinate  = StelUtils::radToDmsStr(lambdaJDE, true);
603 			secondCoordinate = StelUtils::radToDmsStr(betaJDE, true);
604 		}
605 
606 		if (withTables)
607 			res += QString("<tr><td>%1 (%4):</td><td style='text-align:right;'>%2/</td><td style='text-align:right;'>%3</td><td></td></tr>").arg(EqlLongLat, firstCoordinate, secondCoordinate, cepoch) + "</table>";
608 		else
609 			res += QString("%1 (%4): %2/%3<br/>").arg(EqlLongLat, firstCoordinate, secondCoordinate, cepoch);
610 		res += getExtraInfoStrings(EclipticCoordOfDate).join("");
611 
612 		// GZ Only for now: display epsilon_A, angle between Earth's Axis and ecl. of date.
613 		if (withDecimalDegree)
614 			firstCoordinate = StelUtils::radToDecDegStr(eclJDE);
615 		else
616 			firstCoordinate = StelUtils::radToDmsStr(eclJDE, true);
617 
618 		QString eqlObl = q_("Ecliptic obliquity");
619 		if (withTables)
620 		{
621 			res += "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
622 			res += QString("<tr><td>%1 (%4):</td><td>%2</td></tr>").arg(eqlObl, firstCoordinate, cepoch);
623 		}
624 		else
625 			res += QString("%1 (%3): %2<br/>").arg(eqlObl, firstCoordinate, cepoch);
626 	}
627 
628 	if (withTables)
629 		 res += "</table>";
630 
631 	// Specialized plugins (e.g. Astro Navigation or ethno-astronomical specialties) may want to provide additional types of coordinates here.
632 	if (flags&OtherCoord)
633 	{
634 		if (withTables)
635 			res += "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
636 		res += getExtraInfoStrings(OtherCoord).join("");
637 		res += omgr->getExtraInfoStrings(OtherCoord).join("");
638 		if (withTables)
639 			 res += "</table>";
640 	}
641 
642 	if ((flags&SiderealTime) && (currentPlanet==QStringLiteral("Earth")))
643 	{
644 		const double longitude=static_cast<double>(core->getCurrentLocation().longitude);
645 		double sidereal=(get_mean_sidereal_time(core->getJD(), core->getJDE())  + longitude) / 15.;
646 		sidereal=StelUtils::fmodpos(sidereal, 24.);
647 		QString STc = q_("Mean Sidereal Time");
648 		QString STd = StelUtils::hoursToHmsStr(sidereal);
649 
650 		if (withTables)
651 		{
652 			res += "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
653 			res += QString("<tr><td>%1:</td><td style='text-align:right;'>%2</td></tr>").arg(STc, STd);
654 		}
655 		else
656 			res += QString("%1: %2<br/>").arg(STc, STd);
657 
658 		if (core->getUseNutation())
659 		{
660 			sidereal=(get_apparent_sidereal_time(core->getJD(), core->getJDE()) + longitude) / 15.;
661 			sidereal=StelUtils::fmodpos(sidereal, 24.);
662 			STc = q_("Apparent Sidereal Time");
663 			STd = StelUtils::hoursToHmsStr(sidereal);
664 			if (withTables)
665 				res += QString("<tr><td>%1:</td><td style='text-align:right;'>%2</td></tr>").arg(STc, STd);
666 			else
667 				res += QString("%1: %2<br/>").arg(STc, STd);
668 		}
669 		res += getExtraInfoStrings(flags&SiderealTime).join("");
670 		res += omgr->getExtraInfoStrings(flags&SiderealTime).join("");
671 		if (withTables && !(flags&RTSTime && getType()!=QStringLiteral("Satellite")))
672 			res += "</table>";
673 	}
674 
675 	if (flags&RTSTime && getType()!=QStringLiteral("Satellite") && !currentPlanet.contains("observer", Qt::CaseInsensitive) && !(core->getCurrentLocation().name.contains("->")))
676 	{
677 		const double utcShift = core->getUTCOffset(core->getJD()) / 24.; // Fix DST shift...
678 		Vec4d rts = getRTSTime(core);
679 		QString sTransit = qc_("Transit", "celestial event; passage across a meridian");
680 		QString sRise = qc_("Rise", "celestial event");
681 		QString sSet = qc_("Set", "celestial event");
682 		double sunrise = 0.;
683 		double sunset = 24.;
684 		const bool isSun = (getEnglishName()=="Sun");
685 		double hour(0);
686 
687 		if (withTables && !(flags&SiderealTime && currentPlanet==QStringLiteral("Earth")))
688 			res += "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
689 
690 		if (rts[3]==0.)
691 		{
692 			hour = StelUtils::getHoursFromJulianDay(rts[0]+utcShift);
693 			if (withTables)
694 				res += QString("<tr><td>%1:</td><td style='text-align:right;'>%2</td></tr>").arg(sRise, StelUtils::hoursToHmsStr(hour, true));
695 			else
696 				res += QString("%1: %2<br/>").arg(sRise, StelUtils::hoursToHmsStr(hour, true));
697 
698 			sunrise = hour;
699 		}
700 
701 		hour = StelUtils::getHoursFromJulianDay(rts[1]+utcShift);
702 		if (withTables)
703 			res += QString("<tr><td>%1:</td><td style='text-align:right;'>%2</td></tr>").arg(sTransit, StelUtils::hoursToHmsStr(hour, true));
704 		else
705 			res += QString("%1: %2<br/>").arg(sTransit, StelUtils::hoursToHmsStr(hour, true));
706 
707 		if (rts[3]==0.)
708 		{
709 			hour = StelUtils::getHoursFromJulianDay(rts[2]+utcShift);
710 			if (withTables)
711 				res += QString("<tr><td>%1:</td><td style='text-align:right;'>%2</td></tr>").arg(sSet, StelUtils::hoursToHmsStr(hour, true));
712 			else
713 				res += QString("%1: %2<br/>").arg(sSet, StelUtils::hoursToHmsStr(hour, true));
714 
715 			sunset = hour;
716 		}
717 
718 		if (isSun)
719 		{
720 			QString sMTwilight = qc_("Morning twilight", "celestial event");
721 			QString sETwilight = qc_("Evening twilight", "celestial event");
722 			const double twilightAltitude = omgr->getTwilightAltitude();
723 			QString alt = QString::number(twilightAltitude, 'f', 1);
724 			Vec4d twilight = getRTSTime(core, twilightAltitude);
725 			if (twilight[3]==0.)
726 			{
727 				hour = StelUtils::getHoursFromJulianDay(twilight[0]+utcShift);
728 				if (withTables)
729 					res += QString("<tr><td>%1 (h=%2°):</td><td style='text-align:right;'>%3</td></tr>").arg(sMTwilight, alt, StelUtils::hoursToHmsStr(hour, true));
730 				else
731 					res += QString("%1 (h=%2°): %3<br/>").arg(sMTwilight, alt, StelUtils::hoursToHmsStr(hour, true));
732 
733 				hour = StelUtils::getHoursFromJulianDay(twilight[2]+utcShift);
734 				if (withTables)
735 					res += QString("<tr><td>%1 (h=%2°):</td><td style='text-align:right;'>%3</td></tr>").arg(sETwilight, alt, StelUtils::hoursToHmsStr(hour, true));
736 				else
737 					res += QString("%1 (h=%2°): %3<br/>").arg(sETwilight, alt, StelUtils::hoursToHmsStr(hour, true));
738 			}
739 			double lengthOfDay = sunset - sunrise;
740 			if (lengthOfDay<24.)
741 			{
742 				QString sDay = q_("Daytime");
743 				if (withTables)
744 					res += QString("<tr><td>%1:</td><td style='text-align:right;'>%2</td></tr>").arg(sDay, StelUtils::hoursToHmsStr(lengthOfDay, true));
745 				else
746 					res += QString("%1: %2<br/>").arg(sDay, StelUtils::hoursToHmsStr(lengthOfDay, true));
747 			}
748 		}
749 
750 		if (withTables)
751 			res += "</table>";
752 
753 		if (rts[3]<0.)
754 		{
755 			if (isSun)
756 				res += q_("Polar night") + "<br />";
757 			else
758 				res += q_("This object never rises") + "<br />";
759 		}
760 		else if (rts[3]>0.)
761 		{
762 			if (isSun)
763 				res += q_("Polar day") + "<br />";
764 			else
765 				res += q_("Circumpolar (never sets)") + "<br />";
766 		}
767 		// These never could have been seen before (??)
768 		//else if (rts[0]>99. && rts[2]<99.)
769 		//	res += q_("Polar dawn") + "<br />";
770 		//else if (rts[0]<99. && rts[2]>99.)
771 		//	res += q_("Polar dusk") + "<br />";
772 
773 
774 		// Greatest Digression: limiting azimuth and hour angles for stars with upper culmination between pole and zenith
775 		double dec_equ, ra_equ;
776 		StelUtils::rectToSphe(&ra_equ,&dec_equ,getEquinoxEquatorialPos(core));
777 		const double latitude=static_cast<double>(core->getCurrentLocation().latitude)*M_PI_180;
778 		if (((latitude>0.) && (dec_equ>=latitude)) || ((latitude<0.) && (dec_equ<=latitude)))
779 		{
780 			const double theta=acos(tan(latitude)/tan(dec_equ)); // hour angle
781 			double az=asin(cos(dec_equ)/cos(latitude)); // azimuth (eastern)
782 			// TRANSLATORS: Greatest Eastern Digression is the maximum azimuth for stars with upper culmination between pole and zenith
783 			QString event(q_("Max. E. Digression"));
784 			// TRANSLATORS: azimuth (abbrev.)
785 			QString azStr=(withDesignations? qc_("A", "celestial coordinate system") : qc_("Az.", "celestial coordinate system"));
786 			// Translators: hour angle  (abbrev.)
787 			QString haStr=(withDesignations? qc_("h", "celestial coordinate system") : qc_("HA", "celestial coordinate system"));
788 			if (latitude<0.)
789 				az=M_PI-az;
790 			if (StelApp::getInstance().getFlagSouthAzimuthUsage())
791 				az+=M_PI;
792 
793 			if (withDecimalDegree)
794 			{
795 				firstCoordinate  = StelUtils::radToDecDegStr(az,5,false,true);
796 				secondCoordinate = StelUtils::radToDecDegStr(-theta);
797 			}
798 			else
799 			{
800 				firstCoordinate  = StelUtils::radToDmsStr(az,true);
801 				secondCoordinate = StelUtils::radToHmsStr(-theta,true);
802 			}
803 
804 			if (withTables)
805 			{
806 				res += "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
807 				res += QString("<tr><td>%1:</td><td style='text-align:right;'>%2:</td><td style='text-align:right;'>%3</td><td style='text-align:right;'>/%4:</td><td style='text-align:right;'>%5</td></tr>").arg(event,  azStr,  firstCoordinate, haStr, secondCoordinate);
808 			}
809 			else
810 				res += QString("%1: %2=%3, %4=%5<br/>").arg(event,  azStr,  firstCoordinate, haStr, secondCoordinate);
811 
812 			// TRANSLATORS: Greatest Western Digression is the maximum western azimuth for stars with upper culmination between pole and zenith
813 			event=q_("Max. W. Digression");
814 			if (withDecimalDegree)
815 			{
816 				firstCoordinate  = StelUtils::radToDecDegStr(StelUtils::fmodpos(-az, 2.*M_PI),5,false,true);
817 				secondCoordinate = StelUtils::radToDecDegStr(theta);
818 			}
819 			else
820 			{
821 				firstCoordinate  = StelUtils::radToDmsStr(StelUtils::fmodpos(-az, 2.*M_PI),true);
822 				secondCoordinate = StelUtils::radToHmsStr(theta,true);
823 			}
824 
825 			if (withTables)
826 			{
827 				res += QString("<tr><td>%1:</td><td style='text-align:right;'>%2:</td><td style='text-align:right;'>%3</td><td style='text-align:right;'>/%4:</td><td style='text-align:right;'>%5</td></tr>").arg(event,  azStr,  firstCoordinate, haStr, secondCoordinate);
828 				res += QString("</table>");
829 			}
830 			else
831 				res += QString("%1: %2=%3, %4=%5<br/>").arg(event, azStr,  firstCoordinate, haStr, secondCoordinate);
832 		}
833 		res += getExtraInfoStrings(flags&RTSTime).join(' ');
834 		res += omgr->getExtraInfoStrings(flags&RTSTime).join(' ');
835 	}
836 
837 	if (flags&Extra)
838 	{
839 		if (getType()!=QStringLiteral("Star"))
840 		{
841 			QString pa;
842 			const double par = static_cast<double>(getParallacticAngle(core));
843 			if (withDecimalDegree)
844 				pa = StelUtils::radToDecDegStr(par);
845 			else
846 				pa = StelUtils::radToDmsStr(par, true);
847 
848 			res += QString("%1: %2<br/>").arg(q_("Parallactic Angle")).arg(pa);
849 		}
850 		res += getExtraInfoStrings(Extra).join("");
851 		res += omgr->getExtraInfoStrings(Extra).join("");
852 	}
853 
854 	if (flags&IAUConstellation)
855 	{
856 		QString constel=core->getIAUConstellation(getEquinoxEquatorialPos(core));
857 		res += QString("%1: %2<br/>").arg(q_("IAU Constellation"), constel);
858 		res += getExtraInfoStrings(flags&IAUConstellation).join("");
859 		res += omgr->getExtraInfoStrings(flags&IAUConstellation).join("");
860 	}
861 
862 	return res;
863 }
864 
865 // Apply post processing on the info string
postProcessInfoString(QString & str,const InfoStringGroup & flags) const866 void StelObject::postProcessInfoString(QString& str, const InfoStringGroup& flags) const
867 {
868 	StelObjectMgr* omgr;
869 	omgr=GETSTELMODULE(StelObjectMgr);
870 	str.append(getExtraInfoStrings(Script).join(' '));
871 	str.append(omgr->getExtraInfoStrings(Script).join(' '));
872 	str.append(getExtraInfoStrings(DebugAid).join(' ')); // TBD: Remove for Release builds?
873 	str.append(omgr->getExtraInfoStrings(DebugAid).join(' ')); // TBD: Remove for Release builds?
874 
875 	// hack for avoiding an empty line before table
876 	str.replace(QRegularExpression("<br(\\s*/)?><table"), "<table");
877 	// chomp trailing line breaks
878 	str.replace(QRegularExpression("<br(\\s*/)?>\\s*$"), "");
879 
880 	if (flags&PlainText)
881 	{
882 		str.replace("<b>", "");
883 		str.replace("</b>", "");
884 		str.replace("<h2>", "");
885 		str.replace("</h2>", "\n");
886 		str.replace(QRegularExpression("<br(\\s*/)?>"), "\n");
887 		str.replace("<tr>", "");
888 		str.replace(QRegularExpression("<td(\\w*)?>"), "");
889 		str.replace("<td>", "");
890 		str.replace("</tr>", "\n");
891 		str.replace(QRegularExpression("<table(\\w*)?>"), "");
892 		str.replace("</table>", "");
893 	}
894 	else if(!(flags&NoFont))
895 	{
896 		Vec3f color = getInfoColor();
897 		StelCore* core = StelApp::getInstance().getCore();
898 		if (StelApp::getInstance().getFlagOverwriteInfoColor())
899 		{
900 			// make info text more readable...
901 			color = StelApp::getInstance().getOverwriteInfoColor();
902 		}
903 		if (core->isBrightDaylight() && !StelApp::getInstance().getVisionModeNight())
904 		{
905 			// make info text more readable when atmosphere enabled at daylight.
906 			color = StelApp::getInstance().getDaylightInfoColor();
907 		}
908 		str.prepend(QString("<font color=%1>").arg(color.toHtmlColor()));
909 		str.append(QString("</font>"));
910 	}
911 }
912 
getInfoMap(const StelCore * core) const913 QVariantMap StelObject::getInfoMap(const StelCore *core) const
914 {
915 	QVariantMap map;
916 
917 	Vec3d pos;
918 	double ra, dec, alt, az, glong, glat;
919 	bool useOldAzimuth = StelApp::getInstance().getFlagSouthAzimuthUsage();
920 
921 	map.insert("type", getType());
922 	// ra/dec
923 	pos = getEquinoxEquatorialPos(core);
924 	StelUtils::rectToSphe(&ra, &dec, pos);
925 	map.insert("ra", ra*M_180_PI);
926 	map.insert("dec", dec*M_180_PI);
927 	map.insert("iauConstellation", core->getIAUConstellation(pos));
928 
929 	if (getType()!=QStringLiteral("Star"))
930 		map.insert("parallacticAngle", static_cast<double>(getParallacticAngle(core))*M_180_PI);
931 
932 	// Sidereal Time and hour angle
933 	if (core->getCurrentLocation().planetName=="Earth")
934 	{
935 		const double longitude=static_cast<double>(core->getCurrentLocation().longitude);
936 		double sidereal=(get_mean_sidereal_time(core->getJD(), core->getJDE())  + longitude) / 15.;
937 		sidereal=fmod(sidereal, 24.);
938 		if (sidereal < 0.) sidereal+=24.;
939 		map.insert("meanSidTm", StelUtils::hoursToHmsStr(sidereal));
940 
941 		sidereal=(get_apparent_sidereal_time(core->getJD(), core->getJDE()) + longitude) / 15.;
942 		sidereal=fmod(sidereal, 24.);
943 		if (sidereal < 0.) sidereal+=24.;
944 		map.insert("appSidTm", StelUtils::hoursToHmsStr(sidereal));
945 
946 		double ha = sidereal * 15.0 - ra * M_180_PI;
947 		ha=fmod(ha, 360.0);
948 		if (ha < 0.) ha+=360.0;
949 		map.insert("hourAngle-dd", ha);
950 		map.insert("hourAngle-hms", StelUtils::hoursToHmsStr(ha/15.0));
951 	}
952 
953 	// ra/dec in J2000
954 	pos = getJ2000EquatorialPos(core);
955 	StelUtils::rectToSphe(&ra, &dec, pos);
956 	map.insert("raJ2000", ra*M_180_PI);
957 	map.insert("decJ2000", dec*M_180_PI);
958 
959 	// apparent altitude/azimuth
960 	pos = getAltAzPosApparent(core);
961 	StelUtils::rectToSphe(&az, &alt, pos);
962 	double direction = 3.; // N is zero, E is 90 degrees
963 	if (useOldAzimuth)
964 		direction = 2.;
965 	az = direction*M_PI - az;
966 	if (az > M_PI*2)
967 		az -= M_PI*2;
968 
969 	map.insert("altitude", alt*M_180_PI);
970 	map.insert("azimuth", az*M_180_PI);
971 	map.insert("airmass", getAirmass(core));
972 
973 	// geometric altitude/azimuth
974 	pos = getAltAzPosGeometric(core);
975 	StelUtils::rectToSphe(&az, &alt, pos);
976 	az = direction*M_PI - az;
977 	if (az > M_PI*2)
978 		az -= M_PI*2;
979 
980 	map.insert("altitude-geometric", alt*M_180_PI);
981 	map.insert("azimuth-geometric", az*M_180_PI);
982 
983 	// galactic long/lat
984 	pos = getGalacticPos(core);
985 	StelUtils::rectToSphe(&glong, &glat, pos);
986 	map.insert("glong", glong*M_180_PI);
987 	map.insert("glat", glat*M_180_PI);
988 
989 	// supergalactic long/lat
990 	pos = getSupergalacticPos(core);
991 	StelUtils::rectToSphe(&glong, &glat, pos);
992 	map.insert("sglong", glong*M_180_PI);
993 	map.insert("sglat", glat*M_180_PI);
994 
995 	SolarSystem* ssmgr = GETSTELMODULE(SolarSystem);
996 	double ra_equ, dec_equ, lambda, beta;
997 	// J2000
998 	double eclJ2000 = ssmgr->getEarth()->getRotObliquity(2451545.0);
999 	double ecl = ssmgr->getEarth()->getRotObliquity(core->getJDE());
1000 
1001 	// ecliptic longitude/latitude (J2000 frame)
1002 	StelUtils::rectToSphe(&ra_equ,&dec_equ, getJ2000EquatorialPos(core));
1003 	StelUtils::equToEcl(ra_equ, dec_equ, eclJ2000, &lambda, &beta);
1004 	if (lambda<0) lambda+=2.0*M_PI;
1005 	map.insert("elongJ2000", lambda*M_180_PI);
1006 	map.insert("elatJ2000", beta*M_180_PI);
1007 
1008 	if (QString("Earth Sun").contains(core->getCurrentLocation().planetName))
1009 	{
1010 		// ecliptic longitude/latitude
1011 		StelUtils::rectToSphe(&ra_equ,&dec_equ, getEquinoxEquatorialPos(core));
1012 		StelUtils::equToEcl(ra_equ, dec_equ, ecl, &lambda, &beta);
1013 		if (lambda<0) lambda+=2.0*M_PI;
1014 		map.insert("elong", lambda*M_180_PI);
1015 		map.insert("elat", beta*M_180_PI);
1016 	}
1017 
1018 	// magnitude
1019 	map.insert("vmag", getVMagnitude(core));
1020 	map.insert("vmage", getVMagnitudeWithExtinction(core));
1021 
1022 	// angular size
1023 	double angularSize = 2.*getAngularSize(core)*M_PI_180;
1024 	bool sign;
1025 	double deg;
1026 	StelUtils::radToDecDeg(angularSize, sign, deg);
1027 	if (!sign)
1028 		deg *= -1;
1029 	map.insert("size", angularSize);
1030 	map.insert("size-dd", deg);
1031 	map.insert("size-deg", StelUtils::radToDecDegStr(angularSize, 5));
1032 	map.insert("size-dms", StelUtils::radToDmsPStr(angularSize, 2));
1033 
1034 	// english name or designation & localized name
1035 	map.insert("name", getEnglishName());
1036 	map.insert("localized-name", getNameI18n());
1037 
1038 	// 'above horizon' flag
1039 	map.insert("above-horizon", isAboveRealHorizon(core));
1040 
1041 	Vec4d rts = getRTSTime(core);
1042 	if (rts[3]>-1000.)
1043 	{
1044 		const double utcShift = core->getUTCOffset(core->getJD()) / 24.; // Fix DST shift...
1045 		int hr, min, sec;
1046 		StelUtils::getTimeFromJulianDay(rts[1]+utcShift, &hr, &min, &sec);
1047 		double hours=hr+static_cast<double>(min)/60. + static_cast<double>(sec)/3600.;
1048 
1049 		map.insert("transit", StelUtils::hoursToHmsStr(hours, true));
1050 		map.insert("transit-dhr", hours);
1051 		if (rts[3]==0.)
1052 		{
1053 			StelUtils::getTimeFromJulianDay(rts[0]+utcShift, &hr, &min, &sec);
1054 			hours=hr+static_cast<double>(min)/60. + static_cast<double>(sec)/3600.;
1055 			map.insert("rise", StelUtils::hoursToHmsStr(hours, true));
1056 			map.insert("rise-dhr", hours);
1057 			StelUtils::getTimeFromJulianDay(rts[2]+utcShift, &hr, &min, &sec);
1058 			hours=hr+static_cast<double>(min)/60. + static_cast<double>(sec)/3600.;
1059 			map.insert("set", StelUtils::hoursToHmsStr(hours, true));
1060 			map.insert("set-dhr", hours);
1061 		}
1062 		else {
1063 			map.insert("rise", "---");
1064 			map.insert("set", "---");
1065 		}
1066 	}
1067 	return map;
1068 }
1069 
setExtraInfoString(const InfoStringGroup & flags,const QString & str)1070 void StelObject::setExtraInfoString(const InfoStringGroup& flags, const QString &str)
1071 {
1072 	extraInfoStrings.remove(flags); // delete all entries with these flags
1073 	if (str.length()>0)
1074 		extraInfoStrings.insert(flags, str);
1075 }
addToExtraInfoString(const StelObject::InfoStringGroup & flags,const QString & str)1076 void StelObject::addToExtraInfoString(const StelObject::InfoStringGroup &flags, const QString &str)
1077 {
1078 	// Avoid insertion of full duplicates!
1079 	if (!extraInfoStrings.contains(flags, str))
1080 		extraInfoStrings.insertMulti(flags, str);
1081 }
1082 
getExtraInfoStrings(const InfoStringGroup & flags) const1083 QStringList StelObject::getExtraInfoStrings(const InfoStringGroup& flags) const
1084 {
1085 	QStringList list;
1086 	QMultiMap<InfoStringGroup, QString>::const_iterator i = extraInfoStrings.constBegin();
1087 	while (i != extraInfoStrings.constEnd())
1088 	{
1089 		if (i.key() & flags)
1090 		{
1091 			QString val=i.value();
1092 			if (flags&DebugAid)
1093 				val.prepend("DEBUG: ");
1094 			// For unclear reasons the sequence of entries can be preserved by *pre*pending in the returned list.
1095 			list.prepend(val);
1096 		}
1097 		++i;
1098 	}
1099 	return list;
1100 }
1101 
removeExtraInfoStrings(const InfoStringGroup & flags)1102 void StelObject::removeExtraInfoStrings(const InfoStringGroup& flags)
1103 {
1104 	QMultiMap<InfoStringGroup, QString>::iterator i = extraInfoStrings.begin();
1105 	while (i != extraInfoStrings.end())
1106 	{
1107 		if (i.key() & flags)
1108 			i=extraInfoStrings.erase(i);
1109 		else
1110 			++i;
1111 	}
1112 }
1113 
1114 // Add horizontal coordinates of Sun and Moon where useful
getSolarLunarInfoString(const StelCore * core,const InfoStringGroup & flags) const1115 QString StelObject::getSolarLunarInfoString(const StelCore *core, const InfoStringGroup& flags) const
1116 {
1117     QString str;
1118     QTextStream oss(&str);
1119     static SolarSystem *ssystem=GETSTELMODULE(SolarSystem);
1120     PlanetP earth = ssystem->getEarth();
1121     if ((core->getCurrentPlanet()==earth) && (flags&SolarLunarPosition))
1122     {
1123 	const bool withTables = StelApp::getInstance().getFlagUseFormattingOutput();
1124 	const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
1125 
1126 	if (withTables)
1127 	    oss << "<table style='margin:0em 0em 0em -0.125em;border-spacing:0px;border:0px;'>";
1128 	const bool useSouthAzimuth = StelApp::getInstance().getFlagSouthAzimuthUsage();
1129 	const bool withDesignations = StelApp::getInstance().getFlagUseCCSDesignation();
1130 	double az, alt;
1131 	QString azStr, altStr;
1132 
1133 	if (getEnglishName()!="Sun")
1134 	{
1135 	    StelUtils::rectToSphe(&az,&alt,ssystem->getSun()->getAltAzPosAuto(core));
1136 	    az = (useSouthAzimuth? 2. : 3.)*M_PI - az;
1137 	    if (az > M_PI*2)
1138 		az -= M_PI*2;
1139 	    azStr  = (withDecimalDegree ? StelUtils::radToDecDegStr(az, 2)  : StelUtils::radToDmsStr(az,false));
1140 	    altStr = (withDecimalDegree ? StelUtils::radToDecDegStr(alt, 2) : StelUtils::radToDmsStr(alt,false));
1141 
1142 	    // TRANSLATORS: Azimuth/Altitude
1143 	    const QString SolarAzAlt = (withDesignations ? qc_("Solar A/a", "celestial coordinate system") : qc_("Solar Az./Alt.", "celestial coordinate system"));
1144 	    if (withTables)
1145 	    {
1146 		oss << QString("<tr><td>%1:</td><td style='text-align:right;'>%2/</td><td style='text-align:right;'>%3</td></tr>").arg(SolarAzAlt, azStr, altStr);
1147 	    }
1148 	    else
1149 		oss << QString("%1: %2/%3<br/>").arg(SolarAzAlt, azStr, altStr);
1150 	}
1151 	if (getEnglishName()!="Moon")
1152 	{
1153 	    StelUtils::rectToSphe(&az,&alt,ssystem->getMoon()->getAltAzPosAuto(core));
1154 	    az = (useSouthAzimuth? 2. : 3.)*M_PI - az;
1155 	    if (az > M_PI*2)
1156 		az -= M_PI*2;
1157 	    azStr  = (withDecimalDegree ? StelUtils::radToDecDegStr(az, 2)  : StelUtils::radToDmsStr(az,false));
1158 	    altStr = (withDecimalDegree ? StelUtils::radToDecDegStr(alt, 2) : StelUtils::radToDmsStr(alt,false));
1159 
1160 	    // TRANSLATORS: Azimuth/Altitude
1161 	    const QString LunarAzAlt = (withDesignations ? qc_("Lunar A/a", "celestial coordinate system") : qc_("Lunar Az./Alt.", "celestial coordinate system"));
1162 	    if (withTables)
1163 	    {
1164 		oss << QString("<tr><td>%1:</td><td style='text-align:right;'>%2/</td><td style='text-align:right;'>%3</td></tr>").arg(LunarAzAlt, azStr, altStr);
1165 	    }
1166 	    else
1167 		oss << QString("%1: %2/%3<br/>").arg(LunarAzAlt, azStr, altStr);
1168 	}
1169 	if (withTables)
1170 	    oss << "</table>";
1171     }
1172     return str;
1173 }
1174