Limit the overzoom by the resulting tile size rather than number of levels
[GPXSee.git] / src / map / onlinemap.cpp
blob6a198e8639d5d7df28b3b8a64f23e22a5d31741c
1 #include <QPainter>
2 #include <QDir>
3 #include <QPixmapCache>
4 #include "common/rectc.h"
5 #include "common/programpaths.h"
6 #include "common/downloader.h"
7 #include "osm.h"
8 #include "onlinemap.h"
11 #define MAX_TILE_SIZE 4096
13 OnlineMap::OnlineMap(const QString &fileName, const QString &name,
14 const QString &url, const Range &zooms, const RectC &bounds, qreal tileRatio,
15 const QList<HTTPHeader> &headers, int tileSize, bool scalable, bool invertY,
16 bool quadTiles, QObject *parent)
17 : Map(fileName, parent), _name(name), _zooms(zooms), _bounds(bounds),
18 _zoom(_zooms.max()), _tileSize(tileSize), _baseZoom(0), _mapRatio(1.0),
19 _tileRatio(tileRatio), _scalable(scalable), _scaledSize(0), _invertY(invertY)
21 _tileLoader = new TileLoader(QDir(ProgramPaths::tilesDir()).filePath(_name),
22 this);
23 _tileLoader->setUrl(url, quadTiles ? TileLoader::QuadTiles : TileLoader::XYZ);
24 _tileLoader->setHeaders(headers);
25 connect(_tileLoader, &TileLoader::finished, this, &OnlineMap::tilesLoaded);
27 _baseZoom = _zooms.max();
30 QRectF OnlineMap::bounds()
32 return QRectF(ll2xy(_bounds.topLeft()), ll2xy(_bounds.bottomRight()));
35 int OnlineMap::limitZoom(int zoom) const
37 if (zoom < _zooms.min())
38 return _zooms.min();
39 if (zoom > _zooms.max())
40 return _zooms.max();
42 return zoom;
45 int OnlineMap::zoomFit(const QSize &size, const RectC &rect)
47 if (!rect.isValid())
48 _zoom = _zooms.max();
49 else {
50 QRectF tbr(OSM::ll2m(rect.topLeft()), OSM::ll2m(rect.bottomRight()));
51 QPointF sc(tbr.width() / size.width(), tbr.height() / size.height());
52 _zoom = limitZoom(OSM::scale2zoom(qMax(sc.x(), -sc.y())
53 / coordinatesRatio(), _tileSize));
56 return _zoom;
59 qreal OnlineMap::resolution(const QRectF &rect)
61 return OSM::resolution(rect.center(), _zoom, _tileSize);
64 int OnlineMap::zoomIn()
66 cancelJobs(false);
68 _zoom = qMin(_zoom + 1, _zooms.max());
69 return _zoom;
72 int OnlineMap::zoomOut()
74 cancelJobs(false);
76 _zoom = qMax(_zoom - 1, _zooms.min());
77 return _zoom;
80 void OnlineMap::load(const Projection &in, const Projection &out,
81 qreal deviceRatio, bool hidpi)
83 Q_UNUSED(in);
84 Q_UNUSED(out);
86 _mapRatio = hidpi ? deviceRatio : 1.0;
87 _zooms.setMax(_baseZoom);
89 if (_scalable) {
90 _scaledSize = _tileSize * deviceRatio;
91 _tileRatio = deviceRatio;
93 for (int i = _baseZoom + 1; i <= OSM::ZOOMS.max(); i++) {
94 if (_tileSize * _tileRatio * (1U<<(i - _baseZoom)) > MAX_TILE_SIZE)
95 break;
96 _zooms.setMax(i);
101 void OnlineMap::unload()
103 cancelJobs(true);
106 qreal OnlineMap::coordinatesRatio() const
108 return _mapRatio > 1.0 ? _mapRatio / _tileRatio : 1.0;
111 qreal OnlineMap::imageRatio() const
113 return _mapRatio > 1.0 ? _mapRatio : _tileRatio;
116 qreal OnlineMap::tileSize() const
118 return (_tileSize / coordinatesRatio());
121 QPoint OnlineMap::tileCoordinates(int x, int y, int zoom)
123 return QPoint(x, _invertY ? (1<<zoom) - y - 1 : y);
126 bool OnlineMap::isRunning(const QString &key) const
128 for (int i = 0; i < _jobs.size(); i++) {
129 const QList<OnlineMapTile> &tiles = _jobs.at(i)->tiles();
130 for (int j = 0; j < tiles.size(); j++)
131 if (tiles.at(j).key() == key)
132 return true;
135 return false;
138 void OnlineMap::runJob(OnlineMapJob *job)
140 _jobs.append(job);
142 connect(job, &OnlineMapJob::finished, this, &OnlineMap::jobFinished);
143 job->run();
146 void OnlineMap::removeJob(OnlineMapJob *job)
148 _jobs.removeOne(job);
149 job->deleteLater();
152 void OnlineMap::jobFinished(OnlineMapJob *job)
154 const QList<OnlineMapTile> &tiles = job->tiles();
156 for (int i = 0; i < tiles.size(); i++) {
157 const OnlineMapTile &mt = tiles.at(i);
158 if (!mt.pixmap().isNull())
159 QPixmapCache::insert(mt.key(), mt.pixmap());
162 removeJob(job);
164 emit tilesLoaded();
167 void OnlineMap::cancelJobs(bool wait)
169 for (int i = 0; i < _jobs.size(); i++)
170 _jobs.at(i)->cancel(wait);
173 void OnlineMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
175 int baseZoom = qMin(_baseZoom, _zoom);
176 unsigned overzoom = _zoom - baseZoom;
177 unsigned f = 1U<<overzoom;
179 qreal scale = OSM::zoom2scale(baseZoom, _tileSize * f);
180 QPoint tile = OSM::mercator2tile(QPointF(rect.topLeft().x() * scale,
181 -rect.topLeft().y() * scale) * coordinatesRatio(), baseZoom);
182 Coordinates ctl(OSM::tile2ll(tile, baseZoom));
183 QPointF tl(ll2xy(Coordinates(ctl.lon(), -ctl.lat())));
184 QSizeF s(rect.right() - tl.x(), rect.bottom() - tl.y());
185 int width = ceil(s.width() / (tileSize() * f));
186 int height = ceil(s.height() / (tileSize() * f));
188 QVector<TileLoader::Tile> fetchTiles;
189 fetchTiles.reserve(width * height);
190 for (int i = 0; i < width; i++) {
191 for (int j = 0; j < height; j++) {
192 QPoint tc(tileCoordinates(tile.x() + i, tile.y() + j, baseZoom));
193 fetchTiles.append(TileLoader::Tile(tc, baseZoom));
197 if (flags & Map::Block)
198 _tileLoader->loadTilesSync(fetchTiles);
199 else
200 _tileLoader->loadTilesAsync(fetchTiles);
202 QList<OnlineMapTile> renderTiles;
203 for (int i = 0; i < fetchTiles.count(); i++) {
204 const TileLoader::Tile &t = fetchTiles.at(i);
205 if (t.file().isNull())
206 continue;
208 QString key(overzoom
209 ? t.file() + ":" + QString::number(overzoom) : t.file());
210 if (isRunning(key))
211 continue;
213 QPixmap pm;
214 if (QPixmapCache::find(key, &pm)) {
215 QPoint tc(tileCoordinates(t.xy().x(), t.xy().y(), baseZoom));
216 QPointF tp(tl.x() + (tc.x() - tile.x()) * tileSize() * f,
217 tl.y() + (tc.y() - tile.y()) * tileSize() * f);
218 drawTile(painter, pm, tp);
219 } else
220 renderTiles.append(OnlineMapTile(t.xy(), t.file(), _zoom, overzoom,
221 _scaledSize, key));
224 if (!renderTiles.isEmpty()) {
225 if (flags & Map::Block || !_scalable) {
226 QFuture<void> future = QtConcurrent::map(renderTiles,
227 &OnlineMapTile::load);
228 future.waitForFinished();
230 for (int i = 0; i < renderTiles.size(); i++) {
231 const OnlineMapTile &mt = renderTiles.at(i);
232 QPixmap pm(mt.pixmap());
233 if (pm.isNull())
234 continue;
236 QPixmapCache::insert(mt.key(), pm);
238 QPoint tc(tileCoordinates(mt.xy().x(), mt.xy().y(), baseZoom));
239 QPointF tp(tl.x() + (tc.x() - tile.x()) * tileSize() * f,
240 tl.y() + (tc.y() - tile.y()) * tileSize() * f);
241 drawTile(painter, pm, tp);
243 } else
244 runJob(new OnlineMapJob(renderTiles));
248 void OnlineMap::drawTile(QPainter *painter, QPixmap &pixmap, QPointF &tp)
250 pixmap.setDevicePixelRatio(imageRatio());
251 painter->drawPixmap(tp, pixmap);
254 QPointF OnlineMap::ll2xy(const Coordinates &c)
256 qreal scale = OSM::zoom2scale(_zoom, _tileSize);
257 QPointF m = OSM::ll2m(c);
258 return QPointF(m.x() / scale, m.y() / -scale) / coordinatesRatio();
261 Coordinates OnlineMap::xy2ll(const QPointF &p)
263 qreal scale = OSM::zoom2scale(_zoom, _tileSize);
264 return OSM::m2ll(QPointF(p.x() * scale, -p.y() * scale)
265 * coordinatesRatio());
268 void OnlineMap::clearCache()
270 _tileLoader->clearCache();
271 QPixmapCache::clear();