File indexing completed on 2025-03-16 03:33:49
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 °ree, 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 }