Added hillshading to vector maps
[GPXSee.git] / src / map / IMG / rastertile.cpp
blob93ca2d8ffdfb5ccfb8a9875dcc5d879c2546700d
1 #include <QFont>
2 #include <QPainter>
3 #include <QCache>
4 #include "map/textpathitem.h"
5 #include "map/textpointitem.h"
6 #include "map/bitmapline.h"
7 #include "map/rectd.h"
8 #include "map/hillshading.h"
9 #include "data/dem.h"
10 #include "style.h"
11 #include "lblfile.h"
12 #include "rastertile.h"
14 using namespace IMG;
16 #define TEXT_EXTENT 160
17 #define ICON_PADDING 2
19 #define AREA(rect) \
20 (rect.size().width() * rect.size().height())
22 #define HIDPI_IMG(dir, basename, ratio) \
23 (((ratio) > 1.0) \
24 ? QImage(dir "/" basename "@2x.png") \
25 : QImage(dir "/" basename ".png"))
27 #define ROAD 0
28 #define WATER 1
30 static const QColor textColor(Qt::black);
31 static const QColor haloColor(Qt::white);
32 static const QColor shieldColor(Qt::white);
33 static const QColor shieldBgColor1(0xdd, 0x3e, 0x3e);
34 static const QColor shieldBgColor2(0x37, 0x99, 0x47);
35 static const QColor shieldBgColor3(0x4a, 0x7f, 0xc1);
37 static const QColor *shieldBgColor(Shield::Type type)
39 switch (type) {
40 case Shield::USInterstate:
41 case Shield::Hbox:
42 return &shieldBgColor1;
43 case Shield::USShield:
44 case Shield::Box:
45 return &shieldBgColor2;
46 case Shield::USRound:
47 case Shield::Oval:
48 return &shieldBgColor3;
49 default:
50 return 0;
54 static int minShieldZoom(Shield::Type type)
56 switch (type) {
57 case Shield::USInterstate:
58 case Shield::Hbox:
59 return 17;
60 case Shield::USShield:
61 case Shield::Box:
62 return 19;
63 case Shield::USRound:
64 case Shield::Oval:
65 return 20;
66 default:
67 return 0;
71 static qreal area(const QVector<QPointF> &polygon)
73 qreal area = 0;
75 for (int i = 0; i < polygon.size(); i++) {
76 int j = (i + 1) % polygon.size();
77 area += polygon.at(i).x() * polygon.at(j).y();
78 area -= polygon.at(i).y() * polygon.at(j).x();
80 area /= 2.0;
82 return area;
85 static QPointF centroid(const QVector<QPointF> &polygon)
87 qreal cx = 0, cy = 0;
88 qreal factor = 1.0 / (6.0 * area(polygon));
90 for (int i = 0; i < polygon.size(); i++) {
91 int j = (i + 1) % polygon.size();
92 qreal f = (polygon.at(i).x() * polygon.at(j).y() - polygon.at(j).x()
93 * polygon.at(i).y());
94 cx += (polygon.at(i).x() + polygon.at(j).x()) * f;
95 cy += (polygon.at(i).y() + polygon.at(j).y()) * f;
98 return QPointF(cx * factor, cy * factor);
101 static bool rectNearPolygon(const QPolygonF &polygon, const QRectF &rect)
103 return (polygon.boundingRect().contains(rect)
104 && (polygon.containsPoint(rect.topLeft(), Qt::OddEvenFill)
105 || polygon.containsPoint(rect.topRight(), Qt::OddEvenFill)
106 || polygon.containsPoint(rect.bottomLeft(), Qt::OddEvenFill)
107 || polygon.containsPoint(rect.bottomRight(), Qt::OddEvenFill)));
110 const QFont *RasterTile::poiFont(Style::FontSize size, int zoom, bool extended)
112 if (zoom > 25)
113 size = Style::Normal;
114 else if (extended)
115 size = Style::None;
117 switch (size) {
118 case Style::None:
119 return 0;
120 default:
121 return _data->style()->font(Style::ExtraSmall);
125 void RasterTile::ll2xy(QList<MapData::Poly> &polys)
127 for (int i = 0; i < polys.size(); i++) {
128 MapData::Poly &poly = polys[i];
129 for (int j = 0; j < poly.points.size(); j++) {
130 QPointF &p = poly.points[j];
131 p = ll2xy(Coordinates(p.x(), p.y()));
136 void RasterTile::ll2xy(QList<MapData::Point> &points)
138 for (int i = 0; i < points.size(); i++) {
139 QPointF p(ll2xy(points.at(i).coordinates));
140 points[i].coordinates = Coordinates(p.x(), p.y());
144 void RasterTile::drawPolygons(QPainter *painter,
145 const QList<MapData::Poly> &polygons)
147 QCache<const LBLFile *, SubFile::Handle> hc(16);
149 for (int n = 0; n < _data->style()->drawOrder().size(); n++) {
150 for (int i = 0; i < polygons.size(); i++) {
151 const MapData::Poly &poly = polygons.at(i);
152 if (poly.type != _data->style()->drawOrder().at(n))
153 continue;
155 if (poly.raster.isValid()) {
156 RectC r(poly.raster.rect());
157 QPointF tl(ll2xy(r.topLeft()));
158 QPointF br(ll2xy(r.bottomRight()));
159 QSizeF size(QRectF(tl, br).size());
161 bool insert = false;
162 SubFile::Handle *hdl = hc.object(poly.raster.lbl());
163 if (!hdl) {
164 hdl = new SubFile::Handle(poly.raster.lbl());
165 insert = true;
167 QPixmap pm(poly.raster.lbl()->image(*hdl, poly.raster.id()));
168 if (insert)
169 hc.insert(poly.raster.lbl(), hdl);
170 qreal sx = size.width() / (qreal)pm.width();
171 qreal sy = size.height() / (qreal)pm.height();
173 painter->save();
174 painter->scale(sx, sy);
175 painter->drawPixmap(QPointF(tl.x() / sx, tl.y() / sy), pm);
176 painter->restore();
178 //painter->setPen(Qt::blue);
179 //painter->setBrush(Qt::NoBrush);
180 //painter->drawRect(QRectF(tl, br));
181 } else {
182 const Style::Polygon &style = _data->style()->polygon(poly.type);
184 painter->setPen(style.pen());
185 painter->setBrush(style.brush());
186 painter->drawPolygon(poly.points);
192 void RasterTile::drawLines(QPainter *painter, const QList<MapData::Poly> &lines)
194 painter->setBrush(Qt::NoBrush);
196 for (int i = 0; i < lines.size(); i++) {
197 const MapData::Poly &poly = lines.at(i);
198 const Style::Line &style = _data->style()->line(poly.type);
200 if (style.background() == Qt::NoPen)
201 continue;
203 painter->setPen(style.background());
204 painter->drawPolyline(poly.points);
207 for (int i = 0; i < lines.size(); i++) {
208 const MapData::Poly &poly = lines.at(i);
209 const Style::Line &style = _data->style()->line(poly.type);
211 if (!style.img().isNull())
212 BitmapLine::draw(painter, poly.points, style.img());
213 else if (style.foreground() != Qt::NoPen) {
214 painter->setPen(style.foreground());
215 painter->drawPolyline(poly.points);
220 void RasterTile::drawTextItems(QPainter *painter,
221 const QList<TextItem*> &textItems)
223 for (int i = 0; i < textItems.size(); i++)
224 textItems.at(i)->paint(painter);
227 static void removeDuplicitLabel(QList<TextItem *> &labels, const QString &text,
228 const QRectF &tileRect)
230 for (int i = 0; i < labels.size(); i++) {
231 TextItem *item = labels.at(i);
232 if (tileRect.contains(item->boundingRect()) && *(item->text()) == text) {
233 labels.removeAt(i);
234 delete item;
235 return;
240 void RasterTile::processPolygons(const QList<MapData::Poly> &polygons,
241 QList<TextItem*> &textItems)
243 QSet<QString> set;
244 QList<TextItem *> labels;
246 for (int i = 0; i < polygons.size(); i++) {
247 const MapData::Poly &poly = polygons.at(i);
248 bool exists = set.contains(poly.label.text());
250 if (poly.label.text().isEmpty())
251 continue;
253 if (_zoom <= 23 && (Style::isWaterArea(poly.type)
254 || Style::isMilitaryArea(poly.type)
255 || Style::isNatureReserve(poly.type))) {
256 const Style::Polygon &style = _data->style()->polygon(poly.type);
257 TextPointItem *item = new TextPointItem(
258 centroid(poly.points).toPoint(), &poly.label.text(), poiFont(),
259 0, &style.brush().color(), &haloColor);
260 if (item->isValid() && !item->collides(textItems)
261 && !item->collides(labels)
262 && !(exists && _rect.contains(item->boundingRect().toRect()))
263 && rectNearPolygon(poly.points, item->boundingRect())) {
264 if (exists)
265 removeDuplicitLabel(labels, poly.label.text(), _rect);
266 else
267 set.insert(poly.label.text());
268 labels.append(item);
269 } else
270 delete item;
274 textItems.append(labels);
277 void RasterTile::processLines(QList<MapData::Poly> &lines,
278 QList<TextItem*> &textItems, const QImage (&arrows)[2])
280 std::stable_sort(lines.begin(), lines.end());
282 if (_zoom >= 22)
283 processStreetNames(lines, textItems, arrows);
284 processShields(lines, textItems);
287 void RasterTile::processStreetNames(const QList<MapData::Poly> &lines,
288 QList<TextItem*> &textItems, const QImage (&arrows)[2])
290 for (int i = 0; i < lines.size(); i++) {
291 const MapData::Poly &poly = lines.at(i);
292 const Style::Line &style = _data->style()->line(poly.type);
294 if (style.img().isNull() && style.foreground() == Qt::NoPen)
295 continue;
297 const QFont *fnt = _data->style()->font(style.text().size(),
298 Style::Small);
299 const QColor *color = style.text().color().isValid()
300 ? &style.text().color() : 0;
301 const QColor *hColor = Style::isContourLine(poly.type) ? 0 : &haloColor;
302 const QImage *img = poly.oneway
303 ? Style::isWaterLine(poly.type)
304 ? &arrows[WATER] : &arrows[ROAD] : 0;
305 const QString *label = poly.label.text().isEmpty()
306 ? 0 : &poly.label.text();
308 if (!img && (!label || !fnt))
309 continue;
311 TextPathItem *item = new TextPathItem(poly.points, label, _rect, fnt,
312 color, hColor, img);
313 if (item->isValid() && !item->collides(textItems))
314 textItems.append(item);
315 else {
316 delete item;
318 if (img) {
319 TextPathItem *item = new TextPathItem(poly.points, 0, _rect, 0,
320 0, 0, img);
321 if (item->isValid() && !item->collides(textItems))
322 textItems.append(item);
323 else
324 delete item;
330 void RasterTile::processShields(const QList<MapData::Poly> &lines,
331 QList<TextItem*> &textItems)
333 for (int type = FIRST_SHIELD; type <= LAST_SHIELD; type++) {
334 if (minShieldZoom(static_cast<Shield::Type>(type)) > _zoom)
335 continue;
337 QHash<Shield, QPolygonF> shields;
338 QHash<Shield, const Shield*> sp;
340 for (int i = 0; i < lines.size(); i++) {
341 const MapData::Poly &poly = lines.at(i);
342 const Shield &shield = poly.label.shield();
343 if (!shield.isValid() || shield.type() != type
344 || !Style::isMajorRoad(poly.type))
345 continue;
347 QPolygonF &p = shields[shield];
348 for (int j = 0; j < poly.points.size(); j++)
349 p.append(poly.points.at(j));
351 sp.insert(shield, &shield);
354 for (QHash<Shield, QPolygonF>::const_iterator it = shields.constBegin();
355 it != shields.constEnd(); ++it) {
356 const QPolygonF &p = it.value();
357 QRectF rect(p.boundingRect() & _rect);
358 if (AREA(rect) < AREA(QRect(0, 0, _rect.width()/4, _rect.width()/4)))
359 continue;
361 QMap<qreal, int> map;
362 QPointF center = rect.center();
363 for (int j = 0; j < p.size(); j++) {
364 QLineF l(p.at(j), center);
365 map.insert(l.length(), j);
368 QMap<qreal, int>::const_iterator jt = map.constBegin();
370 TextPointItem *item = new TextPointItem(
371 p.at(jt.value()).toPoint(), &(sp.value(it.key())->text()),
372 poiFont(), 0, &shieldColor, 0, shieldBgColor(it.key().type()));
374 bool valid = false;
375 while (true) {
376 if (!item->collides(textItems)
377 && _rect.contains(item->boundingRect().toRect())) {
378 valid = true;
379 break;
381 if (++jt == map.constEnd())
382 break;
383 item->setPos(p.at(jt.value()).toPoint());
386 if (valid)
387 textItems.append(item);
388 else
389 delete item;
394 void RasterTile::processPoints(QList<MapData::Point> &points,
395 QList<TextItem*> &textItems)
397 std::sort(points.begin(), points.end());
399 for (int i = 0; i < points.size(); i++) {
400 const MapData::Point &point = points.at(i);
401 const Style::Point &style = _data->style()->point(point.type);
402 bool poi = Style::isPOI(point.type);
404 const QString *label = point.label.text().isEmpty()
405 ? 0 : &(point.label.text());
406 const QImage *img = style.img().isNull() ? 0 : &style.img();
407 const QFont *fnt = poi
408 ? poiFont(style.text().size(), _zoom, point.classLabel)
409 : _data->style()->font(style.text().size());
410 const QColor *color = style.text().color().isValid()
411 ? &style.text().color() : &textColor;
412 const QColor *hcolor = Style::isDepthPoint(point.type)
413 ? 0 : &haloColor;
415 if ((!label || !fnt) && !img)
416 continue;
418 TextPointItem *item = new TextPointItem(QPoint(point.coordinates.lon(),
419 point.coordinates.lat()), label, fnt, img, color, hcolor, 0,
420 ICON_PADDING);
421 if (item->isValid() && !item->collides(textItems))
422 textItems.append(item);
423 else
424 delete item;
428 void RasterTile::fetchData(QList<MapData::Poly> &polygons,
429 QList<MapData::Poly> &lines, QList<MapData::Point> &points)
431 QPoint ttl(_rect.topLeft());
433 QRectF polyRect(ttl, QPointF(ttl.x() + _rect.width(), ttl.y()
434 + _rect.height()));
435 RectD polyRectD(_transform.img2proj(polyRect.topLeft()),
436 _transform.img2proj(polyRect.bottomRight()));
437 _data->polys(polyRectD.toRectC(_proj, 20), _zoom,
438 &polygons, &lines);
440 QRectF pointRect(QPointF(ttl.x() - TEXT_EXTENT, ttl.y() - TEXT_EXTENT),
441 QPointF(ttl.x() + _rect.width() + TEXT_EXTENT, ttl.y() + _rect.height()
442 + TEXT_EXTENT));
443 RectD pointRectD(_transform.img2proj(pointRect.topLeft()),
444 _transform.img2proj(pointRect.bottomRight()));
445 _data->points(pointRectD.toRectC(_proj, 20), _zoom, &points);
448 Matrix RasterTile::elevation() const
450 Matrix m(_rect.height() + 2, _rect.width() + 2);
452 int left = _rect.left() - 1;
453 int right = _rect.right() + 1;
454 int top = _rect.top() - 1;
455 int bottom = _rect.bottom() + 1;
457 DEM::lock();
458 for (int y = top; y <= bottom; y++) {
459 for (int x = left; x <= right; x++)
460 m.m(y - top, x - left) = DEM::elevation(xy2ll(QPointF(x, y)));
462 DEM::unlock();
464 return m;
467 void RasterTile::render()
469 QImage img(_rect.width() * _ratio, _rect.height() * _ratio,
470 QImage::Format_ARGB32_Premultiplied);
471 QList<MapData::Poly> polygons;
472 QList<MapData::Poly> lines;
473 QList<MapData::Point> points;
474 QList<TextItem*> textItems;
475 QImage arrows[2];
477 arrows[ROAD] = HIDPI_IMG(":/map", "arrow", _ratio);
478 arrows[WATER] = HIDPI_IMG(":/map", "water-arrow", _ratio);
480 fetchData(polygons, lines, points);
481 ll2xy(polygons);
482 ll2xy(lines);
483 ll2xy(points);
485 processPoints(points, textItems);
486 processPolygons(polygons, textItems);
487 processLines(lines, textItems, arrows);
489 img.setDevicePixelRatio(_ratio);
490 img.fill(Qt::transparent);
492 QPainter painter(&img);
493 painter.setRenderHint(QPainter::SmoothPixmapTransform);
494 painter.setRenderHint(QPainter::Antialiasing);
495 painter.translate(-_rect.x(), -_rect.y());
497 drawPolygons(&painter, polygons);
498 if (_hillShading && _zoom >= 18 && _zoom <= 24)
499 painter.drawImage(_rect.x(), _rect.y(), HillShading::render(elevation()));
500 drawLines(&painter, lines);
501 drawTextItems(&painter, textItems);
503 qDeleteAll(textItems);
505 _pixmap = QPixmap::fromImage(img);
507 //painter.setPen(Qt::red);
508 //painter.setRenderHint(QPainter::Antialiasing, false);
509 //painter.drawRect(_rect);