File indexing completed on 2024-04-21 03:44:44

0001 /*
0002     SPDX-FileCopyrightText: 2001 Jason Harris <kstars@30doradus.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "starobject.h"
0008 
0009 #include "deepstardata.h"
0010 #include "ksnumbers.h"
0011 #ifndef KSTARS_LITE
0012 #include "kspopupmenu.h"
0013 #endif
0014 #include "kstarsdata.h"
0015 #include "ksutils.h"
0016 #include "Options.h"
0017 #include "skymap.h"
0018 #include "stardata.h"
0019 
0020 #include <typeinfo>
0021 
0022 #ifdef PROFILE_UPDATECOORDS
0023 double StarObject::updateCoordsCpuTime = 0.;
0024 unsigned int StarObject::starsUpdated  = 0;
0025 #include <cstdlib>
0026 #include <ctime>
0027 #endif
0028 
0029 // DEBUG EDIT. Uncomment for testing Proper Motion
0030 //#include "skycomponents/skymesh.h"
0031 // END DEBUG
0032 
0033 #include "skycomponents/skylabeler.h"
0034 
0035 // DEBUG EDIT. Uncomment for testing Proper Motion
0036 // You will also need to uncomment all related blocks
0037 // from this file, starobject.h and also the trixel-boundaries
0038 // block from lines 253 - 257 of skymapcomposite.cpp
0039 //QVector<SkyPoint *> StarObject::Trail;
0040 // END DEBUG
0041 
0042 #include <KLocalizedString>
0043 
0044 //----- Static Methods -----
0045 //
0046 double StarObject::reindexInterval(double pm)
0047 {
0048     if (pm < 1.0e-6)
0049         return 1.0e6;
0050 
0051     // arcminutes * sec/min * milliarcsec/sec centuries/year
0052     // / [milliarcsec/year] = centuries
0053 
0054     return 25.0 * 60.0 * 10.0 / pm;
0055 }
0056 
0057 StarObject::StarObject(dms r, dms d, float m, const QString &n, const QString &n2, const QString &sptype, double pmra,
0058                        double pmdec, double par, bool mult, bool var, int hd)
0059     : SkyObject(SkyObject::STAR, r, d, m, n, n2, QString()), PM_RA(pmra), PM_Dec(pmdec), Parallax(par),
0060       Multiplicity(mult), Variability(var), HD(hd)
0061 {
0062     QByteArray spt = sptype.toLatin1();
0063     SpType[0]      = spt[0];
0064     SpType[1]      = spt[1];
0065 
0066     QString lname;
0067     if (hasName())
0068     {
0069         lname = n;
0070         if (hasName2())
0071             lname += " (" + gname() + ')';
0072     }
0073     else if (hasName2())
0074     {
0075         lname = gname();
0076         //If genetive name exists, but no primary name, set primary name = genetive name.
0077         setName(gname());
0078     }
0079     else if (HD > 0)
0080     {
0081         lname = QLatin1String("HD ") + QString::number(HD);
0082     }
0083     setLongName(lname);
0084     updateID = updateNumID = 0;
0085 }
0086 
0087 StarObject::StarObject(double r, double d, float m, const QString &n, const QString &n2, const QString &sptype,
0088                        double pmra, double pmdec, double par, bool mult, bool var, int hd)
0089     : SkyObject(SkyObject::STAR, r, d, m, n, n2, QString()), PM_RA(pmra), PM_Dec(pmdec), Parallax(par),
0090       Multiplicity(mult), Variability(var), HD(hd)
0091 {
0092     QByteArray spt = sptype.toLatin1();
0093     SpType[0]      = spt[0];
0094     SpType[1]      = spt[1];
0095 
0096     QString lname;
0097     if (hasName())
0098     {
0099         lname = n;
0100         if (hasName2())
0101             lname += " (" + gname() + ')';
0102     }
0103     else if (hasName2())
0104     {
0105         lname = gname();
0106         //If genetive name exists, but no primary name, set primary name = genetive name.
0107         setName(gname());
0108     }
0109     else if (HD > 0)
0110     {
0111         lname = QLatin1String("HD ") + QString::number(HD);
0112     }
0113     setLongName(lname);
0114     updateID = updateNumID = 0;
0115 }
0116 
0117 StarObject::StarObject(const StarObject &o)
0118     : SkyObject(o), PM_RA(o.PM_RA), PM_Dec(o.PM_Dec), Parallax(o.Parallax), Multiplicity(o.Multiplicity),
0119       Variability(o.Variability), HD(o.HD)
0120 {
0121     SpType[0] = o.SpType[0];
0122     SpType[1] = o.SpType[1];
0123     updateID = updateNumID = 0;
0124 }
0125 
0126 StarObject *StarObject::clone() const
0127 {
0128     Q_ASSERT(typeid(this) ==
0129              typeid(static_cast<const StarObject *>(this))); // Ensure we are not slicing a derived class
0130     return new StarObject(*this);
0131 }
0132 
0133 void StarObject::init(const StarData *stardata)
0134 {
0135     double ra, dec;
0136     ra  = stardata->RA / 1000000.0;
0137     dec = stardata->Dec / 100000.0;
0138     setType(SkyObject::STAR);
0139     setMag(stardata->mag / 100.0);
0140     setRA0(ra);
0141     setDec0(dec);
0142     setRA(ra0());
0143     setDec(dec0());
0144     SpType[0]    = stardata->spec_type[0];
0145     SpType[1]    = stardata->spec_type[1];
0146     PM_RA        = stardata->dRA / 10.0;
0147     PM_Dec       = stardata->dDec / 10.0;
0148     Parallax     = stardata->parallax / 10.0;
0149     Multiplicity = stardata->flags & 0x02;
0150     Variability  = stardata->flags & 0x04;
0151     updateID = updateNumID = 0;
0152     HD                     = stardata->HD;
0153     if (HD > 0)
0154         setNames(QString(QLatin1String("HD ") + QString::number(HD)), QString());
0155     B = V = 99.9;
0156 
0157     // DEBUG Edit. For testing proper motion. Uncomment all related blocks to test.
0158     // WARNING: You can debug only ONE STAR AT A TIME, because
0159     //          the StarObject::Trail is static. It has to be
0160     //          static, because otherwise, we can run into segfaults
0161     //          due to the memcpy() that we do to create stars
0162     /*
0163     testStar = false;
0164     if( stardata->HD == 103095 && Trail.size() == 0 ) {
0165       // Populate Trail with various positions
0166         qDebug() << Q_FUNC_INFO << "TEST STAR FOUND!";
0167         testStar = true;
0168         KSNumbers num( J2000 ); // Some estimate, doesn't matter.
0169         long double jy;
0170         for( jy = -10000.0; jy <= 10000.0; jy += 500.0 ) {
0171             num.updateValues( J2000 + jy * 365.238 );
0172             double ra, dec;
0173             getIndexCoords( &num, &ra, &dec );
0174             Trail.append( new SkyPoint( ra / 15.0, dec ) );
0175         }
0176         qDebug() << Q_FUNC_INFO << "Populated the star's trail with " << Trail.size() << " entries.";
0177     }
0178     */
0179     // END DEBUG.
0180 
0181     lastPrecessJD = J2000;
0182 }
0183 
0184 void StarObject::init(const DeepStarData *stardata)
0185 {
0186     double ra, dec, BV_Index;
0187 
0188     ra  = stardata->RA / 1000000.0;
0189     dec = stardata->Dec / 100000.0;
0190     setType(SkyObject::STAR);
0191 
0192     if (stardata->V == 30000 && stardata->B != 30000)
0193         setMag((stardata->B - 1600) / 1000.0); // FIXME: Is it okay to make up stuff like this?
0194     else
0195         setMag(stardata->V / 1000.0);
0196 
0197     setRA0(ra);
0198     setDec0(dec);
0199     setRA(ra);
0200     setDec(dec);
0201 
0202     SpType[1] = '?';
0203     SpType[0] = 'B';
0204     if (stardata->B == 30000 || stardata->V == 30000)
0205     {
0206         // Unused value
0207 //        BV_Index  = -100;
0208         SpType[0] = '?';
0209     }
0210     else
0211     {
0212         BV_Index = (stardata->B - stardata->V) / 1000.0;
0213         if (BV_Index > 0.0) SpType[0] = 'A';
0214         if (BV_Index > 0.325) SpType[0] = 'F';
0215         if (BV_Index > 0.575) SpType[0] = 'G';
0216         if (BV_Index > 0.975) SpType[0] = 'K';
0217         if (BV_Index > 1.6) SpType[0] = 'M';
0218     }
0219 
0220     PM_RA        = stardata->dRA / 100.0;
0221     PM_Dec       = stardata->dDec / 100.0;
0222     Parallax     = 0.0;
0223     Multiplicity = 0;
0224     Variability  = 0;
0225     updateID = updateNumID = 0;
0226     B                      = stardata->B / 1000.0;
0227     V                      = stardata->V / 1000.0;
0228     lastPrecessJD          = J2000;
0229 }
0230 
0231 void StarObject::setNames(const QString &name, const QString &name2)
0232 {
0233     QString lname;
0234 
0235     setName(name);
0236 
0237     setName2(name2);
0238 
0239     if (hasName() && name.startsWith(QLatin1String("HD")) == false)
0240     {
0241         lname = name;
0242         if (hasName2())
0243             lname += " (" + gname() + ')';
0244     }
0245     else if (hasName2())
0246         lname = gname();
0247     setLongName(lname);
0248 }
0249 void StarObject::initPopupMenu(KSPopupMenu *pmenu)
0250 {
0251 #ifdef KSTARS_LITE
0252     Q_UNUSED(pmenu)
0253 #else
0254     pmenu->createStarMenu(this);
0255 #endif
0256 }
0257 
0258 void StarObject::updateCoords(const KSNumbers *num, bool, const CachingDms *, const CachingDms *, bool)
0259 {
0260 //Correct for proper motion of stars.  Determine RA and Dec offsets.
0261 //Proper motion is given im milliarcsec per year by the pmRA() and pmDec() functions.
0262 //That is numerically identical to the number of arcsec per millenium, so multiply by
0263 //KSNumbers::julianMillenia() to find the offsets in arcsec.
0264 
0265 // Correction:  The method below computes the proper motion before the
0266 // precession.  If we precessed first then the direction of the proper
0267 // motion correction would depend on how far we've precessed.  -jbb
0268 #ifdef PROFILE_UPDATECOORDS
0269     std::clock_t start, stop;
0270     start = std::clock();
0271 #endif
0272     CachingDms saveRA = ra0(), saveDec = dec0();
0273     CachingDms newRA, newDec;
0274 
0275     getIndexCoords(num, newRA, newDec);
0276 
0277     setRA0(newRA);
0278     setDec0(newDec);
0279     SkyPoint::updateCoords(num);
0280     setRA0(saveRA);
0281     setDec0(saveDec);
0282 
0283 #ifdef PROFILE_UPDATECOORDS
0284     stop = std::clock();
0285     updateCoordsCpuTime += double(stop - start) / double(CLOCKS_PER_SEC);
0286     ++starsUpdated;
0287 #endif
0288 }
0289 
0290 bool StarObject::getIndexCoords(const KSNumbers *num, CachingDms &ra, CachingDms &dec)
0291 {
0292     static double pmms;
0293 
0294     // =================== NOTE: CODE DUPLICATION ====================
0295     // If you modify this, please also modify the other getIndexCoords
0296     // ===============================================================
0297     //
0298     // Reason for code duplication is as follows:
0299     //
0300     // This method is designed to use CachingDms, i.e. we know we are
0301     // going to use the sine and cosine of the returned values.
0302     //
0303     // The other method is designed to avoid CachingDms and try to
0304     // compute as little trigonometry as possible when the ra/dec has
0305     // to be returned in double (used in SkyMesh::indexStar() for
0306     // example)
0307     //
0308     // Thus, the philosophy of writing code is different. Granted, we
0309     // don't need to optimize for the smaller star catalogs (which use
0310     // SkyMesh::indexStar()), but it is nevertheless a good idea,
0311     // given that getIndexCoords() shows up in callgrind as one of the
0312     // slightly more expensive operations.
0313 
0314     // Old, Incorrect Proper motion Computation.  We retain this in a
0315     // comment because we might want to use it to come up with a
0316     // linear approximation that's faster.
0317     //    double dra = pmRA() * num->julianMillenia() / ( cos( dec0().radians() ) * 3600.0 );
0318     //    double ddec = pmDec() * num->julianMillenia() / 3600.0;
0319 
0320 
0321     pmms = pmMagnitudeSquared();
0322 
0323     if (std::isnan(pmms) || pmms * num->julianMillenia() * num->julianMillenia() < .01)
0324     {
0325         // Ignore corrections
0326         ra  = ra0();
0327         dec = dec0();
0328         return false;
0329     }
0330 
0331     /*
0332 
0333     // Proper Motion Correction should be implemented as motion along a great
0334     // circle passing through the given (ra0, dec0) in a direction of
0335     // atan2( pmRA(), pmDec() ) to an angular distance given by the Magnitude of
0336     // PM times the number of Julian millenia since J2000.0
0337 
0338     double pm = pmMagnitude() * num->julianMillenia(); // Proper Motion in arcseconds
0339 
0340     double dir0 = ((pm > 0) ? atan2(pmRA(), pmDec()) : atan2(-pmRA(), -pmDec())); // Bearing, in radian
0341 
0342     if (pm < 0)
0343         pm = -pm;
0344 
0345     double dst = (pm * M_PI / (180.0 * 3600.0));
0346     //    double phi = M_PI / 2.0 - dec0().radians();
0347 
0348     // Note: According to callgrind, dms::dms() + dms::setRadians()
0349     // takes ~ 40 CPU cycles, whereas, the advantage afforded by using
0350     // sincos() instead of sin() and cos() calls seems to be about 30
0351     // CPU cycles.
0352 
0353     // So it seems like it is not worth turning dir0 and dst into dms
0354     // objects and using SinCos(). However, caching the values of sin
0355     // and cos if we are going to reuse them avoids expensive (~120
0356     // CPU cycle) recomputation!
0357     CachingDms lat1, dtheta;
0358     double sinDst = sin(dst), cosDst = cos(dst);
0359     lat1.setUsing_asin(dec0().sin() * cosDst + dec0().cos() * sinDst * cos(dir0));
0360     dtheta.setUsing_atan2(sin(dir0) * sinDst * dec0().cos(), cosDst - dec0().sin() * lat1.sin());
0361 
0362     ra  = ra0() + dtheta; // Use operator + to avoid trigonometry
0363     dec = lat1;           // Need variable lat1 because dec may refer to dec0, so cannot construct result in-place
0364 
0365     */
0366 
0367     // Use the formula given in Seidelmann (Explanatory Supplement to
0368     // the Astronomical Almanac) instead. The formulas used here are
0369     // the combination of (3.23-1), (3.23-3), (3.23-5). Not only does
0370     // it reduce trigonometry use, it is more likely to be correct
0371     // than the stuff we came up with on our own above
0372 
0373     // Although it is not explained in the above reference, a formula
0374     // that reduces to α' = α + μ_α * t must have μ_α be the rate of
0375     // change of the angle at the center of the declination circle,
0376     // and not the rate of change of arclength, i.e. μ_α must _not_
0377     // include the cos(δ) factor. I have checked that the formulas
0378     // used here do reduce to the above, and therefore expect μ_α =
0379     // pmRa / cos(δ)
0380 
0381     double cosDec, sinDec, cosRa, sinRa;
0382     double scale = num->julianMillenia() * (M_PI / (180.0 * 3600.0));
0383     dec0().SinCos(sinDec, cosDec);
0384     ra0().SinCos(sinRa, cosRa);
0385 
0386     // Note: Below assumes that pmRA is already pre-scaled by cos(delta), as it is for Hipparcos
0387     double net_pmRA = pmRA() * scale, net_pmDec = pmDec() * scale;
0388 
0389     double x0 = cosDec * cosRa, y0 = cosDec * sinRa, z0 = sinDec;
0390     double dX = - net_pmRA * sinRa - net_pmDec * sinDec * cosRa;
0391     double dY = net_pmRA * cosRa - net_pmDec * sinDec * sinRa;
0392     double dZ = net_pmDec * cosDec;
0393     double x = x0 + dX, y = y0 + dY, z = z0 + dZ;
0394 
0395     ra.setUsing_atan2(y, x);
0396 
0397     // Note: dec = asin(z) is a poor choice, because we aren't
0398     // guaranteed that (x, y, z) lies on the unit sphere due to the
0399     // first-order approximation. Therefore, we must "project" out any
0400     // change in the length of the vector to get our best estimate,
0401     // and this is achieved by using atan. In fact atan gives the
0402     // least-squares estimate for an angle given both its sin and
0403     // cosine components.
0404     dec.setUsing_atan2(z, sqrt(x * x + y * y));
0405 
0406     return true;
0407 }
0408 
0409 bool StarObject::getIndexCoords(const KSNumbers *num, double *ra, double *dec)
0410 {
0411     static double pmms;
0412 
0413     // =================== NOTE: CODE DUPLICATION ====================
0414     // If you modify this, please also modify the other getIndexCoords
0415     // ===============================================================
0416     //
0417     // Reason for code duplication is as follows:
0418     //
0419     // This method is designed to avoid CachingDms and try to compute
0420     // as little trigonometry as possible when the ra/dec has to be
0421     // returned in double (used in SkyMesh::indexStar() for example)
0422     //
0423     // The other method is designed to use CachingDms, i.e. we know we
0424     // are going to use the sine and cosine of the returned values.
0425     //
0426     // Thus, the philosophy of writing code is different. Granted, we
0427     // don't need to optimize for the smaller star catalogs (which use
0428     // SkyMesh::indexStar()), but it is nevertheless a good idea,
0429     // given that getIndexCoords() shows up in callgrind as one of the
0430     // slightly more expensive operations.
0431 
0432     // Old, Incorrect Proper motion Computation.  We retain this in a
0433     // comment because we might want to use it to come up with a
0434     // linear approximation that's faster.
0435     //    double dra = pmRA() * num->julianMillenia() / ( cos( dec0().radians() ) * 3600.0 );
0436     //    double ddec = pmDec() * num->julianMillenia() / 3600.0;
0437 
0438     // Proper Motion Correction should be implemented as motion along a great
0439     // circle passing through the given (ra0, dec0) in a direction of
0440     // atan2( pmRA(), pmDec() ) to an angular distance given by the Magnitude of
0441     // PM times the number of Julian millenia since J2000.0
0442 
0443     pmms = pmMagnitudeSquared();
0444 
0445     if (std::isnan(pmms) || pmms * num->julianMillenia() * num->julianMillenia() < .01)
0446     {
0447         // Ignore corrections
0448         *ra  = ra0().Degrees();
0449         *dec = dec0().Degrees();
0450         return false;
0451     }
0452 
0453     /*
0454     double pm = pmMagnitude() * num->julianMillenia(); // Proper Motion in arcseconds
0455 
0456     double dir0 = ((pm > 0) ? atan2(pmRA(), pmDec()) : atan2(-pmRA(), -pmDec())); // Bearing, in radian
0457 
0458     (pm < 0) && (pm = -pm);
0459 
0460     double dst = (pm * M_PI / (180.0 * 3600.0));
0461     //    double phi = M_PI / 2.0 - dec0().radians();
0462 
0463     // Note: According to callgrind, dms::dms() + dms::setRadians()
0464     // takes ~ 40 CPU cycles, whereas, the advantage afforded by using
0465     // sincos() instead of sin() and cos() calls seems to be about 30
0466     // CPU cycles.
0467 
0468     // So it seems like it is not worth turning dir0 and dst into dms
0469     // objects and using SinCos(). However, caching the values of sin
0470     // and cos if we are going to reuse them avoids expensive (~120
0471     // CPU cycle) recomputation!
0472     dms lat1, dtheta;
0473     double sinDst = sin(dst), cosDst = cos(dst);
0474     double sinLat1 = dec0().sin() * cosDst + dec0().cos() * sinDst * cos(dir0);
0475     lat1.setRadians(asin(sinLat1));
0476     dtheta.setRadians(atan2(sin(dir0) * sinDst * dec0().cos(), cosDst - dec0().sin() * sinLat1));
0477 
0478     // Using dms instead, to ensure that the numbers are in the right range.
0479     dms finalRA(ra0().Degrees() + dtheta.Degrees());
0480 
0481     *ra  = finalRA.Degrees();
0482     *dec = lat1.Degrees();
0483     */
0484 
0485 
0486     // Although it is not explained in the above reference, a formula
0487     // that reduces to α' = α + μ_α * t must have μ_α be the rate of
0488     // change of the angle at the center of the declination circle,
0489     // and not the rate of change of arclength, i.e. μ_α must _not_
0490     // include the cos(δ) factor. I have checked that the formulas
0491     // used in Seidelmann do reduce to the above, and therefore expect
0492     // μ_α = pmRa / cos(δ). Therefore, I'm absorbing the factor in the
0493     // implementation here.
0494 
0495     double cosDec, sinDec, cosRa, sinRa;
0496     double scale = num->julianMillenia() * (M_PI / (180.0 * 3600.0));
0497     dec0().SinCos(sinDec, cosDec);
0498     ra0().SinCos(sinRa, cosRa);
0499 
0500     // Note: Below assumes that pmRA is already pre-scaled by cos(delta), as it is for Hipparcos
0501     double net_pmRA = pmRA() * scale, net_pmDec = pmDec() * scale;
0502 
0503     double x0 = cosDec * cosRa, y0 = cosDec * sinRa, z0 = sinDec;
0504     double dX = - net_pmRA * sinRa - net_pmDec * sinDec * cosRa;
0505     double dY = net_pmRA * cosRa - net_pmDec * sinDec * sinRa;
0506     double dZ = net_pmDec * cosDec;
0507     double x = x0 + dX, y = y0 + dY, z = z0 + dZ;
0508 
0509     dms alpha, delta;
0510     alpha.setRadians(atan2(y, x));
0511 
0512     // Note: dec = asin(z) is a poor choice, because we aren't
0513     // guaranteed that (x, y, z) lies on the unit sphere due to the
0514     // first-order approximation. Therefore, we must "project" out any
0515     // change in the length of the vector to get our best estimate,
0516     // and this is achieved by using atan. In fact atan gives the
0517     // least-squares estimate for an angle given both its sin and
0518     // cosine components.
0519     delta.setRadians(atan2(z, sqrt(x * x + y * y)));
0520     *ra = alpha.reduce().Degrees();
0521     *dec = delta.Degrees();
0522 
0523     return true;
0524 }
0525 
0526 void StarObject::JITupdate()
0527 {
0528     static KStarsData *data = KStarsData::Instance();
0529 
0530     if (updateNumID != data->updateNumID())
0531     {
0532         // TODO: This can be optimized and reorganized further in a better manner.
0533         // Maybe we should do this only for stars, since this is really a slow step only for stars
0534         Q_ASSERT(std::isfinite(lastPrecessJD));
0535 
0536         if (Options::alwaysRecomputeCoordinates() || (Options::useRelativistic() && checkBendLight()) ||
0537             std::abs(lastPrecessJD - data->updateNum()->getJD()) >= 0.00069444) // Update is once per solar minute
0538         {
0539             // Short circuit right here, if recomputing coordinates is not required. NOTE: POTENTIALLY DANGEROUS
0540             updateCoords(data->updateNum());
0541         }
0542 
0543         updateNumID = data->updateNumID();
0544     }
0545     EquatorialToHorizontal(data->lst(), data->geo()->lat());
0546     updateID = data->updateID();
0547 }
0548 
0549 QString StarObject::sptype(void) const
0550 {
0551     return QString(QByteArray(SpType, 2));
0552 }
0553 
0554 char StarObject::spchar() const
0555 {
0556     return SpType[0];
0557 }
0558 
0559 QString StarObject::gname(bool useGreekChars) const
0560 {
0561     if (!name2().isEmpty())
0562         return greekLetter(useGreekChars) + ' ' + constell();
0563     else
0564         return QString();
0565 }
0566 
0567 QString StarObject::greekLetter(bool gchar) const
0568 {
0569     QString code   = name2().left(3);
0570     QString letter = code; //in case genitive name is *not* a Greek letter
0571     int alpha      = 0x03B1;
0572 
0573     auto checkAndGreekify = [&code, gchar, alpha, &letter](const QString &abbrev, int unicodeOffset,
0574                                                            const QString &expansion) {
0575         if (code == abbrev)
0576             gchar ? letter = QString(QChar(alpha + unicodeOffset)) : letter = expansion;
0577     };
0578 
0579     checkAndGreekify("alp", 0, i18n("alpha"));
0580     checkAndGreekify("bet", 1, i18n("beta"));
0581     checkAndGreekify("gam", 2, i18n("gamma"));
0582     checkAndGreekify("del", 3, i18n("delta"));
0583     checkAndGreekify("eps", 4, i18n("epsilon"));
0584     checkAndGreekify("zet", 5, i18n("zeta"));
0585     checkAndGreekify("eta", 6, i18n("eta"));
0586     checkAndGreekify("the", 7, i18n("theta"));
0587     checkAndGreekify("iot", 8, i18n("iota"));
0588     checkAndGreekify("kap", 9, i18n("kappa"));
0589     checkAndGreekify("lam", 10, i18n("lambda"));
0590     checkAndGreekify("mu ", 11, i18n("mu"));
0591     checkAndGreekify("nu ", 12, i18n("nu"));
0592     checkAndGreekify("xi ", 13, i18n("xi"));
0593     checkAndGreekify("omi", 14, i18n("omicron"));
0594     checkAndGreekify("pi ", 15, i18n("pi"));
0595     checkAndGreekify("rho", 16, i18n("rho"));
0596     //there are two unicode symbols for sigma;
0597     //skip the first one, the second is more widely used
0598     checkAndGreekify("sig", 18, i18n("sigma"));
0599     checkAndGreekify("tau", 19, i18n("tau"));
0600     checkAndGreekify("ups", 20, i18n("upsilon"));
0601     checkAndGreekify("phi", 21, i18n("phi"));
0602     checkAndGreekify("chi", 22, i18n("chi"));
0603     checkAndGreekify("psi", 23, i18n("psi"));
0604     checkAndGreekify("ome", 24, i18n("omega"));
0605 
0606     if (name2().length() && name2().mid(3, 1) != " ")
0607         letter += '[' + name2().mid(3, 1) + ']';
0608 
0609     return letter;
0610 }
0611 
0612 // FIXME: Move this somewhere else, make this static, and give it a better name.
0613 // Mostly for code cleanliness. Also, try to put it in a DB.
0614 QString StarObject::constell() const
0615 {
0616     QString code = name2().mid(4, 3);
0617 
0618     return KSUtils::constGenetiveFromAbbrev(code);
0619 }
0620 
0621 // The two routines below seem overly complicated but at least they are doing
0622 // the right thing now.  Please resist the temptation to simplify them unless
0623 // you are prepared to ensure there is no ugly label overlap for all 8 cases
0624 // they deal with ( drawName x DrawMag x star-has-name).  -jbb
0625 
0626 QString StarObject::nameLabel(bool drawName, bool drawMag) const
0627 {
0628     QString sName;
0629 
0630     if (drawName)
0631     {
0632         QString translation = translatedName();
0633         if (translation != i18n("star") && !translation.startsWith("HD"))
0634             sName = translation;
0635         else if (!gname().trimmed().isEmpty())
0636             sName = gname(true);
0637         else
0638         {
0639             if (drawMag)
0640                 return ('[' + QLocale().toString(mag(), 'f', 1) + "m]");
0641         }
0642         if (!drawMag)
0643             return sName;
0644         else
0645             return sName + " [" + QLocale().toString(mag(), 'f', 1) + "m]";
0646     }
0647     return ('[' + QLocale().toString(mag(), 'f', 1) + "m]");
0648 }
0649 
0650 //If this works, we can maybe get rid of customLabel() and nameLabel()??
0651 QString StarObject::labelString() const
0652 {
0653     return nameLabel(Options::showStarNames(), Options::showStarMagnitudes());
0654 }
0655 
0656 double StarObject::labelOffset() const
0657 {
0658     return (6. + 0.5 * (5.0 - mag()) + 0.01 * (Options::zoomFactor() / 500.));
0659 }
0660 
0661 SkyObject::UID StarObject::getUID() const
0662 {
0663     // mag takes 10 bit
0664     SkyObject::UID m = mag() * 10;
0665     if (m < 0)
0666         m = 0;
0667 
0668     // Both RA & dec fits in 24-bits
0669     SkyObject::UID ra  = ra0().Degrees() * 36000;
0670     SkyObject::UID dec = (ra0().Degrees() + 91) * 36000;
0671 
0672     Q_ASSERT("Magnitude is expected to fit into 10bits" && m >= 0 && m < (1 << 10));
0673     Q_ASSERT("RA should fit into 24bits" && ra >= 0 && ra < (1 << 24));
0674     Q_ASSERT("Dec should fit into 24bits" && dec >= 0 && dec < (1 << 24));
0675 
0676     return (SkyObject::UID_STAR << 60) | (m << 48) | (ra << 24) | dec;
0677 }