current_geo_location.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "core/current_geo_location.h"
  8. #include "base/platform/base_platform_info.h"
  9. #include "base/invoke_queued.h"
  10. #include "base/timer.h"
  11. #include "data/raw/raw_countries_bounds.h"
  12. #include "platform/platform_current_geo_location.h"
  13. #include "ui/ui_utility.h"
  14. #include <QtNetwork/QNetworkAccessManager>
  15. #include <QtNetwork/QNetworkReply>
  16. #include <QtCore/QCoreApplication>
  17. #include <QtCore/QPointer>
  18. #include <QtCore/QJsonDocument>
  19. #include <QtCore/QJsonObject>
  20. #include <QtCore/QJsonArray>
  21. namespace Core {
  22. namespace {
  23. constexpr auto kDestroyManagerTimeout = 20 * crl::time(1000);
  24. [[nodiscard]] QString ChooseLanguage(const QString &language) {
  25. // https://docs.mapbox.com/api/search/geocoding#language-coverage
  26. auto result = language.toLower().replace('-', '_');
  27. static const auto kGood = std::array{
  28. // Global coverage.
  29. u"de"_q, u"en"_q, u"es"_q, u"fr"_q, u"it"_q, u"nl"_q, u"pl"_q,
  30. // Local coverage.
  31. u"az"_q, u"bn"_q, u"ca"_q, u"cs"_q, u"da"_q, u"el"_q, u"fa"_q,
  32. u"fi"_q, u"ga"_q, u"hu"_q, u"id"_q, u"is"_q, u"ja"_q, u"ka"_q,
  33. u"km"_q, u"ko"_q, u"lt"_q, u"lv"_q, u"mn"_q, u"pt"_q, u"ro"_q,
  34. u"sk"_q, u"sq"_q, u"sv"_q, u"th"_q, u"tl"_q, u"uk"_q, u"vi"_q,
  35. u"zh"_q, u"zh_Hans"_q, u"zh_TW"_q,
  36. // Limited coverage.
  37. u"ar"_q, u"bs"_q, u"gu"_q, u"he"_q, u"hi"_q, u"kk"_q, u"lo"_q,
  38. u"my"_q, u"nb"_q, u"ru"_q, u"sr"_q, u"te"_q, u"tk"_q, u"tr"_q,
  39. u"zh_Hant"_q,
  40. };
  41. for (const auto &known : kGood) {
  42. if (known.toLower() == result) {
  43. return known;
  44. }
  45. }
  46. if (const auto delimeter = result.indexOf('_'); delimeter > 0) {
  47. result = result.mid(0, delimeter);
  48. for (const auto &known : kGood) {
  49. if (known == result) {
  50. return known;
  51. }
  52. }
  53. }
  54. return u"en"_q;
  55. }
  56. void ResolveLocationAddressGeneric(
  57. const GeoLocation &location,
  58. const QString &language,
  59. const QString &token,
  60. Fn<void(GeoAddress)> callback) {
  61. const auto partialUrl = u"https://api.mapbox.com/search/geocode/v6"
  62. "/reverse?longitude=%1&latitude=%2&language=%3&access_token=%4"_q
  63. .arg(location.point.y())
  64. .arg(location.point.x())
  65. .arg(ChooseLanguage(language));
  66. static auto Cache = base::flat_map<QString, GeoAddress>();
  67. const auto i = Cache.find(partialUrl);
  68. if (i != end(Cache)) {
  69. callback(i->second);
  70. return;
  71. }
  72. const auto finishWith = [=](GeoAddress result) {
  73. Cache[partialUrl] = result;
  74. callback(result);
  75. };
  76. struct State final : QObject {
  77. explicit State(QObject *parent)
  78. : QObject(parent)
  79. , manager(this)
  80. , destroyer([=] { if (sent.empty()) delete this; }) {
  81. }
  82. QNetworkAccessManager manager;
  83. std::vector<QPointer<QNetworkReply>> sent;
  84. base::Timer destroyer;
  85. };
  86. static auto state = QPointer<State>();
  87. if (!state) {
  88. state = Ui::CreateChild<State>(qApp);
  89. }
  90. const auto destroyReplyDelayed = [](QNetworkReply *reply) {
  91. InvokeQueued(reply, [=] {
  92. for (auto i = begin(state->sent); i != end(state->sent);) {
  93. if (!*i || *i == reply) {
  94. i = state->sent.erase(i);
  95. } else {
  96. ++i;
  97. }
  98. }
  99. delete reply;
  100. if (state->sent.empty()) {
  101. state->destroyer.callOnce(kDestroyManagerTimeout);
  102. }
  103. });
  104. };
  105. auto request = QNetworkRequest(partialUrl.arg(token));
  106. request.setRawHeader("Referer", "http://desktop-app-resource/");
  107. const auto reply = state->manager.get(request);
  108. QObject::connect(reply, &QNetworkReply::finished, [=] {
  109. destroyReplyDelayed(reply);
  110. const auto json = QJsonDocument::fromJson(reply->readAll());
  111. if (!json.isObject()) {
  112. finishWith({});
  113. return;
  114. }
  115. const auto features = json["features"].toArray();
  116. if (features.isEmpty()) {
  117. finishWith({});
  118. return;
  119. }
  120. const auto feature = features.at(0).toObject();
  121. const auto properties = feature["properties"].toObject();
  122. const auto context = properties["context"].toObject();
  123. auto names = QStringList();
  124. auto add = [&](std::vector<QString> keys) {
  125. for (const auto &key : keys) {
  126. const auto value = context[key];
  127. if (value.isObject()) {
  128. const auto name = value.toObject()["name"].toString();
  129. if (!name.isEmpty()) {
  130. names.push_back(name);
  131. break;
  132. }
  133. }
  134. }
  135. };
  136. add({ /*u"address"_q, u"street"_q, */u"neighborhood"_q });
  137. add({ u"place"_q, u"region"_q });
  138. add({ u"country"_q });
  139. finishWith({ .name = names.join(", ") });
  140. });
  141. QObject::connect(reply, &QNetworkReply::errorOccurred, [=] {
  142. destroyReplyDelayed(reply);
  143. finishWith({});
  144. });
  145. }
  146. } // namespace
  147. GeoLocation ResolveCurrentCountryLocation() {
  148. const auto iso2 = Platform::SystemCountry().toUpper();
  149. const auto &bounds = Raw::CountryBounds();
  150. const auto i = bounds.find(iso2);
  151. if (i == end(bounds)) {
  152. return {
  153. .accuracy = GeoLocationAccuracy::Failed,
  154. };
  155. }
  156. return {
  157. .point = {
  158. (i->second.minLat + i->second.maxLat) / 2.,
  159. (i->second.minLon + i->second.maxLon) / 2.,
  160. },
  161. .bounds = {
  162. i->second.minLat,
  163. i->second.minLon,
  164. i->second.maxLat - i->second.minLat,
  165. i->second.maxLon - i->second.minLon,
  166. },
  167. .accuracy = GeoLocationAccuracy::Country,
  168. };
  169. }
  170. void ResolveCurrentGeoLocation(Fn<void(GeoLocation)> callback) {
  171. using namespace Platform;
  172. return ResolveCurrentExactLocation([done = std::move(callback)](
  173. GeoLocation result) {
  174. done(result.accuracy != GeoLocationAccuracy::Failed
  175. ? result
  176. : ResolveCurrentCountryLocation());
  177. });
  178. }
  179. void ResolveLocationAddress(
  180. const GeoLocation &location,
  181. const QString &language,
  182. const QString &token,
  183. Fn<void(GeoAddress)> callback) {
  184. auto done = [=, done = std::move(callback)](GeoAddress result) mutable {
  185. if (!result && !token.isEmpty()) {
  186. ResolveLocationAddressGeneric(
  187. location,
  188. language,
  189. token,
  190. std::move(done));
  191. } else {
  192. done(result);
  193. }
  194. };
  195. Platform::ResolveLocationAddress(location, language, std::move(done));
  196. }
  197. bool AreTheSame(const GeoLocation &a, const GeoLocation &b) {
  198. if (a.accuracy != GeoLocationAccuracy::Exact
  199. || b.accuracy != GeoLocationAccuracy::Exact) {
  200. return false;
  201. }
  202. const auto normalize = [](float64 value) {
  203. value = std::fmod(value + 180., 360.);
  204. return (value + (value < 0. ? 360. : 0.)) - 180.;
  205. };
  206. constexpr auto kEpsilon = 0.0001;
  207. const auto lon1 = normalize(a.point.y());
  208. const auto lon2 = normalize(b.point.y());
  209. const auto diffLat = std::abs(a.point.x() - b.point.x());
  210. if (std::abs(a.point.x()) >= (90. - kEpsilon)
  211. || std::abs(b.point.x()) >= (90. - kEpsilon)) {
  212. return diffLat <= kEpsilon;
  213. }
  214. auto diffLon = std::abs(lon1 - lon2);
  215. if (diffLon > 180.) {
  216. diffLon = 360. - diffLon;
  217. }
  218. return diffLat <= kEpsilon && diffLon <= kEpsilon;
  219. }
  220. } // namespace Core