File indexing completed on 2024-04-14 14:11:00

0001 /*
0002     SPDX-FileCopyrightText: 2016 Artem Fedoskin <afedoskin3@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "locationdialoglite.h"
0008 
0009 #include "kspaths.h"
0010 #include "kstarsdata.h"
0011 #include "kstarslite.h"
0012 #include "Options.h"
0013 
0014 #include <QGeoPositionInfo>
0015 #include <QGeoPositionInfoSource>
0016 #include <QJsonArray>
0017 #include <QJsonDocument>
0018 #include <QJsonObject>
0019 #include <QJsonValue>
0020 #include <QNetworkAccessManager>
0021 #include <QNetworkConfigurationManager>
0022 #include <QNetworkReply>
0023 #include <QNetworkSession>
0024 #include <QQmlContext>
0025 #include <QSqlQuery>
0026 #include <QUrlQuery>
0027 
0028 LocationDialogLite::LocationDialogLite()
0029 {
0030     KStarsLite *kstars = KStarsLite::Instance();
0031     KStarsData *data = KStarsData::Instance();
0032 
0033     kstars->qmlEngine()->rootContext()->setContextProperty("CitiesModel", &m_cityList);
0034 
0035     //initialize cities once KStarsData finishes loading everything
0036     connect(kstars, SIGNAL(dataLoadFinished()), this, SLOT(initCityList()));
0037     connect(data, SIGNAL(geoChanged()), this, SLOT(updateCurrentLocation()));
0038 
0039     nam = new QNetworkAccessManager(this);
0040     connect(nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(processLocationNameData(QNetworkReply*)));
0041 }
0042 
0043 void LocationDialogLite::getNameFromCoordinates(double latitude, double longitude)
0044 {
0045     QString lat = QString::number(latitude);
0046     QString lon = QString::number(longitude);
0047     QString latlng(lat + ", " + lon);
0048 
0049     QUrl url("http://maps.googleapis.com/maps/api/geocode/json");
0050     QUrlQuery query;
0051     query.addQueryItem("latlng", latlng);
0052     url.setQuery(query);
0053     qDebug() << "submitting request";
0054 
0055     nam->get(QNetworkRequest(url));
0056     connect(nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(processLocationNameData(QNetworkReply*)));
0057 }
0058 
0059 void LocationDialogLite::processLocationNameData(QNetworkReply *networkReply)
0060 {
0061     if (!networkReply)
0062         return;
0063 
0064     if (!networkReply->error())
0065     {
0066         QJsonDocument document = QJsonDocument::fromJson(networkReply->readAll());
0067 
0068         if (document.isObject())
0069         {
0070             QJsonObject obj = document.object();
0071             QJsonValue val;
0072 
0073             if (obj.contains(QStringLiteral("results")))
0074             {
0075                 val = obj["results"];
0076 
0077                 QString city =
0078                     val.toArray()[0].toObject()["address_components"].toArray()[2].toObject()["long_name"].toString();
0079                 QString region =
0080                     val.toArray()[0].toObject()["address_components"].toArray()[3].toObject()["long_name"].toString();
0081                 QString country =
0082                     val.toArray()[0].toObject()["address_components"].toArray()[4].toObject()["long_name"].toString();
0083 
0084                 emit newNameFromCoordinates(city, region, country);
0085             }
0086             else
0087             {
0088             }
0089         }
0090     }
0091     networkReply->deleteLater();
0092 }
0093 
0094 void LocationDialogLite::initCityList()
0095 {
0096     KStarsData *data = KStarsData::Instance();
0097     QStringList cities;
0098     foreach (GeoLocation *loc, data->getGeoList())
0099     {
0100         QString name = loc->fullName();
0101         cities.append(name);
0102         filteredCityList.insert(name, loc);
0103     }
0104 
0105     //Sort the list of Cities alphabetically...note that filteredCityList may now have a different ordering!
0106     m_cityList.setStringList(cities);
0107     m_cityList.sort(0);
0108 
0109     QStringList TZ;
0110 
0111     for (int i = 0; i < 25; ++i)
0112         TZ.append(QLocale().toString((double)(i - 12)));
0113     setProperty("TZList", TZ);
0114 
0115     QStringList DST;
0116 
0117     foreach (const QString &key, data->getRulebook().keys())
0118     {
0119         if (!key.isEmpty())
0120             DST.append(key);
0121     }
0122     setProperty("DSTRules", DST);
0123 }
0124 
0125 void LocationDialogLite::filterCity(const QString &city, const QString &province, const QString &country)
0126 {
0127     KStarsData *data = KStarsData::Instance();
0128     QStringList cities;
0129     filteredCityList.clear();
0130 
0131     foreach (GeoLocation *loc, data->getGeoList())
0132     {
0133         QString sc(loc->translatedName());
0134         QString ss(loc->translatedCountry());
0135         QString sp = "";
0136         if (!loc->province().isEmpty())
0137             sp = loc->translatedProvince();
0138 
0139         if (sc.toLower().startsWith(city.toLower()) && sp.toLower().startsWith(province.toLower()) &&
0140             ss.toLower().startsWith(country.toLower()))
0141         {
0142             QString name = loc->fullName();
0143             cities.append(name);
0144             filteredCityList.insert(name, loc);
0145         }
0146     }
0147     m_cityList.setStringList(cities);
0148     m_cityList.sort(0);
0149 
0150     setProperty("currLocIndex", m_cityList.stringList().indexOf(m_currentLocation));
0151 }
0152 
0153 bool LocationDialogLite::addCity(const QString &city, const QString &province, const QString &country,
0154                                  const QString &latitude, const QString &longitude,
0155                                  const QString &TimeZoneString, const QString &TZRule)
0156 {
0157     QSqlDatabase mycitydb = getDB();
0158 
0159     if (mycitydb.isValid())
0160     {
0161         QString fullName;
0162 
0163         if (!city.isEmpty())
0164         {
0165             fullName += city;
0166         }
0167 
0168         if (!province.isEmpty())
0169         {
0170             fullName += ", " + province;
0171         }
0172 
0173         if (!country.isEmpty())
0174         {
0175             fullName += ", " + country;
0176         }
0177 
0178         if (m_cityList.stringList().contains(fullName))
0179         {
0180             return editCity(fullName, city, province, country, latitude, longitude, TimeZoneString, TZRule);
0181         }
0182 
0183         bool latOk(false), lngOk(false), tzOk(false);
0184         dms lat = createDms(latitude, true, &latOk);
0185         dms lng = createDms(longitude, true, &lngOk);
0186         //TimeZoneString.replace( QLocale().decimalPoint(), "." );
0187         double TZ = TimeZoneString.toDouble(&tzOk);
0188 
0189         if (!latOk || !lngOk || !tzOk)
0190             return false;
0191 
0192         //Strip off white space
0193         QString City     = city.trimmed();
0194         QString Province = province.trimmed();
0195         QString Country  = country.trimmed();
0196         GeoLocation *g   = nullptr;
0197 
0198         QSqlQuery add_query(mycitydb);
0199         add_query.prepare("INSERT INTO city(Name, Province, Country, Latitude, Longitude, TZ, TZRule) VALUES(:Name, "
0200                           ":Province, :Country, :Latitude, :Longitude, :TZ, :TZRule)");
0201         add_query.bindValue(":Name", City);
0202         add_query.bindValue(":Province", Province);
0203         add_query.bindValue(":Country", Country);
0204         add_query.bindValue(":Latitude", lat.toDMSString());
0205         add_query.bindValue(":Longitude", lng.toDMSString());
0206         add_query.bindValue(":TZ", TZ);
0207         add_query.bindValue(":TZRule", TZRule);
0208         if (add_query.exec() == false)
0209         {
0210             qWarning() << add_query.lastError() << endl;
0211             return false;
0212         }
0213 
0214         //Add city to geoList
0215         g = new GeoLocation(lng, lat, City, Province, Country, TZ, &KStarsData::Instance()->Rulebook[TZRule]);
0216         KStarsData::Instance()->getGeoList().append(g);
0217 
0218         mycitydb.commit();
0219         mycitydb.close();
0220         return true;
0221     }
0222 
0223     return false;
0224 }
0225 
0226 bool LocationDialogLite::deleteCity(const QString &fullName)
0227 {
0228     QSqlDatabase mycitydb = getDB();
0229     GeoLocation *geo      = filteredCityList.value(fullName);
0230 
0231     if (mycitydb.isValid() && geo && !geo->isReadOnly())
0232     {
0233         QSqlQuery delete_query(mycitydb);
0234         delete_query.prepare("DELETE FROM city WHERE Name = :Name AND Province = :Province AND Country = :Country");
0235         delete_query.bindValue(":Name", geo->name());
0236         delete_query.bindValue(":Province", geo->province());
0237         delete_query.bindValue(":Country", geo->country());
0238         if (delete_query.exec() == false)
0239         {
0240             qWarning() << delete_query.lastError() << endl;
0241             return false;
0242         }
0243 
0244         filteredCityList.remove(geo->fullName());
0245         KStarsData::Instance()->getGeoList().removeOne(geo);
0246         delete (geo);
0247         mycitydb.commit();
0248         mycitydb.close();
0249         return true;
0250     }
0251     return false;
0252 }
0253 
0254 bool LocationDialogLite::editCity(const QString &fullName, const QString &city, const QString &province,
0255                                   const QString &country, const QString &latitude,
0256                                   const QString &longitude, const QString &TimeZoneString, const QString &TZRule)
0257 {
0258     QSqlDatabase mycitydb = getDB();
0259     GeoLocation *geo      = filteredCityList.value(fullName);
0260 
0261     bool latOk(false), lngOk(false), tzOk(false);
0262     dms lat   = createDms(latitude, true, &latOk);
0263     dms lng   = createDms(longitude, true, &lngOk);
0264     double TZ = TimeZoneString.toDouble(&tzOk);
0265 
0266     if (mycitydb.isValid() && geo && !geo->isReadOnly() && latOk && lngOk && tzOk)
0267     {
0268         QSqlQuery update_query(mycitydb);
0269         update_query.prepare("UPDATE city SET Name = :newName, Province = :newProvince, Country = :newCountry, "
0270                              "Latitude = :Latitude, Longitude = :Longitude, TZ = :TZ, TZRule = :TZRule WHERE "
0271                              "Name = :Name AND Province = :Province AND Country = :Country");
0272         update_query.bindValue(":newName", city);
0273         update_query.bindValue(":newProvince", province);
0274         update_query.bindValue(":newCountry", country);
0275         update_query.bindValue(":Name", geo->name());
0276         update_query.bindValue(":Province", geo->province());
0277         update_query.bindValue(":Country", geo->country());
0278         update_query.bindValue(":Latitude", lat.toDMSString());
0279         update_query.bindValue(":Longitude", lng.toDMSString());
0280         update_query.bindValue(":TZ", TZ);
0281         update_query.bindValue(":TZRule", TZRule);
0282         if (update_query.exec() == false)
0283         {
0284             qWarning() << update_query.lastError() << endl;
0285             return false;
0286         }
0287 
0288         geo->setName(city);
0289         geo->setProvince(province);
0290         geo->setCountry(country);
0291         geo->setLat(lat);
0292         geo->setLong(lng);
0293         geo->setTZ0(TZ);
0294         geo->setTZRule(&KStarsData::Instance()->Rulebook[TZRule]);
0295 
0296         //If we are changing current location update it
0297         if (m_currentLocation == fullName)
0298         {
0299             setLocation(geo->fullName());
0300         }
0301 
0302         mycitydb.commit();
0303         mycitydb.close();
0304         return true;
0305     }
0306     return false;
0307 }
0308 
0309 QString LocationDialogLite::getCity(const QString &fullName)
0310 {
0311     GeoLocation *geo = filteredCityList.value(fullName);
0312 
0313     if (geo)
0314     {
0315         return geo->name();
0316     }
0317     return "";
0318 }
0319 
0320 QString LocationDialogLite::getProvince(const QString &fullName)
0321 {
0322     GeoLocation *geo = filteredCityList.value(fullName);
0323 
0324     if (geo)
0325     {
0326         return geo->province();
0327     }
0328     return "";
0329 }
0330 
0331 QString LocationDialogLite::getCountry(const QString &fullName)
0332 {
0333     GeoLocation *geo = filteredCityList.value(fullName);
0334 
0335     if (geo)
0336     {
0337         return geo->country();
0338     }
0339     return "";
0340 }
0341 
0342 double LocationDialogLite::getLatitude(const QString &fullName)
0343 {
0344     GeoLocation *geo = filteredCityList.value(fullName);
0345 
0346     if (geo)
0347     {
0348         return geo->lat()->Degrees();
0349     }
0350     return 0;
0351 }
0352 
0353 double LocationDialogLite::getLongitude(const QString &fullName)
0354 {
0355     GeoLocation *geo = filteredCityList.value(fullName);
0356 
0357     if (geo)
0358     {
0359         return geo->lng()->Degrees();
0360     }
0361     return 0;
0362 }
0363 
0364 int LocationDialogLite::getTZ(const QString &fullName)
0365 {
0366     GeoLocation *geo = filteredCityList.value(fullName);
0367     if (geo)
0368     {
0369         return m_TZList.indexOf(QString::number(geo->TZ0()));
0370     }
0371     return -1;
0372 }
0373 
0374 int LocationDialogLite::getDST(const QString &fullName)
0375 {
0376     GeoLocation *geo                      = filteredCityList.value(fullName);
0377     QMap<QString, TimeZoneRule> &Rulebook = KStarsData::Instance()->Rulebook;
0378 
0379     if (geo)
0380     {
0381         foreach (const QString &key, Rulebook.keys())
0382         {
0383             if (!key.isEmpty() && geo->tzrule()->equals(&Rulebook[key]))
0384                 return m_DSTRules.indexOf(key);
0385         }
0386     }
0387     return -1;
0388 }
0389 
0390 bool LocationDialogLite::isDuplicate(const QString &city, const QString &province, const QString &country)
0391 {
0392     KStarsData *data = KStarsData::Instance();
0393 
0394     foreach (GeoLocation *loc, data->getGeoList())
0395     {
0396         QString sc(loc->translatedName());
0397         QString ss(loc->translatedCountry());
0398         QString sp;
0399 
0400         if (!loc->province().isEmpty())
0401             sp = loc->translatedProvince();
0402 
0403         if (sc.toLower() == city.toLower() && sp.toLower() == province.toLower() && ss.toLower() == country.toLower())
0404         {
0405             return true;
0406         }
0407     }
0408     return false;
0409 }
0410 
0411 bool LocationDialogLite::isReadOnly(const QString &fullName)
0412 {
0413     GeoLocation *geo = filteredCityList.value(fullName);
0414 
0415     if (geo)
0416     {
0417         return geo->isReadOnly();
0418     }
0419     else
0420     {
0421         return true; //We return true if geolocation wasn't found
0422     }
0423 }
0424 
0425 QSqlDatabase LocationDialogLite::getDB()
0426 {
0427     QSqlDatabase mycitydb = QSqlDatabase::database("mycitydb");
0428     QString dbfile        = QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("mycitydb.sqlite");
0429 
0430     // If it doesn't exist, create it
0431     if (QFile::exists(dbfile) == false)
0432     {
0433         mycitydb.setDatabaseName(dbfile);
0434         mycitydb.open();
0435         QSqlQuery create_query(mycitydb);
0436         QString query("CREATE TABLE city ( "
0437                       "id INTEGER DEFAULT NULL PRIMARY KEY AUTOINCREMENT, "
0438                       "Name TEXT DEFAULT NULL, "
0439                       "Province TEXT DEFAULT NULL, "
0440                       "Country TEXT DEFAULT NULL, "
0441                       "Latitude TEXT DEFAULT NULL, "
0442                       "Longitude TEXT DEFAULT NULL, "
0443                       "TZ REAL DEFAULT NULL, "
0444                       "TZRule TEXT DEFAULT NULL)");
0445         if (create_query.exec(query) == false)
0446         {
0447             qWarning() << create_query.lastError() << endl;
0448             return QSqlDatabase();
0449         }
0450     }
0451     else if (mycitydb.open() == false)
0452     {
0453         qWarning() << mycitydb.lastError() << endl;
0454         return QSqlDatabase();
0455     }
0456 
0457     return mycitydb;
0458 }
0459 
0460 bool LocationDialogLite::checkLongLat(const QString &longitude, const QString &latitude)
0461 {
0462     if (longitude.isEmpty() || latitude.isEmpty())
0463         return false;
0464 
0465     bool ok = false;
0466     double lng = createDms(longitude, true, &ok).Degrees();
0467 
0468     if (!ok || std::isnan(lng))
0469         return false;
0470 
0471     double lat = createDms(latitude, true, &ok).Degrees();
0472 
0473     if (!ok || std::isnan(lat))
0474         return false;
0475 
0476     if (fabs(lng) > 180 || fabs(lat) > 90)
0477         return false;
0478 
0479     return true;
0480 }
0481 
0482 bool LocationDialogLite::setLocation(const QString &fullName)
0483 {
0484     KStarsData *data = KStarsData::Instance();
0485 
0486     GeoLocation *geo = filteredCityList.value(fullName);
0487     if (!geo)
0488     {
0489         foreach (GeoLocation *loc, data->getGeoList())
0490         {
0491             if (loc->fullName() == fullName)
0492             {
0493                 geo = loc;
0494                 break;
0495             }
0496         }
0497     }
0498 
0499     if (geo)
0500     {
0501         // set new location in options
0502         data->setLocation(*geo);
0503 
0504         // adjust local time to keep UT the same.
0505         // create new LT without DST offset
0506         KStarsDateTime ltime = geo->UTtoLT(data->ut());
0507 
0508         // reset timezonerule to compute next dst change
0509         geo->tzrule()->reset_with_ltime(ltime, geo->TZ0(), data->isTimeRunningForward());
0510 
0511         // reset next dst change time
0512         data->setNextDSTChange(geo->tzrule()->nextDSTChange());
0513 
0514         // reset local sideral time
0515         data->syncLST();
0516 
0517         // Make sure Numbers, Moon, planets, and sky objects are updated immediately
0518         data->setFullTimeUpdate();
0519 
0520         // If the sky is in Horizontal mode and not tracking, reset focus such that
0521         // Alt/Az remain constant.
0522         if (!Options::isTracking() && Options::useAltAz())
0523         {
0524             SkyMapLite::Instance()->focus()->HorizontalToEquatorial(data->lst(), data->geo()->lat());
0525         }
0526 
0527         // recalculate new times and objects
0528         data->setSnapNextFocus();
0529         KStarsLite::Instance()->updateTime();
0530         return true;
0531     }
0532     return false;
0533 }
0534 
0535 dms LocationDialogLite::createDms(const QString &degree, bool deg, bool *ok)
0536 {
0537     dms dmsAngle(0.0); // FIXME: Should we change this to NaN?
0538     bool check = dmsAngle.setFromString(degree, deg);
0539 
0540     if (ok)
0541     {
0542         *ok = check; //ok might be a null pointer!
0543     }
0544     return dmsAngle;
0545 }
0546 
0547 void LocationDialogLite::setCurrentLocation(const QString &loc)
0548 {
0549     if (m_currentLocation != loc)
0550     {
0551         m_currentLocation = loc;
0552         emit currentLocationChanged(loc);
0553     }
0554 }
0555 
0556 void LocationDialogLite::updateCurrentLocation()
0557 {
0558     currentGeo = KStarsData::Instance()->geo();
0559     setCurrentLocation(currentGeo->fullName());
0560 }