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
10#include <osm/datatypes.h>
11#include <osm/geomath.h>
12
13#include <QCoreApplication>
14#include <QDir>
15#include <QDirIterator>
16#include <QFile>
17#include <QFileInfo>
18#include <QNetworkAccessManager>
19#include <QNetworkReply>
20#include <QStandardPaths>
21#include <QUrl>
22
23#include <cmath>
24
25using namespace KOSMIndoorMap;
26
27enum {
28 DefaultCacheDays = 14,
29};
30
31Tile Tile::fromCoordinate(double lat, double lon, uint8_t z)
32{
33 Tile t;
34 t.x = std::floor((lon + 180.0) / 360.0 * (1 << z));
35 const auto latrad = OSM::degToRad(lat);
36 t.y = std::floor((1.0 - std::asinh(std::tan(latrad)) / M_PI) / 2.0 * (1 << z));
37 t.z = z;
38 return t;
39}
40
41OSM::Coordinate Tile::topLeft() const
42{
43 const auto lon = x / (double)(1 << z) * 360.0 - 180.0;
44
45 const auto n = M_PI - 2.0 * M_PI * y / (double)(1 << z);
46 const auto lat = OSM::radToDeg(std::atan(0.5 * (std::exp(n) - std::exp(-n))));
47
48 return OSM::Coordinate(lat, lon);
49}
50
51OSM::BoundingBox Tile::boundingBox() const
52{
53 Tile bottomRight = *this;
54 ++bottomRight.x;
55 ++bottomRight.y;
56
57 const auto tl = topLeft();
58 const auto br = bottomRight.topLeft();
59
60 return OSM::BoundingBox(OSM::Coordinate(br.latitude, tl.longitude), OSM::Coordinate(tl.latitude, br.longitude));
61}
62
63Tile Tile::topLeftAtZ(uint8_t z) const
64{
65 if (z == this->z) {
66 return *this;
67 }
68 if (z < this->z) {
69 return Tile{ x / (1 << (this->z - z)), y / (1 << (this->z - z)), z};
70 }
71 return Tile{ x * (1 << (z - this->z )), y * (1 << (z - this->z)), z};
72}
73
74Tile Tile::bottomRightAtZ(uint8_t z) const
75{
76 if (z <= this->z) {
77 return topLeftAtZ(z);
78 }
79 const auto deltaZ = z - this->z;
80 const auto deltaWidth = 1 << deltaZ;
81 return Tile{ x * deltaWidth + deltaWidth - 1, y * deltaWidth + deltaWidth - 1, z};
82}
83
84TileCache::TileCache(QObject *parent)
85 : QObject(parent)
86 , m_nam(new QNetworkAccessManager(this))
87{
88 m_nam->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
89 m_nam->enableStrictTransportSecurityStore(true, QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/org.kde.osm/hsts/"));
90 m_nam->setStrictTransportSecurityEnabled(true);
91}
92
93TileCache::~TileCache() = default;
94
95QString TileCache::cachedTile(Tile tile) const
96{
97 const auto p = cachePath(tile);
98 if (QFile::exists(p)) {
99 return p;
100 }
101 return {};
102}
103
104void TileCache::ensureCached(Tile tile)
105{
106 const auto t = cachedTile(tile);
107 if (t.isEmpty()) {
108 downloadTile(tile);
109 return;
110 }
111
112 if (tile.ttl.isValid()) {
113 updateTtl(t, tile.ttl);
114 }
115}
116
117void TileCache::downloadTile(Tile tile)
118{
119 m_pendingDownloads.push_back(tile);
120 downloadNext();
121}
122
123QString TileCache::cachePath(Tile tile) const
124{
125 QString base;
126 if (!qEnvironmentVariableIsSet("KOSMINDOORMAP_CACHE_PATH")) {
128 + QLatin1String("/org.kde.osm/vectorosm/");
129 } else {
130 base = qEnvironmentVariable("KOSMINDOORMAP_CACHE_PATH");
131 }
132
133 return base
134 + QString::number(tile.z) + QLatin1Char('/')
135 + QString::number(tile.x) + QLatin1Char('/')
136 + QString::number(tile.y) + QLatin1String(".o5m");
137}
138
139void TileCache::downloadNext()
140{
141 if (m_output.isOpen() || m_pendingDownloads.empty()) {
142 return;
143 }
144
145 const auto tile = m_pendingDownloads.front();
146 m_pendingDownloads.pop_front();
147
148 QFileInfo fi(cachePath(tile));
149 QDir().mkpath(fi.absolutePath());
150 m_output.setFileName(fi.absoluteFilePath() + QLatin1String(".part"));
151 if (!m_output.open(QFile::WriteOnly)) {
152 qCWarning(Log) << m_output.fileName() << m_output.errorString();
153 return;
154 }
155
156 QUrl url;
157 if (qEnvironmentVariableIsSet("KOSMINDOORMAP_TILESERVER")) {
158 url = QUrl(qEnvironmentVariable("KOSMINDOORMAP_TILESERVER"));
159 } else {
160 url.setScheme(QStringLiteral("https"));
161 url.setHost(QStringLiteral("maps.kde.org"));
162 url.setPath(QStringLiteral("/earth/vectorosm/v1/"));
163 }
164
165 url.setPath(url.path() + QString::number(tile.z) + QLatin1Char('/')
166 + QString::number(tile.x) + QLatin1Char('/')
167 + QString::number(tile.y) + QLatin1String(".o5m"));
168
169 QNetworkRequest req(url);
170 req.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);
172 auto reply = m_nam->get(req);
173 connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { dataReceived(reply); });
174 connect(reply, &QNetworkReply::finished, this, [this, reply, tile]() { downloadFinished(reply, tile); });
175 connect(reply, &QNetworkReply::sslErrors, this, [reply](const auto &sslErrors) { reply->setProperty("_ssl_errors", QVariant::fromValue(sslErrors)); });
176}
177
178void TileCache::dataReceived(QNetworkReply *reply)
179{
180 m_output.write(reply->read(reply->bytesAvailable()));
181}
182
183void TileCache::downloadFinished(QNetworkReply* reply, Tile tile)
184{
185 reply->deleteLater();
186 m_output.close();
187
188 if (reply->error() != QNetworkReply::NoError) {
189 qCWarning(Log) << reply->errorString() << reply->url();
190 m_output.remove();
192 const auto sslErrors = reply->property("_ssl_errors").value<QList<QSslError>>();
193 QStringList errorStrings;
194 errorStrings.reserve(sslErrors.size());
195 std::transform(sslErrors.begin(), sslErrors.end(), std::back_inserter(errorStrings), [](const auto &e) { return e.errorString(); });
196 qCWarning(Log) << errorStrings;
197 Q_EMIT tileError(tile, reply->errorString() + QLatin1String(" (") + errorStrings.join(QLatin1String(", ")) + QLatin1Char(')'));
198 } else {
199 Q_EMIT tileError(tile, reply->errorString());
200 }
201 downloadNext();
202 return;
203 }
204
205 const auto t = cachePath(tile);
206 m_output.rename(t);
207 if (tile.ttl.isValid()) {
208 updateTtl(t, std::max(QDateTime::currentDateTimeUtc().addDays(1), tile.ttl));
209 } else {
210 updateTtl(t, QDateTime::currentDateTimeUtc().addDays(DefaultCacheDays));
211 }
212
213 Q_EMIT tileLoaded(tile);
214 downloadNext();
215}
216
217int TileCache::pendingDownloads() const
218{
219 return m_pendingDownloads.size() + (m_output.isOpen() ? 1 : 0);
220}
221
222void TileCache::cancelPending()
223{
224 m_pendingDownloads.clear();
225}
226
227static void expireRecursive(const QString &path)
228{
230 while (it.hasNext()) {
231 it.next();
232
233 if (it.fileInfo().isDir()) {
234 expireRecursive(it.filePath());
235 if (QDir(it.filePath()).isEmpty()) {
236 qCDebug(Log) << "removing empty tile directory" << it.fileName();
237 QDir(path).rmdir(it.filePath());
238 }
239 } else if (it.fileInfo().lastModified() < QDateTime::currentDateTimeUtc()) {
240 qCDebug(Log) << "removing expired tile" << it.filePath();
241 QDir(path).remove(it.filePath());
242 }
243 }
244}
245void TileCache::expire()
246{
248 expireRecursive(base);
249}
250
251void TileCache::updateTtl(const QString &filePath, const QDateTime &ttl)
252{
253 QFile f(filePath);
255 f.setFileTime(std::max(f.fileTime(QFileDevice::FileModificationTime), ttl), QFile::FileModificationTime);
256}
257
258#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 Tue Mar 26 2024 11:20:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.