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

Stellarium / stellarium / 4820509958

pending completion
4820509958

push

github

Alexander V. Wolf
Move TZF file into user directory

22 of 22 new or added lines in 1 file covered. (100.0%)

14729 of 124965 relevant lines covered (11.79%)

26471.06 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

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

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

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

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

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

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

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

106
        StelLocation loc;
×
107

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

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

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

150

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

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

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

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

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

228
#endif
229

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

464
        emit locationListChanged();
×
465
}
×
466

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

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

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

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

542
        return res;
×
543
}
×
544

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

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

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

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

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

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

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

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

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

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

710
        const auto lonVar = conf->value("longitude");
×
711
        if (lonVar.isValid())
×
712
                ret.setLongitude(180/M_PI * lonVar.toDouble());
×
713
        else
714
                ret.role = '!';
×
715
        bool ok;
716
        ret.altitude = conf->value("altitude", 0).toInt(&ok);
×
717
        ret.planetName = conf->value("home_planet", "Earth").toString();
×
718
        ret.landscapeKey = conf->value("landscape_name", "guereins").toString();
×
719
        conf->endGroup();
×
720
        conf->remove("location_run_once");
×
721
        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.
×
722
        return ret;
×
723
}
×
724

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

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

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

740
        //emit before saving the list
741
        emit locationListChanged();
×
742

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

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

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

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

777
        return true;
×
778
}
×
779

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

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

790
        return iter.value().isUserLocation;
×
791
}
792

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

800
        locations.remove(id);
×
801

802
        //emit before saving the list
803
        emit locationListChanged();
×
804

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

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

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

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

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

845
        sourcefile.close();
×
846
        return true;
×
847
}
×
848

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

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

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

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

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

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

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

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

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

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

1074
                        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();
×
1075

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

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

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

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

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

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

1185
void StelLocationMgr::loadTimeZones()
×
1186
{
1187
        QString tzFilePath = StelFileMgr::findFile(tzfFileName, StelFileMgr::File);
×
1188
        if (tzFilePath.isEmpty())
×
1189
        {
1190
                tzFilePath = StelFileMgr::findFile(tzfFileName, StelFileMgr::New);
×
1191
                // Create a default TZF (time zone fixes) file
1192
                QFile tzSrc(":/data/timezone.tab");
×
1193
                if (!tzSrc.copy(tzFilePath))
×
1194
                {
1195
                        qWarning() << "Cannot copy time zones file to " + QDir::toNativeSeparators(tzFilePath);
×
1196
                        return;
×
1197
                }
1198
        }
×
1199
        QFile tzFile(tzFilePath);
×
1200

1201
        if(tzFile.open(QFile::ReadOnly | QFile::Text))
×
1202
        {
1203
                locationDBToIANAtranslations.clear();
×
1204
                QString line;
×
1205
                int readOk=0;
×
1206
                locationDBToIANAtranslations.insert("", "UTC");
×
1207
                while(!tzFile.atEnd())
×
1208
                {
1209
                        line = QString::fromUtf8(tzFile.readLine());
×
1210
                        if (line.startsWith("//") || line.startsWith("#") || line.isEmpty())
×
1211
                                continue;
×
1212

1213
                        if (!line.isEmpty())
×
1214
                        {
1215
                                #if (QT_VERSION>=QT_VERSION_CHECK(5, 14, 0))
1216
                                QStringList list=line.split("\t", Qt::KeepEmptyParts);
×
1217
                                #else
1218
                                QStringList list=line.split("\t", QString::KeepEmptyParts);
1219
                                #endif
1220

1221
                                // The first entry is the DB name, the second is as we display it in the program.
1222
                                locationDBToIANAtranslations.insert(list.at(0).trimmed().toLocal8Bit(), list.at(1).trimmed().toLocal8Bit());
×
1223
                                readOk++;
×
1224
                        }
×
1225
                }
1226
                qDebug() << "Loaded" << readOk << "fixes for time zones";
×
1227
                tzFile.close();
×
1228
        }
×
1229
}
×
1230

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

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

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

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

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

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

1306
        return allregions;
×
1307
}
×
1308

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

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

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

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

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

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

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

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

1391
        // Special cases!
1392
        ret.append("LMST");
×
1393
        ret.append("LTST");
×
1394
        ret.append("system_default");
×
1395
        ret.sort();
×
1396
        return ret;
×
1397
}
×
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