1 /*
2     SPDX-FileCopyrightText: 2005 Jason Harris <kstars@30doradus.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "constellationboundarylines.h"
8 
9 #include "ksfilereader.h"
10 #include "kstarsdata.h"
11 #include "linelist.h"
12 #include "Options.h"
13 #include "polylist.h"
14 #ifdef KSTARS_LITE
15 #include "skymaplite.h"
16 #else
17 #include "skymap.h"
18 #endif
19 #include "skypainter.h"
20 #include "htmesh/MeshIterator.h"
21 #include "skycomponents/skymapcomposite.h"
22 
23 #include <QHash>
24 
ConstellationBoundaryLines(SkyComposite * parent)25 ConstellationBoundaryLines::ConstellationBoundaryLines(SkyComposite *parent)
26     : NoPrecessIndex(parent, i18n("Constellation Boundaries"))
27 {
28     m_skyMesh      = SkyMesh::Instance();
29     m_polyIndexCnt = 0;
30     for (int i = 0; i < m_skyMesh->size(); i++)
31     {
32         m_polyIndex.append(std::shared_ptr<PolyListList>(new PolyListList()));
33     }
34 
35     KStarsData *data = KStarsData::Instance();
36     int verbose      = 0; // -1 => create cbounds-$x.idx on stdout
37     //  0 => normal
38     const char *fname = "cbounds.dat";
39     int flag = 0;
40     double ra, dec = 0, lastRa, lastDec;
41     std::shared_ptr<LineList> lineList;
42     std::shared_ptr<PolyList> polyList;
43     bool ok = false;
44 
45     intro();
46 
47     // Open the .idx file and skip past the first line
48     KSFileReader idxReader, *idxFile = nullptr;
49     QString idxFname = QString("cbounds-%1.idx").arg(SkyMesh::Instance()->level());
50     if (idxReader.open(idxFname))
51     {
52         idxReader.readLine();
53         idxFile = &idxReader;
54     }
55 
56     // now open the file that contains the points
57     KSFileReader fileReader;
58     if (!fileReader.open(fname))
59         return;
60 
61     fileReader.setProgress(i18n("Loading Constellation Boundaries"), 13124, 10);
62 
63     lastRa = lastDec = -1000.0;
64 
65     while (fileReader.hasMoreLines())
66     {
67         QString line = fileReader.readLine();
68         fileReader.showProgress();
69 
70         if (line.at(0) == '#')
71             continue;          // ignore comments
72         if (line.at(0) == ':') // :constellation line
73         {
74             if (lineList.get())
75                 appendLine(lineList);
76             lineList.reset();
77 
78             if (polyList.get())
79                 appendPoly(polyList, idxFile, verbose);
80             QString cName = line.mid(1);
81             polyList.reset(new PolyList(cName));
82             if (verbose == -1)
83                 printf(":\n");
84             lastRa = lastDec = -1000.0;
85             continue;
86         }
87 
88         // read in the data from the line
89         ra = line.midRef(0, 12).toDouble(&ok);
90         if (ok)
91             dec = line.midRef(13, 12).toDouble(&ok);
92         if (ok)
93             flag = line.midRef(26, 1).toInt(&ok);
94         if (!ok)
95         {
96             fprintf(stderr, "%s: conversion error on line: %d\n", fname, fileReader.lineNumber());
97             continue;
98         }
99 
100         if (ra == lastRa && dec == lastDec)
101         {
102             fprintf(stderr, "%s: tossing dupe on line %4d: (%f, %f)\n", fname, fileReader.lineNumber(), ra, dec);
103             continue;
104         }
105 
106         // always add the point to the boundary (and toss dupes)
107 
108         // By the time we come here, we should have polyList. Else we aren't doing good
109         Q_ASSERT(polyList); // Is this the right fix?
110 
111         polyList->append(QPointF(ra, dec));
112         if (ra < 0)
113             polyList->setWrapRA(true);
114 
115         if (flag)
116         {
117             if (!lineList.get())
118                 lineList.reset(new LineList());
119 
120             std::shared_ptr<SkyPoint> point(new SkyPoint(ra, dec));
121 
122             point->EquatorialToHorizontal(data->lst(), data->geo()->lat());
123             lineList->append(std::move(point));
124             lastRa  = ra;
125             lastDec = dec;
126         }
127         else
128         {
129             if (lineList.get())
130                 appendLine(lineList);
131             lineList.reset();
132             lastRa = lastDec = -1000.0;
133         }
134     }
135 
136     if (lineList.get())
137         appendLine(lineList);
138     if (polyList.get())
139         appendPoly(polyList, idxFile, verbose);
140 }
141 
selected()142 bool ConstellationBoundaryLines::selected()
143 {
144 #ifndef KSTARS_LITE
145     return Options::showCBounds() && !(Options::hideOnSlew() && Options::hideCBounds() && SkyMap::IsSlewing());
146 #else
147     return Options::showCBounds() && !(Options::hideOnSlew() && Options::hideCBounds() && SkyMapLite::IsSlewing());
148 #endif
149 }
150 
preDraw(SkyPainter * skyp)151 void ConstellationBoundaryLines::preDraw(SkyPainter *skyp)
152 {
153     QColor color = KStarsData::Instance()->colorScheme()->colorNamed("CBoundColor");
154     skyp->setPen(QPen(QBrush(color), 1, Qt::SolidLine));
155 }
156 
appendPoly(std::shared_ptr<PolyList> & polyList,KSFileReader * file,int debug)157 void ConstellationBoundaryLines::appendPoly(std::shared_ptr<PolyList> &polyList, KSFileReader *file, int debug)
158 {
159     if (!file || debug == -1)
160         return appendPoly(polyList, debug);
161 
162     while (file->hasMoreLines())
163     {
164         QString line = file->readLine();
165         if (line.at(0) == ':')
166             return;
167         Trixel trixel = line.toInt();
168 
169         m_polyIndex[trixel]->append(polyList);
170     }
171 }
172 
appendPoly(const std::shared_ptr<PolyList> & polyList,int debug)173 void ConstellationBoundaryLines::appendPoly(const std::shared_ptr<PolyList> &polyList, int debug)
174 {
175     if (debug >= 0 && debug < m_skyMesh->debug())
176         debug = m_skyMesh->debug();
177 
178     const IndexHash &indexHash     = m_skyMesh->indexPoly(polyList->poly());
179     IndexHash::const_iterator iter = indexHash.constBegin();
180     while (iter != indexHash.constEnd())
181     {
182         Trixel trixel = iter.key();
183         iter++;
184 
185         if (debug == -1)
186             printf("%d\n", trixel);
187 
188         m_polyIndex[trixel]->append(polyList);
189     }
190 
191     if (debug > 9)
192         printf("PolyList: %3d: %d\n", ++m_polyIndexCnt, indexHash.size());
193 }
194 
ContainingPoly(const SkyPoint * p) const195 PolyList *ConstellationBoundaryLines::ContainingPoly(const SkyPoint *p) const
196 {
197     //printf("called ContainingPoly(p)\n");
198 
199     // we save the pointers in a hash because most often there is only one
200     // constellation and we can avoid doing the expensive boundary calculations
201     // and just return it if we know it is unique.  We can avoid this minor
202     // complication entirely if we use index(p) instead of aperture(p, r)
203     // because index(p) always returns a single trixel index.
204 
205     QHash<PolyList *, bool> polyHash;
206     QHash<PolyList *, bool>::const_iterator iter;
207 
208     //printf("\n");
209 
210     // the boundaries don't precess so we use index() not aperture()
211     m_skyMesh->index(p, 1.0, IN_CONSTELL_BUF);
212     MeshIterator region(m_skyMesh, IN_CONSTELL_BUF);
213     while (region.hasNext())
214     {
215         Trixel trixel = region.next();
216         //printf("Trixel: %4d %s\n", trixel, m_skyMesh->indexToName( trixel ) );
217 
218         std::shared_ptr<PolyListList> polyListList = m_polyIndex[trixel];
219 
220         //printf("    size: %d\n", polyListList->size() );
221 
222         for (const auto &item : *polyListList)
223         {
224             polyHash.insert(item.get(), true);
225         }
226     }
227 
228     iter = polyHash.constBegin();
229 
230     // Don't bother with boundaries if there is only one
231     if (polyHash.size() == 1)
232         return iter.key();
233 
234     QPointF point(p->ra().Hours(), p->dec().Degrees());
235     QPointF wrapPoint(p->ra().Hours() - 24.0, p->dec().Degrees());
236     bool wrapRA = p->ra().Hours() > 12.0;
237 
238     while (iter != polyHash.constEnd())
239     {
240         PolyList *polyList = iter.key();
241         iter++;
242 
243         //qDebug() << QString("checking %1 boundary\n").arg( polyList->name() );
244 
245         const QPolygonF *poly = polyList->poly();
246         if (wrapRA && polyList->wrapRA())
247         {
248             if (poly->containsPoint(wrapPoint, Qt::OddEvenFill))
249                 return polyList;
250         }
251         else
252         {
253             if (poly->containsPoint(point, Qt::OddEvenFill))
254                 return polyList;
255         }
256     }
257 
258     return nullptr;
259 }
260 
261 //-------------------------------------------------------------------
262 // The routines for providing public access to the boundary index
263 // start here.  (Some of them may not be needed (or working)).
264 //-------------------------------------------------------------------
265 
constellationName(const SkyPoint * p) const266 QString ConstellationBoundaryLines::constellationName(const SkyPoint *p) const
267 {
268     PolyList *polyList = ContainingPoly(p);
269     if (polyList)
270     {
271         return (Options::useLocalConstellNames() ?
272                     i18nc("Constellation name (optional)", polyList->name().toUpper().toLocal8Bit().data()) :
273                     polyList->name());
274     }
275     return i18n("Unknown");
276 }
277