Kstars

locationdialoglite.cpp
1 /*
2  SPDX-FileCopyrightText: 2016 Artem Fedoskin <[email protected]>
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 
28 LocationDialogLite::LocationDialogLite()
29 {
30  KStarsLite *kstars = KStarsLite::Instance();
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 
43 void 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 
59 void 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 
94 void 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 
125 void 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 
153 bool 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 
226 bool 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 
254 bool 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 
309 QString 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 
320 QString 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 
331 QString 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 
342 double 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 
353 double 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 
364 int 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 
374 int 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 
390 bool 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 
411 bool 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 
425 QSqlDatabase 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 
460 bool 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 
482 bool 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 
535 dms 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 
547 void LocationDialogLite::setCurrentLocation(const QString &loc)
548 {
549  if (m_currentLocation != loc)
550  {
551  m_currentLocation = loc;
552  emit currentLocationChanged(loc);
553  }
554 }
555 
556 void LocationDialogLite::updateCurrentLocation()
557 {
558  currentGeo = KStarsData::Instance()->geo();
559  setCurrentLocation(currentGeo->fullName());
560 }
void append(const T &value)
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
const T value(const Key &key) const const
This class loads QML files and connects SkyMapLite and KStarsData Unlike KStars class it is not a mai...
Definition: kstarslite.h:46
QJsonObject object() const const
std::optional< QSqlQuery > query(const QString &queryStatement)
QList< GeoLocation * > & getGeoList()
Definition: kstarsdata.h:235
void setLocation(const GeoLocation &l)
Set the GeoLocation according to the argument.
Definition: kstarsdata.cpp:403
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
dms createDms(const QString &degree, bool deg, bool *ok)
TODO - port dmsBox to QML.
QTextStream & endl(QTextStream &stream)
QString number(int n, int base)
QString fullName() const
Definition: geolocation.cpp:46
Q_INVOKABLE bool checkLongLat(const QString &longitude, const QString &latitude)
checkLongLat checks whether given longitude and latitude are valid
KI18NLOCALEDATA_EXPORT KCountry country(const char *ianaId)
QNetworkReply::NetworkError error() const const
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QString trimmed() const const
Q_INVOKABLE void getNameFromCoordinates(double latitude, double longitude)
Retrieve name of location by latitude and longitude.
void clear()
CachingDms * lst()
Definition: kstarsdata.h:223
static KStarsLite * Instance()
Definition: kstarslite.h:77
void setContextProperty(const QString &name, QObject *value)
void setFullTimeUpdate()
The Sky is updated more frequently than the moon, which is updated more frequently than the planets.
Definition: kstarsdata.cpp:313
bool exists() const const
QStringList stringList() const const
void updateTime(const bool automaticDSTchange=true)
Update time-dependent data and (possibly) repaint the sky map.
Definition: kstarslite.cpp:234
QQmlContext * rootContext() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QNetworkReply * get(const QNetworkRequest &request)
bool contains(const QString &key) const const
QHash::iterator insert(const Key &key, const T &value)
void deleteLater()
const QString toDMSString(const bool forceSign=false, const bool machineReadable=false, const bool highPrecision=false) const
Definition: dms.cpp:279
const CachingDms * lat() const
Definition: geolocation.h:70
bool isValid() const const
void setDatabaseName(const QString &name)
char * toString(const T &value)
bool removeOne(const T &value)
bool isEmpty() const const
GeoLocation * geo()
Definition: kstarsdata.h:229
QString translatedName() const
Definition: geolocation.cpp:76
Country
QString translatedCountry() const
Definition: geolocation.cpp:97
QString translatedProvince() const
Definition: geolocation.cpp:90
virtual bool setFromString(const QString &s, bool isDeg=true)
Attempt to parse the string argument as a dms value, and set the dms object accordingly.
Definition: dms.cpp:48
GeoCoordinates geo(const QVariant &location)
bool isObject() const const
int indexOf(QStringView str, int from) const const
double toDouble(bool *ok) const const
QJsonArray toArray() const const
QString province() const
Definition: geolocation.h:115
void setQuery(const QString &query, QUrl::ParsingMode mode)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
const QMap< QString, TimeZoneRule > & getRulebook() const
Return map for daylight saving rules.
Definition: kstarsdata.h:261
An angle, stored as degrees, but expressible in many ways.
Definition: dms.h:37
void setSnapNextFocus(bool b=true)
Disable or re-enable the slewing animation for the next Focus change.
Definition: kstarsdata.h:282
const KStarsDateTime & ut() const
Definition: kstarsdata.h:156
bool setProperty(const char *name, const QVariant &value)
QString toLower() const const
QList< Key > keys() const const
int remove(const Key &key)
const double & Degrees() const
Definition: dms.h:141
const char * name(StandardAction id)
QString filePath(const QString &fileName) const const
bool isTimeRunningForward() const
Returns true if time is running forward else false.
Definition: kstarsdata.h:118
SkyPoint * focus()
Retrieve the Focus point; the position on the sky at the center of the skymap.
Definition: skymaplite.h:125
void HorizontalToEquatorial(const dms *LST, const dms *lat)
Determine the (RA, Dec) coordinates of the SkyPoint from its (Altitude, Azimuth) coordinates,...
Definition: skypoint.cpp:143
virtual void sort(int column, Qt::SortOrder order) override
QByteArray readAll()
void setNextDSTChange(const KStarsDateTime &dt)
Set the NextDSTChange member.
Definition: kstarsdata.h:109
void setStringList(const QStringList &strings)
QQmlApplicationEngine * qmlEngine()
Definition: kstarslite.h:92
QSqlDatabase database(const QString &connectionName, bool open)
QSqlError lastError() const const
QString fullName(const PartType &type)
void syncLST()
Sync the LST with the simulation clock.
Definition: kstarsdata.cpp:322
Relevant data about an observing location on Earth.
Definition: geolocation.h:27
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 04:00:55 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.