1 /*
2 * Stellarium
3 * Copyright (C) 2002 Fabien Chereau
4 * Copyright (C) 2011 Alexander Wolf
5 * Copyright (C) 2015 Georg Zotti
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
20 */
21
22 #include "Nebula.hpp"
23 #include "NebulaMgr.hpp"
24 #include "StelTexture.hpp"
25
26 #include "StelUtils.hpp"
27 #include "StelApp.hpp"
28 #include "StelTextureMgr.hpp"
29 #include "StelModuleMgr.hpp"
30 #include "StelCore.hpp"
31 #include "StelPainter.hpp"
32 #include "SolarSystem.hpp"
33 #include "RefractionExtinction.hpp"
34
35 #include <QTextStream>
36 #include <QFile>
37 #include <QString>
38 #include <QRegularExpression>
39
40 #include <QDebug>
41 #include <QBuffer>
42
43 const QString Nebula::NEBULA_TYPE = QStringLiteral("Nebula");
44
45 StelTextureSP Nebula::texCircle;
46 StelTextureSP Nebula::texCircleLarge;
47 StelTextureSP Nebula::texRegion;
48 StelTextureSP Nebula::texGalaxy;
49 StelTextureSP Nebula::texGalaxyLarge;
50 StelTextureSP Nebula::texOpenCluster;
51 StelTextureSP Nebula::texOpenClusterLarge;
52 StelTextureSP Nebula::texOpenClusterXLarge;
53 StelTextureSP Nebula::texGlobularCluster;
54 StelTextureSP Nebula::texGlobularClusterLarge;
55 StelTextureSP Nebula::texPlanetaryNebula;
56 StelTextureSP Nebula::texDiffuseNebula;
57 StelTextureSP Nebula::texDiffuseNebulaLarge;
58 StelTextureSP Nebula::texDiffuseNebulaXLarge;
59 StelTextureSP Nebula::texDarkNebula;
60 StelTextureSP Nebula::texDarkNebulaLarge;
61 StelTextureSP Nebula::texOpenClusterWithNebulosity;
62 StelTextureSP Nebula::texOpenClusterWithNebulosityLarge;
63 bool Nebula::drawHintProportional = false;
64 bool Nebula::surfaceBrightnessUsage = false;
65 bool Nebula::designationUsage = false;
66 float Nebula::hintsBrightness = 0.f;
67 Vec3f Nebula::labelColor = Vec3f(0.4f,0.3f,0.5f);
68 QMap<Nebula::NebulaType, Vec3f>Nebula::hintColorMap;
69 QMap<Nebula::NebulaType, QString> Nebula::typeStringMap;
70 bool Nebula::flagUseTypeFilters = false;
71 Nebula::CatalogGroup Nebula::catalogFilters = Nebula::CatalogGroup(Q_NULLPTR);
72 Nebula::TypeGroup Nebula::typeFilters = Nebula::TypeGroup(Nebula::AllTypes);
73 bool Nebula::flagUseArcsecSurfaceBrightness = false;
74 bool Nebula::flagUseShortNotationSurfaceBrightness = true;
75 bool Nebula::flagUseOutlines = false;
76 bool Nebula::flagShowAdditionalNames = true;
77 bool Nebula::flagUseSizeLimits = false;
78 double Nebula::minSizeLimit = 1.0;
79 double Nebula::maxSizeLimit = 600.0;
80
Nebula()81 Nebula::Nebula()
82 : StelObject()
83 , DSO_nb(0)
84 , M_nb(0)
85 , NGC_nb(0)
86 , IC_nb(0)
87 , C_nb(0)
88 , B_nb(0)
89 , Sh2_nb(0)
90 , VdB_nb(0)
91 , RCW_nb(0)
92 , LDN_nb(0)
93 , LBN_nb(0)
94 , Cr_nb(0)
95 , Mel_nb(0)
96 , PGC_nb(0)
97 , UGC_nb(0)
98 , Arp_nb(0)
99 , VV_nb(0)
100 , DWB_nb(0)
101 , Tr_nb(0)
102 , St_nb(0)
103 , Ru_nb(0)
104 , VdBHa_nb(0)
105 , Ced_nb("")
106 , PK_nb("")
107 , PNG_nb("")
108 , SNRG_nb("")
109 , ACO_nb("")
110 , HCG_nb("")
111 , ESO_nb("")
112 , VdBH_nb("")
113 , withoutID(false)
114 , nameI18("")
115 , mTypeString()
116 , bMag(99.)
117 , vMag(99.)
118 , majorAxisSize(0.)
119 , minorAxisSize(0.)
120 , orientationAngle(0)
121 , oDistance(0.)
122 , oDistanceErr(0.)
123 , redshift(99.)
124 , redshiftErr(0.)
125 , parallax(0.)
126 , parallaxErr(0.)
127 , nType()
128 {
129 outlineSegments.clear();
130 designations.clear();
131 }
132
~Nebula()133 Nebula::~Nebula()
134 {
135 }
136
getMagnitudeInfoString(const StelCore * core,const InfoStringGroup & flags,const int decimals) const137 QString Nebula::getMagnitudeInfoString(const StelCore *core, const InfoStringGroup& flags, const int decimals) const
138 {
139 QString res;
140 const float mmag = qMin(vMag, bMag);
141 if (mmag < 50.f && flags&Magnitude)
142 {
143 QString emag = "";
144 QString fsys = "";
145 bool bmag = false;
146 float mag = getVMagnitude(core);
147 float mage = getVMagnitudeWithExtinction(core);
148 bool hasAtmosphere = core->getSkyDrawer()->getFlagHasAtmosphere();
149 QString tmag = q_("Magnitude");
150 if (nType == NebDn)
151 tmag = q_("Opacity");
152
153 if (bMag < 50.f && vMag > 50.f)
154 {
155 fsys = QString("(%1 B").arg(q_("photometric passband"));
156 if (hasAtmosphere)
157 fsys.append(";");
158 else
159 fsys.append(")");
160 mag = getBMagnitude(core);
161 mage = getBMagnitudeWithExtinction(core);
162 bmag = true;
163 }
164
165 const float airmass = getAirmass(core);
166 if (nType != NebDn && airmass>-1.f) // Don't show extincted magnitude much below horizon where model is meaningless.
167 {
168 emag = QString("%1 <b>%2</b> %3 <b>%4</b> %5)").arg(q_("reduced to"), QString::number(mage, 'f', decimals), q_("by"), QString::number(airmass, 'f', 2), q_("Airmasses"));
169 if (!bmag)
170 emag = QString("(%1").arg(emag);
171 }
172 res = QString("%1: <b>%2</b> %3 %4<br />").arg(tmag, QString::number(mag, 'f', decimals), fsys, emag);
173 }
174 res += getExtraInfoStrings(Magnitude).join("");
175 return res;
176 }
177
getInfoString(const StelCore * core,const InfoStringGroup & flags) const178 QString Nebula::getInfoString(const StelCore *core, const InfoStringGroup& flags) const
179 {
180 QString str;
181 QTextStream oss(&str);
182 bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
183
184 if ((flags&Name) || (flags&CatalogNumber))
185 oss << "<h2>";
186
187 if (!nameI18.isEmpty() && flags&Name)
188 {
189 oss << getNameI18n();
190 QString aliases = getI18nAliases();
191 if (!aliases.isEmpty() && flagShowAdditionalNames)
192 oss << " (" << aliases << ")";
193 }
194
195 if (flags&CatalogNumber)
196 {
197 if (!nameI18.isEmpty() && !withoutID && flags&Name)
198 oss << "<br>";
199
200 oss << designations.join(" - ");
201 }
202
203 if ((flags&Name) || (flags&CatalogNumber))
204 oss << "</h2>";
205
206 if (flags&Name)
207 {
208 QStringList extraNames=getExtraInfoStrings(Name);
209 if (extraNames.length()>0)
210 oss << q_("Additional names: ") << extraNames.join(", ") << "<br/>";
211 }
212 if (flags&CatalogNumber)
213 {
214 QStringList extraCat=getExtraInfoStrings(CatalogNumber);
215 if (extraCat.length()>0)
216 oss << q_("Additional catalog numbers: ") << extraCat.join(", ") << "<br/>";
217 }
218
219 if (flags&ObjectType)
220 {
221 QString mt = getMorphologicalTypeString();
222 if (mt.isEmpty())
223 oss << QString("%1: <b>%2</b>").arg(q_("Type"), getTypeString()) << "<br>";
224 else
225 oss << QString("%1: <b>%2</b> (%3)").arg(q_("Type"), getTypeString(), mt) << "<br>";
226 oss << getExtraInfoStrings(ObjectType).join("");
227 }
228
229 oss << getMagnitudeInfoString(core, flags, 2);
230
231 if (flags&Extra)
232 {
233 if (vMag < 50 && bMag < 50)
234 oss << QString("%1: <b>%2</b>").arg(q_("Color Index (B-V)"), QString::number(bMag-vMag, 'f', 2)) << "<br />";
235 }
236 float mmag = qMin(vMag,bMag);
237 if (nType != NebDn && mmag < 50 && flags&Extra)
238 {
239 QString sb = q_("Surface brightness");
240 QString ae = q_("after extinction");
241 QString mu;
242 if (flagUseShortNotationSurfaceBrightness)
243 {
244 mu = QString("<sup>m</sup>/□'");
245 if (flagUseArcsecSurfaceBrightness)
246 mu = QString("<sup>m</sup>/□\"");
247 }
248 else
249 {
250 mu = QString("%1/%2<sup>2</sup>").arg(qc_("mag", "magnitude"), q_("arc-min"));
251 if (flagUseArcsecSurfaceBrightness)
252 mu = QString("%1/%2<sup>2</sup>").arg(qc_("mag", "magnitude"), q_("arc-sec"));
253 }
254
255 if (getSurfaceBrightness(core)<99.f)
256 {
257 if (getAirmass(core)>-1.f && getSurfaceBrightnessWithExtinction(core)<99.f) // Don't show extincted surface brightness much below horizon where model is meaningless.
258 {
259 oss << QString("%1: <b>%2</b> %5 (%3: <b>%4</b> %5)").arg(sb, QString::number(getSurfaceBrightness(core, flagUseArcsecSurfaceBrightness), 'f', 2),
260 ae, QString::number(getSurfaceBrightnessWithExtinction(core, flagUseArcsecSurfaceBrightness), 'f', 2), mu) << "<br />";
261 }
262 else
263 oss << QString("%1: <b>%2</b> %3").arg(sb, QString::number(getSurfaceBrightness(core, flagUseArcsecSurfaceBrightness), 'f', 2), mu) << "<br />";
264
265 if (getContrastIndex(core)<99.f)
266 oss << QString("%1: %2").arg(q_("Contrast index"), QString::number(getContrastIndex(core), 'f', 2)) << "<br />";
267 }
268 }
269
270 oss << getCommonInfoString(core, flags);
271
272 if (flags&Size && majorAxisSize>0.f)
273 {
274 QString majorAxS, minorAxS, sizeAx = q_("Size");
275 if (withDecimalDegree)
276 {
277 majorAxS = StelUtils::radToDecDegStr(static_cast<double>(majorAxisSize)*M_PI/180., 5, false, true);
278 minorAxS = StelUtils::radToDecDegStr(static_cast<double>(minorAxisSize)*M_PI/180., 5, false, true);
279 }
280 else
281 {
282 majorAxS = StelUtils::radToDmsPStr(static_cast<double>(majorAxisSize)*M_PI/180., 2);
283 minorAxS = StelUtils::radToDmsPStr(static_cast<double>(minorAxisSize)*M_PI/180., 2);
284 }
285
286 if (fuzzyEquals(majorAxisSize, minorAxisSize) || minorAxisSize==0.f)
287 oss << QString("%1: %2").arg(sizeAx, majorAxS) << "<br />";
288 else
289 {
290 oss << QString("%1: %2 x %3").arg(sizeAx, majorAxS, minorAxS) << "<br />";
291 if (orientationAngle>0)
292 oss << QString("%1: %2%3").arg(q_("Orientation angle")).arg(orientationAngle).arg(QChar(0x00B0)) << "<br />";
293 }
294 }
295 if (flags&Size)
296 oss << getExtraInfoStrings(Size).join("");
297
298 if (flags&Distance)
299 {
300 float distance, distanceErr, distanceLY, distanceErrLY;
301 if (qAbs(parallax)>0.f)
302 {
303 QString dx;
304 // distance in light years from parallax
305 distance = 3.162e-5f/(qAbs(parallax)*4.848e-9f);
306 distanceErr = 0.f;
307
308 if (parallaxErr>0.f)
309 distanceErr = qAbs(3.162e-5f/(qAbs(parallaxErr + parallax)*4.848e-9f) - distance);
310
311 if (distanceErr>0.f)
312 dx = QString("%1%2%3").arg(QString::number(distance, 'f', 3)).arg(QChar(0x00B1)).arg(QString::number(distanceErr, 'f', 3));
313 else
314 dx = QString("%1").arg(QString::number(distance, 'f', 3));
315
316 if (oDistance==0.f)
317 {
318 // TRANSLATORS: Unit of measure for distance - Light Years
319 QString ly = qc_("ly", "distance");
320 oss << QString("%1: %2 %3").arg(q_("Distance"), dx, ly) << "<br />";
321 }
322 }
323
324 if (oDistance>0.f)
325 {
326 QString dx, dy;
327 float dc = 3262.f;
328 int ms = 1;
329 //TRANSLATORS: Unit of measure for distance - kiloparsecs
330 QString dupc = qc_("kpc", "distance");
331 //TRANSLATORS: Unit of measure for distance - Light Years
332 QString duly = qc_("ly", "distance");
333
334 distance = oDistance;
335 distanceErr = oDistanceErr;
336 distanceLY = oDistance*dc;
337 distanceErrLY= oDistanceErr*dc;
338 if (oDistance>=1000.f)
339 {
340 distance = oDistance/1000.f;
341 distanceErr = oDistanceErr/1000.f;
342 //TRANSLATORS: Unit of measure for distance - Megaparsecs
343 dupc = qc_("Mpc", "distance");
344 }
345
346 if (distanceLY>=1e6f)
347 {
348 distanceLY /= 1e6f;
349 distanceErrLY /= 1e6f;
350 ms = 3;
351 //TRANSLATORS: Unit of measure for distance - Millions of Light Years
352 duly = qc_("M ly", "distance");
353 }
354
355 if (oDistanceErr>0.f)
356 {
357 dx = QString("%1%2%3").arg(QString::number(distance, 'f', 3)).arg(QChar(0x00B1)).arg(QString::number(distanceErr, 'f', 3));
358 dy = QString("%1%2%3").arg(QString::number(distanceLY, 'f', ms)).arg(QChar(0x00B1)).arg(QString::number(distanceErrLY, 'f', ms));
359 }
360 else
361 {
362 dx = QString("%1").arg(QString::number(distance, 'f', 3));
363 dy = QString("%1").arg(QString::number(distanceLY, 'f', ms));
364 }
365
366 oss << QString("%1: %2 %3 (%4 %5)").arg(q_("Distance"), dx, dupc, dy, duly) << "<br />";
367 }
368 oss << getExtraInfoStrings(Distance).join("");
369 }
370
371 if (flags&Extra)
372 {
373 if (redshift<99.f)
374 {
375 QString z;
376 if (redshiftErr>0.f)
377 z = QString("%1%2%3").arg(QString::number(redshift, 'f', 6)).arg(QChar(0x00B1)).arg(QString::number(redshiftErr, 'f', 6));
378 else
379 z = QString("%1").arg(QString::number(redshift, 'f', 6));
380
381 oss << QString("%1: %2").arg(q_("Redshift"), z) << "<br />";
382 }
383 if (qAbs(parallax)>0.f)
384 {
385 QString px;
386 if (parallaxErr>0.f)
387 px = QString("%1%2%3").arg(QString::number(qAbs(parallax), 'f', 3)).arg(QChar(0x00B1)).arg(QString::number(parallaxErr, 'f', 3));
388 else
389 px = QString("%1").arg(QString::number(qAbs(parallax), 'f', 3));
390
391 oss << QString("%1: %2 %3").arg(q_("Parallax"), px, qc_("mas", "parallax")) << "<br />";
392 }
393 if (!getMorphologicalTypeDescription().isEmpty())
394 oss << QString("%1: %2.").arg(q_("Morphological description"), getMorphologicalTypeDescription()) << "<br />";
395 }
396
397 oss << getSolarLunarInfoString(core, flags);
398
399 postProcessInfoString(str, flags);
400
401 return str;
402 }
403
getInfoMap(const StelCore * core) const404 QVariantMap Nebula::getInfoMap(const StelCore *core) const
405 {
406 QVariantMap map = StelObject::getInfoMap(core);
407
408 map["type"]=getTypeString(); // replace "Nebula" type by detail. This is localized. Maybe add argument such as getTypeString(bool translated=true)?
409 map.insert("morpho", getMorphologicalTypeString());
410 map.insert("surface-brightness", getSurfaceBrightness(core));
411 map.insert("designations", withoutID ? QString() : designations.join(" - "));
412 map.insert("bmag", bMag);
413 if (vMag < 50 && bMag < 50)
414 map.insert("bV", bMag-vMag);
415 if (redshift<99.f)
416 map.insert("redshift", redshift);
417
418 // TODO: more? Names? Data?
419 return map;
420 }
421
getEnglishAliases() const422 QString Nebula::getEnglishAliases() const
423 {
424 QString aliases = "";
425 int asize = englishAliases.size();
426 if (asize!=0)
427 {
428 if (asize>2) // Special case for many AKA
429 {
430 bool firstLine = true;
431 for(int i=1; i<=asize; i++)
432 {
433 aliases.append(englishAliases.at(i-1));
434 if (i<asize)
435 aliases.append(" - ");
436
437 if ((i % 2)==0 && firstLine) // 2 AKA-items on first line!
438 {
439 aliases.append("<br />");
440 firstLine = false;
441 }
442 if (i>3 && ((i-2) % 4)==0 && !firstLine && i<asize)
443 aliases.append("<br />");
444 }
445 }
446 else
447 aliases = nameI18Aliases.join(" - ");
448 }
449 return aliases;
450 }
451
getI18nAliases() const452 QString Nebula::getI18nAliases() const
453 {
454 QString aliases = "";
455 int asize = nameI18Aliases.size();
456 if (asize!=0)
457 {
458 if (asize>2) // Special case for many AKA; NOTE: Should we add size to the config data for skyculture?
459 {
460 bool firstLine = true;
461 for(int i=1; i<=asize; i++)
462 {
463 aliases.append(nameI18Aliases.at(i-1));
464 if (i<asize)
465 aliases.append(" - ");
466
467 if ((i % 2)==0 && firstLine) // 2 AKA-items on first line!
468 {
469 aliases.append("<br />");
470 firstLine = false;
471 }
472 if (i>3 && ((i-2) % 4)==0 && !firstLine && i<asize)
473 aliases.append("<br />");
474 }
475 }
476 else
477 aliases = nameI18Aliases.join(" - ");
478 }
479 return aliases;
480 }
481
getVMagnitude(const StelCore * core) const482 float Nebula::getVMagnitude(const StelCore* core) const
483 {
484 Q_UNUSED(core)
485 return vMag;
486 }
487
getBMagnitude(const StelCore * core) const488 float Nebula::getBMagnitude(const StelCore* core) const
489 {
490 Q_UNUSED(core)
491 return bMag;
492 }
493
getBMagnitudeWithExtinction(const StelCore * core) const494 float Nebula::getBMagnitudeWithExtinction(const StelCore* core) const
495 {
496 Vec3d altAzPos = getAltAzPosGeometric(core);
497 altAzPos.normalize();
498 float mag = getBMagnitude(core);
499 // without the test, planets flicker stupidly in fullsky atmosphere-less view.
500 if (core->getSkyDrawer()->getFlagHasAtmosphere())
501 core->getSkyDrawer()->getExtinction().forward(altAzPos, &mag);
502 return mag;
503 }
504
getAngularSize(const StelCore *) const505 double Nebula::getAngularSize(const StelCore *) const
506 {
507 float size = majorAxisSize;
508 if (!fuzzyEquals(majorAxisSize, minorAxisSize) || minorAxisSize>0)
509 size = (majorAxisSize+minorAxisSize)*0.5f;
510 return static_cast<double>(size);
511 }
512
getSelectPriority(const StelCore * core) const513 float Nebula::getSelectPriority(const StelCore* core) const
514 {
515 float selectPriority = StelObject::getSelectPriority(core);
516 const NebulaMgr* nebMgr = (static_cast<NebulaMgr*>(StelApp::getInstance().getModuleMgr().getModule("NebulaMgr")));
517 // minimize unwanted selection of the deep-sky objects
518 if (!nebMgr->getFlagHints())
519 return selectPriority+3.f;
520
521 float mag = qMin(getVMagnitude(core), getBMagnitude(core));
522 float lim = mag;
523 float mLim = 15.0f;
524
525 if (nType==NebRegion) // special case for regions
526 mag = 3.f;
527
528 if (!objectInDisplayedCatalog() || !objectInDisplayedType())
529 return selectPriority+mLim;
530
531 const StelSkyDrawer* drawer = core->getSkyDrawer();
532
533 if (drawer->getFlagNebulaMagnitudeLimit() && (mag>static_cast<float>(drawer->getCustomNebulaMagnitudeLimit())))
534 return selectPriority+mLim;
535
536 const float maxMagHint = nebMgr->computeMaxMagHint(drawer);
537 // make very easy to select if labeled
538 if (surfaceBrightnessUsage)
539 {
540 lim = mag = getSurfaceBrightness(core);
541 mLim += 1.f;
542 }
543
544 if (nType==NebDn)
545 lim=mLim - mag - 2.0f*qMin(1.5f, majorAxisSize); // Note that "mag" field is used for opacity in this catalog!
546 else if (nType==NebHII) // Sharpless and LBN
547 lim=10.0f - 2.0f*qMin(1.5f, majorAxisSize); // Unfortunately, in Sh catalog, we always have mag=99=unknown!
548
549 if (std::min(mLim, lim)<=maxMagHint || outlineSegments.size()>0 || nType==NebRegion) // High priority for big DSO (with outlines) or regions
550 selectPriority = -10.f;
551 else
552 selectPriority -= 5.f;
553
554 return selectPriority;
555 }
556
getInfoColor(void) const557 Vec3f Nebula::getInfoColor(void) const
558 {
559 return (static_cast<NebulaMgr*>(StelApp::getInstance().getModuleMgr().getModule("NebulaMgr")))->getLabelsColor();
560 }
561
getCloseViewFov(const StelCore *) const562 double Nebula::getCloseViewFov(const StelCore*) const
563 {
564 return majorAxisSize>0.f ? static_cast<double>(majorAxisSize) * 4. : 1.;
565 }
566
getSurfaceBrightness(const StelCore * core,bool arcsec) const567 float Nebula::getSurfaceBrightness(const StelCore* core, bool arcsec) const
568 {
569 const float sq = (arcsec ? 3600.f*3600.f : 3600.f); // arcsec^2 or arcmin^2
570 const float mag = qMin(getVMagnitude(core), getBMagnitude(core));
571 if (mag<99.f && majorAxisSize>0.f && nType!=NebDn)
572 return mag + 2.5f*log10f(getSurfaceArea()*sq);
573 else
574 return 99.f;
575 }
576
getSurfaceBrightnessWithExtinction(const StelCore * core,bool arcsec) const577 float Nebula::getSurfaceBrightnessWithExtinction(const StelCore* core, bool arcsec) const
578 {
579 const float sq = (arcsec ? 3600.f*3600.f : 3600.f); // arcsec^2 or arcmin^2
580 const float mag = qMin(getVMagnitudeWithExtinction(core), getBMagnitudeWithExtinction(core));
581 if (mag<99.f && majorAxisSize>0.f && nType!=NebDn)
582 return mag + 2.5f*log10f(getSurfaceArea()*sq);
583 else
584 return 99.f;
585 }
586
getContrastIndex(const StelCore * core) const587 float Nebula::getContrastIndex(const StelCore* core) const
588 {
589 // Compute an extended object's contrast index: http://www.unihedron.com/projects/darksky/NELM2BCalc.html
590
591 // Sky brightness
592 // Source: Schaefer, B.E. Feb. 1990. Telescopic Limiting Magnitude. PASP 102:212-229
593 // URL: http://adsbit.harvard.edu/cgi-bin/nph-iarticle_query?bibcode=1990PASP..102..212S [1990PASP..102..212S]
594 const float B_mpsas = 21.58f - 5*log10(std::pow(10.f, 1.586f - static_cast<float>(core->getSkyDrawer()->getNELMFromBortleScale())*0.2f)-1);
595 // Compute an extended object's contrast index
596 // Source: Clark, R.N., 1990. Appendix E in Visual Astronomy of the Deep Sky, Cambridge University Press and Sky Publishing.
597 // URL: http://www.clarkvision.com/visastro/appendix-e.html
598 const float emag = getSurfaceBrightnessWithExtinction(core, true);
599 if (emag<99.f)
600 return -0.4f * (emag - B_mpsas);
601 else
602 return 99.f;
603 }
604
getSurfaceArea(void) const605 float Nebula::getSurfaceArea(void) const
606 {
607 if (minorAxisSize==0.f)
608 return M_PIf*(majorAxisSize/2.f)*(majorAxisSize/2.f); // S = pi*R^2 = pi*(D/2)^2
609 else
610 return M_PIf*(majorAxisSize/2.f)*(minorAxisSize/2.f); // S = pi*a*b
611 }
612
getHintColor(Nebula::NebulaType nType)613 Vec3f Nebula::getHintColor(Nebula::NebulaType nType)
614 {
615 return hintColorMap.value(nType, hintColorMap.value(NebUnknown));
616 }
617
getVisibilityLevelByMagnitude(void) const618 float Nebula::getVisibilityLevelByMagnitude(void) const
619 {
620 StelCore* core = StelApp::getInstance().getCore();
621
622 float lim = qMin(vMag, bMag);
623 float mLim = 15.0f;
624
625 if (surfaceBrightnessUsage)
626 {
627 lim = getSurfaceBrightness(core) - 3.f;
628 if (lim > 90.f) lim = mLim + 1.f;
629 }
630 else
631 {
632 float mag = getVMagnitude(core);
633 if (lim > 90.f) lim = mLim;
634
635 // Dark nebulae. Not sure how to assess visibility from opacity? --GZ
636 if (nType==NebDn)
637 {
638 // GZ: ad-hoc visibility formula: assuming good visibility if objects of mag9 are visible, "usual" opacity 5 and size 30', better visibility (discernability) comes with higher opacity and larger size,
639 // 9-(opac-5)-2*(angularSize-0.5)
640 // GZ Not good for non-Barnards. weak opacity and large surface are antagonists. (some LDN are huge, but opacity 2 is not much to discern).
641 // The qMin() maximized the visibility gain for large objects.
642 if (majorAxisSize>0.f && mag<90.f)
643 lim = mLim - mag - 2.0f*qMin(majorAxisSize, 1.5f);
644 else
645 lim = (B_nb>0 ? 9.0f : 12.0f); // GZ I assume LDN objects are rather elusive.
646 }
647 else if (nType==NebHII) // NebHII={Sharpless, LBN, RCW}
648 { // artificially increase visibility of (most) Sharpless objects? No magnitude recorded:-(
649 lim=9.0f;
650 }
651 }
652
653 if (nType==NebRegion) // special case for regions
654 lim=3.0f;
655
656 return lim;
657 }
658
drawOutlines(StelPainter & sPainter,float maxMagHints) const659 void Nebula::drawOutlines(StelPainter &sPainter, float maxMagHints) const
660 {
661 size_t segments = outlineSegments.size();
662 Vec3f color = getHintColor(nType);
663
664 // tune limits for outlines
665 float oLim = getVisibilityLevelByMagnitude() - 3.f;
666
667 float lum = 1.f;
668 Vec3f col(color*lum*hintsBrightness);
669 if (!objectInDisplayedType())
670 col.set(0.f,0.f,0.f);
671 sPainter.setColor(col, 1);
672
673 StelCore *core=StelApp::getInstance().getCore();
674 Vec3d vel=core->getCurrentPlanet()->getHeliocentricEclipticVelocity();
675 vel=StelCore::matVsop87ToJ2000*vel;
676 vel*=core->getAberrationFactor() * (AU/(86400.0*SPEED_OF_LIGHT));
677
678 // Show outlines
679 if (segments>0 && flagUseOutlines && oLim<=maxMagHints)
680 {
681 unsigned int i, j;
682 std::vector<Vec3d> *points;
683
684 sPainter.setBlending(true);
685 sPainter.setLineSmooth(true);
686 const SphericalCap& viewportHalfspace = sPainter.getProjector()->getBoundingCap();
687
688 for (i=0;i<segments;i++)
689 {
690 points = outlineSegments[i];
691
692 for (j=0;j<points->size()-1;j++)
693 {
694 Vec3d point1=points->at(j);
695 Vec3d point2=points->at(j+1);
696 if (core->getUseAberration())
697 {
698 point1+=vel;
699 point1.normalize();
700 point2+=vel;
701 point2.normalize();
702 }
703 sPainter.drawGreatCircleArc(point1, point2, &viewportHalfspace);
704 }
705 }
706 sPainter.setLineSmooth(false);
707 }
708 }
709
drawHints(StelPainter & sPainter,float maxMagHints,StelCore * core) const710 void Nebula::drawHints(StelPainter& sPainter, float maxMagHints, StelCore *core) const
711 {
712 size_t segments = outlineSegments.size();
713 if (segments>0 && flagUseOutlines)
714 return;
715 Vec3d win;
716 // Check visibility of DSO hints
717 if (!(sPainter.getProjector()->projectCheck(XYZ, win)))
718 return;
719
720 if (getVisibilityLevelByMagnitude()>maxMagHints)
721 return;
722
723 Vec3f color = getHintColor(nType);
724
725 const float size = 6.0f;
726 float scaledSize = 0.0f;
727 if (drawHintProportional)
728 scaledSize = static_cast<float>(getAngularSize(Q_NULLPTR)) *M_PI_180f*static_cast<float>(sPainter.getProjector()->getPixelPerRadAtCenter());
729 float finalSize=qMax(size, scaledSize);
730
731 switch (nType)
732 {
733 case NebGx:
734 case NebIGx:
735 case NebAGx:
736 case NebQSO:
737 case NebPossQSO:
738 case NebBLL:
739 case NebBLA:
740 case NebRGx:
741 case NebGxCl:
742 if (finalSize > 35.f)
743 Nebula::texGalaxyLarge->bind();
744 else
745 Nebula::texGalaxy->bind();
746 break;
747 case NebOc:
748 case NebSA:
749 case NebSC:
750 case NebCl:
751 if (finalSize > 75.f)
752 Nebula::texOpenClusterXLarge->bind();
753 else if (finalSize > 35.f)
754 Nebula::texOpenClusterLarge->bind();
755 else
756 Nebula::texOpenCluster->bind();
757 break;
758 case NebGc:
759 if (finalSize > 35.f)
760 Nebula::texGlobularClusterLarge->bind();
761 else
762 Nebula::texGlobularCluster->bind();
763 break;
764 case NebN:
765 case NebHII:
766 case NebMolCld:
767 case NebYSO:
768 case NebRn:
769 case NebSNR:
770 case NebBn:
771 case NebEn:
772 case NebSNC:
773 case NebSNRC:
774 if (finalSize > 75.f)
775 Nebula::texDiffuseNebulaXLarge->bind();
776 else if (finalSize > 35.f)
777 Nebula::texDiffuseNebulaLarge->bind();
778 else
779 Nebula::texDiffuseNebula->bind();
780 break;
781 case NebPn:
782 case NebPossPN:
783 case NebPPN:
784 Nebula::texPlanetaryNebula->bind();
785 break;
786 case NebDn:
787 if (finalSize > 35.f)
788 Nebula::texDarkNebulaLarge->bind();
789 else
790 Nebula::texDarkNebula->bind();
791 break;
792 case NebCn:
793 if (finalSize > 35.f)
794 Nebula::texOpenClusterWithNebulosityLarge->bind();
795 else
796 Nebula::texOpenClusterWithNebulosity->bind();
797 break;
798 case NebRegion:
799 finalSize = size*2.f;
800 Nebula::texRegion->bind();
801 break;
802 //case NebEMO:
803 //case NebStar:
804 //case NebSymbioticStar:
805 //case NebEmissionLineStar:
806 default:
807 if (finalSize > 35.f)
808 Nebula::texCircleLarge->bind();
809 else
810 Nebula::texCircle->bind();
811 }
812
813 float lum = 1.f;
814 Vec3f col(color*lum*hintsBrightness);
815 if (!objectInDisplayedType())
816 col.set(0.f,0.f,0.f);
817
818 sPainter.setColor(col, 1);
819 sPainter.setBlending(true, GL_ONE, GL_ONE);
820
821 // Rotation looks good only for galaxies.
822 if ((nType <=NebQSO) || (nType==NebBLA) || (nType==NebBLL) )
823 {
824 // The rotation angle in drawSprite2dMode() is relative to screen. Make sure to compute correct angle from 90+orientationAngle.
825 // Find an on-screen direction vector from a point offset somewhat in declination from our object.
826 Vec3d XYZrel(getJ2000EquatorialPos(core));
827 XYZrel[2]*=0.95; XYZrel.normalize();
828 Vec3d XYrel;
829 sPainter.getProjector()->project(XYZrel, XYrel);
830 float screenAngle = static_cast<float>(atan2(XYrel[1]-XY[1], XYrel[0]-XY[0]));
831 sPainter.drawSprite2dMode(static_cast<float>(XY[0]), static_cast<float>(XY[1]), finalSize, screenAngle*M_180_PIf + orientationAngle);
832 }
833 else // no galaxy
834 sPainter.drawSprite2dMode(static_cast<float>(XY[0]), static_cast<float>(XY[1]), finalSize);
835 }
836
drawLabel(StelPainter & sPainter,float maxMagLabel) const837 void Nebula::drawLabel(StelPainter& sPainter, float maxMagLabel) const
838 {
839 Vec3d win;
840 // Check visibility of DSO labels
841 if (!(sPainter.getProjector()->projectCheck(XYZ, win)))
842 return;
843
844 if (getVisibilityLevelByMagnitude()>maxMagLabel)
845 return;
846
847 sPainter.setColor(labelColor, objectInDisplayedType() ? hintsBrightness : 0.f);
848
849 const float size = static_cast<float>(getAngularSize(Q_NULLPTR))*M_PI_180f*sPainter.getProjector()->getPixelPerRadAtCenter();
850 const float shift = 5.f + (drawHintProportional ? size*0.9f : 0.f);
851
852 QString str = getNameI18n();
853 if (str.isEmpty() || designationUsage)
854 str = getDSODesignation();
855
856 sPainter.drawText(static_cast<float>(XY[0])+shift, static_cast<float>(XY[1])+shift, str, 0, 0, 0, false);
857 }
858
getDSODesignation() const859 QString Nebula::getDSODesignation() const
860 {
861 QString str = "";
862 // Get designation for DSO with priority as given here.
863 if (catalogFilters&CatM && M_nb>0)
864 str = QString("M %1").arg(M_nb);
865 else if (catalogFilters&CatC && C_nb>0)
866 str = QString("C %1").arg(C_nb);
867 else if (catalogFilters&CatNGC && NGC_nb>0)
868 str = QString("NGC %1").arg(NGC_nb);
869 else if (catalogFilters&CatIC && IC_nb>0)
870 str = QString("IC %1").arg(IC_nb);
871 else if (catalogFilters&CatB && B_nb>0)
872 str = QString("B %1").arg(B_nb);
873 else if (catalogFilters&CatSh2 && Sh2_nb>0)
874 str = QString("SH 2-%1").arg(Sh2_nb);
875 else if (catalogFilters&CatVdB && VdB_nb>0)
876 str = QString("vdB %1").arg(VdB_nb);
877 else if (catalogFilters&CatRCW && RCW_nb>0)
878 str = QString("RCW %1").arg(RCW_nb);
879 else if (catalogFilters&CatLDN && LDN_nb>0)
880 str = QString("LDN %1").arg(LDN_nb);
881 else if (catalogFilters&CatLBN && LBN_nb > 0)
882 str = QString("LBN %1").arg(LBN_nb);
883 else if (catalogFilters&CatCr && Cr_nb > 0)
884 str = QString("Cr %1").arg(Cr_nb);
885 else if (catalogFilters&CatMel && Mel_nb > 0)
886 str = QString("Mel %1").arg(Mel_nb);
887 else if (catalogFilters&CatPGC && PGC_nb > 0)
888 str = QString("PGC %1").arg(PGC_nb);
889 else if (catalogFilters&CatUGC && UGC_nb > 0)
890 str = QString("UGC %1").arg(UGC_nb);
891 else if (catalogFilters&CatCed && !Ced_nb.isEmpty())
892 str = QString("Ced %1").arg(Ced_nb);
893 else if (catalogFilters&CatArp && Arp_nb > 0)
894 str = QString("Arp %1").arg(Arp_nb);
895 else if (catalogFilters&CatVV && VV_nb > 0)
896 str = QString("VV %1").arg(VV_nb);
897 else if (catalogFilters&CatPK && !PK_nb.isEmpty())
898 str = QString("PK %1").arg(PK_nb);
899 else if (catalogFilters&CatPNG && !PNG_nb.isEmpty())
900 str = QString("PN G%1").arg(PNG_nb);
901 else if (catalogFilters&CatSNRG && !SNRG_nb.isEmpty())
902 str = QString("SNR G%1").arg(SNRG_nb);
903 else if (catalogFilters&CatACO && !ACO_nb.isEmpty())
904 str = QString("Abell %1").arg(ACO_nb);
905 else if (catalogFilters&CatHCG && !HCG_nb.isEmpty())
906 str = QString("HCG %1").arg(HCG_nb);
907 else if (catalogFilters&CatESO && !ESO_nb.isEmpty())
908 str = QString("ESO %1").arg(ESO_nb);
909 else if (catalogFilters&CatVdBH && !VdBH_nb.isEmpty())
910 str = QString("vdBH %1").arg(VdBH_nb);
911 else if (catalogFilters&CatDWB && DWB_nb > 0)
912 str = QString("DWB %1").arg(DWB_nb);
913 else if (catalogFilters&CatTr && Tr_nb > 0)
914 str = QString("Tr %1").arg(Tr_nb);
915 else if (catalogFilters&CatSt && St_nb > 0)
916 str = QString("St %1").arg(St_nb);
917 else if (catalogFilters&CatRu && Ru_nb > 0)
918 str = QString("Ru %1").arg(Ru_nb);
919 else if (catalogFilters&CatVdBHa && VdBHa_nb > 0)
920 str = QString("vdB-Ha %1").arg(VdBHa_nb);
921
922 return str;
923 }
924
getDSODesignationWIC() const925 QString Nebula::getDSODesignationWIC() const
926 {
927 if (!withoutID)
928 return designations.first();
929 else
930 return QString();
931 }
932
933
readDSO(QDataStream & in)934 void Nebula::readDSO(QDataStream &in)
935 {
936 float ra, dec;
937 unsigned int oType;
938
939 in >> DSO_nb >> ra >> dec >> bMag >> vMag >> oType >> mTypeString >> majorAxisSize >> minorAxisSize
940 >> orientationAngle >> redshift >> redshiftErr >> parallax >> parallaxErr >> oDistance >> oDistanceErr
941 >> NGC_nb >> IC_nb >> M_nb >> C_nb >> B_nb >> Sh2_nb >> VdB_nb >> RCW_nb >> LDN_nb >> LBN_nb >> Cr_nb
942 >> Mel_nb >> PGC_nb >> UGC_nb >> Ced_nb >> Arp_nb >> VV_nb >> PK_nb >> PNG_nb >> SNRG_nb >> ACO_nb
943 >> HCG_nb >> ESO_nb >> VdBH_nb >> DWB_nb >> Tr_nb >> St_nb >> Ru_nb >> VdBHa_nb;
944
945 const unsigned int f = NGC_nb + IC_nb + M_nb + C_nb + B_nb + Sh2_nb + VdB_nb + RCW_nb + LDN_nb + LBN_nb + Cr_nb + Mel_nb + PGC_nb + UGC_nb + Arp_nb + VV_nb + DWB_nb + Tr_nb + St_nb + Ru_nb + VdBHa_nb;
946 if (f==0 && Ced_nb.isEmpty() && PK_nb.isEmpty() && PNG_nb.isEmpty() && SNRG_nb.isEmpty() && ACO_nb.isEmpty() && HCG_nb.isEmpty() && ESO_nb.isEmpty() && VdBH_nb.isEmpty())
947 withoutID = true;
948
949 if (M_nb > 0) designations << QString("M %1").arg(M_nb);
950 if (C_nb > 0) designations << QString("C %1").arg(C_nb);
951 if (NGC_nb > 0) designations << QString("NGC %1").arg(NGC_nb);
952 if (IC_nb > 0) designations << QString("IC %1").arg(IC_nb);
953 if (B_nb > 0) designations << QString("B %1").arg(B_nb);
954 if (Sh2_nb > 0) designations << QString("SH 2-%1").arg(Sh2_nb);
955 if (VdB_nb > 0) designations << QString("vdB %1").arg(VdB_nb);
956 if (RCW_nb > 0) designations << QString("RCW %1").arg(RCW_nb);
957 if (LDN_nb > 0) designations << QString("LDN %1").arg(LDN_nb);
958 if (LBN_nb > 0) designations << QString("LBN %1").arg(LBN_nb);
959 if (Cr_nb > 0) designations << QString("Cr %1").arg(Cr_nb);
960 if (Mel_nb > 0) designations << QString("Mel %1").arg(Mel_nb);
961 if (PGC_nb > 0) designations << QString("PGC %1").arg(PGC_nb);
962 if (UGC_nb > 0) designations << QString("UGC %1").arg(UGC_nb);
963 if (!Ced_nb.isEmpty()) designations << QString("Ced %1").arg(Ced_nb);
964 if (Arp_nb > 0) designations << QString("Arp %1").arg(Arp_nb);
965 if (VV_nb > 0) designations << QString("VV %1").arg(VV_nb);
966 if (!PK_nb.isEmpty()) designations << QString("PK %1").arg(PK_nb);
967 if (!PNG_nb.isEmpty()) designations << QString("PN G%1").arg(PNG_nb);
968 if (!SNRG_nb.isEmpty()) designations << QString("SNR G%1").arg(SNRG_nb);
969 if (!ACO_nb.isEmpty()) designations << QString("Abell %1").arg(ACO_nb);
970 if (!HCG_nb.isEmpty()) designations << QString("HCG %1").arg(HCG_nb);
971 if (!ESO_nb.isEmpty()) designations << QString("ESO %1").arg(ESO_nb);
972 if (!VdBH_nb.isEmpty()) designations << QString("vdBH %1").arg(VdBH_nb);
973 if (DWB_nb > 0) designations << QString("DWB %1").arg(DWB_nb);
974 if (Tr_nb > 0) designations << QString("Tr %1").arg(Tr_nb);
975 if (St_nb > 0) designations << QString("St %1").arg(St_nb);
976 if (Ru_nb > 0) designations << QString("Ru %1").arg(Ru_nb);
977 if (VdBHa_nb > 0) designations << QString("vdB-Ha %1").arg(VdBHa_nb);
978
979 StelUtils::spheToRect(ra,dec,XYZ);
980 Q_ASSERT(fabs(XYZ.lengthSquared()-1.)<1e-9);
981 nType = static_cast<Nebula::NebulaType>(oType);
982 pointRegion = SphericalRegionP(new SphericalPoint(getJ2000EquatorialPos(Q_NULLPTR)));
983 }
984
objectInDisplayedType() const985 bool Nebula::objectInDisplayedType() const
986 {
987 if (!flagUseTypeFilters)
988 return true;
989
990 int cntype = -1;
991 switch (nType)
992 {
993 case NebGx:
994 cntype = 0; // Galaxies
995 break;
996 case NebAGx:
997 case NebRGx:
998 case NebQSO:
999 case NebPossQSO:
1000 case NebBLL:
1001 case NebBLA:
1002 cntype = 1; // Active Galaxies
1003 break;
1004 case NebIGx:
1005 cntype = 2; // Interacting Galaxies
1006 break;
1007 case NebOc:
1008 case NebCl:
1009 case NebSA:
1010 case NebSC:
1011 cntype = 3; // Open Star Clusters
1012 break;
1013 case NebHII:
1014 case NebISM:
1015 cntype = 4; // Hydrogen regions (include interstellar matter)
1016 break;
1017 case NebN:
1018 case NebBn:
1019 case NebEn:
1020 case NebRn:
1021 cntype = 5; // Bright Nebulae
1022 break;
1023 case NebDn:
1024 case NebMolCld:
1025 case NebYSO:
1026 cntype = 6; // Dark Nebulae
1027 break;
1028 case NebPn:
1029 case NebPossPN:
1030 case NebPPN:
1031 cntype = 7; // Planetary Nebulae
1032 break;
1033 case NebSNR:
1034 case NebSNC:
1035 case NebSNRC:
1036 cntype = 8; // Supernova Remnants
1037 break;
1038 case NebCn:
1039 cntype = 9;
1040 break;
1041 case NebGxCl:
1042 cntype = 10;
1043 break;
1044 case NebGc:
1045 cntype = 11;
1046 break;
1047 default:
1048 cntype = 12;
1049 break;
1050 }
1051 bool r = ( (typeFilters&TypeGalaxies && cntype==0)
1052 || (typeFilters&TypeActiveGalaxies && cntype==1)
1053 || (typeFilters&TypeInteractingGalaxies && cntype==2)
1054 || (typeFilters&TypeOpenStarClusters && cntype==3)
1055 || (typeFilters&TypeGlobularStarClusters && cntype==11)
1056 || (typeFilters&TypeHydrogenRegions && cntype==4)
1057 || (typeFilters&TypeBrightNebulae && cntype==5)
1058 || (typeFilters&TypeDarkNebulae && cntype==6)
1059 || (typeFilters&TypePlanetaryNebulae && cntype==7)
1060 || (typeFilters&TypeSupernovaRemnants && cntype==8)
1061 || (typeFilters&TypeOpenStarClusters && (typeFilters&TypeBrightNebulae || typeFilters&TypeHydrogenRegions) && cntype==9)
1062 || (typeFilters&TypeGalaxyClusters && cntype==10)
1063 || (typeFilters&TypeOther && cntype==12));
1064
1065 return r;
1066 }
1067
objectInDisplayedCatalog() const1068 bool Nebula::objectInDisplayedCatalog() const
1069 {
1070 bool r = ( ((catalogFilters&CatM) && (M_nb>0))
1071 || ((catalogFilters&CatC) && (C_nb>0))
1072 || ((catalogFilters&CatNGC) && (NGC_nb>0))
1073 || ((catalogFilters&CatIC) && (IC_nb>0))
1074 || ((catalogFilters&CatB) && (B_nb>0))
1075 || ((catalogFilters&CatSh2) && (Sh2_nb>0))
1076 || ((catalogFilters&CatVdB) && (VdB_nb>0))
1077 || ((catalogFilters&CatRCW) && (RCW_nb>0))
1078 || ((catalogFilters&CatLDN) && (LDN_nb>0))
1079 || ((catalogFilters&CatLBN) && (LBN_nb>0))
1080 || ((catalogFilters&CatCr) && (Cr_nb>0))
1081 || ((catalogFilters&CatMel) && (Mel_nb>0))
1082 || ((catalogFilters&CatPGC) && (PGC_nb>0))
1083 || ((catalogFilters&CatUGC) && (UGC_nb>0))
1084 || ((catalogFilters&CatCed) && !(Ced_nb.isEmpty()))
1085 || ((catalogFilters&CatArp) && (Arp_nb>0))
1086 || ((catalogFilters&CatVV) && (VV_nb>0))
1087 || ((catalogFilters&CatPK) && !(PK_nb.isEmpty()))
1088 || ((catalogFilters&CatPNG) && !(PNG_nb.isEmpty()))
1089 || ((catalogFilters&CatSNRG) && !(SNRG_nb.isEmpty()))
1090 || ((catalogFilters&CatACO) && (!ACO_nb.isEmpty()))
1091 || ((catalogFilters&CatHCG) && (!HCG_nb.isEmpty()))
1092 || ((catalogFilters&CatESO) && (!ESO_nb.isEmpty()))
1093 || ((catalogFilters&CatVdBH) && (!VdBH_nb.isEmpty()))
1094 || ((catalogFilters&CatDWB) && (DWB_nb>0))
1095 || ((catalogFilters&CatTr) && (Tr_nb>0))
1096 || ((catalogFilters&CatSt) && (St_nb>0))
1097 || ((catalogFilters&CatRu ) && (Ru_nb>0))
1098 || ((catalogFilters&CatVdBHa) && (VdBHa_nb>0)))
1099
1100 // Special case: objects without ID from current catalogs
1101 || ((catalogFilters&CatOther) && withoutID);
1102
1103 return r;
1104 }
1105
objectInAllowedSizeRangeLimits(void) const1106 bool Nebula::objectInAllowedSizeRangeLimits(void) const
1107 {
1108 bool r = true;
1109 if (flagUseSizeLimits)
1110 {
1111 const double size = 60. * static_cast<double>(qMax(majorAxisSize, minorAxisSize));
1112 r = (size>=minSizeLimit && size<=maxSizeLimit);
1113 }
1114 return r;
1115 }
1116
getMorphologicalTypeString(void) const1117 QString Nebula::getMorphologicalTypeString(void) const
1118 {
1119 return mTypeString;
1120 }
1121
getMorphologicalTypeDescription(void) const1122 QString Nebula::getMorphologicalTypeDescription(void) const
1123 {
1124 QString m, r = "";
1125
1126 // Let's avoid showing a wrong morphological description for galaxies
1127 // NOTE: Is required the morphological description for galaxies?
1128 if (nType==NebGx || nType==NebAGx || nType==NebRGx || nType==NebIGx || nType==NebQSO || nType==NebPossQSO || nType==NebBLA || nType==NebBLL || nType==NebGxCl)
1129 return QString();
1130
1131 QRegularExpression GlClRx("\\.*(I|II|III|IV|V|VI|VI|VII|VIII|IX|X|XI|XII)\\.*");
1132 int idx = mTypeString.indexOf(GlClRx);
1133 if (idx>0)
1134 m = mTypeString.mid(idx);
1135 else
1136 m = mTypeString;
1137
1138 static const QStringList glclass = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"};
1139
1140 QRegularExpressionMatch GlClMatch=GlClRx.match(m);
1141 if (GlClMatch.hasMatch()) // Globular Clusters
1142 {
1143 switch(glclass.indexOf(GlClMatch.captured(1).trimmed()))
1144 {
1145 case 0:
1146 r = qc_("high concentration of stars toward the center", "Shapley-Sawyer Concentration Class");
1147 break;
1148 case 1:
1149 r = qc_("dense central concentration of stars", "Shapley-Sawyer Concentration Class");
1150 break;
1151 case 2:
1152 r = qc_("strong inner core of stars", "Shapley-Sawyer Concentration Class");
1153 break;
1154 case 3:
1155 r = qc_("intermediate rich concentrations of stars", "Shapley-Sawyer Concentration Class");
1156 break;
1157 case 4:
1158 case 5:
1159 case 6:
1160 r = qc_("intermediate concentrations of stars", "Shapley-Sawyer Concentration Class");
1161 break;
1162 case 7:
1163 r = qc_("rather loose concentration of stars towards the center", "Shapley-Sawyer Concentration Class");
1164 break;
1165 case 8:
1166 r = qc_("loose concentration of stars towards the center", "Shapley-Sawyer Concentration Class");
1167 break;
1168 case 9:
1169 r = qc_("loose concentration of stars", "Shapley-Sawyer Concentration Class");
1170 break;
1171 case 10:
1172 r = qc_("very loose concentration of stars towards the center", "Shapley-Sawyer Concentration Class");
1173 break;
1174 case 11:
1175 r = qc_("almost no concentration towards the center", "Shapley-Sawyer Concentration Class");
1176 break;
1177 default:
1178 r = qc_("undocumented concentration class", "Shapley-Sawyer Concentration Class");
1179 break;
1180 }
1181 }
1182
1183 QRegularExpression OClRx("\\.*(I|II|III|IV)(\\d)(p|m|r)(n*|N*|u*|U*|e*|E*)\\.*");
1184 idx = mTypeString.indexOf(OClRx);
1185 if (idx>0)
1186 m = mTypeString.mid(idx);
1187 else
1188 m = mTypeString;
1189
1190 QRegularExpressionMatch OClMatch=OClRx.match(m);
1191 if (OClMatch.hasMatch()) // Open Clusters
1192 {
1193 QStringList rtxt;
1194 static const QStringList occlass = { "I", "II", "III", "IV"};
1195 static const QStringList ocrich = { "p", "m", "r"};
1196 switch(occlass.indexOf(OClMatch.captured(1).trimmed()))
1197 {
1198 case 0:
1199 rtxt << qc_("strong central concentration of stars", "Trumpler's Concentration Class");
1200 break;
1201 case 1:
1202 rtxt << qc_("little central concentration of stars", "Trumpler's Concentration Class");
1203 break;
1204 case 2:
1205 rtxt << qc_("no noticeable concentration of stars", "Trumpler's Concentration Class");
1206 break;
1207 case 3:
1208 rtxt << qc_("a star field condensation", "Trumpler's Concentration Class");
1209 break;
1210 default:
1211 rtxt << qc_("undocumented concentration class", "Trumpler's Concentration Class");
1212 break;
1213 }
1214 switch(OClMatch.captured(2).toInt())
1215 {
1216 case 1:
1217 rtxt << qc_("small brightness range of cluster members", "Trumpler's Brightness Class");
1218 break;
1219 case 2:
1220 rtxt << qc_("medium brightness range of cluster members", "Trumpler's Brightness Class");
1221 break;
1222 case 3:
1223 rtxt << qc_("large brightness range of cluster members", "Trumpler's Brightness Class");
1224 break;
1225 default:
1226 rtxt << qc_("undocumented brightness range of cluster members", "Trumpler's Brightness Class");
1227 break;
1228 }
1229 switch(ocrich.indexOf(OClMatch.captured(3).trimmed()))
1230 {
1231 case 0:
1232 rtxt << qc_("poor cluster with less than 50 stars", "Trumpler's Number of Members Class");
1233 break;
1234 case 1:
1235 rtxt << qc_("moderately rich cluster with 50-100 stars", "Trumpler's Number of Members Class");
1236 break;
1237 case 2:
1238 rtxt << qc_("rich cluster with more than 100 stars", "Trumpler's Number of Members Class");
1239 break;
1240 default:
1241 rtxt << qc_("undocumented number of members class", "Trumpler's Number of Members Class");
1242 break;
1243 }
1244 if (!OClMatch.captured(4).trimmed().isEmpty())
1245 rtxt << qc_("the cluster lies within nebulosity", "nebulosity factor of open clusters");
1246
1247 r = rtxt.join(",<br />");
1248 }
1249
1250 QRegularExpression VdBRx("\\.*(I|II|I-II|II P|P),\\s+(VBR|VB|BR|M|F|VF|:)\\.*");
1251 idx = mTypeString.indexOf(VdBRx);
1252 if (idx>0)
1253 m = mTypeString.mid(idx);
1254 else
1255 m = mTypeString;
1256
1257 QRegularExpressionMatch VdBMatch=VdBRx.match(m);
1258 if (VdBMatch.hasMatch()) // Reflection Nebulae
1259 {
1260 QStringList rtx;
1261 static const QStringList rnclass = { "I", "II", "I-II", "II P", "P"};
1262 static const QStringList rnbrightness = { "VBR", "VB", "BR", "M", "F", "VF", ":"};
1263 switch(rnbrightness.indexOf(VdBMatch.captured(2).trimmed()))
1264 {
1265 case 0:
1266 case 1:
1267 rtx << qc_("very bright", "Reflection Nebulae Brightness");
1268 break;
1269 case 2:
1270 rtx << qc_("bright", "Reflection Nebulae Brightness");
1271 break;
1272 case 3:
1273 rtx << qc_("moderate brightness", "Reflection Nebulae Brightness");
1274 break;
1275 case 4:
1276 rtx << qc_("faint", "Reflection Nebulae Brightness");
1277 break;
1278 case 5:
1279 rtx << qc_("very faint", "Reflection Nebulae Brightness");
1280 break;
1281 case 6:
1282 rtx << qc_("uncertain brightness", "Reflection Nebulae Brightness");
1283 break;
1284 default:
1285 rtx << qc_("undocumented brightness of reflection nebulae", "Reflection Nebulae Brightness");
1286 break;
1287 }
1288 switch(rnclass.indexOf(VdBMatch.captured(1).trimmed()))
1289 {
1290 case 0:
1291 rtx << qc_("the illuminating star is embedded in the nebulosity", "Reflection Nebulae Classification");
1292 break;
1293 case 1:
1294 rtx << qc_("star is located outside the illuminated nebulosity", "Reflection Nebulae Classification");
1295 break;
1296 case 2:
1297 rtx << qc_("star is located on the corner of the illuminated nebulosity", "Reflection Nebulae Classification");
1298 break;
1299 case 3:
1300 {
1301 // TRANSLATORS: peculiar: odd or unusual, cf. peculiar star, peculiar galaxy
1302 rtx << qc_("star is located outside the illuminated peculiar nebulosity", "Reflection Nebulae Classification");
1303 break;
1304 }
1305 case 4:
1306 {
1307 // TRANSLATORS: peculiar: odd or unusual, cf. peculiar star, peculiar galaxy
1308 rtx << qc_("the illuminated peculiar nebulosity", "Reflection Nebulae Classification");
1309 break;
1310 }
1311 default:
1312 rtx << qc_("undocumented reflection nebulae", "Reflection Nebulae Classification");
1313 break;
1314 }
1315 r = rtx.join(",<br />");
1316 }
1317
1318
1319 QRegularExpression HIIRx("\\.*(\\d+),\\s+(\\d+),\\s+(\\d+)\\.*");
1320 idx = mTypeString.indexOf(HIIRx);
1321 if (idx>0)
1322 m = mTypeString.mid(idx);
1323 else
1324 m = mTypeString;
1325
1326 QRegularExpressionMatch HIIMatch=HIIRx.match(m);
1327 if (HIIMatch.hasMatch()) // HII regions
1328 {
1329 const int form = HIIMatch.captured(1).toInt();
1330 const int structure = HIIMatch.captured(2).toInt();
1331 const int brightness = HIIMatch.captured(3).toInt();
1332 const QStringList formList={
1333 q_("circular form"),
1334 q_("elliptical form"),
1335 q_("irregular form")};
1336 const QStringList structureList={
1337 q_("amorphous structure"),
1338 q_("conventional structure"),
1339 q_("filamentary structure")};
1340 const QStringList brightnessList={
1341 qc_("faintest", "HII region brightness"),
1342 qc_("moderate brightness", "HII region brightness"),
1343 qc_("brightest", "HII region brightness")};
1344
1345 QStringList morph;
1346 morph << formList.value(form-1, q_("undocumented form"));
1347 morph << structureList.value(structure-1, q_("undocumented structure"));
1348 morph << brightnessList.value(brightness-1, q_("undocumented brightness"));
1349
1350 r = morph.join(",<br />");
1351 }
1352
1353 if (nType==NebSNR)
1354 {
1355 QString delim = "";
1356 if (!r.isEmpty())
1357 delim = ";<br />";
1358
1359 if (mTypeString.contains("S") && !mTypeString.contains("S?"))
1360 r = qc_("remnant shows a shell radio structure", "supernova remnant structure classification") + delim + r;
1361
1362 if (mTypeString.contains("F") && !mTypeString.contains("F?"))
1363 r = qc_("remnant shows a filled center ('plerion') radio structure", "supernova remnant structure classification") + delim + r;
1364
1365 if (mTypeString.contains("C") && !mTypeString.contains("C?"))
1366 r = qc_("remnant shows a composite (or combination) radio structure", "supernova remnant structure classification") + delim + r;
1367
1368 if (mTypeString.contains("S?"))
1369 r = qc_("remnant shows a shell radio structure with some uncertainty", "supernova remnant structure classification") + delim + r;
1370
1371 if (mTypeString.contains("F?"))
1372 r = qc_("remnant shows a filled center ('plerion') radio structure with some uncertainty", "supernova remnant structure classification") + delim + r;
1373
1374 if (mTypeString.contains("C?"))
1375 r = qc_("remnant shows a composite (or combination) radio structure with some uncertainty", "supernova remnant structure classification") + delim + r;
1376 }
1377
1378 return r;
1379 }
1380
getTypeString(Nebula::NebulaType nType)1381 QString Nebula::getTypeString(Nebula::NebulaType nType)
1382 {
1383 return typeStringMap.value(nType, q_("undocumented type"));
1384 }
1385
buildTypeStringMap()1386 void Nebula::buildTypeStringMap()
1387 {
1388 Nebula::typeStringMap = {
1389 { NebGx , q_("galaxy") },
1390 { NebAGx , q_("active galaxy") },
1391 { NebRGx , q_("radio galaxy") },
1392 { NebIGx , q_("interacting galaxy") },
1393 { NebQSO , q_("quasar") },
1394 { NebCl , q_("star cluster") },
1395 { NebOc , q_("open star cluster") },
1396 { NebGc , q_("globular star cluster") },
1397 { NebSA , q_("stellar association") },
1398 { NebSC , q_("star cloud") },
1399 { NebN , q_("nebula") },
1400 { NebPn , q_("planetary nebula") },
1401 { NebDn , q_("dark nebula") },
1402 { NebRn , q_("reflection nebula") },
1403 { NebBn , q_("bipolar nebula") },
1404 { NebEn , q_("emission nebula") },
1405 { NebCn , q_("cluster associated with nebulosity") },
1406 { NebHII , q_("HII region") },
1407 { NebSNR , q_("supernova remnant") },
1408 { NebISM , q_("interstellar matter") },
1409 { NebEMO , q_("emission object") },
1410 { NebBLL , q_("BL Lac object") },
1411 { NebBLA , q_("blazar") },
1412 { NebMolCld , q_("molecular cloud") },
1413 { NebYSO , q_("young stellar object") },
1414 { NebPossQSO, q_("possible quasar") },
1415 { NebPossPN , q_("possible planetary nebula") },
1416 { NebPPN , q_("protoplanetary nebula") },
1417 { NebStar , q_("star") },
1418 { NebSymbioticStar , q_("symbiotic star") },
1419 { NebEmissionLineStar, q_("emission-line star") },
1420 { NebSNC , q_("supernova candidate") },
1421 { NebSNRC , q_("supernova remnant candidate") },
1422 { NebGxCl , q_("cluster of galaxies") },
1423 { NebPartOfGx , q_("part of a galaxy") },
1424 { NebRegion , q_("region of the sky") },
1425 { NebUnknown, q_("object of unknown nature") }};
1426 }
1427
getJ2000EquatorialPos(const StelCore * core) const1428 Vec3d Nebula::getJ2000EquatorialPos(const StelCore* core) const
1429 {
1430 if ((core) && (core->getUseAberration()) && (core->getCurrentPlanet()))
1431 {
1432 Vec3d pos=XYZ;
1433 Q_ASSERT_X(fabs(pos.lengthSquared()-1.0)<0.0001, "Nebula aberration", "vertex length not unity");
1434 //pos.normalize(); // Yay - not required!
1435 Vec3d vel=core->getCurrentPlanet()->getHeliocentricEclipticVelocity();
1436 vel=StelCore::matVsop87ToJ2000*vel*core->getAberrationFactor()*(AU/(86400.0*SPEED_OF_LIGHT));
1437 pos+=vel;
1438 pos.normalize();
1439 return pos;
1440 }
1441 else
1442 {
1443 return XYZ;
1444 }
1445 }
1446