2017-04-27 18:24:53 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2015-02-23 17:09:48 +00:00
|
|
|
//
|
|
|
|
// infrastructure to deal with dive sites
|
|
|
|
//
|
2015-05-10 15:44:35 +00:00
|
|
|
|
|
|
|
#include "divesitehelpers.h"
|
|
|
|
|
2015-02-23 17:09:48 +00:00
|
|
|
#include "divesite.h"
|
2019-08-05 17:41:15 +00:00
|
|
|
#include "errorhelper.h"
|
2018-05-11 15:25:41 +00:00
|
|
|
#include "subsurface-string.h"
|
2018-06-03 20:15:19 +00:00
|
|
|
#include "qthelper.h"
|
2015-02-23 17:09:48 +00:00
|
|
|
#include "membuffer.h"
|
2017-07-04 13:48:43 +00:00
|
|
|
#include <QDebug>
|
2015-02-23 17:09:48 +00:00
|
|
|
#include <QJsonDocument>
|
|
|
|
#include <QJsonArray>
|
|
|
|
#include <QJsonObject>
|
|
|
|
#include <QNetworkReply>
|
|
|
|
#include <QNetworkRequest>
|
|
|
|
#include <QNetworkAccessManager>
|
|
|
|
#include <QUrlQuery>
|
|
|
|
#include <QEventLoop>
|
2015-06-01 18:47:25 +00:00
|
|
|
#include <QTimer>
|
2018-10-11 18:46:44 +00:00
|
|
|
#include <memory>
|
2015-02-23 17:09:48 +00:00
|
|
|
|
2020-09-03 03:04:05 +00:00
|
|
|
/** Performs a REST get request to a service returning a JSON object. */
|
|
|
|
static QJsonObject doAsyncRESTGetRequest(const QString& url, int msTimeout)
|
2017-10-04 14:49:56 +00:00
|
|
|
{
|
2018-10-11 18:07:20 +00:00
|
|
|
// By making the QNetworkAccessManager static and local to this function,
|
|
|
|
// only one manager exists for all geo-lookups and it is only initialized
|
|
|
|
// on first call to this function.
|
|
|
|
static QNetworkAccessManager rgl;
|
2015-02-23 17:09:48 +00:00
|
|
|
QNetworkRequest request;
|
2015-07-01 19:36:21 +00:00
|
|
|
QEventLoop loop;
|
|
|
|
QTimer timer;
|
|
|
|
|
2015-02-23 17:09:48 +00:00
|
|
|
request.setRawHeader("Accept", "text/json");
|
|
|
|
request.setRawHeader("User-Agent", getUserAgent().toUtf8());
|
2018-10-11 16:44:05 +00:00
|
|
|
QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
|
2015-07-01 19:36:21 +00:00
|
|
|
|
2020-09-03 03:04:05 +00:00
|
|
|
request.setUrl(url);
|
2015-06-01 18:47:25 +00:00
|
|
|
|
2018-10-11 18:46:44 +00:00
|
|
|
// By using a std::unique_ptr<>, we can exit from the function at any point
|
|
|
|
// and the reply will be freed. Likewise, when overwriting the pointer with
|
|
|
|
// a new value. According to Qt's documentation it is fine the delete the
|
|
|
|
// reply as long as we're not in a slot connected to error() or finish().
|
|
|
|
std::unique_ptr<QNetworkReply> reply(rgl.get(request));
|
2017-10-03 06:20:08 +00:00
|
|
|
timer.setSingleShot(true);
|
2018-10-11 18:46:44 +00:00
|
|
|
QObject::connect(&*reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
2020-09-03 03:04:05 +00:00
|
|
|
timer.start(msTimeout);
|
2017-10-03 06:20:08 +00:00
|
|
|
loop.exec();
|
2015-06-01 18:47:25 +00:00
|
|
|
|
2020-09-03 03:04:05 +00:00
|
|
|
if (!timer.isActive()) {
|
|
|
|
report_error("timeout accessing %s", qPrintable(url));
|
2018-10-11 18:46:44 +00:00
|
|
|
QObject::disconnect(&*reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
2017-10-03 06:20:08 +00:00
|
|
|
reply->abort();
|
2020-09-03 03:04:05 +00:00
|
|
|
return QJsonObject{};
|
2017-10-03 06:20:08 +00:00
|
|
|
}
|
2020-09-03 03:04:05 +00:00
|
|
|
|
|
|
|
timer.stop();
|
|
|
|
if (reply->error() > 0) {
|
|
|
|
report_error("got error accessing %s: %s", qPrintable(url), qPrintable(reply->errorString()));
|
|
|
|
return QJsonObject{};
|
|
|
|
}
|
|
|
|
int v = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
|
|
if (v < 200 || v >= 300) {
|
|
|
|
return QJsonObject{};
|
|
|
|
}
|
|
|
|
QByteArray fullReply = reply->readAll();
|
|
|
|
QJsonParseError errorObject;
|
|
|
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(fullReply, &errorObject);
|
|
|
|
if (errorObject.error != QJsonParseError::NoError) {
|
|
|
|
report_error("error parsing JSON response: %s", qPrintable(errorObject.errorString()));
|
|
|
|
return QJsonObject{};
|
|
|
|
}
|
|
|
|
// Success, return JSON response from server
|
|
|
|
return jsonDoc.object();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Performs a reverse-geo-lookup of the coordinates and returns the taxonomy data.
|
|
|
|
taxonomy_data reverseGeoLookup(degrees_t latitude, degrees_t longitude)
|
|
|
|
{
|
|
|
|
const QString geonamesNearbyURL = QStringLiteral("http://api.geonames.org/findNearbyJSON?lang=%1&lat=%2&lng=%3&radius=50&username=dirkhh");
|
|
|
|
const QString geonamesNearbyPlaceNameURL = QStringLiteral("http://api.geonames.org/findNearbyPlaceNameJSON?lang=%1&lat=%2&lng=%3&radius=50&username=dirkhh");
|
|
|
|
const QString geonamesOceanURL = QStringLiteral("http://api.geonames.org/oceanJSON?lang=%1&lat=%2&lng=%3&radius=50&username=dirkhh");
|
|
|
|
|
|
|
|
QString url;
|
|
|
|
QJsonObject obj;
|
2020-09-06 10:39:51 +00:00
|
|
|
taxonomy_data taxonomy = { 0, 0 };
|
2020-09-03 03:04:05 +00:00
|
|
|
|
|
|
|
// check the oceans API to figure out the body of water
|
|
|
|
url = geonamesOceanURL.arg(getUiLanguage().section(QRegExp("[-_ ]"), 0, 0)).arg(latitude.udeg / 1000000.0).arg(longitude.udeg / 1000000.0);
|
|
|
|
obj = doAsyncRESTGetRequest(url, 5000); // 5 secs. timeout
|
|
|
|
QVariantMap oceanName = obj.value("ocean").toVariant().toMap();
|
2020-09-06 10:39:51 +00:00
|
|
|
if (oceanName["name"].isValid())
|
|
|
|
taxonomy_set_category(&taxonomy, TC_OCEAN, qPrintable(oceanName["name"].toString()), taxonomy_origin::GEOCODED);
|
2020-09-03 03:04:05 +00:00
|
|
|
|
|
|
|
// check the findNearbyPlaces API from geonames - that should give us country, state, city
|
|
|
|
url = geonamesNearbyPlaceNameURL.arg(getUiLanguage().section(QRegExp("[-_ ]"), 0, 0)).arg(latitude.udeg / 1000000.0).arg(longitude.udeg / 1000000.0);
|
|
|
|
obj = doAsyncRESTGetRequest(url, 5000); // 5 secs. timeout
|
|
|
|
QVariantList geoNames = obj.value("geonames").toVariant().toList();
|
|
|
|
if (geoNames.count() == 0) {
|
|
|
|
// check the findNearby API from geonames if the previous search came up empty - that should give us country, state, location
|
|
|
|
url = geonamesNearbyURL.arg(getUiLanguage().section(QRegExp("[-_ ]"), 0, 0)).arg(latitude.udeg / 1000000.0).arg(longitude.udeg / 1000000.0);
|
|
|
|
obj = doAsyncRESTGetRequest(url, 5000); // 5 secs. timeout
|
|
|
|
geoNames = obj.value("geonames").toVariant().toList();
|
|
|
|
}
|
|
|
|
if (geoNames.count() > 0) {
|
|
|
|
QVariantMap firstData = geoNames.at(0).toMap();
|
|
|
|
|
|
|
|
// fill out all the data - start at COUNTRY since we already got OCEAN above
|
|
|
|
for (int idx = TC_COUNTRY; idx < TC_NR_CATEGORIES; idx++) {
|
|
|
|
if (firstData[taxonomy_api_names[idx]].isValid()) {
|
2020-09-06 10:39:51 +00:00
|
|
|
QString value = firstData[taxonomy_api_names[idx]].toString();
|
|
|
|
taxonomy_set_category(&taxonomy, (taxonomy_category)idx, qPrintable(value), taxonomy_origin::GEOCODED);
|
2015-07-01 19:36:21 +00:00
|
|
|
}
|
2015-05-10 15:44:35 +00:00
|
|
|
}
|
2020-09-06 11:48:52 +00:00
|
|
|
const char *l3 = taxonomy_get_value(&taxonomy, TC_ADMIN_L3);
|
|
|
|
const char *lt = taxonomy_get_value(&taxonomy, TC_LOCALNAME);
|
|
|
|
if (empty_string(l3) && !empty_string(lt)) {
|
2020-09-03 03:04:05 +00:00
|
|
|
// basically this means we did get a local name (what we call town), but just like most places
|
|
|
|
// we didn't get an adminName_3 - which in some regions is the actual city that town belongs to,
|
|
|
|
// then we copy the town into the city
|
2020-09-06 11:48:52 +00:00
|
|
|
taxonomy_set_category(&taxonomy, TC_ADMIN_L3, lt, taxonomy_origin::GEOCOPIED);
|
2020-09-03 03:04:05 +00:00
|
|
|
}
|
2017-10-03 06:20:08 +00:00
|
|
|
} else {
|
2020-09-03 03:04:05 +00:00
|
|
|
report_error("geonames.org did not provide reverse lookup information");
|
|
|
|
//qDebug() << "no reverse geo lookup; geonames returned\n" << fullReply;
|
2017-10-03 06:20:08 +00:00
|
|
|
}
|
2020-09-03 03:04:05 +00:00
|
|
|
|
|
|
|
return taxonomy;
|
2015-05-10 15:44:35 +00:00
|
|
|
}
|