Kstars

locationdialoglite.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Artem Fedoskin <afedoskin3@gmail.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "locationdialoglite.h"
8
9#include "kspaths.h"
10#include "kstarsdata.h"
11#include "kstarslite.h"
12#include "Options.h"
13
14#include <QGeoPositionInfo>
15#include <QGeoPositionInfoSource>
16#include <QJsonArray>
17#include <QJsonDocument>
18#include <QJsonObject>
19#include <QJsonValue>
20#include <QNetworkAccessManager>
21#include <QNetworkConfigurationManager>
22#include <QNetworkReply>
23#include <QNetworkSession>
24#include <QQmlContext>
25#include <QSqlQuery>
26#include <QUrlQuery>
27
28LocationDialogLite::LocationDialogLite()
29{
31 KStarsData *data = KStarsData::Instance();
32
33 kstars->qmlEngine()->rootContext()->setContextProperty("CitiesModel", &m_cityList);
34
35 //initialize cities once KStarsData finishes loading everything
36 connect(kstars, SIGNAL(dataLoadFinished()), this, SLOT(initCityList()));
37 connect(data, SIGNAL(geoChanged()), this, SLOT(updateCurrentLocation()));
38
39 nam = new QNetworkAccessManager(this);
40 connect(nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(processLocationNameData(QNetworkReply*)));
41}
42
43void LocationDialogLite::getNameFromCoordinates(double latitude, double longitude)
44{
45 QString lat = QString::number(latitude);
46 QString lon = QString::number(longitude);
47 QString latlng(lat + ", " + lon);
48
49 QUrl url("http://maps.googleapis.com/maps/api/geocode/json");
50 QUrlQuery query;
51 query.addQueryItem("latlng", latlng);
52 url.setQuery(query);
53 qDebug() << "submitting request";
54
55 nam->get(QNetworkRequest(url));
56 connect(nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(processLocationNameData(QNetworkReply*)));
57}
58
59void LocationDialogLite::processLocationNameData(QNetworkReply *networkReply)
60{
61 if (!networkReply)
62 return;
63
64 if (!networkReply->error())
65 {
66 QJsonDocument document = QJsonDocument::fromJson(networkReply->readAll());
67
68 if (document.isObject())
69 {
70 QJsonObject obj = document.object();
71 QJsonValue val;
72
73 if (obj.contains(QStringLiteral("results")))
74 {
75 val = obj["results"];
76
77 QString city =
78 val.toArray()[0].toObject()["address_components"].toArray()[2].toObject()["long_name"].toString();
79 QString region =
80 val.toArray()[0].toObject()["address_components"].toArray()[3].toObject()["long_name"].toString();
81 QString country =
82 val.toArray()[0].toObject()["address_components"].toArray()[4].toObject()["long_name"].toString();
83
84 emit newNameFromCoordinates(city, region, country);
85 }
86 else
87 {
88 }
89 }
90 }
91 networkReply->deleteLater();
92}
93
94void LocationDialogLite::initCityList()
95{
96 KStarsData *data = KStarsData::Instance();
97 QStringList cities;
98 foreach (GeoLocation *loc, data->getGeoList())
99 {
100 QString name = loc->fullName();
101 cities.append(name);
102 filteredCityList.insert(name, loc);
103 }
104
105 //Sort the list of Cities alphabetically...note that filteredCityList may now have a different ordering!
106 m_cityList.setStringList(cities);
107 m_cityList.sort(0);
108
109 QStringList TZ;
110
111 for (int i = 0; i < 25; ++i)
112 TZ.append(QLocale().toString((double)(i - 12)));
113 setProperty("TZList", TZ);
114
115 QStringList DST;
116
117 foreach (const QString &key, data->getRulebook().keys())
118 {
119 if (!key.isEmpty())
120 DST.append(key);
121 }
122 setProperty("DSTRules", DST);
123}
124
125void LocationDialogLite::filterCity(const QString &city, const QString &province, const QString &country)
126{
127 KStarsData *data = KStarsData::Instance();
128 QStringList cities;
129 filteredCityList.clear();
130
131 foreach (GeoLocation *loc, data->getGeoList())
132 {
133 QString sc(loc->translatedName());
134 QString ss(loc->translatedCountry());
135 QString sp = "";
136 if (!loc->province().isEmpty())
137 sp = loc->translatedProvince();
138
139 if (sc.toLower().startsWith(city.toLower()) && sp.toLower().startsWith(province.toLower()) &&
140 ss.toLower().startsWith(country.toLower()))
141 {
142 QString name = loc->fullName();
143 cities.append(name);
144 filteredCityList.insert(name, loc);
145 }
146 }
147 m_cityList.setStringList(cities);
148 m_cityList.sort(0);
149
150 setProperty("currLocIndex", m_cityList.stringList().indexOf(m_currentLocation));
151}
152
153bool LocationDialogLite::addCity(const QString &city, const QString &province, const QString &country,
154 const QString &latitude, const QString &longitude,
155 const QString &TimeZoneString, const QString &TZRule)
156{
157 QSqlDatabase mycitydb = getDB();
158
159 if (mycitydb.isValid())
160 {
162
163 if (!city.isEmpty())
164 {
165 fullName += city;
166 }
167
168 if (!province.isEmpty())
169 {
170 fullName += ", " + province;
171 }
172
173 if (!country.isEmpty())
174 {
175 fullName += ", " + country;
176 }
177
178 if (m_cityList.stringList().contains(fullName))
179 {
180 return editCity(fullName, city, province, country, latitude, longitude, TimeZoneString, TZRule);
181 }
182
183 bool latOk(false), lngOk(false), tzOk(false);
184 dms lat = createDms(latitude, true, &latOk);
185 dms lng = createDms(longitude, true, &lngOk);
186 //TimeZoneString.replace( QLocale().decimalPoint(), "." );
187 double TZ = TimeZoneString.toDouble(&tzOk);
188
189 if (!latOk || !lngOk || !tzOk)
190 return false;
191
192 //Strip off white space
193 QString City = city.trimmed();
194 QString Province = province.trimmed();
195 QString Country = country.trimmed();
196 GeoLocation *g = nullptr;
197
198 QSqlQuery add_query(mycitydb);
199 add_query.prepare("INSERT INTO city(Name, Province, Country, Latitude, Longitude, TZ, TZRule) VALUES(:Name, "
200 ":Province, :Country, :Latitude, :Longitude, :TZ, :TZRule)");
201 add_query.bindValue(":Name", City);
202 add_query.bindValue(":Province", Province);
203 add_query.bindValue(":Country", Country);
204 add_query.bindValue(":Latitude", lat.toDMSString());
205 add_query.bindValue(":Longitude", lng.toDMSString());
206 add_query.bindValue(":TZ", TZ);
207 add_query.bindValue(":TZRule", TZRule);
208 if (add_query.exec() == false)
209 {
210 qWarning() << add_query.lastError() << endl;
211 return false;
212 }
213
214 //Add city to geoList
215 g = new GeoLocation(lng, lat, City, Province, Country, TZ, &KStarsData::Instance()->Rulebook[TZRule]);
216 KStarsData::Instance()->getGeoList().append(g);
217
218 mycitydb.commit();
219 mycitydb.close();
220 return true;
221 }
222
223 return false;
224}
225
226bool LocationDialogLite::deleteCity(const QString &fullName)
227{
228 QSqlDatabase mycitydb = getDB();
229 GeoLocation *geo = filteredCityList.value(fullName);
230
231 if (mycitydb.isValid() && geo && !geo->isReadOnly())
232 {
233 QSqlQuery delete_query(mycitydb);
234 delete_query.prepare("DELETE FROM city WHERE Name = :Name AND Province = :Province AND Country = :Country");
235 delete_query.bindValue(":Name", geo->name());
236 delete_query.bindValue(":Province", geo->province());
237 delete_query.bindValue(":Country", geo->country());
238 if (delete_query.exec() == false)
239 {
240 qWarning() << delete_query.lastError() << endl;
241 return false;
242 }
243
244 filteredCityList.remove(geo->fullName());
245 KStarsData::Instance()->getGeoList().removeOne(geo);
246 delete (geo);
247 mycitydb.commit();
248 mycitydb.close();
249 return true;
250 }
251 return false;
252}
253
254bool LocationDialogLite::editCity(const QString &fullName, const QString &city, const QString &province,
255 const QString &country, const QString &latitude,
256 const QString &longitude, const QString &TimeZoneString, const QString &TZRule)
257{
258 QSqlDatabase mycitydb = getDB();
259 GeoLocation *geo = filteredCityList.value(fullName);
260
261 bool latOk(false), lngOk(false), tzOk(false);
262 dms lat = createDms(latitude, true, &latOk);
263 dms lng = createDms(longitude, true, &lngOk);
264 double TZ = TimeZoneString.toDouble(&tzOk);
265
266 if (mycitydb.isValid() && geo && !geo->isReadOnly() && latOk && lngOk && tzOk)
267 {
268 QSqlQuery update_query(mycitydb);
269 update_query.prepare("UPDATE city SET Name = :newName, Province = :newProvince, Country = :newCountry, "
270 "Latitude = :Latitude, Longitude = :Longitude, TZ = :TZ, TZRule = :TZRule WHERE "
271 "Name = :Name AND Province = :Province AND Country = :Country");
272 update_query.bindValue(":newName", city);
273 update_query.bindValue(":newProvince", province);
274 update_query.bindValue(":newCountry", country);
275 update_query.bindValue(":Name", geo->name());
276 update_query.bindValue(":Province", geo->province());
277 update_query.bindValue(":Country", geo->country());
278 update_query.bindValue(":Latitude", lat.toDMSString());
279 update_query.bindValue(":Longitude", lng.toDMSString());
280 update_query.bindValue(":TZ", TZ);
281 update_query.bindValue(":TZRule", TZRule);
282 if (update_query.exec() == false)
283 {
284 qWarning() << update_query.lastError() << endl;
285 return false;
286 }
287
288 geo->setName(city);
289 geo->setProvince(province);
290 geo->setCountry(country);
291 geo->setLat(lat);
292 geo->setLong(lng);
293 geo->setTZ0(TZ);
294 geo->setTZRule(&KStarsData::Instance()->Rulebook[TZRule]);
295
296 //If we are changing current location update it
297 if (m_currentLocation == fullName)
298 {
299 setLocation(geo->fullName());
300 }
301
302 mycitydb.commit();
303 mycitydb.close();
304 return true;
305 }
306 return false;
307}
308
309QString LocationDialogLite::getCity(const QString &fullName)
310{
311 GeoLocation *geo = filteredCityList.value(fullName);
312
313 if (geo)
314 {
315 return geo->name();
316 }
317 return "";
318}
319
320QString LocationDialogLite::getProvince(const QString &fullName)
321{
322 GeoLocation *geo = filteredCityList.value(fullName);
323
324 if (geo)
325 {
326 return geo->province();
327 }
328 return "";
329}
330
331QString LocationDialogLite::getCountry(const QString &fullName)
332{
333 GeoLocation *geo = filteredCityList.value(fullName);
334
335 if (geo)
336 {
337 return geo->country();
338 }
339 return "";
340}
341
342double LocationDialogLite::getLatitude(const QString &fullName)
343{
344 GeoLocation *geo = filteredCityList.value(fullName);
345
346 if (geo)
347 {
348 return geo->lat()->Degrees();
349 }
350 return 0;
351}
352
353double LocationDialogLite::getLongitude(const QString &fullName)
354{
355 GeoLocation *geo = filteredCityList.value(fullName);
356
357 if (geo)
358 {
359 return geo->lng()->Degrees();
360 }
361 return 0;
362}
363
364int LocationDialogLite::getTZ(const QString &fullName)
365{
366 GeoLocation *geo = filteredCityList.value(fullName);
367 if (geo)
368 {
369 return m_TZList.indexOf(QString::number(geo->TZ0()));
370 }
371 return -1;
372}
373
374int LocationDialogLite::getDST(const QString &fullName)
375{
376 GeoLocation *geo = filteredCityList.value(fullName);
377 QMap<QString, TimeZoneRule> &Rulebook = KStarsData::Instance()->Rulebook;
378
379 if (geo)
380 {
381 foreach (const QString &key, Rulebook.keys())
382 {
383 if (!key.isEmpty() && geo->tzrule()->equals(&Rulebook[key]))
384 return m_DSTRules.indexOf(key);
385 }
386 }
387 return -1;
388}
389
390bool LocationDialogLite::isDuplicate(const QString &city, const QString &province, const QString &country)
391{
392 KStarsData *data = KStarsData::Instance();
393
394 foreach (GeoLocation *loc, data->getGeoList())
395 {
396 QString sc(loc->translatedName());
397 QString ss(loc->translatedCountry());
398 QString sp;
399
400 if (!loc->province().isEmpty())
401 sp = loc->translatedProvince();
402
403 if (sc.toLower() == city.toLower() && sp.toLower() == province.toLower() && ss.toLower() == country.toLower())
404 {
405 return true;
406 }
407 }
408 return false;
409}
410
411bool LocationDialogLite::isReadOnly(const QString &fullName)
412{
413 GeoLocation *geo = filteredCityList.value(fullName);
414
415 if (geo)
416 {
417 return geo->isReadOnly();
418 }
419 else
420 {
421 return true; //We return true if geolocation wasn't found
422 }
423}
424
425QSqlDatabase LocationDialogLite::getDB()
426{
427 QSqlDatabase mycitydb = QSqlDatabase::database("mycitydb");
428 QString dbfile = QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("mycitydb.sqlite");
429
430 // If it doesn't exist, create it
431 if (QFile::exists(dbfile) == false)
432 {
433 mycitydb.setDatabaseName(dbfile);
434 mycitydb.open();
435 QSqlQuery create_query(mycitydb);
436 QString query("CREATE TABLE city ( "
437 "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, "
438 "Name TEXT DEFAULT NULL, "
439 "Province TEXT DEFAULT NULL, "
440 "Country TEXT DEFAULT NULL, "
441 "Latitude TEXT DEFAULT NULL, "
442 "Longitude TEXT DEFAULT NULL, "
443 "TZ REAL DEFAULT NULL, "
444 "TZRule TEXT DEFAULT NULL)");
445 if (create_query.exec(query) == false)
446 {
447 qWarning() << create_query.lastError() << endl;
448 return QSqlDatabase();
449 }
450 }
451 else if (mycitydb.open() == false)
452 {
453 qWarning() << mycitydb.lastError() << endl;
454 return QSqlDatabase();
455 }
456
457 return mycitydb;
458}
459
460bool LocationDialogLite::checkLongLat(const QString &longitude, const QString &latitude)
461{
462 if (longitude.isEmpty() || latitude.isEmpty())
463 return false;
464
465 bool ok = false;
466 double lng = createDms(longitude, true, &ok).Degrees();
467
468 if (!ok || std::isnan(lng))
469 return false;
470
471 double lat = createDms(latitude, true, &ok).Degrees();
472
473 if (!ok || std::isnan(lat))
474 return false;
475
476 if (fabs(lng) > 180 || fabs(lat) > 90)
477 return false;
478
479 return true;
480}
481
482bool LocationDialogLite::setLocation(const QString &fullName)
483{
484 KStarsData *data = KStarsData::Instance();
485
486 GeoLocation *geo = filteredCityList.value(fullName);
487 if (!geo)
488 {
489 foreach (GeoLocation *loc, data->getGeoList())
490 {
491 if (loc->fullName() == fullName)
492 {
493 geo = loc;
494 break;
495 }
496 }
497 }
498
499 if (geo)
500 {
501 // set new location in options
502 data->setLocation(*geo);
503
504 // adjust local time to keep UT the same.
505 // create new LT without DST offset
506 KStarsDateTime ltime = geo->UTtoLT(data->ut());
507
508 // reset timezonerule to compute next dst change
509 geo->tzrule()->reset_with_ltime(ltime, geo->TZ0(), data->isTimeRunningForward());
510
511 // reset next dst change time
512 data->setNextDSTChange(geo->tzrule()->nextDSTChange());
513
514 // reset local sideral time
515 data->syncLST();
516
517 // Make sure Numbers, Moon, planets, and sky objects are updated immediately
518 data->setFullTimeUpdate();
519
520 // If the sky is in Horizontal mode and not tracking, reset focus such that
521 // Alt/Az remain constant.
522 if (!Options::isTracking() && Options::useAltAz())
523 {
524 SkyMapLite::Instance()->focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
525 }
526
527 // recalculate new times and objects
528 data->setSnapNextFocus();
530 return true;
531 }
532 return false;
533}
534
535dms LocationDialogLite::createDms(const QString &degree, bool deg, bool *ok)
536{
537 dms dmsAngle(0.0); // FIXME: Should we change this to NaN?
538 bool check = dmsAngle.setFromString(degree, deg);
539
540 if (ok)
541 {
542 *ok = check; //ok might be a null pointer!
543 }
544 return dmsAngle;
545}
546
547void LocationDialogLite::setCurrentLocation(const QString &loc)
548{
549 if (m_currentLocation != loc)
550 {
551 m_currentLocation = loc;
552 emit currentLocationChanged(loc);
553 }
554}
555
556void LocationDialogLite::updateCurrentLocation()
557{
558 currentGeo = KStarsData::Instance()->geo();
559 setCurrentLocation(currentGeo->fullName());
560}
Contains all relevant information for specifying a location on Earth: City Name, State/Province name,...
Definition geolocation.h:28
QString fullName() const
const CachingDms * lat() const
Definition geolocation.h:70
QString translatedCountry() const
QString province() const
QString translatedName() const
QString translatedProvince() const
KStarsData is the backbone of KStars.
Definition kstarsdata.h:74
CachingDms * lst()
Definition kstarsdata.h:226
void setNextDSTChange(const KStarsDateTime &dt)
Set the NextDSTChange member.
Definition kstarsdata.h:112
void setLocation(const GeoLocation &l)
Set the GeoLocation according to the argument.
QList< GeoLocation * > & getGeoList()
Definition kstarsdata.h:238
void setFullTimeUpdate()
The Sky is updated more frequently than the moon, which is updated more frequently than the planets.
void syncLST()
Sync the LST with the simulation clock.
const KStarsDateTime & ut() const
Definition kstarsdata.h:159
bool isTimeRunningForward() const
Returns true if time is running forward else false.
Definition kstarsdata.h:121
GeoLocation * geo()
Definition kstarsdata.h:232
const QMap< QString, TimeZoneRule > & getRulebook() const
Return map for daylight saving rules.
Definition kstarsdata.h:264
void setSnapNextFocus(bool b=true)
Disable or re-enable the slewing animation for the next Focus change.
Definition kstarsdata.h:285
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
This class loads QML files and connects SkyMapLite and KStarsData Unlike KStars class it is not a mai...
Definition kstarslite.h:47
void updateTime(const bool automaticDSTchange=true)
Update time-dependent data and (possibly) repaint the sky map.
static KStarsLite * Instance()
Definition kstarslite.h:77
QQmlApplicationEngine * qmlEngine()
Definition kstarslite.h:92
Q_INVOKABLE bool checkLongLat(const QString &longitude, const QString &latitude)
checkLongLat checks whether given longitude and latitude are valid
dms createDms(const QString &degree, bool deg, bool *ok)
TODO - port dmsBox to QML.
Q_INVOKABLE void getNameFromCoordinates(double latitude, double longitude)
Retrieve name of location by latitude and longitude.
SkyPoint * focus()
Retrieve the Focus point; the position on the sky at the center of the skymap.
Definition skymaplite.h:125
An angle, stored as degrees, but expressible in many ways.
Definition dms.h:38
const QString toDMSString(const bool forceSign=false, const bool machineReadable=false, const bool highPrecision=false) const
Definition dms.cpp:287
QString fullName(const PartType &type)
char * toString(const EngineQuery &query)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
GeoCoordinates geo(const QVariant &location)
QString name(StandardAction id)
KI18NLOCALEDATA_EXPORT KCountry country(const char *ianaId)
QString filePath(const QString &fileName) const const
bool exists() const const
void clear()
iterator insert(const Key &key, const T &value)
bool remove(const Key &key)
T value(const Key &key) const const
QByteArray readAll()
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
bool isObject() const const
QJsonObject object() const const
bool contains(QLatin1StringView key) const const
QJsonArray toArray() const const
void append(QList< T > &&value)
bool removeOne(const AT &t)
QList< Key > keys() const const
QNetworkReply * get(const QNetworkRequest &request)
NetworkError error() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
bool setProperty(const char *name, QVariant &&value)
void setContextProperty(const QString &name, QObject *value)
QQmlContext * rootContext() const const
QSqlDatabase database(const QString &connectionName, bool open)
bool isValid() const const
QSqlError lastError() const const
void setDatabaseName(const QString &name)
bool isEmpty() const const
QString number(double n, char format, int precision)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
double toDouble(bool *ok) const const
QString toLower() const const
QString trimmed() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
qsizetype indexOf(const QRegularExpression &re, qsizetype from) const const
void setStringList(const QStringList &strings)
virtual void sort(int column, Qt::SortOrder order) override
QStringList stringList() const const
QTextStream & endl(QTextStream &stream)
void setQuery(const QString &query, ParsingMode mode)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:41 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.