Reload the ENC style on device pixel ratio changes
[GPXSee.git] / src / map / encatlas.cpp
blob70bc0ba92fbd8b90a47067c2c5cd883074345a7f
1 #include <QPainter>
2 #include <QPixmapCache>
3 #include "common/wgs84.h"
4 #include "GUI/format.h"
5 #include "rectd.h"
6 #include "pcs.h"
7 #include "encjob.h"
8 #include "encatlas.h"
10 using namespace ENC;
12 #define TILE_SIZE 512
14 Range ENCAtlas::zooms(IntendedUsage usage)
16 switch (usage) {
17 case Overview:
18 return Range(6, 7);
19 case General:
20 return Range(8, 9);
21 case Coastal:
22 return Range(10, 11);
23 case Approach:
24 return Range(12, 13);
25 case Harbour:
26 return Range(14, 18);
27 case Berthing:
28 return Range(19, 19);
30 case River:
31 return Range(12, 17);
32 case RiverHarbour:
33 return Range(18, 18);
34 case RiverBerthing:
35 return Range(19, 19);
36 default:
37 return Range(0, 19);
41 ENCAtlas::IntendedUsage ENCAtlas::usage(const QString &path)
43 QFileInfo fi(path);
44 QString basename(fi.baseName());
46 if (basename.size() != 8)
47 return Unknown;
48 int iu = basename.at(2).digitValue();
49 if (iu < 1 || iu > 9)
50 return Unknown;
52 return (IntendedUsage)iu;
55 bool ENCAtlas::processRecord(const ISO8211::Record &record, QByteArray &file,
56 RectC &bounds)
58 if (record.size() < 2)
59 return false;
61 const ENC::ISO8211::Field &f = record.at(1);
62 const QByteArray &ba = f.tag();
64 if (ba == "CATD") {
65 QByteArray FILE, IMPL;
67 if (!f.subfield("IMPL", &IMPL))
68 return false;
69 if (!f.subfield("FILE", &FILE))
70 return false;
72 if (IMPL == "BIN" && FILE.endsWith("000")) {
73 QByteArray SLAT, WLON, NLAT, ELON;
75 if (!f.subfield("SLAT", &SLAT))
76 return false;
77 if (!f.subfield("WLON", &WLON))
78 return false;
79 if (!f.subfield("NLAT", &NLAT))
80 return false;
81 if (!f.subfield("ELON", &ELON))
82 return false;
84 bool ok1, ok2, ok3, ok4;
85 bounds = RectC(Coordinates(WLON.toDouble(&ok1), NLAT.toDouble(&ok2)),
86 Coordinates(ELON.toDouble(&ok3), SLAT.toDouble(&ok4)));
87 if (!(ok1 && ok2 && ok3 && ok4))
88 return false;
90 file = FILE.replace('\\', '/');
92 return true;
96 return false;
99 void ENCAtlas::addMap(const QDir &dir, const QByteArray &file,
100 const RectC &bounds)
102 QString path(dir.absoluteFilePath(file));
103 if (!QFileInfo::exists(path)) {
104 qWarning("%s: No such map file", qPrintable(path));
105 return;
107 if (!bounds.isValid()) {
108 qWarning("%s: Invalid map bounds", qPrintable(path));
109 return;
112 IntendedUsage iu = usage(path);
113 auto it = _data.find(iu);
114 if (it == _data.end())
115 it = _data.insert(iu, new AtlasData(_cache, _lock));
117 it.value()->addMap(bounds, path);
119 _name = "ENC (" + Format::coordinates(bounds.center(), DecimalDegrees) + ")";
120 _llBounds |= bounds;
123 ENCAtlas::ENCAtlas(const QString &fileName, QObject *parent)
124 : Map(fileName, parent), _projection(PCS::pcs(3857)), _tileRatio(1.0),
125 _style(0), _zoom(0), _valid(false)
127 QDir dir(QFileInfo(fileName).absoluteDir());
128 ISO8211 ddf(fileName);
129 ISO8211::Record record;
130 QByteArray file;
131 RectC bounds;
133 if (!ddf.readDDR()) {
134 _errorString = ddf.errorString();
135 return;
137 while (ddf.readRecord(record)) {
138 if (processRecord(record, file, bounds))
139 addMap(dir, file, bounds);
141 if (!ddf.errorString().isNull()) {
142 _errorString = ddf.errorString();
143 return;
146 if (_data.isEmpty()) {
147 _errorString = "No usable ENC map found";
148 return;
151 _usage = _data.firstKey();
152 _zoom = zooms(_usage).min();
153 updateTransform();
155 _cache.setMaxCost(10);
157 _valid = true;
160 ENCAtlas::~ENCAtlas()
162 qDeleteAll(_data);
163 delete _style;
166 void ENCAtlas::load(const Projection &in, const Projection &out,
167 qreal deviceRatio, bool hidpi)
169 Q_UNUSED(in);
170 Q_UNUSED(hidpi);
172 _tileRatio = deviceRatio;
173 _projection = out;
175 Q_ASSERT(!_style);
176 _style = new Style(deviceRatio);
178 QPixmapCache::clear();
181 void ENCAtlas::unload()
183 cancelJobs(true);
185 _cache.clear();
187 delete _style;
188 _style = 0;
191 int ENCAtlas::zoomFit(const QSize &size, const RectC &rect)
193 if (rect.isValid()) {
194 RectD pr(rect, _projection, 10);
196 for (auto it = _data.cbegin(); it != _data.cend(); ++it) {
197 Range z(zooms(it.key()));
199 _usage = it.key();
200 _zoom = z.min();
202 for (int i = z.min() + 1; i <= z.max(); i++) {
203 Transform t(transform(i));
204 QRectF r(t.proj2img(pr.topLeft()), t.proj2img(pr.bottomRight()));
206 if (size.width() < r.width() || size.height() < r.height()) {
207 updateTransform();
208 return _zoom;
211 _zoom = i;
214 } else {
215 IntendedUsage usage(_data.lastKey());
216 _usage = usage;
217 _zoom = zooms(usage).max();
220 updateTransform();
222 return _zoom;
225 int ENCAtlas::zoomIn()
227 cancelJobs(false);
229 if (_zoom + 1 <= zooms(_usage).max())
230 _zoom++;
231 else {
232 auto it = _data.find(_usage);
233 if (++it != _data.end()) {
234 _usage = it.key();
235 _zoom = zooms(it.key()).min();
239 updateTransform();
241 return _zoom;
244 int ENCAtlas::zoomOut()
246 cancelJobs(false);
248 if (_zoom - 1 >= zooms(_usage).min())
249 _zoom--;
250 else {
251 auto it = _data.find(_usage);
252 if (it != _data.begin()) {
253 --it;
254 _usage = it.key();
255 _zoom = zooms(it.key()).max();
259 updateTransform();
261 return _zoom;
264 void ENCAtlas::setZoom(int zoom)
266 _zoom = zoom;
267 updateTransform();
270 Transform ENCAtlas::transform(int zoom) const
272 int z = zoom + Util::log2i(TILE_SIZE);
274 double scale = _projection.isGeographic()
275 ? 360.0 / (1<<z) : (2.0 * M_PI * WGS84_RADIUS) / (1<<z);
276 PointD topLeft(_projection.ll2xy(_llBounds.topLeft()));
277 return Transform(ReferencePoint(PointD(0, 0), topLeft),
278 PointD(scale, scale));
281 void ENCAtlas::updateTransform()
283 _transform = transform(_zoom);
285 RectD prect(_llBounds, _projection);
286 _bounds = QRectF(_transform.proj2img(prect.topLeft()),
287 _transform.proj2img(prect.bottomRight()));
290 bool ENCAtlas::isRunning(int zoom, const QPoint &xy) const
292 for (int i = 0; i < _jobs.size(); i++) {
293 const QList<ENC::RasterTile> &tiles = _jobs.at(i)->tiles();
294 for (int j = 0; j < tiles.size(); j++) {
295 const ENC::RasterTile &mt = tiles.at(j);
296 if (mt.zoom() == zoom && mt.xy() == xy)
297 return true;
301 return false;
304 void ENCAtlas::runJob(ENCJob *job)
306 _jobs.append(job);
308 connect(job, &ENCJob::finished, this, &ENCAtlas::jobFinished);
309 job->run();
312 void ENCAtlas::removeJob(ENCJob *job)
314 _jobs.removeOne(job);
315 job->deleteLater();
318 void ENCAtlas::jobFinished(ENCJob *job)
320 const QList<ENC::RasterTile> &tiles = job->tiles();
322 for (int i = 0; i < tiles.size(); i++) {
323 const ENC::RasterTile &mt = tiles.at(i);
324 if (mt.isValid())
325 QPixmapCache::insert(key(mt.zoom(), mt.xy()), mt.pixmap());
328 removeJob(job);
330 emit tilesLoaded();
333 void ENCAtlas::cancelJobs(bool wait)
335 for (int i = 0; i < _jobs.size(); i++)
336 _jobs.at(i)->cancel(wait);
339 QString ENCAtlas::key(int zoom, const QPoint &xy) const
341 return path() + "-" + QString::number(zoom) + "_"
342 + QString::number(xy.x()) + "_" + QString::number(xy.y());
345 void ENCAtlas::draw(QPainter *painter, const QRectF &rect, Flags flags)
347 Q_UNUSED(flags);
348 QPointF tl(floor(rect.left() / TILE_SIZE) * TILE_SIZE,
349 floor(rect.top() / TILE_SIZE) * TILE_SIZE);
350 QSizeF s(rect.right() - tl.x(), rect.bottom() - tl.y());
351 int width = ceil(s.width() / TILE_SIZE);
352 int height = ceil(s.height() / TILE_SIZE);
354 QList<RasterTile> tiles;
356 for (int i = 0; i < width; i++) {
357 for (int j = 0; j < height; j++) {
358 QPoint ttl(tl.x() + i * TILE_SIZE, tl.y() + j * TILE_SIZE);
359 if (isRunning(_zoom, ttl))
360 continue;
362 QPixmap pm;
363 if (QPixmapCache::find(key(_zoom, ttl), &pm))
364 painter->drawPixmap(ttl, pm);
365 else
366 tiles.append(RasterTile(_projection, _transform, _style,
367 _data.value(_usage), _zoom, zooms(_usage),
368 QRect(ttl, QSize(TILE_SIZE, TILE_SIZE)), _tileRatio));
372 if (!tiles.isEmpty()) {
373 if (flags & Map::Block) {
374 QFuture<void> future = QtConcurrent::map(tiles, &RasterTile::render);
375 future.waitForFinished();
377 for (int i = 0; i < tiles.size(); i++) {
378 const RasterTile &mt = tiles.at(i);
379 const QPixmap &pm = mt.pixmap();
380 painter->drawPixmap(mt.xy(), pm);
381 QPixmapCache::insert(key(mt.zoom(), mt.xy()), pm);
383 } else
384 runJob(new ENCJob(tiles));
388 Map *ENCAtlas::create(const QString &path, const Projection &proj, bool *isDir)
390 Q_UNUSED(proj);
392 if (isDir)
393 *isDir = true;
395 return new ENCAtlas(path);