KOSMIndoorMap

tilecache.cpp
1/*
2 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "tilecache_p.h"
8#include "logging.h"
9#include "network/useragent_p.h"
10
11#include <osm/datatypes.h>
12#include <osm/geomath.h>
13
14#include <QCoreApplication>
15#include <QDir>
16#include <QDirIterator>
17#include <QFile>
18#include <QFileInfo>
19#include <QNetworkAccessManager>
20#include <QNetworkReply>
21#include <QStandardPaths>
22#include <QUrl>
23
24#include <cmath>
25
26using namespace KOSMIndoorMap;
27
28enum {
29 DefaultCacheDays = 14,
30};
31
32Tile Tile::fromCoordinate(double lat, double lon, uint8_t z)
33{
34 Tile t;
35 t.x = std::floor((lon + 180.0) / 360.0 * (1 << z));
36 const auto latrad = OSM::degToRad(lat);
37 t.y = std::floor((1.0 - std::asinh(std::tan(latrad)) / M_PI) / 2.0 * (1 << z));
38 t.z = z;
39 return t;
40}
41
42OSM::Coordinate Tile::topLeft() const
43{
44 const auto lon = x / (double)(1 << z) * 360.0 - 180.0;
45
46 const auto n = M_PI - 2.0 * M_PI * y / (double)(1 << z);
47 const auto lat = OSM::radToDeg(std::atan(0.5 * (std::exp(n) - std::exp(-n))));
48
49 return OSM::Coordinate(lat, lon);
50}
51
52OSM::BoundingBox Tile::boundingBox() const
53{
54 Tile bottomRight = *this;
55 ++bottomRight.x;
56 ++bottomRight.y;
57
58 const auto tl = topLeft();
59 const auto br = bottomRight.topLeft();
60
61 return OSM::BoundingBox(OSM::Coordinate(br.latitude, tl.longitude), OSM::Coordinate(tl.latitude, br.longitude));
62}
63
64Tile Tile::topLeftAtZ(uint8_t z) const
65{
66 if (z == this->z) {
67 return *this;
68 }
69 if (z < this->z) {
70 return Tile{ x / (1 << (this->z - z)), y / (1 << (this->z - z)), z};
71 }
72 return Tile{ x * (1 << (z - this->z )), y * (1 << (z - this->z)), z};
73}
74
75Tile Tile::bottomRightAtZ(uint8_t z) const
76{
77 if (z <= this->z) {
78 return topLeftAtZ(z);
79 }
80 const auto deltaZ = z - this->z;
81 const auto deltaWidth = 1 << deltaZ;
82 return Tile{ x * deltaWidth + deltaWidth - 1, y * deltaWidth + deltaWidth - 1, z};
83}
84
85TileCache::TileCache(const NetworkAccessManagerFactory &namFactory, QObject *parent)
86 : QObject(parent)
87 , m_nam(namFactory)
88{
89}
90
91TileCache::~TileCache() = default;
92
93QString TileCache::cachedTile(Tile tile) const
94{
95 const auto p = cachePath(tile);
96 if (QFile::exists(p)) {
97 return p;
98 }
99 return {};
100}
101
102void TileCache::ensureCached(Tile tile)
103{
104 const auto t = cachedTile(tile);
105 if (t.isEmpty()) {
106 downloadTile(tile);
107 return;
108 }
109
110 if (tile.ttl.isValid()) {
111 updateTtl(t, tile.ttl);
112 }
113}
114
115void TileCache::downloadTile(Tile tile)
116{
117 m_pendingDownloads.push_back(tile);
118 downloadNext();
119}
120
121QString TileCache::cachePath(Tile tile) const
122{
123 QString base;
124 if (!qEnvironmentVariableIsSet("KOSMINDOORMAP_CACHE_PATH")) {
126 + QLatin1String("/org.kde.osm/vectorosm/");
127 } else {
128 base = qEnvironmentVariable("KOSMINDOORMAP_CACHE_PATH");
129 }
130
131 return base
132 + QString::number(tile.z) + QLatin1Char('/')
133 + QString::number(tile.x) + QLatin1Char('/')
134 + QString::number(tile.y) + QLatin1String(".o5m");
135}
136
137void TileCache::downloadNext()
138{
139 if (m_output.isOpen() || m_pendingDownloads.empty()) {
140 return;
141 }
142
143 const auto tile = m_pendingDownloads.front();
144 m_pendingDownloads.pop_front();
145
146 QFileInfo fi(cachePath(tile));
147 QDir().mkpath(fi.absolutePath());
148 m_output.setFileName(fi.absoluteFilePath() + QLatin1String(".part"));
149 if (!m_output.open(QFile::WriteOnly)) {
150 qCWarning(Log) << m_output.fileName() << m_output.errorString();
151 return;
152 }
153
154 QUrl url;
155 if (qEnvironmentVariableIsSet("KOSMINDOORMAP_TILESERVER")) {
156 url = QUrl(qEnvironmentVariable("KOSMINDOORMAP_TILESERVER"));
157 } else {
158 url.setScheme(QStringLiteral("https"));
159 url.setHost(QStringLiteral("maps.kde.org"));
160 url.setPath(QStringLiteral("/earth/vectorosm/v1/"));
161 }
162
163 url.setPath(url.path() + QString::number(tile.z) + QLatin1Char('/')
164 + QString::number(tile.x) + QLatin1Char('/')
165 + QString::number(tile.y) + QLatin1String(".o5m"));
166
167 QNetworkRequest req(url);
168 req.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);
170 // bypass cache, we manage that ourselves
172 req.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
173 req.setHeader(QNetworkRequest::UserAgentHeader, KOSMIndoorMap::userAgent());
174 auto reply = m_nam()->get(req);
175 connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { dataReceived(reply); });
176 connect(reply, &QNetworkReply::finished, this, [this, reply, tile]() { downloadFinished(reply, tile); });
177 connect(reply, &QNetworkReply::sslErrors, this, [reply](const auto &sslErrors) { reply->setProperty("_ssl_errors", QVariant::fromValue(sslErrors)); });
178}
179
180void TileCache::dataReceived(QNetworkReply *reply)
181{
182 m_output.write(reply->read(reply->bytesAvailable()));
183}
184
185void TileCache::downloadFinished(QNetworkReply* reply, Tile tile)
186{
187 reply->deleteLater();
188 m_output.close();
189
190 if (reply->error() != QNetworkReply::NoError) {
191 qCWarning(Log) << reply->errorString() << reply->url();
192 m_output.remove();
194 const auto sslErrors = reply->property("_ssl_errors").value<QList<QSslError>>();
195 QStringList errorStrings;
196 errorStrings.reserve(sslErrors.size());
197 std::transform(sslErrors.begin(), sslErrors.end(), std::back_inserter(errorStrings), [](const auto &e) { return e.errorString(); });
198 qCWarning(Log) << errorStrings;
199 Q_EMIT tileError(tile, reply->errorString() + QLatin1String(" (") + errorStrings.join(QLatin1String(", ")) + QLatin1Char(')'));
200 } else {
201 Q_EMIT tileError(tile, reply->errorString());
202 }
203 downloadNext();
204 return;
205 }
206
207 const auto t = cachePath(tile);
208 m_output.rename(t);
209 if (tile.ttl.isValid()) {
210 updateTtl(t, std::max(QDateTime::currentDateTimeUtc().addDays(1), tile.ttl));
211 } else {
212 updateTtl(t, QDateTime::currentDateTimeUtc().addDays(DefaultCacheDays));
213 }
214
215 Q_EMIT tileLoaded(tile);
216 downloadNext();
217}
218
219int TileCache::pendingDownloads() const
220{
221 return m_pendingDownloads.size() + (m_output.isOpen() ? 1 : 0);
222}
223
224void TileCache::cancelPending()
225{
226 m_pendingDownloads.clear();
227}
228
229static void expireRecursive(const QString &path)
230{
232 while (it.hasNext()) {
233 it.next();
234
235 if (it.fileInfo().isDir()) {
236 expireRecursive(it.filePath());
237 if (QDir(it.filePath()).isEmpty()) {
238 qCDebug(Log) << "removing empty tile directory" << it.fileName();
239 QDir(path).rmdir(it.filePath());
240 }
241 } else if (it.fileInfo().lastModified() < QDateTime::currentDateTimeUtc()) {
242 qCDebug(Log) << "removing expired tile" << it.filePath();
243 QDir(path).remove(it.filePath());
244 }
245 }
246}
247void TileCache::expire()
248{
250 expireRecursive(base);
251}
252
253void TileCache::updateTtl(const QString &filePath, const QDateTime &ttl)
254{
255 QFile f(filePath);
257 f.setFileTime(std::max(f.fileTime(QFileDevice::FileModificationTime), ttl), QFile::FileModificationTime);
258}
259
260#include "moc_tilecache_p.cpp"
Bounding box, ie.
Definition datatypes.h:95
Coordinate, stored as 1e7 * degree to avoid floating point precision issues, and offset to unsigned v...
Definition datatypes.h:37
OSM-based multi-floor indoor maps for buildings.
constexpr double radToDeg(double rad)
Radian to degree conversion.
Definition geomath.h:24
constexpr double degToRad(double deg)
Degree to radian conversion.
Definition geomath.h:19
QDateTime currentDateTimeUtc()
bool isEmpty(Filters filters) const const
bool mkpath(const QString &dirPath) const const
bool remove(const QString &fileName)
bool rmdir(const QString &dirName) const const
bool exists() const const
virtual qint64 bytesAvailable() const const
QString errorString() const const
QByteArray read(qint64 maxSize)
void readyRead()
void reserve(qsizetype size)
NetworkError error() const const
QUrl url() const const
void deleteLater()
QVariant property(const char *name) const const
QString writableLocation(StandardLocation type)
QString number(double n, char format, int precision)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString path(ComponentFormattingOptions options) const const
void setHost(const QString &host, ParsingMode mode)
void setPath(const QString &path, ParsingMode mode)
void setScheme(const QString &scheme)
QVariant fromValue(T &&value)
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:57:46 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.