Translated using Weblate (Catalan)
[GPXSee.git] / src / map / IMG / rastertile.cpp
blobf07e8ceb225cd31beddf9e9fac80ac97dfadfbb9
1 #include <QFont>
2 #include <QPainter>
3 #include <QCache>
4 #include "map/dem.h"
5 #include "map/textpathitem.h"
6 #include "map/textpointitem.h"
7 #include "map/bitmapline.h"
8 #include "map/rectd.h"
9 #include "map/hillshading.h"
10 #include "map/filter.h"
11 #include "style.h"
12 #include "lblfile.h"
13 #include "demtree.h"
14 #include "rastertile.h"
16 using namespace IMG;
18 #define TEXT_EXTENT 160
19 #define ICON_PADDING 2
21 #define AREA(rect) \
22 (rect.size().width() * rect.size().height())
24 #define HIDPI_IMG(dir, basename, ratio) \
25 (((ratio) > 1.0) \
26 ? QImage(dir "/" basename "@2x.png") \
27 : QImage(dir "/" basename ".png"))
29 #define ROAD 0
30 #define WATER 1
32 static const QColor textColor(Qt::black);
33 static const QColor haloColor(Qt::white);
34 static const QColor shieldColor(Qt::white);
35 static const QColor shieldBgColor1(0xdd, 0x3e, 0x3e);
36 static const QColor shieldBgColor2(0x37, 0x99, 0x47);
37 static const QColor shieldBgColor3(0x4a, 0x7f, 0xc1);
39 static const QColor *shieldBgColor(Shield::Type type)
41 switch (type) {
42 case Shield::USInterstate:
43 case Shield::Hbox:
44 return &shieldBgColor1;
45 case Shield::USShield:
46 case Shield::Box:
47 return &shieldBgColor2;
48 case Shield::USRound:
49 case Shield::Oval:
50 return &shieldBgColor3;
51 default:
52 return 0;
56 static int minShieldZoom(Shield::Type type)
58 switch (type) {
59 case Shield::USInterstate:
60 case Shield::Hbox:
61 return 17;
62 case Shield::USShield:
63 case Shield::Box:
64 return 19;
65 case Shield::USRound:
66 case Shield::Oval:
67 return 20;
68 default:
69 return 0;
73 static QPointF centroid(const QVector<QPointF> &polygon)
75 qreal area = 0;
76 qreal cx = 0, cy = 0;
78 for (int i = 0; i < polygon.size(); i++) {
79 int j = (i == polygon.size() - 1) ? 0 : i + 1;
80 qreal f = (polygon.at(i).x() * polygon.at(j).y() - polygon.at(j).x()
81 * polygon.at(i).y());
82 area += f;
83 cx += (polygon.at(i).x() + polygon.at(j).x()) * f;
84 cy += (polygon.at(i).y() + polygon.at(j).y()) * f;
87 qreal factor = 1.0 / (3.0 * area);
89 return QPointF(cx * factor, cy * factor);
92 static bool rectNearPolygon(const QPolygonF &polygon, const QRectF &rect)
94 return (polygon.boundingRect().contains(rect)
95 && (polygon.containsPoint(rect.topLeft(), Qt::OddEvenFill)
96 || polygon.containsPoint(rect.topRight(), Qt::OddEvenFill)
97 || polygon.containsPoint(rect.bottomLeft(), Qt::OddEvenFill)
98 || polygon.containsPoint(rect.bottomRight(), Qt::OddEvenFill)));
101 const QFont *RasterTile::poiFont(Style::FontSize size, int zoom,
102 bool extended) const
104 if (zoom > 25)
105 size = Style::Normal;
106 else if (extended)
107 size = Style::None;
109 switch (size) {
110 case Style::None:
111 return 0;
112 default:
113 return _data->style()->font(Style::ExtraSmall);
117 void RasterTile::ll2xy(QList<MapData::Poly> &polys) const
119 for (int i = 0; i < polys.size(); i++) {
120 MapData::Poly &poly = polys[i];
121 for (int j = 0; j < poly.points.size(); j++) {
122 QPointF &p = poly.points[j];
123 p = ll2xy(Coordinates(p.x(), p.y()));
128 void RasterTile::ll2xy(QList<MapData::Point> &points) const
130 for (int i = 0; i < points.size(); i++) {
131 QPointF p(ll2xy(points.at(i).coordinates));
132 points[i].coordinates = Coordinates(p.x(), p.y());
136 void RasterTile::drawPolygons(QPainter *painter,
137 const QList<MapData::Poly> &polygons) const
139 QCache<const LBLFile *, SubFile::Handle> hc(16);
141 for (int n = 0; n < _data->style()->drawOrder().size(); n++) {
142 for (int i = 0; i < polygons.size(); i++) {
143 const MapData::Poly &poly = polygons.at(i);
144 if (poly.type != _data->style()->drawOrder().at(n))
145 continue;
147 if (poly.raster.isValid()) {
148 RectC r(poly.raster.rect());
149 QPointF tl(ll2xy(r.topLeft()));
150 QPointF br(ll2xy(r.bottomRight()));
151 QSizeF size(QRectF(tl, br).size());
153 bool insert = false;
154 SubFile::Handle *hdl = hc.object(poly.raster.lbl());
155 if (!hdl) {
156 hdl = new SubFile::Handle(poly.raster.lbl());
157 insert = true;
159 QPixmap pm(poly.raster.lbl()->image(*hdl, poly.raster.id()));
160 if (insert)
161 hc.insert(poly.raster.lbl(), hdl);
162 qreal sx = size.width() / (qreal)pm.width();
163 qreal sy = size.height() / (qreal)pm.height();
165 painter->save();
166 painter->scale(sx, sy);
167 painter->drawPixmap(QPointF(tl.x() / sx, tl.y() / sy), pm);
168 painter->restore();
170 //painter->setPen(Qt::blue);
171 //painter->setBrush(Qt::NoBrush);
172 //painter->setRenderHint(QPainter::Antialiasing, false);
173 //painter->drawRect(QRectF(tl, br));
174 //painter->setRenderHint(QPainter::Antialiasing);
175 } else {
176 const Style::Polygon &style = _data->style()->polygon(poly.type);
178 painter->setPen(style.pen());
179 painter->setBrush(style.brush());
180 painter->drawPolygon(poly.points);
186 void RasterTile::drawLines(QPainter *painter,
187 const QList<MapData::Poly> &lines) const
189 painter->setBrush(Qt::NoBrush);
191 for (int i = 0; i < lines.size(); i++) {
192 const MapData::Poly &poly = lines.at(i);
193 const Style::Line &style = _data->style()->line(poly.type);
195 if (style.background() == Qt::NoPen)
196 continue;
198 painter->setPen(style.background());
199 painter->drawPolyline(poly.points);
202 for (int i = 0; i < lines.size(); i++) {
203 const MapData::Poly &poly = lines.at(i);
204 const Style::Line &style = _data->style()->line(poly.type);
206 if (!style.img().isNull())
207 BitmapLine::draw(painter, poly.points, style.img());
208 else if (style.foreground() != Qt::NoPen) {
209 painter->setPen(style.foreground());
210 painter->drawPolyline(poly.points);
215 void RasterTile::drawTextItems(QPainter *painter,
216 const QList<TextItem*> &textItems) const
218 for (int i = 0; i < textItems.size(); i++)
219 textItems.at(i)->paint(painter);
222 static void removeDuplicitLabel(QList<TextItem *> &labels, const QString &text,
223 const QRectF &tileRect)
225 for (int i = 0; i < labels.size(); i++) {
226 TextItem *item = labels.at(i);
227 if (tileRect.contains(item->boundingRect()) && *(item->text()) == text) {
228 labels.removeAt(i);
229 delete item;
230 return;
235 void RasterTile::processPolygons(const QList<MapData::Poly> &polygons,
236 QList<TextItem*> &textItems)
238 QSet<QString> set;
239 QList<TextItem *> labels;
241 for (int i = 0; i < polygons.size(); i++) {
242 const MapData::Poly &poly = polygons.at(i);
243 bool exists = set.contains(poly.label.text());
245 if (poly.label.text().isEmpty())
246 continue;
248 if (_zoom <= 23 && (Style::isWaterArea(poly.type)
249 || Style::isMilitaryArea(poly.type)
250 || Style::isNatureReserve(poly.type))) {
251 const Style::Polygon &style = _data->style()->polygon(poly.type);
252 TextPointItem *item = new TextPointItem(
253 centroid(poly.points).toPoint(), &poly.label.text(), poiFont(),
254 0, &style.brush().color(), &haloColor);
255 if (item->isValid() && !item->collides(textItems)
256 && !item->collides(labels)
257 && !(exists && _rect.contains(item->boundingRect().toRect()))
258 && rectNearPolygon(poly.points, item->boundingRect())) {
259 if (exists)
260 removeDuplicitLabel(labels, poly.label.text(), _rect);
261 else
262 set.insert(poly.label.text());
263 labels.append(item);
264 } else
265 delete item;
269 textItems.append(labels);
272 void RasterTile::processLines(QList<MapData::Poly> &lines,
273 QList<TextItem*> &textItems, const QImage (&arrows)[2])
275 std::stable_sort(lines.begin(), lines.end());
277 if (_zoom >= 22)
278 processStreetNames(lines, textItems, arrows);
279 processShields(lines, textItems);
282 void RasterTile::processStreetNames(const QList<MapData::Poly> &lines,
283 QList<TextItem*> &textItems, const QImage (&arrows)[2])
285 for (int i = 0; i < lines.size(); i++) {
286 const MapData::Poly &poly = lines.at(i);
287 const Style::Line &style = _data->style()->line(poly.type);
289 if (style.img().isNull() && style.foreground() == Qt::NoPen)
290 continue;
292 const QFont *fnt = _data->style()->font(style.text().size(),
293 Style::Small);
294 const QColor *color = style.text().color().isValid()
295 ? &style.text().color() : Style::isContourLine(poly.type)
296 ? 0 : &textColor;
297 const QColor *hColor = Style::isContourLine(poly.type) ? 0 : &haloColor;
298 const QImage *img = poly.oneway
299 ? Style::isWaterLine(poly.type)
300 ? &arrows[WATER] : &arrows[ROAD] : 0;
301 const QString *label = poly.label.text().isEmpty()
302 ? 0 : &poly.label.text();
304 if (!img && (!label || !fnt || !color))
305 continue;
307 TextPathItem *item = new TextPathItem(poly.points, label, _rect, fnt,
308 color, hColor, img);
309 if (item->isValid() && !item->collides(textItems))
310 textItems.append(item);
311 else {
312 delete item;
314 if (img) {
315 TextPathItem *item = new TextPathItem(poly.points, 0, _rect, 0,
316 0, 0, img);
317 if (item->isValid() && !item->collides(textItems))
318 textItems.append(item);
319 else
320 delete item;
326 void RasterTile::processShields(const QList<MapData::Poly> &lines,
327 QList<TextItem*> &textItems)
329 for (int type = FIRST_SHIELD; type <= LAST_SHIELD; type++) {
330 if (minShieldZoom(static_cast<Shield::Type>(type)) > _zoom)
331 continue;
333 QHash<Shield, QPolygonF> shields;
334 QHash<Shield, const Shield*> sp;
336 for (int i = 0; i < lines.size(); i++) {
337 const MapData::Poly &poly = lines.at(i);
338 const Shield &shield = poly.label.shield();
339 if (!shield.isValid() || shield.type() != type
340 || !Style::isMajorRoad(poly.type))
341 continue;
343 QPolygonF &p = shields[shield];
344 for (int j = 0; j < poly.points.size(); j++)
345 p.append(poly.points.at(j));
347 sp.insert(shield, &shield);
350 for (QHash<Shield, QPolygonF>::const_iterator it = shields.constBegin();
351 it != shields.constEnd(); ++it) {
352 const QPolygonF &p = it.value();
353 QRectF rect(p.boundingRect() & _rect);
354 if (AREA(rect) < AREA(QRect(0, 0, _rect.width()/4, _rect.width()/4)))
355 continue;
357 QMap<qreal, int> map;
358 QPointF center = rect.center();
359 for (int j = 0; j < p.size(); j++) {
360 QLineF l(p.at(j), center);
361 map.insert(l.length(), j);
364 QMap<qreal, int>::const_iterator jt = map.constBegin();
366 TextPointItem *item = new TextPointItem(
367 p.at(jt.value()).toPoint(), &(sp.value(it.key())->text()),
368 poiFont(), 0, &shieldColor, 0, shieldBgColor(it.key().type()));
370 bool valid = false;
371 while (true) {
372 if (!item->collides(textItems)
373 && _rect.contains(item->boundingRect().toRect())) {
374 valid = true;
375 break;
377 if (++jt == map.constEnd())
378 break;
379 item->setPos(p.at(jt.value()).toPoint());
382 if (valid)
383 textItems.append(item);
384 else
385 delete item;
390 void RasterTile::processPoints(QList<MapData::Point> &points,
391 QList<TextItem*> &textItems)
393 std::sort(points.begin(), points.end());
395 for (int i = 0; i < points.size(); i++) {
396 const MapData::Point &point = points.at(i);
397 const Style::Point &style = _data->style()->point(point.type);
398 bool poi = Style::isPOI(point.type);
400 const QString *label = point.label.text().isEmpty()
401 ? 0 : &(point.label.text());
402 const QImage *img = style.img().isNull() ? 0 : &style.img();
403 const QFont *fnt = poi
404 ? poiFont(style.text().size(), _zoom, point.classLabel)
405 : _data->style()->font(style.text().size());
406 const QColor *color = style.text().color().isValid()
407 ? &style.text().color() : &textColor;
408 const QColor *hcolor = Style::isDepthPoint(point.type)
409 ? 0 : &haloColor;
411 if ((!label || !fnt) && !img)
412 continue;
414 QPoint offset = img ? style.offset() : QPoint(0, 0);
416 TextPointItem *item = new TextPointItem(QPoint(point.coordinates.lon(),
417 point.coordinates.lat()) + offset, label, fnt, img, color, hcolor, 0,
418 ICON_PADDING);
419 if (item->isValid() && !item->collides(textItems))
420 textItems.append(item);
421 else
422 delete item;
426 void RasterTile::fetchData(QList<MapData::Poly> &polygons,
427 QList<MapData::Poly> &lines, QList<MapData::Point> &points)
429 QPoint ttl(_rect.topLeft());
431 QRectF polyRect(ttl, QPointF(ttl.x() + _rect.width(), ttl.y()
432 + _rect.height()));
433 RectD polyRectD(_transform.img2proj(polyRect.topLeft()),
434 _transform.img2proj(polyRect.bottomRight()));
435 _data->polys(polyRectD.toRectC(_proj, 20), _zoom,
436 &polygons, &lines);
438 QRectF pointRect(QPointF(ttl.x() - TEXT_EXTENT, ttl.y() - TEXT_EXTENT),
439 QPointF(ttl.x() + _rect.width() + TEXT_EXTENT, ttl.y() + _rect.height()
440 + TEXT_EXTENT));
441 RectD pointRectD(_transform.img2proj(pointRect.topLeft()),
442 _transform.img2proj(pointRect.bottomRight()));
443 _data->points(pointRectD.toRectC(_proj, 20), _zoom, &points);
446 MatrixD RasterTile::elevation(int extend) const
448 MatrixD m(_rect.height() + 2 * extend, _rect.width() + 2 * extend);
449 QVector<Coordinates> ll;
451 int left = _rect.left() - extend;
452 int right = _rect.right() + extend;
453 int top = _rect.top() - extend;
454 int bottom = _rect.bottom() + extend;
456 ll.reserve(m.w() * m.h());
457 for (int y = top; y <= bottom; y++)
458 for (int x = left; x <= right; x++)
459 ll.append(xy2ll(QPointF(x, y)));
461 if (_data->hasDEM()) {
462 RectC rect;
463 QList<MapData::Elevation> tiles;
465 for (int i = 0; i < ll.size(); i++)
466 rect = rect.united(ll.at(i));
467 // Extra margin for always including the next DEM tile on the map tile
468 // edges (the DEM tile resolution is usally 0.5-15% of the map tile)
469 double factor = 6 - (_zoom - 24) * 1.7;
470 _data->elevations(rect.adjusted(0, 0, rect.width() / factor,
471 -rect.height() / factor), _zoom, &tiles);
473 DEMTree tree(tiles);
474 for (int i = 0; i < ll.size(); i++)
475 m.at(i) = tree.elevation(ll.at(i));
476 } else {
477 DEM::lock();
478 for (int i = 0; i < ll.size(); i++)
479 m.at(i) = DEM::elevation(ll.at(i));
480 DEM::unlock();
483 return m;
486 void RasterTile::drawHillShading(QPainter *painter) const
488 if (_hillShading && _zoom >= 18 && _zoom <= 24) {
489 if (HillShading::blur()) {
490 MatrixD dem(Filter::blur(elevation(HillShading::blur() + 1),
491 HillShading::blur()));
492 QImage img(HillShading::render(dem, HillShading::blur() + 1));
493 painter->drawImage(_rect.x(), _rect.y(), img);
494 } else
495 painter->drawImage(_rect.x(), _rect.y(),
496 HillShading::render(elevation(1), 1));
500 void RasterTile::render()
502 QImage img(_rect.width() * _ratio, _rect.height() * _ratio,
503 QImage::Format_ARGB32_Premultiplied);
504 QList<MapData::Poly> polygons;
505 QList<MapData::Poly> lines;
506 QList<MapData::Point> points;
507 QList<TextItem*> textItems;
508 QImage arrows[2];
510 arrows[ROAD] = HIDPI_IMG(":/map", "arrow", _ratio);
511 arrows[WATER] = HIDPI_IMG(":/map", "water-arrow", _ratio);
513 fetchData(polygons, lines, points);
514 ll2xy(polygons);
515 ll2xy(lines);
516 ll2xy(points);
518 processPoints(points, textItems);
519 processPolygons(polygons, textItems);
520 processLines(lines, textItems, arrows);
522 img.setDevicePixelRatio(_ratio);
523 img.fill(Qt::transparent);
525 QPainter painter(&img);
526 painter.setRenderHint(QPainter::SmoothPixmapTransform);
527 painter.setRenderHint(QPainter::Antialiasing);
528 painter.translate(-_rect.x(), -_rect.y());
530 drawPolygons(&painter, polygons);
531 drawHillShading(&painter);
532 drawLines(&painter, lines);
533 drawTextItems(&painter, textItems);
535 qDeleteAll(textItems);
537 //painter.setPen(Qt::red);
538 //painter.setBrush(Qt::NoBrush);
539 //painter.setRenderHint(QPainter::Antialiasing, false);
540 //painter.drawRect(_rect);
542 _pixmap.convertFromImage(img);