5 #include "map/textpathitem.h"
6 #include "map/textpointitem.h"
7 #include "map/bitmapline.h"
9 #include "map/hillshading.h"
10 #include "map/filter.h"
14 #include "rastertile.h"
18 #define TEXT_EXTENT 160
19 #define ICON_PADDING 2
22 (rect.size().width() * rect.size().height())
24 #define HIDPI_IMG(dir, basename, ratio) \
26 ? QImage(dir "/" basename "@2x.png") \
27 : QImage(dir "/" basename ".png"))
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
)
42 case Shield::USInterstate
:
44 return &shieldBgColor1
;
45 case Shield::USShield
:
47 return &shieldBgColor2
;
50 return &shieldBgColor3
;
56 static int minShieldZoom(Shield::Type type
)
59 case Shield::USInterstate
:
62 case Shield::USShield
:
73 static QPointF
centroid(const QVector
<QPointF
> &polygon
)
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()
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
,
105 size
= Style::Normal
;
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
))
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());
154 SubFile::Handle
*hdl
= hc
.object(poly
.raster
.lbl());
156 hdl
= new SubFile::Handle(poly
.raster
.lbl());
159 QPixmap
pm(poly
.raster
.lbl()->image(*hdl
, poly
.raster
.id()));
161 hc
.insert(poly
.raster
.lbl(), hdl
);
162 qreal sx
= size
.width() / (qreal
)pm
.width();
163 qreal sy
= size
.height() / (qreal
)pm
.height();
166 painter
->scale(sx
, sy
);
167 painter
->drawPixmap(QPointF(tl
.x() / sx
, tl
.y() / sy
), pm
);
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);
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
)
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
) {
235 void RasterTile::processPolygons(const QList
<MapData::Poly
> &polygons
,
236 QList
<TextItem
*> &textItems
)
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())
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())) {
260 removeDuplicitLabel(labels
, poly
.label
.text(), _rect
);
262 set
.insert(poly
.label
.text());
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());
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
)
292 const QFont
*fnt
= _data
->style()->font(style
.text().size(),
294 const QColor
*color
= style
.text().color().isValid()
295 ? &style
.text().color() : Style::isContourLine(poly
.type
)
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
))
307 TextPathItem
*item
= new TextPathItem(poly
.points
, label
, _rect
, fnt
,
309 if (item
->isValid() && !item
->collides(textItems
))
310 textItems
.append(item
);
315 TextPathItem
*item
= new TextPathItem(poly
.points
, 0, _rect
, 0,
317 if (item
->isValid() && !item
->collides(textItems
))
318 textItems
.append(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
)
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
))
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)))
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()));
372 if (!item
->collides(textItems
)
373 && _rect
.contains(item
->boundingRect().toRect())) {
377 if (++jt
== map
.constEnd())
379 item
->setPos(p
.at(jt
.value()).toPoint());
383 textItems
.append(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
)
411 if ((!label
|| !fnt
) && !img
)
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,
419 if (item
->isValid() && !item
->collides(textItems
))
420 textItems
.append(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()
433 RectD
polyRectD(_transform
.img2proj(polyRect
.topLeft()),
434 _transform
.img2proj(polyRect
.bottomRight()));
435 _data
->polys(polyRectD
.toRectC(_proj
, 20), _zoom
,
438 QRectF
pointRect(QPointF(ttl
.x() - TEXT_EXTENT
, ttl
.y() - TEXT_EXTENT
),
439 QPointF(ttl
.x() + _rect
.width() + TEXT_EXTENT
, ttl
.y() + _rect
.height()
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()) {
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
);
474 for (int i
= 0; i
< ll
.size(); i
++)
475 m
.at(i
) = tree
.elevation(ll
.at(i
));
478 for (int i
= 0; i
< ll
.size(); i
++)
479 m
.at(i
) = DEM::elevation(ll
.at(i
));
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
);
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
;
510 arrows
[ROAD
] = HIDPI_IMG(":/map", "arrow", _ratio
);
511 arrows
[WATER
] = HIDPI_IMG(":/map", "water-arrow", _ratio
);
513 fetchData(polygons
, lines
, 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
);