KOSMIndoorMap

tilecache.cpp
1 /*
2  SPDX-FileCopyrightText: 2020 Volker Krause <[email protected]>
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 <QDir>
14 #include <QDirIterator>
15 #include <QFile>
16 #include <QFileInfo>
17 #include <QNetworkAccessManager>
18 #include <QNetworkReply>
19 #include <QStandardPaths>
20 #include <QUrl>
21 
22 #include <cmath>
23 
24 using namespace KOSMIndoorMap;
25 
26 enum {
27  DefaultCacheDays = 14,
28 };
29 
30 Tile Tile::fromCoordinate(double lat, double lon, uint8_t z)
31 {
32  Tile t;
33  t.x = std::floor((lon + 180.0) / 360.0 * (1 << z));
34  const auto latrad = OSM::degToRad(lat);
35  t.y = std::floor((1.0 - std::asinh(std::tan(latrad)) / M_PI) / 2.0 * (1 << z));
36  t.z = z;
37  return t;
38 }
39 
40 OSM::Coordinate Tile::topLeft() const
41 {
42  const auto lon = x / (double)(1 << z) * 360.0 - 180.0;
43 
44  const auto n = M_PI - 2.0 * M_PI * y / (double)(1 << z);
45  const auto lat = OSM::radToDeg(std::atan(0.5 * (std::exp(n) - std::exp(-n))));
46 
47  return OSM::Coordinate(lat, lon);
48 }
49 
50 OSM::BoundingBox Tile::boundingBox() const
51 {
52  Tile bottomRight = *this;
53  ++bottomRight.x;
54  ++bottomRight.y;
55 
56  const auto tl = topLeft();
57  const auto br = bottomRight.topLeft();
58 
59  return OSM::BoundingBox(OSM::Coordinate(br.latitude, tl.longitude), OSM::Coordinate(tl.latitude, br.longitude));
60 }
61 
62 
63 TileCache::TileCache(QObject *parent)
64  : QObject(parent)
65  , m_nam(new QNetworkAccessManager(this))
66 {
67  m_nam->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
68  m_nam->enableStrictTransportSecurityStore(true, QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/org.kde.osm/hsts/"));
69  m_nam->setStrictTransportSecurityEnabled(true);
70 }
71 
72 TileCache::~TileCache() = default;
73 
74 QString TileCache::cachedTile(Tile tile) const
75 {
76  const auto p = cachePath(tile);
77  if (QFile::exists(p)) {
78  return p;
79  }
80  return {};
81 }
82 
83 void TileCache::ensureCached(Tile tile)
84 {
85  const auto t = cachedTile(tile);
86  if (t.isEmpty()) {
87  downloadTile(tile);
88  return;
89  }
90 
91  if (tile.ttl.isValid()) {
92  updateTtl(t, tile.ttl);
93  }
94 }
95 
96 void TileCache::downloadTile(Tile tile)
97 {
98  m_pendingDownloads.push_back(tile);
99  downloadNext();
100 }
101 
102 QString TileCache::cachePath(Tile tile) const
103 {
104  QString base;
105  if (!qEnvironmentVariableIsSet("KOSMINDOORMAP_CACHE_PATH")) {
107  + QLatin1String("/org.kde.osm/vectorosm/");
108  } else {
109  base = qEnvironmentVariable("KOSMINDOORMAP_CACHE_PATH");
110  }
111 
112  return base
113  + QString::number(tile.z) + QLatin1Char('/')
114  + QString::number(tile.x) + QLatin1Char('/')
115  + QString::number(tile.y) + QLatin1String(".o5m");
116 }
117 
118 void TileCache::downloadNext()
119 {
120  if (m_output.isOpen() || m_pendingDownloads.empty()) {
121  return;
122  }
123 
124  const auto tile = m_pendingDownloads.front();
125  m_pendingDownloads.pop_front();
126 
127  QFileInfo fi(cachePath(tile));
128  QDir().mkpath(fi.absolutePath());
129  m_output.setFileName(fi.absoluteFilePath() + QLatin1String(".part"));
130  if (!m_output.open(QFile::WriteOnly)) {
131  qCWarning(Log) << m_output.fileName() << m_output.errorString();
132  return;
133  }
134 
135  QUrl url;
136  if (qEnvironmentVariableIsSet("KOSMINDOORMAP_TILESERVER")) {
137  url = QUrl(qEnvironmentVariable("KOSMINDOORMAP_TILESERVER"));
138  } else {
139  url.setScheme(QStringLiteral("https"));
140  url.setHost(QStringLiteral("maps.kde.org"));
141  url.setPath(QStringLiteral("/earth/vectorosm/v1/"));
142  }
143 
144  url.setPath(url.path() + QString::number(tile.z) + QLatin1Char('/')
145  + QString::number(tile.x) + QLatin1Char('/')
146  + QString::number(tile.y) + QLatin1String(".o5m"));
147 
148  QNetworkRequest req(url);
149  req.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);
150  auto reply = m_nam->get(req);
151  connect(reply, &QNetworkReply::readyRead, this, [this, reply]() { dataReceived(reply); });
152  connect(reply, &QNetworkReply::finished, this, [this, reply, tile]() { downloadFinished(reply, tile); });
153 }
154 
155 void TileCache::dataReceived(QNetworkReply *reply)
156 {
157  m_output.write(reply->read(reply->bytesAvailable()));
158 }
159 
160 void TileCache::downloadFinished(QNetworkReply* reply, Tile tile)
161 {
162  reply->deleteLater();
163  m_output.close();
164 
165  if (reply->error() != QNetworkReply::NoError) {
166  qCWarning(Log) << reply->errorString() << reply->url();
167  m_output.remove();
168  Q_EMIT tileError(tile, reply->errorString());
169  downloadNext();
170  return;
171  }
172 
173  const auto t = cachePath(tile);
174  m_output.rename(t);
175  if (tile.ttl.isValid()) {
176  updateTtl(t, std::max(QDateTime::currentDateTimeUtc().addDays(1), tile.ttl));
177  } else {
178  updateTtl(t, QDateTime::currentDateTimeUtc().addDays(DefaultCacheDays));
179  }
180 
181  Q_EMIT tileLoaded(tile);
182  downloadNext();
183 }
184 
185 int TileCache::pendingDownloads() const
186 {
187  return m_pendingDownloads.size() + (m_output.isOpen() ? 1 : 0);
188 }
189 
190 void TileCache::cancelPending()
191 {
192  m_pendingDownloads.clear();
193 }
194 
195 static void expireRecursive(const QString &path)
196 {
198  while (it.hasNext()) {
199  it.next();
200 
201  if (it.fileInfo().isDir()) {
202  expireRecursive(it.filePath());
203  if (QDir(it.filePath()).isEmpty()) {
204  qCDebug(Log) << "removing empty tile directory" << it.fileName();
205  QDir(path).rmdir(it.filePath());
206  }
207  } else if (it.fileInfo().lastModified() < QDateTime::currentDateTimeUtc()) {
208  qCDebug(Log) << "removing expired tile" << it.filePath();
209  QDir(path).remove(it.filePath());
210  }
211  }
212 }
213 void TileCache::expire()
214 {
216  expireRecursive(base);
217 }
218 
219 void TileCache::updateTtl(const QString &filePath, const QDateTime &ttl)
220 {
221  QFile f(filePath);
223  f.setFileTime(std::max(f.fileTime(QFileDevice::FileModificationTime), ttl), QFile::FileModificationTime);
224 }
OSM-based multi-floor indoor maps for buildings.
QString writableLocation(QStandardPaths::StandardLocation type)
QString errorString() const const
bool remove(const QString &fileName)
Coordinate, stored as 1e7 * degree to avoid floating point precision issues, and offset to unsigned v...
Definition: datatypes.h:37
bool exists() const const
void setPath(const QString &path, QUrl::ParsingMode mode)
QString number(int n, int base)
bool rmdir(const QString &dirName) const const
constexpr double degToRad(double deg)
Degree to radian conversion.
Definition: geomath.h:19
void setScheme(const QString &scheme)
QString path(QUrl::ComponentFormattingOptions options) const const
qint64 read(char *data, qint64 maxSize)
void deleteLater()
virtual qint64 bytesAvailable() const const
QUrl url() const const
QNetworkReply::NetworkError error() const const
void setHost(const QString &host, QUrl::ParsingMode mode)
constexpr double radToDeg(double rad)
Radian to degree conversion.
Definition: geomath.h:24
void readyRead()
Bounding box, ie.
Definition: datatypes.h:95
QDateTime currentDateTimeUtc()
bool mkpath(const QString &dirPath) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Oct 25 2021 23:04:00 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.