• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Stellarium / stellarium / 4853788370

pending completion
4853788370

push

github

Alexander V. Wolf
Special patch for John Simple

3 of 3 new or added lines in 3 files covered. (100.0%)

14729 of 125046 relevant lines covered (11.78%)

20166.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/core/StelLocationMgr.cpp
1
/*
2
 * Copyright (C) 2008 Fabien Chereau
3
 *
4
 * This program is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU General Public License
6
 * as published by the Free Software Foundation; either version 2
7
 * of the License, or (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program; if not, write to the Free Software
16
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
17
 */
18

19
#include "StelLocationMgr.hpp"
20
#include "StelLocationMgr_p.hpp"
21

22
#include "StelApp.hpp"
23
#include "StelCore.hpp"
24
#include "StelFileMgr.hpp"
25
#include "StelUtils.hpp"
26
#include "StelJsonParser.hpp"
27

28
#include <QStringListModel>
29
#include <QDebug>
30
#include <QFile>
31
#include <QDir>
32
#include <QNetworkInterface>
33
#include <QNetworkAccessManager>
34
#include <QNetworkRequest>
35
#include <QNetworkReply>
36
#include <QUrl>
37
#include <QUrlQuery>
38
#include <QSettings>
39
#include <QTimeZone>
40
#include <QTimer>
41
#include <QApplication>
42
#include <QRegularExpression>
43

44
TimezoneNameMap StelLocationMgr::locationDBToIANAtranslations;
45
QString StelLocationMgr::tzfFileName = "data/timezone.tab";
46
QList<GeoRegion> StelLocationMgr::regions;
47
QMap<QString, QString> StelLocationMgr::countryCodeToRegionMap;
48
QMap<QString, QString> StelLocationMgr::countryNameToCodeMap;
49
bool StelLocationMgr::unknownTZ = false;
50

51
#ifdef ENABLE_GPS
52
#ifdef ENABLE_LIBGPS
53
LibGPSLookupHelper::LibGPSLookupHelper(QObject *parent)
×
54
        : GPSLookupHelper(parent), ready(false)
×
55
{
56
        QSettings* conf = StelApp::getInstance().getSettings();
×
57

58
        QString gpsdHostname=conf->value("gui/gpsd_hostname", "localhost").toString();
×
59
        QString gpsdPort=conf->value("gui/gpsd_port", DEFAULT_GPSD_PORT).toString();
×
60

61
        timer.setSingleShot(false);
×
62
        if (qApp->property("verbose").toBool())
×
63
                qDebug() << "Opening GPSD connection to" << gpsdHostname << ":" << gpsdPort;
×
64
        // Example almost straight from http://www.catb.org/gpsd/client-howto.html
65
        gps_rec = new gpsmm(gpsdHostname.toUtf8(), gpsdPort.toUtf8());
×
66
        if(gps_rec->is_open())
×
67
        {
68
                ready = gps_rec->stream(WATCH_ENABLE|WATCH_JSON);
×
69
        }
70
        if(ready)
×
71
        {
72
                connect(&timer, SIGNAL(timeout()), this, SLOT(query()));
×
73
        }
74
        else
75
                qWarning()<<"libGPS lookup not ready, GPSD probably not running.";
×
76
}
×
77

78
LibGPSLookupHelper::~LibGPSLookupHelper()
×
79
{
80
        delete gps_rec;
×
81
}
×
82

83
bool LibGPSLookupHelper::isReady()
×
84
{
85
        return ready;
×
86
}
87

88
void LibGPSLookupHelper::setPeriodicQuery(int interval)
×
89
{
90
        if (interval==0)
×
91
                timer.stop();
×
92
        else
93
                {
94
                        timer.start(interval);
×
95
                }
96
}
×
97
void LibGPSLookupHelper::query()
×
98
{
99
        bool verbose=qApp->property("verbose").toBool();
×
100

101
        if(!ready)
×
102
        {
103
                emit queryError("GPSD helper not ready");
×
104
                return;
×
105
        }
106

107
        StelLocation loc;
×
108

109
        int tries=0;
×
110
        int fixmode=0;
×
111
        while (tries<10)
×
112
        {
113
                tries++;
×
114
                if (verbose)
×
115
                        qDebug() << "query(): tries=" << tries;
×
116

117
                if (!gps_rec->waiting(750000)) // argument usec. wait 0.75 sec. (example had 50s)
×
118
                {
119
                        qDebug() << " - waiting timed out after 0.75sec.";
×
120
                        continue;
×
121
                }
122

123
                struct gps_data_t* newdata;
124
                if ((newdata = gps_rec->read()) == Q_NULLPTR)
×
125
                {
126
                        emit queryError("GPSD query: Read error.");
×
127
                        return;
×
128
                }
129
                else
130
                {
131
// It is unclear why some data elements seem to be not filled by gps_rec.read().
132
//                        if (newdata->status==0) // no fix?
133
//                        {
134
//                                // This can happen indoors.
135
//                                qDebug() << "GPS has no fix.";
136
//                                emit queryError("GPSD query: No Fix.");
137
//                                return;
138
//                        }
139
#if GPSD_API_MAJOR_VERSION < 9
140
                        if (newdata->online==0.0) // no device?
141
#else
142
                        if (newdata->online.tv_sec == 0 && newdata->online.tv_nsec == 0) // no device?
×
143
#endif
144
                        {
145
                                // This can happen when unplugging the GPS while running Stellarium,
146
                                // or running gpsd with no GPS receiver.
147
                                emit queryError("GPS seems offline. No fix.");
×
148
                                return;
×
149
                        }
150

151

152
                        fixmode=newdata->fix.mode; // 0:not_seen, 1:no_fix, 2:2Dfix(no alt), 3:3Dfix(perfect)
×
153
                        if (verbose)
×
154
                                qDebug() << "GPSD newdata->fix.mode=" << fixmode;
×
155

156
                        if (fixmode==0)
×
157
                        {
158
                                // This may come just after creation of the GPSDhelper.
159
                                // It seems to take some time to fill the data.
160
                                if (verbose)
×
161
                                        qDebug() << "GPSD seems not ready yet. Retry.";
×
162
                                continue;
×
163
                        }
164

165
                        if (verbose)
×
166
                        {
167
                                //qDebug() << "newdata->online=" << newdata->online;
168
                                qDebug() << "Solution from " << newdata->satellites_used << "out of " << newdata->satellites_visible << " visible Satellites.";
×
169
                                dop_t dop=newdata->dop;
×
170
#if GPSD_API_MAJOR_VERSION < 9
171
                                qDebug() << "GPSD data: Long" << newdata->fix.longitude << "Lat" << newdata->fix.latitude << "Alt" << newdata->fix.altitude;
172
#else
173
                                qDebug() << "GPSD data: Long" << newdata->fix.longitude << "Lat" << newdata->fix.latitude << "Alt" << newdata->fix.altHAE;
×
174
#endif
175
                                qDebug() << "Dilution of Precision:";
×
176
                                qDebug() << " - xdop:" << dop.xdop << "ydop:" << dop.ydop;
×
177
                                qDebug() << " - pdop:" << dop.pdop << "hdop:" << dop.hdop;
×
178
                                qDebug() << " - vdop:" << dop.vdop << "tdop:" << dop.tdop << "gdop:" << dop.gdop;
×
179
                                // GPSD API 8.0:
180
                                // * Remove epe from gps_data_t, it duplicates gps_fix_t eph
181
                                // * Added sep (estimated spherical error, 3D)
182
                                // Details: https://github.com/Stellarium/stellarium/issues/733
183
                                // #if GPSD_API_MAJOR_VERSION >= 8
184
                                // qDebug() << "Spherical Position Error (sep):" << newdata->fix.sep;
185
                                // #else
186
                                // qDebug() << "Spherical Position Error (epe):" << newdata->epe;
187
                                // #endif
188
                        }
189
                        loc.setLongitude(static_cast<float> (newdata->fix.longitude));
×
190
                        loc.setLatitude (static_cast<float> (newdata->fix.latitude));
×
191
                        // Frequently hdop, vdop and satellite counts are NaN. Sometimes they show OK. This is minor issue.
192
                        if ((verbose) && (fixmode<3))
×
193
                        {
194
                                qDebug() << "GPSDfix " << fixmode << ": Location" << QString("lat %1, long %2, alt %3").arg(loc.getLatitude()).arg(loc.getLongitude()).arg(loc.altitude);
×
195
                                qDebug() << "    Estimated HDOP " << newdata->dop.hdop << "m from " << newdata->satellites_used << "(of" << newdata->satellites_visible  << "visible) satellites";
×
196
                        }
197
                        else
198
                        {
199
#if GPSD_API_MAJOR_VERSION < 9
200
                                loc.altitude=static_cast<int>(newdata->fix.altitude);
201
#else
202
                                loc.altitude=static_cast<int>(newdata->fix.altHAE);
×
203
#endif
204
                                if (verbose)
×
205
                                {
206
                                        qDebug() << "GPSDfix " << fixmode << ": Location" << QString("lat %1, long %2, alt %3").arg(loc.getLatitude()).arg(loc.getLongitude()).arg(loc.altitude);
×
207
                                        qDebug() << "    Estimated HDOP " << newdata->dop.hdop << "m, VDOP " << newdata->dop.vdop <<  "m from " << newdata->satellites_used << "(of" << newdata->satellites_visible  << "visible) satellites";
×
208
                                }
209
                                break; // escape from the tries loop
×
210
                        }
211
                }
212
        }
213

214
        if (fixmode <2)
×
215
        {
216
                emit queryError("GPSD: Could not get valid position.");
×
217
                return;
×
218
        }
219
        if ((verbose) && (fixmode<3))
×
220
        {
221
                qDebug() << "Fix only quality " << fixmode << " after " << tries << " tries";
×
222
        }
223
        if (verbose)
×
224
                qDebug() << "GPSD location" << QString("lat %1, long %2, alt %3").arg(loc.getLatitude()).arg(loc.getLongitude()).arg(loc.altitude);
×
225

226
        emit queryFinished(loc);
×
227
}
×
228

229
#endif
230

231
NMEALookupHelper::NMEALookupHelper(QObject *parent)
×
232
        : GPSLookupHelper(parent), serial(Q_NULLPTR), nmea(Q_NULLPTR)
×
233
{
234
        //use RAII
235
        // Getting a list of ports may enable auto-detection!
236
        QList<QSerialPortInfo> portInfoList=QSerialPortInfo::availablePorts();
×
237

238
        if (portInfoList.size()==0)
×
239
        {
240
                qDebug() << "No connected devices found. NMEA GPS lookup failed.";
×
241
                return;
×
242
        }
243

244
        QSettings* conf = StelApp::getInstance().getSettings();
×
245

246
        // As long as we only have one, this is OK. Else we must do something about COM3, COM4 etc.
247
        QSerialPortInfo portInfo;
×
248
        if (portInfoList.size()==1)
×
249
        {
250
                portInfo=portInfoList.at(0);
×
251
                qDebug() << "Only one port found at " << portInfo.portName();
×
252
        }
253
        else
254
        {
255
                #ifdef Q_OS_WIN
256
                QString portName=conf->value("gui/gps_interface", "COM3").toString();
257
                #else
258
                QString portName=conf->value("gui/gps_interface", "ttyUSB0").toString();
×
259
                #endif
260
                bool portFound=false;
×
261
                for (int i=0; i<portInfoList.size(); ++i)
×
262
                {
263
                        QSerialPortInfo pi=portInfoList.at(i);
×
264
                        qDebug() << "Serial port list. Make sure you are using the right configuration.";
×
265
                        qDebug() << "Port: " << pi.portName();
×
266
                        qDebug() << "  SystemLocation:" << pi.systemLocation();
×
267
                        qDebug() << "  Description:"    << pi.description();
×
268
                        qDebug() << "  Manufacturer:"   << pi.manufacturer();
×
269
                        qDebug() << "  VendorID:"       << pi.vendorIdentifier();
×
270
                        qDebug() << "  ProductID:"      << pi.productIdentifier();
×
271
                        qDebug() << "  SerialNumber:"   << pi.serialNumber();
×
272
#if (QT_VERSION<QT_VERSION_CHECK(5,14,0))
273
                        qDebug() << "  Busy:"           << pi.isBusy();
274
#endif
275
                        qDebug() << "  Null:"           << pi.isNull();
×
276
                        if (pi.portName()==portName)
×
277
                        {
278
                                portInfo=pi;
×
279
                                portFound=true;
×
280
                        }
281
                }
×
282
                if (!portFound)
×
283
                {
284
                        qDebug() << "Configured port" << portName << "not found. No GPS query.";
×
285
                        return;
×
286
                }
287
        }
×
288

289
        // NMEA-0183 specifies device sends at 4800bps, 8N1. Some devices however send at 9600, allow this.
290
        // baudrate is configurable via config
291
        qint32 baudrate=conf->value("gui/gps_baudrate", 4800).toInt();
×
292

293
        nmea=new QNmeaPositionInfoSource(QNmeaPositionInfoSource::RealTimeMode,this);
×
294
        //serial = new QSerialPort(portInfo, nmea);
295
        serial = new QSerialPort(portInfo, this);
×
296
        serial->setBaudRate(baudrate);
×
297
        serial->setDataBits(QSerialPort::Data8);
×
298
        serial->setParity(QSerialPort::NoParity);
×
299
        serial->setStopBits(QSerialPort::OneStop);
×
300
        serial->setFlowControl(QSerialPort::NoFlowControl);
×
301
        if (serial->open(QIODevice::ReadOnly)) // may fail when line used by other program!
×
302
        {
303
                nmea->setDevice(serial);
×
304
                qDebug() << "Query GPS NMEA device at port " << serial->portName();
×
305
                connect(nmea, SIGNAL(positionUpdated(const QGeoPositionInfo)),this,SLOT(nmeaUpdated(const QGeoPositionInfo)));
×
306
                #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
307
                connect(nmea, SIGNAL(errorOccurred(QGeoPositionInfoSource::Error)), this, SLOT(nmeaError(QGeoPositionInfoSource::Error)));
308
                #else
309
                connect(nmea, SIGNAL(error(QGeoPositionInfoSource::Error)), this, SLOT(nmeaError(QGeoPositionInfoSource::Error)));
×
310
                connect(nmea, SIGNAL(updateTimeout()),this,SLOT(nmeaTimeout()));
×
311
                #endif
312
        }
313
        else qWarning() << "Cannot open serial port to NMEA device at port " << serial->portName();
×
314
        // This may leave an un-ready object. Must be cleaned-up later.
315
}
×
316
NMEALookupHelper::~NMEALookupHelper()
×
317
{
318
        if(nmea)
×
319
        {
320
                delete nmea;
×
321
                nmea=Q_NULLPTR;
×
322
        }
323
        if (serial)
×
324
        {
325
                if (serial->isOpen())
×
326
                {
327
                        //qDebug() << "NMEALookupHelper destructor: Close serial first";
328
                        serial->clear();
×
329
                        serial->close();
×
330
                }
331
                delete serial;
×
332
                serial=Q_NULLPTR;
×
333
        }
334
}
×
335

336
void NMEALookupHelper::query()
×
337
{
338
        if(isReady())
×
339
        {
340
                //kick off a single update request
341
                nmea->requestUpdate(3000);
×
342
        }
343
        else
344
                emit queryError("NMEA helper not ready");
×
345
}
×
346
void NMEALookupHelper::setPeriodicQuery(int interval)
×
347
{
348
        if(isReady())
×
349
        {
350
                if (interval==0)
×
351
                        nmea->stopUpdates();
×
352
                else
353
                {
354
                        nmea->setUpdateInterval(interval);
×
355
                        nmea->startUpdates();
×
356
                }
357
        }
358
        else
359
                emit queryError("NMEA helper not ready");
×
360
}
×
361

362
void NMEALookupHelper::nmeaUpdated(const QGeoPositionInfo &update)
×
363
{
364
        bool verbose=qApp->property("verbose").toBool();
×
365
        if (verbose)
×
366
                qDebug() << "NMEA updated";
×
367

368
        QGeoCoordinate coord=update.coordinate();
×
369
        QDateTime timestamp=update.timestamp();
×
370

371
        if (verbose)
×
372
        {
373
                qDebug() << " - time: " << timestamp.toString();
×
374
                qDebug() << " - location: Long=" << coord.longitude() << " Lat=" << coord.latitude() << " Alt=" << coord.altitude();
×
375
        }
376
        if (update.isValid()) // emit queryFinished(loc) with new location
×
377
        {
378
                StelLocation loc;
×
379
                loc.setLongitude(static_cast<float>(coord.longitude()));
×
380
                loc.setLatitude(static_cast<float>(coord.latitude()));
×
381
                // 2D fix may have only long/lat, invalid altitude.
382
                loc.altitude=( qIsNaN(coord.altitude()) ? 0 : static_cast<int>(floor(coord.altitude())));
×
383
                emit queryFinished(loc);
×
384
        }
×
385
        else
386
        {
387
                if (verbose)
×
388
                        qDebug() << "(This position update was an invalid package)";
×
389
                emit queryError("NMEA update: invalid package");
×
390
        }
391
}
×
392

393
void NMEALookupHelper::nmeaError(QGeoPositionInfoSource::Error error)
×
394
{
395
#if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
396
        emit queryError(QString("NMEA general error: %1").arg(QVariant::fromValue(error).toString()));
397
#else
398
        emit queryError(QString("NMEA general error: %1").arg(error));
×
399
#endif
400
}
×
401
#if (QT_VERSION<QT_VERSION_CHECK(6,0,0))
402
void NMEALookupHelper::nmeaTimeout()
×
403
{
404
        emit queryError("NMEA timeout");
×
405
}
×
406
#endif
407
#endif
408

409
StelLocationMgr::StelLocationMgr()
×
410
        : nmeaHelper(Q_NULLPTR), libGpsHelper(Q_NULLPTR)
×
411
{
412
        QSettings* conf = StelApp::getInstance().getSettings();
×
413

414
        // N.B. Further missing TZ names will be printed out in the log.txt. Resolve these by adding into data/timezone.tab file.
415
        loadTimeZoneFixes();
×
416

417
        loadCountries();
×
418
        loadRegions();
×
419
        // The line below allows to re-generate the location file, you still need to gunzip it manually afterward.
420
        if (conf->value("devel/convert_locations_list", false).toBool())
×
421
                generateBinaryLocationFile("data/base_locations.txt", false, "data/base_locations.bin");
×
422

423
        locations = loadCitiesBin("data/base_locations.bin.gz");
×
424
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
425
        locations.insert(loadCities("data/user_locations.txt", true));
×
426
#else
427
        locations.unite(loadCities("data/user_locations.txt", true));
428
#endif
429
        // Init to Paris France because it's the center of the world.
430
        lastResortLocation = locationForString(conf->value("init_location/last_location", "Paris, Western Europe").toString());
×
431
}
×
432

433
StelLocationMgr::~StelLocationMgr()
×
434
{
435
        if (nmeaHelper)
×
436
        {
437
                delete nmeaHelper;
×
438
                nmeaHelper=Q_NULLPTR;
×
439
        }
440
        if (libGpsHelper)
×
441
        {
442
                delete libGpsHelper;
×
443
                libGpsHelper=Q_NULLPTR;
×
444
        }
445
}
×
446

447
StelLocationMgr::StelLocationMgr(const LocationList &locations)
×
448
        : nmeaHelper(Q_NULLPTR), libGpsHelper(Q_NULLPTR)
×
449
{
450
        setLocations(locations);
×
451

452
        QSettings* conf = StelApp::getInstance().getSettings();
×
453
        // Init to Paris France because it's the center of the world.
454
        lastResortLocation = locationForString(conf->value("init_location/last_location", "Paris, Western Europe").toString());
×
455
}
×
456

457
void StelLocationMgr::setLocations(const LocationList &locations)
×
458
{
459
        this->locations.clear();
×
460
        for (const auto& loc : locations)
×
461
        {
462
                this->locations.insert(loc.getID(), loc);
×
463
        }
464

465
        emit locationListChanged();
×
466
}
×
467

468
void StelLocationMgr::generateBinaryLocationFile(const QString& fileName, bool isUserLocation, const QString& binFilePath) const
×
469
{
470
        qDebug() << "Generating a locations list...";
×
471
        const QMap<QString, StelLocation>& cities = loadCities(fileName, isUserLocation);
×
472
        QFile binfile(StelFileMgr::findFile(binFilePath, StelFileMgr::New));
×
473
        if(binfile.open(QIODevice::WriteOnly))
×
474
        {
475
                QDataStream out(&binfile);
×
476
                out.setVersion(QDataStream::Qt_5_2);
×
477
                out << cities;
×
478
                binfile.flush();
×
479
                binfile.close();
×
480
        }
×
481
        qDebug() << "[...] Please use 'gzip -nc base_locations.bin > base_locations.bin.gz' to pack a locations list.";
×
482
}
×
483

484
LocationMap StelLocationMgr::loadCitiesBin(const QString& fileName)
×
485
{
486
        QMap<QString, StelLocation> res;
×
487
        QString cityDataPath = StelFileMgr::findFile(fileName);
×
488
        if (cityDataPath.isEmpty())
×
489
                return res;
×
490

491
        QFile sourcefile(cityDataPath);
×
492
        if (!sourcefile.open(QIODevice::ReadOnly))
×
493
        {
494
                qWarning() << "ERROR: Could not open location data file: " << QDir::toNativeSeparators(cityDataPath);
×
495
                return res;
×
496
        }
497

498
        if (fileName.endsWith(".gz"))
×
499
        {
500
                QDataStream in(StelUtils::uncompress(sourcefile.readAll()));
×
501
                in.setVersion(QDataStream::Qt_5_2);
×
502
                in >> res;
×
503
        }
×
504
        else
505
        {
506
                QDataStream in(&sourcefile);
×
507
                in.setVersion(QDataStream::Qt_5_2);
×
508
                in >> res;
×
509
        }
×
510
        // Now res has all location data. However, some timezone names are not available in various versions of Qt.
511
        // Sanity checks: It seems we must translate timezone names. Quite a number on Windows, but also still some on Linux.
512
        QList<QByteArray> availableTimeZoneList=QTimeZone::availableTimeZoneIds();
×
513
        QStringList unknownTZlist;
×
514
        for (auto& loc : res)
×
515
        {
516
                if ((loc.ianaTimeZone!="LMST") && (loc.ianaTimeZone!="LTST") && ( ! availableTimeZoneList.contains(loc.ianaTimeZone.toUtf8())) )
×
517
                {
518
                        // TZ name which is currently unknown to Qt detected. See if we can translate it, if not: complain to qDebug().
519
                        QString fixTZname=sanitizeTimezoneStringFromLocationDB(loc.ianaTimeZone);
×
520
                        if (availableTimeZoneList.contains(fixTZname.toUtf8()))
×
521
                        {
522
                                loc.ianaTimeZone=fixTZname;
×
523
                        }
524
                        else
525
                        {
526
                                qDebug() << "StelLocationMgr::loadCitiesBin(): TimeZone for " << loc.name <<  " not found: " << loc.ianaTimeZone;
×
527
                                unknownTZlist.append(loc.ianaTimeZone);
×
528
                        }
529
                }
×
530
        }
531
        if (!unknownTZlist.isEmpty())
×
532
        {
533
                unknownTZlist.removeDuplicates();
×
534
                unknownTZ = true;
×
535
                qDebug() << "StelLocationMgr::loadCitiesBin(): Summary of unknown TimeZones:";
×
536
                for (const auto& tz : unknownTZlist)
×
537
                {
538
                        qDebug() << tz;
×
539
                }
540
                qDebug() << "Please report these timezone names (this logfile) to the Stellarium developers.";
×
541
                // Note to developers: Fill those names and replacements to the map above.
542
        }
543

544
        return res;
×
545
}
×
546

547
// Done in the following: TZ name sanitizing also for text file!
548
LocationMap StelLocationMgr::loadCities(const QString& fileName, bool isUserLocation)
×
549
{
550
        // Load the cities from data file
551
        QMap<QString, StelLocation> locations;
×
552
        QString cityDataPath = StelFileMgr::findFile(fileName);
×
553
        if (cityDataPath.isEmpty())
×
554
        {
555
                // Note it is quite normal not to have a user locations file (e.g. first run)
556
                if (!isUserLocation)
×
557
                        qWarning() << "WARNING: Failed to locate location data file: " << QDir::toNativeSeparators(fileName);
×
558
                return locations;
×
559
        }
560

561
        QFile sourcefile(cityDataPath);
×
562
        if (!sourcefile.open(QIODevice::ReadOnly | QIODevice::Text))
×
563
        {
564
                qWarning() << "ERROR: Could not open location data file: " << QDir::toNativeSeparators(cityDataPath);
×
565
                return locations;
×
566
        }
567

568
        // Read the data serialized from the file.
569
        // Code below borrowed from Marble (http://edu.kde.org/marble/)
570
        QTextStream sourcestream(&sourcefile);
×
571
#if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
572
        sourcestream.setEncoding(QStringConverter::Utf8);
573
#else
574
        sourcestream.setCodec("UTF-8");
×
575
#endif
576
        StelLocation loc;
×
577
        while (!sourcestream.atEnd())
×
578
        {
579
                const QString& rawline=sourcestream.readLine();
×
580
                if (rawline.isEmpty() || rawline.startsWith('#') || (rawline.split("\t").count() < 8))
×
581
                        continue;
×
582
                loc = StelLocation::createFromLine(rawline);
×
583
                loc.isUserLocation = isUserLocation;
×
584
                const QString& locId = loc.getID();
×
585

586
                if (locations.contains(locId))
×
587
                {
588
                        // Add the state in the name of the existing one and the new one to differentiate
589
                        StelLocation loc2 = locations[locId];
×
590
                        if (!loc2.state.isEmpty())
×
591
                                loc2.name += " ("+loc2.state+")";
×
592
                        // remove and re-add the fixed version
593
                        locations.remove(locId);
×
594
                        locations.insert(loc2.getID(), loc2);
×
595

596
                        if (!loc.state.isEmpty())
×
597
                                loc.name += " ("+loc.state+")";
×
598
                        locations.insert(loc.getID(), loc);
×
599
                }
×
600
                else
601
                {
602
                        locations.insert(locId, loc);
×
603
                }
604
        }
×
605
        sourcefile.close();
×
606
        return locations;
×
607
}
×
608

609
static float parseAngle(const QString& s, bool* ok)
×
610
{
611
        float ret;
612
        // First try normal decimal value.
613
        ret = s.toFloat(ok);
×
614
        if (*ok) return ret;
×
615
        // Try GPS coordinate like +121°33'38.28"
616
        static const QRegularExpression reg("([+-]?[\\d.]+)°(?:([\\d.]+)')?(?:([\\d.]+)\")?");
×
617
        QRegularExpressionMatch match=reg.match(s);
×
618
        if (match.hasMatch())
×
619
        {
620
                float deg = match.captured(1).toFloat(ok);
×
621
                if (!*ok) return 0;
×
622
                float min = match.captured(2).isEmpty()? 0 : match.captured(2).toFloat(ok);
×
623
                if (!*ok) return 0;
×
624
                float sec = match.captured(3).isEmpty()? 0 : match.captured(3).toFloat(ok);
×
625
                if (!*ok) return 0;
×
626
                if (deg < 0)
×
627
                        return deg - min / 60 - sec / 3600;
×
628
                else
629
                        return deg + min / 60 + sec / 3600;
×
630
        }
631
        return 0;
×
632
}
×
633

634
const StelLocation StelLocationMgr::locationForString(const QString& s) const
×
635
{
636
        auto iter = locations.find(s);
×
637
        if (iter!=locations.end())
×
638
        {
639
                return iter.value();
×
640
        }
641
        // Maybe this is a city and country names (old format of the data)?
642
        static const QRegularExpression cnreg("(.+),\\s+(.+)$");
×
643
        QRegularExpressionMatch cnMatch=cnreg.match(s);
×
644
        if (cnMatch.hasMatch())
×
645
        {
646
                // NOTE: This method will give wrong data for some Russians and U.S. locations
647
                //       (Asian locations for Russia and for locations on Hawaii for U.S.)
648
                QString city = cnMatch.captured(1).trimmed();
×
649
                QString country = cnMatch.captured(2).trimmed();
×
650
                auto iter = locations.find(QString("%1, %2").arg(city, pickRegionFromCountry(country)));
×
651
                if (iter!=locations.end())
×
652
                {
653
                        return iter.value();
×
654
                }
655
        }
×
656
        // Maybe a full Location line? (e.g. ObservationLists)
657
        if (s.count("\t")>6) // heuristic. Regular locations should not have tabs in the name.
×
658
                return StelLocation::createFromLine(s);
×
659

660
        StelLocation ret;
×
661
        // Maybe it is a coordinate set with elevation?
662
        static const QRegularExpression csreg("(.+),\\s*(.+),\\s*(.+)");
×
663
        QRegularExpressionMatch csMatch=csreg.match(s);
×
664
        if (csMatch.hasMatch())
×
665
        {
666
                bool ok;
667
                // We have a set of coordinates
668
                ret.setLatitude(parseAngle(csMatch.captured(1).trimmed(), &ok));
×
669
                if (!ok) ret.role = '!';
×
670
                ret.setLongitude(parseAngle(csMatch.captured(2).trimmed(), &ok));
×
671
                if (!ok) ret.role = '!';
×
672
                ret.altitude = csMatch.captured(3).trimmed().toInt(&ok);
×
673
                if (!ok) ret.role = '!';
×
674
                ret.name = QString("%1, %2").arg(QString::number(ret.getLatitude(), 'f', 2), QString::number(ret.getLongitude(), 'f', 2));
×
675
                ret.planetName = "Earth";
×
676
                return ret;
×
677
        }
678
        // Maybe it is a coordinate set without elevation? (e.g. GPS 25.107363,121.558807 )
679
        static const QRegularExpression reg("(?:(.+)\\s+)?(.+),\\s*(.+)"); // FIXME: Seems regexp is not very good
×
680
        QRegularExpressionMatch match=reg.match(s);
×
681
        if (match.hasMatch())
×
682
        {
683
                bool ok;
684
                // We have a set of coordinates
685
                ret.setLatitude(parseAngle(match.captured(2).trimmed(), &ok));
×
686
                if (!ok) ret.role = '!';
×
687
                ret.setLongitude(parseAngle(match.captured(3).trimmed(), &ok));
×
688
                if (!ok) ret.role = '!';
×
689
                ret.name = match.captured(1).trimmed();
×
690
                ret.planetName = "Earth";
×
691
                return ret;
×
692
        }
693
        // All attempts to decode have failed. Return invalid location with this name.
694
        ret.name=s;
×
695
        ret.planetName = "Earth";
×
696
        ret.role = '!';
×
697
        return ret;
×
698
}
×
699

700
const StelLocation StelLocationMgr::locationFromCLI() const
×
701
{
702
        StelLocation ret;
×
703
        QSettings* conf = StelApp::getInstance().getSettings();
×
704
        conf->beginGroup("location_run_once");
×
705

706
        const auto latVar = conf->value("latitude");
×
707
        if (latVar.isValid())
×
708
                ret.setLatitude(180/M_PI * latVar.toDouble());
×
709
        else
710
                ret.role = '!';
×
711

712
        const auto lonVar = conf->value("longitude");
×
713
        if (lonVar.isValid())
×
714
                ret.setLongitude(180/M_PI * lonVar.toDouble());
×
715
        else
716
                ret.role = '!';
×
717
        bool ok;
718
        ret.altitude = conf->value("altitude", 0).toInt(&ok);
×
719
        ret.planetName = conf->value("home_planet", "Earth").toString();
×
720
        ret.landscapeKey = conf->value("landscape_name", "guereins").toString();
×
721
        conf->endGroup();
×
722
        conf->remove("location_run_once");
×
723
        ret.state="CLI"; // flag this location with a marker for handling in LandscapeMgr::init(). state is not displayed anywhere, so I expect no issues from that.
×
724
        return ret;
×
725
}
×
726

727
// Get whether a location can be permanently added to the list of user locations
728
bool StelLocationMgr::canSaveUserLocation(const StelLocation& loc) const
×
729
{
730
        return loc.isValid() && locations.find(loc.getID())==locations.end();
×
731
}
732

733
// Add permanently a location to the list of user locations
734
bool StelLocationMgr::saveUserLocation(const StelLocation& loc)
×
735
{
736
        if (!canSaveUserLocation(loc))
×
737
                return false;
×
738

739
        // Add in the program
740
        locations[loc.getID()]=loc;
×
741

742
        //emit before saving the list
743
        emit locationListChanged();
×
744

745
        // Append to the user location file
746
        QString cityDataPath = StelFileMgr::findFile("data/user_locations.txt", StelFileMgr::Flags(StelFileMgr::Writable|StelFileMgr::File));
×
747
        if (cityDataPath.isEmpty())
×
748
        {
749
                if (!StelFileMgr::exists(StelFileMgr::getUserDir()+"/data"))
×
750
                {
751
                        if (!StelFileMgr::mkDir(StelFileMgr::getUserDir()+"/data"))
×
752
                        {
753
                                qWarning() << "ERROR - cannot create non-existent data directory" << QDir::toNativeSeparators(StelFileMgr::getUserDir()+"/data");
×
754
                                qWarning() << "Location cannot be saved";
×
755
                                return false;
×
756
                        }
757
                }
758

759
                cityDataPath = StelFileMgr::getUserDir()+"/data/user_locations.txt";
×
760
                qWarning() << "Will create a new user location file: " << QDir::toNativeSeparators(cityDataPath);
×
761
        }
762

763
        QFile sourcefile(cityDataPath);
×
764
        if (!sourcefile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append))
×
765
        {
766
                qWarning() << "ERROR: Could not open location data file: " << QDir::toNativeSeparators(cityDataPath);
×
767
                return false;
×
768
        }
769

770
        QTextStream outstream(&sourcefile);
×
771
#if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
772
        outstream.setEncoding(QStringConverter::Utf8);
773
#else
774
        outstream.setCodec("UTF-8");
×
775
#endif
776
        outstream << loc.serializeToLine() << '\n';
×
777
        sourcefile.close();
×
778

779
        return true;
×
780
}
×
781

782
// Get whether a location can be deleted from the list of user locations
783
// If the location comes from the base read only list, it cannot be deleted
784
bool StelLocationMgr::canDeleteUserLocation(const QString& id) const
×
785
{
786
        auto iter=locations.find(id);
×
787

788
        // If it's not known at all there is a problem
789
        if (iter==locations.end())
×
790
                return false;
×
791

792
        return iter.value().isUserLocation;
×
793
}
794

795
// Delete permanently the given location from the list of user locations
796
// If the location comes from the base read only list, it cannot be deleted and false is returned
797
bool StelLocationMgr::deleteUserLocation(const QString& id)
×
798
{
799
        if (!canDeleteUserLocation(id))
×
800
                return false;
×
801

802
        locations.remove(id);
×
803

804
        //emit before saving the list
805
        emit locationListChanged();
×
806

807
        // Resave the whole remaining user locations file
808
        QString cityDataPath = StelFileMgr::findFile("data/user_locations.txt", StelFileMgr::Writable);
×
809
        if (cityDataPath.isEmpty())
×
810
        {
811
                if (!StelFileMgr::exists(StelFileMgr::getUserDir()+"/data"))
×
812
                {
813
                        if (!StelFileMgr::mkDir(StelFileMgr::getUserDir()+"/data"))
×
814
                        {
815
                                qWarning() << "ERROR - cannot create non-existent data directory" << QDir::toNativeSeparators(StelFileMgr::getUserDir()+"/data");
×
816
                                qWarning() << "Location cannot be saved";
×
817
                                return false;
×
818
                        }
819
                }
820

821
                cityDataPath = StelFileMgr::getUserDir()+"/data/user_locations.txt";
×
822
                qWarning() << "Will create a new user location file: " << QDir::toNativeSeparators(cityDataPath);
×
823
        }
824

825
        QFile sourcefile(cityDataPath);
×
826
        if (!sourcefile.open(QIODevice::WriteOnly | QIODevice::Text))
×
827
        {
828
                qWarning() << "ERROR: Could not open location data file: " << QDir::toNativeSeparators(cityDataPath);
×
829
                return false;
×
830
        }
831

832
        QTextStream outstream(&sourcefile);
×
833
#if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
834
        outstream.setEncoding(QStringConverter::Utf8);
835
#else
836
        outstream.setCodec("UTF-8");
×
837
#endif
838

839
        for (QMap<QString, StelLocation>::ConstIterator iter=locations.constBegin();iter!=locations.constEnd();++iter)
×
840
        {
841
                if (iter.value().isUserLocation)
×
842
                {
843
                        outstream << iter.value().serializeToLine() << '\n';
×
844
                }
845
        }
846

847
        sourcefile.close();
×
848
        return true;
×
849
}
×
850

851
// lookup location from IP address.
852
void StelLocationMgr::locationFromIP()
×
853
{
854
        QSettings* conf = StelApp::getInstance().getSettings();
×
855
        QNetworkRequest req( QUrl( conf->value("main/geoip_api_url", "https://freegeoip.stellarium.org/json/").toString() ) );
×
856
        req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
×
857
        req.setRawHeader("User-Agent", StelUtils::getUserAgentString().toLatin1());
×
858
        QNetworkReply* networkReply=StelApp::getInstance().getNetworkAccessManager()->get(req);
×
859
        connect(networkReply, SIGNAL(finished()), this, SLOT(changeLocationFromNetworkLookup()));
×
860
}
×
861

862
#ifdef ENABLE_GPS
863
void StelLocationMgr::locationFromGPS(int interval)
×
864
{
865
        bool verbose=qApp->property("verbose").toBool();
×
866

867
#ifdef ENABLE_LIBGPS
868
        if(!libGpsHelper)
×
869
        {
870
                libGpsHelper = new LibGPSLookupHelper(this);
×
871
                connect(libGpsHelper, SIGNAL(queryFinished(StelLocation)), this, SLOT(changeLocationFromGPSQuery(StelLocation)));
×
872
                connect(libGpsHelper, SIGNAL(queryError(QString)), this, SLOT(gpsQueryError(QString)));
×
873
        }
874
        if(libGpsHelper->isReady())
×
875
        {
876
                if (interval<0)
×
877
                        libGpsHelper->query();
×
878
                else
879
                {
880
                        libGpsHelper->setPeriodicQuery(interval);
×
881
                        // It seemed possible to leave the LibGPShelper object alive once created, because it does not block
882
                        // access to the GPS device. However, under pathological circumstances (start/stop of GPSD daemon
883
                        // after first query and while Stellarium is running, annoying GPSD in other ways, etc.,
884
                        // the LibGPSLookupHelper may signal ready but still shows problems.
885
                        // It seems better to also destroy it after finish of queries here and in case of non-readiness below.
886
                        if (interval==0)
×
887
                        {
888
                                if (verbose)
×
889
                                        qDebug() << "Deactivating and deleting LibGPShelper...";
×
890
                                delete libGpsHelper;
×
891
                                libGpsHelper=Q_NULLPTR;
×
892
                                emit gpsQueryFinished(true); // signal "successful operation", avoid showing any error in GUI.
×
893
                                if (verbose)
×
894
                                        qDebug() << "Deactivating and deleting LibGPShelper... DONE";
×
895
                        }
896
                }
897
                return;
×
898
        }
899
        else
900
        {
901
                qDebug() << "LibGPSHelper not ready. Attempting a direct NMEA connection instead.";
×
902
                delete libGpsHelper;
×
903
                libGpsHelper=Q_NULLPTR;
×
904
        }
905
#endif
906
        if(!nmeaHelper)
×
907
        {
908
                if (verbose)
×
909
                        qDebug() << "Creating new NMEAhelper...";
×
910
                nmeaHelper = new NMEALookupHelper(this);
×
911
                connect(nmeaHelper, SIGNAL(queryFinished(StelLocation)), this, SLOT(changeLocationFromGPSQuery(StelLocation)));
×
912
                connect(nmeaHelper, SIGNAL(queryError(QString)), this, SLOT(gpsQueryError(QString)));
×
913
                if (verbose)
×
914
                        qDebug() << "Creating new NMEAhelper...done";
×
915
        }
916
        if(nmeaHelper->isReady())
×
917
        {
918
                if (interval<0)
×
919
                        nmeaHelper->query();
×
920
                else
921
                {
922
                        nmeaHelper->setPeriodicQuery(interval);
×
923
                        if (interval==0)
×
924
                        {
925
                                if (verbose)
×
926
                                        qDebug() << "Deactivating and deleting NMEAhelper...";
×
927
                                delete nmeaHelper;
×
928
                                nmeaHelper=Q_NULLPTR;
×
929
                                emit gpsQueryFinished(true); // signal "successful operation", avoid showing any error in GUI.
×
930
                                if (verbose)
×
931
                                        qDebug() << "Deactivating and deleting NMEAhelper... DONE";
×
932
                        }
933
                }
934
        }
935
        else
936
        {
937
                // something went wrong. However, a dysfunctional nmeaHelper may still exist, better delete it.
938
                if (verbose)
×
939
                        qDebug() << "nmeaHelper not ready. Something went wrong.";
×
940
                delete nmeaHelper;
×
941
                nmeaHelper=Q_NULLPTR;
×
942
#ifndef Q_OS_WIN
943
                emit gpsQueryFinished(false);
×
944
#else
945
                if (!positionSource)
946
                        positionSource = QGeoPositionInfoSource::createDefaultSource(this);
947
                if (positionSource)
948
                {
949
                        if (interval)
950
                        {
951
                                if (verbose)
952
                                        qDebug() << "Creating new positionSource...";
953
                                positionSource->setUpdateInterval(interval);
954
                                connect(positionSource, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(positionUpdated(QGeoPositionInfo)));
955
                                positionSource->startUpdates();
956
                                if (verbose)
957
                                        qDebug() << "Creating new positionSource...done";
958
                        }
959
                        else
960
                        {
961
                                if (verbose)
962
                                        qDebug() << "Deactivating and deleting gps...";
963
                                positionSource->stopUpdates();
964
                                delete positionSource;
965
                                positionSource=Q_NULLPTR;
966
                        }
967
                        emit gpsQueryFinished(true); // signal "successful operation", avoid showing any error in GUI.
968
                }
969
                else
970
                {
971
                        emit gpsQueryFinished(false);
972
                }
973
#endif
974
        }
975
}
976

977
#ifdef Q_OS_WIN
978
void StelLocationMgr::positionUpdated(QGeoPositionInfo info)
979
{
980
        bool verbose=qApp->property("verbose").toBool();
981
        StelLocation loc;
982
        if (info.isValid())
983
        {
984
                loc.setLongitude(info.coordinate().longitude());
985
                loc.setLatitude(info.coordinate().latitude());
986
                double a = info.coordinate().altitude();
987
                loc.altitude = qIsNaN(a) ? 0 : qRound(a);
988
                changeLocationFromGPSQuery(loc);
989
        }
990
        else
991
        {
992
                // something went wrong. However, a dysfunctional positionSource may still exist, better delete it.
993
                if (verbose)
994
                        qDebug() << "gps not ready. Something went wrong.";
995
                positionSource->stopUpdates();
996
                delete positionSource;
997
                positionSource=Q_NULLPTR;
998
                emit gpsQueryFinished(false);
999
        }
1000
}
1001
#endif
1002

1003
void StelLocationMgr::changeLocationFromGPSQuery(const StelLocation &locin)
×
1004
{
1005
        bool verbose=qApp->property("verbose").toBool();
×
1006
        StelCore *core=StelApp::getInstance().getCore();
×
1007
        StelLocation loc=locin;
×
1008
        loc.lightPollutionLuminance=StelLocation::DEFAULT_LIGHT_POLLUTION_LUMINANCE;
×
1009
        // Usually you don't leave your time zone with GPS.
1010
        loc.ianaTimeZone=core->getCurrentTimeZone();
×
1011
        loc.isUserLocation=true;
×
1012
        loc.planetName="Earth";
×
1013
        loc.name=QString("GPS %1%2 %3%4")
×
1014
                .arg(loc.getLatitude()<0?"S":"N").arg(qRound(abs(loc.getLatitude())))
×
1015
                .arg(loc.getLongitude()<0?"W":"E").arg(qRound(abs(loc.getLongitude())));
×
1016

1017
        core->moveObserverTo(loc, 0.0, 0.0);
×
1018
        if (nmeaHelper)
×
1019
        {
1020
                if (verbose)
×
1021
                        qDebug() << "Change location from NMEA... successful. NMEAhelper stays active.";
×
1022
        }
1023
        if (verbose)
×
1024
        {
1025
                qDebug() << "Location in progress: Long=" << loc.getLongitude() << " Lat=" << loc.getLatitude() << " Alt" << loc.altitude;
×
1026
                qDebug() << "New location named " << loc.name;
×
1027
                qDebug() << "queryOK, resetting GUI";
×
1028
        }
1029
        emit gpsQueryFinished(true);
×
1030
}
×
1031

1032
void StelLocationMgr::gpsQueryError(const QString &err)
×
1033
{
1034
        qWarning()<<err;
×
1035
        if (nmeaHelper)
×
1036
        {
1037
                nmeaHelper->setPeriodicQuery(0); // stop queries if they came periodically.
×
1038
                //qDebug() << "Would Close nmeaHelper during error...";
1039
                // We should close the serial line to let other programs use the GPS device. (Not needed for the GPSD solution!)
1040
                //delete nmeaHelper;
1041
                //nmeaHelper=Q_NULLPTR;
1042
                //qDebug() << "Would Close nmeaHelper during error.....successful";
1043
        }
1044
        qDebug() << "GPS queryError, resetting GUI";
×
1045
        emit gpsQueryFinished(false);
×
1046
}
×
1047
#endif
1048

1049
// slot that receives IP-based location data from the network.
1050
void StelLocationMgr::changeLocationFromNetworkLookup()
×
1051
{
1052
        StelCore *core=StelApp::getInstance().getCore();
×
1053
        QNetworkReply* networkReply = qobject_cast<QNetworkReply*>(sender());
×
1054
        if (!networkReply)
×
1055
            return;
×
1056

1057
        if (networkReply->error() == QNetworkReply::NoError && networkReply->bytesAvailable()>0)
×
1058
        {
1059
                // success                 
1060
                try
1061
                {
1062
                        QVariantMap locMap = StelJsonParser::parse(networkReply->readAll()).toMap();
×
1063

1064
                        QString ipRegion = locMap.value("region_name").toString();
×
1065
                        if (ipRegion.isEmpty())
×
1066
                                ipRegion = locMap.value("region").toString();
×
1067
                        QString ipCity = locMap.value("city").toString();
×
1068
                        QString ipCountry = locMap.value("country_name").toString(); // NOTE: Got a short name of country
×
1069
                        QString ipCountryCode = locMap.value("country_code").toString();
×
1070
                        QString ipTimeZone = locMap.value("time_zone").toString();
×
1071
                        if (ipTimeZone.isEmpty())
×
1072
                                ipTimeZone = locMap.value("timezone").toString();
×
1073
                        double latitude=locMap.value("latitude").toDouble();
×
1074
                        double longitude=locMap.value("longitude").toDouble();
×
1075

1076
                        qDebug() << "Got location" << QString("%1, %2, %3 (%4, %5; %6)").arg(ipCity, ipRegion, ipCountry).arg(latitude).arg(longitude).arg(ipTimeZone) << "for IP" << locMap.value("ip").toString();
×
1077

1078
                        StelLocation loc;
×
1079
                        loc.name    = (ipCity.isEmpty() ? QString("%1, %2").arg(latitude).arg(longitude) : ipCity);
×
1080
                        loc.state   = (ipRegion.isEmpty() ? "IPregion"  : ipRegion);
×
1081
                        loc.region = pickRegionFromCountryCode(ipCountryCode.isEmpty() ? "" : ipCountryCode.toLower());
×
1082
                        loc.role    = QChar(0x0058); // char 'X'
×
1083
                        loc.population = 0;
×
1084
                        loc.setLatitude  (static_cast<float>(latitude));
×
1085
                        loc.setLongitude (static_cast<float>(longitude));
×
1086
                        loc.altitude = 0;
×
1087
                        loc.lightPollutionLuminance = StelLocation::DEFAULT_LIGHT_POLLUTION_LUMINANCE;
×
1088
                        loc.ianaTimeZone = (ipTimeZone.isEmpty() ? "" : ipTimeZone);
×
1089
                        loc.planetName = "Earth";
×
1090
                        loc.landscapeKey = "";
×
1091

1092
                        // Ensure that ipTimeZone is a valid IANA timezone name!
1093
                        QTimeZone ipTZ(ipTimeZone.toUtf8());
×
1094
                        core->setCurrentTimeZone( !ipTZ.isValid() || ipTimeZone.isEmpty() ? "LMST" : ipTimeZone);
×
1095
                        core->moveObserverTo(loc, 0.0, 0.0);
×
1096
                        QSettings* conf = StelApp::getInstance().getSettings();
×
1097
                        conf->setValue("init_location/last_location", QString("%1, %2").arg(latitude).arg(longitude));
×
1098
                }
×
1099
                catch (const std::exception& e)
×
1100
                {
1101
                        qDebug() << "Failure getting IP-based location: answer is in not acceptable format! Error: " << e.what()
×
1102
                                        << "\nLet's use Paris, France as default location...";
×
1103
                        core->moveObserverTo(getLastResortLocation(), 0.0, 0.0); // Answer is not in JSON format! A possible block by DNS server or firewall
×
1104
                }
×
1105
        }
1106
        else
1107
        {
1108
                qDebug() << "Failure getting IP-based location: \n\t" << networkReply->errorString();
×
1109
                // If there is a problem, this must not change to some other location! Just ignore.
1110
        }
1111
        networkReply->deleteLater();
×
1112
}
1113

1114
LocationMap StelLocationMgr::pickLocationsNearby(const QString planetName, const float longitude, const float latitude, const float radiusDegrees)
×
1115
{
1116
        QMap<QString, StelLocation> results;
×
1117
        QMapIterator<QString, StelLocation> iter(locations);
×
1118
        while (iter.hasNext())
×
1119
        {
1120
                iter.next();
×
1121
                const StelLocation *loc=&iter.value();
×
1122
                if ( (loc->planetName == planetName) &&
×
1123
                                (StelLocation::distanceDegrees(longitude, latitude, loc->getLongitude(), loc->getLatitude()) <= radiusDegrees) )
×
1124
                {
1125
                        results.insert(iter.key(), iter.value());
×
1126
                }
1127
        }
1128
        return results;
×
1129
}
×
1130

1131
LocationMap StelLocationMgr::pickLocationsInRegion(const QString region)
×
1132
{
1133
        QMap<QString, StelLocation> results;
×
1134
        QMapIterator<QString, StelLocation> iter(locations);
×
1135
        while (iter.hasNext())
×
1136
        {
1137
                iter.next();
×
1138
                const StelLocation *loc=&iter.value();
×
1139
                if (loc->region == region)
×
1140
                {
1141
                        results.insert(iter.key(), iter.value());
×
1142
                }
1143
        }
1144
        return results;
×
1145
}
×
1146

1147
void StelLocationMgr::loadCountries()
×
1148
{
1149
        // Load ISO 3166-1 two-letter country codes from file
1150
        // The format is "[code][tab][country name containing spaces][newline]"
1151
        countryNameToCodeMap.clear();
×
1152
        QFile textFile(StelFileMgr::findFile("data/iso3166.tab"));
×
1153
        if(textFile.open(QFile::ReadOnly | QFile::Text))
×
1154
        {
1155
                QString line;
×
1156
                int readOk=0;
×
1157
                while(!textFile.atEnd())
×
1158
                {
1159
                        line = QString::fromUtf8(textFile.readLine());
×
1160
                        if (line.startsWith("//") || line.startsWith("#") || line.isEmpty())
×
1161
                                continue;
×
1162

1163
                        if (!line.isEmpty())
×
1164
                        {
1165
                                #if (QT_VERSION>=QT_VERSION_CHECK(5, 14, 0))
1166
                                QStringList list=line.split("\t", Qt::KeepEmptyParts);
×
1167
                                #else
1168
                                QStringList list=line.split("\t", QString::KeepEmptyParts);
1169
                                #endif
1170
                                QString code = list.at(0).trimmed().toLower();
×
1171
                                QString country = list.at(1).trimmed().replace("&", "and");
×
1172
                                countryNameToCodeMap.insert(country, code);
×
1173
                                readOk++;
×
1174
                        }
×
1175
                }
1176
                textFile.close();
×
1177
                if (readOk>0)
×
1178
                        qDebug() << "Loaded" << readOk << "countries";
×
1179
                else
1180
                        qDebug() << "ERROR: List of countries was not loaded!";
×
1181
        }
×
1182
        // aliases for some countries to backward compatibility
1183
        countryNameToCodeMap.insert("Russian Federation", "ru");
×
1184
        countryNameToCodeMap.insert("Taiwan (Provice of China)", "tw");
×
1185
}
×
1186

1187
void StelLocationMgr::loadTimeZoneFixes()
×
1188
{
1189
        QString tzFilePath = StelFileMgr::findFile(tzfFileName);
×
1190
        if (tzFilePath.isEmpty())
×
1191
        {
1192
                tzFilePath = StelFileMgr::findFile(tzfFileName, StelFileMgr::New);
×
1193
                // Create a default TZF (timezone fixes) file
1194
                QFile tzSrc(":/data/timezone.tab");
×
1195
                if (!tzSrc.copy(tzFilePath))
×
1196
                {
1197
                        qWarning() << "Cannot copy timezone fixes to " + QDir::toNativeSeparators(tzFilePath);
×
1198
                        return;
×
1199
                }
1200
                QFile dest(tzFilePath);
×
1201
                // fix file permissions
1202
                dest.setPermissions(dest.permissions() | QFile::WriteOwner);
×
1203
        }
×
1204
        QFile tzFile(tzFilePath);
×
1205

1206
        if(tzFile.open(QFile::ReadOnly | QFile::Text))
×
1207
        {
1208
                locationDBToIANAtranslations.clear();
×
1209
                QString line;
×
1210
                locationDBToIANAtranslations.insert("", "UTC"); // a first fix
×
1211
                int readOk = 1;
×
1212
                static const QRegularExpression dataRx("^([\\w\\/]+)\\t([\\w\\/\\+\\-:]+)\\s*(.*)$");
×
1213
                while(!tzFile.atEnd())
×
1214
                {
1215
                        line = QString::fromUtf8(tzFile.readLine());
×
1216
                        if (line.startsWith("//") || line.startsWith("#") || line.isEmpty())
×
1217
                                continue;
×
1218

1219
                        QRegularExpressionMatch dataMatch = dataRx.match(line);
×
1220
                        if (dataMatch.hasMatch())
×
1221
                        {
1222
                                // The first entry is the DB name, the second is as we display it in the program.
1223
                                locationDBToIANAtranslations.insert(dataMatch.captured(1).trimmed().toUtf8(), dataMatch.captured(2).trimmed().toUtf8());
×
1224
                                readOk++;
×
1225
                        }
1226
                }
×
1227
                qDebug() << "Loaded" << readOk << "fixes for time zones";
×
1228
                tzFile.close();
×
1229
        }
×
1230
}
×
1231

1232
void StelLocationMgr::loadRegions()
×
1233
{
1234
        QFile geoFile(StelFileMgr::findFile("data/regions-geoscheme.tab"));
×
1235
        if(geoFile.open(QFile::ReadOnly | QFile::Text))
×
1236
        {
1237
                QString line;
×
1238
                int readOk=0;
×
1239
                regions.clear();
×
1240
                countryCodeToRegionMap.clear();
×
1241
                while(!geoFile.atEnd())
×
1242
                {
1243
                        line = QString::fromUtf8(geoFile.readLine());
×
1244
                        if (line.startsWith("//") || line.startsWith("#") || line.isEmpty())
×
1245
                                continue;
×
1246

1247
                        if (!line.isEmpty())
×
1248
                        {
1249
                                #if (QT_VERSION>=QT_VERSION_CHECK(5, 14, 0))
1250
                                QStringList list=line.split("\t", Qt::KeepEmptyParts);
×
1251
                                #else
1252
                                QStringList list=line.split("\t", QString::KeepEmptyParts);
1253
                                #endif
1254

1255
                                QString regionName = list.at(2).trimmed();
×
1256
                                QString countries;
×
1257
                                if (list.size()>3)
×
1258
                                        countries = list.at(3).trimmed().toLower();
×
1259
                                GeoRegion region;
×
1260
                                region.code = list.at(0).trimmed().toInt();
×
1261
                                region.planet = list.at(1).trimmed();
×
1262
                                region.regionName = regionName;
×
1263
                                region.countries = countries;
×
1264

1265
                                if (!countries.isEmpty())
×
1266
                                {
1267
                                        #if (QT_VERSION>=QT_VERSION_CHECK(5, 14, 0))
1268
                                        QStringList country=countries.split(",", Qt::KeepEmptyParts);
×
1269
                                        #else
1270
                                        QStringList country=countries.split(",", QString::KeepEmptyParts);
1271
                                        #endif
1272
                                        for (int i = 0; i<country.size(); i++)
×
1273
                                        {
1274
                                                countryCodeToRegionMap.insert(country.at(i), regionName);
×
1275
                                        }
1276
                                }
×
1277

1278
                                regions.push_back(region);
×
1279
                                readOk++;
×
1280
                        }
×
1281
                }
1282
                geoFile.close();
×
1283
                if (readOk>0)
×
1284
                        qDebug() << "Loaded" << readOk << "regions";
×
1285
                else
1286
                        qDebug() << "ERROR: List of regions was not loaded!";
×
1287
        }
×
1288
}
×
1289

1290
QStringList StelLocationMgr::getRegionNames(const QString& planet) const
×
1291
{
1292
        QStringList allregions;
×
1293
        if (planet.isEmpty())
×
1294
        {
1295
                for (int i=0;i<regions.size();i++)
×
1296
                        allregions.append(regions.at(i).regionName);
×
1297
        }
1298
        else
1299
        {
1300
                for (int i=0;i<regions.size();i++)
×
1301
                {
1302
                        if (planet.contains(regions.at(i).planet, Qt::CaseInsensitive) && !planet.contains("Observer", Qt::CaseInsensitive))
×
1303
                                allregions.append(regions.at(i).regionName);
×
1304
                }
1305
        }
1306

1307
        return allregions;
×
1308
}
×
1309

1310
QString StelLocationMgr::pickRegionFromCountryCode(const QString countryCode)
×
1311
{
1312
        QMap<QString, QString>::ConstIterator i = countryCodeToRegionMap.find(countryCode);
×
1313
        return (i!=countryCodeToRegionMap.constEnd()) ? i.value() : QString();
×
1314
}
1315

1316
QString StelLocationMgr::pickRegionFromCountry(const QString country)
×
1317
{
1318
        QMap<QString, QString>::ConstIterator i = countryNameToCodeMap.find(country);
×
1319
        QString code = (i!=countryNameToCodeMap.constEnd()) ? i.value() : QString();
×
1320
        return pickRegionFromCountryCode(code);
×
1321
}
×
1322

1323
QString StelLocationMgr::pickRegionFromCode(int regionCode)
×
1324
{
1325
        QString region;
×
1326
        for(int i=0;i<regions.size();i++)
×
1327
        {
1328
                if (regions.at(i).code == regionCode)
×
1329
                        region = regions.at(i).regionName;
×
1330
        }
1331
        return region;
×
1332
}
×
1333

1334
// Check timezone string and return either the same or the corresponding string that we use in the Stellarium location database.
1335
// If timezone name starts with "UTC", always return unchanged.
1336
// This is required to store timezone names exactly as we know them, and not mix ours and current-iana spelling flavour.
1337
// In practice, reverse lookup to locationDBToIANAtranslations
1338
QString StelLocationMgr::sanitizeTimezoneStringForLocationDB(QString tzString)
×
1339
{
1340
        if (tzString.startsWith("UTC"))
×
1341
                return tzString;
×
1342
        QByteArray res=locationDBToIANAtranslations.key(tzString.toUtf8(), "---");
×
1343
        if ( res != "---")
×
1344
                return QString(res);
×
1345
        return tzString;
×
1346
}
×
1347

1348
// Attempt to translate a timezone name from those used in Stellarium's location database to a name which is valid
1349
// as ckeckable by QTimeZone::availableTimeZoneIds(). That list may be updated anytime and is known to differ
1350
// between OSes. Some spellings may be different, or in some cases some names get simply translated to "UTC+HH:MM" style.
1351
// The empty string gets translated to "UTC".
1352
QString StelLocationMgr::sanitizeTimezoneStringFromLocationDB(QString dbString)
×
1353
{
1354
        if (dbString.startsWith("UTC"))
×
1355
                return dbString;
×
1356
        // Maybe silences a debug later:
1357
        if (dbString=="")
×
1358
                return "UTC";
×
1359
        QByteArray res=locationDBToIANAtranslations.value(dbString.toUtf8(), "---");
×
1360
        if ( res != "---")
×
1361
                return QString(res);
×
1362
        return dbString;
×
1363
}
×
1364

1365
QStringList StelLocationMgr::getAllTimezoneNames() const
×
1366
{
1367
        QStringList ret;
×
1368

1369
        QMapIterator<QString, StelLocation> iter(locations);
×
1370
        while (iter.hasNext())
×
1371
        {
1372
                iter.next();
×
1373
                const StelLocation *loc=&iter.value();
×
1374
                QString tz(loc->ianaTimeZone);
×
1375
                if (!ret.contains(tz))
×
1376
                        ret.append(tz);
×
1377
        }
×
1378
        // 0.19: So far, this includes the existing names, but QTimeZone also has a few other names.
1379
        // Accept others after testing against sanitized names, and especially all UT+/- names!
1380

1381
        auto tzList = QTimeZone::availableTimeZoneIds(); // System dependent set of IANA timezone names.
×
1382
        for (const auto& tz : qAsConst(tzList))
×
1383
        {
1384
                QString tzcand=sanitizeTimezoneStringFromLocationDB(tz); // try to find name as we use it in the program.
×
1385
                if (!ret.contains(tzcand))
×
1386
                {
1387
                        //qDebug() << "Extra insert Qt/IANA TZ entry from QTimeZone::availableTimeZoneIds(): " << tz << "as" << tzcand;
1388
                        ret.append(QString(tzcand));
×
1389
                }
1390
        }
×
1391

1392
        // Special cases!
1393
        ret.append("LMST");
×
1394
        ret.append("LTST");
×
1395
        ret.append("system_default");
×
1396
        ret.sort();
×
1397
        return ret;
×
1398
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc