From 537b1c37165a0bd718a2288431b3c0a5300e335b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20T=C5=AFma?= Date: Sun, 30 Jun 2019 20:39:22 +0200 Subject: [PATCH] Added support for label shields --- gpxsee.pro | 3 +- src/map/IMG/img.h | 5 +- src/map/IMG/label.h | 74 +++++++++++++++ src/map/IMG/lblfile.cpp | 73 ++++++++------- src/map/IMG/lblfile.h | 7 +- src/map/IMG/style.cpp | 7 +- src/map/IMG/style.h | 1 + src/map/IMG/textpointitem.cpp | 44 +++++++-- src/map/IMG/textpointitem.h | 6 +- src/map/imgmap.cpp | 163 ++++++++++++++++++++++++++-------- 10 files changed, 298 insertions(+), 85 deletions(-) create mode 100644 src/map/IMG/label.h diff --git a/gpxsee.pro b/gpxsee.pro index 1a80bc43..d8123db1 100644 --- a/gpxsee.pro +++ b/gpxsee.pro @@ -180,7 +180,8 @@ HEADERS += src/common/config.h \ src/map/IMG/netfile.h \ src/GUI/limitedcombobox.h \ src/GUI/pathtickitem.h \ - src/map/IMG/textitem.h + src/map/IMG/textitem.h \ + src/map/IMG/label.h SOURCES += src/main.cpp \ src/common/coordinates.cpp \ src/common/rectc.cpp \ diff --git a/src/map/IMG/img.h b/src/map/IMG/img.h index 8e0dab29..305ed651 100644 --- a/src/map/IMG/img.h +++ b/src/map/IMG/img.h @@ -10,6 +10,7 @@ #include "common/rectc.h" #include "common/range.h" #include "style.h" +#include "label.h" class VectorTile; class SubFile; @@ -23,7 +24,7 @@ public: ll2xy() the points in the IMG class as this can not be done in parallel. */ QVector points; - QString label; + Label label; quint32 type; bool operator<(const Poly &other) const @@ -34,7 +35,7 @@ public: Point() : id(0) {} Coordinates coordinates; - QString label; + Label label; quint32 type; bool poi; quint64 id; diff --git a/src/map/IMG/label.h b/src/map/IMG/label.h new file mode 100644 index 00000000..51f0bc98 --- /dev/null +++ b/src/map/IMG/label.h @@ -0,0 +1,74 @@ +#ifndef LABEL_H +#define LABEL_H + +#include +#include + +class Label { +public: + class Shield + { + public: + enum Type { + None, + USInterstate, + USShield, + USRound, + Hbox, + Box, + Oval + }; + + Shield() : _type(None) {} + Shield(Type type, const QString &name) : _type(type), _text(name) {} + + Type type() const {return _type;} + const QString &text() const {return _text;} + bool isValid() const {return _type > None && !_text.isEmpty();} + + bool operator==(const Shield &other) const + {return _type == other._type && _text == other._text;} + + private: + Type _type; + QString _text; + }; + + Label() {} + Label(const QString &text, const Shield &shield = Shield()) + : _text(text), _shield(shield) {} + + const Shield &shield() const {return _shield;} + const QString &text() const {return _text;} + bool isValid() const {return _shield.isValid() || !_text.isEmpty();} + + void setText(const QString &text) {_text = text;} + +private: + QString _text; + Shield _shield; +}; + +inline uint qHash(const Label::Shield &shield) +{ + return qHash(shield.text()) ^ shield.type(); +} + +#ifndef QT_NO_DEBUG +inline QDebug operator<<(QDebug dbg, const Label::Shield &shield) +{ + dbg.nospace() << "Shield(" << shield.type() << ", " << shield.text() << ")"; + return dbg.space(); +} + +inline QDebug operator<<(QDebug dbg, const Label &label) +{ + dbg.nospace() << "Label("; + if (label.shield().isValid()) + dbg << label.shield() << ", "; + dbg << label.text() << ")"; + return dbg.space(); +} +#endif // QT_NO_DEBUG + +#endif // LABEL_H diff --git a/src/map/IMG/lblfile.cpp b/src/map/IMG/lblfile.cpp index 4c2baea2..9cb8f4a4 100644 --- a/src/map/IMG/lblfile.cpp +++ b/src/map/IMG/lblfile.cpp @@ -7,7 +7,7 @@ static quint8 NORMAL_CHARS[] = { ' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', - 'X', 'Y', 'Z', '~', '~', '~', '~', '~', + 'X', 'Y', 'Z', '~', '~', '~', ' ', ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '~', '~', '~', '~', '~', '~' }; @@ -48,6 +48,7 @@ static QString capitalize(const QString &str) return ret; } + bool LBLFile::init() { Handle hdl; @@ -73,45 +74,48 @@ bool LBLFile::init() return true; } -QString LBLFile::label6b(Handle &hdl, quint32 offset) const +Label LBLFile::label6b(Handle &hdl, quint32 offset) const { - QByteArray result; + Label::Shield::Type shieldType = Label::Shield::None; + QByteArray label, shieldLabel; + QByteArray *bap = &label; Charset curCharSet = Normal; quint8 b1, b2, b3; if (!seek(hdl, offset)) - return QString(); + return Label(); while (true) { if (!(readByte(hdl, b1) && readByte(hdl, b2) && readByte(hdl, b3))) - return QString(); + return Label(); int c[]= {b1>>2, (b1&0x3)<<4|b2>>4, (b2&0xF)<<2|b3>>6, b3&0x3F}; for (int cpt = 0; cpt < 4; cpt++) { - if (c[cpt] > 0x2F) - return QString::fromLatin1(result); + if (c[cpt] > 0x2f || (curCharSet == Normal && c[cpt] == 0x1d)) + return Label(capitalize(QString::fromLatin1(label)), + Label::Shield(shieldType, shieldLabel)); switch (curCharSet) { case Normal: if (c[cpt] == 0x1c) curCharSet = Symbol; else if (c[cpt] == 0x1b) curCharSet = Special; - else if(c[cpt] == 0x1d) - result.append('|'); - else if (c[cpt] == 0x1f) - result.append(' '); - else if (c[cpt] == 0x1e) - result.append(' '); + else if (c[cpt] >= 0x2a && c[cpt] <= 0x2f) { + shieldType = (Label::Shield::Type)(c[cpt] - 0x29); + bap = &shieldLabel; + } else if (bap == &shieldLabel + && NORMAL_CHARS[c[cpt]] == ' ') + bap = &label; else - result.append(NORMAL_CHARS[c[cpt]]); + bap->append(NORMAL_CHARS[c[cpt]]); break; case Symbol: - result.append(SYMBOL_CHARS[c[cpt]]); + bap->append(SYMBOL_CHARS[c[cpt]]); curCharSet = Normal; break; case Special: - result.append(SPECIAL_CHARS[c[cpt]]); + bap->append(SPECIAL_CHARS[c[cpt]]); curCharSet = Normal; break; } @@ -119,30 +123,39 @@ QString LBLFile::label6b(Handle &hdl, quint32 offset) const } } -QString LBLFile::label8b(Handle &hdl, quint32 offset) const +Label LBLFile::label8b(Handle &hdl, quint32 offset) const { - QByteArray result; + Label::Shield::Type shieldType = Label::Shield::None; + QByteArray label, shieldLabel; + QByteArray *bap = &label; quint8 c; if (!seek(hdl, offset)) - return QString(); + return Label(); while (true) { if (!readByte(hdl, c)) - return QString(); - if (!c) + return Label(); + if (!c || c == 0x1d) break; - if (c >= 0x1B && c <= 0x1F) - result.append(' '); - else if (c > 0x07) - result.append(c); + if ((c >= 0x1e && c <= 0x1f)) + bap->append(' '); + else if (c <= 0x07) { + shieldType = (Label::Shield::Type)c; + bap = &shieldLabel; + } else if (bap == &shieldLabel && QChar(c).isSpace()) { + bap = &label; + } else + bap->append(c); } - return _codec ? _codec->toUnicode(result) : QString::fromLatin1(result); + return Label(capitalize(_codec ? _codec->toUnicode(label) + : QString::fromLatin1(label)), Label::Shield(shieldType, _codec + ? _codec->toUnicode(shieldLabel) : QString::fromLatin1(shieldLabel))); } -QString LBLFile::label(Handle &hdl, quint32 offset, bool poi) +Label LBLFile::label(Handle &hdl, quint32 offset, bool poi) { if (!_init) { if (!(_init = init())) @@ -163,11 +176,11 @@ QString LBLFile::label(Handle &hdl, quint32 offset, bool poi) switch (_encoding) { case 6: - return capitalize(label6b(hdl, labelOffset)); + return label6b(hdl, labelOffset); case 9: case 10: - return capitalize(label8b(hdl, labelOffset)); + return label8b(hdl, labelOffset); default: - return QString(); + return Label(); } } diff --git a/src/map/IMG/lblfile.h b/src/map/IMG/lblfile.h index b3b4951a..cb7b3614 100644 --- a/src/map/IMG/lblfile.h +++ b/src/map/IMG/lblfile.h @@ -2,6 +2,7 @@ #define LBLFILE_H #include "subfile.h" +#include "label.h" class QTextCodec; @@ -10,13 +11,13 @@ class LBLFile : public SubFile public: LBLFile(IMG *img, quint32 size) : SubFile(img, size), _init(false) {} - QString label(Handle &hdl, quint32 offset, bool poi = false); + Label label(Handle &hdl, quint32 offset, bool poi = false); private: bool init(); - QString label6b(Handle &hdl, quint32 offset) const; - QString label8b(Handle &hdl, quint32 offset) const; + Label label6b(Handle &hdl, quint32 offset) const; + Label label8b(Handle &hdl, quint32 offset) const; quint32 _offset; quint32 _size; diff --git a/src/map/IMG/style.cpp b/src/map/IMG/style.cpp index 5458d338..8a82fe64 100644 --- a/src/map/IMG/style.cpp +++ b/src/map/IMG/style.cpp @@ -977,7 +977,12 @@ bool Style::isSpot(quint32 type) bool Style::isSummit(quint32 type) { - return (type == 0x6616); + return (type == 0x6616); +} + +bool Style::isMajorRoad(quint32 type) +{ + return (type <= TYPE(0x04)); } Style::POIClass Style::poiClass(quint32 type) diff --git a/src/map/IMG/style.h b/src/map/IMG/style.h index f99f1d84..ea058fd6 100644 --- a/src/map/IMG/style.h +++ b/src/map/IMG/style.h @@ -106,6 +106,7 @@ public: static bool isContourLine(quint32 type); static bool isSpot(quint32 type); static bool isSummit(quint32 type); + static bool isMajorRoad(quint32 type); static POIClass poiClass(quint32 type); private: diff --git a/src/map/IMG/textpointitem.cpp b/src/map/IMG/textpointitem.cpp index 59e6606c..068ccb38 100644 --- a/src/map/IMG/textpointitem.cpp +++ b/src/map/IMG/textpointitem.cpp @@ -7,29 +7,48 @@ #define FLAGS (Qt::AlignCenter | Qt::TextWordWrap | Qt::TextDontClip) #define MAX_TEXT_WIDTH 8 +#define MIN_BOX_WIDTH 2 + + +static void expand(QRect &rect, int width) +{ + rect.adjust(-(width/2 - rect.width()/2), 0, width/2 - rect.width()/2, 0); +} TextPointItem::TextPointItem(const QPoint &point, const QString *text, - const QFont *font, const QImage *img, const QColor *color) - : _text(text), _font(font), _img(img), _color(color) + const QFont *font, const QImage *img, const QColor *color, + const QColor *bgColor) : _text(text), _font(font), _img(img), _color(color), + _bgColor(bgColor) { - QRect iconRect; - if (text) { QFontMetrics fm(*font); int limit = font->pixelSize() * MAX_TEXT_WIDTH; _textRect = fm.boundingRect(QRect(0, 0, limit, 0), FLAGS, *text); _textRect.adjust(0, 0, 1, 1); } - if (img) { - iconRect = QRect(QPoint(point.x() - img->width()/2, point.y() - - img->height()/2), img->size()); - _textRect.moveTopLeft(QPoint(point.x() + img->width(), point.y() + + if (_bgColor && _textRect.width() < font->pixelSize() * MIN_BOX_WIDTH) + expand(_textRect, font->pixelSize() * MIN_BOX_WIDTH); + + setPos(point); +} + +void TextPointItem::setPos(const QPoint &point) +{ + QPainterPath shape; + QRect iconRect; + + if (_img) { + iconRect = QRect(QPoint(point.x() - _img->width()/2, point.y() + - _img->height()/2), _img->size()); + _textRect.moveTopLeft(QPoint(point.x() + _img->width(), point.y() - _textRect.height()/2)); } else _textRect.moveCenter(point); _rect = _textRect | iconRect; - _shape.addRect(_rect); + shape.addRect(_rect); + _shape = shape; } void TextPointItem::paint(QPainter *painter) const @@ -54,6 +73,13 @@ void TextPointItem::paint(QPainter *painter) const painter->drawImage(_textRect.x() - 1, _textRect.y(), img); painter->drawImage(_textRect.x() + 1, _textRect.y(), img); + + if (_bgColor) { + painter->setPen(*_color); + painter->setBrush(*_bgColor); + painter->drawRect(_textRect); + painter->setBrush(Qt::NoBrush); + } if (_color) { painter->setFont(*_font); painter->setPen(*_color); diff --git a/src/map/IMG/textpointitem.h b/src/map/IMG/textpointitem.h index f4b710d1..c4190a7d 100644 --- a/src/map/IMG/textpointitem.h +++ b/src/map/IMG/textpointitem.h @@ -16,7 +16,7 @@ class TextPointItem : public TextItem public: TextPointItem() : _text(0), _font(0), _img(0) {} TextPointItem(const QPoint &point, const QString *text, const QFont *font, - const QImage *img, const QColor *color); + const QImage *img, const QColor *color, const QColor *bgColor = 0); bool isValid() const {return !_rect.isEmpty();} @@ -24,11 +24,13 @@ public: QPainterPath shape() const {return _shape;} void paint(QPainter *painter) const; + void setPos(const QPoint &point); + private: const QString *_text; const QFont *_font; const QImage *_img; - const QColor *_color; + const QColor *_color, *_bgColor; QRect _rect, _textRect; QPainterPath _shape; }; diff --git a/src/map/imgmap.cpp b/src/map/imgmap.cpp index 06e312c1..8653a332 100644 --- a/src/map/imgmap.cpp +++ b/src/map/imgmap.cpp @@ -73,12 +73,18 @@ private: QList _points; }; -static void convertUnits(QString &str) + +static QColor shieldColor(Qt::white); +static QColor shieldBgColor1("#dd3e3e"); +static QColor shieldBgColor2("#379947"); +static QColor shieldBgColor3("#4a7fc1"); + + +static QString convertUnits(const QString &str) { bool ok; int number = str.toInt(&ok); - if (ok) - str = QString::number(qRound(number * 0.3048)); + return ok ? QString::number(qRound(number * 0.3048)) : str; } static int minPOIZoom(Style::POIClass cl) @@ -123,6 +129,40 @@ FONT(normalFont, NORMAL_FONT_SIZE) FONT(smallFont, SMALL_FONT_SIZE) FONT(poiFont, POI_FONT_SIZE) +static const QColor *shieldBgColor(Label::Shield::Type type) +{ + switch (type) { + case Label::Shield::USInterstate: + case Label::Shield::Hbox: + return &shieldBgColor1; + case Label::Shield::USShield: + case Label::Shield::Box: + return &shieldBgColor2; + case Label::Shield::USRound: + case Label::Shield::Oval: + return &shieldBgColor3; + default: + return 0; + } +} + +static int minShieldZoom(Label::Shield::Type type) +{ + switch (type) { + case Label::Shield::USInterstate: + case Label::Shield::Hbox: + return 18; + case Label::Shield::USShield: + case Label::Shield::Box: + return 19; + case Label::Shield::USRound: + case Label::Shield::Oval: + return 20; + default: + return INT_MAX; + } +} + IMGMap::IMGMap(const QString &fileName, QObject *parent) : Map(parent), _fileName(fileName), _img(fileName), @@ -278,6 +318,8 @@ void IMGMap::processPolygons(QList &polygons) void IMGMap::processLines(QList &lines, const QPoint &tile, QList &textItems) { + QRect tileRect(tile, QSize(TILE_SIZE, TILE_SIZE)); + qStableSort(lines); for (int i = 0; i < lines.size(); i++) { @@ -288,41 +330,87 @@ void IMGMap::processLines(QList &lines, const QPoint &tile, } } - if (_zoom < LINE_TEXT_MIN_ZOOM) - return; + if (_zoom >= LINE_TEXT_MIN_ZOOM) { + for (int i = 0; i < lines.size(); i++) { + IMG::Poly &poly = lines[i]; + const Style::Line &style = _img.style().line(poly.type); - for (int i = 0; i < lines.size(); i++) { - IMG::Poly &poly = lines[i]; - const Style::Line &style = _img.style().line(poly.type); + if (style.img().isNull() && style.foreground() == Qt::NoPen) + continue; + if (poly.label.text().isEmpty() + || style.textFontSize() == Style::None) + continue; - if (style.img().isNull() && style.foreground() == Qt::NoPen) - continue; - if (poly.label.isEmpty() || style.textFontSize() == Style::None) - continue; + if (Style::isContourLine(poly.type)) + poly.label.setText(convertUnits(poly.label.text())); - if (Style::isContourLine(poly.type)) - convertUnits(poly.label); + const QFont *font; + switch (style.textFontSize()) { + case Style::Large: + font = largeFont(); + break; + case Style::Small: + font = smallFont(); + break; + default: + font = normalFont(); + } + const QColor *color = style.textColor().isValid() + ? &style.textColor() : 0; - const QFont *font; - switch (style.textFontSize()) { - case Style::Large: - font = largeFont(); - break; - case Style::Small: - font = smallFont(); - break; - default: - font = normalFont(); + TextPathItem *item = new TextPathItem(poly.points, + &poly.label.text(), tileRect, font, color); + if (item->isValid() && !item->collides(textItems)) + textItems.append(item); + else + delete item; } - const QColor *color = style.textColor().isValid() - ? &style.textColor() : 0; + } - TextPathItem *item = new TextPathItem(poly.points, &poly.label, - QRect(tile, QSize(TILE_SIZE, TILE_SIZE)), font, color); - if (item->isValid() && !item->collides(textItems)) - textItems.append(item); - else - delete item; + + for (int type = 1; type < 7; type++) { + if (minShieldZoom((Label::Shield::Type)type) > _zoom) + continue; + + QSet shields; + + for (int i = 0; i < lines.size(); i++) { + const IMG::Poly &poly = lines.at(i); + const Label::Shield &shield = poly.label.shield(); + if (shield.type() != type || !Style::isMajorRoad(poly.type)) + continue; + + if (poly.label.shield().isValid() && !shields.contains(shield)) { + bool valid = false; + int idx = poly.points.size()/2, inc = 0, sign = 1; + + TextPointItem *item = new TextPointItem( + poly.points.at(idx).toPoint(), &shield.text(), normalFont(), + 0, &shieldColor, shieldBgColor(shield.type())); + + while (1) { + if (!item->collides(textItems) + && tileRect.contains(item->boundingRect().toRect())) { + valid = true; + break; + } + inc++; + sign = (sign < 0) ? 1 : -1; + idx += inc * sign; + + if (!(idx >= 0 && idx < poly.points.size())) + break; + + item->setPos(poly.points.at(idx).toPoint()); + } + + if (valid) { + textItems.append(item); + shields.insert(shield); + } else + delete item; + } + } } } @@ -339,7 +427,8 @@ void IMGMap::processPoints(QList &points, if (point.poi && _zoom < minPOIZoom(Style::poiClass(point.type))) continue; - const QString *label = point.label.isEmpty() ? 0 : &(point.label); + const QString *label = point.label.text().isEmpty() + ? 0 : &(point.label.text()); const QImage *img = style.img().isNull() ? 0 : &style.img(); const QFont *font = 0; if (point.poi) { @@ -369,10 +458,10 @@ void IMGMap::processPoints(QList &points, continue; if (Style::isSpot(point.type)) - convertUnits(point.label); - if (Style::isSummit(point.type) && !point.label.isEmpty()) { - QStringList list = point.label.split(" "); - convertUnits(list.last()); + point.label.setText(convertUnits(point.label.text())); + if (Style::isSummit(point.type) && !point.label.text().isEmpty()) { + QStringList list = point.label.text().split(" "); + list.last() = convertUnits(list.last()); point.label = list.join(" "); }